<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Genesis — Exaucez vos souhaits</title> <style> /* ===== RESET & BASE ===== */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } html, body { width: 100%; height: 100%; overflow-x: hidden; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #0a0a0f; color: #e0e0ff; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; min-height: 100dvh; padding: 16px; position: relative; box-shadow: inset 0 0 150px rgba(0,0,0,0.8); overflow-y: auto; } /* Fond anime profond - respiration cosmique */ body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, #0a0a0f 0%, #0d0d1a 30%, #0a0a12 60%, #07070d 100%); background-size: 400% 400%; animation: breatheBg 60s ease-in-out infinite; z-index: -2; } @keyframes breatheBg { 0%, 100% { background-position: 0% 0%; } 25% { background-position: 50% 50%; } 50% { background-position: 100% 100%; } 75% { background-position: 50% 0%; } } /* Vignettage profond */ body::after { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; box-shadow: inset 0 0 200px rgba(0,0,0,0.85); pointer-events: none; z-index: -1; } /* ===== PARTICULES DE POUSSIERE COSMIQUE ===== */ .cosmic-dust { position: fixed; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; z-index: 0; overflow: hidden; } .dust-particle { position: absolute; width: 2px; height: 2px; background: #7c7cdd; border-radius: 50%; opacity: 0; animation: dustFloat var(--dur, 20s) ease-in-out infinite; animation-delay: var(--delay, 0s); left: var(--x, 50%); top: var(--y, 50%); } @keyframes dustFloat { 0% { opacity: 0; transform: translate(0, 0) scale(0.5); } 10% { opacity: 0.12; } 50% { opacity: 0.08; transform: translate(var(--dx1, 30px), var(--dy1, -40px)) scale(1); } 90% { opacity: 0.1; } 100% { opacity: 0; transform: translate(var(--dx2, -20px), var(--dy2, 20px)) scale(0.6); } } /* ===== HALO LUMINEUX CENTRAL ===== */ .central-halo { position: fixed; top: 50%; left: 50%; width: 400px; height: 400px; transform: translate(-50%, -50%); background: radial-gradient(circle at center, rgba(124,124,221,0.06) 0%, rgba(124,124,221,0.02) 40%, transparent 70%); pointer-events: none; z-index: 0; animation: haloPulse 8s ease-in-out infinite; } @keyframes haloPulse { 0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.05); } } /* ===== LAYOUT PRINCIPAL ===== */ .main-container { position: relative; z-index: 1; width: 100%; max-width: 560px; display: flex; flex-direction: column; align-items: center; gap: 24px; padding: 16px 0; } /* ===== TITRE GENESIS ===== */ .genesis-title { font-size: 14px; letter-spacing: 6px; text-transform: uppercase; color: #4a4a8a; font-weight: 300; margin-bottom: 4px; text-align: center; user-select: none; } .genesis-subtitle { font-size: 12px; letter-spacing: 2px; color: #4a4a8a; opacity: 0.6; font-weight: 200; text-align: center; margin-bottom: 8px; } /* ===== ZONE DE SOUHAIT (PORTAIL) ===== */ .wish-portal { position: relative; width: 100%; max-width: 500px; border-radius: 16px; padding: 2px; transition: box-shadow 0.3s ease; } .wish-portal::before { content: ''; position: absolute; inset: -2px; border-radius: 18px; background: linear-gradient(135deg, rgba(124,124,221,0.15), transparent 40%, rgba(124,124,221,0.05)); opacity: 0; transition: opacity 0.5s ease; z-index: -1; } .wish-portal.working::before { opacity: 1; } .wish-portal.glow-pulse { animation: portalGlowIdle 4s ease-in-out infinite; } .wish-portal.working { animation: portalGlowActive 2s ease-in-out infinite; } @keyframes portalGlowIdle { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.01); } } @keyframes portalGlowActive { 0%, 100% { transform: scale(1); box-shadow: 0 0 20px rgba(124,124,221,0.15); } 50% { transform: scale(1.02); box-shadow: 0 0 40px rgba(124,124,221,0.3); } } #genesis-wish-input { width: 100%; min-height: 100px; max-height: 200px; padding: 20px 20px 20px 24px; background: rgba(16,16,26,0.75); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 1px solid rgba(124,124,221,0.2); border-radius: 14px; color: #e0e0ff; font-size: 17px; line-height: 1.6; font-weight: 300; letter-spacing: 0.3px; resize: none; outline: none; transition: border-color 0.4s ease, box-shadow 0.4s ease; font-family: inherit; position: relative; z-index: 1; } #genesis-wish-input:focus { border-color: rgba(124,124,221,0.4); box-shadow: 0 0 30px rgba(124,124,221,0.08); } #genesis-wish-input::placeholder { color: #4a4a8a; font-size: 15px; font-style: italic; font-weight: 200; letter-spacing: 0.5px; opacity: 0.7; } /* ===== BOUTON SOUMETTRE ===== */ .submit-area { display: flex; justify-content: center; width: 100%; margin-top: -8px; } #genesis-submit-btn { background: none; border: 1px solid rgba(124,124,221,0.2); border-radius: 30px; padding: 12px 32px; color: #b8b8ff; font-size: 14px; font-weight: 300; letter-spacing: 2px; cursor: pointer; transition: all 0.4s ease; font-family: inherit; position: relative; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); background: rgba(16,16,26,0.3); min-height: 48px; min-width: 200px; display: flex; align-items: center; justify-content: center; gap: 10px; } #genesis-submit-btn:hover, #genesis-submit-btn:focus-visible { border-color: rgba(124,124,221,0.5); background: rgba(124,124,221,0.08); box-shadow: 0 0 20px rgba(124,124,221,0.1); } #genesis-submit-btn:active { transform: scale(0.97); } #genesis-submit-btn.loading { pointer-events: none; opacity: 0.7; } .spinner { display: none; width: 16px; height: 16px; border: 2px solid rgba(124,124,221,0.2); border-top-color: #7c7cdd; border-radius: 50%; animation: spin 0.8s linear infinite; } #genesis-submit-btn.loading .spinner { display: inline-block; } #genesis-submit-btn.loading .btn-text { display: none; } @keyframes spin { to { transform: rotate(360deg); } } /* ===== CHAINE DE LUMINESCENCE (3 PHASES) ===== */ .phases-container { width: 100%; max-width: 440px; padding: 12px 0; display: flex; flex-direction: column; align-items: center; gap: 8px; } .phase-label { font-size: 11px; letter-spacing: 1.5px; color: #4a4a8a; font-weight: 200; text-transform: uppercase; margin-bottom: 4px; } .phase-chain { display: flex; align-items: center; justify-content: space-between; width: 100%; position: relative; padding: 8px 0; } .phase-chain::before { content: ''; position: absolute; top: 50%; left: 12.5%; right: 12.5%; height: 1px; background: linear-gradient(90deg, #4a4a8a, #4a4a8a); transform: translateY(-50%); z-index: 0; } .phase-chain.active-line::before { background: linear-gradient(90deg, #7c7cdd, #7c7cdd); transition: background 0.6s ease; } .phase-node { display: flex; flex-direction: column; align-items: center; gap: 8px; position: relative; z-index: 1; flex: 1; } .phase-circle { width: 40px; height: 40px; border-radius: 50%; border: 2px solid #4a4a8a; background: rgba(16,16,26,0.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); position: relative; display: flex; align-items: center; justify-content: center; transition: border-color 0.8s ease; } .phase-circle.active { border-color: #7c7cdd; } .phase-circle.done { border-color: #2ecc71; background: rgba(46,204,113,0.1); } .phase-circle .fill { position: absolute; inset: 3px; border-radius: 50%; background: radial-gradient(circle at center, rgba(124,124,221,0.3), rgba(124,124,221,0.1)); clip-path: circle(0% at 50% 50%); transition: clip-path 0.1s linear; } .phase-circle.active .fill { clip-path: circle(100% at 50% 50%); animation: waveFill 1.5s ease-out forwards; } @keyframes waveFill { 0% { clip-path: circle(0% at 50% 50%); } 100% { clip-path: circle(100% at 50% 50%); } } .phase-circle.done .fill { background: radial-gradient(circle at center, rgba(46,204,113,0.3), rgba(46,204,113,0.1)); clip-path: circle(100% at 50% 50%); } .phase-circle .inner-icon { font-size: 14px; opacity: opacity 0.4; transition: opacity 0.4s ease; } .phase-circle.active .inner-icon, .phase-circle.done .inner-icon { opacity: 0.9; } .phase-name { font-size: 10px; color: #4a4a8a; letter-spacing: 0.8px; font-weight: 200; text-align: center; transition: color 0.4s ease; } .phase-circle.active ~ .phase-name, .phase-circle.done ~ .phase-name { color: #b8b8ff; } /* Onde de propagation */ .phase-wave { position: absolute; top: 50%; left: 0; width: 100%; height: 2px; transform: translateY(-50%); pointer-events: none; z-index: 0; } .phase-wave.active::after { content: ''; position: absolute; left: var(--wave-start, 0); width: 60px; height: 2px; background: linear-gradient(90deg, rgba(124,124,221,0.6), transparent); animation: waveTravel 1s ease-out forwards; } @keyframes waveTravel { 0% { left: var(--wave-start, 0); opacity: 1; width: 60px; } 100% { left: var(--wave-end, 100%); opacity: 0; width: 10px; } } /* ===== ZONE DE STATUT ===== */ .status-area { width: 100%; max-width: 500px; min-height: 60px; padding: 12px 16px; background: rgba(16,16,26,0.3); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(74,74,138,0.15); border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: all 0.4s ease; } .status-area.has-wish { border-color: rgba(124,124,221,0.2); background: rgba(16,16,26,0.5); } .status-wish-text { font-size: 15px; font-weight: 300; color: #b8b8ff; text-align: center; line-height: 1.5; word-break: break-word; font-style: italic; letter-spacing: 0.3px; } .status-phase-indicator { font-size: 11px; color: #4a4a8a; letter-spacing: 1px; margin-top: 6px; font-weight: 200; text-transform: uppercase; } /* ===== COMPTEUR ===== */ .counter-container { position: fixed; bottom: 24px; left: 20px; z-index: 10; display: flex; flex-direction: column; align-items: flex-start; } .counter-label { font-size: 9px; letter-spacing: 1.5px; color: #4a4a8a; font-weight: 200; text-transform: uppercase; } .counter-number { font-size: 28px; font-weight: 200; color: #b8b8ff; letter-spacing: 2px; line-height: 1; position: relative; } .counter-particles { position: absolute; top: 50%; left: 50%; pointer-events: none; } .particle-burst { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: #b8b8ff; box-shadow: 0 0 4px rgba(184,184,255,0.6); animation: particleExplode 1.2s ease-out forwards; } @keyframes particleExplode { 0% { opacity: 1; transform: translate(0, 0) scale(1); } 100% { opacity: 0; transform: translate(var(--px), var(--py)) scale(0); } } /* ===== BOUTONS BAS DROITE ===== */ .bottom-actions { position: fixed; bottom: 24px; right: 20px; z-index: 10; display: flex; gap: 12px; align-items: center; } .icon-btn { background: rgba(16,16,26,0.5); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); border: 1px solid rgba(74,74,138,0.2); border-radius: 50%; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #4a4a8a; font-size: 18px; transition: all 0.3s ease; font-family: inherit; user-select: none; -webkit-tap-highlight-color: transparent; } .icon-btn:hover, .icon-btn:focus-visible { border-color: rgba(124,124,221,0.4); color: #b8b8ff; background: rgba(124,124,221,0.08); } .icon-btn:active { transform: scale(0.92); } .icon-btn.active { color: #7c7cdd; border-color: rgba(124,124,221,0.4); box-shadow: 0 0 12px rgba(124,124,221,0.1); } /* ===== PANNEAU D'HISTORIQUE ===== */ #genesis-history-panel { position: fixed; z-index: 20; background: rgba(16,16,26,0.88); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-left: 1px solid rgba(74,74,138,0.2); transition: transform 0.5s cubic-bezier(0.22, 0.16, 0.1, 1); display: flex; flex-direction: column; overflow: hidden; } /* Desktop: droite */ @media (min-width: 768px) { #genesis-history-panel { top: 0; right: 0; bottom: 0; width: 340px; max-width: 90vw; transform: translateX(100%); padding: 24px 20px; } #genesis-history-panel.open { transform: translateX(0); } } /* Mobile: bas */ @media (max-width: 767px) { #genesis-history-panel { left: 0; right: 0; bottom: 0; max-height: 70vh; border-left: none; border-top: 1px solid rgba(74,74,138,0.2); border-radius: 20px 20px 0 0; transform: translateY(100%); padding: 20px 16px 24px; } #genesis-history-panel.open { transform: translateY(0); } } .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-shrink: 0; } .panel-title { font-size: 14px; letter-spacing: 3px; text-transform: uppercase; color: #b8b8ff; font-weight: 300; } .panel-close-btn { background: none; border: 1px solid rgba(74,74,138,0.2); border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; color: #4a4a8a; cursor: pointer; font-size: 16px; transition: all 0.3s ease; font-family: inherit; } .panel-close-btn:hover { color: #b8b8ff; border-color: rgba(124,124,221,0.3); } .history-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; padding-right: 4px; } .history-list::-webkit-scrollbar { width: 2px; } .history-list::-webkit-scrollbar-track { background: transparent; } .history-list::-webkit-scrollbar-thumb { background: #4a4a8a; border-radius: 2px; } .history-empty { font-size: 13px; color: #4a4a8a; text-align: center; font-weight: 200; letter-spacing: 0.5px; padding: 20px 0; font-style: italic; } /* Carte souhaite */ .wish-card { background: rgba(26,26,46,0.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(74,74,138,0.15); border-radius: 12px; padding: 14px 16px; cursor: pointer; transition: all 0.3s ease; opacity: 0; transform: translateY(20px); animation: cardAppear 0.4s ease forwards; } .wish-card.appeared { opacity: 1; transform: translateY(0); } @keyframes cardAppear { to { opacity: 1; transform: translateY(0); } } .wish-card:hover { border-color: rgba(124,124,221,0.25); background: rgba(26,26,46,0.8); } .wish-card-text { font-size: 14px; font-weight: 300; color: #b8b8ff; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 10px; } .wish-card-footer { display: flex; justify-content: space-between; align-items: center; } .wish-card-date { font-size: 10px; color: #4a4a8a; letter-spacing: 0.5px; } .wish-card-status { font-size: 9px; padding: 3px 10px; border-radius: 10px; letter-spacing: 0.8px; text-transform: uppercase; font-weight: 400; } .wish-card-status.pending { background: rgba(124,124,221,0.12); color: #7c7cdd; border: 1px solid rgba(124,124,221,0.15); } .wish-card-status.working { background: rgba(124,124,221,0.15); color: #b8b8ff; border: 1px solid rgba(124,124,221,0.2); } .wish-card-status.done { background: rgba(46,204,113,0.12); color: #2ecc71; border: 1px solid rgba(46,204,113,0.15); } .wish-card-status.cancelled { background: rgba(255,107,107,0.12); color: #ff6b6b; border: 1px solid rgba(255,107,107,0.15); } /* ===== TOAST ===== */ .toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%) translateY(20px); background: rgba(16,16,26,0.85); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 1px solid rgba(124,124,221,0.2); border-radius: 12px; padding: 10px 20px; color: #b8b8ff; font-size: 13px; font-weight: 300; letter-spacing: 0.5px; opacity: 0; transition: opacity 0.4s ease, transform 0.4s ease; z-index: 30; pointer-events: none; white-space: nowrap; } .toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } /* ===== RESPONSIVE ===== */ @media (max-width: 480px) { body { padding: 12px; } #genesis-wish-input { font-size: 16px; padding: 16px 16px 16px 20px; min-height: 90px; } .phase-circle { width: 34px; height: 34px; } .counter-number { font-size: 24px; } #genesis-submit-btn { padding: 10px 24px; font-size: 13px; min-width: 160px; } .bottom-actions { bottom: 16px; right: 12px; gap: 8px; } .counter-container { bottom: 16px; left: 12px; } .icon-btn { width: 40px; height: 40px; font-size: 16px; } } @media (max-width: 380px) { .phase-chain::before { left: 8%; right: 8%; } .phase-circle { width: 28px; height: 28px; } .phase-name { font-size: 8px; } } /* ===== SKIP LINK ACCESSIBILITE ===== */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; } </style> </head> <body> <!-- Skip link accessibilite --> <a href="#genesis-wish-input" class="sr-only" tabindex="1">Aller au champ de saisie du souhait</a> <!-- Particules cosmiques --> <div class="cosmic-dust" aria-hidden="true" id="dustContainer"></div> <!-- Halo lumineux central --> <div class="central-halo" aria-hidden="true"></div> <!-- Toast pour notifications --> <div class="toast" id="toast" role="status" aria-live="polite"></div> <!-- Panneau d'historique --> <div id="genesis-history-panel" role="dialog" aria-label="Historique des souhaits"> <div class="panel-header"> <span class="panel-title">Historique</span> <button class="panel-close-btn" id="history-close-btn" aria-label="Fermer l'historique">✕</button> </div> <div class="history-list" id="historyList"> <div class="history-empty">Aucun souhait pour l'instant.<br>Formulez-en un.</div> </div> </div> <!-- Compteur --> <div class="counter-container" aria-label="Souhaits exaucés"> <span class="counter-label">Exaucés</span> <span class="counter-number" id="counterDisplay">0</span> <div class="counter-particles" id="counterParticles"></div> </div> <!-- Boutons bas droite --> <div class="bottom-actions"> <button class="icon-btn" id="genesis-ambience-toggle" aria-label="Activer l'ambiance sonore Genesis" title="Ambiance sonore"> <span id="ambienceIcon">🔇</span> </button> <button class="icon-btn" id="genesis-history-btn" aria-label="Ouvrir l'historique des souhaits" title="Historique"> <span id="historyIcon">📋</span> </button> </div> <!-- Contenu principal --> <main class="main-container" role="main"> <h1 class="genesis-title">Genesis</h1> <p class="genesis-subtitle">Formulez un souhait</p> <!-- Portail de souhait --> <div class="wish-portal glow-pulse" id="wishPortal"> <label for="genesis-wish-input" class="sr-only">Votre souhait</label> <textarea id="genesis-wish-input" placeholder="Écris ton souhait ici... Je réaliserai l'impossible." aria-describedby="wishDescription" rows="3" ></textarea> <span id="wishDescription" class="sr-only">Écrivez votre souhait, puis soumettez-le à Genesis en cliquant sur le bouton ou en appuyant sur Entrée.</span> </div> <!-- Bouton soumettre --> <div class="submit-area"> <button id="genesis-submit-btn" aria-label="Soumettre votre souhait à Genesis"> <span class="spinner"></span> <span class="btn-text">Soumettre à Genesis</span> </button> </div> <!-- Chaîne de phases --> <div class="phases-container" role="region" aria-label="Phases de réalisation du souhait"> <span class="phase-label">Réalisation</span> <div class="phase-chain" id="phaseChain"> <div class="phase-node"> <div class="phase-circle" id="phaseCircle1"> <div class="fill"></div> <span class="inner-icon">⊙</span> </div> <span class="phase-name">Pris en compte</span> </div> <div class="phase-node"> <div class="phase-circle" id="phaseCircle2"> <div class="fill"></div> <span class="inner-icon">◎</span> </div> <span class="phase-name">En travail</span> </div> <div class="phase-node"> <div class="phase-circle" id="phaseCircle3"> <div class="fill"></div> <span class="inner-icon">★</span> </div> <span class="phase-name">Réalisé</span> </div> <div class="phase-wave" id="phaseWave"></div> </div> </div> <!-- Zone de statut --> <div class="status-area" id="statusArea" role="status" aria-live="polite" aria-atomic="true"> <span class="status-wish-text" id="statusWishText">En attente de votre souhait...</span> <span class="status-phase-indicator" id="statusPhaseIndicator">—</span> </div> </main> <!-- Audio ambiant (cree dynamiquement) --> <audio id="ambienceAudio" preload="none" loop crossorigin="anonymous"></audio> <script> (function() { 'use strict'; // ===== CONFIGURATION ===== const CONFIG = { phaseDuration: 4000, // ms entre chaque phase loadingDuration: 1000, // ms pour le spinner bouton particleCount: 18, cardAppearDelay: 50, // ms entre chaque carte }; // ===== ETAT ===== const state = { wishes: [], // { id, text, status: 'pending'|'working'|'done'|'cancelled', date, phase: 0|1|2|3 } currentWishId: null, counter: 0, phaseInterval: null, isWorking: false, isAmbienceOn: false, isHistoryOpen: false, isFirstAmbience: true, audioContextReady: false, }; // ===== DOM REFS ===== const $ = (sel) => document.querySelector(sel); const input = $('#genesis-wish-input'); const submitBtn = $('#genesis-submit-btn'); const wishPortal = $('#wishPortal'); const statusArea = $('#statusArea'); const statusWishText = $('#statusWishText'); const statusPhaseIndicator = $('#statusPhaseIndicator'); const phaseChain = $('#phaseChain'); const phaseCircle1 = $('#phaseCircle1'); const phaseCircle2 = $('#phaseCircle2'); const phaseCircle3 = $('#phaseCircle3'); const phaseWave = $('#phaseWave'); const counterDisplay = $('#counterDisplay'); const counterParticles = $('#counterParticles'); const historyPanel = $('#genesis-history-panel'); const historyList = $('#historyList'); const historyBtn = $('#genesis-history-btn'); const historyIcon = $('#historyIcon'); const historyCloseBtn = $('#history-close-btn'); const ambienceBtn = $('#genesis-ambience-toggle'); const ambienceIcon = $('#ambienceIcon'); const ambienceAudio = $('#ambienceAudio'); const toast = $('#toast'); // ===== CREATION POUSSIERE COSMIQUE ===== function createDustParticles() { const container = document.getElementById('dustContainer'); for (let i = 0; i < 10; i++) { const p = document.createElement('div'); p.className = 'dust-particle'; const x = Math.random() * 100; const y = Math.random() * 100; const dur = 18 + Math.random() * 20; const delay = Math.random() * 20; const dx1 = (Math.random() - 0.5) * 60; const dy1 = (Math.random() - 0.5) * 60; const dx2 = (Math.random() - 0.5) * 60; const dy2 = (Math.random() - 0.5) * 60; p.style.setProperty('--x', x + '%'); p.style.setProperty('--y', y + '%'); p.style.setProperty('--dur', dur + 's'); p.style.setProperty('--delay', delay + 's'); p.style.setProperty('--dx1', dx1 + 'px'); p.style.setProperty('--dy1', dy1 + 'px'); p.style.setProperty('--dx2', dx2 + 'px'); p.style.setProperty('--dy2', dy2 + 'px'); container.appendChild(p); } } createDustParticles(); // ===== UTILITAIRES ===== function getTimeStr() { const d = new Date(); const h = String(d.getHours()).padStart(2, '0'); const m = String(d.getMinutes()).padStart(2, '0'); return h + ':' + m; } function getDateStr() { const d = new Date(); return d.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' }); } function truncateText(text, maxLen = 50) { if (text.length <= maxLen) return text; return text.substring(0, maxLen) + '…'; } function generateId() { return 'wish_' + Date.now() + '_' + Math.random().toString(36).substring(2, 6); } // ===== TOAST ===== let toastTimer = null; function showToast(message, duration = 2500) { toast.textContent = message; toast.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => { toast.classList.remove('show'); }, duration); } // ===== PARTICULES D'INCREMENTATION ===== function spawnCounterParticles() { const colors = ['#b8b8ff', '#7c7cdd', '#e0e0ff']; for (let i = 0; i < CONFIG.particleCount; i++) { const p = document.createElement('div'); p.className = 'particle-burst'; const angle = Math.random() * Math.PI * 2; const dist = 20 + Math.random() * 70; const px = Math.cos(angle) * dist; const py = Math.sin(angle) * dist - 20; p.style.setProperty('--px', px + 'px'); p.style.setProperty('--py', py + 'px'); p.style.background = colors[Math.floor(Math.random() * colors.length)]; p.style.width = (2 + Math.random() * 3) + 'px'; p.style.height = p.style.width; const dur = 0.8 + Math.random() * 0.6; p.style.animationDuration = dur + 's'; counterParticles.appendChild(p); setTimeout(() => p.remove(), dur * 1000 + 100); } } // ===== MISE A JOUR COMPTEUR ===== function updateCounter(value) { counterDisplay.textContent = value; } // ===== GESTION DES PHASES ===== function resetPhases() { phaseCircle1.classList.remove('active', 'done'); phaseCircle2.classList.remove('active', 'done'); phaseCircle3.classList.remove('active', 'done'); phaseChain.classList.remove('active-line'); phaseWave.classList.remove('active'); // Reset fills document.querySelectorAll('.phase-circle .fill').forEach(el => { el.style.clipPath = 'circle(0% at 50% 50%)'; }); } function setPhase(phaseIndex) { // phaseIndex: 0 = rien, 1 = pris en compte, 2 = en travail, 3 = réalisé resetPhases(); if (phaseIndex >= 1) { phaseCircle1.classList.add('active'); phaseChain.classList.add('active-line'); // Wave phaseWave.style.setProperty('--wave-start', '0%'); phaseWave.style.setProperty('--wave-end', '30%'); phaseWave.classList.remove('active'); void phaseWave.offsetWidth; phaseWave.classList.add('active'); playSound('ethereal chime'); } if (phaseIndex >= 2) { setTimeout(() => { phaseCircle1.classList.remove('active'); phaseCircle1.classList.add('done'); phaseCircle2.classList.add('active'); phaseWave.style.setProperty('--wave-start', '30%'); phaseWave.style.setProperty('--wave-end', '65%'); phaseWave.classList.remove('active'); void phaseWave.offsetWidth; phaseWave.classList.add('active'); playSound('ethereal chime'); }, CONFIG.phaseDuration); } if (phaseIndex >= 3) { setTimeout(() => { phaseCircle2.classList.remove('active'); phaseCircle2.classList.add('done'); phaseCircle3.classList.add('active'); phaseWave.style.setProperty('--wave-start', '65%'); phaseWave.style.setProperty('--wave-end', '100%'); phaseWave.classList.remove('active'); void phaseWave.offsetWidth; phaseWave.classList.add('active'); playSound('ethereal chime'); // Apres un delai, marque comme done setTimeout(() => { phaseCircle3.classList.remove('active'); phaseCircle3.classList.add('done'); // Incrementation compteur + particules state.counter++; updateCounter(state.counter); spawnCounterParticles(); // Mise a jour du statut const wish = state.wishes.find(w => w.id === state.currentWishId); if (wish) { wish.status = 'done'; wish.phase = 3; statusPhaseIndicator.textContent = '✓ Réalisé'; } renderHistory(); state.isWorking = false; wishPortal.classList.remove('working'); wishPortal.classList.add('glow-pulse'); }, 1500); }, CONFIG.phaseDuration * 2); } } // ===== SOUMISSION D'UN SOUHAIT ===== function submitWish() { const text = input.value.trim(); if (!text) { showToast('Écrivez un souhait...'); return; } if (state.isWorking) { showToast('Genesis est déjà à l\'œuvre...'); return; } // Reset resetPhases(); clearTimeout(state.phaseInterval); const id = generateId = generateId(); state.currentWishId = id; state.isWorking = true; const wish = { id, text, status: 'pending', phase: 0, date: getDateStr() + ' ' + getTimeStr(), }; state.wishes.unshift(wish); renderHistory(); // UI wishPortal.classList.remove('glow-pulse'); wishPortal.classList.add('working'); statusArea.classList.add('has-wish'); statusWishText.textContent = '« ' + text + ' »'; statusPhaseIndicator.textContent = 'Pris en compte'; // Loading bouton submitBtn.classList.add('loading'); // Phase 1 immediate setTimeout(() => { submitBtn.classList.remove('loading'); wish.phase = 1; wish.status = 'pending'; setPhase(1); statusPhaseIndicator.textContent = '⊙ Pris en compte'; renderHistory(); // Phase 2 apres delai state.phaseInterval = setTimeout(() => { wish.phase = 2; wish.status = 'working'; statusPhaseIndicator.textContent = '◎ En travail'; setPhase(2); renderHistory(); // Phase 3 apres delai state.phaseInterval = setTimeout(() => { setPhase(3); }, CONFIG.phaseDuration); }, CONFIG.phaseDuration); }, CONFIG.loadingDuration); // Nettoyage input input.value = ''; input.focus(); } // ===== RENDU HISTORIQUE ===== function renderHistory() { if (state.wishes.length === 0) { historyList.innerHTML = '<div class="history-empty">Aucun souhait pour l\'instant.<br>Formulez-en un.</div>'; return; } let html = ''; state.wishes.forEach((w, idx) => { const statusLabel = w.status === 'pending' ? 'En attente' : w.status === 'working' ? 'En travail' : w.status === 'done' ? 'Réalisé' : 'Annulé'; const statusClass = w.status === 'pending' ? 'pending' : w.status === 'working' ? 'working' : w.status === 'done' ? 'done' : 'cancelled'; const delay = idx * CONFIG.cardAppearDelay; html += ` <div class="wish-card" data-id="${w.id}" style="animation-delay:${delay}ms" tabindex="0" role="button" aria-label="Souhait: ${w.text}"> <div class="wish-card-text">${truncateText(w.text, 45)}</div> <div class="wish-card-footer"> <span class="wish-card-date">${w.date}</span> <span class="wish-card-status ${statusClass}">${statusLabel}</span> </div> </div> `; }); historyList.innerHTML = html; // Clic sur une carte -> affiche details dans le statut document.querySelectorAll('.wish-card').forEach(card => { card.addEventListener('click', function(e) { const id = this.dataset.id; const wish = state.wishes.find(w => w.id === id); if (wish) { statusWishText.textContent = '« ' + wish.text + ' »'; const statusLabel = wish.status === 'pending' ? '⊙ Pris en compte' : wish.status === 'working' ? '◎ En travail' : wish.status === 'done' ? '✓ Réalisé' : '✕ Annulé'; statusPhaseIndicator.textContent = statusLabel; } }); card.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.click(); } }); }); } // ===== GESTION AUDIO ===== function playSound(theme) { // Joue un son court via l'API Freesound (utilise l'endpoint) const audio = new Audio(); audio.src = 'https://genesis-runtime.onrender.com/api/son?theme=' + encodeURIComponent(theme); audio.volume = 0.3; audio.play().catch(() => {}); } function toggleAmbience() { state.isAmbienceOn = !state.isAmbienceOn; if (state.isAmbienceOn) { ambienceAudio.src = 'https://genesis-runtime.onrender.com/api/son?theme=deep%20space%20hum'; ambienceAudio.volume = 0.15; ambienceAudio.loop = true; ambienceAudio.play().then(() => { ambienceIcon.textContent = '🔊'; ambienceBtn.classList.add('active'); ambienceBtn.setAttribute('aria-label', 'Désactiver l\'ambiance sonore'); if (state.isFirstAmbience) { state.isFirstAmbience = false; showToast('Ambiance Genesis activée', 2000); } }).catch(() => { state.isAmbienceOn = false; showToast('Son non disponible'); }); } else { ambienceAudio.pause(); ambienceAudio.currentTime = 0; ambienceIcon.textContent = '🔇'; ambienceBtn.classList.remove('active'); ambienceBtn.setAttribute('aria-label', 'Activer l\'ambiance sonore'); } } // ===== PANNEAU HISTORIQUE ===== function toggleHistory() { state.isHistoryOpen = !state.isHistoryOpen; if (state.isHistoryOpen) { historyPanel.classList.add('open'); historyIcon.textContent = '✕'; historyBtn.classList.add('active'); historyBtn.setAttribute('aria-label', 'Fermer l\'historique'); renderHistory(); // re-rend pour animations } else { historyPanel.classList.remove('open'); historyIcon.textContent = '📋'; historyBtn.classList.remove('active'); historyBtn.setAttribute('aria-label', 'Ouvrir l\'historique'); } } // ===== EVENEMENTS ===== // Soumission submitBtn.addEventListener('click', submitWish); input.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submitWish(); } }); // Ambiance ambienceBtn.addEventListener('click', toggleAmbience); // Historique historyBtn.addEventListener('click', toggleHistory); historyCloseBtn.addEventListener('click, addEventListener('click', toggleHistory); // Fermer historique en cliquant en dehors document.addEventListener('click', function(e) { if (state.isHistoryOpen) { const panel = historyPanel; const btn = historyBtn; if (!panel.contains(e.target) && !btn.contains(e.target)) { toggleHistory(); } } }); // Touche Echap document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && state.isHistoryOpen) { toggleHistory(); } }); // ===== INIT ===== resetPhases(); updateCounter(0); renderHistory(); // Focus input au chargement setTimeout(() => input.focus(), 300); console.log('Genesis interface loaded. Faites un vœu.'); })(); </script> <script id="genesis-interactions"> document.getElementById('genesis-submit-btn').addEventListener('click', function(e) { e.preventDefault(); var input = document.getElementById('genesis-wish-input'); var statusZone = document.getElementById('genesis-status-zone'); if (!input || !statusZone) return; var wish = input.value.trim(); if (!wish) return; statusZone.textContent = 'Souhait : ' + wish; var phaseIndicator = document.getElementById('genesis-phase-indicator'); if (phaseIndicator) { phaseIndicator.textContent = 'Pris en compte'; var circle = phaseIndicator.querySelector('.circle') || phaseIndicator; circle.style.fill = 'currentColor'; circle.style.opacity = '1'; } var btn = e.currentTarget; btn.classList.add('loading'); var spinner = document.createElement('span'); spinner.className = 'spinner'; spinner.style.cssText = 'display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.6s linear infinite;margin-left:8px;vertical-align:middle;'; btn.appendChild(spinner); if (input) input.style.borderColor = '#fff'; if (input) input.style.boxShadow = '0 0 15px rgba(255,255,255,0.8)'; setTimeout(function() { btn.classList.remove('loading'); var sp = btn.querySelector('.spinner'); if (sp) sp.remove(); if (input) input.style.borderColor = ''; if (input) input.style.boxShadow = ''; }, 1000); new Audio('https://genesis-runtime.onrender.com/api/son?theme=ethereal%20chime').play(); }); document.getElementById('genesis-wish-input').addEventListener('keydown', function(e) { if (e.key === 'Enter') { var btn = document.getElementById('genesis-submit-btn'); if (btn) btn.click(); } }); document.getElementById('genesis-ambience-toggle').addEventListener('click', function(e) { var btn = e.currentTarget; var isActive = btn.dataset.active !== 'true'; btn.dataset.active = isActive ? 'true' : 'false'; if (isActive) { btn.style.opacity = '1'; btn.style.filter = 'brightness(1.5)'; var audio = new Audio('https://genesis-runtime.onrender.com/api/son?theme=deep%20space%20hum'); audio.loop = true; audio.dataset.ambience = 'true'; audio.play().catch(function(){}); window.__genesisAmbience = audio; if (!btn.dataset.toasted) { btn.dataset.toasted = 'true'; var toast = document.createElement('div'); toast.textContent = 'Ambiance Genesis activée'; toast.style.cssText = 'position:fixed;bottom:80px;right:20px;background:rgba(0,0,0,0.8);color:#fff;padding:8px 16px;border-radius:8px;font-size:14px;z-index:9999;opacity:0;transition:opacity 0.3s;pointer-events:none;'; document.body.appendChild(toast); setTimeout(function() { toast.style.opacity = '1'; }, 50); setTimeout(function() { toast.style.opacity = '0'; setTimeout(function() { toast.remove(); }, 400); }, 2500); } } else { btn.style.opacity = '0.5'; btn.style.filter = 'brightness(0.7)'; if (window.__genesisAmbience) { window.__genesisAmbience.pause(); window.__genesisAmbience = null; } } }); document.getElementById('genesis-history-btn').addEventListener('click', function(e) { var btn = e.currentTarget; var panel = document.getElementById('genesis-history-panel'); if (!panel) return; var isOpen = panel.dataset.open === 'true'; if (!isOpen) { panel.dataset.open = 'true'; btn.innerHTML = '✕'; panel.style.display = 'block'; var isMobile = window.innerWidth <= 768; panel.style.cssText = isMobile ? 'position:fixed;bottom:0;left:0;right:0;height:50vh;background:rgba(0,0,0,0.95);z-index:9998;transform:translateY(100%);transition:transform 0.4s cubic-bezier(0.22,1,0.36,1);' : 'position:fixed;top:0;right:0;width:380px;height:100vh;background:rgba(0,0,0,0.95);z-index:9998;transform:translateX(100%);transition:transform 0.4s cubic-bezier(0.22,1,0.36,1);'; requestAnimationFrame(function() { panel.style.transform = isMobile ? 'translateY(0)' : 'translateX(0)'; }); var cards = panel.querySelectorAll('.wish-card'); cards.forEach(function(card, i) { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; setTimeout(function() { card.style.transition = 'opacity 0.4s ease, transform 0.4s ease'; card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, 100 + i * 120); }); } else { panel.dataset.open = 'false'; btn.innerHTML = '☰'; var isMobile = window.innerWidth <= 768; panel.style.transform = isMobile ? 'translateY(100%)' : 'translateX(100%)'; setTimeout(function() { panel.style.display = 'none'; }, 400); } new Audio('https://genesis-runtime.onrender.com/api/son?theme=soft%20interface%20slide').play(); }); </script> </body> </html>