Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.

ALPAC+ #949

@levidounat-spec

Description

@levidounat-spec

from pathlib import Path
import base64, json, os, sys

img_path = Path("/mnt/data/d963e04e-f5cd-4e67-a88e-4818770e2656.png")
if img_path.exists():
b = img_path.read_bytes()
data_uri = "data:image/png;base64," + base64.b64encode(b).decode('ascii')
else:
data_uri = ""

html_template = """

<title>ALPAC+ – Programme de Fidélité</title> <style> :root{--grad:linear-gradient(90deg,#ff004c,#ff7a00,#ffe600,#00c94d,#0084ff,#7a00ff);--card:#1a1a1a;--bg:#000} *{box-sizing:border-box} body{margin:0;font-family:Inter,Arial,Helvetica,sans-serif;background:var(--bg);color:#fff} .header{display:flex;align-items:center;gap:16px;justify-content:center;padding:20px;border-bottom:6px solid transparent;border-image:var(--grad) 1} .logo{height:64px;width:64px;border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,0.6)} h1{margin:0;font-size:20px} nav{background:#111;padding:12px;display:flex;justify-content:center;gap:24px;border-bottom:4px solid transparent;border-image:var(--grad) 1} nav a{color:#fff;text-decoration:none;font-weight:700} .container{max-width:1100px;margin:28px auto;padding:0 20px} .hero{background:#111;padding:32px;text-align:center;border-top:4px solid transparent;border-image:var(--grad) 1} .points-box{display:inline-block;background:#000;padding:14px;border-radius:10px;border:3px solid transparent;border-image:var(--grad) 1;font-weight:700} .progress-wrap{max-width:700px;margin:18px auto} .progress-bar-outer{background:#222;border-radius:12px;overflow:hidden;border:2px solid #fff;height:30px} .progress-bar-inner{height:100%;width:0;background:var(--grad);color:#000;display:flex;align-items:center;justify-content:center;font-weight:700} .section{padding:28px 0} .card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px} .card{background:var(--card);padding:16px;border-radius:12px;box-shadow:0 4px 16px rgba(0,0,0,0.4)} button.primary{background:var(--grad);border:none;color:#fff;padding:10px 14px;border-radius:10px;cursor:pointer;font-weight:700} .small{padding:8px 10px;font-size:14px;border-radius:8px} .profile-card{display:flex;flex-direction:column;align-items:center;gap:8px} #profilePhoto{width:120px;height:120px;border-radius:50%;object-fit:cover;border:4px solid #ff004c} .footer{text-align:center;padding:24px;margin-top:24px;border-top:1px solid rgba(255,255,255,0.03)} .modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.75);align-items:center;justify-content:center;z-index:9999} .modal .box{background:#111;padding:18px;border-radius:12px;border:3px solid #ff004c;max-width:420px;width:90%} .input{width:100%;padding:10px;border-radius:8px;margin-top:10px;border:none;background:#222;color:#fff} .mission-proof{display:flex;gap:8px;align-items:center} .history-list{max-height:160px;overflow:auto;margin-top:10px;padding:8px;background:#0f0f0f;border-radius:8px} .notice{background:#081b0f;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.03);margin-top:10px} </style>

ALPAC+

ALPAC+ – Programme de Fidélité

Gagne des points et débloque des récompenses
Tableau de bord Récompenses Missions Mon compte

Bienvenue 🎉

Points : 0 pts
0%
Niveau : Bronze

🎁 Récompenses

10% réduction

300 pts

Accès VIP 24h

500 pts

Cadeau exclusif

750 pts

🔥 Missions

<div style="margin-top:16px">
  <h4>Historique des missions validées</h4>
  <div id="history" class="history-list">Aucune mission validée pour l'instant.</div>
</div>

👤 Mon compte

Photo de profil
Non connecté
-
Se déconnecter S'inscrire / Se connecter
© 2025 ALPAC+ – Tous droits réservés

Créer un compte / Se connecter

S'inscrire Se connecter
Fermer

Valider mission

Envoyer preuve Annuler
Envoie une photo ou ticket prouvant la mission.
<script> const API = (function(){ function delay(ms){return new Promise(r=>setTimeout(r,ms))} return { register: async (data) => { await delay(400); localStorage.setItem('alpac_user', JSON.stringify({name:data.name,email:data.email,points:0})); return {ok:true} }, login: async (data) => { await delay(300); const u = JSON.parse(localStorage.getItem('alpac_user')||'null'); if(u && u.email===data.email) return {ok:true,user:u}; return {ok:false,error:'Utilisateur non trouvé'} }, getUser: async () => { await delay(120); return JSON.parse(localStorage.getItem('alpac_user')||'null') }, addPoints: async (pts) => { await delay(250); const u = JSON.parse(localStorage.getItem('alpac_user')||'null')||{points:0}; u.points = (u.points||0)+pts; localStorage.setItem('alpac_user', JSON.stringify(u)); return {points:u.points} }, setPhoto: async (dataUrl) => { await delay(120); const u = JSON.parse(localStorage.getItem('alpac_user')||'null')||{}; u.photo = dataUrl; localStorage.setItem('alpac_user', JSON.stringify(u)); return {ok:true} }, getMissions: async () => { await delay(100); return [ {key:'achat_mois',title:'Achat du mois',points:20}, {key:'parrainage',title:'Parrainage',points:50}, {key:'challenge_5j',title:'Connexion 5 jours',points:15} ]}, validateMission: async (missionKey, proofDataUrl) => { await delay(500); if(!proofDataUrl || proofDataUrl.length<1000) return {ok:false,error:'Preuve invalide'}; const history = JSON.parse(localStorage.getItem('alpac_history')||'[]'); if(history.find(h=>h.missionKey===missionKey)) return {ok:false,error:'Mission déjà validée'}; const missions = await API.getMissions(); const m = missions.find(x=>x.key===missionKey); if(!m) return {ok:false,error:'Mission introuvable'}; history.push({missionKey, title:m.title, points:m.points, date:new Date().toISOString()}); localStorage.setItem('alpac_history', JSON.stringify(history)); const res = await API.addPoints(m.points); return {ok:true,points:res.points,added:m.points}; }, getHistory: async ()=>{ await delay(80); return JSON.parse(localStorage.getItem('alpac_history')||'[]') } } })(); const NEXT_REWARD = 300; let currentMissionKey = null; async function init(){ document.getElementById('btnOpenRegister').addEventListener('click', ()=>openModal('modalRegister')); document.getElementById('uploadPhoto').addEventListener('change', handleProfileUpload); document.getElementById('btnLogout').addEventListener('click', logout); await renderMissions(); await refreshProfile(); } function openModal(id){ document.getElementById(id).style.display='flex'; document.getElementById(id).setAttribute('aria-hidden','false') } function closeModal(id){ document.getElementById(id).style.display='none'; document.getElementById(id).setAttribute('aria-hidden','true') } async function doRegister(){ const name = document.getElementById('regName').value.trim(); const email = document.getElementById('regEmail').value.trim(); if(!name || !email) return alert('Remplis nom et email'); await API.register({name,email}); alert('Inscription OK — connecté'); closeModal('modalRegister'); await refreshProfile(); } async function doLogin(){ const email = document.getElementById('regEmail').value.trim(); if(!email) return alert('Donne ton email'); const r = await API.login({email}); if(!r.ok) return alert(r.error||'Erreur'); alert('Connecté'); closeModal('modalRegister'); await refreshProfile(); } async function refreshProfile(){ const u = await API.getUser(); if(u){ document.getElementById('profileName').textContent = u.name || '—'; document.getElementById('profileEmail').textContent = u.email || '-'; document.getElementById('btnLogout').style.display = 'inline-block'; if(u.photo) document.getElementById('profilePhoto').src = u.photo; document.getElementById('pointsDisplay').textContent = (u.points||0); updateProgress(u.points||0); renderHistory(); } else { document.getElementById('profileName').textContent = 'Non connecté'; document.getElementById('profileEmail').textContent = '-'; document.getElementById('btnLogout').style.display = 'none'; document.getElementById('profilePhoto').src = ''; document.getElementById('pointsDisplay').textContent = '0'; updateProgress(0); } } async function renderMissions(){ const grid = document.getElementById('missionsGrid'); grid.innerHTML = ''; const missions = await API.getMissions(); missions.forEach(m=>{ const div = document.createElement('div'); div.className = 'card'; div.innerHTML = `

${m.title}

+${m.points} pts

Valider
`; grid.appendChild(div); }); } function openMission(key,title,points){ currentMissionKey = key; document.getElementById('missionTitle').textContent = `Valider : ${title} (+${points} pts)`; openModal('modalMission'); } async function submitMission(){ const fi = document.getElementById('missionProof'); if(!fi.files.length) return alert('Ajoute une photo comme preuve'); const file = fi.files[0]; const reader = new FileReader(); reader.onload = async function(e){ const dataUrl = e.target.result; const res = await API.validateMission(currentMissionKey,dataUrl); if(!res.ok){ alert(res.error || 'Preuve non acceptée'); return; } alert(`Mission validée ! +${res.added} pts`); closeModal('modalMission'); document.getElementById('missionProof').value = ''; await refreshProfile(); await renderMissions(); }; reader.readAsDataURL(file); } async function renderHistory(){ const history = await API.getHistory(); const el = document.getElementById('history'); if(!history.length){ el.innerHTML = "Aucune mission validée pour l'instant."; return; } el.innerHTML = history.map(h=>`
${h.title} — +${h.points} pts
${new Date(h.date).toLocaleString()}
`).join(''); } function handleProfileUpload(e){ const f = e.target.files[0]; if(!f) return; const r = new FileReader(); r.onload = async function(ev){ const url = ev.target.result; document.getElementById('profilePhoto').src = url; await API.setPhoto(url); alert('Photo de profil mise à jour'); }; r.readAsDataURL(f); } function logout(){ localStorage.removeItem('alpac_user'); localStorage.removeItem('alpac_history'); alert('Déconnecté'); refreshProfile(); } function updateProgress(points){ const p = points||0; const percent = Math.min(Math.round((p / NEXT_REWARD) * 100),100); const bar = document.getElementById('progressBar'); bar.style.width = percent + '%'; bar.textContent = percent + '%'; const levelLabel = document.getElementById('levelLabel'); if(p >= 1000) levelLabel.textContent = 'Niveau : Platine'; else if(p >= 500) levelLabel.textContent = 'Niveau : Or'; else if(p >= 300) levelLabel.textContent = 'Niveau : Argent'; else levelLabel.textContent = 'Niveau : Bronze'; } init(); </script> """

html_content = html_template.replace("{DATA_URI}", data_uri)

out_path = "/mnt/data/alpac-plus.html"
with open(out_path, "w", encoding="utf-8") as f:
f.write(html_content)

out_path

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions