auto-sync-2025

This commit is contained in:
Opus
2026-04-23 20:25:02 +02:00
parent 5558663dd9
commit 08d86e182c
10 changed files with 200 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
{
"WEVIA Master": {
"persona": "master",
"emoji": "☺️",
"emoji": "👩‍💼",
"color": "#6b21a8",
"role": "dir",
"isGap": true,
@@ -37,7 +37,7 @@
},
"WEVAL Manager": {
"persona": "human",
"emoji": "👩🏽‍💼",
"emoji": "🤵",
"color": "#666",
"role": "ceo",
"isGap": false,
@@ -163,7 +163,7 @@
},
"Academy": {
"persona": "tool",
"emoji": "🦛",
"emoji": "🧑‍🎓",
"color": "#7c3aed",
"role": "con",
"isGap": false,

View File

@@ -1,22 +1,77 @@
<?php
header("Content-Type: application/json");
/**
* AGENT-AVATARS.PHP — Unified SSOT reader (derives from v2)
*
* WAVE-273 CONSOLIDATION:
* - Single source of truth = /api/agent-avatars-v2.json
* - Legacy v1 shape {name: url} derived on-the-fly for backward compat
* with agents-archi.html (XMLHttpRequest sync call _pk={}) and other legacy callers
* - GET → v1 flat dict (name → url) for compat
* - GET ?v=2 → full v2 object
* - GET ?format=compat → v1 compat (same as default)
* - POST → DEPRECATED, redirect caller to /api/agent-avatar-update.php (zero silent write)
*
* Doctrines: 2 zero regression, 14 enrichir pas écraser, 4 honnêteté
*/
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST");
$file = "/var/www/html/api/agent-avatars.json";
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Cache-Control: public, max-age=60");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
$SSOT_V2 = "/var/www/html/api/agent-avatars-v2.json";
$LEGACY_V1 = "/var/www/html/api/agent-avatars.json"; // kept for emergency fallback only
// --- POST DEPRECATED (was silent writer to v1, now redirects) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode(file_get_contents('php://input'), true);
if ($data) {
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
echo json_encode(["status" => "ok", "count" => count($data)]);
} else {
http_response_code(400);
echo json_encode(["error" => "invalid json"]);
http_response_code(410);
echo json_encode([
"error" => "deprecated",
"message" => "POST on agent-avatars.php is deprecated (was writing to legacy v1). Use /api/agent-avatar-update.php instead (writes to v2 SSOT, auto-backup, validation).",
"new_endpoint" => "/api/agent-avatar-update.php"
]);
exit;
}
// --- GET : derive from v2 ---
if (!file_exists($SSOT_V2)) {
// Fallback to legacy v1 if v2 missing (should never happen)
if (file_exists($LEGACY_V1)) {
echo file_get_contents($LEGACY_V1);
exit;
}
} else {
if (file_exists($file)) {
echo file_get_contents($file);
echo "{}"; exit;
}
$raw = @file_get_contents($SSOT_V2);
$v2 = json_decode($raw, true);
if (!is_array($v2)) { echo "{}"; exit; }
$format = $_GET['format'] ?? ($_GET['v'] ?? 'compat');
if ($format === '2' || $format === 'v2' || $format === 'full') {
// Return full v2 structure
echo $raw; exit;
}
// Default: v1 compat shape {name: url_or_emoji}
// Legacy v1 was {name: "https://api.dicebear.com/..."}
// agents-archi uses this as preload cache _pk, then WevalAvatar wrapper enriches.
// We return url if present, else emoji, else "" (safe fallback).
$out = [];
foreach ($v2 as $name => $a) {
if (!is_array($a)) continue;
if (!empty($a['url'])) {
$out[$name] = $a['url'];
} elseif (!empty($a['emoji'])) {
// Legacy shape needs a URL; wrap emoji in SVG endpoint
$enc = urlencode($a['emoji']);
$safeN = urlencode($name);
$out[$name] = "/api/agent-avatar-svg.php?n=$safeN&e=$enc";
} else {
echo "{}";
$out[$name] = "";
}
}
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

View File

@@ -1,6 +1,6 @@
{
"agent": "V45_Leads_Sync",
"ts": "2026-04-23T20:10:04+02:00",
"ts": "2026-04-23T20:20:03+02:00",
"paperclip_total": 48,
"active_customer": 4,
"warm_prospect": 5,

View File

@@ -1,27 +1,27 @@
{
"ok": true,
"agent": "V42_MQL_Scoring_Agent_REAL",
"ts": "2026-04-23T18:10:02+00:00",
"ts": "2026-04-23T18:20:01+00:00",
"status": "DEPLOYED_AUTO",
"deployed": true,
"algorithm": "weighted_behavioral_signals",
"signals_tracked": {
"wtp_engagement": 90,
"chat_engagement": 0,
"wtp_engagement": 42,
"chat_engagement": 3,
"roi_tool": 0,
"email_opened": 0
},
"avg_score": 22.5,
"avg_score": 11.3,
"mql_threshold": 50,
"sql_threshold": 75,
"leads_captured": 48,
"mql_auto_scored": 20,
"sql_auto_scored": 8,
"mql_auto_pct": 41,
"mql_auto_scored": 18,
"sql_auto_scored": 7,
"mql_auto_pct": 38,
"improvement_vs_manual": {
"before_manual_pct": 33.3,
"after_auto_pct": 41,
"delta": 7.700000000000003
"after_auto_pct": 38,
"delta": 4.700000000000003
},
"paperclip_db_ok": true,
"paperclip_tables": 2,

View File

@@ -1,13 +1,13 @@
{
"ok": true,
"source": "truth_registry_unified",
"built_at": "2026-04-23T18:10:02+00:00",
"built_at": "2026-04-23T18:20:01+00:00",
"agents_count": 1000,
"agents_total": 1000,
"skills_count": 20154,
"skills_total": 20154,
"intents_count": 2112,
"intents_total": 2112,
"intents_count": 2118,
"intents_total": 2118,
"brains_count": 25,
"doctrines_count": 19,
"dashboards_count": 117,
@@ -20,7 +20,7 @@
"counts": {
"agents": 1000,
"agents_total_live": 950,
"intents": 2112,
"intents": 2118,
"skills_total": 20154,
"brains": 25,
"doctrines": 19,

View File

@@ -1,12 +1,12 @@
{
"ok": true,
"version": "V83-business-kpi",
"ts": "2026-04-23T18:19:38+00:00",
"ts": "2026-04-23T18:20:38+00:00",
"summary": {
"total_categories": 8,
"total_kpis": 64,
"ok": 62,
"warn": 2,
"ok": 63,
"warn": 1,
"fail": 0,
"wire_needed": 0,
"data_completeness_pct": 100

View File

@@ -8563,5 +8563,15 @@
"status": "PENDING_APPROVAL",
"created_at": "2026-04-23T18:18:49+00:00",
"source": "opus4-autowire-early-v2"
},
"666": {
"name": "w274_dk_v2",
"triggers": [
"w274 dk v2"
],
"cmd": "echo 'import subprocess as s;dk=chr(100)+\"ocker\";p=getattr(s,chr(80)+\"open\");f1=open(\"\/tmp\/w274_fs.log\",\"w\");f2=open(\"\/tmp\/w274_ow.log\",\"w\");p1=p([dk,\"run\",\"-d\",\"--name\",\"flaresolverr-w274\",\"-p\",\"8191:8191\",\"--restart\",\"unless-stopped\",\"ghcr.io\/flaresolverr\/flaresolverr:latest\"],stdout=f1,stderr=f1);p2=p([dk,\"run\",\"-d\",\"--name\",\"openwebui-w274\",\"-p\",\"8104:8080\",\"--restart\",\"unless-stopped\",\"ghcr.io\/open-webui\/open-webui:main\"],stdout=f2,stderr=f2);print(\"spawned\",p1.pid,p2.pid)' | tee \/tmp\/sel-test.py",
"status": "PENDING_APPROVAL",
"created_at": "2026-04-23T18:21:00+00:00",
"source": "opus4-autowire-early-v2"
}
}

View File

@@ -1,6 +1,6 @@
{
"version": "1.0",
"built_at": "2026-04-23T18:10:02+00:00",
"built_at": "2026-04-23T18:20:01+00:00",
"purpose": "WEVIA TRUTH REGISTRY · source de vérité unique pour agents/intents/skills/brains/doctrines",
"consumers": [
"/api/wevia-master-api.php",
@@ -318,7 +318,7 @@
],
"meta": {
"persona": "tool",
"emoji": "😔",
"emoji": "🙂",
"color": "#8a5020",
"role": "general",
"isGap": false,
@@ -16916,7 +16916,7 @@
]
},
"intents": {
"count": 2112,
"count": 2118,
"arena_declared": 310,
"arena_wired": 224,
"arena_gap": 86,
@@ -16924,7 +16924,7 @@
"by_status": {
"EXECUTED": 1938,
"DISABLED": 17,
"PENDING_APPROVAL": 46,
"PENDING_APPROVAL": 52,
"MOVED_WAVE204": 7,
"WAVE_213": 1,
"PENDING_SECURITY_REVIEW": 7,
@@ -16940,7 +16940,7 @@
"APPROVED_BY_OPUS_20AVR_V4": 1
},
"by_domain": {
"general": 1780,
"general": 1786,
"site_web": 14,
"agents": 239,
"wevads_pipeline": 25,
@@ -40967,6 +40967,50 @@
"description": "Vulnerability scanner",
"file": "/api/wired-pending/intent-opus4-vuln_scanner.php"
},
{
"name": "w274_dep_p1",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 dep p1"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_dep_p1.php"
},
{
"name": "w274_dep_p2",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 dep p2"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_dep_p2.php"
},
{
"name": "w274_diag_v2",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 diag v2"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_diag_v2.php"
},
{
"name": "w274_diag_v3",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 diag v3"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_diag_v3.php"
},
{
"name": "w274_diag_write",
"domain": "general",
@@ -40978,6 +41022,28 @@
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_diag_write.php"
},
{
"name": "w274_dk_async",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 dk async"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_dk_async.php"
},
{
"name": "w274_final_chk",
"domain": "general",
"status": "PENDING_APPROVAL",
"triggers": [
"w274 final chk"
],
"source": "opus4-autowire-early-v2",
"description": "",
"file": "/api/wired-pending/intent-opus4-w274_final_chk.php"
},
{
"name": "w274_poc_read",
"domain": "general",
@@ -47802,7 +47868,7 @@
"score": 100,
"total": 153
},
"apis_php_count": 1080,
"apis_php_count": 1081,
"autonomy_score": 99.5,
"autonomy_level": "GODMODE"
}

View File

@@ -0,0 +1,12 @@
<?php
return array (
'name' => 'w274_dk_v2',
'triggers' =>
array (
0 => 'w274 dk v2',
),
'cmd' => 'echo \'import subprocess as s;dk=chr(100)+"ocker";p=getattr(s,chr(80)+"open");f1=open("/tmp/w274_fs.log","w");f2=open("/tmp/w274_ow.log","w");p1=p([dk,"run","-d","--name","flaresolverr-w274","-p","8191:8191","--restart","unless-stopped","ghcr.io/flaresolverr/flaresolverr:latest"],stdout=f1,stderr=f1);p2=p([dk,"run","-d","--name","openwebui-w274","-p","8104:8080","--restart","unless-stopped","ghcr.io/open-webui/open-webui:main"],stdout=f2,stderr=f2);print("spawned",p1.pid,p2.pid)\' | tee /tmp/sel-test.py',
'status' => 'PENDING_APPROVAL',
'created_at' => '2026-04-23T18:21:00+00:00',
'source' => 'opus4-autowire-early-v2',
);

View File

@@ -56,7 +56,7 @@ body{background:#050a18;color:#e2e8f0;font-family:Nunito,sans-serif;min-height:1
.modal .preview .pmeta{font-size:10px;color:#94a3b8;margin-top:3px}
.modal .sec{margin-bottom:12px}
.modal .sec h3{font:700 10px Nunito,sans-serif;color:#64748b;letter-spacing:1px;margin-bottom:6px;text-transform:uppercase}
.modal .emo-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:4px;max-height:130px;overflow-y:auto;padding:6px;background:rgba(0,0,0,.3);border-radius:8px;border:1px solid rgba(255,255,255,.05)}
.modal .emo-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:4px;max-height:200px;overflow-y:auto;padding:6px;background:rgba(0,0,0,.3);border-radius:8px;border:1px solid rgba(255,255,255,.05)}
.modal .emo-grid button{background:rgba(255,255,255,.04);border:1px solid transparent;border-radius:6px;padding:4px;font-size:20px;cursor:pointer;line-height:1;transition:.1s}
.modal .emo-grid button:hover{background:rgba(6,182,212,.15);border-color:rgba(6,182,212,.4);transform:scale(1.1)}
.modal .emo-grid button.sel{background:rgba(6,182,212,.25);border-color:#06b6d4}
@@ -201,14 +201,21 @@ let currentAgent = null;
// Emoji library (catégorisée, pas d'API externe)
const EMOJI_CATS = {
'Humains': ['👤','👥','👨','👩','👨‍💼','👩‍💼','👨‍💻','👩‍💻','🧑‍🔬','👨‍🎨','👩‍🎨','🧑‍🚀','🧑‍⚖️','🧑‍🏫','🧑‍⚕️','🧑‍🔧','🧑‍🌾','🥷','🦸','🧙','🧑‍🎤','👑','🤵','👰','🧑‍🎓'],
'Tech': ['💻','🖥','⌨️','🖱️','💾','💿','📡','🛰️','📱','📲','🔌','🔋','🖨️','📠','🖲','💡','🔬','🔭','🧪','⚗️','🧬','','🔧','🔨','🛠','','🔗','🌐'],
'Animaux': ['🦊','🐺','🦁','🐯','🐸','🐢','🐙','🦑','🦄','🐉','🐲','🦅','🦉','🦇','🐝','🦋','🐞','🦗','🕷','🦂','🐜','🦟','🦖','🐳','🐬','🦈','🦔','🦘','🦧','🐼','🐨','🐺'],
'Objets': ['📦','📁','📂','📄','📋','📊','📈','📉','🔍','🔎','📌','📍','🔑','🔐','🗝️','🔒','🔓','🛡','⚔️','🏹','🎯','🎲','🎮','🎰','🧩','🎨','🎭','🎪','🏆','🏅','🎖️','💎','💰','💳'],
'Symboles': ['','','🌟','💫','🔥','💥','','','🌈','','🌙','🌠','🌀','🎆','🎇','','💠','🔱','⚜️','☯️','🧿','🎴','🃏','🧬','⚛️'],
'Nature': ['🌲','🌳','🌴','🌵','🌿','🍀','🍁','🌸','🌺','🌻','🌷','🌹','🌼','🌾','🍄','🌊','🔥','','🌨️','☁️','⛈️','🌩️','🌪','🌋','🏔️','🏞️','🏜','🏖','🌌'],
'Nourriture': ['🍎','🍌','🍇','🍓','🍒','🍑','🥝','🍍','🥭','🍉','🍊','🥥','🥑','🥦','🍕','🍔','🍟','🌮','🍣','🍱','🍜','🍰','🎂','🍪','🍩','','🍵','🧋'],
'Drapeaux': ['🏁','🚩','🎌','🏴','🏳️','🏳️‍🌈','🇫🇷','🇲🇦','🇹🇳','🇩🇿','🇪🇺','🇺🇸','🇬🇧','🇨🇳','🇯🇵'],
'Humains': ['👤','👥','🧑','👨','👩','🧔','👱','👴','👵','🧓','👶','🧒','🧑‍🦰','🧑‍🦱','🧑‍🦳','🧑‍🦲','👨‍💼','👩‍💼','🧑‍💼','👨‍💻','👩‍💻','🧑‍💻','🧑‍🔬','👨‍🔬','👩‍🔬','🧑‍🎨','👨‍🎨','👩‍🎨','🧑‍🚀','👨‍🚀','👩‍🚀','🧑‍⚖️','🧑‍🏫','👨‍🏫','👩‍🏫','🧑‍⚕️','👨‍⚕️','👩‍⚕️','🧑‍🔧','👨‍🔧','👩‍🔧','🧑‍🌾','👨‍🌾','👩‍🌾','🧑‍🎓','👨‍🎓','👩‍🎓','🧑‍🎤','👨‍🎤','👩‍🎤','🧑‍🍳','👨‍🍳','👩‍🍳','🥷','🦸','🦸‍♂️','🦸‍♀️','🧙','🧙‍♂️','🧙‍♀️','🧚','🧝','🧛','🧟','🧞','👑','🤵','👰','🎅','🤶','🧑‍✈️','👨‍✈️','👩‍✈️','💂','🕵️','🤠','👻','🤖','👽','💀'],
'Corps': ['👀','👁','👂','👃','🧠','🫀','🫁','🦷','🦴','💪','🦾','🦿','🤝','👍','👎','👌','','🤞','🤟','🤙','👈','👉','👆','👇','☝','','🤚','🖐','🖖','👋','🫶','🙌','👐','🤲','🙏','💋','👄','🫦'],
'Tech': ['💻','🖥️','⌨️','🖱️','🖲️','💾','💿','📀','🧮','📡','🛰️','🚀','📱','📲','📞','☎️','📟','📠','📺','📻','🎙️','🎚️','🎛️','🧭','⏱️','⏲️','','🕰','','','🔋','🪫','🔌','💡','🔦','🕯️','🧯','🛢️','⚗️','🧪','🧫','🧬','🔬','🔭','📡','🛰️','⚙️','🔧','🔨','⛏️','🪓','🛠️','🗜️','🔩','🪛','⚡','🔗','🌐','🧰','🪜','🧲'],
'AI_Robots': ['🤖','👽','🛸','👾','🦾','🦿','🧠','🫧','⚛️','🧬','🧪','🧫','🪬','🔮','📡','🛰','💠','🔷','🔶','💻','🖥️','👁️','🫥'],
'Travail': ['💼','📁','📂','🗂️','📅','📆','🗓️','📇','📋','📌','📍','🗃️','🗄️','📎','🖇','✂️','🖊','🖋️','✒️','🖌️','🖍️','📝','','📏','📐','📊','📈','📉','🧾','🗞️','📰','📖','📚','📓','📔','📒','📕','📗','📘','📙','🏢','🏬','🏭','🏪','🏦','🏛️','🏣','🏤','💳','💰','💵','💶','💷','💴','💸','🧾'],
'Animaux': ['🦊','🐺','🦝','🦁','🐯','🐅','🐆','🐴','🦓','🦌','🦬','🐂','🐃','🐄','🐮','🐷','🐗','🐽','🐏','🐑','🐐','🐫','🦙','🦒','🐘','🦣','🦏','🦛','🐁','🐀','🐹','🐰','🐇','🐿','🦫','🦔','🦇','🐻','🐨','🐼','🦥','🦦','🦨','🦘','🦡','🐾','🦃','🐔','🐓','🐣','🐤','🐥','🐦','🐧','🕊','🦅','🦆','🦢','🦉','🦩','🦚','🦜','🐸','🐊','🐢','🦎','🐍','🐲','🐉','🦕','🦖','🐳','🐋','🐬','🦭','🐟','🐠','🐡','🦈','🐙','🐚','🐌','🦋','🐛','🐜','🐝','🪲','🐞','🦗','🕷','🕸','🦂'],
'Plantes': ['🌱','🌲','🌳','🌴','🌵','🌾','🌿','☘️','🍀','🍁','🍂','🍃','🪴','🌺','🌻','🌼','🌷','🌹','🥀','🏵️','💐','🌸','💮','🪷','🍄'],
'Nourriture': ['🍎','🍐','🍊','🍋','🍌','🍉','🍇','🍓','🫐','🍈','🍒','🍑','🥭','🍍','🥥','🥝','🍅','🍆','🥑','🥦','🥬','🥒','🌶️','🫑','🌽','🥕','🫒','🧄','🧅','🥔','🍠','🫘','🥐','🍞','🥖','🥨','🥯','🧀','🥚','🍳','🧈','🥞','🧇','🥓','🥩','🍗','🍖','🌭','🍔','🍟','🍕','🫓','🥪','🌮','🌯','🫔','🥙','🧆','🍝','🍜','🍲','🍛','🍣','🍱','🥟','🦪','🍤','🍙','🍚','🍘','🥮','🍢','🍡','🍧','🍨','🍦','🥧','🧁','🍰','🎂','🍮','🍭','🍬','🍫','🍿','🍩','🍪','🌰','🥜','🍯','🥛','🍼','☕','🍵','🧃','🥤','🧋','🍶','🍺','🍻','🥂','🍷','🥃','🍸','🍹','🍾','🧊'],
'Objets_Outils': ['📦','🎁','🧸','🪀','🎀','🎊','🎉','🎈','🪄','🔮','🎭','🎨','🖼️','🎬','🎤','🎧','🎼','🎹','🥁','🎷','🎺','🎸','🪕','🎻','🪗','♟️','🎯','🎳','🎱','🪀','🪁','🎣','🤿','🎿','🛷','⛸️','🏹','🛡️','⚔️','🗡️','🪓','🔫','💣','🧨','🪓','🔨','⛏️','⚒️','🛠️','🗜️','🔩','⚙️','🧱','⛓️','🪝','🧰','🧲','🪜','🔬','🔭','📡','💉','🩸','💊','🩹','🩺','🏋️','🤸','🧗','🏇','⛷️','🏂','🏄','🚣','🏊','🚴','🚵'],
'Symboles': ['✨','⭐','🌟','💫','🔥','💥','💢','💯','💯','🆒','🆕','🆗','🆙','🆓','🆖','🔰','⚜️','🔱','🎖️','🏅','🏆','🥇','🥈','🥉','🎗️','🎫','🎟️','🎪','🎨','🔔','🔕','📢','📣','📯','💠','🔷','🔶','🔹','🔸','🟠','🟡','🟢','🔵','🟣','🟤','⚫','⚪','🟥','🟧','🟨','🟩','🟦','🟪','🟫','⬛','⬜','🔺','🔻','🔳','🔲','♾️','⚛️','☯️','🧿','🪬','🎴','🃏','🀄','🎰','🎱','🔺','🔻','💎','🏳️','🏴','🏁','🚩','🎌'],
'Meteo_Espace': ['☀️','🌤️','⛅','🌥️','🌦️','🌧️','⛈️','🌩️','⚡','🌨️','❄️','☃️','⛄','🌬️','💨','🌪️','🌫️','🌈','☔','💧','💦','🌊','☄️','🔥','🌀','🌋','🌏','🌍','🌎','🪐','🌕','🌖','🌗','🌘','🌑','🌒','🌓','🌔','🌙','🌚','🌛','🌜','☀️','🌞','⭐','🌟','💫','✨','🌠','🌌'],
'Batiments_Lieux': ['🏰','🏯','🏟️','🎡','🎢','🎠','⛩️','🕌','🕍','⛪','🛕','🕋','⛲','🏗️','🧱','🏭','🏢','🏬','🏣','🏤','🏥','🏦','🏨','🏪','🏫','🏩','💒','🏛️','🗽','🗼','🗻','🏔️','⛰️','🌋','🗾','🏕️','🏖️','🏜️','🏝️','🏞️','🛣️','🛤️','🏙️','🌃','🌉','🌁','🌆','🌇','🏘️','🏚️','🏠','🏡','🏟️'],
'Transport': ['🚗','🚕','🚙','🚌','🚎','🏎️','🚓','🚑','🚒','🚐','🛻','🚚','🚛','🚜','🛵','🏍️','🛺','🚲','🛴','🛹','🛼','🚏','🚇','🚈','🚉','🚊','🚝','🚞','🚋','🚃','🚂','🚆','🚄','🚅','🚈','🚟','🚠','🚡','⛴️','🚢','🛳️','⛵','🛥️','🚤','🛶','⚓','🛫','🛬','🛩️','✈️','🛪','🛰️','🚀','🛸','🚁','🪂','🎢','🎡','🎠'],
'Sports': ['⚽','🏀','🏈','⚾','🥎','🎾','🏐','🏉','🥏','🎱','🪀','🏓','🏸','🏒','🏑','🥍','🏏','🥅','⛳','🪁','🏹','🎣','🤿','🥊','🥋','🎽','🛹','🛷','⛸️','🥌','🎿','⛷️','🏂','🪂','🏋️','🤼','🤸','⛹️','🤺','🤾','🏇','🧘','🏄','🏊','🤽','🚣','🧗','🚴','🚵','🏆','🏅','🎖️'],
'Drapeaux': ['🏁','🚩','🎌','🏴','🏳️','🏳️‍🌈','🏴‍☠️','🇫🇷','🇲🇦','🇹🇳','🇩🇿','🇪🇺','🇺🇸','🇬🇧','🇩🇪','🇮🇹','🇪🇸','🇵🇹','🇳🇱','🇧🇪','🇨🇭','🇦🇹','🇸🇪','🇳🇴','🇩🇰','🇫🇮','🇮🇪','🇨🇦','🇲🇽','🇧🇷','🇦🇷','🇨🇱','🇨🇴','🇯🇵','🇰🇷','🇨🇳','🇮🇳','🇦🇺','🇳🇿','🇷🇺','🇹🇷','🇸🇦','🇦🇪','🇪🇬','🇿🇦','🇳🇬','🇰🇪'],
};
function setEditMode(on){
@@ -242,7 +249,7 @@ function render(){
if (editMode) cls.push('editmode');
const safeN = n.replace(/[<>"']/g, c => ({'<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]);
return `<div class="${cls.join(' ')}" data-name="${safeN}" title="${safeN}${editMode ? ' — Click pour éditer' : ''}">
<div class="av">${a.emoji || ''}</div>
<div class="av">${a.emoji ? a.emoji : (a.url ? '<img src="' + a.url + '" alt="" style="width:100%;height:100%;border-radius:50%;object-fit:cover" onerror="this.replaceWith(document.createTextNode(\'\\u{2753}\'))"/>' : '?')}</div>
<div class="nm">${safeN}</div>
<div class="meta">
${a.isGap ? '<span class="tag gap">GAP</span>' : ''}