Thomas Fransolet a5a8ecdb20 Documentation interne MyInfoMate / Unov
Import initial de la documentation : statut, roadmap, plans V1/V2,
specs verticales (creche, sport), audits securite, plan de test,
analyse concurrentielle et maquettes de design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:17:01 +02:00

775 lines
70 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700;800&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400;1,6..72,500&display=swap" rel="stylesheet">
<style>
@keyframes pp-pulse { 0%{ transform:translate(-50%,-50%) scale(1); opacity:.55 } 70%{ transform:translate(-50%,-50%) scale(2.4); opacity:0 } 100%{ opacity:0 } }
@keyframes pp-glow { 0%,100%{ opacity:.45 } 50%{ opacity:.9 } }
@keyframes pp-float { 0%,100%{ transform:translateY(0) } 50%{ transform:translateY(-7px) } }
@keyframes pp-rise { from{ transform:translateY(16px) } to{ transform:translateY(0) } }
@keyframes pp-pop { 0%{ transform:scale(.82) } 60%{ transform:scale(1.06) } 100%{ transform:scale(1) } }
@keyframes pp-spin { to{ transform:rotate(360deg) } }
</style>
</helmet>
<div style="display:flex;flex-direction:column;align-items:center;gap:18px;font-family:'Hanken Grotesk',-apple-system,sans-serif">
<div style="width:408px;background:linear-gradient(150deg,#23232a,#0a0a0d);border-radius:54px;padding:11px;box-shadow:0 40px 70px -28px rgba(20,20,30,.55),0 2px 6px rgba(0,0,0,.3),inset 0 0 0 2px rgba(255,255,255,.05)">
<div style="position:relative;width:386px;height:836px;border-radius:44px;overflow:hidden;background:{{ screenBg }}">
<!-- ===== STATUS BAR ===== -->
<div style="position:absolute;top:0;left:0;right:0;height:54px;display:flex;align-items:center;justify-content:space-between;padding:0 30px;z-index:60;pointer-events:none">
<span style="font-weight:700;font-size:15px;letter-spacing:.2px;color:{{ statusColor }}">9:41</span>
<span style="display:flex;gap:6px;align-items:center;color:{{ statusColor }}">
<svg width="18" height="12" viewBox="0 0 18 12" fill="currentColor"><rect x="0" y="7" width="3" height="5" rx="1"/><rect x="5" y="4" width="3" height="8" rx="1"/><rect x="10" y="2" width="3" height="10" rx="1"/><rect x="15" y="0" width="3" height="12" rx="1"/></svg>
<svg width="17" height="12" viewBox="0 0 17 12" fill="currentColor"><path d="M8.5 2.2c2.3 0 4.4.9 6 2.4l1-1.1A10.4 10.4 0 0 0 8.5.8C5.5.8 2.8 2 .9 3.9l1 1.1A8.4 8.4 0 0 1 8.5 2.2Z"/><path d="M8.5 6c1.3 0 2.5.5 3.4 1.4l1-1.1A6.2 6.2 0 0 0 8.5 4.5 6.2 6.2 0 0 0 4 6.3l1 1.1A4.7 4.7 0 0 1 8.5 6Z"/><circle cx="8.5" cy="10" r="1.7"/></svg>
<svg width="26" height="13" viewBox="0 0 26 13" fill="none"><rect x="1" y="1" width="21" height="11" rx="3" stroke="currentColor" stroke-opacity=".4"/><rect x="3" y="3" width="16" height="7" rx="1.5" fill="currentColor"/><rect x="23.5" y="4" width="1.6" height="5" rx=".8" fill="currentColor" fill-opacity=".5"/></svg>
</span>
</div>
<div style="position:absolute;top:12px;left:50%;transform:translateX(-50%);width:116px;height:33px;background:#000;border-radius:18px;z-index:65"></div>
<!-- ============================= SCREEN 1 : HOME ============================= -->
<sc-if value="{{ isHome }}" hint-placeholder-val="{{ true }}">
<div style="position:absolute;inset:0;overflow:auto;background:#F6F3EE">
<div style="padding:70px 22px 16px">
<div style="font-family:'Newsreader',serif;font-size:32px;font-weight:500;color:#1E2A33;letter-spacing:-.5px">Explorer</div>
<div style="color:#6B7B86;font-size:14.5px;margin-top:2px">Carcassonne · 6 sections à découvrir</div>
</div>
<div style="display:flex;gap:8px;padding:4px 22px 14px;overflow:auto">
<div style="padding:8px 15px;border-radius:30px;background:{{ primary }};color:{{ onPrimary }};font-size:13px;font-weight:600;white-space:nowrap">Tout</div>
<div style="padding:8px 15px;border-radius:30px;background:#fff;border:1px solid #E7E1D8;color:#54636E;font-size:13px;font-weight:500;white-space:nowrap">Patrimoine</div>
<div style="padding:8px 15px;border-radius:30px;background:#fff;border:1px solid #E7E1D8;color:#54636E;font-size:13px;font-weight:500;white-space:nowrap">Nature</div>
<div style="padding:8px 15px;border-radius:30px;background:#fff;border:1px solid #E7E1D8;color:#54636E;font-size:13px;font-weight:500;white-space:nowrap">Musées</div>
</div>
<!-- featured section tile (OUR component) -->
<div onClick="{{ openSection }}" style="margin:6px 18px 0;border-radius:24px;overflow:hidden;background:#fff;box-shadow:0 14px 30px -16px rgba(38,72,99,.35),0 1px 0 rgba(0,0,0,.02);cursor:pointer">
<div style="position:relative;height:188px;background:linear-gradient(150deg,#3a5f70,#264863 70%)">
<div style="position:absolute;inset:0;background:radial-gradient(120% 90% at 75% 15%, rgba(255,255,255,.18), transparent 55%)"></div>
<svg viewBox="0 0 386 188" style="position:absolute;inset:0;width:100%;height:100%" preserveAspectRatio="none"><path d="M0 150 L0 96 L34 96 L48 70 L62 96 L96 96 L96 60 L120 60 L120 40 L150 22 L180 40 L180 96 L230 96 L230 70 L250 56 L270 70 L270 96 L320 96 L334 74 L348 96 L386 96 L386 188 L0 188Z" fill="rgba(0,0,0,.18)"/><path d="M120 40 L150 22 L180 40 L180 96 L120 96 Z" fill="rgba(255,255,255,.06)"/></svg>
<div style="position:absolute;top:14px;right:14px;display:flex;align-items:center;gap:5px;padding:7px 12px;border-radius:30px;background:rgba(10,18,24,.4);backdrop-filter:blur(6px);color:#fff;font-size:12.5px;font-weight:600">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M9 6l6 6-6 6"/></svg>3 parcours
</div>
<div style="position:absolute;left:16px;bottom:13px;display:flex;align-items:center;gap:6px;padding:6px 11px;border-radius:10px;background:rgba(255,255,255,.92);color:#264863;font-size:11.5px;font-weight:700;letter-spacing:.3px">PATRIMOINE</div>
</div>
<div style="padding:15px 17px 17px">
<div style="font-family:'Newsreader',serif;font-size:22px;font-weight:500;color:#1E2A33;letter-spacing:-.3px">La Cité Médiévale</div>
<div style="color:#6B7B86;font-size:14px;line-height:1.45;margin-top:5px">Remparts, cathédrale et ruelles : trois itinéraires pour explorer mille ans d'histoire.</div>
</div>
</div>
<!-- other tiles (dim) -->
<div style="margin:14px 18px 0;border-radius:22px;overflow:hidden;background:#fff;box-shadow:0 8px 22px -16px rgba(0,0,0,.25);display:flex;height:96px">
<div style="width:108px;background:linear-gradient(150deg,#5a7a52,#3B6B4A)"></div>
<div style="padding:14px 15px;flex:1">
<div style="font-family:'Newsreader',serif;font-size:18px;color:#1E2A33">Le Sentier des Garrigues</div>
<div style="color:#6B7B86;font-size:13px;margin-top:3px">Balade nature · 1 parcours</div>
</div>
</div>
<div style="margin:12px 18px 28px;border-radius:22px;overflow:hidden;background:#fff;box-shadow:0 8px 22px -16px rgba(0,0,0,.25);display:flex;height:96px">
<div style="width:108px;background:linear-gradient(150deg,#a9663f,#8a4b28)"></div>
<div style="padding:14px 15px;flex:1">
<div style="font-family:'Newsreader',serif;font-size:18px;color:#1E2A33">Musée des Beaux-Arts</div>
<div style="color:#6B7B86;font-size:13px;margin-top:3px">Exposition · 1 parcours</div>
</div>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 2 : PATH LIST (bottom sheet) ============================= -->
<sc-if value="{{ isPathList }}">
<div style="position:absolute;inset:0;background:#F6F3EE">
<!-- dimmed home behind -->
<div style="position:absolute;inset:0;background:linear-gradient(150deg,#3a5f70,#264863 70%);opacity:.96"></div>
<div style="position:absolute;inset:0;background:rgba(10,16,22,.5)"></div>
<div onClick="{{ goHome }}" style="position:absolute;top:64px;left:18px;width:40px;height:40px;border-radius:50%;background:rgba(255,255,255,.92);display:flex;align-items:center;justify-content:center;z-index:5;cursor:pointer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1E2A33" stroke-width="2.4"><path d="M15 6l-6 6 6 6"/></svg>
</div>
<!-- sheet -->
<div style="position:absolute;left:0;right:0;bottom:0;top:150px;background:#F6F3EE;border-radius:26px 26px 0 0;box-shadow:0 -16px 40px rgba(0,0,0,.3);animation:pp-rise .35s ease;overflow:auto">
<div style="display:flex;justify-content:center;padding-top:11px"><div style="width:40px;height:5px;border-radius:3px;background:#D8D2C8"></div></div>
<div style="padding:14px 22px 6px">
<div style="font-family:'Newsreader',serif;font-size:25px;color:#1E2A33;letter-spacing:-.3px">Choisir un parcours</div>
<div style="color:#6B7B86;font-size:14px;margin-top:2px">La Cité Médiévale · 3 itinéraires</div>
</div>
<div style="padding:8px 18px 30px;display:flex;flex-direction:column;gap:12px">
<sc-for list="{{ paths }}" as="p" hint-placeholder-count="3">
<div onClick="{{ p.onTap }}" style="display:flex;gap:13px;padding:11px;border-radius:20px;background:#fff;border:1px solid #EFEAE2;box-shadow:0 8px 20px -16px rgba(0,0,0,.3);cursor:pointer">
<div style="width:84px;height:84px;border-radius:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:{{ p.thumb }}">
<span style="font-size:26px">{{ p.emoji }}</span>
</div>
<div style="flex:1;min-width:0;padding-top:2px">
<div style="display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:7px;font-size:10.5px;font-weight:700;letter-spacing:.4px;background:{{ p.badgeBg }};color:{{ p.badgeFg }}">{{ p.badge }}</div>
<div style="font-family:'Newsreader',serif;font-size:18px;color:#1E2A33;margin-top:6px;line-height:1.15">{{ p.title }}</div>
<div style="color:#6B7B86;font-size:12.5px;line-height:1.35;margin-top:3px">{{ p.desc }}</div>
<div style="color:#8A969F;font-size:12px;font-weight:600;margin-top:6px">{{ p.steps }}</div>
</div>
</div>
</sc-for>
</div>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 3a : START (normal) ============================= -->
<sc-if value="{{ isStart }}">
<div style="position:absolute;inset:0;background:#F6F3EE;overflow:auto">
<div style="position:relative;height:340px;background:linear-gradient(155deg,#3a5f70,#264863 72%)">
<div style="position:absolute;inset:0;background:radial-gradient(110% 80% at 70% 10%, rgba(255,255,255,.16), transparent 55%)"></div>
<svg viewBox="0 0 386 340" style="position:absolute;bottom:0;width:100%;height:62%" preserveAspectRatio="none"><path d="M0 250 L0 150 L40 150 L60 110 L80 150 L130 150 L130 96 L165 70 L200 96 L200 150 L250 150 L250 116 L275 96 L300 116 L300 150 L360 150 L360 340 L0 340Z" fill="rgba(0,0,0,.2)"/><path d="M165 70 L200 96 L200 150 L130 150 L130 96 Z" fill="rgba(255,255,255,.05)"/></svg>
<div onClick="{{ back }}" style="position:absolute;top:64px;left:18px;width:40px;height:40px;border-radius:50%;background:rgba(255,255,255,.2);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;cursor:pointer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4"><path d="M15 6l-6 6 6 6"/></svg>
</div>
<div style="position:absolute;left:22px;bottom:22px;right:22px">
<div style="display:inline-flex;padding:6px 11px;border-radius:8px;background:rgba(255,255,255,.92);color:#264863;font-size:11px;font-weight:700;letter-spacing:.4px;white-space:nowrap">DÉCOUVERTE LIBRE</div>
<div style="font-family:'Newsreader',serif;font-size:32px;color:#fff;margin-top:11px;line-height:1.05;letter-spacing:-.5px">Les Remparts<br>de la Cité</div>
</div>
</div>
<div style="padding:20px 22px 0">
<div style="display:flex;gap:18px;color:#54636E;font-size:13.5px;font-weight:600">
<span style="display:flex;align-items:center;gap:6px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="{{ primary }}" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>~50 min</span>
<span style="display:flex;align-items:center;gap:6px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="{{ primary }}" stroke-width="2"><path d="M9 20l-5-3V4l5 3 6-3 5 3v13l-5-3-6 3Z"/><path d="M9 7v13M15 4v13"/></svg>5 étapes</span>
<span style="display:flex;align-items:center;gap:6px"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="{{ primary }}" stroke-width="2"><path d="M3 6l6-2 6 2 6-2v14l-6 2-6-2-6 2Z"/></svg>2,1 km</span>
</div>
<div style="color:#46555F;font-size:15px;line-height:1.55;margin-top:16px">Longez les tours et les courtines de la double enceinte. À chaque étape, une halte raconte un siège, une légende ou un détail d'architecture que l'œil ne voit pas.</div>
<!-- mini map preview -->
<div style="margin-top:18px;height:118px;border-radius:18px;overflow:hidden;position:relative;border:1px solid #E7E1D8">
<div style="position:absolute;inset:0;background:radial-gradient(120% 100% at 70% 20%,#E8EFE6,#D6E1D3)"></div>
<svg viewBox="0 0 342 118" style="position:absolute;inset:0;width:100%;height:100%"><path d="M-5 40 H120 L160 80 H360" stroke="#cdd8c8" stroke-width="9" fill="none"/><path d="M40 -5 V60 L90 100 V125" stroke="#cdd8c8" stroke-width="7" fill="none"/><path d="M30 70 Q120 50 200 92 T350 70" stroke="#bcd3e6" stroke-width="6" fill="none" opacity=".8"/><path d="M40 50 L120 30 L210 70 L160 95 L290 78" stroke="{{ primary }}" stroke-width="2.4" stroke-dasharray="2 6" stroke-linecap="round" fill="none"/></svg>
<div style="position:absolute;left:34px;top:42px;width:14px;height:14px;border-radius:50%;background:{{ primary }};border:2.5px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.25)"></div>
<div style="position:absolute;left:280px;top:70px;width:14px;height:14px;border-radius:50%;background:#2E9E6B;border:2.5px solid #fff"></div>
<div style="position:absolute;right:11px;bottom:10px;padding:5px 10px;border-radius:8px;background:rgba(255,255,255,.9);font-size:11px;font-weight:600;color:#54636E">Aperçu du tracé</div>
</div>
</div>
<div style="height:108px"></div>
<div style="position:absolute;left:0;right:0;bottom:0;padding:16px 22px 30px;background:linear-gradient(to top,#F6F3EE 72%,rgba(246,243,238,0))">
<button onClick="{{ beginNormal }}" style="width:100%;border:none;padding:17px;border-radius:18px;background:{{ primary }};color:{{ onPrimary }};font-family:'Hanken Grotesk';font-size:17px;font-weight:700;cursor:pointer;box-shadow:0 12px 26px -10px {{ primaryShadow }}">Commencer le parcours</button>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 3b : GAME START (immersive) ============================= -->
<sc-if value="{{ isGameStart }}">
<div style="position:absolute;inset:0;background:radial-gradient(120% 90% at 50% 22%, #16202e, #0B1018 62%, #06090e)">
<div style="position:absolute;inset:0;background:radial-gradient(60% 40% at 50% 30%, rgba(212,175,55,.10), transparent 60%)"></div>
<div style="position:absolute;inset:0;background:linear-gradient(to bottom, transparent 55%, rgba(0,0,0,.55))"></div>
<svg viewBox="0 0 386 836" style="position:absolute;inset:0;width:100%;height:100%;opacity:.10" preserveAspectRatio="xMidYMid slice"><g stroke="#D4AF37" stroke-width="1" fill="none"><circle cx="193" cy="240" r="150"/><circle cx="193" cy="240" r="110"/><path d="M193 90 V390 M43 240 H343 M86 133 L300 347 M300 133 L86 347"/></g></svg>
<div onClick="{{ back }}" style="position:absolute;top:64px;left:18px;width:40px;height:40px;border-radius:50%;border:1px solid rgba(212,175,55,.4);display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:5">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#D4AF37" stroke-width="2.2"><path d="M15 6l-6 6 6 6"/></svg>
</div>
<!-- emblem -->
<div style="position:absolute;top:128px;left:50%;transform:translateX(-50%);width:150px;height:150px;animation:pp-float 5s ease-in-out infinite">
<div style="position:absolute;inset:0;border-radius:50%;border:1.5px solid rgba(212,175,55,.5);box-shadow:0 0 40px rgba(212,175,55,.25),inset 0 0 30px rgba(212,175,55,.12)"></div>
<div style="position:absolute;inset:18px;border-radius:50%;border:1px solid rgba(212,175,55,.35)"></div>
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center">
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="#D4AF37" stroke-width="1.3"><circle cx="12" cy="8.5" r="4"/><path d="M12 12.5 V20 M9 16 h6 M9 18.5 h6"/></svg>
</div>
</div>
<div style="position:absolute;left:0;right:0;top:312px;padding:0 30px;text-align:center">
<div style="color:#D4AF37;font-size:12.5px;font-weight:700;letter-spacing:4px">ESCAPE GAME · ÉPISODE I</div>
<div style="font-family:'Newsreader',serif;font-size:38px;color:#F4ECD8;margin-top:14px;line-height:1.05;letter-spacing:-.5px">Le Secret<br>du Cloître</div>
<div style="font-family:'Newsreader',serif;font-style:italic;font-size:16.5px;color:#b9ac8c;line-height:1.6;margin-top:18px">« Minuit sonne. Le dernier moine a disparu en laissant cinq énigmes scellées dans la pierre. À vous de retrouver sa trace avant l'aube… »</div>
<div style="display:flex;justify-content:center;gap:22px;margin-top:22px;color:#8a8068;font-size:13px;font-weight:600">
<span>5 énigmes</span><span style="opacity:.4">·</span><span>~45 min</span><span style="opacity:.4">·</span><span>Solo ou équipe</span>
</div>
</div>
<div style="position:absolute;left:0;right:0;bottom:0;padding:18px 26px 34px">
<button onClick="{{ beginGame }}" style="width:100%;border:none;padding:18px;border-radius:16px;background:linear-gradient(180deg,#E7C765,#D4AF37);color:#1a1305;font-family:'Hanken Grotesk';font-size:17px;font-weight:800;letter-spacing:.3px;cursor:pointer;box-shadow:0 0 30px rgba(212,175,55,.35),0 12px 26px -8px rgba(0,0,0,.6)">Démarrer l'aventure</button>
<div style="text-align:center;color:#6f6655;font-size:12px;margin-top:12px">Activez le son pour une immersion totale</div>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 4a : MAP PROGRESS (HERO) ============================= -->
<sc-if value="{{ isMap }}">
<div style="position:absolute;inset:0;overflow:hidden">
<!-- stylised map -->
<div style="position:absolute;inset:0;background:radial-gradient(120% 80% at 75% 8%, #EAF0E6, #DCE6D8 45%, #D0DCCD 80%)"></div>
<svg viewBox="0 0 386 836" style="position:absolute;inset:0;width:100%;height:100%" preserveAspectRatio="xMidYMid slice">
<g opacity=".9">
<path d="M-20 120 H140 L210 60 H420" stroke="#E7DFCF" stroke-width="22" fill="none"/>
<path d="M-20 300 H120 L200 380 H420" stroke="#E7DFCF" stroke-width="16" fill="none"/>
<path d="M70 -20 V200 L150 280 V460" stroke="#E7DFCF" stroke-width="14" fill="none"/>
<path d="M300 -20 V120 L250 200 V420" stroke="#E7DFCF" stroke-width="12" fill="none"/>
<path d="M-20 460 Q120 420 220 470 T420 440" stroke="#BFD8E8" stroke-width="20" fill="none" opacity=".75"/>
<circle cx="318" cy="300" r="44" fill="#CFE0C4" opacity=".7"/>
<circle cx="60" cy="360" r="30" fill="#CFE0C4" opacity=".7"/>
</g>
<!-- route -->
<path d="M60 250 L140 165 L250 250 L150 360 L290 420" stroke="{{ primary }}" stroke-width="4" stroke-dasharray="1 11" stroke-linecap="round" fill="none" opacity=".85"/>
</svg>
<!-- top header -->
<div style="position:absolute;top:62px;left:14px;right:14px;display:flex;align-items:center;gap:10px;z-index:30">
<div onClick="{{ back }}" style="width:42px;height:42px;border-radius:14px;background:rgba(255,255,255,.92);box-shadow:0 4px 14px -4px rgba(0,0,0,.2);display:flex;align-items:center;justify-content:center;cursor:pointer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1E2A33" stroke-width="2.4"><path d="M15 6l-6 6 6 6"/></svg>
</div>
<div style="flex:1;height:42px;border-radius:14px;background:rgba(255,255,255,.92);box-shadow:0 4px 14px -4px rgba(0,0,0,.2);display:flex;align-items:center;justify-content:space-between;padding:0 7px 0 15px">
<span style="font-family:'Newsreader',serif;font-size:16px;color:#1E2A33">Les Remparts</span>
<span style="display:flex;align-items:center;gap:6px;padding:6px 11px;border-radius:9px;background:{{ primarySoft }};color:{{ primary }};font-size:12.5px;font-weight:700;white-space:nowrap">{{ progressText }}</span>
</div>
</div>
<!-- PINS -->
<sc-for list="{{ steps }}" as="s" hint-placeholder-count="5">
<div style="position:absolute;left:{{ s.x }};top:{{ s.y }};transform:translate(-50%,-50%);z-index:20">
<sc-if value="{{ s.isCurrent }}">
<div style="position:absolute;left:50%;top:50%;width:46px;height:46px;border-radius:50%;background:{{ primary }};animation:pp-pulse 2.2s ease-out infinite"></div>
</sc-if>
<div style="position:relative;width:{{ s.size }};height:{{ s.size }};border-radius:50%;background:{{ s.bg }};border:3px solid #fff;box-shadow:0 6px 14px -4px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;color:{{ s.fg }};font-weight:800;font-size:15px">
<sc-if value="{{ s.isDone }}"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 13l4 4L19 7"/></svg></sc-if>
<sc-if value="{{ s.isCurrent }}"><span>{{ s.n }}</span></sc-if>
<sc-if value="{{ s.isLocked }}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></sc-if>
</div>
</div>
</sc-for>
<!-- BOTTOM SHEET -->
<div style="position:absolute;left:0;right:0;bottom:0;background:#fff;border-radius:26px 26px 0 0;box-shadow:0 -18px 44px -12px rgba(20,30,40,.3);z-index:40;padding-bottom:26px">
<div style="display:flex;justify-content:center;padding:11px 0 6px"><div style="width:40px;height:5px;border-radius:3px;background:#E2DCD2"></div></div>
<div onClick="{{ openDetail }}" style="padding:4px 20px 0;display:flex;gap:14px;cursor:pointer">
<div style="width:78px;height:78px;border-radius:18px;flex-shrink:0;background:linear-gradient(150deg,#5e7d8c,{{ primary }});display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden">
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,.9)" stroke-width="1.6"><path d="M3 21h18M5 21V8l7-4 7 4v13M9 21v-6h6v6"/></svg>
<div style="position:absolute;left:6px;bottom:5px;width:26px;height:26px;border-radius:8px;background:{{ primary }};color:{{ onPrimary }};display:flex;align-items:center;justify-content:center;font-weight:800;font-size:13px;border:2px solid #fff">{{ currentN }}</div>
</div>
<div style="flex:1;min-width:0;padding-top:2px">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">
<div style="color:{{ primary }};font-size:11.5px;font-weight:700;letter-spacing:.5px">ÉTAPE EN COURS</div>
<div style="display:flex;align-items:center;gap:2px;color:{{ primary }};font-size:12px;font-weight:700;white-space:nowrap">En savoir plus<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M9 6l6 6-6 6"/></svg></div>
</div>
<div style="font-family:'Newsreader',serif;font-size:21px;color:#1E2A33;line-height:1.1;margin-top:3px">{{ currentTitle }}</div>
<div style="color:#6B7B86;font-size:13px;line-height:1.4;margin-top:4px">{{ currentDesc }}</div>
</div>
</div>
<!-- progress bar -->
<div style="padding:16px 20px 0">
<div style="height:7px;border-radius:4px;background:#EEE9E0;overflow:hidden"><div style="height:100%;width:{{ progressPct }};border-radius:4px;background:{{ primary }};transition:width .4s"></div></div>
</div>
<div style="display:flex;gap:11px;padding:14px 20px 0">
<button onClick="{{ nextStep }}" style="flex:1;border:1.5px solid #E2DCD2;background:#fff;padding:15px;border-radius:15px;color:#46555F;font-family:'Hanken Grotesk';font-size:15px;font-weight:700;cursor:pointer">Étape suivante</button>
<button onClick="{{ openChallenge }}" style="flex:1.4;border:none;background:{{ primary }};color:{{ onPrimary }};padding:15px;border-radius:15px;font-family:'Hanken Grotesk';font-size:15px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px;box-shadow:0 10px 22px -10px {{ primaryShadow }}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 2l2.4 7.4H22l-6 4.5 2.3 7.1L12 16.6 5.7 21l2.3-7.1-6-4.5h7.6Z"/></svg>Voir le défi</button>
</div>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 4b : SEQUENTIAL PROGRESS (no map) ============================= -->
<sc-if value="{{ isSeq }}">
<div style="position:absolute;inset:0;background:#F6F3EE;overflow:auto">
<div style="position:absolute;top:62px;left:14px;right:14px;display:flex;align-items:center;gap:10px;z-index:30">
<div onClick="{{ back }}" style="width:42px;height:42px;border-radius:14px;background:rgba(255,255,255,.85);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;cursor:pointer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1E2A33" stroke-width="2.4"><path d="M15 6l-6 6 6 6"/></svg>
</div>
<!-- segmented progress -->
<div style="flex:1;display:flex;gap:5px;align-items:center">
<sc-for list="{{ steps }}" as="s" hint-placeholder-count="5">
<div style="flex:1;height:6px;border-radius:3px;background:{{ s.barColor }}"></div>
</sc-for>
</div>
<div style="padding:7px 12px;border-radius:11px;background:rgba(255,255,255,.85);color:#46555F;font-size:13px;font-weight:700">{{ currentN }} / 5</div>
</div>
<div style="height:118px"></div>
<div style="position:relative;height:300px;margin:0 16px;border-radius:24px;overflow:hidden;background:linear-gradient(150deg,#5e7d8c,{{ primary }})">
<div style="position:absolute;inset:0;background:radial-gradient(120% 80% at 70% 12%, rgba(255,255,255,.18), transparent 55%)"></div>
<svg viewBox="0 0 354 300" style="position:absolute;bottom:0;width:100%;height:65%" preserveAspectRatio="none"><path d="M0 200 L0 120 L60 120 L60 70 L120 40 L180 70 L180 120 L240 120 L240 80 L290 80 L290 120 L354 120 L354 300 L0 300Z" fill="rgba(0,0,0,.2)"/></svg>
<div style="position:absolute;top:14px;left:14px;display:inline-flex;padding:7px 12px;border-radius:30px;background:rgba(10,16,22,.4);backdrop-filter:blur(6px);color:#fff;font-size:12.5px;font-weight:700">Étape {{ currentN }}</div>
</div>
<div style="padding:20px 22px 0">
<div style="font-family:'Newsreader',serif;font-size:27px;color:#1E2A33;letter-spacing:-.4px">{{ currentTitle }}</div>
<div style="color:#46555F;font-size:15.5px;line-height:1.6;margin-top:10px">{{ currentLong }}</div>
<!-- locked teaser -->
<div style="margin-top:22px;display:flex;align-items:center;gap:12px;padding:15px;border-radius:18px;background:#EFEBE3;border:1px dashed #D8D2C8">
<div style="width:42px;height:42px;border-radius:12px;background:#E0DACF;display:flex;align-items:center;justify-content:center;color:#9AA0A4"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></div>
<div><div style="font-size:14px;font-weight:700;color:#7A848C">Étape suivante verrouillée</div><div style="font-size:12.5px;color:#9AA0A4;margin-top:2px">Terminez le défi pour la déverrouiller</div></div>
</div>
</div>
<div style="height:110px"></div>
<div style="position:absolute;left:0;right:0;bottom:0;padding:14px 18px 28px;background:linear-gradient(to top,#F6F3EE 72%,rgba(246,243,238,0));display:flex;gap:11px">
<button onClick="{{ prevStep }}" style="width:54px;border:1.5px solid #E2DCD2;background:#fff;border-radius:15px;display:flex;align-items:center;justify-content:center;cursor:pointer"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#54636E" stroke-width="2.2"><path d="M15 6l-6 6 6 6"/></svg></button>
<button onClick="{{ openChallenge }}" style="flex:1;border:none;background:{{ primary }};color:{{ onPrimary }};padding:16px;border-radius:15px;font-family:'Hanken Grotesk';font-size:16px;font-weight:700;cursor:pointer;box-shadow:0 10px 22px -10px {{ primaryShadow }}">Voir le défi de l'étape</button>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN : STEP DETAIL (rich content) ============================= -->
<sc-if value="{{ isDetail }}">
<div style="position:absolute;inset:0;background:#F6F3EE;overflow:auto">
<!-- HERO -->
<div style="position:relative;height:312px;background:{{ detailHero }}">
<div style="position:absolute;inset:0;background:radial-gradient(120% 80% at 70% 15%, rgba(255,255,255,.16), transparent 55%)"></div>
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center">
<svg width="84" height="84" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,.5)" stroke-width="1.1"><path d="M3 21h18M5 21V8l7-4 7 4v13M9 21v-7h6v7M9 11h.01M15 11h.01"/></svg>
</div>
<div style="position:absolute;inset:0;background:linear-gradient(to top, rgba(10,16,22,.5), transparent 42%)"></div>
<div onClick="{{ back }}" style="position:absolute;top:62px;left:18px;width:42px;height:42px;border-radius:14px;background:rgba(255,255,255,.2);backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;cursor:pointer"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4"><path d="M15 6l-6 6 6 6"/></svg></div>
<div style="position:absolute;top:64px;right:18px;padding:8px 13px;border-radius:11px;background:rgba(10,16,22,.4);backdrop-filter:blur(6px);color:#fff;font-size:12.5px;font-weight:700;white-space:nowrap">Étape {{ currentN }} / 5</div>
<div style="position:absolute;left:18px;bottom:16px;display:flex;align-items:center;gap:7px;padding:6px 11px;border-radius:9px;background:rgba(255,255,255,.92);color:{{ primary }};font-size:11.5px;font-weight:700;white-space:nowrap"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 16 5-5 4 4 3-3 6 6"/></svg>{{ detailIdxLabel }}</div>
</div>
<!-- THUMBS -->
<div style="display:flex;gap:9px;padding:13px 18px 4px;overflow:auto">
<sc-for list="{{ detailImages }}" as="im" hint-placeholder-count="4">
<div onClick="{{ im.onTap }}" style="width:64px;height:48px;border-radius:11px;flex-shrink:0;background:{{ im.grad }};cursor:pointer;border:2.5px solid {{ im.ring }};box-shadow:0 2px 8px -3px rgba(0,0,0,.25)"></div>
</sc-for>
</div>
<!-- BODY -->
<div style="padding:10px 22px 0">
<div style="display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:8px;background:{{ primarySoft }};color:{{ primary }};font-size:11.5px;font-weight:700;letter-spacing:.3px;white-space:nowrap">LA CITÉ MÉDIÉVALE</div>
<div style="font-family:'Newsreader',serif;font-size:29px;color:#1E2A33;letter-spacing:-.4px;margin-top:11px;line-height:1.08">{{ currentTitle }}</div>
<!-- AUDIO GUIDE -->
<div style="margin-top:18px;display:flex;align-items:center;gap:14px;padding:13px;border-radius:18px;background:#fff;border:1px solid #ECE6DC;box-shadow:0 10px 24px -18px rgba(0,0,0,.35)">
<div style="width:50px;height:50px;border-radius:50%;flex-shrink:0;background:{{ primary }};color:{{ onPrimary }};display:flex;align-items:center;justify-content:center;box-shadow:0 8px 18px -8px {{ primaryShadow }}"><svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></div>
<div style="flex:1;min-width:0">
<div style="font-size:13.5px;font-weight:700;color:#1E2A33">Guide audio</div>
<div style="display:flex;align-items:flex-end;gap:2px;height:26px;margin-top:7px">
<sc-for list="{{ waveBars }}" as="b" hint-placeholder-count="40">
<div style="flex:1;height:{{ b.h }};border-radius:2px;background:{{ b.color }}"></div>
</sc-for>
</div>
</div>
<div style="font-size:12.5px;font-weight:700;color:#8A969F;white-space:nowrap">3:24</div>
</div>
<!-- TEXT -->
<div style="color:#46555F;font-size:15.5px;line-height:1.65;margin-top:18px">{{ currentLong }}</div>
<div style="color:#46555F;font-size:15.5px;line-height:1.65;margin-top:12px">{{ currentLong2 }}</div>
<!-- FACT -->
<div style="margin-top:18px;display:flex;gap:12px;padding:15px;border-radius:18px;background:{{ primarySoft }}">
<div style="flex-shrink:0;color:{{ primary }}"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v4h1"/></svg></div>
<div><div style="font-size:12px;font-weight:800;letter-spacing:.4px;color:{{ primary }}">LE SAVIEZ-VOUS ?</div><div style="color:#46555F;font-size:14px;line-height:1.5;margin-top:4px">{{ currentFact }}</div></div>
</div>
<!-- VIDEO -->
<div style="margin-top:16px;position:relative;height:172px;border-radius:18px;overflow:hidden;background:linear-gradient(150deg,#46555F,#1E2A33)">
<div style="position:absolute;inset:0;background:radial-gradient(100% 80% at 62% 8%, rgba(255,255,255,.13), transparent 55%)"></div>
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center"><div style="width:58px;height:58px;border-radius:50%;background:rgba(255,255,255,.94);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px -6px rgba(0,0,0,.4)"><svg width="22" height="22" viewBox="0 0 24 24" fill="#1E2A33"><path d="M8 5v14l11-7z"/></svg></div></div>
<div style="position:absolute;left:14px;bottom:13px;display:flex;align-items:center;gap:7px;color:#fff;font-size:13px;font-weight:700"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="5" width="15" height="14" rx="2"/><path d="m17 9 5-3v12l-5-3"/></svg>Visite filmée · 1 min 30</div>
</div>
</div>
<div style="height:104px"></div>
<!-- BOTTOM BAR -->
<div style="position:absolute;left:0;right:0;bottom:0;padding:14px 18px 28px;background:linear-gradient(to top,#F6F3EE 74%,rgba(246,243,238,0));display:flex;gap:11px">
<button onClick="{{ nextStep }}" style="flex:1;border:1.5px solid #E2DCD2;background:#fff;padding:16px;border-radius:15px;color:#46555F;font-family:'Hanken Grotesk';font-size:15px;font-weight:700;cursor:pointer">Étape suivante</button>
<button onClick="{{ openChallenge }}" style="flex:1.3;border:none;background:{{ primary }};color:{{ onPrimary }};padding:16px;border-radius:15px;font-family:'Hanken Grotesk';font-size:15px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px;box-shadow:0 10px 22px -10px {{ primaryShadow }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M12 2l2.4 7.4H22l-6 4.5 2.3 7.1L12 16.6 5.7 21l2.3-7.1-6-4.5h7.6Z"/></svg>Voir le défi</button>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 6a : END (normal) ============================= -->
<sc-if value="{{ isEndNormal }}">
<div style="position:absolute;inset:0;background:radial-gradient(110% 70% at 50% 0%, #ffffff, #F6F3EE 60%);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:0 34px">
<div style="position:relative;width:120px;height:120px;animation:pp-pop .5s ease both">
<div style="position:absolute;inset:0;border-radius:50%;background:rgba(46,158,107,.12)"></div>
<div style="position:absolute;inset:16px;border-radius:50%;background:#2E9E6B;display:flex;align-items:center;justify-content:center;box-shadow:0 16px 32px -10px rgba(46,158,107,.5)">
<svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.6"><path d="M5 13l4 4L19 7"/></svg>
</div>
</div>
<div style="font-family:'Newsreader',serif;font-size:32px;color:#1E2A33;margin-top:28px;letter-spacing:-.4px">Parcours terminé</div>
<div style="color:#6B7B86;font-size:15.5px;line-height:1.55;margin-top:10px">Vous avez exploré les cinq étapes des Remparts de la Cité. Bravo, l'enceinte n'a plus de secret pour vous.</div>
<div style="display:flex;gap:12px;margin-top:26px">
<div style="padding:16px 22px;border-radius:18px;background:#fff;border:1px solid #EEE9E0;box-shadow:0 8px 20px -16px rgba(0,0,0,.3)"><div style="font-family:'Newsreader',serif;font-size:28px;color:{{ primary }}">5</div><div style="font-size:12px;color:#8A969F;font-weight:600;margin-top:2px">étapes</div></div>
<div style="padding:16px 22px;border-radius:18px;background:#fff;border:1px solid #EEE9E0;box-shadow:0 8px 20px -16px rgba(0,0,0,.3)"><div style="font-family:'Newsreader',serif;font-size:28px;color:{{ primary }}">48<span style="font-size:15px"> min</span></div><div style="font-size:12px;color:#8A969F;font-weight:600;margin-top:2px">durée</div></div>
</div>
<button onClick="{{ goHome }}" style="margin-top:30px;width:100%;border:none;padding:17px;border-radius:18px;background:{{ primary }};color:{{ onPrimary }};font-family:'Hanken Grotesk';font-size:16px;font-weight:700;cursor:pointer;box-shadow:0 12px 26px -10px {{ primaryShadow }}">Retour à la section</button>
<button onClick="{{ goHome }}" style="margin-top:11px;background:none;border:none;color:#8A969F;font-size:14px;font-weight:600;cursor:pointer">Partager mon parcours</button>
</div>
</sc-if>
<!-- ============================= SCREEN 6b : END (game) ============================= -->
<sc-if value="{{ isEndGame }}">
<div style="position:absolute;inset:0;background:radial-gradient(120% 80% at 50% 18%, #1a2436, #0B1018 60%, #05070b)">
<div style="position:absolute;inset:0;background:radial-gradient(50% 35% at 50% 26%, rgba(212,175,55,.22), transparent 65%);animation:pp-glow 3.5s ease-in-out infinite"></div>
<svg viewBox="0 0 386 836" style="position:absolute;inset:0;width:100%;height:100%;opacity:.12" preserveAspectRatio="xMidYMid slice"><g stroke="#D4AF37" stroke-width="1" fill="none"><circle cx="193" cy="250" r="170"/><circle cx="193" cy="250" r="120"/></g></svg>
<!-- rays -->
<svg viewBox="0 0 386 836" style="position:absolute;inset:0;width:100%;height:100%;opacity:.5"><g stroke="rgba(212,175,55,.25)" stroke-width="2"><path d="M193 250 L193 30 M193 250 L373 110 M193 250 L13 110 M193 250 L353 250 M193 250 L33 250"/></g></svg>
<div style="position:absolute;top:150px;left:50%;transform:translateX(-50%);width:150px;height:150px;animation:pp-pop .6s ease both">
<div style="position:absolute;inset:0;border-radius:50%;border:1.5px solid rgba(212,175,55,.55);box-shadow:0 0 60px rgba(212,175,55,.4),inset 0 0 30px rgba(212,175,55,.18)"></div>
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center">
<svg width="60" height="60" viewBox="0 0 24 24" fill="none" stroke="#E7C765" stroke-width="1.4"><path d="M12 2l2.4 5.6L20 8l-4 4 1.2 6L12 15.5 6.8 18 8 12 4 8l5.6-.4Z"/></svg>
</div>
</div>
<div style="position:absolute;left:0;right:0;top:328px;padding:0 32px;text-align:center">
<div style="color:#D4AF37;font-size:12.5px;font-weight:700;letter-spacing:4px">ÉNIGME RÉSOLUE</div>
<div style="font-family:'Newsreader',serif;font-size:36px;color:#F4ECD8;margin-top:14px;line-height:1.05;letter-spacing:-.5px">Le secret est<br>à vous</div>
<div style="font-family:'Newsreader',serif;font-style:italic;font-size:16.5px;color:#b9ac8c;line-height:1.6;margin-top:16px">« Au creux de la dernière pierre brillait le sceau du moine. Vous avez percé le mystère du Cloître avant le lever du jour. »</div>
<div style="display:inline-flex;align-items:center;gap:10px;margin-top:22px;padding:11px 18px;border-radius:30px;border:1px solid rgba(212,175,55,.4);background:rgba(212,175,55,.08)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#E7C765" stroke-width="1.8"><path d="M8 21h8M12 17v4M5 4h14v4a7 7 0 0 1-14 0Z"/></svg>
<span style="color:#E7C765;font-size:13.5px;font-weight:700">Badge « Gardien du Cloître » obtenu</span>
</div>
</div>
<div style="position:absolute;left:0;right:0;bottom:0;padding:18px 26px 34px">
<button onClick="{{ goHome }}" style="width:100%;border:none;padding:18px;border-radius:16px;background:linear-gradient(180deg,#E7C765,#D4AF37);color:#1a1305;font-family:'Hanken Grotesk';font-size:17px;font-weight:800;cursor:pointer;box-shadow:0 0 30px rgba(212,175,55,.35)">Terminer l'aventure</button>
</div>
</div>
</sc-if>
<!-- ============================= SCREEN 5 : CHALLENGE OVERLAY ============================= -->
<sc-if value="{{ isChallenge }}">
<div style="position:absolute;inset:0;z-index:55">
<div onClick="{{ closeChallenge }}" style="position:absolute;inset:0;background:{{ challBackdrop }};backdrop-filter:blur(3px)"></div>
<div style="position:absolute;left:0;right:0;bottom:0;background:{{ challSheetBg }};border-radius:28px 28px 0 0;box-shadow:0 -20px 50px rgba(0,0,0,.4);animation:pp-rise .35s ease;padding-bottom:30px;max-height:90%;overflow:auto">
<div style="display:flex;justify-content:center;padding:11px 0 4px"><div style="width:40px;height:5px;border-radius:3px;background:{{ challHandle }}"></div></div>
<!-- IDLE: question -->
<sc-if value="{{ challIdle }}">
<div style="padding:8px 24px 0">
<div style="display:flex;align-items:center;justify-content:space-between">
<div style="display:inline-flex;align-items:center;gap:7px;padding:6px 12px;border-radius:9px;background:{{ challChipBg }};color:{{ challAccent }};font-size:12px;font-weight:700;letter-spacing:.3px;white-space:nowrap">{{ challChip }}</div>
<div onClick="{{ closeChallenge }}" style="width:34px;height:34px;border-radius:50%;background:{{ challCloseBg }};display:flex;align-items:center;justify-content:center;cursor:pointer"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="{{ challMuted }}" stroke-width="2.4"><path d="M6 6l12 12M18 6L6 18"/></svg></div>
</div>
<div style="font-family:'Newsreader',serif;font-size:24px;color:{{ challInk }};line-height:1.25;margin-top:14px">{{ challQuestion }}</div>
<div style="color:{{ challMuted }};font-size:14px;line-height:1.5;margin-top:8px">{{ challHint }}</div>
<!-- QCM -->
<sc-if value="{{ isQcm }}">
<div style="display:flex;flex-direction:column;gap:11px;margin-top:20px">
<sc-for list="{{ qcm }}" as="o" hint-placeholder-count="4">
<div onClick="{{ o.onTap }}" style="display:flex;align-items:center;gap:13px;padding:16px;border-radius:16px;background:{{ challOptBg }};border:1.5px solid {{ challOptBorder }};cursor:pointer">
<div style="width:30px;height:30px;border-radius:9px;flex-shrink:0;background:{{ challChipBg }};color:{{ challAccent }};display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px">{{ o.key }}</div>
<span style="color:{{ challInk }};font-size:16px;font-weight:600;white-space:nowrap">{{ o.t }}</span>
</div>
</sc-for>
</div>
</sc-if>
<!-- TEXT -->
<sc-if value="{{ isText }}">
<div style="margin-top:20px">
<div style="display:flex;align-items:center;gap:10px;padding:4px 4px 4px 18px;border-radius:16px;background:{{ challOptBg }};border:1.5px solid {{ challAccent }}">
<span style="flex:1;color:{{ challInk }};font-size:17px;font-weight:600">vitrail{{ caret }}</span>
<button onClick="{{ submitGeneric }}" style="width:46px;height:46px;border-radius:12px;border:none;background:{{ challAccent }};color:{{ challOnAccent }};display:flex;align-items:center;justify-content:center;cursor:pointer"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M5 12h14M13 6l6 6-6 6"/></svg></button>
</div>
<div style="margin-top:16px;background:{{ challKbBg }};border-radius:14px;padding:8px;display:grid;grid-template-columns:repeat(10,1fr);gap:5px">
<sc-for list="{{ keyboard }}" as="k" hint-placeholder-count="10">
<div style="height:38px;border-radius:7px;background:{{ challKeyBg }};display:flex;align-items:center;justify-content:center;color:{{ challInk }};font-size:14px;font-weight:600">{{ k }}</div>
</sc-for>
</div>
</div>
</sc-if>
<!-- PIN / DIGICODE -->
<sc-if value="{{ isPin }}">
<div style="margin-top:22px">
<div style="display:flex;justify-content:center;gap:14px">
<sc-for list="{{ pinDots }}" as="d" hint-placeholder-count="4">
<div style="width:52px;height:62px;border-radius:14px;background:{{ challOptBg }};border:1.5px solid {{ d.border }};display:flex;align-items:center;justify-content:center;font-family:'Newsreader',serif;font-size:30px;color:{{ challInk }}">{{ d.ch }}</div>
</sc-for>
</div>
<div style="margin:22px auto 0;max-width:280px;display:grid;grid-template-columns:repeat(3,1fr);gap:13px">
<sc-for list="{{ keypad }}" as="k" hint-placeholder-count="12">
<div onClick="{{ k.onTap }}" style="height:64px;border-radius:50%;background:{{ k.bg }};display:flex;align-items:center;justify-content:center;color:{{ k.fg }};font-size:25px;font-weight:600;cursor:pointer">{{ k.label }}</div>
</sc-for>
</div>
</div>
</sc-if>
<!-- PUZZLE -->
<sc-if value="{{ isPuzzle }}">
<div style="margin-top:20px">
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:7px;border-radius:16px;overflow:hidden">
<sc-for list="{{ puzzle }}" as="t" hint-placeholder-count="9">
<div onClick="{{ t.onTap }}" style="aspect-ratio:1;border-radius:10px;background:{{ t.bg }};display:flex;align-items:center;justify-content:center;cursor:pointer;border:2px solid {{ t.border }};position:relative">
<span style="font-family:'Newsreader',serif;font-size:24px;color:rgba(255,255,255,.85)">{{ t.n }}</span>
</div>
</sc-for>
</div>
<button onClick="{{ submitGeneric }}" style="width:100%;margin-top:18px;border:none;background:{{ challAccent }};color:{{ challOnAccent }};padding:16px;border-radius:15px;font-family:'Hanken Grotesk';font-size:16px;font-weight:700;cursor:pointer">Valider le motif</button>
</div>
</sc-if>
</div>
</sc-if>
<!-- SUCCESS -->
<sc-if value="{{ challSuccess }}">
<div style="padding:30px 28px 8px;text-align:center;animation:pp-rise .3s ease">
<div style="width:88px;height:88px;margin:0 auto;border-radius:50%;background:rgba(46,158,107,.14);display:flex;align-items:center;justify-content:center;animation:pp-pop .45s ease both">
<div style="width:62px;height:62px;border-radius:50%;background:#2E9E6B;display:flex;align-items:center;justify-content:center;box-shadow:0 12px 26px -8px rgba(46,158,107,.5)"><svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><path d="M5 13l4 4L19 7"/></svg></div>
</div>
<div style="font-family:'Newsreader',serif;font-size:26px;color:{{ challInk }};margin-top:18px">Bien joué !</div>
<div style="color:{{ challMuted }};font-size:15px;line-height:1.5;margin-top:8px">{{ challSuccessMsg }}</div>
<button onClick="{{ continueAfter }}" style="width:100%;margin-top:24px;border:none;background:#2E9E6B;color:#fff;padding:17px;border-radius:16px;font-family:'Hanken Grotesk';font-size:16px;font-weight:700;cursor:pointer;box-shadow:0 12px 26px -10px rgba(46,158,107,.5)">{{ challContinueLabel }}</button>
</div>
</sc-if>
<!-- FAIL -->
<sc-if value="{{ challFail }}">
<div style="padding:30px 28px 8px;text-align:center;animation:pp-rise .3s ease">
<div style="width:88px;height:88px;margin:0 auto;border-radius:50%;background:rgba(209,67,67,.12);display:flex;align-items:center;justify-content:center;animation:pp-pop .45s ease both">
<div style="width:62px;height:62px;border-radius:50%;background:#D14343;display:flex;align-items:center;justify-content:center;box-shadow:0 12px 26px -8px rgba(209,67,67,.45)"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.8"><path d="M7 7l10 10M17 7L7 17"/></svg></div>
</div>
<div style="font-family:'Newsreader',serif;font-size:26px;color:{{ challInk }};margin-top:18px">Pas tout à fait…</div>
<div style="color:{{ challMuted }};font-size:15px;line-height:1.5;margin-top:8px">Un indice se cache dans la description de l'étape. Observez bien les détails autour de vous.</div>
<button onClick="{{ retryChallenge }}" style="width:100%;margin-top:24px;border:none;background:{{ challAccent }};color:{{ challOnAccent }};padding:17px;border-radius:16px;font-family:'Hanken Grotesk';font-size:16px;font-weight:700;cursor:pointer">Réessayer</button>
</div>
</sc-if>
</div>
</div>
</sc-if>
<!-- HOME INDICATOR -->
<div style="position:absolute;bottom:8px;left:50%;transform:translateX(-50%);width:130px;height:5px;border-radius:3px;background:{{ homeIndColor }};z-index:70"></div>
</div>
</div>
<!-- ===== DEMO CONTROLS (only when controls=true) ===== -->
<sc-if value="{{ showControls }}">
<div style="width:408px;background:#fff;border:1px solid #E7E1D8;border-radius:20px;padding:14px 16px;box-shadow:0 14px 30px -18px rgba(0,0,0,.3)">
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap">
<div style="display:flex;align-items:center;gap:9px">
<span style="font-size:11.5px;font-weight:700;color:#8A969F;letter-spacing:.4px">COULEUR</span>
<sc-for list="{{ swatches }}" as="c" hint-placeholder-count="3">
<div onClick="{{ c.onTap }}" style="width:30px;height:30px;border-radius:50%;background:{{ c.hex }};cursor:pointer;border:3px solid {{ c.ring }};box-shadow:0 2px 6px rgba(0,0,0,.15)"></div>
</sc-for>
</div>
<div style="display:flex;align-items:center;gap:7px">
<div onClick="{{ setNormal }}" style="padding:9px 14px;border-radius:11px;font-size:13px;font-weight:700;cursor:pointer;white-space:nowrap;background:{{ normalTabBg }};color:{{ normalTabFg }}">Normal</div>
<div onClick="{{ setGame }}" style="padding:9px 14px;border-radius:11px;font-size:13px;font-weight:700;cursor:pointer;white-space:nowrap;background:{{ gameTabBg }};color:{{ gameTabFg }}">🔦 Game</div>
</div>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:12px;padding-top:12px;border-top:1px solid #F0EBE3">
<span style="font-size:12.5px;color:#8A969F">Prototype interactif · écran : <b style="color:#54636E">{{ screenLabel }}</b></span>
<div onClick="{{ resetFlow }}" style="font-size:12.5px;font-weight:700;color:#54636E;cursor:pointer;display:flex;align-items:center;gap:5px"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M4 12a8 8 0 1 1 2.3 5.6M4 12V6m0 6h6"/></svg>Recommencer</div>
</div>
</div>
</sc-if>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:440,&quot;height&quot;:960},&quot;primaryColor&quot;:{&quot;editor&quot;:&quot;color&quot;,&quot;default&quot;:&quot;#264863&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;mode&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;normal&quot;,&quot;game&quot;],&quot;default&quot;:&quot;normal&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;startScreen&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;home&quot;,&quot;pathList&quot;,&quot;start&quot;,&quot;gameStart&quot;,&quot;map&quot;,&quot;seq&quot;,&quot;challenge&quot;,&quot;endNormal&quot;,&quot;endGame&quot;],&quot;default&quot;:&quot;home&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;challengeType&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;qcm&quot;,&quot;text&quot;,&quot;pin&quot;,&quot;puzzle&quot;],&quot;default&quot;:&quot;qcm&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;challengeState&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;idle&quot;,&quot;success&quot;,&quot;fail&quot;],&quot;default&quot;:&quot;idle&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;stepIndex&quot;:{&quot;editor&quot;:&quot;int&quot;,&quot;default&quot;:2,&quot;min&quot;:0,&quot;max&quot;:4,&quot;tsType&quot;:&quot;number&quot;},&quot;controls&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;}}">
class Component extends DCLogic {
constructor(props) {
super(props);
const start = props.startScreen || 'home';
const startsOnChallenge = start === 'challenge';
const md = props.mode || 'normal';
this.state = {
primary: props.primaryColor || '#264863',
mode: md,
screen: startsOnChallenge ? (md === 'game' ? 'gameStart' : 'map') : start,
challengeOpen: startsOnChallenge,
detailImg: 0,
stepIndex: props.stepIndex != null ? parseInt(props.stepIndex, 10) : 2,
challengeType: props.challengeType || 'qcm',
challengeState: props.challengeState || 'idle',
pin: '',
};
}
// ---- color helpers ----
hexToRgb(h){ h=(h||'#264863').replace('#',''); return [parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)]; }
onColor(h){ const [r,g,b]=this.hexToRgb(h); const l=(0.299*r+0.587*g+0.114*b)/255; return l>0.62?'#15212b':'#ffffff'; }
rgba(h,a){ const [r,g,b]=this.hexToRgb(h); return `rgba(${r},${g},${b},${a})`; }
set(p){ this.setState(p); }
go(screen){ this.setState({ screen }); }
stepData(){
return [
{n:1,t:'Porte Narbonnaise',x:'60px',y:'250px',desc:'L\u2019entr\u00e9e fortifi\u00e9e de la Cit\u00e9, gard\u00e9e par deux tours jumelles.',long:'Deux tours massives encadrent la herse. Observez les bossages de pierre, taill\u00e9s pour d\u00e9vier les projectiles des assaillants.'},
{n:2,t:'Tour du Tr\u00e9seau',x:'140px',y:'165px',desc:'O\u00f9 l\u2019on conservait jadis les archives du vicomte.',long:'Cette tour ronde abritait le tr\u00e9sor et les chartes. Ses meurtri\u00e8res en croix permettaient le tir \u00e0 l\u2019arbal\u00e8te.'},
{n:3,t:'La Cath\u00e9drale',x:'250px',y:'250px',desc:'Vitraux du XIIIe si\u00e8cle et nef baign\u00e9e de lumi\u00e8re.',long:'Saint-Nazaire m\u00eale roman et gothique. Les vitraux comptent parmi les plus anciens du Midi \u2014 cherchez l\u2019Arbre de J\u00e9ss\u00e9.'},
{n:4,t:'Ch\u00e2teau Comtal',x:'150px',y:'360px',desc:'Le c\u0153ur d\u00e9fensif, citadelle dans la citadelle.',long:'Un ch\u00e2teau dans la ville : douves s\u00e8ches, pont-levis et chemin de ronde dominant la double enceinte.'},
{n:5,t:'Le Belv\u00e9d\u00e8re',x:'290px',y:'420px',desc:'Panorama sur la plaine de l\u2019Aude et les Pyr\u00e9n\u00e9es.',long:'Derni\u00e8re halte : tout en bas serpente l\u2019Aude. Par temps clair, la cha\u00eene des Pyr\u00e9n\u00e9es ferme l\u2019horizon.'},
];
}
renderVals(){
const P = this.state.primary;
const onP = this.onColor(P);
const idx = this.state.stepIndex;
const screen = this.state.screen;
const mode = this.state.mode;
const data = this.stepData();
const steps = data.map((s,i)=>{
const isDone = i<idx, isCurrent = i===idx, isLocked = i>idx;
let bg='#D8D2C8', fg='#9AA0A4', barColor='#E2DCD2';
if(isDone){ bg='#2E9E6B'; fg='#ffffff'; barColor='#2E9E6B'; }
else if(isCurrent){ bg=P; fg=onP; barColor=P; }
return { n:s.n, title:s.t, x:s.x, y:s.y, isDone, isCurrent, isLocked, bg, fg, barColor,
size: isCurrent ? '46px' : '38px' };
});
const cur = data[Math.min(idx,4)];
// ---- step detail rich content ----
const di = this.state.detailImg || 0;
const imgGrads = [
'linear-gradient(150deg,#5e7d8c,'+P+')',
'linear-gradient(160deg,#c9a45c,#9c6b2e)',
'linear-gradient(150deg,#6f8f64,#3B6B4A)',
'linear-gradient(150deg,#7c6c88,#46405e)',
];
const detailImages = imgGrads.map((g,i)=>({ grad:g, ring: i===di ? P : 'transparent', onTap:()=>this.setState({detailImg:i}) }));
const wh=[...Array(46)].map((_,i)=> Math.round(7 + 17*Math.abs(Math.sin(i*0.9)) + 7*Math.abs(Math.cos(i*0.37))));
const waveBars = wh.map((h,i)=>({ h:h+'px', color: i<15 ? P : '#D6D0C6' }));
const long2List=[
'Observez les bossages de la pierre : ces reliefs taill\u00e9s d\u00e9viaient les tirs et renfor\u00e7aient la base des deux tours.',
'\u00c0 l\u2019int\u00e9rieur, un escalier en vis dessert les \u00e9tages de tir, \u00e9clair\u00e9s par d\u2019\u00e9troites meurtri\u00e8res en croix.',
'Levez les yeux vers la rosace : la lumi\u00e8re y change de couleur au fil de la journ\u00e9e et anime toute la nef.',
'Douves s\u00e8ches, pont-levis et chemin de ronde composaient une d\u00e9fense que l\u2019on pouvait tenir m\u00eame ville prise.',
'Tout en bas serpente l\u2019Aude ; la vue embrasse la plaine viticole jusqu\u2019aux premiers reliefs des Cor\u00e8bres.',
];
const factList=[
'La herse de bois pouvait tomber en moins de cinq secondes pour pi\u00e9ger les assaillants dans le passage.',
'Son nom vient de \u00ab tr\u00e9sor \u00bb : on y conservait les chartes et l\u2019argent du vicomte de Carcassonne.',
'Certains vitraux datent du XIIIe si\u00e8cle et comptent parmi les plus anciens du sud de la France.',
'On l\u2019appelle la \u00ab citadelle dans la citadelle \u00bb : un ch\u00e2teau fortifi\u00e9 \u00e0 l\u2019int\u00e9rieur m\u00eame de la ville.',
'Par temps clair, on aper\u00e7oit la cha\u00eene des Pyr\u00e9n\u00e9es \u00e0 plus de 80 kilom\u00e8tres au sud.',
];
// screen flags
const F = {
isHome: screen==='home', isPathList: screen==='pathList', isStart: screen==='start',
isGameStart: screen==='gameStart', isMap: screen==='map', isSeq: screen==='seq',
isEndNormal: screen==='endNormal', isEndGame: screen==='endGame', isChallenge: this.state.challengeOpen,
isDetail: screen==='detail',
};
// status bar / chrome theming
const darkUI = screen==='gameStart' || screen==='endGame' || (this.state.challengeOpen && mode==='game');
const screenBg = darkUI ? '#0B1018' : '#F6F3EE';
const statusColor = darkUI ? '#F4ECD8' : '#15212b';
const homeIndColor = darkUI ? 'rgba(212,175,55,.5)' : 'rgba(20,30,40,.22)';
// paths (screen 2)
const paths = [
{key:'libre', emoji:'\ud83c\udff0', thumb:'linear-gradient(150deg,#5e7d8c,'+P+')', title:'Les Remparts de la Cit\u00e9', desc:'Tours, courtines et l\u00e9gendes de la double enceinte.', steps:'5 \u00e9tapes \u00b7 ~50 min', badge:'LIBRE', badgeBg:this.rgba(P,.12), badgeFg:P, onTap:()=>this.setState({mode:'normal',screen:'start'})},
{key:'guide', emoji:'\ud83d\udce3', thumb:'linear-gradient(150deg,#6a8a60,#3B6B4A)', title:'Sur les pas des b\u00e2tisseurs', desc:'Itin\u00e9raire guid\u00e9, \u00e9tape par \u00e9tape.', steps:'7 \u00e9tapes \u00b7 ~1h10', badge:'GUID\u00c9', badgeBg:'rgba(59,107,74,.12)', badgeFg:'#3B6B4A', onTap:()=>this.setState({mode:'normal',screen:'start'})},
{key:'game', emoji:'\ud83d\udd26', thumb:'linear-gradient(150deg,#2a2336,#0B1018)', title:'Le Secret du Clo\u00eetre', desc:'Cinq \u00e9nigmes \u00e0 r\u00e9soudre avant l\u2019aube.', steps:'5 \u00e9nigmes \u00b7 ~45 min', badge:'\ud83d\udd26 ESCAPE GAME', badgeBg:'rgba(212,175,55,.16)', badgeFg:'#9c7b1e', onTap:()=>this.setState({mode:'game',screen:'gameStart'})},
];
// ---- CHALLENGE ----
const ct = this.state.challengeType;
const cs = this.state.challengeState;
const gameChall = mode==='game';
const accent = gameChall ? '#D4AF37' : P;
const onAccent = gameChall ? '#1a1305' : onP;
const chall = {
challIdle: cs==='idle', challSuccess: cs==='success', challFail: cs==='fail',
isQcm: ct==='qcm', isText: ct==='text', isPin: ct==='pin', isPuzzle: ct==='puzzle',
challAccent: accent, challOnAccent: onAccent,
challSheetBg: gameChall ? '#121821' : '#ffffff',
challBackdrop: gameChall ? 'rgba(4,7,12,.7)' : 'rgba(20,30,40,.45)',
challInk: gameChall ? '#F4ECD8' : '#1E2A33',
challMuted: gameChall ? '#8a8068' : '#6B7B86',
challHandle: gameChall ? 'rgba(212,175,55,.4)' : '#E2DCD2',
challChipBg: gameChall ? 'rgba(212,175,55,.14)' : this.rgba(P,.12),
challCloseBg: gameChall ? 'rgba(255,255,255,.06)' : '#F0EBE3',
challOptBg: gameChall ? 'rgba(255,255,255,.04)' : '#F7F4EF',
challOptBorder: gameChall ? 'rgba(212,175,55,.18)' : '#ECE6DC',
challKbBg: gameChall ? 'rgba(255,255,255,.04)' : '#EAE5DC',
challKeyBg: gameChall ? 'rgba(255,255,255,.07)' : '#fff',
challChip: gameChall ? '\u00c9NIGME '+cur.n : 'D\u00c9FI \u00b7 \u00c9TAPE '+cur.n,
};
// challenge content per type
let challQuestion='', challHint='', challSuccessMsg='Vous avez trouv\u00e9 la bonne r\u00e9ponse.';
let qcm=[];
if(ct==='qcm'){
challQuestion='En quel mat\u00e9riau sont les c\u00e9l\u00e8bres \u0153uvres qui illuminent la cath\u00e9drale ?';
challHint='Indice : la lumi\u00e8re du XIIIe si\u00e8cle.';
const opts=[{key:'A',t:'La fresque'},{key:'B',t:'Le vitrail',correct:true},{key:'C',t:'La mosa\u00efque'},{key:'D',t:'La tapisserie'}];
qcm=opts.map(o=>({...o, onTap:()=> this.setState({challengeState: o.correct?'success':'fail'})}));
challSuccessMsg='Les vitraux du XIIIe si\u00e8cle \u00e9clairent bien la nef.';
} else if(ct==='text'){
challQuestion='Quel art du verre color\u00e9 orne les fen\u00eatres de la cath\u00e9drale ?';
challHint='\u00c9crivez le mot que vous lisez sur la plaque.';
} else if(ct==='pin'){
challQuestion='Le cadran de la tour indique une ann\u00e9e grav\u00e9e. Composez le code.';
challHint='Quatre chiffres \u2014 l\u2019ann\u00e9e du grand si\u00e8ge.';
} else if(ct==='puzzle'){
challQuestion='Reconstituez le blason dispers\u00e9 sur la porte.';
challHint='Touchez les fragments pour les remettre en ordre.';
}
chall.challQuestion=challQuestion; chall.challHint=challHint; chall.qcm=qcm; chall.challSuccessMsg=challSuccessMsg;
chall.caret=''; // simple
// keyboard (text)
chall.keyboard = 'azertyuiopqsdfghjklmwxcvbn'.toUpperCase().split('').slice(0,20);
// pin
const pin=this.state.pin;
const target='1209';
chall.pinDots=[0,1,2,3].map(i=>({ ch: pin[i]||'', border: pin.length===i?accent:(gameChall?'rgba(212,175,55,.2)':'#ECE6DC') }));
const keypadKeys=['1','2','3','4','5','6','7','8','9','','0','del'];
chall.keypad=keypadKeys.map(k=>{
if(k===''){ return {label:'', bg:'transparent', fg:'transparent', onTap:()=>{}}; }
if(k==='del'){ return {label:'\u232b', bg: gameChall?'rgba(255,255,255,.05)':'#EFEBE3', fg: gameChall?'#8a8068':'#54636E', onTap:()=>this.setState({pin: pin.slice(0,-1)})}; }
return { label:k, bg: gameChall?'rgba(255,255,255,.07)':'#fff', fg: gameChall?'#F4ECD8':'#1E2A33',
onTap:()=>{ if(pin.length<4){ const np=pin+k; if(np.length===4){ this.setState({pin:np, challengeState: np===target?'success':'fail'}); } else { this.setState({pin:np}); } } } };
});
// puzzle
const puzColors=['#3a5f70','#5e7d8c','#264863','#6a8a60','#3B6B4A','#a9663f','#8a4b28','#7a6a52','#46555F'];
chall.puzzle=[5,2,8,1,7,3,9,4,6].map((n,i)=>({ n, bg:puzColors[i], border: gameChall?'rgba(212,175,55,.25)':'rgba(255,255,255,.5)', onTap:()=>{} }));
chall.submitGeneric=()=>this.setState({challengeState:'success'});
chall.continueAfter=()=>this.continueAfter();
chall.retryChallenge=()=>this.setState({challengeState:'idle', pin:''});
chall.closeChallenge=()=>this.setState({challengeOpen:false, challengeState:'idle', pin:''});
chall.challContinueLabel = mode==='game' ? 'Indice suivant' : (idx>=4?'Terminer le parcours':'\u00c9tape suivante');
// ---- controls ----
const swColors=['#264863','#3B6B4A','#B5562E'];
const swatches=swColors.map(hex=>({ hex, ring: hex===P?this.rgba(hex,.45):'#fff', onTap:()=>this.setState({primary:hex}) }));
const screenNames={home:'Accueil',pathList:'Parcours',start:'D\u00e9marrage',gameStart:'Game \u2014 intro',map:'Carte 4a',seq:'S\u00e9quence 4b',challenge:'D\u00e9fi',endNormal:'Fin',endGame:'Game \u2014 victoire'};
return {
...F, ...chall,
primary:P, onPrimary:onP, primarySoft:this.rgba(P,.12), primaryShadow:this.rgba(P,.55),
screenBg, statusColor, homeIndColor,
steps, currentN:cur.n, currentTitle:cur.t, currentDesc:cur.desc, currentLong:cur.long,
progressText:'\u00c9tape '+(idx+1)+' / 5',
progressPct: ((idx)/5*100)+'%',
paths,
// step detail
detailImages, detailHero: imgGrads[di], detailIdxLabel: (di+1)+' / '+imgGrads.length,
waveBars, currentLong2: long2List[Math.min(idx,4)], currentFact: factList[Math.min(idx,4)],
openDetail:()=>this.go('detail'),
// nav handlers
openSection:()=>this.go('pathList'),
goHome:()=>this.setState({screen:'home', mode:'normal', stepIndex:2}),
back:()=>this.setState({screen: this.state.screen==='detail' ? 'map' : 'pathList'}),
beginNormal:()=>this.setState({screen:'map', stepIndex:2}),
beginGame:()=>this.setState({screen:'gameStart', mode:'game', challengeOpen:true, challengeType:'pin', challengeState:'idle', pin:''}),
openChallenge:()=>this.setState({challengeOpen:true, challengeType: this.state.mode==='game'?'pin':'qcm', challengeState:'idle', pin:''}),
nextStep:()=>this.nextStep(),
prevStep:()=>this.setState({stepIndex: Math.max(0,idx-1)}),
// controls
showControls: this.props.controls==='true' || this.props.controls===true,
swatches,
setNormal:()=>this.setState({mode:'normal', screen:'home', stepIndex:2, challengeOpen:false}),
setGame:()=>this.setState({mode:'game', screen:'gameStart', challengeOpen:false}),
normalTabBg: mode==='normal'?P:'#F0EBE3', normalTabFg: mode==='normal'?onP:'#8A969F',
gameTabBg: mode==='game'?'#1a2230':'#F0EBE3', gameTabFg: mode==='game'?'#D4AF37':'#8A969F',
screenLabel: screenNames[screen]||screen,
resetFlow:()=>this.setState({screen: this.state.mode==='game'?'gameStart':'home', stepIndex:2, challengeOpen:false, challengeState:'idle', pin:''}),
};
}
nextStep(){
const idx=this.state.stepIndex;
if(idx>=4){ this.setState({screen: this.state.mode==='game'?'endGame':'endNormal'}); }
else { this.setState({stepIndex: idx+1}); }
}
continueAfter(){
if(this.state.mode==='game'){ this.setState({screen:'endGame', challengeOpen:false}); return; }
const idx=this.state.stepIndex;
if(idx>=4){ this.setState({screen:'endNormal', challengeOpen:false, challengeState:'idle'}); }
else { this.setState({screen:'map', stepIndex:idx+1, challengeOpen:false, challengeState:'idle'}); }
}
}
</script>
</body>
</html>