Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)
Python 73.6%
HTML 13.2%
TypeScript 6%
JavaScript 4.5%
CSS 1.7%
Dockerfile 0.6%
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 : {"_author":"Simon-Pierre Boucher <contact@spboucher.ai>","_file":"frontend/src/icons.json","_desc":"Trac\u00e9s SVG des marques de plateformes (simple-icons v13, CC0) + glyphes maison (linkedin/site-web/autre)","instagram":"M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077","tiktok":"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z","youtube":"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z","twitch":"M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z","kick":"M1.333 0h8v5.333H12V2.667h2.667V0h8v8H20v2.667h-2.667v2.666H20V16h2.667v8h-8v-2.667H12v-2.666H9.333V24h-8Z","x":"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z","facebook":"M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z","snapchat":"M12.206.793c.99 0 4.347.276 5.93 3.821.529 1.193.403 3.219.299 4.847l-.003.06c-.012.18-.022.345-.03.51.075.045.203.09.401.09.3-.016.659-.12 1.033-.301.165-.088.344-.104.464-.104.182 0 .359.029.509.09.45.149.734.479.734.838.015.449-.39.839-1.213 1.168-.089.029-.209.075-.344.119-.45.135-1.139.36-1.333.81-.09.224-.061.524.12.868l.015.015c.06.136 1.526 3.475 4.791 4.014.255.044.435.27.42.509 0 .075-.015.149-.045.225-.24.569-1.273.988-3.146 1.271-.059.091-.12.375-.164.57-.029.179-.074.36-.134.553-.076.271-.27.405-.555.405h-.03c-.135 0-.313-.031-.538-.074-.36-.075-.765-.135-1.273-.135-.3 0-.599.015-.913.074-.6.104-1.123.464-1.723.884-.853.599-1.826 1.288-3.294 1.288-.06 0-.119-.015-.18-.015h-.149c-1.468 0-2.427-.675-3.279-1.288-.599-.42-1.107-.779-1.707-.884-.314-.045-.629-.074-.928-.074-.54 0-.958.089-1.272.149-.211.043-.391.074-.54.074-.374 0-.523-.224-.583-.42-.061-.192-.09-.389-.135-.567-.046-.181-.105-.494-.166-.57-1.918-.222-2.95-.642-3.189-1.226-.031-.063-.052-.15-.055-.225-.015-.243.165-.465.42-.509 3.264-.54 4.73-3.879 4.791-4.02l.016-.029c.18-.345.224-.645.119-.869-.195-.434-.884-.658-1.332-.809-.121-.029-.24-.074-.346-.119-1.107-.435-1.257-.93-1.197-1.273.09-.479.674-.793 1.168-.793.146 0 .27.029.383.074.42.194.789.3 1.104.3.234 0 .384-.06.465-.105l-.046-.569c-.098-1.626-.225-3.651.307-4.837C7.392 1.077 10.739.807 11.727.807l.419-.015h.06z","substack":"M22.539 8.242H1.46V5.406h21.08v2.836zM1.46 10.812V24L12 18.11 22.54 24V10.812H1.46zM22.54 0H1.46v2.836h21.08V0z","patreon":"M22.957 7.21c-.004-3.064-2.391-5.576-5.191-6.482-3.478-1.125-8.064-.962-11.384.604C2.357 3.231 1.093 7.391 1.046 11.54c-.039 3.411.302 12.396 5.369 12.46 3.765.047 4.326-4.804 6.068-7.141 1.24-1.662 2.836-2.132 4.801-2.618 3.376-.836 5.678-3.501 5.673-7.031Z","onlyfans":"M24 4.003h-4.015c-3.45 0-5.3.197-6.748 1.957a7.996 7.996 0 1 0 2.103 9.211c3.182-.231 5.39-2.134 6.085-5.173 0 0-2.399.585-4.43 0 4.018-.777 6.333-3.037 7.005-5.995zM5.61 11.999A2.391 2.391 0 0 1 9.28 9.97a2.966 2.966 0 0 1 2.998-2.528h.008c-.92 1.778-1.407 3.352-1.998 5.263A2.392 2.392 0 0 1 5.61 12Zm2.386-7.996a7.996 7.996 0 1 0 7.996 7.996 7.996 7.996 0 0 0-7.996-7.996Zm0 10.394A2.399 2.399 0 1 1 10.395 12a2.396 2.396 0 0 1-2.399 2.398Z","threads":"M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z","podcast":"M5.34 0A5.328 5.328 0 000 5.34v13.32A5.328 5.328 0 005.34 24h13.32A5.328 5.328 0 0024 18.66V5.34A5.328 5.328 0 0018.66 0zm6.525 2.568c2.336 0 4.448.902 6.056 2.587 1.224 1.272 1.912 2.619 2.264 4.392.12.59.12 2.2.007 2.864a8.506 8.506 0 01-3.24 5.296c-.608.46-2.096 1.261-2.336 1.261-.088 0-.096-.091-.056-.46.072-.592.144-.715.48-.856.536-.224 1.448-.874 2.008-1.435a7.644 7.644 0 002.008-3.536c.208-.824.184-2.656-.048-3.504-.728-2.696-2.928-4.792-5.624-5.352-.784-.16-2.208-.16-3 0-2.728.56-4.984 2.76-5.672 5.528-.184.752-.184 2.584 0 3.336.456 1.832 1.64 3.512 3.192 4.512.304.2.672.408.824.472.336.144.408.264.472.856.04.36.03.464-.056.464-.056 0-.464-.176-.896-.384l-.04-.03c-2.472-1.216-4.056-3.274-4.632-6.012-.144-.706-.168-2.392-.03-3.04.36-1.74 1.048-3.1 2.192-4.304 1.648-1.737 3.768-2.656 6.128-2.656zm.134 2.81c.409.004.803.04 1.106.106 2.784.62 4.76 3.408 4.376 6.174-.152 1.114-.536 2.03-1.216 2.88-.336.43-1.152 1.15-1.296 1.15-.023 0-.048-.272-.048-.603v-.605l.416-.496c1.568-1.878 1.456-4.502-.256-6.224-.664-.67-1.432-1.064-2.424-1.246-.64-.118-.776-.118-1.448-.008-1.02.167-1.81.562-2.512 1.256-1.72 1.704-1.832 4.342-.264 6.222l.413.496v.608c0 .336-.027.608-.06.608-.03 0-.264-.16-.512-.36l-.034-.011c-.832-.664-1.568-1.842-1.872-2.997-.184-.698-.184-2.024.008-2.72.504-1.878 1.888-3.335 3.808-4.019.41-.145 1.133-.22 1.814-.211zm-.13 2.99c.31 0 .62.06.844.178.488.253.888.745 1.04 1.259.464 1.578-1.208 2.96-2.72 2.254h-.015c-.712-.331-1.096-.956-1.104-1.77 0-.733.408-1.371 1.112-1.745.224-.117.534-.176.844-.176zm-.011 4.728c.988-.004 1.706.349 1.97.97.198.464.124 1.932-.218 4.302-.232 1.656-.36 2.074-.68 2.356-.44.39-1.064.498-1.656.288h-.003c-.716-.257-.87-.605-1.164-2.644-.341-2.37-.416-3.838-.218-4.302.262-.616.974-.966 1.97-.97z","linktree":"m13.73635 5.85251 4.00467-4.11665 2.3248 2.3808-4.20064 4.00466h5.9085v3.30473h-5.9365l4.22865 4.10766-2.3248 2.3338L12.0005 12.099l-5.74052 5.76852-2.3248-2.3248 4.22864-4.10766h-5.9375V8.12132h5.9085L3.93417 4.11666l2.3248-2.3808 4.00468 4.11665V0h3.4727zm-3.4727 10.30614h3.4727V24h-3.4727z","linkedin":"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0z","site-web":"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm7.938 9h-3.243a15.6 15.6 0 00-1.4-4.653A8.03 8.03 0 0119.938 9zM12 2.05c.9 1.2 1.9 3.2 2.5 6.95h-5C10.1 5.25 11.1 3.25 12 2.05zM4.062 15A8.06 8.06 0 013.75 12c0-1.05.15-2.05.312-3h3.55A25 25 0 007.5 12c0 1.05.05 2.05.113 3zm.643 2h3.243c.35 1.8.85 3.35 1.4 4.653A8.03 8.03 0 014.705 17zm3.243-10H4.705a8.03 8.03 0 014.643-4.653A15.6 15.6 0 007.948 7zM12 21.95c-.9-1.2-1.9-3.2-2.5-6.95h5c-.6 3.75-1.6 5.75-2.5 6.95zM14.787 13H9.213A23 23 0 019.1 12c0-1.05.037-2.05.113-3h5.574c.076.95.113 1.95.113 3s-.037 2.05-.113 3zm.508 8.653c.55-1.303 1.05-2.853 1.4-4.653h3.243a8.03 8.03 0 01-4.643 4.653zM16.388 15c.062-.95.112-1.95.112-3s-.05-2.05-.112-3h3.55c.162.95.312 1.95.312 3s-.15 2.05-.312 3z","autre":"M10.59 13.41a1 1 0 010-1.41l2.83-2.83a3 3 0 114.24 4.24l-1.41 1.42a1 1 0 11-1.42-1.42l1.42-1.41a1 1 0 10-1.42-1.42L12 13.41a1 1 0 01-1.41 0zm2.82-2.82a1 1 0 010 1.41l-2.82 2.83a3 3 0 11-4.25-4.24l1.42-1.42a1 1 0 111.41 1.42L7.76 12a1 1 0 101.41 1.41L12 10.59a1 1 0 011.41 0z","spotify":"M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z","discord":"M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"} (tracés SVG simple-icons, CC0),6 /* -----------------------------------------------------------------------------7 Auteur : Simon-Pierre Boucher — contact@spboucher.ai8 Fichier : ka-ui/tokens.css — SOURCE CANONIQUE (repo ka-ui sur spbgit)9 Desc. : Design system commun GROUPE KA « éditorial sharp » — tokens + socle10 de composants partagés par les 12 plateformes. Chaque site importe ce11 fichier PUIS surcharge uniquement son accent (voir accents.css).1213 · Typo : display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono14 · Palette: papier #f5f3ee · encre #141814 · vert profond #1c5c41 + ACCENT du site15 · Signature : bordures encre 1,5–2 px + ombres décalées dures, grain de film,16 surlignés d'accent inclinés (néo-brutalisme raffiné)17 · Breakpoints communs : 360 / 768 / 1024 / 1440 px (mobile-first)18 · Zones tactiles ≥ 44×44 px, safe-areas iOS, typographie fluide (clamp)19----------------------------------------------------------------------------- */2021:root {22 /* ---- Palette fixe Groupe KA (identique sur les 12 sites) ---- */23 --paper: #f5f3ee;24 --surface: #ffffff;25 --surface-2: #faf9f5;26 --ink: #141814;27 --ink-2: #4d5551;28 --ink-3: #8b928c;29 --line: rgba(20, 24, 20, 0.14);30 --line-strong: rgba(20, 24, 20, 0.85);31 --green: #1c5c41;32 --green-deep: #123f2e;33 --amber: #e8a33d;34 --amber-soft: #fdf3e2;35 --danger: #b3423a;36 --danger-soft: #fbe9e7;3738 /* ---- ACCENT du site — SEULES variables surchargées par marque ---- */39 --accent: #d9f26b; /* défaut : lime Groupe KA */40 --accent-soft: #f0f9d2;41 --accent-deep: #123f2e;42 --on-accent: var(--ink); /* couleur du texte posé SUR l'accent */4344 /* alias rétro-compatibles (les satellites historiques utilisent --lime) */45 --lime: var(--accent);46 --lime-soft: var(--accent-soft);4748 /* ---- Géométrie sharp ---- */49 --r-card: 10px;50 --r-ctl: 6px;51 --r-pill: 999px;5253 /* ---- Ombres décalées — la signature du groupe ---- */54 --shadow-flat: 0 1px 2px rgba(20, 24, 20, 0.05);55 --shadow-off: 6px 6px 0 var(--ink);56 --shadow-off-soft: 8px 8px 0 rgba(20, 24, 20, 0.08);57 --shadow-off-mid: 4px 4px 0 rgba(20, 24, 20, 0.18);5859 /* ---- Typographie ---- */60 --font-display: "Space Grotesk", system-ui, sans-serif;61 --font-body: "Inter", system-ui, sans-serif;62 --font-mono: "JetBrains Mono", ui-monospace, monospace;6364 /* Échelle fluide (mobile-first, clamp) */65 --fs-h1: clamp(30px, 3.2vw + 18px, 54px);66 --fs-h2: clamp(23px, 1.8vw + 14px, 34px);67 --fs-h3: clamp(17px, 0.9vw + 12px, 22px);68 --fs-body: 15px;69 --fs-small: 13px;7071 /* Espacements */72 --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;73 --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;7475 /* Cible tactile minimale */76 --touch: 44px;7778 /* ---- Échelle z-index COMMUNE (obligatoire — aucune valeur arbitraire) ----79 grain de film body::before = 9999 (pointer-events:none, toujours au-dessus,80 purement visuel). Tout composant interactif se place sous 9999 : */81 --z-content: 1; /* contenu positionné ordinaire */82 --z-sticky: 300; /* éléments sticky de contenu (sous-nav…) */83 --z-header: 500; /* header du site */84 --z-bottombar: 600; /* tab bar / barre d'action basse */85 --z-dropdown: 700; /* menus déroulants (au-dessus des barres) */86 --z-overlay: 800; /* voile sous modale/panneau */87 --z-modal: 900; /* modales, panneaux de filtres, galeries */88 --z-toast: 950; /* notifications */8990 /* ---- Hauteur de viewport DYNAMIQUE (barre du navigateur mobile qui se91 replie au scroll) : toujours var(--vh100), JAMAIS 100vh en dur. ---- */92 --vh100: 100vh;93}94@supports (height: 100dvh) { :root { --vh100: 100dvh; } }9596/* ---- Socle ---- */97* { box-sizing: border-box; }98/* overflow-x: clip sur html ET body — sur WebKit iOS, clip posé sur body seul99 ne bloque PAS le défilement latéral du viewport (quirk de propagation) ;100 clip (≠ hidden) ne casse pas position:sticky. */101html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; overflow-x: clip; }102body {103 margin: 0;104 background: var(--paper);105 color: var(--ink);106 font-family: var(--font-body);107 font-size: var(--fs-body);108 line-height: 1.55;109 -webkit-font-smoothing: antialiased;110 padding-bottom: env(safe-area-inset-bottom);111 overflow-x: clip; /* jamais de débordement horizontal */112}113img, svg, video { max-width: 100%; height: auto; display: block; }114h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0 0 0.5em; }115h1 { font-size: var(--fs-h1); line-height: 1.06; }116h2 { font-size: var(--fs-h2); line-height: 1.15; }117h3 { font-size: var(--fs-h3); line-height: 1.25; }118a { color: inherit; }119::selection { background: var(--accent); color: var(--on-accent); }120:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }121122/* Grain de film pleine page — ⚠️ jamais de z-index sur body > * (position:123 relative seulement), sinon les utilitaires z-* sont écrasés. */124body::before {125 content: "";126 position: fixed; inset: 0; pointer-events: none; opacity: 0.35; z-index: 9999;127 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.06'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");128}129body > * { position: relative; }130131/* ---- Conteneur commun ---- */132.container { max-width: 1152px; margin: 0 auto; padding: 0 var(--sp-4); }133@media (min-width: 768px) { .container { padding: 0 var(--sp-5); } }134135/* ---- Micro-typographie signature ---- */136.kicker {137 display: inline-flex; align-items: center; gap: 10px;138 font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;139 text-transform: uppercase; letter-spacing: 0.12em; color: var(--green);140}141.kicker::before { content: ""; width: 22px; height: 2px; background: var(--green); }142.klabel {143 font-family: var(--font-mono); font-size: 10px; font-weight: 700;144 text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3);145}146.hl {147 display: inline-block; background: var(--accent); color: var(--on-accent);148 border-radius: 8px; padding: 0 10px 2px; transform: rotate(-1deg);149}150.outline-txt { color: transparent; -webkit-text-stroke: 2px var(--ink); }151152/* ---- Boutons (cible ≥ 44 px) ---- */153.btn {154 display: inline-flex; align-items: center; justify-content: center; gap: 8px;155 min-height: var(--touch); padding: 10px 18px;156 font-family: var(--font-display); font-weight: 700; font-size: 14px;157 border: 1.5px solid var(--ink); border-radius: var(--r-ctl);158 background: var(--surface); color: var(--ink);159 cursor: pointer; text-decoration: none;160 transition: transform 0.15s, box-shadow 0.15s, background 0.15s, color 0.15s;161}162.btn:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off-mid); }163.btn:active { transform: translate(0, 0); box-shadow: none; }164.btn:disabled { opacity: 0.45; pointer-events: none; }165.btn-primary { background: var(--ink); color: var(--accent); }166.btn-primary:hover { background: var(--accent-deep); }167.btn-accent { background: var(--accent); color: var(--on-accent); }168.btn-ghost { background: transparent; border-color: var(--line); }169.btn-ghost:hover { border-color: var(--ink); }170171/* ---- Cartes ---- */172.card {173 background: var(--surface); border: 1.5px solid var(--ink);174 border-radius: var(--r-card); box-shadow: var(--shadow-off-soft);175 overflow: hidden;176}177.card-hover { transition: transform 0.15s, box-shadow 0.15s; }178.card-hover:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); }179180/* ---- Chips / badges ---- */181.chip {182 display: inline-flex; align-items: center; gap: 6px;183 padding: 4px 11px; font-family: var(--font-mono); font-size: 11px; font-weight: 700;184 text-transform: uppercase; letter-spacing: 0.06em;185 border: 1.5px solid var(--ink); border-radius: var(--r-pill);186 background: var(--surface); color: var(--ink);187}188.chip-accent { background: var(--accent); color: var(--on-accent); }189.chip-soft { background: var(--accent-soft); border-color: var(--line); }190191/* ---- Champs ---- */192.input, .select, .textarea {193 width: 100%; min-height: var(--touch); padding: 10px 14px;194 font: inherit; color: var(--ink); background: var(--surface);195 border: 1.5px solid var(--ink); border-radius: var(--r-ctl);196}197.input:focus, .select:focus, .textarea:focus {198 outline: none; box-shadow: 3px 3px 0 var(--accent); border-color: var(--ink);199}200.input::placeholder { color: var(--ink-3); }201202/* ---- Badge « Un service Groupe KA » (header, cliquable → hub) ---- */203.gk-badge {204 display: inline-flex; align-items: center; gap: 7px;205 min-height: 30px; padding: 3px 10px 4px;206 font-family: var(--font-mono); font-size: 10px; font-weight: 700;207 letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none;208 border: 1.5px solid var(--ink); border-radius: var(--r-pill);209 background: var(--surface); color: var(--ink-2); white-space: nowrap;210}211.gk-badge b {212 font-family: var(--font-display); font-size: 12px; letter-spacing: -0.02em;213 text-transform: none; color: var(--ink);214}215.gk-badge b .ka {216 display: inline-block; background: var(--ink); color: var(--accent);217 border-radius: 5px; padding: 0 5px 1px; margin-left: 3px; transform: rotate(-2deg);218}219.gk-badge:hover .ka { transform: rotate(0); }220.gk-badge--dark { background: transparent; border-color: rgba(245,243,238,0.4); color: rgba(245,243,238,0.75); }221.gk-badge--dark b { color: var(--paper); }222223/* ---- Footer commun (fond encre — voir react/KaFooter.tsx) ---- */224.ka-footer { margin-top: var(--sp-8); background: var(--ink); padding: 44px 0; font-size: 13px; color: rgba(245,243,238,0.75); }225.ka-footer a { text-decoration: none; }226.ka-footer .wordmark { font-family: var(--font-display); font-weight: 700; font-size: 30px; letter-spacing: -0.04em; color: var(--paper); text-decoration: none; }227.ka-footer .wordmark .ka { color: var(--accent); }228.ka-footer .desc { max-width: 640px; margin-top: var(--sp-4); }229.ka-footer .notice { max-width: 640px; margin-top: var(--sp-4); border-left: 2px solid var(--accent); padding-left: var(--sp-4); }230.ka-footer .notice b { color: var(--paper); }231.ka-footer .sites { list-style: none; display: flex; flex-wrap: wrap; gap: 10px 26px; margin: 26px 0 0; padding: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; }232.ka-footer .sites a { color: rgba(245,243,238,0.65); }233.ka-footer .sites a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }234.ka-footer .contacts { display: grid; gap: 14px 32px; border-top: 1px solid rgba(245,243,238,0.15); margin-top: 30px; padding-top: 26px; }235@media (min-width: 768px) { .ka-footer .contacts { grid-template-columns: repeat(3, 1fr); } }236.ka-footer .contacts a { font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: rgba(245,243,238,0.85); }237.ka-footer .contacts a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }238.ka-footer .contacts span { display: block; margin-top: 3px; font-size: 11px; color: rgba(245,243,238,0.5); }239.ka-footer .legal { margin-top: 30px; font-family: var(--font-mono); font-size: 11px; color: rgba(245,243,238,0.45); }240.ka-footer .legal a { color: inherit; }241.ka-footer .legal a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }242243/* ---- Tableaux → cartes empilées sous 768 px ---- */244.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }245@media (max-width: 767px) {246 .tbl-stack thead { display: none; }247 .tbl-stack tr { display: block; border: 1.5px solid var(--ink); border-radius: var(--r-card); margin-bottom: var(--sp-3); background: var(--surface); }248 .tbl-stack td { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 8px 12px; border: 0; }249 .tbl-stack td::before { content: attr(data-label); font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); }250}251252/* ---- Utilitaires responsive ---- */253.only-mobile { display: initial; } .only-desktop { display: none; }254@media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } }255256/* =============================================================================257 SOCLE MOBILE OBLIGATOIRE (2026-08-19) — règles communes aux 13 sites.258 RÈGLES pour tout composant futur :259 · TAP, jamais :hover, pour ouvrir un menu (le survol n'existe pas au doigt) ;260 le :hover ne sert qu'aux effets décoratifs, sous @media (hover: hover).261 · var(--vh100) (dvh) et JAMAIS 100vh pour tout élément calé sur le viewport.262 · position:fixed exige qu'AUCUN ancêtre n'ait transform/filter/perspective/263 will-change/backdrop-filter — sinon monter l'élément à la racine (body).264 · Barre basse fixe = .ka-bottombar + classe has-bottombar sur <body>.265 · Menu/panneau ouvert = .ka-scroll-lock sur <html> (scroll arrière-plan gelé).266 · z-index : uniquement l'échelle --z-* ci-dessus.267 · Zones tactiles ≥ var(--touch) (44 px), champs ≥ 16 px (zoom iOS).268 ========================================================================== */269270/* Anti-zoom iOS : au doigt, aucun champ sous 16 px (le focus d'un champ <16 px271 déclenche un zoom automatique de page sur Safari iOS). !important assumé :272 c'est un filet d'accessibilité, un champ plus petit casse le zoom quoi273 qu'il arrive — ne s'applique qu'aux écrans tactiles. */274@media (hover: none) and (pointer: coarse) {275 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),276 select, textarea { font-size: max(16px, 1em) !important; }277}278279/* Zone tactile du badge Groupe KA étendue à ≥44 px au doigt, sans changer280 son rendu (débord de la zone cliquable via pseudo-élément). */281@media (pointer: coarse) {282 .gk-badge { position: relative; }283 .gk-badge::after { content: ""; position: absolute; inset: -8px; }284}285286/* Hauteurs viewport dynamiques */287.h-viewport { height: 100vh; height: 100dvh; }288.min-h-viewport { min-height: 100vh; min-height: 100dvh; }289290/* Barre fixe basse fiable : safe-area iPhone incluse, immunisée contre les291 transforms accidentels, et compensation d'espace via body.has-bottombar. */292.ka-bottombar {293 position: fixed; left: 0; right: 0; bottom: 0;294 z-index: var(--z-bottombar);295 padding-bottom: env(safe-area-inset-bottom, 0px);296 background: var(--surface);297 border-top: 1.5px solid var(--ink);298 transform: none !important; filter: none !important;299}300body.has-bottombar { padding-bottom: calc(var(--bottombar-h, 64px) + env(safe-area-inset-bottom, 0px)); }301302/* Verrou de scroll d'arrière-plan (menu mobile, modale, panneau de filtres303 ouverts) : ajouter/retirer .ka-scroll-lock sur <html>. */304html.ka-scroll-lock, html.ka-scroll-lock body { overflow: hidden !important; overscroll-behavior: none; }305306/* Menu déroulant de référence : au-dessus de tout contenu (cartes Mapbox307 incluses), défilement interne avec élan, jamais plus haut que l'écran. */308.ka-menu {309 position: absolute; z-index: var(--z-dropdown);310 min-width: 180px;311 max-height: min(60vh, 420px); max-height: min(60dvh, 420px);312 overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;313 background: var(--surface); border: 1.5px solid var(--ink);314 border-radius: var(--r-card); box-shadow: var(--shadow-off-mid);315}316.ka-menu a, .ka-menu button, .ka-menu label, .ka-menu [role="menuitem"], .ka-menu [role="option"] {317 display: flex; align-items: center; min-height: var(--touch);318 padding: 10px 14px; width: 100%; text-decoration: none;319}320/* Voile plein écran sous une modale / un panneau */321.ka-overlay {322 position: fixed; inset: 0; z-index: var(--z-overlay);323 background: rgba(20, 24, 20, 0.45);324}325 (ka/tokens.css — design system commun Groupe KA,326 importé EN PREMIER) et {"org":{"name":"Groupe KA","legalName":"Groupe KA — Simon-Pierre Boucher","tagline":"Holding québécois d'agrégateurs de produits et services entièrement automatisés.","disclaimer":"Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.","copyrightHolder":"Groupe KA — Simon-Pierre Boucher"},"hub":{"url":"https://www.groupe-ka.com","loginPath":"/connexion","signupNote":"La création de compte KA ID se fait sur le hub groupe-ka.com ; chaque site délègue sa connexion via /api/auth/ka/login."},"contacts":[{"email":"contact@groupe-ka.com","role":"Projets, partenariats & données"},{"email":"info@groupe-ka.com","role":"Médias & questions générales"},{"email":"admin@groupe-ka.com","role":"Légal, vie privée & Loi 25"}],"legal":[{"label":"Conditions d'utilisation","href":"https://www.groupe-ka.com/conditions"},{"label":"Politique de confidentialité","href":"https://www.groupe-ka.com/confidentialite"},{"label":"Protection des renseignements personnels (Loi 25)","href":"https://www.groupe-ka.com/loi-25"},{"label":"Transparence des robots d'indexation","href":"https://www.groupe-ka.com/bots"}],"sites":[{"id":"groupe-ka","wordmark":"Groupe KA","domain":"www.groupe-ka.com","accent":"#d9f26b","accentSoft":"#f0f9d2","accentDeep":"#123f2e","onAccent":"#141814","tagline":"Le portail de l'écosystème ·Ka"},{"id":"trouve-ka","wordmark":"Trouve·Ka","domain":"www.trouve-ka.com","accent":"#1c7ed6","accentSoft":"#e7f2fd","accentDeep":"#14508f","onAccent":"#ffffff","tagline":"Le moteur de recherche du web québécois"},{"id":"lou-ka","wordmark":"Lou·Ka","domain":"www.lou-ka.com","accent":"#ff6a00","accentSoft":"#fff1e6","accentDeep":"#cc5500","onAccent":"#ffffff","tagline":"Tous les logements à louer"},{"id":"immo-ka","wordmark":"Immo·Ka","domain":"www.immo-ka.com","accent":"#e23744","accentSoft":"#fbe0e2","accentDeep":"#a8232e","onAccent":"#ffffff","tagline":"Toutes les propriétés à vendre"},{"id":"vrai-prix","wordmark":"Vrai-Prix","domain":"www.vrai-prix.com","accent":"#ff5148","accentSoft":"#ffe3e0","accentDeep":"#9e2a25","onAccent":"#ffffff","tagline":"La valeur réelle de chaque propriété"},{"id":"auto-ka","wordmark":"Auto·Ka","domain":"www.auto-ka.com","accent":"#ff5a2a","accentSoft":"#ffe8de","accentDeep":"#cc3f16","onAccent":"#ffffff","tagline":"Les voitures usagées du Québec"},{"id":"fabri-ka","wordmark":"Fabri·Ka","domain":"www.fabri-ka.com","accent":"#c4532e","accentSoft":"#f7e3da","accentDeep":"#a94525","onAccent":"#ffffff","tagline":"Les produits fabriqués au Québec"},{"id":"food-ka","wordmark":"Food·Ka","domain":"www.food-ka.com","accent":"#1f9d55","accentSoft":"#e2f5ea","accentDeep":"#157a40","onAccent":"#ffffff","tagline":"Les prix d'épicerie, suivis à la source"},{"id":"resto-ka","wordmark":"Resto·Ka","domain":"www.resto-ka.com","accent":"#f08c00","accentSoft":"#fdeed7","accentDeep":"#b96a00","onAccent":"#141814","tagline":"Chaque resto, chaque plat, chaque prix"},{"id":"sorti-ka","wordmark":"Sorti·Ka","domain":"www.sorti-ka.com","accent":"#d6336c","accentSoft":"#fbe0eb","accentDeep":"#a12551","onAccent":"#ffffff","tagline":"Toutes les sorties, dans les 17 régions"},{"id":"crea-ka","wordmark":"Créa·Ka","domain":"www.crea-ka.com","accent":"#7048e8","accentSoft":"#ece5fc","accentDeep":"#5433b8","onAccent":"#ffffff","tagline":"Les créateurs d'ici, tous leurs liens"},{"id":"api-ka","wordmark":"API·Ka","domain":"www.api-ka.com","accent":"#3b5bdb","accentSoft":"#e4eafb","accentDeep":"#2b44a8","onAccent":"#ffffff","tagline":"La donnée de l'écosystème, par API"},{"id":"job-ka","wordmark":"Job·Ka","domain":"www.job-ka.com","accent":"#0c8599","accentSoft":"#def0f4","accentDeep":"#095c6b","onAccent":"#ffffff","tagline":"Tous les emplois des employeurs québécois"},{"id":"ka-stats","wordmark":"Ka·Stats","domain":"www.ka-stats.com","accent":"#095797","accentSoft":"#e2edf6","accentDeep":"#063a63","onAccent":"#ffffff","tagline":"L'explorateur de statistiques du Québec"}],"extraFooterLinks":[{"label":"ValoPlex","href":"https://www.valoplex.com"},{"label":"Ka2","href":"https://www.ka2.bot"},{"label":"Ka4","href":"https://www.ka4.bot"},{"label":"Ka6","href":"https://www.ka6.bot"}]} (ka/ecosystem.json — contacts,327 sites, mentions légales du groupe : footer commun + page /contact).328 Système « éditorial sharp » du Groupe KA (papier grainé #f5f3ee,329 encre #141814, bordures encre + ombres décalées dures, Space Grotesk330 / Inter / JetBrains Mono) — accent attribué à Créa-Ka : VIOLET331 (--accent:#7048e8). Patrons mobiles calqués sur Lou-Ka (tab bar332 4 onglets, filtres défilants, anti-zoom iOS).333 Routes : / · /createur/{id} · /stats · /favoris · /retrait · /compte · /contact334-->335<!DOCTYPE html>336<html lang="fr-CA">337<head>338<meta charset="utf-8">339<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">340<meta name="theme-color" content="#141814">341<link rel="preconnect" href="https://fonts.googleapis.com">342<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>343<link rel="preconnect" href="https://unavatar.io">344<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">345<link rel="icon" type="image/svg+xml" href="/favicon.svg">346<link rel="apple-touch-icon" href="/apple-touch-icon.png">347<!--KA:SEO-->348<title>Créa-Ka — Annuaire des créateurs de contenu québécois</title>349<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.">350<link rel="canonical" href="https://www.crea-ka.com/">351<meta property="og:site_name" content="Créa-Ka">352<meta property="og:locale" content="fr_CA">353<meta property="og:type" content="website">354<meta property="og:title" content="Créa-Ka — Annuaire des créateurs de contenu québécois">355<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.">356<meta property="og:url" content="https://www.crea-ka.com/">357<meta property="og:image" content="https://www.crea-ka.com/og.png">358<meta property="og:image:width" content="1200">359<meta property="og:image:height" content="630">360<meta name="twitter:card" content="summary_large_image">361<meta name="twitter:title" content="Créa-Ka — Annuaire des créateurs de contenu québécois">362<meta name="twitter: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.">363<meta name="twitter:image" content="https://www.crea-ka.com/og.png">364<script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"WebSite","@id":"https://www.crea-ka.com/#website","url":"https://www.crea-ka.com/","name":"Créa-Ka","description":"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.","inLanguage":"fr-CA","publisher":{"@id":"https://www.crea-ka.com/#org"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://www.crea-ka.com/?q={search_term_string}"},"query-input":"required name=search_term_string"}},{"@type":"Organization","@id":"https://www.crea-ka.com/#org","name":"Créa-Ka","url":"https://www.crea-ka.com/","logo":"https://www.crea-ka.com/og.png","email":"contact@groupe-ka.com","parentOrganization":{"@type":"Organization","name":"Groupe KA","url":"https://www.groupe-ka.com"}}]}</script>365<!--/KA:SEO-->366<style>367/* ka/tokens.css — design system commun Groupe KA, importé EN PREMIER368 (injecté au build par scripts/build_frontend.py) */369/* -----------------------------------------------------------------------------370 Auteur : Simon-Pierre Boucher — contact@spboucher.ai371 Fichier : ka-ui/tokens.css — SOURCE CANONIQUE (repo ka-ui sur spbgit)372 Desc. : Design system commun GROUPE KA « éditorial sharp » — tokens + socle373 de composants partagés par les 12 plateformes. Chaque site importe ce374 fichier PUIS surcharge uniquement son accent (voir accents.css).375376 · Typo : display Space Grotesk / texte Inter / micro-étiquettes JetBrains Mono377 · Palette: papier #f5f3ee · encre #141814 · vert profond #1c5c41 + ACCENT du site378 · Signature : bordures encre 1,5–2 px + ombres décalées dures, grain de film,379 surlignés d'accent inclinés (néo-brutalisme raffiné)380 · Breakpoints communs : 360 / 768 / 1024 / 1440 px (mobile-first)381 · Zones tactiles ≥ 44×44 px, safe-areas iOS, typographie fluide (clamp)382----------------------------------------------------------------------------- */383384:root {385 /* ---- Palette fixe Groupe KA (identique sur les 12 sites) ---- */386 --paper: #f5f3ee;387 --surface: #ffffff;388 --surface-2: #faf9f5;389 --ink: #141814;390 --ink-2: #4d5551;391 --ink-3: #8b928c;392 --line: rgba(20, 24, 20, 0.14);393 --line-strong: rgba(20, 24, 20, 0.85);394 --green: #1c5c41;395 --green-deep: #123f2e;396 --amber: #e8a33d;397 --amber-soft: #fdf3e2;398 --danger: #b3423a;399 --danger-soft: #fbe9e7;400401 /* ---- ACCENT du site — SEULES variables surchargées par marque ---- */402 --accent: #d9f26b; /* défaut : lime Groupe KA */403 --accent-soft: #f0f9d2;404 --accent-deep: #123f2e;405 --on-accent: var(--ink); /* couleur du texte posé SUR l'accent */406407 /* alias rétro-compatibles (les satellites historiques utilisent --lime) */408 --lime: var(--accent);409 --lime-soft: var(--accent-soft);410411 /* ---- Géométrie sharp ---- */412 --r-card: 10px;413 --r-ctl: 6px;414 --r-pill: 999px;415416 /* ---- Ombres décalées — la signature du groupe ---- */417 --shadow-flat: 0 1px 2px rgba(20, 24, 20, 0.05);418 --shadow-off: 6px 6px 0 var(--ink);419 --shadow-off-soft: 8px 8px 0 rgba(20, 24, 20, 0.08);420 --shadow-off-mid: 4px 4px 0 rgba(20, 24, 20, 0.18);421422 /* ---- Typographie ---- */423 --font-display: "Space Grotesk", system-ui, sans-serif;424 --font-body: "Inter", system-ui, sans-serif;425 --font-mono: "JetBrains Mono", ui-monospace, monospace;426427 /* Échelle fluide (mobile-first, clamp) */428 --fs-h1: clamp(30px, 3.2vw + 18px, 54px);429 --fs-h2: clamp(23px, 1.8vw + 14px, 34px);430 --fs-h3: clamp(17px, 0.9vw + 12px, 22px);431 --fs-body: 15px;432 --fs-small: 13px;433434 /* Espacements */435 --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;436 --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;437438 /* Cible tactile minimale */439 --touch: 44px;440441 /* ---- Échelle z-index COMMUNE (obligatoire — aucune valeur arbitraire) ----442 grain de film body::before = 9999 (pointer-events:none, toujours au-dessus,443 purement visuel). Tout composant interactif se place sous 9999 : */444 --z-content: 1; /* contenu positionné ordinaire */445 --z-sticky: 300; /* éléments sticky de contenu (sous-nav…) */446 --z-header: 500; /* header du site */447 --z-bottombar: 600; /* tab bar / barre d'action basse */448 --z-dropdown: 700; /* menus déroulants (au-dessus des barres) */449 --z-overlay: 800; /* voile sous modale/panneau */450 --z-modal: 900; /* modales, panneaux de filtres, galeries */451 --z-toast: 950; /* notifications */452453 /* ---- Hauteur de viewport DYNAMIQUE (barre du navigateur mobile qui se454 replie au scroll) : toujours var(--vh100), JAMAIS 100vh en dur. ---- */455 --vh100: 100vh;456}457@supports (height: 100dvh) { :root { --vh100: 100dvh; } }458459/* ---- Socle ---- */460* { box-sizing: border-box; }461/* overflow-x: clip sur html ET body — sur WebKit iOS, clip posé sur body seul462 ne bloque PAS le défilement latéral du viewport (quirk de propagation) ;463 clip (≠ hidden) ne casse pas position:sticky. */464html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; overflow-x: clip; }465body {466 margin: 0;467 background: var(--paper);468 color: var(--ink);469 font-family: var(--font-body);470 font-size: var(--fs-body);471 line-height: 1.55;472 -webkit-font-smoothing: antialiased;473 padding-bottom: env(safe-area-inset-bottom);474 overflow-x: clip; /* jamais de débordement horizontal */475}476img, svg, video { max-width: 100%; height: auto; display: block; }477h1, h2, h3, h4 { font-family: var(--font-display); letter-spacing: -0.03em; margin: 0 0 0.5em; }478h1 { font-size: var(--fs-h1); line-height: 1.06; }479h2 { font-size: var(--fs-h2); line-height: 1.15; }480h3 { font-size: var(--fs-h3); line-height: 1.25; }481a { color: inherit; }482::selection { background: var(--accent); color: var(--on-accent); }483:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }484485/* Grain de film pleine page — ⚠️ jamais de z-index sur body > * (position:486 relative seulement), sinon les utilitaires z-* sont écrasés. */487body::before {488 content: "";489 position: fixed; inset: 0; pointer-events: none; opacity: 0.35; z-index: 9999;490 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.06'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");491}492body > * { position: relative; }493494/* ---- Conteneur commun ---- */495.container { max-width: 1152px; margin: 0 auto; padding: 0 var(--sp-4); }496@media (min-width: 768px) { .container { padding: 0 var(--sp-5); } }497498/* ---- Micro-typographie signature ---- */499.kicker {500 display: inline-flex; align-items: center; gap: 10px;501 font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;502 text-transform: uppercase; letter-spacing: 0.12em; color: var(--green);503}504.kicker::before { content: ""; width: 22px; height: 2px; background: var(--green); }505.klabel {506 font-family: var(--font-mono); font-size: 10px; font-weight: 700;507 text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3);508}509.hl {510 display: inline-block; background: var(--accent); color: var(--on-accent);511 border-radius: 8px; padding: 0 10px 2px; transform: rotate(-1deg);512}513.outline-txt { color: transparent; -webkit-text-stroke: 2px var(--ink); }514515/* ---- Boutons (cible ≥ 44 px) ---- */516.btn {517 display: inline-flex; align-items: center; justify-content: center; gap: 8px;518 min-height: var(--touch); padding: 10px 18px;519 font-family: var(--font-display); font-weight: 700; font-size: 14px;520 border: 1.5px solid var(--ink); border-radius: var(--r-ctl);521 background: var(--surface); color: var(--ink);522 cursor: pointer; text-decoration: none;523 transition: transform 0.15s, box-shadow 0.15s, background 0.15s, color 0.15s;524}525.btn:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off-mid); }526.btn:active { transform: translate(0, 0); box-shadow: none; }527.btn:disabled { opacity: 0.45; pointer-events: none; }528.btn-primary { background: var(--ink); color: var(--accent); }529.btn-primary:hover { background: var(--accent-deep); }530.btn-accent { background: var(--accent); color: var(--on-accent); }531.btn-ghost { background: transparent; border-color: var(--line); }532.btn-ghost:hover { border-color: var(--ink); }533534/* ---- Cartes ---- */535.card {536 background: var(--surface); border: 1.5px solid var(--ink);537 border-radius: var(--r-card); box-shadow: var(--shadow-off-soft);538 overflow: hidden;539}540.card-hover { transition: transform 0.15s, box-shadow 0.15s; }541.card-hover:hover { transform: translate(-2px, -2px); box-shadow: var(--shadow-off); }542543/* ---- Chips / badges ---- */544.chip {545 display: inline-flex; align-items: center; gap: 6px;546 padding: 4px 11px; font-family: var(--font-mono); font-size: 11px; font-weight: 700;547 text-transform: uppercase; letter-spacing: 0.06em;548 border: 1.5px solid var(--ink); border-radius: var(--r-pill);549 background: var(--surface); color: var(--ink);550}551.chip-accent { background: var(--accent); color: var(--on-accent); }552.chip-soft { background: var(--accent-soft); border-color: var(--line); }553554/* ---- Champs ---- */555.input, .select, .textarea {556 width: 100%; min-height: var(--touch); padding: 10px 14px;557 font: inherit; color: var(--ink); background: var(--surface);558 border: 1.5px solid var(--ink); border-radius: var(--r-ctl);559}560.input:focus, .select:focus, .textarea:focus {561 outline: none; box-shadow: 3px 3px 0 var(--accent); border-color: var(--ink);562}563.input::placeholder { color: var(--ink-3); }564565/* ---- Badge « Un service Groupe KA » (header, cliquable → hub) ---- */566.gk-badge {567 display: inline-flex; align-items: center; gap: 7px;568 min-height: 30px; padding: 3px 10px 4px;569 font-family: var(--font-mono); font-size: 10px; font-weight: 700;570 letter-spacing: 0.08em; text-transform: uppercase; text-decoration: none;571 border: 1.5px solid var(--ink); border-radius: var(--r-pill);572 background: var(--surface); color: var(--ink-2); white-space: nowrap;573}574.gk-badge b {575 font-family: var(--font-display); font-size: 12px; letter-spacing: -0.02em;576 text-transform: none; color: var(--ink);577}578.gk-badge b .ka {579 display: inline-block; background: var(--ink); color: var(--accent);580 border-radius: 5px; padding: 0 5px 1px; margin-left: 3px; transform: rotate(-2deg);581}582.gk-badge:hover .ka { transform: rotate(0); }583.gk-badge--dark { background: transparent; border-color: rgba(245,243,238,0.4); color: rgba(245,243,238,0.75); }584.gk-badge--dark b { color: var(--paper); }585586/* ---- Footer commun (fond encre — voir react/KaFooter.tsx) ---- */587.ka-footer { margin-top: var(--sp-8); background: var(--ink); padding: 44px 0; font-size: 13px; color: rgba(245,243,238,0.75); }588.ka-footer a { text-decoration: none; }589.ka-footer .wordmark { font-family: var(--font-display); font-weight: 700; font-size: 30px; letter-spacing: -0.04em; color: var(--paper); text-decoration: none; }590.ka-footer .wordmark .ka { color: var(--accent); }591.ka-footer .desc { max-width: 640px; margin-top: var(--sp-4); }592.ka-footer .notice { max-width: 640px; margin-top: var(--sp-4); border-left: 2px solid var(--accent); padding-left: var(--sp-4); }593.ka-footer .notice b { color: var(--paper); }594.ka-footer .sites { list-style: none; display: flex; flex-wrap: wrap; gap: 10px 26px; margin: 26px 0 0; padding: 0; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; }595.ka-footer .sites a { color: rgba(245,243,238,0.65); }596.ka-footer .sites a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }597.ka-footer .contacts { display: grid; gap: 14px 32px; border-top: 1px solid rgba(245,243,238,0.15); margin-top: 30px; padding-top: 26px; }598@media (min-width: 768px) { .ka-footer .contacts { grid-template-columns: repeat(3, 1fr); } }599.ka-footer .contacts a { font-family: var(--font-mono); font-weight: 700; font-size: 12px; color: rgba(245,243,238,0.85); }600.ka-footer .contacts a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }601.ka-footer .contacts span { display: block; margin-top: 3px; font-size: 11px; color: rgba(245,243,238,0.5); }602.ka-footer .legal { margin-top: 30px; font-family: var(--font-mono); font-size: 11px; color: rgba(245,243,238,0.45); }603.ka-footer .legal a { color: inherit; }604.ka-footer .legal a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }605606/* ---- Tableaux → cartes empilées sous 768 px ---- */607.tbl-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }608@media (max-width: 767px) {609 .tbl-stack thead { display: none; }610 .tbl-stack tr { display: block; border: 1.5px solid var(--ink); border-radius: var(--r-card); margin-bottom: var(--sp-3); background: var(--surface); }611 .tbl-stack td { display: flex; justify-content: space-between; gap: var(--sp-3); padding: 8px 12px; border: 0; }612 .tbl-stack td::before { content: attr(data-label); font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); }613}614615/* ---- Utilitaires responsive ---- */616.only-mobile { display: initial; } .only-desktop { display: none; }617@media (min-width: 768px) { .only-mobile { display: none; } .only-desktop { display: initial; } }618619/* =============================================================================620 SOCLE MOBILE OBLIGATOIRE (2026-08-19) — règles communes aux 13 sites.621 RÈGLES pour tout composant futur :622 · TAP, jamais :hover, pour ouvrir un menu (le survol n'existe pas au doigt) ;623 le :hover ne sert qu'aux effets décoratifs, sous @media (hover: hover).624 · var(--vh100) (dvh) et JAMAIS 100vh pour tout élément calé sur le viewport.625 · position:fixed exige qu'AUCUN ancêtre n'ait transform/filter/perspective/626 will-change/backdrop-filter — sinon monter l'élément à la racine (body).627 · Barre basse fixe = .ka-bottombar + classe has-bottombar sur <body>.628 · Menu/panneau ouvert = .ka-scroll-lock sur <html> (scroll arrière-plan gelé).629 · z-index : uniquement l'échelle --z-* ci-dessus.630 · Zones tactiles ≥ var(--touch) (44 px), champs ≥ 16 px (zoom iOS).631 ========================================================================== */632633/* Anti-zoom iOS : au doigt, aucun champ sous 16 px (le focus d'un champ <16 px634 déclenche un zoom automatique de page sur Safari iOS). !important assumé :635 c'est un filet d'accessibilité, un champ plus petit casse le zoom quoi636 qu'il arrive — ne s'applique qu'aux écrans tactiles. */637@media (hover: none) and (pointer: coarse) {638 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),639 select, textarea { font-size: max(16px, 1em) !important; }640}641642/* Zone tactile du badge Groupe KA étendue à ≥44 px au doigt, sans changer643 son rendu (débord de la zone cliquable via pseudo-élément). */644@media (pointer: coarse) {645 .gk-badge { position: relative; }646 .gk-badge::after { content: ""; position: absolute; inset: -8px; }647}648649/* Hauteurs viewport dynamiques */650.h-viewport { height: 100vh; height: 100dvh; }651.min-h-viewport { min-height: 100vh; min-height: 100dvh; }652653/* Barre fixe basse fiable : safe-area iPhone incluse, immunisée contre les654 transforms accidentels, et compensation d'espace via body.has-bottombar. */655.ka-bottombar {656 position: fixed; left: 0; right: 0; bottom: 0;657 z-index: var(--z-bottombar);658 padding-bottom: env(safe-area-inset-bottom, 0px);659 background: var(--surface);660 border-top: 1.5px solid var(--ink);661 transform: none !important; filter: none !important;662}663body.has-bottombar { padding-bottom: calc(var(--bottombar-h, 64px) + env(safe-area-inset-bottom, 0px)); }664665/* Verrou de scroll d'arrière-plan (menu mobile, modale, panneau de filtres666 ouverts) : ajouter/retirer .ka-scroll-lock sur <html>. */667html.ka-scroll-lock, html.ka-scroll-lock body { overflow: hidden !important; overscroll-behavior: none; }668669/* Menu déroulant de référence : au-dessus de tout contenu (cartes Mapbox670 incluses), défilement interne avec élan, jamais plus haut que l'écran. */671.ka-menu {672 position: absolute; z-index: var(--z-dropdown);673 min-width: 180px;674 max-height: min(60vh, 420px); max-height: min(60dvh, 420px);675 overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;676 background: var(--surface); border: 1.5px solid var(--ink);677 border-radius: var(--r-card); box-shadow: var(--shadow-off-mid);678}679.ka-menu a, .ka-menu button, .ka-menu label, .ka-menu [role="menuitem"], .ka-menu [role="option"] {680 display: flex; align-items: center; min-height: var(--touch);681 padding: 10px 14px; width: 100%; text-decoration: none;682}683/* Voile plein écran sous une modale / un panneau */684.ka-overlay {685 position: fixed; inset: 0; z-index: var(--z-overlay);686 background: rgba(20, 24, 20, 0.45);687}688689</style>690<style>691/* Surcharges Créa-Ka par-dessus ka/tokens.css. Convention du groupe : les692 NOMS de variables sont partagés (--accent/--lime), seules les VALEURS693 changent — accent attribué à Créa-Ka : VIOLET. */694:root{695 --accent:#7048e8; --accent-soft:#ece5fc; --accent-deep:#5433b8; --on-accent:#ffffff;696 /* alias rétro-compatibles (le CSS historique du site utilise --lime) */697 --lime:var(--accent); --lime-soft:var(--accent-soft);698 --green:var(--accent-deep); /* primaire du site = violet profond */699 --green-deep:#3d2178;700}701/* le layout du site est calibré sur des titres sans marge basse */702h1,h2,h3,h4{margin:0}703a{text-decoration:none} button{font-family:inherit;cursor:pointer}704.icn{width:1em;height:1em;display:block;fill:currentColor}705.container{max-width:1240px;margin:0 auto;padding:0 24px}706@media(max-width:640px){.container{padding:0 16px}}707708/* ---------- cartes & boutons (langage du groupe) ---------- */709.gk-card{background:var(--surface);border:1.5px solid var(--ink);710 border-radius:var(--r-card);box-shadow:var(--shadow-off-soft)}711.gk-hover{transition:transform .18s ease,box-shadow .18s ease}712.gk-hover:hover{transform:translate(-3px,-3px);box-shadow:var(--shadow-off)}713.btn:active{transform:translate(2px,2px);box-shadow:none!important}714.btn-primary{box-shadow:4px 4px 0 rgba(20,24,20,.25)}715716/* ===== header ===== */717.header{position:sticky;top:0;z-index:var(--z-header,500);background:var(--paper);718 border-bottom:1.5px solid var(--ink)}719.header-inner{display:flex;align-items:center;gap:16px;height:64px}720.brand{font-family:var(--font-display);font-weight:700;font-size:23px;letter-spacing:-.03em;721 display:flex;align-items:center;line-height:1;color:var(--ink)}722.brand svg{margin-right:11px;flex:none}723@media(pointer:coarse){.brand{position:relative}.brand::after{content:"";position:absolute;inset:-8px}}724.brand .ka{background:var(--accent);color:var(--on-accent);border-radius:6px;padding:0 6px 1px;margin-left:2px;transform:rotate(-1deg)}725.brand-tag{font-family:var(--font-mono);font-size:10px;font-weight:500;color:var(--ink-3);726 line-height:1.5;text-transform:uppercase;letter-spacing:.08em}727@media(max-width:1040px){.brand-tag{display:none}}728.header-spacer{flex:1}729.hbtn{border:1.5px solid var(--ink);background:var(--surface);color:var(--ink);730 border-radius:var(--r-ctl);padding:8px 16px;min-height:44px;font-family:var(--font-display);731 font-size:13px;font-weight:700;transition:.15s;display:inline-flex;align-items:center;732 gap:8px;white-space:nowrap;box-shadow:3px 3px 0 rgba(20,24,20,.12)}733.hbtn:hover{background:var(--accent);color:var(--on-accent)}734.hbtn:active{transform:translate(2px,2px);box-shadow:none}735.hbtn.primary{background:var(--ink);color:var(--accent)}736.hbtn.primary:hover{background:var(--accent-deep);color:var(--on-accent)}737@media(max-width:940px){.header .hbtn:not(.acct){display:none}}738/* bouton / jeton de compte KA ID — toujours visible, même sur mobile */739.hbtn.acct{padding:7px 14px}740.hbtn.acct img{width:22px;height:22px;border-radius:50%;object-fit:cover;741 border:1.5px solid var(--ink);margin-left:-4px}742.hbtn.acct .ka-badge{background:var(--accent);color:var(--on-accent);border-radius:4px;padding:0 5px;743 font-family:var(--font-mono);font-size:10px;font-weight:700}744.hbtn:hover .ka-badge{background:var(--on-accent);color:var(--accent)}745/* badge « Un service Groupe KA » (ka/GroupeKaBadge — styles dans tokens.css) */746.header .gk-badge{flex:none}747.hbtn .acct-short{display:none}748@media(max-width:560px){749 .header-inner{gap:10px}750 .header .gk-badge .pfx{display:none}751 .hbtn .acct-long{display:none}.hbtn .acct-short{display:inline}}752@media(max-width:400px){.brand svg{width:26px;height:26px;margin-right:8px}.brand{font-size:20px}.header .gk-badge{display:none}}753754/* ===== page compte (carte de membre Groupe KA) ===== */755.compte-page{max-width:640px;margin:0 auto;padding:44px 0 70px}756.membercard{background:var(--ink);border:1.5px solid var(--ink);border-radius:14px;757 padding:28px;color:var(--paper);box-shadow:var(--shadow-off);margin-top:24px;758 position:relative;overflow:hidden}759.membercard::after{content:"";position:absolute;right:-40px;top:-40px;width:160px;760 height:160px;border-radius:50%;background:rgba(112,72,232,.18)}761.mc-brand{font-family:var(--font-mono);font-size:10px;font-weight:700;762 text-transform:uppercase;letter-spacing:.14em;color:var(--accent-soft)}763.mc-row{display:flex;gap:18px;align-items:center;margin-top:18px}764.mc-avatar{width:72px;height:72px;border-radius:14px;border:2px solid var(--accent);765 background:var(--green-deep);overflow:hidden;flex:none;display:flex;766 align-items:center;justify-content:center;font-family:var(--font-display);767 font-weight:700;font-size:26px;color:var(--accent-soft)}768.mc-avatar img{width:100%;height:100%;object-fit:cover}769.mc-name{font-family:var(--font-display);font-size:22px;font-weight:700;color:var(--paper)}770.mc-kaid{font-family:var(--font-mono);font-size:12px;color:var(--accent-soft);margin-top:2px;letter-spacing:.04em}771.mc-role{display:inline-flex;background:var(--accent);color:var(--on-accent);border-radius:5px;772 padding:2px 10px;font-family:var(--font-mono);font-size:10px;font-weight:700;773 text-transform:uppercase;letter-spacing:.06em;margin-top:8px}774.mc-meta{margin-top:20px;display:grid;gap:6px;font-size:12.5px;color:rgba(245,243,238,.75)}775.mc-meta b{color:var(--paper);font-weight:600}776.compte-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:24px}777778/* ===== héros ===== */779.hero{padding:54px 0 26px}780@media(max-width:640px){.hero{padding:30px 0 12px}}781.hero h1{font-size:clamp(30px,5.4vw,54px);font-weight:700;max-width:820px;line-height:1.06;782 text-transform:uppercase}783.hero p.lead{color:var(--ink-2);font-size:clamp(14.5px,2vw,16.5px);max-width:640px;margin:18px 0 0}784.stats-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:24px}785.stat-pill{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-ctl);786 padding:8px 16px;font-family:var(--font-mono);font-size:11px;font-weight:700;787 text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2);788 display:inline-flex;gap:8px;align-items:center;box-shadow:3px 3px 0 rgba(20,24,20,.1)}789.stat-pill b{font-family:var(--font-display);color:var(--ink);font-size:16px;letter-spacing:0}790.advantages{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:32px 0 8px}791@media(max-width:900px){.advantages{grid-template-columns:repeat(2,1fr);gap:10px}}792.adv{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);793 padding:16px;box-shadow:var(--shadow-off-soft)}794.adv .ico{width:36px;height:36px;border-radius:8px;border:1.5px solid var(--ink);795 background:var(--accent);color:var(--on-accent);display:flex;align-items:center;796 justify-content:center;margin-bottom:10px;font-size:15px}797.adv:nth-child(2n) .ico{color:var(--ink)}798.adv:nth-child(2n) .ico{background:var(--amber-soft)}799.adv h3{font-size:14px;margin-bottom:3px}800.adv p{margin:0;font-size:12px;color:var(--ink-3);line-height:1.45}801802/* ===== recherche + filtres ===== */803.searchbar{position:sticky;top:64px;z-index:var(--z-sticky,300);background:rgba(245,243,238,.94);804 backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);805 padding:12px 0;border-bottom:1.5px solid var(--ink)}806.search-wrap{display:flex;gap:10px;align-items:center}807.search-input{flex:1 1 auto;display:flex;align-items:center;gap:10px;background:var(--surface);808 border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:10px 15px;809 transition:.15s;min-width:0;box-shadow:3px 3px 0 rgba(20,24,20,.08)}810.search-input:focus-within{box-shadow:3px 3px 0 rgba(112,72,232,.35)}811.search-input input{border:0;background:none;outline:none;font:inherit;flex:1;color:var(--ink);min-width:0}812.search-input svg{flex:none;color:var(--ink-3)}813.filters{display:flex;gap:8px;flex-wrap:wrap}814select.f{appearance:none;-webkit-appearance:none;border:1.5px solid var(--ink);815 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;816 border-radius:var(--r-ctl);padding:9px 30px 9px 14px;font-family:var(--font-display);817 font-size:13px;font-weight:700;color:var(--ink-2);max-width:170px;818 box-shadow:3px 3px 0 rgba(20,24,20,.08)}819select.f:focus{outline:2px solid var(--green);outline-offset:1px}820select.f.active{background-color:var(--accent-soft);color:var(--ink);border-color:var(--accent-deep)}821@media(max-width:760px){822 .search-wrap{flex-wrap:wrap}823 .search-input{flex-basis:100%}824 .search-input input{font-size:16px}825 .filters{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;826 margin:0 -16px;padding:4px 16px 6px;scrollbar-width:none;width:calc(100% + 32px)}827 .filters::-webkit-scrollbar{display:none}828 select.f{flex:none;font-size:16px;padding:8px 30px 8px 13px}829}830831/* ===== grille créateurs ===== */832.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:18px;padding:24px 0 10px}833@media(max-width:640px){.grid{grid-template-columns:repeat(2,1fr);gap:12px}}834@media(max-width:420px){.grid{grid-template-columns:1fr;gap:14px}}835.card{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);836 overflow:hidden;box-shadow:var(--shadow-off-soft);transition:transform .18s ease,box-shadow .18s ease;837 display:flex;flex-direction:column}838.card:hover{transform:translate(-3px,-3px);box-shadow:var(--shadow-off)}839.card-photo{position:relative;aspect-ratio:1/1;background:840 repeating-linear-gradient(-45deg,var(--green-deep) 0 14px,#33195f 14px 28px);841 display:flex;align-items:center;justify-content:center;overflow:hidden;842 border-bottom:1.5px solid var(--ink)}843.card-photo .init{color:var(--accent-soft);font-family:var(--font-display);font-weight:700;844 font-size:46px;letter-spacing:.02em}845.card-photo img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}846.card-photo .kc-reco{position:absolute;bottom:8px;left:8px;padding:2px 8px 3px;border-radius:5px;847 background:var(--ink);color:var(--accent);font-family:var(--font-mono);font-size:9px;848 font-weight:700;text-transform:uppercase;letter-spacing:.07em;z-index:2}849.tier{position:absolute;top:10px;left:10px;z-index:2}850.card-photo .nplat{position:absolute;top:10px;right:10px;z-index:2;background:var(--ink);851 color:var(--accent-soft);border:1.5px solid var(--ink);border-radius:var(--r-ctl);852 padding:2px 8px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;853 text-transform:uppercase;letter-spacing:.05em}854.tier{display:inline-flex;padding:2px 9px;border:1.5px solid var(--ink);855 border-radius:var(--r-ctl);font-family:var(--font-mono);font-size:9.5px;856 font-weight:700;text-transform:uppercase;letter-spacing:.06em;background:var(--surface);color:var(--ink)}857.tier.mega{background:var(--accent);color:var(--on-accent)}858.tier.macro{background:var(--amber-soft)}859.card-body{padding:14px 15px 15px;display:flex;flex-direction:column;gap:9px;flex:1}860.card-name{font-family:var(--font-display);font-size:16px;font-weight:700;861 letter-spacing:-.01em;line-height:1.25;display:flex;align-items:center;gap:6px}862.vbadge{color:var(--green);flex:none;width:15px;height:15px}863.card-sub{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);864 text-transform:uppercase;letter-spacing:.05em;display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:-4px}865.chips{display:flex;gap:6px;flex-wrap:wrap}866.chip{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-ctl);867 padding:2px 10px;font-family:var(--font-body);font-size:11px;font-weight:600;color:var(--ink-2);868 white-space:nowrap;text-transform:none;letter-spacing:0}869.reach{font-size:12px;color:var(--ink-2)}870.reach b{font-family:var(--font-display);color:var(--ink);font-size:15.5px;font-weight:700}871.platrow{display:flex;gap:6px;flex-wrap:wrap;margin-top:auto;padding-top:2px}872.plat{width:32px;height:32px;border-radius:8px;border:1.5px solid var(--ink);873 display:flex;align-items:center;justify-content:center;color:#fff;transition:.15s;874 box-shadow:2px 2px 0 rgba(20,24,20,.25)}875.plat svg{width:15px;height:15px;fill:currentColor}876.plat:hover{transform:translate(-1px,-1px);box-shadow:3px 3px 0 var(--ink)}877/* ===== favoris ♥ « Mon univers Ka » (hub groupe-ka.com) ===== */878.favbtn{position:absolute;right:10px;bottom:10px;z-index:2;width:36px;height:36px;879 border-radius:50%;border:1.5px solid var(--ink);background:var(--surface);880 color:var(--ink-2);display:flex;align-items:center;justify-content:center;881 padding:0;box-shadow:2px 2px 0 rgba(20,24,20,.25);transition:.15s}882.favbtn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;883 stroke-linecap:round;stroke-linejoin:round}884.favbtn:hover{transform:translate(-1px,-1px);box-shadow:3px 3px 0 var(--ink);color:var(--accent-deep)}885.favbtn:active{transform:translate(1px,1px);box-shadow:none}886.favbtn.on{background:var(--accent);color:var(--on-accent)}887.favbtn.on svg{fill:currentColor}888.favbtn[disabled]{opacity:.55;pointer-events:none}889.fav-cta{display:inline-flex;align-items:center;gap:9px;border:1.5px solid var(--ink);890 background:var(--surface);color:var(--ink);border-radius:var(--r-ctl);891 padding:10px 18px;min-height:44px;font-family:var(--font-display);font-size:13px;892 font-weight:700;transition:.15s;box-shadow:3px 3px 0 rgba(20,24,20,.25);cursor:pointer}893.fav-cta svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;894 stroke-linecap:round;stroke-linejoin:round;flex:none}895.fav-cta:hover{background:var(--accent);color:var(--on-accent)}896.fav-cta:active{transform:translate(2px,2px);box-shadow:none}897.fav-cta.on{background:var(--accent);color:var(--on-accent)}898.fav-cta.on svg{fill:currentColor}899.fav-cta[disabled]{opacity:.55;pointer-events:none}900.fav-empty{padding:50px 0;text-align:center;color:var(--ink-3)}901.fav-empty h2{text-transform:uppercase;color:var(--ink);margin-bottom:10px}902.favoris-page{padding:40px 0 60px}903.favoris-page .kicker+h1{font-size:clamp(26px,4.4vw,42px);margin:12px 0 0;text-transform:uppercase}904.favoris-page .lead{color:var(--ink-2);margin:14px 0 0;max-width:620px}905.more-wrap{display:flex;justify-content:center;padding:16px 0 44px}906.empty{padding:60px 0;text-align:center;color:var(--ink-3);grid-column:1/-1}907.count-line{font-family:var(--font-mono);font-size:11px;text-transform:uppercase;908 letter-spacing:.08em;color:var(--ink-3);padding-top:18px}909.count-line b{color:var(--ink)}910911/* ===== page créateur ===== */912.cpage{padding:30px 0 60px}913@media(max-width:640px){.cpage{padding:20px 0 40px}}914.backlink{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);915 font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;916 color:var(--green);margin-bottom:20px}917.cpage-head{display:flex;gap:26px;align-items:center;background:var(--ink);918 border:1.5px solid var(--ink);border-radius:var(--r-card);padding:32px;919 color:var(--paper);box-shadow:var(--shadow-off);flex-wrap:wrap}920@media(max-width:640px){.cpage-head{padding:22px 18px;gap:16px}}921.cavatar{width:104px;height:104px;border-radius:var(--r-card);922 background:repeating-linear-gradient(-45deg,var(--green-deep) 0 14px,#33195f 14px 28px);923 color:var(--accent-soft);flex:none;display:flex;align-items:center;justify-content:center;924 font-family:var(--font-display);font-weight:700;font-size:36px;overflow:hidden;925 position:relative;border:2px solid var(--accent)}926@media(max-width:640px){.cavatar{width:76px;height:76px;font-size:26px}}927.cavatar img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}928.cpage-head h1{font-size:clamp(24px,4vw,40px);font-weight:700;color:var(--paper);929 line-height:1.1;display:flex;align-items:center;gap:10px;flex-wrap:wrap;text-transform:uppercase}930.cpage-head h1 .vbadge{width:22px;height:22px;color:var(--accent-soft)}931.cpage-head .meta{display:flex;gap:7px;flex-wrap:wrap;margin-top:12px}932.mchip{background:rgba(245,243,238,.12);border:1px solid rgba(245,243,238,.3);933 border-radius:var(--r-ctl);padding:3px 11px;font-family:var(--font-mono);934 font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--paper)}935.mchip.o{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}936.cpage-reach{margin-left:auto;text-align:right}937.cpage-reach b{display:block;font-family:var(--font-display);938 font-size:clamp(28px,3.6vw,40px);font-weight:700;color:var(--accent-soft);letter-spacing:-.02em}939.cpage-reach span{font-family:var(--font-mono);font-size:10px;color:rgba(245,243,238,.6);940 text-transform:uppercase;letter-spacing:.08em;font-weight:700}941@media(max-width:760px){.cpage-reach{margin-left:0;text-align:left;flex-basis:100%}}942.cpage-bio{margin:24px 0 0;max-width:820px;color:var(--ink-2);font-size:15px}943.cpage-notes{margin:14px 0 0;max-width:820px;color:var(--ink-2);font-size:12.5px;944 background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);945 padding:13px 17px;box-shadow:var(--shadow-off-soft)}946.sect-title{font-size:19px;margin:36px 0 15px;display:flex;align-items:center;gap:10px;text-transform:uppercase}947/* ===== page créateur : métriques calculées + onglets par plateforme ===== */948.hkpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin:22px 0 6px}949.hkpi{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);950 box-shadow:var(--shadow-off-soft);padding:13px 15px}951.hkpi b{display:block;font-family:var(--font-display);font-size:22px;font-weight:800;color:var(--ink);line-height:1}952.hkpi span{display:block;margin-top:5px;font-family:var(--font-mono);font-size:9.5px;color:var(--ink-3);953 text-transform:uppercase;letter-spacing:.05em;font-weight:700}954/* barre d'onglets */955.ptabs{display:flex;gap:8px;overflow-x:auto;margin:26px 0 0;padding-bottom:2px}956.ptabs::-webkit-scrollbar{height:5px}.ptabs::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}957.ptab{flex:none;display:inline-flex;align-items:center;gap:7px;cursor:pointer;958 background:var(--surface);border:1.5px solid var(--ink);border-bottom:none;959 border-radius:12px 12px 0 0;padding:10px 15px;font-family:var(--font-display);960 font-weight:700;font-size:13px;color:var(--ink-2);transition:.14s;margin-bottom:-1.5px}961.ptab:hover{background:var(--surface-2)}962.ptab.on{background:var(--accent);color:var(--on-accent);border-color:var(--ink)}963.ptab.on .ptab-ico{color:var(--on-accent)!important}964.ptab-ico{width:16px;height:16px;display:inline-flex}.ptab-ico svg{width:16px;height:16px}965.ptab-n{font-family:var(--font-mono);font-size:10px;opacity:.75;font-weight:700}966.ppanels{border:1.5px solid var(--ink);border-radius:0 12px 12px 12px;967 background:var(--surface);box-shadow:var(--shadow-off-soft);padding:20px 18px 22px}968.ppanel{display:none}.ppanel.on{display:block;animation:fade .2s ease}969@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}970.ppanel-head{display:flex;align-items:center;gap:13px}971.ppanel-head .plat{width:46px;height:46px;border-radius:10px;flex:none;display:flex;align-items:center;justify-content:center}972.ppanel-head .plat svg{width:22px;height:22px}973.ppanel-id{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}974.ppanel-name{font-family:var(--font-display);font-weight:800;font-size:17px;display:flex;align-items:center;gap:6px}975.ppanel-handle{font-size:13px;color:var(--green);font-weight:600}976.conf{display:inline-flex;padding:2px 9px;border-radius:var(--r-ctl);977 font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.03em;flex:none}978.conf.hi{background:var(--lime-soft);color:var(--green-deep)}979.conf.mid{background:var(--amber-soft);color:#8a5a12}980.abadge-row{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 4px}981.abadge{display:inline-flex;background:var(--lime-soft);color:var(--green-deep);982 border:1px solid var(--line);border-radius:var(--r-ctl);padding:3px 9px;983 font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.02em}984.abadge.ok{background:var(--surface-2);color:var(--ink-2)}985.abadge.live{background:#ffe3e0;color:#b0201a;border-color:#b0201a}986/* grille KPI par plateforme */987.kpi-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;margin:16px 0 4px}988.kpi{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-ctl);padding:11px 13px}989.kpi b{display:block;font-family:var(--font-display);font-size:19px;font-weight:800;color:var(--ink);line-height:1}990.kpi span{display:block;margin-top:4px;font-family:var(--font-mono);font-size:9px;color:var(--ink-3);991 text-transform:uppercase;letter-spacing:.04em;font-weight:700}992.ppanel-top{display:inline-flex;margin:12px 0 2px;background:var(--surface-2);border:1px solid var(--line);993 border-radius:var(--r-ctl);padding:8px 13px;font-size:12.5px;font-weight:700;color:var(--ink-2)}994.ppanel-top:hover{color:var(--green)}995.ppanel-foot{margin-top:14px;font-family:var(--font-mono);font-size:10px;color:var(--ink-3)}996/* grille de contenu récent (images/vidéos) */997.cg-title{font-family:var(--font-display);font-size:14px;font-weight:800;margin:20px 0 12px;color:var(--ink)}998.cg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:13px}999.cg-card{display:flex;flex-direction:column;text-decoration:none;color:inherit;1000 border:1.5px solid var(--ink);border-radius:var(--r-card);overflow:hidden;1001 background:var(--surface);box-shadow:var(--shadow-off-soft);transition:transform .15s,box-shadow .15s}1002.cg-card:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-off)}1003.cg-thumb{position:relative;display:block;width:100%;aspect-ratio:1/1;background:var(--surface-2);overflow:hidden}1004.cg-thumb img{width:100%;height:100%;object-fit:cover;display:block}1005.cg-thumb.noimg{display:flex;align-items:center;justify-content:center}1006.cg-thumb.noimg svg{width:44px;height:44px;opacity:.3}1007.cg-play{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;1008 font-size:26px;text-shadow:0 2px 6px rgba(0,0,0,.7);pointer-events:none}1009.cg-dur{position:absolute;right:6px;bottom:6px;background:rgba(0,0,0,.8);color:#fff;1010 font-family:var(--font-mono);font-size:10px;padding:2px 6px;border-radius:5px}1011.cg-pin{position:absolute;left:6px;top:6px;font-size:13px}1012.cg-body{padding:9px 11px 11px;display:flex;flex-direction:column;gap:5px}1013.cg-cap{font-size:12px;line-height:1.3;color:var(--ink-2);display:-webkit-box;1014 -webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:31px}1015.cg-cap.dim{color:var(--ink-3)}1016.cg-meta{font-family:var(--font-mono);font-size:10px;color:var(--ink-3);display:flex;1017 justify-content:space-between;gap:6px;flex-wrap:wrap}1018.cg-when{color:var(--ink-3);opacity:.8}1019.cg-empty{padding:26px;text-align:center;color:var(--ink-3);font-size:13px}1020.cg-empty svg{width:20px;height:20px;vertical-align:-4px;opacity:.4;margin-right:6px}1021/* vue d'ensemble : barres de portée */1022.obars{display:flex;flex-direction:column;gap:11px}1023.obar{display:flex;align-items:center;gap:11px}1024.obar-ico{width:32px;height:32px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center}1025.obar-ico svg{width:16px;height:16px}1026.obar-lab{width:96px;flex:none;font-family:var(--font-display);font-weight:700;font-size:13px}1027.obar-track{flex:1;height:12px;background:var(--surface-2);border:1px solid var(--line);border-radius:6px;overflow:hidden}1028.obar-fill{display:block;height:100%;border-radius:6px 0 0 6px;min-width:3px}1029.obar-val{width:78px;flex:none;text-align:right;font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--ink)}1030@media(max-width:640px){.obar-lab{width:64px;font-size:11px}.obar-val{width:60px;font-size:11px}1031 .cg-grid{grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}}1032.linkinbio{display:inline-flex;align-items:center;gap:10px;margin-top:22px;1033 background:var(--accent);color:var(--on-accent);border:1.5px solid var(--ink);1034 border-radius:var(--r-ctl);padding:11px 20px;font-family:var(--font-display);1035 font-weight:700;font-size:13.5px;transition:.15s;box-shadow:4px 4px 0 rgba(20,24,20,.25)}1036.linkinbio:hover{background:var(--accent-soft);color:var(--ink)}1037.linkinbio:active{transform:translate(2px,2px);box-shadow:none}1038.linkinbio svg{width:16px;height:16px;fill:currentColor}1039.cpage-foot{margin-top:38px;font-size:12px;color:var(--ink-3);1040 border-top:1.5px solid var(--ink);padding-top:16px}1041/* bannière héro (banner_url capté par les acteurs Apify) */1042.cbanner{position:relative;border:1.5px solid var(--ink);border-bottom:none;1043 border-radius:var(--r-card) var(--r-card) 0 0;overflow:hidden;height:clamp(120px,22vw,240px);1044 background:var(--ink)}1045.cbanner img{width:100%;height:100%;object-fit:cover;display:block}1046.cbanner+.cpage-head{border-radius:0 0 var(--r-card) var(--r-card)}1047/* Ka Score */1048.kascore{display:flex;gap:22px;align-items:center;flex-wrap:wrap;margin:6px 0 2px}1049.kascore-num{flex:none;width:112px;height:112px;border-radius:50%;display:flex;1050 flex-direction:column;align-items:center;justify-content:center;1051 border:2px solid var(--ink);background:var(--accent);color:var(--on-accent);1052 box-shadow:var(--shadow-off-soft)}1053.kascore-num b{font-family:var(--font-display);font-size:34px;font-weight:800;line-height:1}1054.kascore-num span{font-family:var(--font-mono);font-size:9px;font-weight:700;1055 text-transform:uppercase;letter-spacing:.06em;margin-top:3px}1056.kascore-parts{flex:1;min-width:230px;display:flex;flex-direction:column;gap:7px}1057.kspart{display:flex;align-items:center;gap:10px}1058.kspart-lab{width:110px;flex:none;font-family:var(--font-mono);font-size:10px;1059 font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3)}1060.kspart-track{flex:1;height:9px;background:var(--surface-2);border:1px solid var(--line);1061 border-radius:5px;overflow:hidden}1062.kspart-fill{display:block;height:100%;background:var(--green);border-radius:5px 0 0 5px;min-width:2px}1063.kspart-val{width:52px;flex:none;text-align:right;font-family:var(--font-mono);1064 font-size:11px;font-weight:700;color:var(--ink)}1065@media(max-width:640px){.kspart-lab{width:88px;font-size:9px}}1066/* sparkline évolution d'audience */1067.spark-wrap{margin-top:8px}1068.spark-svg{width:100%;height:88px;display:block}1069.spark-line{fill:none;stroke:var(--green);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}1070.spark-fill{fill:var(--lime-soft);opacity:.7;stroke:none}1071.spark-meta{display:flex;justify-content:space-between;font-family:var(--font-mono);1072 font-size:10px;color:var(--ink-3);margin-top:4px}1073.growth-up{color:var(--green-deep);font-weight:700}1074.growth-down{color:#b0201a;font-weight:700}1075/* bloc EN DIRECT (miniature live Twitch/Kick) */1076.live-card{display:flex;gap:14px;align-items:stretch;margin:14px 0 4px;1077 border:1.5px solid #b0201a;border-radius:var(--r-card);overflow:hidden;1078 background:var(--surface);text-decoration:none;color:inherit}1079.live-card img{width:200px;min-height:100%;object-fit:cover;flex:none;display:block}1080.live-card-body{padding:12px 14px;display:flex;flex-direction:column;gap:5px;justify-content:center}1081.live-card-tag{font-family:var(--font-mono);font-size:10px;font-weight:700;color:#b0201a}1082.live-card-title{font-size:13.5px;font-weight:700;color:var(--ink);line-height:1.3}1083.live-card-meta{font-family:var(--font-mono);font-size:10.5px;color:var(--ink-3)}1084@media(max-width:560px){.live-card{flex-direction:column}.live-card img{width:100%;height:150px}}1085.cpage-foot a{color:var(--green);font-weight:600}10861087/* ===== page stats ===== */1088.stats-page{padding-top:40px;padding-bottom:70px}1089.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px;margin:26px 0}1090.tile{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);1091 padding:18px 20px;box-shadow:var(--shadow-off-soft)}1092.tile-v{font-family:var(--font-display);font-weight:700;font-size:clamp(23px,3vw,32px);1093 letter-spacing:-.03em;line-height:1.05;color:var(--ink)}1094.tile-k{font-family:var(--font-mono);font-size:9.5px;font-weight:700;text-transform:uppercase;1095 letter-spacing:.1em;color:var(--ink-3);margin-top:7px}1096.tile.hero-tile{background:var(--ink)}1097.tile.hero-tile .tile-v{color:var(--accent-soft)}1098.tile.hero-tile .tile-k{color:rgba(245,243,238,.55)}1099.viz-card{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);1100 box-shadow:var(--shadow-off-soft);padding:24px 26px 18px;margin-bottom:22px}1101.viz-card h2{font-size:18px;text-transform:uppercase}1102.viz-sub{font-family:var(--font-mono);font-size:10px;font-weight:700;letter-spacing:.08em;1103 text-transform:uppercase;color:var(--ink-3);margin:4px 0 18px}1104.hrow{display:flex;align-items:center;gap:12px;margin-bottom:10px}1105.hrow .plat{width:28px;height:28px;border-radius:7px;flex:none}1106.hrow .plat svg{width:13px;height:13px}1107.hrow-label{flex:0 0 130px;font-size:12.5px;font-weight:600;color:var(--ink-2);1108 overflow:hidden;text-overflow:ellipsis;white-space:nowrap}1109@media(max-width:640px){.hrow-label{flex-basis:96px;font-size:11.5px}}1110.hbar{flex:1;height:12px;border:1.5px solid var(--ink);border-radius:4px;1111 background:var(--surface-2);overflow:hidden}1112.hbar i{display:block;height:100%;background:var(--lime);border-right:1.5px solid var(--ink)}1113.hrow .n{flex:none;font-family:var(--font-display);font-size:12.5px;font-weight:700;1114 color:var(--ink);min-width:44px;text-align:right}11151116/* ===== page stats : tableau de bord ka-stats (SPEC) ===== */1117.stats-head{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start;justify-content:space-between}1118.pdf-actions{display:flex;gap:8px;flex-wrap:wrap}1119.fresh-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:14px;1120 font-family:var(--font-mono);font-size:11px;color:var(--ink-3);text-transform:uppercase;letter-spacing:.06em}1121.pchips{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:22px 0 2px}1122.pchip{border:1.5px solid var(--ink);background:var(--surface);border-radius:var(--r-ctl);1123 padding:8px 14px;min-height:44px;font-family:var(--font-display);font-size:12.5px;1124 font-weight:700;color:var(--ink-2);box-shadow:3px 3px 0 rgba(20,24,20,.08);transition:.15s}1125.pchip[aria-pressed="true"]{background:var(--accent);color:var(--on-accent)}1126.pchip:active{transform:translate(2px,2px);box-shadow:none}1127.pdates{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap}1128.pdates input{border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:8px 10px;1129 font:inherit;font-size:13px;background:var(--surface);min-height:44px;max-width:160px}1130@media(max-width:760px){.pchips{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;1131 padding:4px 0 8px;scrollbar-width:none}1132 .pchips::-webkit-scrollbar{display:none}1133 .pchip{flex:none}.pdates{flex:none}.pdates input{font-size:16px}}1134@media(max-width:640px){.pchips{margin-left:-16px;margin-right:-16px;1135 padding-left:16px;padding-right:16px}}1136.tile-d{font-family:var(--font-mono);font-size:10.5px;font-weight:700;margin-top:8px}1137.tile-d.up{color:var(--green)}.tile-d.down{color:var(--danger)}1138.tile.hero-tile .tile-d.up{color:var(--accent-soft)}1139.viz-grid{display:grid;gap:22px;grid-template-columns:1fr;margin-bottom:22px}1140@media(min-width:960px){.viz-grid.two{grid-template-columns:1fr 1fr}}1141.viz-grid .viz-card{margin-bottom:0}1142.viz-grid>*,.dt,.lc{min-width:0}1143.dt .viz-card,.lc .viz-card{margin-bottom:0}1144.lc svg{width:100%;height:auto;display:block;touch-action:pan-y}1145.lc-legend{display:flex;gap:14px;flex-wrap:wrap;margin:2px 0 0}1146.leg-btn{display:inline-flex;align-items:center;gap:6px;border:0;background:none;1147 font-family:var(--font-mono);font-size:10px;font-weight:700;text-transform:uppercase;1148 letter-spacing:.06em;color:var(--ink-2);min-height:44px}1149.leg-btn[aria-pressed="false"]{opacity:.35}1150.leg-swatch{width:18px;height:0;border-top:3px solid var(--accent)}1151.leg-swatch.cmp{border-top-style:dashed;border-top-color:var(--ink-3)}1152.lc-tip{display:inline-flex;margin-top:6px;visibility:hidden}1153.donut-wrap{display:flex;flex-wrap:wrap;gap:22px;align-items:center;margin-top:14px}1154.donut-legend{list-style:none;margin:0;padding:0;display:grid;gap:7px;flex:1;min-width:200px}1155.donut-legend li{display:flex;align-items:center;gap:8px;font-size:12.5px;min-width:0}1156.donut-legend .dsw{width:11px;height:11px;border-radius:3px;border:1px solid var(--ink);1157 background:var(--accent);flex:none}1158.donut-legend .dlbl{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}1159.donut-legend b{font-family:var(--font-mono);font-size:11px}1160.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}1161.dtable table{width:100%;border-collapse:collapse;font-size:13px}1162.dtable th{cursor:pointer;text-align:left;padding:8px 10px;background:var(--ink);1163 color:var(--paper);font-family:var(--font-mono);font-size:10px;text-transform:uppercase;1164 letter-spacing:.06em;white-space:nowrap;user-select:none}1165.dtable td{padding:7px 10px;border-bottom:1px solid var(--line);white-space:nowrap}1166.dtable tbody tr:nth-child(even) td{background:var(--surface-2)}1167.dtable .dt-q{border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:9px 13px;1168 font:inherit;font-size:13px;background:var(--surface);max-width:230px;min-height:44px}1169@media(max-width:760px){.dtable .dt-q{font-size:16px;max-width:100%;flex:1}}1170.dtable-foot{display:flex;justify-content:space-between;align-items:center;margin-top:12px;1171 flex-wrap:wrap;gap:8px}1172.records-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:14px}1173.record{background:var(--surface-2);border:1.5px solid var(--ink);border-radius:var(--r-card);1174 padding:13px 16px;display:flex;justify-content:space-between;gap:12px;align-items:baseline;1175 box-shadow:var(--shadow-off-soft)}1176.record .r-lbl{font-size:12.5px;color:var(--ink-2)}1177.record .r-val{text-align:right;font-family:var(--font-display);font-size:14.5px;font-weight:700}1178.hm svg{min-width:520px;width:100%;height:auto}1179.empty-block{border:1.5px dashed var(--ink);border-radius:var(--r-card);1180 background:var(--surface-2);padding:20px}1181.stats-sect{font-size:17px;text-transform:uppercase;margin:34px 0 14px}11821183/* ===== page stats v2 : sparklines, jauges, histogrammes, heatmap horaire, menu PDF ===== */1184.tile svg.spark{display:block;margin-top:9px;width:100%;height:28px}1185.gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:14px;margin-bottom:8px}1186.gauge{background:var(--surface);border:1.5px solid var(--ink);border-radius:var(--r-card);1187 padding:16px 14px 13px;box-shadow:var(--shadow-off-soft);text-align:center}1188.gauge svg{width:124px;max-width:100%;height:auto;margin:0 auto;display:block}1189.gauge .g-v{font-family:var(--font-display);font-weight:700;font-size:21px;color:var(--ink);margin-top:-30px}1190.gauge .g-l{font-family:var(--font-mono);font-size:9px;font-weight:700;text-transform:uppercase;1191 letter-spacing:.05em;color:var(--ink-3);margin-top:10px;line-height:1.55}1192.statsum{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px}1193.statsum .chip b{font-family:var(--font-mono);margin-left:4px}1194.leg-swatch.ml1{border-top:3px solid var(--ink)}1195.leg-swatch.ml2{border-top:3px dashed var(--accent)}1196.leg-swatch.ml3{border-top:3px dotted var(--ink-3)}1197.hrow .d{flex:none;font-family:var(--font-mono);font-size:10px;font-weight:700;min-width:54px;text-align:right}1198.hrow .d.up{color:var(--green)}.hrow .d.down{color:var(--danger)}1199.hh svg{min-width:560px;width:100%;height:auto}1200.pdf-menu-wrap{position:relative;display:inline-flex;gap:8px;flex-wrap:wrap}1201.pdf-menu{position:absolute;right:0;top:calc(100% + 8px);z-index:70;background:var(--surface);1202 border:1.5px solid var(--ink);border-radius:var(--r-card);box-shadow:var(--shadow-off);1203 display:none;min-width:270px;padding:6px}1204.pdf-menu.open{display:block}1205.pdf-menu button{display:block;width:100%;text-align:left;border:0;background:none;1206 padding:11px 13px;border-radius:9px;font-family:var(--font-display);font-weight:700;1207 font-size:13px;color:var(--ink);min-height:44px}1208.pdf-menu button:hover{background:var(--accent-soft)}1209.pdf-menu small{display:block;font-family:var(--font-body);font-weight:400;font-size:11px;1210 color:var(--ink-3);margin-top:2px}1211@media(max-width:760px){.pdf-menu{right:auto;left:0}}12121213/* ===== retrait ===== */1214.optout-page{max-width:620px;margin:0 auto;padding:44px 0 70px}1215.optout-page h1{font-size:clamp(26px,4vw,32px);margin:12px 0;text-transform:uppercase}1216.optout-page p{color:var(--ink-2)}1217.field{margin-top:16px}1218.field label{display:block;font-family:var(--font-mono);font-size:10px;font-weight:700;1219 text-transform:uppercase;letter-spacing:.08em;margin-bottom:7px;color:var(--ink-2)}1220.field input,.field textarea{width:100%;border:1.5px solid var(--ink);border-radius:var(--r-ctl);1221 padding:11px 14px;font:inherit;background:var(--surface);color:var(--ink);1222 box-shadow:3px 3px 0 rgba(20,24,20,.08);transition:box-shadow .15s ease}1223.field input:focus,.field textarea:focus{outline:2px solid var(--green);outline-offset:1px;1224 box-shadow:3px 3px 0 rgba(112,72,232,.25)}1225@media(max-width:900px){.field input,.field textarea{font-size:16px}}1226.optout-ok{background:var(--lime-soft);color:var(--green-deep);border:1.5px solid var(--ink);1227 border-radius:var(--r-card);padding:16px 20px;font-weight:600;margin-top:20px;1228 box-shadow:var(--shadow-off-soft)}1229.optout-ok.warn{background:var(--amber-soft);color:#8a5a12}12301231/* ===== footer : nav locale + footer commun Groupe KA (ka/KaFooter) ===== */1232.footer-local{margin-top:56px;border-top:1.5px solid var(--ink);background:var(--surface);padding:16px 0}1233.footer-local nav{display:flex;gap:10px 26px;flex-wrap:wrap;align-items:center;1234 font-family:var(--font-mono);font-size:11px;font-weight:700;1235 text-transform:uppercase;letter-spacing:.08em;color:var(--ink-2)}1236.footer-local .klabel{color:var(--ink-3)}1237.footer-local a{display:inline-flex;align-items:center;min-height:44px}1238.footer-local a:hover{color:var(--accent-deep);text-decoration:underline;text-underline-offset:4px}1239.footer-local+.ka-footer{margin-top:0}1240.ka-footer .brand{font-size:19px;color:var(--paper)}12411242/* ===== contact ===== */1243.contact-page{max-width:880px;margin:0 auto;padding:44px 0 70px}1244.contact-page h1{font-size:clamp(26px,4vw,38px);margin:12px 0;text-transform:uppercase}1245.contact-page .lead{color:var(--ink-2);max-width:640px}1246.contact-notice{background:var(--accent-soft);border:1.5px solid var(--ink);border-radius:var(--r-card);1247 padding:16px 20px;margin:22px 0;box-shadow:var(--shadow-off-soft);font-weight:600;color:var(--ink)}1248.contact-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px;margin:8px 0 4px}1249.contact-card{display:block;background:var(--surface);border:1.5px solid var(--ink);1250 border-radius:var(--r-card);padding:16px 18px;box-shadow:var(--shadow-off-soft);1251 transition:transform .16s ease,box-shadow .16s ease;min-height:44px}1252.contact-card:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-off)}1253.contact-card b{display:block;font-family:var(--font-mono);font-size:13px;font-weight:700;1254 color:var(--accent-deep);word-break:break-all}1255.contact-card span{display:block;margin-top:5px;font-size:12px;color:var(--ink-2)}1256.site-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px;margin:14px 0 0}1257.site-item{display:flex;align-items:center;gap:10px;background:var(--surface);1258 border:1.5px solid var(--ink);border-radius:var(--r-ctl);padding:10px 13px;min-height:44px;1259 transition:transform .15s ease,box-shadow .15s ease}1260.site-item:hover{transform:translate(-2px,-2px);box-shadow:3px 3px 0 var(--ink)}1261.site-item i{width:10px;height:10px;border-radius:3px;border:1px solid var(--ink);flex:none}1262.site-item b{font-family:var(--font-display);font-size:13.5px;font-weight:700}1263.site-item span{font-size:11px;color:var(--ink-3);margin-left:auto;font-family:var(--font-mono)}1264@media(max-width:480px){.site-item span{display:none}}1265.tabbar{display:none}1266@media(max-width:940px){1267 .tabbar{display:grid;grid-template-columns:repeat(5,1fr);1268 position:fixed;left:0;right:0;bottom:0;z-index:var(--z-bottombar,600);1269 background:rgba(245,243,238,.94);1270 backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);1271 border-top:1.5px solid var(--ink);1272 padding:6px 4px calc(6px + env(safe-area-inset-bottom))}1273 .tabbar a{display:flex;flex-direction:column;align-items:center;gap:3px;1274 padding:5px 2px;min-height:48px;justify-content:center;1275 font-family:var(--font-mono);font-size:9.5px;font-weight:700;1276 text-transform:uppercase;letter-spacing:.04em;color:var(--ink-3);1277 border-radius:var(--r-ctl);transition:color .12s ease}1278 .tabbar a.active{color:var(--green)}1279 .tabbar a svg{display:block;width:20px;height:20px;fill:none;stroke:currentColor;1280 stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}1281 body{padding-bottom:calc(64px + env(safe-area-inset-bottom))}1282 .ka-footer{padding-bottom:calc(76px + env(safe-area-inset-bottom))}1283}1284.spin{width:26px;height:26px;border:3px solid var(--line);border-top-color:var(--green);1285 border-radius:50%;margin:50px auto;animation:sp .8s linear infinite}1286@keyframes sp{to{transform:rotate(360deg)}}1287</style>1288</head>1289<body>1290<div id="app"></div>1291<script>1292"use strict";1293/* Créa-Ka SPA — routes : / · /createur/{id} · /stats · /favoris · /retrait · /compte · /contact */1294const ICONS={"_author":"Simon-Pierre Boucher <contact@spboucher.ai>","_file":"frontend/src/icons.json","_desc":"Trac\u00e9s SVG des marques de plateformes (simple-icons v13, CC0) + glyphes maison (linkedin/site-web/autre)","instagram":"M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077","tiktok":"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z","youtube":"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z","twitch":"M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z","kick":"M1.333 0h8v5.333H12V2.667h2.667V0h8v8H20v2.667h-2.667v2.666H20V16h2.667v8h-8v-2.667H12v-2.666H9.333V24h-8Z","x":"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z","facebook":"M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z","snapchat":"M12.206.793c.99 0 4.347.276 5.93 3.821.529 1.193.403 3.219.299 4.847l-.003.06c-.012.18-.022.345-.03.51.075.045.203.09.401.09.3-.016.659-.12 1.033-.301.165-.088.344-.104.464-.104.182 0 .359.029.509.09.45.149.734.479.734.838.015.449-.39.839-1.213 1.168-.089.029-.209.075-.344.119-.45.135-1.139.36-1.333.81-.09.224-.061.524.12.868l.015.015c.06.136 1.526 3.475 4.791 4.014.255.044.435.27.42.509 0 .075-.015.149-.045.225-.24.569-1.273.988-3.146 1.271-.059.091-.12.375-.164.57-.029.179-.074.36-.134.553-.076.271-.27.405-.555.405h-.03c-.135 0-.313-.031-.538-.074-.36-.075-.765-.135-1.273-.135-.3 0-.599.015-.913.074-.6.104-1.123.464-1.723.884-.853.599-1.826 1.288-3.294 1.288-.06 0-.119-.015-.18-.015h-.149c-1.468 0-2.427-.675-3.279-1.288-.599-.42-1.107-.779-1.707-.884-.314-.045-.629-.074-.928-.074-.54 0-.958.089-1.272.149-.211.043-.391.074-.54.074-.374 0-.523-.224-.583-.42-.061-.192-.09-.389-.135-.567-.046-.181-.105-.494-.166-.57-1.918-.222-2.95-.642-3.189-1.226-.031-.063-.052-.15-.055-.225-.015-.243.165-.465.42-.509 3.264-.54 4.73-3.879 4.791-4.02l.016-.029c.18-.345.224-.645.119-.869-.195-.434-.884-.658-1.332-.809-.121-.029-.24-.074-.346-.119-1.107-.435-1.257-.93-1.197-1.273.09-.479.674-.793 1.168-.793.146 0 .27.029.383.074.42.194.789.3 1.104.3.234 0 .384-.06.465-.105l-.046-.569c-.098-1.626-.225-3.651.307-4.837C7.392 1.077 10.739.807 11.727.807l.419-.015h.06z","substack":"M22.539 8.242H1.46V5.406h21.08v2.836zM1.46 10.812V24L12 18.11 22.54 24V10.812H1.46zM22.54 0H1.46v2.836h21.08V0z","patreon":"M22.957 7.21c-.004-3.064-2.391-5.576-5.191-6.482-3.478-1.125-8.064-.962-11.384.604C2.357 3.231 1.093 7.391 1.046 11.54c-.039 3.411.302 12.396 5.369 12.46 3.765.047 4.326-4.804 6.068-7.141 1.24-1.662 2.836-2.132 4.801-2.618 3.376-.836 5.678-3.501 5.673-7.031Z","onlyfans":"M24 4.003h-4.015c-3.45 0-5.3.197-6.748 1.957a7.996 7.996 0 1 0 2.103 9.211c3.182-.231 5.39-2.134 6.085-5.173 0 0-2.399.585-4.43 0 4.018-.777 6.333-3.037 7.005-5.995zM5.61 11.999A2.391 2.391 0 0 1 9.28 9.97a2.966 2.966 0 0 1 2.998-2.528h.008c-.92 1.778-1.407 3.352-1.998 5.263A2.392 2.392 0 0 1 5.61 12Zm2.386-7.996a7.996 7.996 0 1 0 7.996 7.996 7.996 7.996 0 0 0-7.996-7.996Zm0 10.394A2.399 2.399 0 1 1 10.395 12a2.396 2.396 0 0 1-2.399 2.398Z","threads":"M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z","podcast":"M5.34 0A5.328 5.328 0 000 5.34v13.32A5.328 5.328 0 005.34 24h13.32A5.328 5.328 0 0024 18.66V5.34A5.328 5.328 0 0018.66 0zm6.525 2.568c2.336 0 4.448.902 6.056 2.587 1.224 1.272 1.912 2.619 2.264 4.392.12.59.12 2.2.007 2.864a8.506 8.506 0 01-3.24 5.296c-.608.46-2.096 1.261-2.336 1.261-.088 0-.096-.091-.056-.46.072-.592.144-.715.48-.856.536-.224 1.448-.874 2.008-1.435a7.644 7.644 0 002.008-3.536c.208-.824.184-2.656-.048-3.504-.728-2.696-2.928-4.792-5.624-5.352-.784-.16-2.208-.16-3 0-2.728.56-4.984 2.76-5.672 5.528-.184.752-.184 2.584 0 3.336.456 1.832 1.64 3.512 3.192 4.512.304.2.672.408.824.472.336.144.408.264.472.856.04.36.03.464-.056.464-.056 0-.464-.176-.896-.384l-.04-.03c-2.472-1.216-4.056-3.274-4.632-6.012-.144-.706-.168-2.392-.03-3.04.36-1.74 1.048-3.1 2.192-4.304 1.648-1.737 3.768-2.656 6.128-2.656zm.134 2.81c.409.004.803.04 1.106.106 2.784.62 4.76 3.408 4.376 6.174-.152 1.114-.536 2.03-1.216 2.88-.336.43-1.152 1.15-1.296 1.15-.023 0-.048-.272-.048-.603v-.605l.416-.496c1.568-1.878 1.456-4.502-.256-6.224-.664-.67-1.432-1.064-2.424-1.246-.64-.118-.776-.118-1.448-.008-1.02.167-1.81.562-2.512 1.256-1.72 1.704-1.832 4.342-.264 6.222l.413.496v.608c0 .336-.027.608-.06.608-.03 0-.264-.16-.512-.36l-.034-.011c-.832-.664-1.568-1.842-1.872-2.997-.184-.698-.184-2.024.008-2.72.504-1.878 1.888-3.335 3.808-4.019.41-.145 1.133-.22 1.814-.211zm-.13 2.99c.31 0 .62.06.844.178.488.253.888.745 1.04 1.259.464 1.578-1.208 2.96-2.72 2.254h-.015c-.712-.331-1.096-.956-1.104-1.77 0-.733.408-1.371 1.112-1.745.224-.117.534-.176.844-.176zm-.011 4.728c.988-.004 1.706.349 1.97.97.198.464.124 1.932-.218 4.302-.232 1.656-.36 2.074-.68 2.356-.44.39-1.064.498-1.656.288h-.003c-.716-.257-.87-.605-1.164-2.644-.341-2.37-.416-3.838-.218-4.302.262-.616.974-.966 1.97-.97z","linktree":"m13.73635 5.85251 4.00467-4.11665 2.3248 2.3808-4.20064 4.00466h5.9085v3.30473h-5.9365l4.22865 4.10766-2.3248 2.3338L12.0005 12.099l-5.74052 5.76852-2.3248-2.3248 4.22864-4.10766h-5.9375V8.12132h5.9085L3.93417 4.11666l2.3248-2.3808 4.00468 4.11665V0h3.4727zm-3.4727 10.30614h3.4727V24h-3.4727z","linkedin":"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.225 0z","site-web":"M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm7.938 9h-3.243a15.6 15.6 0 00-1.4-4.653A8.03 8.03 0 0119.938 9zM12 2.05c.9 1.2 1.9 3.2 2.5 6.95h-5C10.1 5.25 11.1 3.25 12 2.05zM4.062 15A8.06 8.06 0 013.75 12c0-1.05.15-2.05.312-3h3.55A25 25 0 007.5 12c0 1.05.05 2.05.113 3zm.643 2h3.243c.35 1.8.85 3.35 1.4 4.653A8.03 8.03 0 014.705 17zm3.243-10H4.705a8.03 8.03 0 014.643-4.653A15.6 15.6 0 007.948 7zM12 21.95c-.9-1.2-1.9-3.2-2.5-6.95h5c-.6 3.75-1.6 5.75-2.5 6.95zM14.787 13H9.213A23 23 0 019.1 12c0-1.05.037-2.05.113-3h5.574c.076.95.113 1.95.113 3s-.037 2.05-.113 3zm.508 8.653c.55-1.303 1.05-2.853 1.4-4.653h3.243a8.03 8.03 0 01-4.643 4.653zM16.388 15c.062-.95.112-1.95.112-3s-.05-2.05-.112-3h3.55c.162.95.312 1.95.312 3s-.15 2.05-.312 3z","autre":"M10.59 13.41a1 1 0 010-1.41l2.83-2.83a3 3 0 114.24 4.24l-1.41 1.42a1 1 0 11-1.42-1.42l1.42-1.41a1 1 0 10-1.42-1.42L12 13.41a1 1 0 01-1.41 0zm2.82-2.82a1 1 0 010 1.41l-2.82 2.83a3 3 0 11-4.25-4.24l1.42-1.42a1 1 0 111.41 1.42L7.76 12a1 1 0 101.41 1.41L12 10.59a1 1 0 011.41 0z","spotify":"M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z","discord":"M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"};1295/* écosystème Groupe KA (ka/ecosystem.json, injecté au build) — footer commun + /contact */1296const KA_ECO={"org":{"name":"Groupe KA","legalName":"Groupe KA — Simon-Pierre Boucher","tagline":"Holding québécois d'agrégateurs de produits et services entièrement automatisés.","disclaimer":"Groupe KA est un agrégateur de contenu : nous ne vendons rien, ne louons rien et ne sommes partie à aucune transaction.","copyrightHolder":"Groupe KA — Simon-Pierre Boucher"},"hub":{"url":"https://www.groupe-ka.com","loginPath":"/connexion","signupNote":"La création de compte KA ID se fait sur le hub groupe-ka.com ; chaque site délègue sa connexion via /api/auth/ka/login."},"contacts":[{"email":"contact@groupe-ka.com","role":"Projets, partenariats & données"},{"email":"info@groupe-ka.com","role":"Médias & questions générales"},{"email":"admin@groupe-ka.com","role":"Légal, vie privée & Loi 25"}],"legal":[{"label":"Conditions d'utilisation","href":"https://www.groupe-ka.com/conditions"},{"label":"Politique de confidentialité","href":"https://www.groupe-ka.com/confidentialite"},{"label":"Protection des renseignements personnels (Loi 25)","href":"https://www.groupe-ka.com/loi-25"},{"label":"Transparence des robots d'indexation","href":"https://www.groupe-ka.com/bots"}],"sites":[{"id":"groupe-ka","wordmark":"Groupe KA","domain":"www.groupe-ka.com","accent":"#d9f26b","accentSoft":"#f0f9d2","accentDeep":"#123f2e","onAccent":"#141814","tagline":"Le portail de l'écosystème ·Ka"},{"id":"trouve-ka","wordmark":"Trouve·Ka","domain":"www.trouve-ka.com","accent":"#1c7ed6","accentSoft":"#e7f2fd","accentDeep":"#14508f","onAccent":"#ffffff","tagline":"Le moteur de recherche du web québécois"},{"id":"lou-ka","wordmark":"Lou·Ka","domain":"www.lou-ka.com","accent":"#ff6a00","accentSoft":"#fff1e6","accentDeep":"#cc5500","onAccent":"#ffffff","tagline":"Tous les logements à louer"},{"id":"immo-ka","wordmark":"Immo·Ka","domain":"www.immo-ka.com","accent":"#e23744","accentSoft":"#fbe0e2","accentDeep":"#a8232e","onAccent":"#ffffff","tagline":"Toutes les propriétés à vendre"},{"id":"vrai-prix","wordmark":"Vrai-Prix","domain":"www.vrai-prix.com","accent":"#ff5148","accentSoft":"#ffe3e0","accentDeep":"#9e2a25","onAccent":"#ffffff","tagline":"La valeur réelle de chaque propriété"},{"id":"auto-ka","wordmark":"Auto·Ka","domain":"www.auto-ka.com","accent":"#ff5a2a","accentSoft":"#ffe8de","accentDeep":"#cc3f16","onAccent":"#ffffff","tagline":"Les voitures usagées du Québec"},{"id":"fabri-ka","wordmark":"Fabri·Ka","domain":"www.fabri-ka.com","accent":"#c4532e","accentSoft":"#f7e3da","accentDeep":"#a94525","onAccent":"#ffffff","tagline":"Les produits fabriqués au Québec"},{"id":"food-ka","wordmark":"Food·Ka","domain":"www.food-ka.com","accent":"#1f9d55","accentSoft":"#e2f5ea","accentDeep":"#157a40","onAccent":"#ffffff","tagline":"Les prix d'épicerie, suivis à la source"},{"id":"resto-ka","wordmark":"Resto·Ka","domain":"www.resto-ka.com","accent":"#f08c00","accentSoft":"#fdeed7","accentDeep":"#b96a00","onAccent":"#141814","tagline":"Chaque resto, chaque plat, chaque prix"},{"id":"sorti-ka","wordmark":"Sorti·Ka","domain":"www.sorti-ka.com","accent":"#d6336c","accentSoft":"#fbe0eb","accentDeep":"#a12551","onAccent":"#ffffff","tagline":"Toutes les sorties, dans les 17 régions"},{"id":"crea-ka","wordmark":"Créa·Ka","domain":"www.crea-ka.com","accent":"#7048e8","accentSoft":"#ece5fc","accentDeep":"#5433b8","onAccent":"#ffffff","tagline":"Les créateurs d'ici, tous leurs liens"},{"id":"api-ka","wordmark":"API·Ka","domain":"www.api-ka.com","accent":"#3b5bdb","accentSoft":"#e4eafb","accentDeep":"#2b44a8","onAccent":"#ffffff","tagline":"La donnée de l'écosystème, par API"},{"id":"job-ka","wordmark":"Job·Ka","domain":"www.job-ka.com","accent":"#0c8599","accentSoft":"#def0f4","accentDeep":"#095c6b","onAccent":"#ffffff","tagline":"Tous les emplois des employeurs québécois"},{"id":"ka-stats","wordmark":"Ka·Stats","domain":"www.ka-stats.com","accent":"#095797","accentSoft":"#e2edf6","accentDeep":"#063a63","onAccent":"#ffffff","tagline":"L'explorateur de statistiques du Québec"}],"extraFooterLinks":[{"label":"ValoPlex","href":"https://www.valoplex.com"},{"label":"Ka2","href":"https://www.ka2.bot"},{"label":"Ka4","href":"https://www.ka4.bot"},{"label":"Ka6","href":"https://www.ka6.bot"}]};1297const KA_SITE=KA_ECO.sites.find(s=>s.id==="crea-ka");1298const $=s=>document.querySelector(s);1299const app=$("#app");1300const fmt=new Intl.NumberFormat("fr-CA",{notation:"compact",maximumFractionDigits:1});1301const fmtFull=new Intl.NumberFormat("fr-CA");1302const PLAT={1303 instagram:{label:"Instagram",color:"#c13584",av:"instagram"},1304 tiktok:{label:"TikTok",color:"#141814",av:"tiktok"},1305 youtube:{label:"YouTube",color:"#e02424",av:"youtube"},1306 twitch:{label:"Twitch",color:"#9146ff",av:"twitch"},1307 kick:{label:"Kick",color:"#141814"},1308 x:{label:"X (Twitter)",color:"#141814",av:"twitter"},1309 facebook:{label:"Facebook",color:"#1877f2"},1310 snapchat:{label:"Snapchat",color:"#d4a900"},1311 substack:{label:"Substack",color:"#ff6719"},1312 patreon:{label:"Patreon",color:"#f1465a"},1313 onlyfans:{label:"OnlyFans",color:"#00aff0"},1314 mym:{label:"MYM",color:"#141814"},1315 fansly:{label:"Fansly",color:"#2699f7"},1316 linkedin:{label:"LinkedIn",color:"#0a66c2"},1317 threads:{label:"Threads",color:"#141814"},1318 spotify:{label:"Spotify",color:"#1db954"},1319 discord:{label:"Discord",color:"#5865f2"},1320 podcast:{label:"Balado",color:"#8940fa"},1321 "site-web":{label:"Site web",color:"#7048e8"},1322 autre:{label:"Autre",color:"#8b928c"}};1323const NICHE_LBL={"humour":"Humour","mode":"Mode","beaute":"Beauté","lifestyle":"Lifestyle",1324 "famille-parentalite":"Famille","cuisine":"Cuisine","gaming":"Gaming","tech":"Tech",1325 "sport-fitness":"Sport & fitness","plein-air":"Plein air","voyage":"Voyage","musique":"Musique",1326 "arts":"Arts","danse":"Danse","education":"Éducation","finance-affaires":"Finance & affaires",1327 "sante-mieux-etre":"Santé & mieux-être","bouffe-resto":"Bouffe & resto",1328 "actualite-opinion":"Actualité & opinion","autre":"Autre"};1329const TIER_LBL={nano:"Nano",micro:"Micro",macro:"Macro",mega:"Méga"};1330const state={q:"",niche:"",region:"",langue:"",plateforme:"",tier:"",sort:"reach",1331 offset:0,items:[],total:0,stats:null,tax:null};13321333const esc=s=>String(s??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));1334const initials=n=>n.split(/\s+/).slice(0,2).map(w=>w[0]||"").join("").toUpperCase();1335async function api(p){const r=await fetch(p);if(!r.ok)throw new Error(r.status);return r.json()}1336function icon(p,extra){return `<svg class="icn" viewBox="0 0 24 24" aria-hidden="true"${extra||""}><path d="${ICONS[p]||ICONS.autre}"/></svg>`}1337const 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>`;13381339/* ---------- favoris ♥ « Mon univers Ka » — magasin central : hub groupe-ka.com1340 (AUCUN stockage local : toggle synchrone via /api/favorites/toggle, lecture1341 /api/favorites — cache serveur 30 s). item_id = id stable du créateur. ---------- */1342const heartSvg=`<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 20.5S4.5 15.7 2.6 11.4C1.2 8.2 2.9 4.9 6.1 4.4c2-.3 3.9.6 5.9 2.9 2-2.3 3.9-3.2 5.9-2.9 3.2.5 4.9 3.8 3.5 7-1.9 4.3-9.4 9.1-9.4 9.1z"/></svg>`;1343const FAV={ids:new Set(),loaded:false,src:{}};1344function favItem(c){1345 const main=(c.platforms||[]).slice().sort((x,y)=>(y.followers||0)-(x.followers||0))[0];1346 const sub=main?(PLAT[main.platform]||PLAT.autre).label:(c.region||"");1347 return {item_id:c.id,title:c.display_name||c.id,1348 subtitle:sub,image_url:avatarSources(c)[0]||"",1349 url:"https://www.crea-ka.com/createur/"+encodeURIComponent(c.id)};1350}1351function favBtn(c){1352 FAV.src[c.id]=favItem(c);1353 const on=FAV.ids.has(c.id);1354 return `<button type="button" class="favbtn${on?" on":""}" data-fav="${esc(c.id)}"1355 aria-pressed="${on}" aria-label="${on?"Retirer de mes favoris":"Ajouter à mes favoris"}"1356 title="${on?"Retirer de mes favoris":"Ajouter à mes favoris"}"1357 onclick="event.stopPropagation();favToggle(this.dataset.fav,this)">${heartSvg}</button>`;1358}1359function favPaint(){1360 document.querySelectorAll("[data-fav]").forEach(b=>{1361 const on=FAV.ids.has(b.dataset.fav);1362 b.classList.toggle("on",on);b.setAttribute("aria-pressed",on);1363 const lbl=on?"Retirer de mes favoris":"Ajouter à mes favoris";1364 b.title=lbl;b.setAttribute("aria-label",lbl);1365 const t=b.querySelector(".fav-cta-txt");if(t)t.textContent=lbl;1366 });1367}1368async function favLoad(){1369 if(FAV.loaded||!state.me)return favPaint();1370 try{const r=await fetch("/api/favorites");1371 if(r.ok){const j=await r.json();FAV.ids=new Set(j.ids||[]);FAV.loaded=true;}1372 }catch(e){}1373 favPaint();1374}1375window.favToggle=async(id,el)=>{1376 if(state.me===null){location.href="/api/auth/ka/login";return}1377 const on=!FAV.ids.has(id);1378 if(el)el.disabled=true;1379 try{1380 const r=await fetch("/api/favorites/toggle",{method:"POST",1381 headers:{"Content-Type":"application/json"},1382 body:JSON.stringify({on,item:FAV.src[id]||{item_id:id}})});1383 if(r.status===401){location.href="/api/auth/ka/login";return}1384 if(!r.ok)throw new Error(r.status);1385 if(on)FAV.ids.add(id);else FAV.ids.delete(id);1386 if(!on&&location.pathname==="/favoris"&&el){1387 const card=el.closest(".card");if(card)card.remove();1388 const grid=$("#fav-grid");1389 if(grid&&!grid.querySelector(".card"))renderFavoris();1390 }1391 }catch(e){alert("Impossible de synchroniser le favori avec le hub Groupe KA — réessayez.")}1392 if(el)el.disabled=false;1393 favPaint();1394};13951396/* cascade de photos : avatar capté à la source, puis unavatar par plateforme,1397 repli final = initiales sur motif encre/violet (déjà en dessous) */1398function avatarSources(c){1399 const order=["instagram","tiktok","youtube","twitch","x"];1400 const srcs=[];1401 if(c.avatar_url)srcs.push(c.avatar_url);1402 for(const p of order){1403 const acc=(c.platforms||[]).find(a=>a.platform===p&&PLAT[p]&&PLAT[p].av);1404 if(acc)srcs.push(`https://unavatar.io/${PLAT[p].av}/${encodeURIComponent(acc.handle)}?fallback=false`);1405 }1406 return srcs;1407}1408window.nextSrc=el=>{1409 const srcs=JSON.parse(el.dataset.srcs||"[]");const i=+(el.dataset.i||0)+1;1410 if(i<srcs.length){el.dataset.i=i;el.src=srcs[i]}else el.remove();1411};1412function photoImg(c){1413 const srcs=avatarSources(c);1414 if(!srcs.length)return"";1415 return `<img loading="lazy" alt="${esc(c.display_name)}" src="${srcs[0]}"1416 data-srcs='${esc(JSON.stringify(srcs))}' data-i="0" onerror="nextSrc(this)">`;1417}1418const 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>`;14191420function header(){1421 return `<header class="header"><div class="container header-inner">1422 <a href="/" data-nav class="brand">${brandSvg}Créa<span class="ka">Ka</span></a>1423 <a class="gk-badge" href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer"1424 aria-label="Un service Groupe KA — visiter le portail de l'écosystème"><span class="pfx">Un service</span>1425 <b>Groupe<span class="ka">KA</span></b></a>1426 <span class="brand-tag">Tous les créateurs québécois.<br>Tous leurs liens.</span>1427 <span class="header-spacer"></span>1428 <a class="hbtn" href="/stats" data-nav>Statistiques</a>1429 <a class="hbtn" href="/favoris" data-nav>♥ Favoris</a>1430 <a class="hbtn" href="/contact" data-nav>Contact</a>1431 <a class="hbtn primary" href="/#annuaire" data-nav>Explorer l'annuaire</a>1432 <span id="acct-slot"></span>1433 </div></header>`;1434}1435/* jeton de compte KA ID dans l'en-tête (connecté → chip, sinon → bouton hub) */1436async function loadAccount(){1437 const slot=$("#acct-slot");if(!slot)return;1438 if(state.me===undefined){1439 try{const r=await fetch("/api/me");state.me=r.ok?await r.json():null}1440 catch(e){state.me=null}}1441 const me=state.me;1442 if(me){1443 slot.innerHTML=`<a class="hbtn acct" href="/compte" data-nav>1444 ${me.picture?`<img src="${esc(me.picture)}" alt="">`:""}${esc((me.name||"").split(" ")[0])}1445 <span class="ka-badge">KA</span></a>`;1446 }else{1447 slot.innerHTML=`<a class="hbtn acct" href="/api/auth/ka/login"1448 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>`;1449 }1450 bindNav(slot);1451 if(me){favSlotPaint();favLoad();}1452 else{FAV.ids=new Set();FAV.loaded=false;favSlotPaint();favPaint();}1453}1454/* CTA favori de la fiche créateur : ♥ si connecté, sinon « Se connecter avec KA ID » */1455function favSlotPaint(){1456 const s=document.getElementById("fav-slot");if(!s)return;1457 const c=state.creator;if(!c)return;1458 if(state.me){1459 FAV.src[c.id]=favItem(c);1460 const on=FAV.ids.has(c.id);1461 s.innerHTML=`<button type="button" class="fav-cta${on?" on":""}" data-fav="${esc(c.id)}"1462 aria-pressed="${on}" onclick="favToggle(this.dataset.fav,this)">${heartSvg}<span class="fav-cta-txt">${on?"Retirer de mes favoris":"Ajouter à mes favoris"}</span></button>`;1463 }else if(state.me===null){1464 s.innerHTML=`<a class="fav-cta" href="/api/auth/ka/login"1465 title="Un seul compte pour tout le Groupe KA — favoris partagés « Mon univers Ka »">${heartSvg}<span>Se connecter avec KA ID</span></a>`;1466 }1467}1468function tabbar(active){1469 const t=(href,label,svg,key)=>`<a href="${href}" data-nav class="${active===key?"active":""}">${svg}<span>${label}</span></a>`;1470 return `<nav class="tabbar">1471 ${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")}1472 ${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")}1473 ${t("/favoris","Favoris",`<svg viewBox="0 0 24 24"><path d="M12 20.5S4.5 15.7 2.6 11.4C1.2 8.2 2.9 4.9 6.1 4.4c2-.3 3.9.6 5.9 2.9 2-2.3 3.9-3.2 5.9-2.9 3.2.5 4.9 3.8 3.5 7-1.9 4.3-9.4 9.1-9.4 9.1z"/></svg>`,"fav")}1474 ${t("/stats","Stats",`<svg viewBox="0 0 24 24"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/></svg>`,"stats")}1475 ${t("/retrait","Retrait",`<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>`,"optout")}1476 </nav>`;1477}1478/* footer commun Groupe KA (équivalent vanilla de ka/KaFooter.tsx, siteId1479 "crea-ka", localLegal = /retrait) — nav locale conservée au-dessus */1480function footer(){1481 const year=new Date().getFullYear();1482 const others=KA_ECO.sites.filter(s=>s.id!=="crea-ka");1483 const localLegal=[{label:"Documentation",href:"/doc/"},{label:"Retrait (opt-out)",href:"/retrait",nav:true}];1484 return `<div class="footer-local"><div class="container"><nav>1485 <span class="klabel">Créa·Ka</span>1486 <a href="/compte" data-nav>Mon compte KA ID</a>1487 <a href="/favoris" data-nav>Mes favoris</a>1488 <a href="/stats" data-nav>Statistiques</a>1489 <a href="/retrait" data-nav>Demander un retrait</a>1490 <a href="/contact" data-nav>Contact</a>1491 <a href="/api/stats" target="_blank" rel="noopener">API publique</a>1492 </nav></div></div>1493 <footer class="ka-footer" id="contact-footer">1494 <div class="container">1495 <a class="wordmark" href="${esc(KA_ECO.hub.url)}" target="_blank" rel="noopener noreferrer"1496 aria-label="Groupe KA — le portail de l'écosystème">Groupe <span class="ka">KA</span></a>1497 <p class="desc">${esc(KA_ECO.org.tagline)} Des centaines de connecteurs lisent les sites à la1498 source, normalisent la donnée et se resynchronisent seuls.1499 <b style="color:var(--paper)">${esc(KA_SITE.wordmark)}</b> — ${esc(KA_SITE.tagline)} — est un service Groupe KA.</p>1500 <p class="notice"><b>${esc(KA_ECO.org.disclaimer)}</b></p>1501 <ul class="sites">1502 ${others.map(s=>`<li><a href="https://${esc(s.domain)}" target="_blank" rel="noopener noreferrer">${esc(s.wordmark)}</a></li>`).join("")}1503 ${(KA_ECO.extraFooterLinks||[]).map(l=>`<li><a href="${esc(l.href)}" target="_blank" rel="noopener noreferrer">${esc(l.label)}</a></li>`).join("")}1504 </ul>1505 <div class="contacts">1506 ${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("")}1507 </div>1508 <p class="legal">© ${year} ${esc(KA_ECO.org.copyrightHolder)}1509 ${localLegal.map(l=>` · <a href="${esc(l.href)}"${l.nav?" data-nav":""}>${esc(l.label)}</a>`).join("")}1510 ${KA_ECO.legal.map(l=>` · <a href="${esc(l.href)}" target="_blank" rel="noopener noreferrer">${esc(l.label)}</a>`).join("")}1511 </p>1512 </div>1513 </footer>`;1514}15151516/* ---------- annuaire ---------- */1517function platIcon(a){1518 const p=PLAT[a.platform]||PLAT.autre;1519 return `<a class="plat" href="${esc(a.url)}" target="_blank" rel="noopener"1520 title="${p.label} — @${esc(a.handle)}" style="background:${p.color}"1521 onclick="event.stopPropagation()">${icon(a.platform)}</a>`;1522}1523function cardHtml(c){1524 const plats=(c.platforms||[]).slice(0,7).map(platIcon).join("");1525 const niches=(c.niches||[]).slice(0,2).map(n=>`<span class="chip">${NICHE_LBL[n]||n}</span>`).join("");1526 const verified=(c.platforms||[]).some(a=>a.verified);1527 const np=(c.platforms||[]).length;1528 return `<div class="card" data-href="/createur/${esc(c.id)}" role="link" tabindex="0" style="cursor:pointer">1529 <div class="card-photo"><span class="init">${esc(initials(c.display_name))}</span>${photoImg(c)}1530 <span class="tier ${esc(c.audience_tier)}">${TIER_LBL[c.audience_tier]||c.audience_tier}</span>1531 ${c.ka_reco?`<span class="kc-reco" title="Correspond à vos préférences KA ID — gérez-les sur groupe-ka.com/mon-ka">Recommandé pour vous</span>`:""}1532 <span class="nplat">${np} plat.</span>${favBtn(c)}</div>1533 <div class="card-body">1534 <div class="card-name">${esc(c.display_name)}${verified?checkSvg:""}</div>1535 <div class="card-sub">${c.region?`${esc(c.region)} · `:""}${(c.languages||[]).map(l=>({fr:"FR",en:"EN",bilingue:"FR/EN"}[l]||l)).join(", ")}</div>1536 <div class="chips">${niches}</div>1537 ${c.total_reach?`<div class="reach"><b>${fmt.format(c.total_reach)}</b> abonnés cumulés</div>`:""}1538 <div class="platrow">${plats}</div></div></div>`;1539}1540function selectHtml(id,label,opts,val,lbls){1541 const o=opts.map(v=>`<option value="${esc(v)}"${v===val?" selected":""}>${esc(lbls?(lbls[v]||v):v)}</option>`).join("");1542 return `<select class="f${val?" active":""}" id="${id}"><option value="">${label}</option>${o}</select>`;1543}1544async function renderHome(){1545 document.title="Créa-Ka — Annuaire des créateurs de contenu québécois";1546 if(!state.tax)state.tax=await api("/api/taxonomies");1547 if(!state.stats)state.stats=await api("/api/stats").catch(()=>null);1548 const s=state.stats||{creators:0,accounts:0,by_platform:{}};1549 app.innerHTML=header()+`1550 <section class="hero"><div class="container">1551 <span class="kicker">Annuaire des créateurs du Québec</span>1552 <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>1553 <p class="lead">Un « Linktree collectif » du Québec : influenceurs, youtubeurs, streamers,1554 podcasteurs — leurs comptes regroupés et découvrables par niche, région, langue et taille d'audience.</p>1555 <div class="stats-row">1556 <span class="stat-pill"><b>${fmtFull.format(s.creators)}</b> créateurs</span>1557 <span class="stat-pill"><b>${fmtFull.format(s.accounts)}</b> comptes reliés</span>1558 <span class="stat-pill"><b>${Object.keys(s.by_platform||{}).length}</b> plateformes</span>1559 </div>1560 <div class="advantages">1561 <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>1562 <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>1563 <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>1564 <div class="adv"><div class="ico">🛡</div><h3>Respectueux</h3><p>Profils publics seulement, retrait (opt-out) simple et respecté. Loi 25.</p></div>1565 </div>1566 </div></section>1567 <div class="searchbar" id="annuaire"><div class="container search-wrap">1568 <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>1569 <input id="q" placeholder="Chercher un créateur…" value="${esc(state.q)}"></label>1570 <div class="filters">1571 ${selectHtml("f-niche","Niche",state.tax.niches,state.niche,NICHE_LBL)}1572 ${selectHtml("f-region","Région",state.tax.regions,state.region)}1573 ${selectHtml("f-plat","Plateforme",state.tax.plateformes,state.plateforme,Object.fromEntries(Object.entries(PLAT).map(([k,v])=>[k,v.label])))}1574 ${selectHtml("f-tier","Taille",state.tax.tiers,state.tier,TIER_LBL)}1575 ${selectHtml("f-langue","Langue",state.tax.langues,state.langue,{fr:"Français",en:"Anglais",bilingue:"Bilingue"})}1576 ${selectHtml("f-sort","Tri",["reach","nom","recent"],state.sort==="reach"?"":state.sort,{reach:"Audience",nom:"Nom A-Z",recent:"Récents"})}1577 </div>1578 </div></div>1579 <main class="container"><div class="count-line" id="countline"></div>1580 <div class="grid" id="grid"><div class="spin"></div></div>1581 <div class="more-wrap" id="more"></div></main>`+footer()+tabbar(location.hash==="#annuaire"?"search":"home");1582 bindNav();1583 $("#q").addEventListener("input",deb(()=>{state.q=$("#q").value;reload()},300));1584 [["f-niche","niche"],["f-region","region"],["f-plat","plateforme"],1585 ["f-tier","tier"],["f-langue","langue"],["f-sort","sort"]].forEach(([id,key])=>{1586 $("#"+id).addEventListener("change",e=>{state[key]=e.target.value||(key==="sort"?"reach":"");1587 e.target.classList.toggle("active",!!e.target.value);reload()})});1588 if(location.hash==="#annuaire"){const el=$("#annuaire");if(el)el.scrollIntoView()}1589 await reload();1590}1591function deb(fn,ms){let t;return()=>{clearTimeout(t);t=setTimeout(fn,ms)}}1592async function reload(more){1593 if(!more){state.offset=0;state.items=[]}1594 const p=new URLSearchParams({q:state.q,niche:state.niche,region:state.region,1595 langue:state.langue,plateforme:state.plateforme,tier:state.tier,sort:state.sort,1596 limit:"60",offset:String(state.offset)});1597 const data=await api("/api/creators?"+p).catch(()=>({items:[],total:0}));1598 state.items=state.items.concat(data.items);state.total=data.total;1599 const grid=$("#grid");if(!grid)return;1600 $("#countline").innerHTML=`<b>${fmtFull.format(state.total)}</b> créateur${state.total>1?"s":""} trouvé${state.total>1?"s":""}`;1601 grid.innerHTML=state.items.length?state.items.map(cardHtml).join(""):1602 `<div class="empty">Aucun créateur ne correspond à ces filtres.</div>`;1603 const moreDiv=$("#more");1604 moreDiv.innerHTML=state.items.length<state.total?1605 `<button class="btn btn-primary" id="loadmore">Afficher plus (${fmtFull.format(state.total-state.items.length)} restants)</button>`:"";1606 const lm=$("#loadmore");if(lm)lm.onclick=()=>{state.offset+=60;reload(true)};1607 bindNav(grid);1608}16091610/* ---------- page créateur ---------- */1611/* --- bande de contenu récent par compte (enrichissement Apify) --- */1612const _relTime=ts=>{if(!ts)return"";if(typeof ts==="string"&&!/^\d+$/.test(ts))return esc(ts.slice(0,10));1613 const d=Math.floor((Date.now()/1000-Number(ts))/86400);1614 return d<=0?"aujourd'hui":d===1?"hier":d<30?d+" j":d<365?Math.floor(d/30)+" mois":Math.floor(d/365)+" ans";};1615const _relTimeC=ts=>_relTime(ts);1616/* image de contenu : IG/CDN expirent souvent → onerror bascule sur repli icône */1617window.thumbFail=el=>{const w=el.parentElement;if(w){w.classList.add("noimg");1618 w.innerHTML=w.dataset.icon||"";}};1619function _mediaItems(a){const m=a.metrics||{};1620 const base=m.recent_posts||m.recent_videos||m.recent_tweets||[];1621 if(base.length)return base;1622 // Snapchat : les previews de story/spotlight sont des listes d'URLs d'images1623 const prev=[...(m.story_previews||[]),...(m.spotlight_previews||[])];1624 return prev.map(u=>({display_url:u,type:"story"}));}16251626/* grille de contenu récent, images/vidéos bien affichées */1627function contentGrid(a){1628 const items=_mediaItems(a);1629 if(!items.length)return `<div class="cg-empty">${icon(a.platform)} Aucun contenu récent capté pour ce compte.</div>`;1630 const ico=icon(a.platform);1631 const cards=items.slice(0,12).map(it=>{1632 const img=it.display_url||it.thumbnail_url||it.thumbnail||it.cover||(it.media_urls||[])[0]||"";1633 const isVid=it.type==="video"||it.type==="reel"||!!it.duration||it.video_views!=null;1634 const cap=esc((it.caption||it.title||it.text||"").slice(0,140));1635 const when=_relTimeC(it.timestamp||it.published_at||it.published||it.created_at);1636 const stats=[1637 it.views!=null?fmt.format(it.views)+" vues":(it.video_views!=null?fmt.format(it.video_views)+" vues":null),1638 it.likes!=null?fmt.format(it.likes)+" ❤":null,1639 it.comments!=null?fmt.format(it.comments)+" 💬":null,1640 it.retweets!=null?fmt.format(it.retweets)+" ↻":null,1641 it.shares!=null?fmt.format(it.shares)+" ↗":null,1642 ].filter(Boolean).join(" · ");1643 const thumb=img1644 ? `<span class="cg-thumb" data-icon='${esc(ico)}'><img loading="lazy" src="${esc(img)}" alt="" referrerpolicy="no-referrer" onerror="thumbFail(this)">1645 ${isVid?'<span class="cg-play">▶</span>':""}${it.duration?`<span class="cg-dur">${esc(it.duration)}</span>`:""}1646 ${it.is_pinned?'<span class="cg-pin">📌</span>':""}</span>`1647 : `<span class="cg-thumb noimg" data-icon='${esc(ico)}'>${ico}${isVid?'<span class="cg-play">▶</span>':""}</span>`;1648 return `<a class="cg-card" href="${esc(it.url||a.url)}" target="_blank" rel="noopener">1649 ${thumb}1650 <span class="cg-body">${cap?`<span class="cg-cap">${cap}</span>`:`<span class="cg-cap dim">Publication ${a.platform}</span>`}1651 <span class="cg-meta">${stats||"—"}${when?`<span class="cg-when">${when}</span>`:""}</span></span></a>`;1652 }).join("");1653 return `<div class="cg-grid">${cards}</div>`;1654}16551656/* définitions de métriques calculées, par plateforme */1657function platformKPIs(a){1658 const m=a.metrics||{}, out=[];1659 const push=(v,l,suf)=>{if(v!=null&&v!=="")out.push([typeof v==="number"?fmt.format(v)+(suf||""):esc(v)+(suf||""),l]);};1660 push(a.followers,a.platform==="discord"?"membres":a.platform==="patreon"?"patrons":"abonnés");1661 push(m.engagement_rate_pct!=null?m.engagement_rate_pct:null,"engagement","%");1662 push(m.avg_likes,"j'aime / pub (moy.)");1663 push(m.avg_views!=null?m.avg_views:m.avg_video_views,"vues / pub (moy.)");1664 push(m.avg_comments,"comm. / pub (moy.)");1665 push(m.avg_retweets,"RT / pub (moy.)");1666 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");1667 push(m.total_views,"vues totales");1668 push(m.posts,"publications");1669 push(m.videos,"vidéos");1670 push(m.likes,a.platform==="tiktok"?"j'aime cumulés":"j'aime");1671 push(m.following,"abonnements");1672 push(m.online,"en ligne");1673 push(m.boosts,"boosts");1674 push(m.patrons,"patrons");1675 push(m.live_viewers,"spectateurs (live)");1676 return out;1677}1678function platformBadges(a){1679 const m=a.metrics||{};1680 return [1681 a.verified?'<span class="abadge ok">✓ Vérifié</span>':"",1682 m.is_partner?'<span class="abadge">Partenaire</span>':"",1683 m.is_affiliate&&!m.is_partner?'<span class="abadge">Affilié</span>':"",1684 m.partnered?'<span class="abadge">Serveur partenaire</span>':"",1685 m.is_live_now?'<span class="abadge live">● En direct</span>':"",1686 m.is_seller?'<span class="abadge">Boutique</span>':"",1687 m.is_free===true?'<span class="abadge">Gratuit</span>':"",1688 m.is_monthly?'<span class="abadge">Mensuel</span>':"",1689 m.has_story?'<span class="abadge">Story active</span>':"",1690 m.has_spotlight?'<span class="abadge">Spotlight</span>':"",1691 m.is_nsfw?'<span class="abadge live">18+</span>':"",1692 m.creation_name?`<span class="abadge">${esc(String(m.creation_name).slice(0,60))}</span>`:"",1693 m.joined_date?`<span class="abadge">${esc(m.joined_date)}</span>`:"",1694 m.is_blue_verified?'<span class="abadge ok">✓ Bleu</span>':"",1695 m.country?`<span class="abadge">${esc(m.country)}</span>`:"",1696 (m.recent_games||[]).slice(0,2).map(g=>`<span class="abadge">🎮 ${esc(g)}</span>`).join(""),1697 (m.top_hashtags||[]).slice(0,3).map(h=>`<span class="abadge">#${esc(h)}</span>`).join(""),1698 ].filter(Boolean).join("");1699}17001701/* panneau d'un compte (onglet plateforme) */1702function platformPanel(a,idx,active){1703 const p=PLAT[a.platform]||PLAT.autre;1704 const conf=Math.round((a.confidence||0)*100);1705 const confCls=(a.confidence||0)>=0.75?"hi":"mid";1706 const kpis=platformKPIs(a).map(([v,l])=>`<div class="kpi"><b>${v}</b><span>${l}</span></div>`).join("");1707 const badges=platformBadges(a);1708 const stamp=(a.metrics||{})[a.platform+"_apify_at"]||a.last_checked;1709 const top=(a.metrics||{}).top_post||(a.metrics||{}).top_video||(a.metrics||{}).top_tweet;1710 return `<section class="ppanel${active?" on":""}" data-panel="${idx}">1711 <div class="ppanel-head">1712 <span class="plat" style="background:${p.color}">${icon(a.platform)}</span>1713 <div class="ppanel-id"><span class="ppanel-name">${p.label}${a.verified?checkSvg:""}</span>1714 <a class="ppanel-handle" href="${esc(a.url)}" target="_blank" rel="noopener">@${esc(a.handle)} ↗</a></div>1715 <span class="conf ${confCls}" title="Score de rattachement">${conf}%</span>1716 </div>1717 ${badges?`<div class="abadge-row">${badges}</div>`:""}1718 ${(a.metrics||{}).is_live_now&&(a.metrics||{}).live_thumbnail?`1719 <a class="live-card" href="${esc(a.url)}" target="_blank" rel="noopener">1720 <img loading="lazy" src="${esc((a.metrics||{}).live_thumbnail)}" alt="" referrerpolicy="no-referrer" onerror="this.remove()">1721 <span class="live-card-body"><span class="live-card-tag">● EN DIRECT</span>1722 ${(a.metrics||{}).live_title?`<span class="live-card-title">${esc((a.metrics||{}).live_title)}</span>`:""}1723 <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>1724 </span></a>`:""}1725 <div class="kpi-grid">${kpis||'<div class="kpi"><b>—</b><span>pas encore de métriques</span></div>'}</div>1726 ${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>`:""}1727 <h3 class="cg-title">Contenu récent</h3>1728 ${contentGrid(a)}1729 <div class="ppanel-foot">${stamp?"Dernière vérification "+esc(String(stamp).slice(0,10)):""}</div>1730 </section>`;1731}17321733window.showPtab=(i,el)=>{1734 document.querySelectorAll(".ppanel").forEach(s=>s.classList.toggle("on",s.dataset.panel==String(i)));1735 document.querySelectorAll(".ptab").forEach(t=>t.classList.remove("on"));1736 if(el)el.classList.add("on");1737 const ov=document.getElementById("ov-panel");if(ov)ov.classList.toggle("on",i==="ov");1738};17391740function computeAgg(c){1741 const P=(c.platforms||[]);1742 const reach=c.total_reach||P.reduce((s,a)=>s+(a.followers||0),0);1743 const eng=P.map(a=>(a.metrics||{}).engagement_rate_pct).filter(v=>v!=null);1744 const avgEng=eng.length?(eng.reduce((s,v)=>s+v,0)/eng.length):null;1745 const content=P.reduce((s,a)=>s+_mediaItems(a).length,0);1746 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);1747 const main=P.slice().sort((x,y)=>(y.followers||0)-(x.followers||0))[0];1748 return {reach,avgEng,content,maxCad:cad.length?Math.max(...cad):null,1749 nPlat:P.length,nVerif:P.filter(a=>a.verified).length,main};1750}17511752/* sparkline SVG (série quotidienne des snapshots — insights.history.total) */1753function sparkSvg(pts){1754 if(!pts||pts.length<2)return"";1755 const w=600,h=88,pad=4;1756 const vals=pts.map(p=>p.followers);1757 const min=Math.min(...vals),max=Math.max(...vals),span=Math.max(1,max-min);1758 const x=i=>pad+i*(w-2*pad)/(pts.length-1);1759 const y=v=>h-pad-((v-min)/span)*(h-2*pad);1760 const line=pts.map((p,i)=>`${x(i).toFixed(1)},${y(p.followers).toFixed(1)}`).join(" ");1761 const area=`${pad},${h-pad} ${line} ${(w-pad).toFixed(1)},${h-pad}`;1762 return `<div class="spark-wrap"><svg class="spark-svg" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" aria-hidden="true">1763 <polygon class="spark-fill" points="${area}"/><polyline class="spark-line" points="${line}"/></svg>1764 <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>`;1765}17661767async function renderCreator(id){1768 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("");bindNav();1769 let c;try{c=await api("/api/creators/"+encodeURIComponent(id))}1770 catch(e){app.innerHTML=header()+`<div class="container"><div class="empty"><h2>Créateur introuvable</h2>1771 <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}1772 state.creator=c;1773 document.title=`${c.display_name} — Créa-Ka`;1774 const verified=(c.platforms||[]).some(a=>a.verified);1775 const niches=(c.niches||[]).map(n=>`<span class="mchip">${NICHE_LBL[n]||n}</span>`).join("");1776 const P=(c.platforms||[]).slice().sort((x,y)=>(y.followers||0)-(x.followers||0));1777 const g=computeAgg(c);1778 const ins=c.insights||{};1779 const banner=c.banner_url||P.map(a=>(a.metrics||{}).banner).find(Boolean);1780 // bandeau de métriques CALCULÉES (multi-plateforme)1781 const kpi=(v,l)=>v==null?"":`<div class="hkpi"><b>${typeof v==="number"?fmt.format(v):esc(v)}</b><span>${l}</span></div>`;1782 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>`;1783 const heroKpis=[1784 kpi(g.reach,"abonnés cumulés"),1785 gkpi(ins.growth_7d,"abonnés / 7 j"),1786 gkpi(ins.growth_30d,"abonnés / 30 j"),1787 kpi(g.nPlat,"plateformes"),1788 kpi(ins.avg_engagement_pct!=null?ins.avg_engagement_pct+" %":(g.avgEng!=null?Math.round(g.avgEng*100)/100+" %":null),"engagement moyen"),1789 kpi(ins.publications_per_week!=null?ins.publications_per_week:g.maxCad,"pubs / semaine"),1790 kpi(g.content||null,"contenus analysés"),1791 kpi(g.main?(PLAT[g.main.platform]||PLAT.autre).label:null,"plateforme nº 1"),1792 ].filter(Boolean).join("");1793 // Ka Score /100 (insights serveur) + sparkline d'audience (snapshots)1794 const KS_MAX={audience:40,engagement:25,presence:20,rythme:10,verification:5};1795 const KS_LBL={audience:"Audience",engagement:"Engagement",presence:"Multi-plateforme",1796 rythme:"Rythme de pub.",verification:"Vérification"};1797 const ksParts=ins.ka_score_parts||{};1798 const ksHtml=ins.ka_score!=null?`<h3 class="cg-title">Ka Score</h3><div class="kascore">1799 <div class="kascore-num"><b>${ins.ka_score}</b><span>/ 100</span></div>1800 <div class="kascore-parts">${Object.keys(KS_MAX).map(k=>`<div class="kspart">1801 <span class="kspart-lab">${KS_LBL[k]}</span>1802 <span class="kspart-track"><span class="kspart-fill" style="width:${Math.min(100,Math.round((ksParts[k]||0)/KS_MAX[k]*100))}%"></span></span>1803 <span class="kspart-val">${ksParts[k]!=null?ksParts[k]:0}/${KS_MAX[k]}</span></div>`).join("")}</div>1804 </div>`:"";1805 const hist=((ins.history||{}).total)||[];1806 const sparkHtml=hist.length>1?`<h3 class="cg-title">Évolution de l'audience</h3>${sparkSvg(hist)}`:"";1807 // barres comparatives de portée par plateforme (vue d'ensemble)1808 const maxF=Math.max(1,...P.map(a=>a.followers||0));1809 const bars=P.map(a=>{const p=PLAT[a.platform]||PLAT.autre;const w=Math.round((a.followers||0)/maxF*100);1810 return `<div class="obar"><span class="obar-ico" style="background:${p.color}">${icon(a.platform)}</span>1811 <span class="obar-lab">${p.label}</span>1812 <span class="obar-track"><span class="obar-fill" style="width:${w}%;background:${p.color}"></span></span>1813 <span class="obar-val">${a.followers!=null?fmt.format(a.followers):"—"}</span></div>`;}).join("");1814 // onglets + panneaux1815 const tabs=`<button class="ptab on" onclick="showPtab('ov',this)">Vue d'ensemble</button>`+1816 P.map((a,i)=>{const p=PLAT[a.platform]||PLAT.autre;1817 return `<button class="ptab" onclick="showPtab(${i},this)">1818 <span class="ptab-ico" style="color:${p.color}">${icon(a.platform)}</span>${p.label}1819 ${a.followers!=null?`<span class="ptab-n">${fmt.format(a.followers)}</span>`:""}</button>`;}).join("");1820 const ovPanel=`<section class="ppanel on" id="ov-panel" data-panel="ov">1821 ${ksHtml}1822 <h3 class="cg-title">Portée par plateforme</h3>1823 <div class="obars">${bars||"<p class=klabel>Aucune plateforme.</p>"}</div>1824 ${sparkHtml}1825 ${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>`:""}1826 </section>`;1827 const panels=ovPanel+P.map((a,i)=>platformPanel(a,i,false)).join("");1828 const checked=(c.platforms||[]).map(a=>{const m=a.metrics||{};return m[a.platform+"_apify_at"]||a.last_checked;}).filter(Boolean).sort().pop();1829 app.innerHTML=header()+`1830 <div class="container cpage">1831 <a class="backlink" href="/" data-nav>← Tous les créateurs</a>1832 ${banner?`<div class="cbanner"><img src="${esc(banner)}" alt="" referrerpolicy="no-referrer" onerror="this.parentElement.remove()"></div>`:""}1833 <div class="cpage-head">1834 <div class="cavatar">${esc(initials(c.display_name))}${photoImg(c)}</div>1835 <div style="flex:1;min-width:200px"><h1>${esc(c.display_name)}${verified?checkSvg:""}</h1>1836 <div class="meta"><span class="mchip o">${TIER_LBL[c.audience_tier]||c.audience_tier}</span>1837 ${niches}${c.region?`<span class="mchip">${esc(c.region)}</span>`:""}1838 ${(c.languages||[]).map(l=>`<span class="mchip">${{fr:"Français",en:"Anglais",bilingue:"Bilingue"}[l]||l}</span>`).join("")}</div>1839 ${c.bio?`<p class="cpage-bio">${esc(c.bio)}</p>`:""}1840 <div id="fav-slot" style="margin-top:16px"></div></div>1841 </div>1842 <div class="hkpi-row">${heroKpis}</div>1843 ${c.notes?`<div class="cpage-notes"><span class="klabel">Contexte public</span><br>${esc(c.notes)}</div>`:""}1844 <div class="ptabs">${tabs}</div>1845 <div class="ppanels">${panels}</div>1846 <div class="cpage-foot">Sources : listes médias publiques, Wikidata, annuaires publics et profils publics des plateformes (enrichissement Apify).1847 Audiences et métriques approximatives${checked?`, dernière vérification ${esc(String(checked).slice(0,10))}`:""}.1848 Une erreur dans cette fiche ? <a href="/retrait" data-nav>Signaler / demander un retrait</a>.</div>1849 </div>`+footer()+tabbar("");1850 bindNav();window.scrollTo(0,0);1851}18521853/* ---------- page stats : tableau de bord analytique (contrat ka-stats) ---------- */1854const ST={period:"30j",from:"",to:"",data:null,hide:{},tbl:{}};1855const PERIODS_UI=[["auj","Aujourd'hui"],["7j","7 jours"],["30j","30 jours"],["3m","3 mois"],1856 ["6m","6 mois"],["12m","12 mois"],["annee","Année en cours"],["tout","Tout"]];1857const fmtPct=v=>(v>=0?"+":"")+v.toLocaleString("fr-CA",{maximumFractionDigits:1})+" %";1858const emptyBlock=t=>`<div class="empty-block"><b style="font-family:var(--font-display);font-size:14px">${esc(t)}</b>1859 <p class="klabel" style="margin:6px 0 0">Pas encore mesuré — aucune donnée disponible pour cette période.</p></div>`;18601861/* mini-tendance dans une carte KPI (série spark du contrat v2) */1862function sparkSvg(spark,hero){1863 if(!spark||spark.length<2)return"";1864 const W=120,H=28,vs=spark.map(p=>p.v);1865 const mn=Math.min(...vs),mx=Math.max(...vs),rng=(mx-mn)||1;1866 const pts=spark.map((p,i)=>`${(i/(spark.length-1)*W).toFixed(1)},${(H-2-(H-4)*((p.v-mn)/rng)).toFixed(1)}`).join(" ");1867 return `<svg class="spark" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true">1868 <polyline points="${pts}" fill="none" stroke="${hero?"var(--accent-soft)":"var(--accent)"}" stroke-width="2"/></svg>`;1869}1870function kpiHtml(k,i){1871 const v=k.value,hero=i===0;1872 const num=typeof v==="number"?(v>=1e6?fmt.format(v):fmtFull.format(v)):esc(v);1873 let delta="";1874 if(k.delta_pct!==undefined&&k.delta_pct!==null){1875 const up=(k.direction||(k.delta_pct>=0?"up":"down"))==="up";1876 delta=`<div class="tile-d ${up?"up":"down"}">${up?"▲":"▼"} ${fmtPct(k.delta_pct)} <span style="opacity:.6">vs période préc.</span></div>`;}1877 return `<div class="tile${hero?" hero-tile":""}"${typeof v==="number"?` title="${fmtFull.format(v)}"`:""}>1878 <div class="tile-v">${num}${k.unit?`<span style="font-size:.42em;opacity:.6"> ${esc(k.unit)}</span>`:""}</div>1879 <div class="tile-k">${esc(k.label)}</div>${delta}${sparkSvg(k.spark,hero)}</div>`;1880}18811882/* --- jauge demi-arc (taux, couvertures, complétude) --- */1883function gaugeHtml(g){1884 if(typeof g.value!=="number"||!g.max)return"";1885 const frac=Math.max(0,Math.min(1,g.value/g.max));1886 const R=52,C=Math.PI*R;1887 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)})">1888 <svg viewBox="0 0 120 68" role="img" aria-label="${esc(g.label)}">1889 <path d="M8 60 A${R} ${R} 0 0 1 112 60" fill="none" stroke="var(--line)" stroke-width="11" stroke-linecap="round"/>1890 ${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"1891 stroke-dasharray="${(frac*C).toFixed(1)} ${(C+20).toFixed(1)}"/>`:""}1892 </svg>1893 <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>1894 <div class="g-l">${esc(g.label)}</div></div>`;1895}18961897/* --- statistiques d'une série : min / max / moyenne / médiane / écart-type --- */1898function statSummary(s){1899 const vs=(s.points||[]).map(p=>p.v).filter(v=>typeof v==="number");1900 if(vs.length<2)return"";1901 const sv=[...vs].sort((a,b)=>a-b),mean=vs.reduce((a,b)=>a+b,0)/vs.length;1902 const med=sv[Math.floor(sv.length/2)];1903 const sd=Math.sqrt(vs.reduce((a,v)=>a+(v-mean)*(v-mean),0)/vs.length);1904 const f=v=>fmtFull.format(Math.round(v*100)/100);1905 return `<div class="statsum"><span class="chip">min<b>${f(sv[0])}</b></span>1906 <span class="chip">max<b>${f(sv[sv.length-1])}</b></span>1907 <span class="chip">moyenne<b>${f(mean)}</b></span>1908 <span class="chip">médiane<b>${f(med)}</b></span>1909 <span class="chip">écart-type σ<b>${f(sd)}</b></span></div>`;1910}19111912/* --- barres verticales : série kind=bar (volumes/jour) & histogrammes (bins) --- */1913function vbarsHtml(spec){1914 const pts=(spec.points||(spec.bins||[]).map(b=>({t:b.label,v:b.value})))1915 .filter(p=>typeof p.v==="number");1916 if(!pts.length||!pts.some(p=>p.v>0))return emptyBlock(spec.title);1917 const W=720,H=250,PL=54,PR=12,PT=14,PB=40,cw=W-PL-PR,ch=H-PT-PB;1918 const vmax=Math.max(...pts.map(p=>p.v),1);1919 let grid="";1920 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax*(4-k)/4;1921 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1922 <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>`;}1923 const n=pts.length,bw=Math.max(2,cw/n-3);1924 const bars=pts.map((p,i)=>{const bh=ch*p.v/vmax,x=PL+cw*i/n+1.5;1925 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)}"1926 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("");1927 const lblIdx=n<=10?pts.map((_,i)=>i):[0,Math.floor(n/2),n-1];1928 const xl=lblIdx.map(i=>`<text x="${(PL+cw*i/n+bw/2).toFixed(1)}" y="${H-8}" text-anchor="middle"1929 font-size="${n<=10?10:10}" fill="var(--ink-3)" font-family="var(--font-mono)">${esc(String(pts[i].t))}</text>`).join("");1930 return `<div class="viz-card"><h2>${esc(spec.title)}</h2>1931 ${spec.unit?`<div class="viz-sub">${esc(spec.unit)}</div>`:'<div style="height:10px"></div>'}1932 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(spec.title)}">${grid}${bars}${xl}</svg></div>`;1933}19341935/* --- multi-courbes (≤ 4) : identité par MOTIF de trait en plus de la couleur --- */1936const ML_STYLES=[1937 {stroke:"var(--accent)",w:2.6,dash:"",cls:"",name:"trait plein accent"},1938 {stroke:"var(--ink)",w:2,dash:"",cls:"ml1",name:"trait plein encre"},1939 {stroke:"var(--accent)",w:2.2,dash:"7 4",cls:"ml2",name:"tirets accent"},1940 {stroke:"var(--ink-3)",w:2,dash:"2 3",cls:"ml3",name:"pointillé gris"}];1941function multiFigure(ms,hide){1942 const series=(ms.series||[]).filter(s=>(s.points||[]).length>1).slice(0,4);1943 if(!series.length)return emptyBlock(ms.title);1944 const W=720,H=250,PL=54,PR=12,PT=14,PB=26,cw=W-PL-PR,ch=H-PT-PB;1945 const shown=series.filter((_,i)=>!hide[i]);1946 const all=(shown.length?shown:series).flatMap(s=>s.points.map(p=>p.v));1947 const vmax=Math.max(...all,1),vmin=Math.min(0,...all),rng=(vmax-vmin)||1;1948 let grid="";1949 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax-(vmax-vmin)*k/4;1950 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>1951 <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>`;}1952 const X=(i,n)=>PL+cw*i/((n-1)||1),Y=v=>PT+ch*(1-(v-vmin)/rng);1953 const paths=series.map((s,si)=>{if(hide[si])return"";1954 const st=ML_STYLES[si];1955 const d=s.points.map((p,i)=>`${i?"L":"M"}${X(i,s.points.length).toFixed(1)},${Y(p.v).toFixed(1)}`).join("");1956 return `<path d="${d}" fill="none" stroke="${st.stroke}" stroke-width="${st.w}"${st.dash?` stroke-dasharray="${st.dash}"`:""}/>`;}).join("");1957 const ref=series[0].points;1958 const xl=[0,Math.floor(ref.length/2),ref.length-1].map(i=>1959 `<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("");1960 const leg=series.map((s,si)=>`<button type="button" class="leg-btn" data-mls="${si}" aria-pressed="${!hide[si]}"1961 title="${ML_STYLES[si].name}"><span class="leg-swatch ${ML_STYLES[si].cls}"></span>${esc(s.label)}</button>`).join("");1962 return `<div class="viz-card">1963 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">1964 <h2>${esc(ms.title)}</h2><span class="lc-legend">${leg}</span></div>1965 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(ms.title)}" style="margin-top:10px">1966 ${grid}${xl}${paths}1967 <g class="lc-cursor" style="display:none"><line y1="${PT}" y2="${H-PB}" stroke="var(--ink)" stroke-dasharray="2 3"/></g>1968 </svg><span class="chip lc-tip"> </span></div>`;1969}1970function mountMulti(el,ms){1971 const hide=ST.hide["ml:"+ms.id]||(ST.hide["ml:"+ms.id]={});1972 const series=(ms.series||[]).filter(s=>(s.points||[]).length>1).slice(0,4);1973 const render=()=>{1974 el.innerHTML=multiFigure(ms,hide);1975 el.querySelectorAll("[data-mls]").forEach(b=>b.addEventListener("click",()=>{1976 hide[b.dataset.mls]=!hide[b.dataset.mls];render();}));1977 const svg=el.querySelector("svg");if(!svg||!series.length)return;1978 const W=720,PL=54,PR=12,n=series[0].points.length;1979 const cursor=el.querySelector(".lc-cursor"),tip=el.querySelector(".lc-tip");1980 const cl=cursor?cursor.querySelector("line"):null;1981 svg.addEventListener("pointermove",e=>{1982 const r=svg.getBoundingClientRect();1983 const fx=(e.clientX-r.left)/r.width*W;1984 let i=Math.round((fx-PL)/(W-PL-PR)*(n-1));i=Math.max(0,Math.min(n-1,i));1985 const x=(PL+(W-PL-PR)*i/((n-1)||1)).toFixed(1);1986 if(cl){cl.setAttribute("x1",x);cl.setAttribute("x2",x);cursor.style.display="";}1987 const vals=series.map((s,si)=>hide[si]?null:`${esc(s.label)} : <b> ${fmtFull.format((s.points[i]||{}).v??0)}</b>`).filter(Boolean).join(" · ");1988 tip.innerHTML=`${esc(series[0].points[i].t)} — ${vals}${ms.unit?` <span style="color:var(--ink-3)">${esc(ms.unit)}</span>`:""}`;1989 tip.style.visibility="visible";});1990 svg.addEventListener("pointerleave",()=>{if(cursor)cursor.style.display="none";tip.style.visibility="hidden"});1991 };1992 render();1993}19941995/* --- barres empilées (composition dans le temps, nuances d'accent) --- */1996function stackedHtml(st){1997 const keys=(st.keys||[]).slice(0,6),pts=st.points||[];1998 if(!keys.length||!pts.length)return emptyBlock(st.title);1999 const totals=pts.map(p=>(p.values||[]).slice(0,keys.length).reduce((a,b)=>a+(b||0),0));2000 if(!totals.some(t=>t>0))return emptyBlock(st.title);2001 const W=720,H=250,PL=54,PR=12,PT=14,PB=26,cw=W-PL-PR,ch=H-PT-PB;2002 const vmax=Math.max(...totals,1),shades=[1,.78,.58,.42,.3,.22];2003 let grid="";2004 for(let k=0;k<5;k++){const y=PT+ch*k/4,v=vmax*(4-k)/4;2005 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>2006 <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>`;}2007 const n=pts.length,bw=Math.max(2,cw/n-3);2008 const bars=pts.map((p,i)=>{let y=PT+ch;const x=PL+cw*i/n+1.5;2009 return keys.map((k,j)=>{const v=(p.values||[])[j]||0;if(!v)return"";2010 const bh=ch*v/vmax;y-=bh;2011 return `<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(bh-.4,.5).toFixed(1)}"2012 fill="var(--accent)" fill-opacity="${shades[j%6]}" stroke="var(--paper)" stroke-width="0.4">2013 <title>${esc(p.t)} · ${esc(k)} — ${fmtFull.format(v)}${st.unit?" "+esc(st.unit):""} (total ${fmtFull.format(totals[i])})</title></rect>`;}).join("");}).join("");2014 const lblIdx=[0,Math.floor(n/2),n-1];2015 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("");2016 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("");2017 return `<div class="viz-card">2018 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">2019 <h2>${esc(st.title)}</h2><span class="lc-legend">${leg}</span></div>2020 <svg viewBox="0 0 ${W} ${H}" role="img" aria-label="${esc(st.title)}" style="margin-top:10px">${grid}${bars}${xl}</svg></div>`;2021}20222023/* --- heatmap horaire 7 × 24 (dow 0=lun … 6=dim) --- */2024function hourHeatHtml(h){2025 const cells=h&&h.cells||[];2026 if(!cells.length)return emptyBlock(h?h.title:"Activité horaire");2027 const grid=new Map(cells.map(c=>[c.dow+":"+c.hour,c.value||0]));2028 const max=Math.max(...cells.map(c=>c.value||0),1);2029 const CS=22,LX=42,LY=18,DOWS=["Lun","Mar","Mer","Jeu","Ven","Sam","Dim"];2030 let out="";2031 [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>`;});2032 for(let d=0;d<7;d++){2033 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>`;2034 for(let hh=0;hh<24;hh++){const v=grid.get(d+":"+hh)||0;2035 out+=`<rect x="${LX+hh*CS}" y="${LY+d*CS}" width="${CS-2.5}" height="${CS-2.5}" rx="3.5"2036 fill="${v?"var(--accent)":"rgba(20,24,20,0.06)"}" fill-opacity="${v?(0.2+0.8*v/max).toFixed(2):1}"2037 stroke="rgba(20,24,20,0.14)" stroke-width="0.5"><title>${DOWS[d]} ${hh} h — ${fmtFull.format(v)}</title></rect>`;}}2038 return `<div class="viz-card hh"><h2>${esc(h.title)}</h2><div class="viz-sub">7 jours × 24 heures (heure de l'Est)</div>2039 <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>`;2040}20412042/* --- courbe SVG : infobulle (pointeur), légende cliquable, comparaison N-1 --- */2043function lineGeom(s,hide){2044 const W=720,H=240,PL=54,PR=12,PT=14,PB=26,pts=s.points||[];2045 const all=[...(hide.cur?[]:pts),...(!hide.cmp&&s.compare?s.compare:[])];2046 const vmax=Math.max(...all.map(p=>p.v),1),vmin=Math.min(0,...all.map(p=>p.v));2047 const X=(i,n)=>PL+(W-PL-PR)*i/((n||pts.length)-1);2048 const Y=v=>PT+(H-PT-PB)*(1-(v-vmin)/((vmax-vmin)||1));2049 return{W,H,PL,PR,PT,PB,vmax,vmin,X,Y};2050}2051function lineFigure(s,hide){2052 const pts=s.points||[];2053 if(pts.length<2)return emptyBlock(s.title);2054 const g=lineGeom(s,hide),{W,H,PL,PR,PT,PB,vmax,vmin,X,Y}=g;2055 const path=arr=>arr.map((p,i)=>`${i?"L":"M"}${X(i,arr.length).toFixed(1)},${Y(p.v).toFixed(1)}`).join("");2056 let grid="";2057 for(let k=0;k<5;k++){const y=PT+(H-PT-PB)*k/4,v=vmax-(vmax-vmin)*k/4;2058 grid+=`<line x1="${PL}" x2="${W-PR}" y1="${y.toFixed(1)}" y2="${y.toFixed(1)}" stroke="var(--line)"/>2059 <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>`;}2060 const xl=[0,Math.floor(pts.length/2),pts.length-1].map(i=>2061 `<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("");2062 return `<div class="viz-card">2063 <div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;align-items:center">2064 <h2>${esc(s.title)}</h2>2065 <span class="lc-legend">2066 <button type="button" class="leg-btn" data-leg="cur" aria-pressed="${!hide.cur}"><span class="leg-swatch"></span>Période courante</button>2067 ${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>`:""}2068 </span></div>2069 <svg viewBox="0 0 ${g.W} ${g.H}" role="img" aria-label="${esc(s.title)}" style="margin-top:10px">2070 ${grid}${xl}2071 ${!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"/>`:""}2072 ${!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"/>`:""}2073 ${!hide.cur?`<path d="${path(pts)}" fill="none" stroke="var(--accent)" stroke-width="2.4"/>`:""}2074 <g class="lc-cursor" style="display:none">2075 <line y1="${PT}" y2="${H-PB}" stroke="var(--ink)" stroke-dasharray="2 3"/>2076 <circle r="4" fill="var(--accent)" stroke="var(--ink)" stroke-width="1.5"/></g>2077 </svg><span class="chip lc-tip"> </span>`+statSummary(s)+2078 `</div>`;2079}2080function mountLine(el,s){2081 if(s.kind==="bar"){el.innerHTML=vbarsHtml(s);return;}2082 const hide=ST.hide[s.id]||(ST.hide[s.id]={cur:false,cmp:false});2083 const render=()=>{2084 el.innerHTML=lineFigure(s,hide);2085 el.querySelectorAll("[data-leg]").forEach(b=>b.addEventListener("click",()=>{2086 hide[b.dataset.leg]=!hide[b.dataset.leg];render();}));2087 const svg=el.querySelector("svg");if(!svg)return;2088 const pts=s.points,g=lineGeom(s,hide);2089 const cursor=el.querySelector(".lc-cursor"),tip=el.querySelector(".lc-tip");2090 const cl=cursor.querySelector("line"),cc=cursor.querySelector("circle");2091 svg.addEventListener("pointermove",e=>{2092 const r=svg.getBoundingClientRect();2093 const fx=(e.clientX-r.left)/r.width*g.W;2094 let i=Math.round((fx-g.PL)/(g.W-g.PL-g.PR)*(pts.length-1));2095 i=Math.max(0,Math.min(pts.length-1,i));2096 const x=g.X(i).toFixed(1);2097 cl.setAttribute("x1",x);cl.setAttribute("x2",x);2098 cc.setAttribute("cx",x);cc.setAttribute("cy",g.Y(pts[i].v).toFixed(1));2099 cursor.style.display="";2100 const cmp=s.compare&&!hide.cmp&&s.compare[i]?` · N-1 : ${fmtFull.format(s.compare[i].v)}`:"";2101 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>`;2102 tip.style.visibility="visible";});2103 svg.addEventListener("pointerleave",()=>{cursor.style.display="none";tip.style.visibility="hidden"});2104 };2105 render();2106}21072108/* --- anneau (top 7 + « Autres ») --- */2109function donutHtml(b){2110 let rows=(b.items||[]).filter(i=>i.value>0);2111 if(!rows.length)return emptyBlock(b.title);2112 if(rows.length>8){const rest=rows.slice(7).reduce((s,r)=>s+r.value,0);2113 rows=rows.slice(0,7);rows.push({label:"Autres",value:rest});}2114 const total=rows.reduce((s,r)=>s+r.value,0);2115 const R=74,C=2*Math.PI*R,shades=[1,.78,.58,.42,.3,.22,.15,.1];let acc=0;2116 const segs=rows.map((r,i)=>{const frac=r.value/total,off=acc;acc+=frac;2117 return `<circle cx="100" cy="100" r="${R}" fill="none" stroke="var(--accent)" stroke-opacity="${shades[i%8]}"2118 stroke-width="30" stroke-dasharray="${(frac*C).toFixed(2)} ${C.toFixed(2)}" stroke-dashoffset="${(-off*C).toFixed(2)}"2119 transform="rotate(-90 100 100)"><title>${esc(r.label)} — ${fmtFull.format(r.value)}</title></circle>`;}).join("");2120 const leg=rows.map((r,i)=>`<li><span class="dsw" style="opacity:${shades[i%8]}"></span>2121 <span class="dlbl" title="${esc(r.label)} — ${fmtFull.format(r.value)}">${esc(r.label)}</span>2122 <b>${(100*r.value/total).toFixed(1).replace(".",",")} %</b></li>`).join("");2123 return `<div class="viz-card"><h2>${esc(b.title)}</h2><div class="donut-wrap">2124 <svg viewBox="0 0 200 200" role="img" aria-label="${esc(b.title)}" style="width:180px;max-width:100%">${segs}2125 <circle cx="100" cy="100" r="${R}" fill="none" stroke="var(--ink)" opacity=".5"/></svg>2126 <ul class="donut-legend">${leg}</ul></div></div>`;2127}21282129/* --- barres horizontales (répartitions, géo) --- */2130function barsHtml(title,items,sub){2131 const rows=(items||[]).slice(0,14);2132 if(!rows.length)return emptyBlock(title);2133 const max=Math.max(...rows.map(r=>r.value),1);2134 return `<div class="viz-card"><h2>${esc(title)}</h2>${sub?`<div class="viz-sub">${esc(sub)}</div>`:`<div style="height:14px"></div>`}2135 ${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)`:""}">2136 <span class="hrow-label">${esc(r.label)}</span>2137 <span class="hbar"><i style="width:${Math.max(2,Math.round(r.value/max*100))}%"></i></span>2138 <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>`;2139}21402141/* --- calendrier de chaleur (26 dernières semaines) --- */2142function heatmapHtml(h){2143 const cells=h&&h.cells||[];2144 if(!cells.length)return emptyBlock(h?h.title:"Activité");2145 const byDate=new Map(cells.map(c=>[c.date,c.value]));2146 const dates=cells.map(c=>c.date).sort();2147 const end=new Date(dates[dates.length-1]+"T12:00:00");2148 const max=Math.max(...cells.map(c=>c.value),1);2149 const weeks=26,cols=[];const cur=new Date(end);2150 cur.setDate(cur.getDate()-(weeks*7-1));2151 for(let w=0;w<weeks;w++){const col=[];2152 for(let d=0;d<7;d++){const iso=cur.toISOString().slice(0,10);2153 col.push({date:iso,v:byDate.get(iso)||0});cur.setDate(cur.getDate()+1);}2154 cols.push(col);}2155 const rects=cols.map((col,w)=>col.map((c,d)=>2156 `<rect x="${w*14}" y="${d*14}" width="12" height="12" rx="2.5"2157 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}"2158 stroke="rgba(20,24,20,0.15)" stroke-width="0.5"><title>${c.date} — ${fmtFull.format(c.v)}</title></rect>`).join("")).join("");2159 return `<div class="viz-card hm"><h2>${esc(h.title)}</h2><div class="viz-sub">26 dernières semaines</div>2160 <div class="tbl-wrap"><svg viewBox="0 0 ${weeks*14} ${7*14}" role="img" aria-label="${esc(h.title)}">${rects}</svg></div></div>`;2161}21622163/* --- tableau : tri par colonne, recherche, pagination 25/pg --- */2164function mountTable(el,spec){2165 const st=ST.tbl[spec.id]||(ST.tbl[spec.id]={q:"",sort:null,page:0});2166 el.innerHTML=`<div class="viz-card dtable">2167 <div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center">2168 <h2>${esc(spec.title)}</h2>2169 <input class="dt-q" placeholder="Rechercher…" value="${esc(st.q)}" aria-label="Rechercher dans ${esc(spec.title)}">2170 </div><div class="dt-body"></div></div>`;2171 const body=el.querySelector(".dt-body"),qi=el.querySelector(".dt-q");2172 const num=x=>typeof x==="number"?x:parseFloat(String(x).replace(/[^\d.,-]/g,"").replace(",","."));2173 const render=()=>{2174 let rows=spec.rows||[];2175 if(st.q){const q=st.q.toLowerCase();2176 rows=rows.filter(r=>r.some(c=>String(c).toLowerCase().includes(q)));}2177 if(st.sort){const{col,dir}=st.sort;2178 rows=[...rows].sort((a,b)=>{const nx=num(a[col]),ny=num(b[col]);2179 if(!Number.isNaN(nx)&&!Number.isNaN(ny))return(nx-ny)*dir;2180 return String(a[col]).localeCompare(String(b[col]),"fr")*dir;});}2181 const pages=Math.max(1,Math.ceil(rows.length/25));2182 st.page=Math.min(st.page,pages-1);const cur=st.page;2183 body.innerHTML=`<div class="tbl-wrap" style="margin-top:12px"><table><thead><tr>2184 ${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("")}2185 </tr></thead><tbody>2186 ${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>`}2187 </tbody></table></div>2188 <div class="dtable-foot"><span class="klabel">${fmtFull.format(rows.length)} ligne${rows.length>1?"s":""}</span>2189 <span style="display:flex;gap:6px;align-items:center">2190 <button type="button" class="btn btn-ghost dt-prev"${cur===0?" disabled":""}>←</button>2191 <span class="chip">${cur+1} / ${pages}</span>2192 <button type="button" class="btn btn-ghost dt-next"${cur>=pages-1?" disabled":""}>→</button></span></div>`;2193 body.querySelectorAll("th").forEach(th=>th.addEventListener("click",()=>{2194 const col=+th.dataset.col;2195 st.sort={col,dir:st.sort&&st.sort.col===col&&st.sort.dir===1?-1:1};render();}));2196 const prev=body.querySelector(".dt-prev"),next=body.querySelector(".dt-next");2197 prev.addEventListener("click",()=>{st.page=Math.max(0,cur-1);render()});2198 next.addEventListener("click",()=>{st.page=Math.min(pages-1,cur+1);render()});2199 };2200 qi.addEventListener("input",deb(()=>{st.q=qi.value;st.page=0;render()},250));2201 render();2202}22032204/* --- barre de période + boutons PDF --- */2205function periodBar(){2206 const customOn=!!(ST.from&&ST.to);2207 return `<div class="pchips" role="group" aria-label="Période d'analyse">2208 ${PERIODS_UI.map(([id,l])=>`<button type="button" class="pchip" data-period="${id}" aria-pressed="${!customOn&&ST.period===id}">${l}</button>`).join("")}2209 <span class="pdates"><input type="date" id="pfrom" value="${esc(ST.from)}" aria-label="Du">2210 <span class="klabel">au</span><input type="date" id="pto" value="${esc(ST.to)}" aria-label="Au"></span></div>`;2211}2212function bindPeriod(){2213 document.querySelectorAll(".pchip").forEach(b=>b.addEventListener("click",()=>{2214 ST.period=b.dataset.period;ST.from="";ST.to="";loadStatsData();}));2215 const pf=$("#pfrom"),pt=$("#pto");2216 const go=()=>{if(pf.value&&pt.value){ST.from=pf.value;ST.to=pt.value;loadStatsData();}};2217 pf.addEventListener("change",go);pt.addEventListener("change",go);2218}2219/* --- menu PDF : 5 rapports (équivalent vanilla du PdfButton v2) --- */2220const PDF_MODES=[2221 ["synthese","Synthèse exécutive","Couverture, KPI, jauges & records (2-3 p.)"],2222 ["tendances","Tendances & évolution","Toutes les séries + min/max/moy/méd/σ"],2223 ["repartitions","Répartitions & géographie","Anneaux, distributions, géo, activité horaire"],2224 ["donnees","Données détaillées","Tous les tableaux en version longue (400 lignes)"]];2225function pdfActionsHtml(){2226 return `<span class="pdf-actions pdf-menu-wrap">2227 <button type="button" class="btn btn-primary" data-pdf="complet">⬇ Rapport PDF complet</button>2228 <button type="button" class="btn btn-ghost" id="pdf-more" aria-haspopup="true" aria-expanded="false">Autres rapports ▾</button>2229 <div class="pdf-menu" id="pdf-menu" role="menu">2230 ${PDF_MODES.map(([m,l,d])=>`<button type="button" role="menuitem" data-pdf="${m}">${l}<small>${d}</small></button>`).join("")}2231 </div></span>`;2232}2233function bindPdf(){2234 const url=m=>{const p=new URLSearchParams({period:ST.period,mode:m});2235 if(ST.from&&ST.to){p.set("from",ST.from);p.set("to",ST.to);}2236 return "/api/stats/report?"+p;};2237 const menu=document.getElementById("pdf-menu"),more=document.getElementById("pdf-more");2238 if(more&&menu){2239 more.addEventListener("click",e=>{e.stopPropagation();2240 const open=menu.classList.toggle("open");more.setAttribute("aria-expanded",open);2241 if(open)document.addEventListener("click",()=>{menu.classList.remove("open");2242 more.setAttribute("aria-expanded","false");},{once:true});});}2243 document.querySelectorAll("[data-pdf]").forEach(b=>{2244 b.addEventListener("click",async()=>{2245 if(menu)menu.classList.remove("open");2246 const t=b.innerHTML;b.disabled=true;2247 b.innerHTML=b.closest(".pdf-menu")?"Génération…":"⏳ Génération…";2248 try{const r=await fetch(url(b.dataset.pdf));if(!r.ok)throw new Error(r.status);2249 const blob=await r.blob();2250 const fn=((r.headers.get("Content-Disposition")||"").match(/filename="?([^";]+)/)||[])[1]||"rapport-crea-ka.pdf";2251 const a=document.createElement("a");a.href=URL.createObjectURL(blob);a.download=fn;2252 document.body.appendChild(a);a.click();a.remove();2253 setTimeout(()=>URL.revokeObjectURL(a.href),4000);}2254 catch(e){alert("Échec de la génération du PDF — réessayez.");}2255 b.disabled=false;b.innerHTML=t;});});2256}22572258async function renderStats(){2259 document.title="Statistiques — Créa-Ka";2260 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("stats");bindNav();2261 await loadStatsData(true);2262 window.scrollTo(0,0);2263}2264async function loadStatsData(first){2265 const main=$("#stats-main");if(main)main.style.opacity=".45";2266 let d;2267 try{const p=new URLSearchParams({period:ST.period});2268 if(ST.from&&ST.to){p.set("from",ST.from);p.set("to",ST.to);}2269 d=await api("/api/stats/dashboard?"+p);}2270 catch(e){app.innerHTML=header()+`<div class="container empty">Statistiques indisponibles pour le moment.</div>`+footer()+tabbar("stats");bindNav();return}2271 ST.data=d;renderStatsPage();2272}2273function renderStatsPage(){2274 const d=ST.data;2275 const upd=new Date(d.updated).toLocaleString("fr-CA",{dateStyle:"medium",timeStyle:"short"});2276 const donuts=(d.breakdowns||[]).filter(b=>b.kind==="donut");2277 const barsB=(d.breakdowns||[]).filter(b=>b.kind!=="donut");2278 const gauges=(d.gauges||[]).filter(g=>typeof g.value==="number"&&g.max);2279 const hasEvo=(d.series||[]).length||(d.multiseries||[]).length||(d.stacked||[]).length;2280 app.innerHTML=header()+`2281 <div class="container stats-page" id="stats-main">2282 <div class="stats-head">2283 <div><span class="kicker">Tableau de bord — ${esc(d.period.label)}</span>2284 <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>2285 <div class="fresh-row"><span>Mis à jour le ${esc(upd)}</span>2286 <button type="button" class="btn btn-ghost" id="st-refresh">↻ Rafraîchir</button></div></div>2287 ${pdfActionsHtml()}2288 </div>2289 <div class="tiles">${(d.kpis||[]).map(kpiHtml).join("")||emptyBlock("Indicateurs")}</div>2290 ${periodBar()}2291 <p class="klabel" style="margin:6px 0 0">Période analysée : ${esc(d.period.from)} → ${esc(d.period.to)}</p>2292 ${gauges.length?`<h2 class="stats-sect">Taux & couvertures</h2>2293 <div class="gauges">${gauges.map(gaugeHtml).join("")}</div>`:""}2294 <h2 class="stats-sect">Évolution</h2>2295 ${hasEvo?`<div class="viz-grid">2296 ${(d.series||[]).map(s=>`<div class="lc" data-serie="${esc(s.id)}"></div>`).join("")}2297 ${(d.multiseries||[]).map(m=>`<div class="lc" data-multi="${esc(m.id)}"></div>`).join("")}2298 ${(d.stacked||[]).map(st=>stackedHtml(st)).join("")}2299 </div>`:emptyBlock("Évolution")}2300 <h2 class="stats-sect">Répartitions & distributions</h2>2301 <div class="viz-grid two">2302 ${donuts.map(donutHtml).join("")}2303 ${barsB.map(b=>barsHtml(b.title,b.items)).join("")}2304 ${(d.distributions||[]).map(vbarsHtml).join("")}2305 </div>2306 ${d.geo?`<h2 class="stats-sect">Répartition géographique</h2>2307 <div class="viz-grid two">${barsHtml(d.geo.title,d.geo.items)}</div>`:""}2308 ${d.heatmap||d.hourly?`<h2 class="stats-sect">Activité</h2>2309 ${d.heatmap?heatmapHtml(d.heatmap):""}${d.hourly?hourHeatHtml(d.hourly):""}`:""}2310 ${(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>`:""}2311 ${(d.records||[]).length?`<h2 class="stats-sect">Records & faits marquants</h2>2312 <div class="records-grid">${d.records.map(r=>`<div class="record"><span class="r-lbl">${esc(r.label)}</span>2313 <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>`:""}2314 <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.2315 Rapports PDF estampillés Groupe-KA (5 formats) disponibles en haut de page.</p>2316 </div>`+footer()+tabbar("stats");2317 bindNav();bindPeriod();bindPdf();2318 $("#st-refresh").addEventListener("click",()=>loadStatsData());2319 (ST.data.series||[]).forEach(s=>{const el=document.querySelector(`.lc[data-serie="${s.id}"]`);if(el)mountLine(el,s);});2320 (ST.data.multiseries||[]).forEach(m=>{const el=document.querySelector(`.lc[data-multi="${m.id}"]`);if(el)mountMulti(el,m);});2321 (ST.data.tables||[]).forEach(t=>{const el=document.querySelector(`.dt[data-table="${t.id}"]`);if(el)mountTable(el,t);});2322}23232324/* ---------- page compte (KA ID) ---------- */2325async function renderCompte(){2326 document.title="Mon compte KA — Créa-Ka";2327 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("");bindNav();2328 if(state.me===undefined){2329 try{const r=await fetch("/api/me");state.me=r.ok?await r.json():null}2330 catch(e){state.me=null}}2331 const me=state.me;2332 if(!me){2333 app.innerHTML=header()+`2334 <div class="container compte-page">2335 <span class="kicker">KA ID — un seul compte pour tout le groupe</span>2336 <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>2337 <p style="color:var(--ink-2)">Le même compte que sur groupe-ka.com, Lou-Ka, Vrai-Prix et2338 les autres plateformes du Groupe KA. Créez-le en une minute si vous n'en avez pas.</p>2339 <div class="compte-actions">2340 <a class="btn btn-primary" href="/api/auth/ka/login">Se connecter avec KA ID</a>2341 <a class="btn btn-accent" href="https://www.groupe-ka.com/connexion" target="_blank" rel="noopener">Créer un compte KA ID ↗</a>2342 <a class="btn btn-ghost" href="https://www.groupe-ka.com" target="_blank" rel="noopener">Qu'est-ce que KA ID ?</a>2343 </div>2344 </div>`+footer()+tabbar("");2345 bindNav();loadAccount();return;2346 }2347 const init=(me.name||"?").split(/\s+/).slice(0,2).map(w=>w[0]||"").join("").toUpperCase();2348 app.innerHTML=header()+`2349 <div class="container compte-page">2350 <span class="kicker">Mon compte</span>2351 <h1 style="font-size:clamp(26px,4vw,34px);margin:12px 0;text-transform:uppercase">Carte de <span class="hl">membre.</span></h1>2352 <div class="membercard">2353 <div class="mc-brand">Groupe KA · carte de membre</div>2354 <div class="mc-row">2355 <div class="mc-avatar">${me.picture?`<img src="${esc(me.picture)}" alt="">`:esc(init)}</div>2356 <div><div class="mc-name">${esc(me.name||"Membre")}</div>2357 <div class="mc-kaid">${esc(me.ka_id||"")}</div>2358 ${me.role_label||me.role?`<span class="mc-role">${esc(me.role_label||me.role)}</span>`:""}</div>2359 </div>2360 <div class="mc-meta">2361 ${me.email?`<span><b>Courriel :</b> ${esc(me.email)}</span>`:""}2362 ${me.city?`<span><b>Ville :</b> ${esc(me.city)}</span>`:""}2363 ${me.job_title||me.company?`<span><b>Occupation :</b> ${esc([me.job_title,me.company].filter(Boolean).join(" · "))}</span>`:""}2364 ${me.bio?`<span>${esc(me.bio)}</span>`:""}2365 </div>2366 </div>2367 <div class="compte-actions">2368 <a class="btn btn-ghost" href="https://www.groupe-ka.com/compte" target="_blank" rel="noopener">Modifier sur groupe-ka.com ↗</a>2369 ${me.public_url?`<a class="btn btn-ghost" href="${esc(me.public_url)}" target="_blank" rel="noopener">Profil public ↗</a>`:""}2370 <button class="btn btn-primary" id="logout">Se déconnecter</button>2371 </div>2372 <p style="font-size:12px;color:var(--ink-3);margin-top:18px">Le profil est dicté par le hub2373 groupe-ka.com (source de vérité) ; Créa-Ka n'entrepose aucune donnée de compte.</p>2374 </div>`+footer()+tabbar("");2375 bindNav();loadAccount();2376 $("#logout").addEventListener("click",async()=>{2377 await fetch("/api/auth/logout",{method:"POST"}).catch(()=>{});2378 state.me=null;history.pushState(null,"","/");route();});2379}23802381/* ---------- page favoris — « Mon univers Ka » (hub groupe-ka.com) ---------- */2382function favCardHtml(it){2383 const href=(it.url||"").replace(/^https?:\/\/(www\.)?crea-ka\.com/i,"")2384 ||("/createur/"+encodeURIComponent(it.item_id));2385 FAV.src[it.item_id]={item_id:it.item_id,title:it.title||"",subtitle:it.subtitle||"",2386 image_url:it.image_url||"",url:it.url||""};2387 return `<div class="card" data-href="${esc(href)}" role="link" tabindex="0" style="cursor:pointer">2388 <div class="card-photo"><span class="init">${esc(initials(it.title||"?"))}</span>2389 ${it.image_url?`<img loading="lazy" alt="${esc(it.title)}" src="${esc(it.image_url)}" onerror="this.remove()">`:""}2390 <button type="button" class="favbtn on" data-fav="${esc(it.item_id)}" aria-pressed="true"2391 aria-label="Retirer de mes favoris" title="Retirer de mes favoris"2392 onclick="event.stopPropagation();favToggle(this.dataset.fav,this)">${heartSvg}</button></div>2393 <div class="card-body">2394 <div class="card-name">${esc(it.title||it.item_id)}</div>2395 ${it.subtitle?`<div class="card-sub">${esc(it.subtitle)}</div>`:""}2396 </div></div>`;2397}2398async function renderFavoris(){2399 document.title="Mes favoris — Créa-Ka";2400 app.innerHTML=header()+`<div class="container"><div class="spin"></div></div>`+footer()+tabbar("fav");bindNav();2401 if(state.me===undefined){2402 try{const r=await fetch("/api/me");state.me=r.ok?await r.json():null}2403 catch(e){state.me=null}}2404 const shell=inner=>{app.innerHTML=header()+`2405 <div class="container favoris-page">2406 <span class="kicker">Mon univers Ka — favoris partagés</span>2407 <h1>Mes <span class="hl">favoris.</span></h1>2408 <p class="lead">Vos créateurs favoris, gardés dans votre compte KA ID sur groupe-ka.com :2409 les mêmes favoris vous suivent sur toutes les plateformes du Groupe KA.</p>2410 ${inner}2411 </div>`+footer()+tabbar("fav");bindNav();};2412 if(!state.me){2413 shell(`<div class="fav-empty" style="text-align:left;padding:34px 0">2414 <div class="compte-actions" style="margin-top:0">2415 <a class="btn btn-primary" href="/api/auth/ka/login">Se connecter avec KA ID</a>2416 <a class="btn btn-ghost" href="https://www.groupe-ka.com/connexion" target="_blank" rel="noopener">Créer un compte KA ID ↗</a>2417 </div>2418 <p style="font-size:12.5px;color:var(--ink-3);margin-top:16px">Un seul compte pour tout le2419 groupe — vos favoris sont entreposés au hub groupe-ka.com, jamais ici.</p></div>`);2420 loadAccount();return;2421 }2422 let data=null;2423 try{const r=await fetch("/api/favorites");if(r.ok)data=await r.json();}catch(e){}2424 if(!data){2425 shell(`<div class="fav-empty"><h2>Hub injoignable</h2>2426 <p>Impossible de lire vos favoris au hub Groupe KA pour le moment — réessayez dans un instant.</p></div>`);2427 loadAccount();return;2428 }2429 FAV.ids=new Set(data.ids||[]);FAV.loaded=true;2430 const items=(data.items||[]).filter(i=>i.item_id);2431 shell(items.length?`2432 <div class="count-line" style="padding-top:26px"><b>${fmtFull.format(items.length)}</b> créateur${items.length>1?"s":""} en favori</div>2433 <div class="grid" id="fav-grid">${items.map(favCardHtml).join("")}</div>`2434 :`<div class="fav-empty"><h2>Aucun favori pour l'instant</h2>2435 <p>Touchez le ♥ sur une carte ou une fiche de créateur pour l'ajouter à votre univers Ka.</p>2436 <p style="margin-top:16px"><a class="btn btn-primary" href="/#annuaire" data-nav>Explorer l'annuaire</a></p></div>`);2437 loadAccount();window.scrollTo(0,0);2438}24392440/* ---------- page contact (écosystème Groupe KA — ka/ecosystem.json) ---------- */2441function renderContact(){2442 document.title="Contact — Créa-Ka, un service Groupe KA";2443 const sites=KA_ECO.sites.map(s=>{2444 const cur=s.id==="crea-ka";2445 return `<a class="site-item" href="https://${esc(s.domain)}"${cur?"":' target="_blank" rel="noopener noreferrer"'}2446 title="${esc(s.tagline)}"><i style="background:${esc(s.accent)}"></i><b>${esc(s.wordmark)}${cur?" (ici)":""}</b><span>${esc(s.domain)}</span></a>`;2447 }).join("");2448 app.innerHTML=header()+`2449 <div class="container contact-page">2450 <span class="kicker">Contact — Groupe KA</span>2451 <h1>Nous <span class="hl">joindre.</span></h1>2452 <p class="lead">${esc(KA_SITE.wordmark)} — ${esc(KA_SITE.tagline)} — est un service du2453 <b>Groupe KA</b>. ${esc(KA_ECO.org.tagline)} Les contacts ci-dessous valent pour2454 l'ensemble des plateformes du groupe.</p>2455 <div class="contact-notice">${esc(KA_ECO.org.disclaimer)}</div>2456 <h2 class="sect-title" style="margin-top:26px">Courriels</h2>2457 <div class="contact-grid">2458 ${KA_ECO.contacts.map(c=>`<a class="contact-card" href="mailto:${esc(c.email)}">2459 <b>${esc(c.email)}</b><span>${esc(c.role)}</span></a>`).join("")}2460 </div>2461 <div class="compte-actions" style="margin-top:22px">2462 <a class="btn btn-primary" href="${esc(KA_ECO.hub.url)}" target="_blank" rel="noopener noreferrer">Visiter le hub groupe-ka.com ↗</a>2463 <a class="btn btn-ghost" href="/retrait" data-nav>Demander un retrait (Loi 25)</a>2464 </div>2465 <h2 class="sect-title">Les plateformes du groupe</h2>2466 <div class="site-list">${sites}</div>2467 <p style="font-size:12px;color:var(--ink-3);margin-top:20px">${esc(KA_ECO.hub.signupNote)}</p>2468 </div>`+footer()+tabbar("");2469 bindNav();window.scrollTo(0,0);2470}24712472/* ---------- retrait / opt-out ---------- */2473function renderOptout(){2474 document.title="Retrait (opt-out) — Créa-Ka";2475 app.innerHTML=header()+`2476 <div class="container optout-page">2477 <span class="kicker">Vie privée d'abord</span>2478 <h1>Demander un <span class="hl">retrait</span></h1>2479 <p>Vous êtes créateur ou créatrice et souhaitez ne plus figurer dans l'annuaire ?2480 La fiche est <b>masquée immédiatement</b> et exclue de toute ré-agrégation future.2481 Une correction d'erreur (mauvais compte rattaché) passe par le même formulaire.</p>2482 <form id="of">2483 <div class="field"><label>Nom public / nom de scène</label><input id="o-name" maxlength="200" placeholder="ex. Prénom Nom"></div>2484 <div class="field"><label>Un de vos comptes (plateforme:handle)</label><input id="o-acc" maxlength="200" placeholder="ex. instagram:monhandle"></div>2485 <div class="field"><label>Raison (facultatif)</label><textarea id="o-reason" rows="3" maxlength="500"></textarea></div>2486 <div class="field"><label>Courriel de contact (facultatif, pour confirmation)</label><input id="o-contact" maxlength="200" type="email"></div>2487 <div class="field"><button class="btn btn-primary" type="submit">Masquer ma fiche</button></div>2488 </form><div id="o-result"></div>2489 </div>`+footer()+tabbar("optout");2490 bindNav();2491 $("#of").addEventListener("submit",async e=>{e.preventDefault();2492 const body={name:$("#o-name").value.trim(),account:$("#o-acc").value.trim(),2493 reason:$("#o-reason").value.trim(),contact:$("#o-contact").value.trim()};2494 if(!body.name&&!body.account){$("#o-result").innerHTML=`<div class="optout-ok warn">Indiquez au moins un nom ou un compte.</div>`;return}2495 try{const r=await fetch("/api/optout",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});2496 const j=await r.json();2497 $("#o-result").innerHTML=`<div class="optout-ok">✓ ${esc(j.message||"Demande enregistrée.")} ${j.masked?`(${j.masked} fiche(s) masquée(s))`:""}</div>`;2498 e.target.reset();}2499 catch(err){$("#o-result").innerHTML=`<div class="optout-ok warn">Erreur — réessayez ou écrivez à contact@spboucher.ai.</div>`}});2500}25012502/* ---------- routeur ---------- */2503function bindNav(root){2504 (root||document).querySelectorAll("[data-nav]").forEach(a=>{2505 if(a._nav)return;a._nav=1;2506 a.addEventListener("click",e=>{const href=a.getAttribute("href");2507 e.preventDefault();history.pushState(null,"",href);route();2508 if(href.endsWith("#annuaire")){const el=$("#annuaire");if(el){el.scrollIntoView();const q=$("#q");if(q)q.focus()}}})});2509 (root||document).querySelectorAll("[data-href]").forEach(card=>{2510 if(card._nav)return;card._nav=1;2511 const go=()=>{history.pushState(null,"",card.dataset.href);route()};2512 card.addEventListener("click",e=>{if(e.target.closest("a"))return;go()});2513 card.addEventListener("keydown",e=>{if(e.key==="Enter")go()});2514 });}2515function route(){2516 const p=location.pathname;2517 const m=p.match(/^\/createur\/([^/]+)$/);2518 const pl=p.match(/^\/plateforme\/([^/]+)$/); /* pages SEO /plateforme/{p} */2519 const qq=new URLSearchParams(location.search).get("q");if(qq&&!state.q)state.q=qq;2520 let r;2521 if(m)r=renderCreator(decodeURIComponent(m[1]));2522 else if(pl){state.plateforme=decodeURIComponent(pl[1]);r=renderHome();}2523 else if(p==="/retrait")r=renderOptout();2524 else if(p==="/favoris")r=renderFavoris();2525 else if(p==="/stats")r=renderStats();2526 else if(p==="/compte")r=renderCompte();2527 else if(p==="/contact")r=renderContact();2528 else r=renderHome();2529 Promise.resolve(r).then(()=>loadAccount()).catch(()=>{});2530}2531window.addEventListener("popstate",route);2532route();2533</script>2534<script>window.KA_AGENT={site:"crea-ka"}</script>2535<script src="/ka-agent.js" defer></script>2536<!-- KA ID v2 : signaux navigateur de la personnalisation (clics, dwell) -->2537<script>window.KAID_CONF={card:".card[data-href^='/createur/']",detail:"^/createur/(.+)$",entity:"creator",2538 uid:function(el){var h=el.getAttribute("data-href")||"";var m=h.match(/^\/createur\/(.+)$/);return m?decodeURIComponent(m[1]):null;}}</script>2539<script src="/ka-id.js" defer></script>2540</body>2541</html>2542