CChopTonSiteLE STUDIO WEB VISUEL
Mon site · Aperçu en direct
`} $('#zipBtn').onclick=async()=>{try{if(!window.JSZip){toast('Bibliothèque ZIP indisponible : vérifie ta connexion internet');return}snapshotActivePage();let zip=new JSZip();const home=sitePages.find(p=>p.slug==='index')||sitePages[0];zip.file('index.html',finalHtml(home.blocks||blocks,home.design||design,false));sitePages.filter(p=>p!==home).forEach(p=>zip.file('pages/'+p.slug+'.html',finalHtml(p.blocks||blocks,p.design||design,true)));zip.file('style.css',finalCss());zip.file('script.js',finalJs());zip.file('README.txt','Site créé avec ChopTonSite. Accueil : index.html. Pages supplémentaires : dossier pages/. Dans les liens de boutons, utilise pages/nom-de-page.html. Les liens d’achat ouvrent les URL de paiement configurées par article. Le panier intégré reste une démonstration : aucun paiement réel ni commande n’est traité par ce site seul. Pour encaisser, configure un lien externe chez Stripe, PayPal, Tebex ou un autre prestataire.');let blob=await zip.generateAsync({type:'blob'});let a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='ChopTonSite-site.zip';a.click();URL.revokeObjectURL(a.href);toast('ZIP généré')}catch(e){toast('Impossible de créer le ZIP : '+e.message)}}; $('#publishBtn').onclick=()=>{let json=JSON.stringify({blocks,design});let encoded=btoa(unescape(encodeURIComponent(json)));let link=location.href.split('#')[0]+'#site='+encoded;openModal('Générer un lien ou exporter',`

Le lien ci-dessous contient le contenu du site encodé. Il fonctionne si la personne ouvre cette même application ChopTonSite ; pour héberger un site public autonome, télécharge plutôt le ZIP et publie ses fichiers sur un hébergeur.

Le lien peut devenir très long si ton site contient beaucoup d’images ou de texte.

`);$('#copyLink').onclick=()=>{copyText($('#shareLink').value)};$('#showCode').onclick=()=>{$('#codeWrap').innerHTML=`
`;$('#htmlCode').value=finalHtml();$('#copyHtml').onclick=()=>copyText($('#htmlCode').value)}}; $('#uploadSiteBtn').onclick=()=>{openModal('Uploader / publier mon site',`

Prépare les fichiers de ton site puis envoie-les sur Cloudflare Pages pour obtenir une adresse publique.

  1. Clique sur Télécharger le ZIP du site ci-dessous.
  2. Ouvre Cloudflare Pages et crée un projet en choisissant l’option de téléversement des fichiers (Direct Upload).
  3. Dépose le fichier ZIP ou les fichiers extraits selon ce que l’interface Cloudflare demande, puis lance le déploiement.
À savoir : ce bouton prépare et télécharge le site, puis ouvre Cloudflare. Il ne peut pas publier automatiquement à ta place sans connexion et autorisation Cloudflare configurées.
`);$('#uploadDownloadZip').onclick=()=>$('#zipBtn').click();$('#openCloudflarePages').onclick=()=>window.open('https://dash.cloudflare.com/','_blank','noopener,noreferrer')}; async function copyText(s){try{await navigator.clipboard.writeText(s);toast('Copié dans le presse-papiers')}catch(e){toast('Sélectionne le texte puis copie-le manuellement')}} function loadHash(){let m=location.hash.match(/site=([^&]+)/);if(!m)return;try{let data=JSON.parse(decodeURIComponent(escape(atob(m[1]))));if(data.blocks&&data.design){blocks=data.blocks;design=data.design;selected=blocks[0]?.id;$('#accentColor').value=design.accent;$('#inkColor').value=design.ink;$('#bgColor').value=design.bg;$('#fontFamily').value=design.font;$('#siteWidth').value=design.width;render();toast('Site partagé chargé')}}catch(e){toast('Le lien de partage est invalide ou incomplet')}} // Comptes ChopTonSite V2 : l'authentification et les projets nécessitent les Pages Functions + D1. let ctsUser=null, currentProjectId=null, currentProjectName='Mon site'; const api=async(path,options={})=>{const res=await fetch(path,{credentials:'same-origin',headers:{'content-type':'application/json',...(options.headers||{})},...options});const type=res.headers.get('content-type')||'';if(!type.toLowerCase().includes('application/json')){const isApi=String(path).startsWith('/api/');const detail=isApi?`La route ${path} renvoie une page HTML au lieu du JSON attendu (HTTP ${res.status}). Les Pages Functions ne sont probablement pas déployées, ou cette route n'est pas reconnue. Le point d’entrée _worker.js doit être présent à la racine du déploiement.`:`Réponse inattendue (HTTP ${res.status}).`;throw new Error(`${detail} Vérifie ensuite https://chop-ton-site.pages.dev/api/health. Pour corriger : redéploie l’intégralité du ZIP corrigé à la racine du projet Pages, en conservant _worker.js et functions/.`)}let data;try{data=await res.json()}catch{throw new Error(`La réponse de ${path} n'est pas un JSON valide (HTTP ${res.status}). Vérifie les Pages Functions.`)}if(!res.ok||data.ok===false)throw new Error(data.error?.message||data.message||`Erreur HTTP ${res.status}`);return data}; function accountButtonLabel(){const b=$('#accountBtn');if(b)b.textContent=ctsUser?`♙ ${ctsUser.username}`:'♙ Compte'} function authModal(message=''){openModal('Compte ChopTonSite',`

Crée un compte pour sauvegarder tes sites en ligne et les retrouver depuis tes autres appareils.

Les comptes et projets sont stockés dans Cloudflare D1. Si l’API indique une configuration manquante, consulte le README inclus dans le ZIP.
${esc(message)}

Vérification de l’API…

`);let register=false;const setMode=r=>{register=r;$('#registerFields').classList.toggle('hidden',!r);$('#authLogin').parentElement.classList.toggle('hidden',r);$('#authPassword').setAttribute('autocomplete',r?'new-password':'current-password');$('#authPassword').setAttribute('minlength',r?'12':'1');$('#authSubmit').textContent=r?'Créer mon compte':'Se connecter';$('#showLogin').classList.toggle('primary',!r);$('#showRegister').classList.toggle('primary',r)};$('#showLogin').onclick=()=>setMode(false);$('#showRegister').onclick=()=>setMode(true);$('#authForm').onsubmit=async e=>{e.preventDefault();const submit=$('#authSubmit');submit.disabled=true;try{let result;if(register){result=await api('/api/auth/register',{method:'POST',body:JSON.stringify({username:$('#authUsername').value,email:$('#authEmail').value,password:$('#authPassword').value})})}else{result=await api('/api/auth/login',{method:'POST',body:JSON.stringify({login:$('#authLogin').value,password:$('#authPassword').value})})}ctsUser=result.user;accountButtonLabel();closeModal();toast(register?'Compte créé et connecté':'Connexion réussie');if(register)openDashboard();}catch(err){$('#authMessage').textContent=err.message;$('#authMessage').style.color='#ff9f9f'}finally{submit.disabled=false}};api('/api/health').then(d=>{$('#apiStatus').textContent=d.database?'API et base D1 opérationnelles':'API disponible, mais base D1 à configurer';}).catch(e=>{$('#apiStatus').textContent=e.message;});setMode(false)} async function refreshSession(){try{const d=await api('/api/me');ctsUser=d.user||null}catch(e){ctsUser=null;console.warn('Session/API ChopTonSite:',e.message)}accountButtonLabel()} function accountModal(){if(!ctsUser){authModal();return}openModal('Mon compte',`

Connecté en tant que ${esc(ctsUser.username)}

${esc(ctsUser.email)} · Rôle : ${esc(ctsUser.role)}


Changer le mot de passe

Création de compte : ${new Date(ctsUser.created_at).toLocaleDateString('fr-FR')}

${ctsUser.role==='admin'?'

':''}`);$('#saveProfile').onclick=async()=>{try{const d=await api('/api/me',{method:'PATCH',body:JSON.stringify({username:$('#profileUsername').value})});ctsUser.username=d.username;accountButtonLabel();accountModal();toast('Profil enregistré')}catch(e){toast(e.message)}};$('#changePassword').onclick=async()=>{try{await api('/api/auth/password',{method:'POST',body:JSON.stringify({currentPassword:$('#currentPassword').value,newPassword:$('#newPassword').value})});toast('Mot de passe modifié');accountModal()}catch(e){toast(e.message)}};$('#logoutBtn').onclick=async()=>{try{await api('/api/auth/logout',{method:'POST',body:'{}'})}catch(e){}ctsUser=null;currentProjectId=null;accountButtonLabel();closeModal();toast('Déconnexion effectuée')};if($('#adminBtn'))$('#adminBtn').onclick=adminModal} async function openDashboard(){if(!ctsUser){authModal('Connecte-toi pour accéder à tes projets.');return}openModal('Mes sites web',`

Tes projets enregistrés dans ton compte. Les autres utilisateurs ne peuvent pas les consulter ni les modifier.

Chargement des projets…

`);$('#newProjectBtn').onclick=()=>{blocks=defaultBlocks();design={accent:'#6558f5',ink:'#172033',bg:'#ffffff',theme:'light',font:'Inter,system-ui,sans-serif',width:'1200',themeStyle:'modern',musicEnabled:false,musicTracks:[],musicVolume:35,musicTitle:'Ambiance du site'};selected=blocks[1]?.id;currentProjectId=null;currentProjectName='Mon site';closeModal();render();toast('Nouveau site prêt. Enregistre-le dans ton compte.')};$('#refreshProjectsBtn').onclick=loadProjectList;await loadProjectList()} async function loadProjectList(){const list=$('#projectsList');if(!list)return;list.innerHTML='

Chargement…

';try{const d=await api('/api/projects');if(!d.projects.length){list.innerHTML='

Tu n’as pas encore de site enregistré. Crée ton premier site puis clique sur « Enregistrer ».

';return}list.innerHTML=d.projects.map(p=>`
${esc(p.name)}Modifié le ${new Date(p.updated_at).toLocaleString('fr-FR')}
`).join('');list.querySelectorAll('[data-load-project]').forEach(b=>b.onclick=()=>loadProject(b.dataset.loadProject));list.querySelectorAll('[data-delete-project]').forEach(b=>b.onclick=()=>deleteProject(b.dataset.deleteProject))}catch(e){list.innerHTML=`

Impossible de charger les projets : ${esc(e.message)}

`}} async function loadProject(id){try{const d=await api('/api/projects/'+encodeURIComponent(id));blocks=d.project.data.blocks;design=d.project.data.design;sitePages=Array.isArray(d.project.data.pages)&&d.project.data.pages.length?d.project.data.pages:[{id:'home',name:'Accueil',slug:'index',blocks:JSON.parse(JSON.stringify(blocks)),design:JSON.parse(JSON.stringify(design))}];activePageId=d.project.data.activePageId||sitePages[0].id;currentProjectId=d.project.id;currentProjectName=d.project.name;selected=blocks[0]?.id;syncDesignControls();snapshotActivePage();$('#accentColor').value=design.accent;$('#inkColor').value=design.ink;$('#bgColor').value=design.bg;$('#fontFamily').value=design.font;$('#siteWidth').value=design.width;closeModal();render();toast('Projet chargé : '+currentProjectName)}catch(e){toast(e.message)}} async function saveProject(){if(!ctsUser){authModal('Connecte-toi pour sauvegarder ce site sur ton compte.');return}const suggested=currentProjectName||blocks.find(b=>b.type==='header')?.logo||'Mon site';openModal(currentProjectId?'Enregistrer les modifications':'Enregistrer un nouveau site',`

Le contenu du site sera sauvegardé sur ton compte ChopTonSite.

`);$('#confirmSave').onclick=async()=>{const name=$('#projectName').value.trim()||'Mon site';snapshotActivePage();const data={blocks,design,pages:sitePages,activePageId};try{const path=currentProjectId?'/api/projects/'+encodeURIComponent(currentProjectId):'/api/projects';const method=currentProjectId?'PUT':'POST';const payload=currentProjectId?{name,data}:{name,data};const d=await api(path,{method,body:JSON.stringify(payload)});currentProjectId=d.id||currentProjectId;currentProjectName=name;closeModal();toast('Site sauvegardé dans ton compte')}catch(e){toast(e.message)}}} async function deleteProject(id){if(!confirm('Supprimer définitivement ce site enregistré ?'))return;try{await api('/api/projects/'+encodeURIComponent(id),{method:'DELETE'});if(currentProjectId===id){currentProjectId=null;currentProjectName='Mon site'}toast('Site supprimé');await loadProjectList()}catch(e){toast(e.message)}} async function adminModal(){openModal('Administration — utilisateurs',`

Accès administrateur contrôlé côté serveur.

`);async function search(){const box=$('#adminUsers');box.innerHTML='

Chargement…

';try{const d=await api('/api/admin/users?q='+encodeURIComponent($('#adminSearch').value));box.innerHTML=d.users.map(u=>`
${esc(u.username)} ${u.role==='admin'?'(admin)':''}${esc(u.email)} · ${esc(u.status)}
${u.role!=='admin'?``:''}
`).join('')||'

Aucun résultat.

';box.querySelectorAll('[data-user-status]').forEach(b=>b.onclick=async()=>{try{await api('/api/admin/users',{method:'PATCH',body:JSON.stringify({id:b.dataset.userStatus,status:b.dataset.status})});await search()}catch(e){toast(e.message)}})}catch(e){box.innerHTML=`

${esc(e.message)}

`}}$('#adminSearchBtn').onclick=search;search()} $('#accountBtn').onclick=accountModal;$('#mySitesBtn').onclick=openDashboard;$('#saveProjectBtn').onclick=saveProject; refreshSession(); loadHash();render();