Option B) Plugin "Simple Custom CSS and JS" : → Ajoutez un nouveau JS, sélectionnez "Footer", collez le JS ci-dessous Option C) Dans votre thème enfant, functions.php : wp_enqueue_script('cofyndo', get_stylesheet_directory_uri() . '/cofyndo.js', [], null, true); Puis sauvegardez ce fichier en tant que cofyndo.js dans votre thème enfant. ================================================================ */ /* ================================================================ PROFILES DATA — 12 Tunisian founders ================================================================ */ const PROFILES = [ { id:1, init:'A', name:'Amira Belhadj', role:'CEO / Founder', city:'Tunis', industry:'Fintech', skills:['Business Dev','Fundraising','Strategy'], available:'Full-time', match:97, bio:"Fondatrice de PayTunis. Je cherche un CTO passionné par la fintech pour co-construire notre produit.", grad:'135deg,#EC4899,#F97316', online:true }, { id:2, init:'Y', name:'Yassine Trabelsi', role:'CTO / Full-Stack', city:'Sfax', industry:'SaaS B2B', skills:['React','Node.js','PostgreSQL','DevOps'], available:'Full-time', match:95, bio:"Dev full-stack 6 ans d'expérience. J'ai construit 3 SaaS de zéro. Je recherche un co-fondateur business.", grad:'135deg,#6366F1,#8B5CF6', online:true }, { id:3, init:'M', name:'Mohamed Cherni', role:'Co-fondateur', city:'Sousse', industry:'AgriTech', skills:['IoT','Python','Agriculture','Hardware'], available:'Full-time', match:91, bio:"Co-fondateur de GreenField TN. On cherche un CMO pour accélérer notre croissance agricole.", grad:'135deg,#10B981,#3B82F6', online:false }, { id:4, init:'I', name:'Ines Mansouri', role:'Fondatrice', city:'Tunis', industry:'EdTech', skills:['Product','UX Research','Pédagogie','Growth'], available:'Full-time', match:89, bio:"Fondatrice EdTech Lab. Ancienne prof. Je cherche un associé technique pour digitaliser l'éducation en Tunisie.", grad:'135deg,#F59E0B,#EF4444', online:true }, { id:5, init:'K', name:'Karim Bouzid', role:'Fondateur', city:'Monastir', industry:'E-commerce', skills:['E-commerce','Marketing','Logistics'], available:'Part-time', match:86, bio:"Fondateur de Kartab. Ex-Jumia. Je cherche un CTO pour une marketplace de livraison express.", grad:'135deg,#06B6D4,#6366F1', online:false }, { id:6, init:'S', name:'Salma Khedher', role:'Designer / CPO', city:'Tunis', industry:'SaaS B2B', skills:['Figma','Branding','Product Design','UI/UX'], available:'Freelance', match:93, bio:"Designer produit 5 ans en startups. Disponible en freelance ou equity. Portfolio sur demande.", grad:'135deg,#8B5CF6,#EC4899', online:true }, { id:7, init:'R', name:'Riadh Hammouda', role:'CTO / AI Lead', city:'Tunis', industry:'AI / ML', skills:['Python','TensorFlow','LLMs','MLOps','AWS'], available:'Full-time', match:98, bio:"Ingénieur ML chez Vermeg pendant 4 ans. Je veux co-fonder une startup AI, idéalement santé ou finance.", grad:'135deg,#0EA5E9,#6366F1', online:true }, { id:8, init:'N', name:'Nadia Sfar', role:'CMO', city:'Sfax', industry:'Health', skills:['Growth','SEO','Content','Community'], available:'Full-time', match:82, bio:"Growth marketer, 100k+ users acquis. Passionnée de HealthTech. Je rejoins un projet avec traction.", grad:'135deg,#EF4444,#F97316', online:false }, { id:9, init:'H', name:'Hatem Gharbi', role:'Product Manager', city:'Remote', industry:'Fintech', skills:['Agile','Roadmap','Data','Figma'], available:'Full-time', match:88, bio:"PM en remote. Ancien Expensya. Je cherche une équipe fondatrice pour un produit fintech B2C.", grad:'135deg,#10B981,#F59E0B', online:true }, { id:10, init:'L', name:'Leila Dridi', role:'Dev Freelance', city:'Tunis', industry:'E-commerce', skills:['Vue.js','Laravel','MySQL','API'], available:'Freelance', match:79, bio:"Dev freelance 4 ans. Disponible pour rejoindre un projet early-stage equity + rémunération.", grad:'135deg,#F97316,#EF4444', online:false }, { id:11, init:'O', name:'Omar Ben Salah', role:'CEO / Bizdev', city:'Sousse', industry:'SaaS B2B', skills:['Sales','B2B','Partnerships','Finance'], available:'Full-time', match:85, bio:"Chargé d'affaires, réseau solide PME tunisiennes. Je cherche un CTO pour lancer un SaaS de gestion.", grad:'135deg,#6366F1,#10B981', online:true }, { id:12, init:'F', name:'Fatma Jebali', role:'Designer UX', city:'Monastir', industry:'EdTech', skills:['UX Design','Wireframing','Illustrator'], available:'Part-time', match:77, bio:"Designer UX spécialisée apps mobiles éducatives. Disponible mi-temps ou freelance.", grad:'135deg,#8B5CF6,#06B6D4', online:false }, ]; let filteredProfiles = [...PROFILES]; /* ---- Render profile cards ---- */ function renderProfiles(list) { const grid = document.getElementById('profilesGrid'); document.getElementById('profilesCount').textContent = list.length + ' profil' + (list.length > 1 ? 's' : ''); if (!list.length) { grid.innerHTML = `
🔍

Aucun profil ne correspond à votre recherche.

`; return; } grid.innerHTML = list.map(p => `
${p.match}% match
${p.init} ${p.online ? '
' : ''}
${p.name}
${p.role}
📍 ${p.city} · ${p.industry}

${p.bio}

${p.skills.map((s,i) => `${s}`).join('')} ${p.available}
Compatibilité${p.match}%
`).join(''); } function filterProfiles() { const q = document.getElementById('profilesSearchInput').value.toLowerCase().trim(); filteredProfiles = PROFILES.filter(p => !q || p.name.toLowerCase().includes(q) || p.role.toLowerCase().includes(q) || p.city.toLowerCase().includes(q) || p.industry.toLowerCase().includes(q) || p.skills.some(s => s.toLowerCase().includes(q)) ); renderProfiles(filteredProfiles); } function sortProfiles(val) { if (val === 'match') filteredProfiles.sort((a,b) => b.match - a.match); if (val === 'name') filteredProfiles.sort((a,b) => a.name.localeCompare(b.name)); if (val === 'recent') filteredProfiles.sort((a,b) => b.id - a.id); renderProfiles(filteredProfiles); } function connectProfile(e, id) { const btn = e.target; btn.textContent = '✓ Demande envoyée'; btn.style.background = 'var(--mint)'; btn.style.color = 'var(--bg)'; btn.disabled = true; } function viewProfile(id) { const p = PROFILES.find(x => x.id === id); alert(`👤 ${p.name}\n${p.role} · ${p.city}\n\n"${p.bio}"\n\nCompétences : ${p.skills.join(', ')}\nDisponibilité : ${p.available}\nCompatibilité : ${p.match}%`); } function toggleChip(el) { el.parentElement.querySelectorAll('.chip').forEach(c => c.classList.remove('active')); el.classList.add('active'); filterProfiles(); } function updateRangeLabel(el) { document.getElementById('matchRangeVal').textContent = el.value + '%+'; } function initProfilesPage() { filteredProfiles = [...PROFILES].sort((a,b) => b.match - a.match); renderProfiles(filteredProfiles); } /* ================================================================ PAGE OVERLAY SYSTEM ================================================================ */ /* ================================================================ PAGE OVERLAY SYSTEM ================================================================ */ function openPage(id) { const el = document.getElementById(id); if (!el) return; el.classList.add('open'); document.body.style.overflow = 'hidden'; if (id === 'profilesPage') initProfilesPage(); } function closePage(id) { const el = document.getElementById(id); if (!el) return; el.classList.remove('open'); document.body.style.overflow = ''; } /* Auth Gate — profiles require account */ function openAuthGate() { document.getElementById('authGate').classList.add('open'); document.body.style.overflow = 'hidden'; } function closeAuthGate() { document.getElementById('authGate').classList.remove('open'); document.body.style.overflow = ''; } document.addEventListener('keydown', e => { if (e.key === 'Escape') { ['chatPage','pricingPage','signupPage','profilesPage','personalChatPage','myProfilePage'].forEach(id => closePage(id)); closeAuthGate(); } }); /* ---- Sticky nav ---- */ const navbar = document.getElementById('navbar'); window.addEventListener('scroll', () => navbar.classList.toggle('stuck', window.scrollY > 20), { passive:true }); /* ---- Mobile nav ---- */ document.getElementById('ham').addEventListener('click', () => document.getElementById('mobNav').classList.add('open')); document.getElementById('mobClose').addEventListener('click', () => document.getElementById('mobNav').classList.remove('open')); document.querySelectorAll('.mob-nav a').forEach(a => a.addEventListener('click', () => document.getElementById('mobNav').classList.remove('open'))); /* ---- Scroll reveal ---- */ const io = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -30px 0px' }); document.querySelectorAll('.reveal').forEach(el => io.observe(el)); /* ---- Smooth scroll ---- */ document.querySelectorAll('a[href^="#"]:not([href="#"])').forEach(a => { a.addEventListener('click', e => { const t = document.querySelector(a.getAttribute('href')); if (t && !a.closest('.page-overlay')) { e.preventDefault(); t.scrollIntoView({ behavior: 'smooth' }); } }); }); /* ---- Matching Profiles → requires account ---- */ document.getElementById('navProfilesBtn').addEventListener('click', e => { e.preventDefault(); openAuthGate(); }); document.getElementById('mobProfilesBtn').addEventListener('click', e => { e.preventDefault(); openAuthGate(); document.getElementById('mobNav').classList.remove('open'); }); /* ---- Personal Chat (DMs) triggers ---- */ document.getElementById('navPersonalChatBtn').addEventListener('click', e => { e.preventDefault(); openPage('personalChatPage'); }); document.getElementById('mobPersonalChatBtn').addEventListener('click', e => { e.preventDefault(); openPage('personalChatPage'); document.getElementById('mobNav').classList.remove('open'); }); /* ---- Global Chat triggers ---- */ ['navChatBtn','chatOpenBtn','footerChatLink'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('click', e => { e.preventDefault(); openPage('chatPage'); }); }); document.getElementById('mobChatBtn').addEventListener('click', e => { e.preventDefault(); openPage('chatPage'); document.getElementById('mobNav').classList.remove('open'); }); document.querySelector('.chat-send-btn').addEventListener('click', () => openPage('chatPage')); /* ---- Pricing triggers ---- */ ['navPricingBtn','fullPricingBtn'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('click', e => { e.preventDefault(); openPage('pricingPage'); }); }); document.getElementById('mobPricingBtn').addEventListener('click', e => { e.preventDefault(); openPage('pricingPage'); document.getElementById('mobNav').classList.remove('open'); }); /* ---- Signup triggers ---- */ ['navGetStarted','heroGetStarted','ctaSignup','footerSignupLink', 'pricingSignup1','pricingSignup2','pricingSignup3','pricingSignup4'].forEach(id => { const el = document.getElementById(id); if (el) el.addEventListener('click', e => { e.preventDefault(); handleSignup_mode(); openPage('signupPage'); }); }); const navSignInEl = document.getElementById('navSignIn'); if (navSignInEl) navSignInEl.addEventListener('click', e => { e.preventDefault(); handleSignin(); }); document.getElementById('mobSignupBtn').addEventListener('click', e => { e.preventDefault(); openPage('signupPage'); document.getElementById('mobNav').classList.remove('open'); }); /* ---- Global Chat live messages ---- */ const chatInput = document.getElementById('chatMsgInput'); const chatSend = document.getElementById('chatSendBtn'); const chatMsgs = document.getElementById('fullChatMsgs'); function sendChatMsg() { const val = chatInput.value.trim(); if (!val) return; const msg = document.createElement('div'); msg.className = 'msg me'; msg.innerHTML = `
M
${escHtml(val)}
Vous · maintenant
`; chatMsgs.appendChild(msg); chatInput.value = ''; chatMsgs.scrollTop = chatMsgs.scrollHeight; setTimeout(() => { const replies = [ { av:'A', col:'#EC4899,#F97316', name:'Amira', text:'Super initiative ! Tu cherches quel type de co-fondateur ?' }, { av:'R', col:'#0EA5E9,#6366F1', name:'Riadh', text:"Bienvenue ! N'hésite pas à partager ton projet 🚀" }, { av:'S', col:'#8B5CF6,#EC4899', name:'Salma', text:'On est une communauté soudée ici, tu vas trouver ton match 💪' }, { av:'Y', col:'#6366F1,#8B5CF6', name:'Yassine', text:'Donne-nous plus de détails sur ton projet !' }, ]; const r = replies[Math.floor(Math.random() * replies.length)]; const rep = document.createElement('div'); rep.className = 'msg'; rep.innerHTML = `
${r.av}
${r.text}
${r.name} · maintenant
`; chatMsgs.appendChild(rep); chatMsgs.scrollTop = chatMsgs.scrollHeight; }, 800 + Math.random() * 600); } chatSend.addEventListener('click', sendChatMsg); chatInput.addEventListener('keydown', e => { if (e.key === 'Enter') sendChatMsg(); }); /* ---- Personal Chat (DMs) logic ---- */ const dmConversations = {}; let currentDM = 'Amira Belhadj'; const dmAutoReplies = { 'Amira Belhadj': ["Je serais disponible jeudi matin, ça te convient ?", "Super ! Envoie-moi ton pitch deck quand tu peux.", "Je pense qu'on est vraiment complémentaires 💪"], 'Yassine Trabelsi': ["Oui, planifions un call cette semaine.", "Tu utilises quelle stack actuellement ?", "Je peux contribuer côté architecture dès maintenant."], 'Riadh Hammouda': ["J'ai de l'expérience en ML qui pourrait vraiment aider.", "On peut faire un call découverte de 30min ?", "Très bon profil, j'aime l'approche produit."], 'Salma Khedher': ["Je t'envoie mon portfolio ce soir sans faute.", "On peut commencer par un projet test d'une semaine ?", "J'adore le concept, le design est crucial dans ce secteur."], 'Mohamed Cherni': ["Notre modèle AgriTech peut s'adapter à d'autres secteurs aussi.", "On cherche toujours un CMO senior si tu connais quelqu'un.", "La levée de fonds était plus facile avec Cofyndo 🙌"], }; function switchDM(el, name, grad, init, online) { document.querySelectorAll('.dm-item').forEach(i => i.classList.remove('active')); el.classList.add('active'); const badge = el.querySelector('.dm-badge'); if (badge) badge.remove(); currentDM = name; document.getElementById('dmHeaderAv').style.background = `linear-gradient(${grad})`; document.getElementById('dmHeaderAv').textContent = init; document.getElementById('dmHeaderName').textContent = name; const msgs = document.getElementById('dmMessages'); msgs.innerHTML = dmConversations[name] || `
${init}
Salut ! Content(e) de te retrouver ici sur Cofyndo 👋
${name.split(' ')[0]} · maintenant
`; } /* ---- DM sending is locked — requires profile + plan ---- */ function sendDM() { /* locked — user must create profile and choose a plan */ } function sendDM() { const input = document.getElementById('dmInput'); const val = input.value.trim(); if (!val) return; const msgs = document.getElementById('dmMessages'); const out = document.createElement('div'); out.className = 'msg me'; out.innerHTML = `
M
${escHtml(val)}
Vous · maintenant
`; msgs.appendChild(out); input.value = ''; msgs.scrollTop = msgs.scrollHeight; dmConversations[currentDM] = msgs.innerHTML; const replies = dmAutoReplies[currentDM] || ["Intéressant, dis-moi en plus 🙏"]; setTimeout(() => { const activeItem = document.querySelector('.dm-item.active .dm-av'); const replyText = replies[Math.floor(Math.random() * replies.length)]; const rep = document.createElement('div'); const init2 = activeItem ? activeItem.textContent.trim()[0] : '?'; const bg2 = activeItem ? activeItem.style.background : 'linear-gradient(135deg,#6366F1,#8B5CF6)'; rep.className = 'msg'; rep.innerHTML = `
${init2}
${replyText}
${currentDM.split(' ')[0]} · maintenant
`; msgs.appendChild(rep); msgs.scrollTop = msgs.scrollHeight; dmConversations[currentDM] = msgs.innerHTML; const lastEl = document.querySelector('.dm-item.active .dm-last'); if (lastEl) lastEl.textContent = replyText; }, 700 + Math.random() * 500); } function escHtml(s) { return s.replace(/&/g,'&').replace(//g,'>'); } /* ================================================================ OAUTH CONFIG — Google & Apple ───────────────────────────────────────────────────────────────── GOOGLE: 1. Allez sur console.cloud.google.com → APIs & Services → Credentials 2. Créez un "OAuth 2.0 Client ID" (type: Web application) 3. Ajoutez votre domaine dans "Authorized JavaScript origins" 4. Copiez le Client ID ci-dessous et remplacez-le aussi dans l'attribut data-client_id du div#g_id_onload dans le HTML APPLE: 1. Allez sur developer.apple.com → Certificates → Identifiers 2. Créez un "Services ID" et activez "Sign In with Apple" 3. Configurez le domaine et l'URL de retour 4. Copiez le Services ID ci-dessous ================================================================ */ const GOOGLE_CLIENT_ID = '472891473799-ds632tb76p8ojn1nk7ifhlmd7a0ehksm.apps.googleusercontent.com'; const APPLE_CLIENT_ID = 'VOTRE_APPLE_SERVICES_ID'; // ex: com.cofyndo.app const APPLE_REDIRECT = window.location.origin + '/auth/apple/callback'; // URL de callback Apple /* ---- Google Sign In ---- */ function signInWithGoogle() { const errEl = document.getElementById('signupError'); errEl.style.display = 'none'; if (GOOGLE_CLIENT_ID === 'VOTRE_GOOGLE_CLIENT_ID') { showOAuthNotice('Google'); return; } if (typeof google !== 'undefined' && google.accounts) { google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback: handleGoogleCredential, auto_select: false, }); google.accounts.id.prompt((notification) => { if (notification.isNotDisplayed() || notification.isSkippedMoment()) { // Fallback: open popup manually google.accounts.oauth2.initCodeClient({ client_id: GOOGLE_CLIENT_ID, scope: 'openid email profile', callback: handleGoogleCredential, }).requestCode(); } }); } else { // Fallback: redirect-based flow const params = new URLSearchParams({ client_id: GOOGLE_CLIENT_ID, redirect_uri: window.location.origin + '/auth/google/callback', response_type: 'code', scope: 'openid email profile', prompt: 'select_account', }); window.location.href = 'https://accounts.google.com/o/oauth2/v2/auth?' + params.toString(); } } /* Called by Google SDK with the credential JWT */ function handleGoogleCredential(response) { if (!response || !response.credential) return; const payload = JSON.parse(atob(response.credential.split('.')[1])); const { name, email, picture } = payload; onOAuthSuccess({ provider: 'google', name, email, avatar: picture, token: response.credential }); } /* ---- Apple Sign In ---- */ function signInWithApple() { const errEl = document.getElementById('signupError'); errEl.style.display = 'none'; if (APPLE_CLIENT_ID === 'VOTRE_APPLE_SERVICES_ID') { showOAuthNotice('Apple'); return; } if (typeof AppleID !== 'undefined') { AppleID.auth.init({ clientId: APPLE_CLIENT_ID, scope: 'name email', redirectURI: APPLE_REDIRECT, state: crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36), usePopup: true, }); AppleID.auth.signIn() .then(data => { const name = data.user ? `${data.user.name?.firstName || ''} ${data.user.name?.lastName || ''}`.trim() : ''; const email = data.user?.email || ''; onOAuthSuccess({ provider: 'apple', name, email, token: data.authorization?.id_token }); }) .catch(err => { if (err.error !== 'popup_closed_by_user') { showError('Connexion Apple annulée ou échouée.'); } }); } else { // Fallback: redirect-based Apple flow const params = new URLSearchParams({ client_id: APPLE_CLIENT_ID, redirect_uri: APPLE_REDIRECT, response_type: 'code id_token', scope: 'name email', response_mode: 'form_post', state: Math.random().toString(36).substring(2), }); window.location.href = 'https://appleid.apple.com/auth/authorize?' + params.toString(); } } /* ---- Common OAuth success handler ---- */ function onOAuthSuccess({ provider, name, email, avatar, token }) { loginUser({ name: name || email?.split('@')[0] || 'Utilisateur', email: email || '', provider }); } /* ── Notice shown when credentials not yet configured ── */ function showOAuthNotice(provider) { const errEl = document.getElementById('signupError'); const docsUrl = provider === 'Google' ? 'https://console.cloud.google.com/apis/credentials' : 'https://developer.apple.com/account/resources/identifiers/list/serviceId'; errEl.innerHTML = `⚙️ ${provider} OAuth non configuré.
Remplacez ${provider === 'Google' ? 'VOTRE_GOOGLE_CLIENT_ID' : 'VOTRE_APPLE_SERVICES_ID'} dans le code. Configurer →`; errEl.style.display = 'block'; } function showError(msg) { const errEl = document.getElementById('signupError'); errEl.textContent = '⚠️ ' + msg; errEl.style.display = 'block'; } /* ================================================================ SESSION MANAGEMENT — login / logout / restore ================================================================ */ let currentUser = null; function loginUser(user) { // user = { name, email, avatar, provider } currentUser = user; try { sessionStorage.setItem('cofyndo_user', JSON.stringify(user)); } catch(e){} // Update navbar const navUser = document.getElementById('navUser'); const navSignIn = document.getElementById('navSignIn'); const navJoin = document.getElementById('navGetStarted'); const avatar = document.getElementById('navUserAvatar'); const nameEl = document.getElementById('navUserName'); const udName = document.getElementById('udName'); const udEmail = document.getElementById('udEmail'); const displayName = user.name || user.email?.split('@')[0] || 'Utilisateur'; const initial = displayName.charAt(0).toUpperCase(); avatar.textContent = initial; nameEl.textContent = displayName; udName.textContent = displayName; udEmail.textContent = user.email || (user.provider ? `via ${user.provider}` : ''); navSignIn.style.display = 'none'; navJoin.style.display = 'none'; navUser.classList.add('visible'); // Toggle dropdown on click navUser.addEventListener('click', (e) => { e.stopPropagation(); navUser.classList.toggle('menu-open'); }); document.addEventListener('click', () => navUser.classList.remove('menu-open')); // Close signup page closePage('signupPage'); // Welcome toast showWelcomeToast(displayName, user.provider); // Check if user had a pending plan purchase setTimeout(checkPendingPlan, 500); } function logoutUser() { currentUser = null; try { sessionStorage.removeItem('cofyndo_user'); } catch(e){} const navUser = document.getElementById('navUser'); const navSignIn = document.getElementById('navSignIn'); const navJoin = document.getElementById('navGetStarted'); navUser.classList.remove('visible', 'menu-open'); navSignIn.style.display = ''; navJoin.style.display = ''; showToast('👋 Vous êtes déconnecté.', 'var(--card-border)'); } function restoreSession() { try { const saved = sessionStorage.getItem('cofyndo_user'); if (saved) loginUser(JSON.parse(saved)); } catch(e){} } function showWelcomeToast(name, provider) { const providerLine = provider ? ` via ${provider}` : ''; showToast(`✅ Bienvenue, ${name} !${providerLine}`, 'rgba(62,255,199,.25)'); } function showToast(html, borderColor) { const old = document.getElementById('cofToast'); if (old) old.remove(); const t = document.createElement('div'); t.id = 'cofToast'; t.style.cssText = ` position:fixed; bottom:28px; left:50%; transform:translateX(-50%) translateY(20px); background:var(--bg2); border:1px solid ${borderColor}; border-radius:var(--r-md); padding:14px 22px; display:flex; align-items:center; gap:10px; box-shadow:0 8px 32px rgba(0,0,0,.4); z-index:9999; font-size:14px; color:var(--text); opacity:0; transition:all .35s var(--ease); white-space:nowrap; `; t.innerHTML = html; document.body.appendChild(t); requestAnimationFrame(() => { t.style.opacity = '1'; t.style.transform = 'translateX(-50%) translateY(0)'; }); setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(-50%) translateY(10px)'; setTimeout(() => t.remove(), 400); }, 3500); } /* ================================================================ SIGNUP / SIGNIN FORM ================================================================ */ /* ================================================================ SUPABASE CONFIG — replace with your own credentials Get them from: supabase.com → Your Project → Settings → API ================================================================ */ const SUPABASE_URL = 'VOTRE_SUPABASE_URL'; // e.g. https://xyz.supabase.co const SUPABASE_KEY = 'VOTRE_SUPABASE_ANON_KEY'; // anon public key async function handleSignup(e) { e.preventDefault(); const btn = document.getElementById('signupSubmitBtn'); const errEl = document.getElementById('signupError'); errEl.style.display = 'none'; // ── SIGN-IN MODE ── if (isSigninMode) { const identifier = document.getElementById('su-identifier').value.trim(); const pass = document.getElementById('su-pass').value; if (!identifier || !pass) { errEl.textContent = '⚠️ Veuillez remplir tous les champs.'; errEl.style.display = 'block'; return; } btn.textContent = 'Connexion en cours…'; btn.disabled = true; if (SUPABASE_URL === 'VOTRE_SUPABASE_URL') { setTimeout(() => { const id = document.getElementById('su-identifier').value.trim(); loginUser({ name: id, email: id.includes('@') ? id : '', provider: null }); btn.textContent = 'Se connecter →'; btn.style.background = ''; btn.style.color = ''; btn.disabled = false; }, 1000); return; } try { // Determine if identifier is email, phone, or username let email = identifier; if (!identifier.includes('@')) { // Try to look up by username/phone — app-specific logic email = identifier; // fallback, replace with your lookup } const res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=password`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_KEY }, body: JSON.stringify({ email, password: pass }) }); const data = await res.json(); if (!res.ok) throw new Error(data.error_description || data.msg || 'Identifiants incorrects'); loginUser({ name: data.user?.user_metadata?.name || identifier, email: data.user?.email || identifier, provider: null }); btn.textContent = 'Se connecter →'; btn.style.background = ''; btn.style.color = ''; btn.disabled = false; } catch(err) { errEl.textContent = '⚠️ ' + err.message; errEl.style.display = 'block'; btn.textContent = 'Se connecter →'; btn.disabled = false; } return; } // ── SIGN-UP MODE ── const name = document.getElementById('su-name').value.trim(); const email = document.getElementById('su-email').value.trim(); const pass = document.getElementById('su-pass').value; btn.textContent = 'Inscription en cours…'; btn.disabled = true; // Check if Supabase is configured if (SUPABASE_URL === 'VOTRE_SUPABASE_URL') { setTimeout(() => { const n = document.getElementById('su-name').value.trim(); const em = document.getElementById('su-email').value.trim(); loginUser({ name: n, email: em, provider: null }); btn.textContent = 'Rejoindre Cofyndo →'; btn.style.background = ''; btn.style.color = ''; btn.disabled = false; }, 1200); return; } try { // 1. Create auth user const authRes = await fetch(`${SUPABASE_URL}/auth/v1/signup`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_KEY }, body: JSON.stringify({ email, password: pass }) }); const authData = await authRes.json(); if (!authRes.ok) { throw new Error(authData.msg || authData.error_description || 'Erreur lors de l\'inscription'); } // 2. Save extra profile info to users table await fetch(`${SUPABASE_URL}/rest/v1/users`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${authData.access_token}`, 'Prefer': 'return=minimal' }, body: JSON.stringify({ id: authData.user?.id, name, email, plan: 'starter', created_at: new Date().toISOString() }) }); // Success loginUser({ name, email, provider: null }); btn.textContent = 'Rejoindre Cofyndo →'; btn.style.background = ''; btn.style.color = ''; btn.disabled = false; } catch (err) { errEl.textContent = '⚠️ ' + err.message; errEl.style.display = 'block'; btn.textContent = 'Rejoindre Cofyndo →'; btn.disabled = false; } } let isSigninMode = false; function updateOAuthLabels(mode) { const prefix = mode === 'signin' ? 'Se connecter' : 'Continuer'; document.getElementById('oauthGoogleLabel').textContent = prefix + ' avec Google'; document.getElementById('oauthAppleLabel').textContent = prefix + ' avec Apple'; } function handleSignin() { isSigninMode = true; document.querySelector('.signup-title').textContent = 'Connexion à Cofyndo'; document.querySelector('.signup-sub').textContent = 'Bon retour parmi nous 👋'; document.getElementById('formDividerText').innerHTML = 'ou se connecter avec vos identifiants'; if (document.querySelector('.sl-headline')) { document.querySelector('.sl-headline').innerHTML = 'Bon retour
parmi nous 👋'; } updateOAuthLabels('signin'); // Hide sign-up only fields document.getElementById('nameGroup').style.display = 'none'; document.getElementById('emailGroup').style.display = 'none'; document.getElementById('phoneGroup').style.display = 'none'; // Show combined identifier field document.getElementById('identifierGroup').style.display = 'block'; // Remove required from hidden fields document.getElementById('su-name').removeAttribute('required'); document.getElementById('su-email').removeAttribute('required'); document.getElementById('su-identifier').setAttribute('required', ''); // Update button + switch link document.getElementById('signupSubmitBtn').textContent = 'Se connecter →'; document.getElementById('signinSwitchText').innerHTML = 'Pas encore de compte ? Créer un compte'; // Live icon update based on input type — attach only once const identifierInput = document.getElementById('su-identifier'); const identifierIcon = document.getElementById('identifierIcon'); if (!identifierInput.dataset.listenerAttached) { identifierInput.dataset.listenerAttached = 'true'; identifierInput.addEventListener('input', function() { const v = this.value.trim(); if (/^\+?\d[\d\s]{6,}$/.test(v)) identifierIcon.textContent = '📱'; else if (v.includes('@')) identifierIcon.textContent = '✉️'; else identifierIcon.textContent = '👤'; }); } openPage('signupPage'); } function handleSignup_mode() { isSigninMode = false; document.querySelector('.signup-title').textContent = 'Créez votre compte'; document.querySelector('.signup-sub').textContent = 'Rejoignez 1 180+ fondateurs tunisiens sur Cofyndo.'; document.getElementById('formDividerText').innerHTML = 'ou s\'inscrire avec un email'; if (document.querySelector('.sl-headline')) { document.querySelector('.sl-headline').innerHTML = 'Trouvez les
bonnes personnes
pour bâtir.'; } updateOAuthLabels('signup'); document.getElementById('nameGroup').style.display = 'block'; document.getElementById('emailGroup').style.display = 'block'; document.getElementById('phoneGroup').style.display = 'block'; document.getElementById('identifierGroup').style.display = 'none'; document.getElementById('su-name').setAttribute('required', ''); document.getElementById('su-email').setAttribute('required', ''); document.getElementById('su-identifier').removeAttribute('required'); document.getElementById('signupSubmitBtn').textContent = 'Rejoindre Cofyndo →'; document.getElementById('signinSwitchText').innerHTML = 'Déjà un compte ? Se connecter'; } /* ================================================================ LIVE STATS — animated counters + real-time Supabase sync ================================================================ Remplace les valeurs ci-dessous par tes vrais chiffres. Une fois Supabase configuré, les stats se mettent à jour automatiquement depuis la base de données. ================================================================ */ const STATS = { founders: { target: 1180, suffix: '+', prefix: '', id: 'stat-founders' }, matches: { target: 237, suffix: '', prefix: '', id: 'stat-matches' }, raised: { target: 42, suffix: 'M dt', prefix: '', id: 'stat-raised' }, satisfaction: { target: 94, suffix: '%', prefix: '', id: 'stat-satisfaction' }, }; /* ---- Animated count-up on scroll into view ---- */ function animateCounter(el, from, to, suffix, prefix, duration = 1800) { const startTime = performance.now(); function tick(now) { const elapsed = now - startTime; const progress = Math.min(elapsed / duration, 1); // Ease out cubic const eased = 1 - Math.pow(1 - progress, 3); const current = Math.round(from + (to - from) * eased); el.textContent = prefix + current.toLocaleString('fr-TN') + suffix; if (progress < 1) requestAnimationFrame(tick); } requestAnimationFrame(tick); } /* ---- Trigger counters when stats section enters viewport ---- */ const statsTriggered = {}; const statsObserver = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { Object.values(STATS).forEach(stat => { if (!statsTriggered[stat.id]) { statsTriggered[stat.id] = true; const el = document.getElementById(stat.id); if (el) animateCounter(el, 0, stat.target, stat.suffix, stat.prefix); } }); statsObserver.disconnect(); } }); }, { threshold: 0.2 }); const statsSection = document.querySelector('.stats-section'); if (statsSection) statsObserver.observe(statsSection); /* ---- Live tick simulation (±1 every ~25s) ---- */ /* Simulates organic growth until Supabase is connected */ function liveTick() { const keys = ['founders', 'matches']; const key = keys[Math.floor(Math.random() * keys.length)]; const stat = STATS[key]; if (!statsTriggered[stat.id]) return; const el = document.getElementById(stat.id); if (!el) return; stat.target += 1; el.style.transition = 'color .4s'; el.style.color = 'var(--mint)'; el.textContent = stat.prefix + stat.target.toLocaleString('fr-TN') + stat.suffix; setTimeout(() => { el.style.color = ''; }, 1200); } setInterval(liveTick, 25000 + Math.random() * 15000); /* ---------------------------------------------------------------- SUPABASE REAL-TIME SYNC (activé une fois ton projet configuré) Décommente ce bloc après avoir renseigné SUPABASE_URL + KEY ---------------------------------------------------------------- async function fetchLiveStats() { try { const res = await fetch(`${SUPABASE_URL}/rest/v1/stats?id=eq.global&select=founders,matches,raised_dt,satisfaction`, { headers: { 'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}` } }); const [data] = await res.json(); if (!data) return; if (data.founders !== undefined) { STATS.founders.target = data.founders; document.getElementById('stat-founders').textContent = data.founders.toLocaleString('fr-TN') + '+'; } if (data.matches !== undefined) { STATS.matches.target = data.matches; document.getElementById('stat-matches').textContent = data.matches.toLocaleString('fr-TN'); } if (data.raised_dt !== undefined) { STATS.raised.target = data.raised_dt; document.getElementById('stat-raised').textContent = data.raised_dt + 'M dt'; } if (data.satisfaction !== undefined) { STATS.satisfaction.target = data.satisfaction; document.getElementById('stat-satisfaction').textContent = data.satisfaction + '%'; } } catch(e) { console.warn('Stats sync failed', e); } } fetchLiveStats(); setInterval(fetchLiveStats, 60000); // refresh every 60s ---------------------------------------------------------------- */ /* ================================================================ LANGUAGE SWITCHER ================================================================ */ const TRANSLATIONS = { fr: { dir:'ltr', code:'FR', 'hero-badge': 'En ligne — 1 180+ fondateurs actifs 🇹🇳', 'hero-h1': 'Trouve les bonnes personnes
pour bâtir ton projet', 'hero-sub': "On sait que lancer seul c'est dur. Cofyndo te connecte avec les co-fondateurs, freelancers et builders qu'il te faut — en Tunisie et au-delà.", 'hero-cta': 'Rejoindre Cofyndo', 'hero-explore': 'Voir les fondateurs', 'proof-text': '1 180+ fondateurs actifs en ce moment
★★★★★ recommandé par la communauté', 'stat-0': 'Fondateurs actifs', 'stat-1': 'Matches réalisés', 'stat-2': 'Levés par les équipes', 'stat-3': 'Satisfaction des matchs', 'trusted-lbl': 'Des fondateurs issus de', 'how-label': 'Comment ça marche', 'how-title': 'Du profil au lancement
en quatre étapes', 'how-sub': "On a rendu la recherche d'un co-fondateur aussi simple que de coder un MVP.", 's1-h': 'Crée ton profil', 's1-p': "Décris ta vision, tes compétences et ce que tu cherches. Moins de 5 minutes pour un profil qui attire les bons profils.", 's2-h': 'Découvre tes matchs', 's2-p': "Notre IA te propose chaque jour les profils les plus compatibles — filtrés par compétences, valeurs et style de travail.", 's3-h': 'Teste ensemble', 's3-p': "Discutez, lancez un projet test de 2 semaines, alignez-vous sur l'équity — tout ça dans l'espace de travail Cofyndo.", 's4-h': 'Lance ta startup', 's4-p': "Formalisez l'association, gérez vos tâches et suivez votre avancement depuis le tableau de projet partagé.", 'nav-sign': 'Se connecter', 'nav-join': 'Rejoindre Cofyndo', }, en: { dir:'ltr', code:'EN', 'hero-badge': 'Live — 1,180+ active founders 🇹🇳', 'hero-h1': 'Find the right people
to build your startup', 'hero-sub': "We know building alone is hard. Cofyndo connects you with the co-founders, freelancers and builders you need — in Tunisia and beyond.", 'hero-cta': 'Join Cofyndo', 'hero-explore': 'Explore founders', 'proof-text': '1,180+ founders building right now
★★★★★ rated by our community', 'stat-0': 'Active founders', 'stat-1': 'Co-founder matches', 'stat-2': 'Raised by teams', 'stat-3': 'Match satisfaction', 'trusted-lbl': 'Founders from', 'how-label': 'How it works', 'how-title': 'From profile to launch
in four steps', 'how-sub': "We made finding a co-founder as intuitive as building the product itself.", 's1-h': 'Create your profile', 's1-p': "Describe your vision, skills and what you're looking for. Under 5 minutes.", 's2-h': 'Discover your matches', 's2-p': "Our AI surfaces the most compatible profiles daily — filtered by skills, values and work style.", 's3-h': 'Test together', 's3-p': "Chat, run a 2-week trial project, align on equity — all in Cofyndo's workspace.", 's4-h': 'Launch your startup', 's4-p': "Formalize the partnership, manage tasks and track progress from your shared project board.", 'nav-sign': 'Sign in', 'nav-join': 'Join Cofyndo', }, ar: { dir:'rtl', code:'AR', 'hero-badge': '🇹🇳 متصل — أكثر من 1180 مؤسس نشط', 'hero-h1': 'اعثر على الأشخاص المناسبين
لبناء مشروعك', 'hero-sub': "نعلم أن البناء وحدك أمر صعب. Cofyndo يربطك بالمؤسسين المشاركين والمستقلين الذين تحتاجهم.", 'hero-cta': 'انضم إلى Cofyndo', 'hero-explore': 'استكشف المؤسسين', 'proof-text': '+1180 مؤسس يبنون الآن
★★★★★ موصى به من المجتمع', 'stat-0': 'مؤسسون نشطون', 'stat-1': 'مطابقات منجزة', 'stat-2': 'تمويل جُمع', 'stat-3': 'رضا المطابقة', 'trusted-lbl': 'مؤسسون من', 'how-label': 'كيف يعمل', 'how-title': 'من الملف إلى الإطلاق
في أربع خطوات', 'how-sub': "جعلنا إيجاد شريك مؤسس بسيطاً مثل بناء المنتج نفسه.", 's1-h': 'أنشئ ملفك الشخصي', 's1-p': "صِف رؤيتك ومهاراتك وما تبحث عنه. أقل من 5 دقائق.", 's2-h': 'اكتشف مطابقاتك', 's2-p': "يقترح الذكاء الاصطناعي يومياً أكثر الملفات توافقاً معك.", 's3-h': 'اختبر معاً', 's3-p': "تحدث، أطلق مشروعاً تجريبياً لأسبوعين، تفق على الحصص.", 's4-h': 'أطلق شركتك الناشئة', 's4-p': "أضفِ الطابع الرسمي على الشراكة وتابع التقدم من لوحة المشروع المشتركة.", 'nav-sign': 'تسجيل الدخول', 'nav-join': 'انضم إلى Cofyndo', }, de: { dir:'ltr', code:'DE', 'hero-badge': 'Live — 1.180+ aktive Gründer 🇹🇳', 'hero-h1': 'Finde die richtigen Menschen
für dein Startup', 'hero-sub': "Alleine zu gründen ist schwer. Cofyndo verbindet dich mit Co-Foundern und Freelancern, die du brauchst.", 'hero-cta': 'Jetzt starten', 'hero-explore': 'Gründer entdecken', 'proof-text': '1.180+ Gründer bauen gerade
★★★★★ von der Community empfohlen', 'stat-0': 'Aktive Gründer', 'stat-1': 'Co-Founder Matches', 'stat-2': 'Von Teams gesammelt', 'stat-3': 'Match-Zufriedenheit', 'trusted-lbl': 'Gründer von', 'how-label': 'So funktioniert es', 'how-title': 'Vom Profil zum Launch
in vier Schritten', 'how-sub': "Wir haben die Suche nach einem Co-Founder so einfach gemacht wie das Bauen eines MVPs.", 's1-h': 'Profil erstellen', 's1-p': "Beschreibe deine Vision, Fähigkeiten und was du suchst. Unter 5 Minuten.", 's2-h': 'Matches entdecken', 's2-p': "Unsere KI schlägt täglich die kompatibelsten Profile vor.", 's3-h': 'Zusammen testen', 's3-p': "Chatte, starte ein 2-Wochen-Testprojekt, einige dich auf Anteile.", 's4-h': 'Startup launchen', 's4-p': "Formalisiere die Partnerschaft und verwalte Aufgaben im gemeinsamen Board.", 'nav-sign': 'Anmelden', 'nav-join': 'Cofyndo beitreten', }, es: { dir:'ltr', code:'ES', 'hero-badge': 'En vivo — 1.180+ fundadores activos 🇹🇳', 'hero-h1': 'Encuentra a las personas correctas
para construir tu startup', 'hero-sub': "Sabemos que construir solo es difícil. Cofyndo te conecta con los co-fundadores y freelancers que necesitas.", 'hero-cta': 'Unirse a Cofyndo', 'hero-explore': 'Explorar fundadores', 'proof-text': '1.180+ fundadores construyendo ahora
★★★★★ recomendado por la comunidad', 'stat-0': 'Fundadores activos', 'stat-1': 'Matches realizados', 'stat-2': 'Recaudado por equipos', 'stat-3': 'Satisfacción de matches', 'trusted-lbl': 'Fundadores de', 'how-label': 'Cómo funciona', 'how-title': 'Del perfil al lanzamiento
en cuatro pasos', 'how-sub': "Hemos hecho que encontrar un co-fundador sea tan intuitivo como construir un MVP.", 's1-h': 'Crea tu perfil', 's1-p': "Describe tu visión, habilidades y lo que buscas. Menos de 5 minutos.", 's2-h': 'Descubre tus matches', 's2-p': "Nuestra IA te sugiere los perfiles más compatibles cada día.", 's3-h': 'Prueba juntos', 's3-p': "Chatea, lanza un proyecto de prueba de 2 semanas, alinéate en equity.", 's4-h': 'Lanza tu startup', 's4-p': "Formaliza la asociación y gestiona tareas desde el tablero compartido.", 'nav-sign': 'Iniciar sesión', 'nav-join': 'Unirse a Cofyndo', }, it: { dir:'ltr', code:'IT', 'hero-badge': 'Online — 1.180+ fondatori attivi 🇹🇳', 'hero-h1': 'Trova le persone giuste
per costruire la tua startup', 'hero-sub': "Sappiamo che costruire da soli è difficile. Cofyndo ti connette con i co-fondatori e freelancer di cui hai bisogno.", 'hero-cta': 'Unisciti a Cofyndo', 'hero-explore': 'Esplora i fondatori', 'proof-text': '1.180+ fondatori che costruiscono ora
★★★★★ consigliato dalla community', 'stat-0': 'Fondatori attivi', 'stat-1': 'Match realizzati', 'stat-2': 'Raccolto dai team', 'stat-3': 'Soddisfazione dei match', 'trusted-lbl': 'Fondatori di', 'how-label': 'Come funziona', 'how-title': 'Dal profilo al lancio
in quattro passi', 'how-sub': "Abbiamo reso la ricerca di un co-fondatore intuitiva come costruire un MVP.", 's1-h': 'Crea il tuo profilo', 's1-p': "Descrivi la tua visione, le competenze e cosa cerchi. Meno di 5 minuti.", 's2-h': 'Scopri i tuoi match', 's2-p': "La nostra IA ti propone ogni giorno i profili più compatibili.", 's3-h': 'Testa insieme', 's3-p': "Chatta, lancia un progetto di prova di 2 settimane, allineati sull'equity.", 's4-h': 'Lancia la tua startup', 's4-p': "Formalizza la partnership e gestisci i task dal pannello di progetto condiviso.", 'nav-sign': 'Accedi', 'nav-join': 'Unisciti a Cofyndo', }, }; function setLang(lang) { const t = TRANSLATIONS[lang]; if (!t) return; document.documentElement.dir = t.dir; document.documentElement.lang = lang; const set = (sel, val, html = false) => { const el = typeof sel === 'string' ? document.querySelector(sel) : sel; if (!el) return; if (html) el.innerHTML = val; else el.textContent = val; }; // Hero set('.hero-badge', t['hero-badge'], true); set('.hero-h1', t['hero-h1'], true); set('.hero-sub', t['hero-sub']); set('#heroGetStarted', t['hero-cta']); set('.hero-btns .btn-ghost', t['hero-explore']); set('.proof-text', t['proof-text'], true); // Stats labels document.querySelectorAll('.stat-label').forEach((el, i) => { if (t['stat-'+i]) el.textContent = t['stat-'+i]; }); // Trusted by set('.logos-label', t['trusted-lbl']); // How it works set('#how .section-label', t['how-label']); set('#how .section-title', t['how-title'], true); set('#how .section-sub', t['how-sub']); const steps = document.querySelectorAll('.step-block'); ['s1','s2','s3','s4'].forEach((k, i) => { if (!steps[i]) return; steps[i].querySelector('h3').textContent = t[k+'-h']; steps[i].querySelector('p').textContent = t[k+'-p']; }); // Nav set('#navSignIn', t['nav-sign']); set('#navGetStarted', t['nav-join']); // Indicator + dropdown active state document.getElementById('langCurrent').textContent = t.code; document.querySelectorAll('.lang-option').forEach(btn => { btn.classList.toggle('active', btn.dataset.lang === lang); }); // Close dropdown document.getElementById('langSwitcher').classList.remove('open'); } // Toggle dropdown open/close document.getElementById('langBtn').addEventListener('click', e => { e.stopPropagation(); document.getElementById('langSwitcher').classList.toggle('open'); }); document.addEventListener('click', () => { document.getElementById('langSwitcher').classList.remove('open'); }); document.getElementById('langDropdown').addEventListener('click', e => e.stopPropagation()); /* ---- Mouse parallax hero ---- */ const heroVisual = document.querySelector('.hero-visual'); if (heroVisual) { document.addEventListener('mousemove', e => { const xR = (e.clientX / window.innerWidth - 0.5) * 10; const yR = (e.clientY / window.innerHeight - 0.5) * 6; heroVisual.style.transform = `perspective(900px) rotateY(${xR*.3}deg) rotateX(${-yR*.3}deg)`; }); } /* ================================================================ MY PROFILE PAGE ================================================================ */ let profileData = {}; let editMode = false; const PROFILE_FIELDS = [ { disp: 'dispFullName', edit: 'editFullName' }, { disp: 'dispRole', edit: 'editRole' }, { disp: 'dispCity', edit: 'editCity' }, { disp: 'dispBio', edit: 'editBio' }, { disp: 'dispProject', edit: 'editProject' }, { disp: 'dispSector', edit: 'editSector' }, { disp: 'dispStage', edit: 'editStage' }, { disp: 'dispLooking', edit: 'editLooking' }, { disp: 'dispSkills', edit: 'editSkills' }, { disp: 'dispAvail', edit: 'editAvail' }, { disp: 'dispContactEmail', edit: 'editContactEmail' }, { disp: 'dispPhone', edit: 'editPhone' }, { disp: 'dispLinkedin', edit: 'editLinkedin' }, ]; function openMyProfile() { // Load saved profile from sessionStorage try { const saved = sessionStorage.getItem('cofyndo_profile'); if (saved) profileData = JSON.parse(saved); } catch(e){} // Pre-fill from currentUser if fields empty if (!profileData.dispFullName && currentUser?.name) profileData.dispFullName = currentUser.name; if (!profileData.dispContactEmail && currentUser?.email) profileData.dispContactEmail = currentUser.email; // Update header const name = profileData.dispFullName || currentUser?.name || 'Utilisateur'; const email = profileData.dispContactEmail || currentUser?.email || ''; document.getElementById('mypAvatar').textContent = name.charAt(0).toUpperCase(); document.getElementById('mypName').textContent = name; document.getElementById('mypEmail').textContent = email; // Render display values PROFILE_FIELDS.forEach(({ disp, edit }) => { const val = profileData[disp] || ''; renderField(disp, val); const inp = document.getElementById(edit); if (inp) inp.value = val; }); openPage('myProfilePage'); } function renderField(dispId, val) { const el = document.getElementById(dispId); if (!el) return; if (dispId === 'dispSkills') { if (val) { el.innerHTML = val.split(',').map(s => s.trim()).filter(Boolean) .map(s => `${s}`).join(''); } else { el.innerHTML = 'Non renseigné'; } } else { el.textContent = val || '—'; el.style.color = val ? '' : 'var(--text-dim)'; el.style.fontStyle = val ? '' : 'italic'; } } function toggleEditMode() { editMode = true; document.querySelector('.myp-edit-btn').textContent = '👁️ Aperçu'; document.querySelector('.myp-edit-btn').onclick = cancelEditMode; document.getElementById('mypActions').style.display = 'flex'; PROFILE_FIELDS.forEach(({ disp, edit }) => { const d = document.getElementById(disp); const i = document.getElementById(edit); if (d) d.style.display = 'none'; if (i) i.style.display = ''; }); } function cancelEditMode() { editMode = false; document.querySelector('.myp-edit-btn').textContent = '✏️ Modifier le profil'; document.querySelector('.myp-edit-btn').onclick = toggleEditMode; document.getElementById('mypActions').style.display = 'none'; PROFILE_FIELDS.forEach(({ disp, edit }) => { const d = document.getElementById(disp); const i = document.getElementById(edit); if (d) d.style.display = ''; if (i) { i.style.display = 'none'; i.value = profileData[disp] || ''; } }); } function saveProfile() { PROFILE_FIELDS.forEach(({ disp, edit }) => { const inp = document.getElementById(edit); if (inp) profileData[disp] = inp.value.trim(); }); try { sessionStorage.setItem('cofyndo_profile', JSON.stringify(profileData)); } catch(e){} // Update nav name & avatar const newName = profileData.dispFullName || currentUser?.name || 'Utilisateur'; if (currentUser) { currentUser.name = newName; } document.getElementById('navUserAvatar').textContent = newName.charAt(0).toUpperCase(); document.getElementById('navUserName').textContent = newName; document.getElementById('udName').textContent = newName; // Refresh display cancelEditMode(); openMyProfile(); showToast('✅ Profil enregistré avec succès !', 'rgba(62,255,199,.3)'); } /* ================================================================ CHECKOUT — full purchase flow ================================================================ */ const PLANS = { Starter: { price: 0, label: 'Starter', color: 'rgba(255,255,255,.1)', features: ['Profil fondateur créé', '2 demandes de matching/mois', 'Accès à la communauté'], payLabel: 'Activer gratuitement →' }, Basic: { price: 20, label: 'Basic', color: 'rgba(91,78,248,.2)', features: ['20 demandes de matching/mois', 'Scores de compatibilité IA', 'Messagerie + partage de fichiers', 'Événements communautaires'], payLabel: 'Payer 20 dt / mois →' }, Pro: { price: 40, label: 'Pro', color: 'rgba(91,78,248,.3)', features: ['Demandes illimitées', 'Accès Chat Global 🌍', 'Intros vidéo & appels', 'Templates juridiques', 'Support prioritaire'], payLabel: 'Démarrer essai Pro gratuit →' }, Team: { price: 50, label: 'Team', color: 'rgba(62,255,199,.15)', features: ['Tout le plan Pro', 'Jusqu\'à 5 membres d\'équipe', 'Accès intros investisseurs', 'Conseiller dédié', 'Tableau de projet personnalisé'], payLabel: 'Démarrer essai Team gratuit →' } }; let currentPlan = null; let currentTab = 'card'; function startPurchase(planName, price) { // If not logged in → go to signup first, then come back if (!currentUser) { handleSignup_mode(); openPage('signupPage'); // Store intent sessionStorage.setItem('pendingPlan', planName); return; } // Already on this plan const activePlan = currentUser.plan || 'Starter'; if (activePlan === planName && planName !== 'Starter') { showToast(`✅ Vous êtes déjà sur le plan ${planName} !`, 'rgba(62,255,199,.25)'); return; } currentPlan = PLANS[planName]; if (!currentPlan) return; // Reset to step 1 document.getElementById('checkoutStep1').style.display = 'block'; document.getElementById('checkoutStep2').style.display = 'none'; document.getElementById('checkoutError').style.display = 'none'; // Fill plan info document.getElementById('checkoutPlanBadge').textContent = `Plan ${planName}`; document.getElementById('checkoutTitle') && (document.getElementById('checkoutTitle').textContent = 'Finaliser votre abonnement'); document.getElementById('csPlan').textContent = planName; document.getElementById('checkoutSub').textContent = price === 0 ? 'Gratuit — aucune carte requise' : `${price} dt / mois — Essai gratuit 14 jours, sans engagement`; const totalEl = document.getElementById('csTotal'); if (price === 0) { totalEl.innerHTML = `Gratuit`; } else { totalEl.innerHTML = `${price} dt après 14 jours d'essai`; } document.getElementById('checkoutPayLabel').textContent = currentPlan.payLabel; document.getElementById('virementRef').textContent = `COFYNDO-${planName.toUpperCase()}-${Math.random().toString(36).slice(2,6).toUpperCase()}`; document.getElementById('virementAmount').textContent = price === 0 ? 'Gratuit' : `${price} dt/mois`; document.getElementById('konnectAmount').textContent = price === 0 ? 'Gratuit' : `${price} dt`; // Hide card tab for free plan document.getElementById('tabCard').style.display = price === 0 ? 'none' : ''; document.getElementById('tabVirement').style.display = price === 0 ? 'none' : ''; document.getElementById('tabKonnect').style.display = price === 0 ? 'none' : ''; if (price === 0) { document.getElementById('payCard').style.display = 'none'; document.getElementById('payVirement').style.display = 'none'; document.getElementById('payKonnect').style.display = 'none'; } else { switchTab('card'); } // Close other overlays closePage('pricingPage'); document.getElementById('checkoutOverlay').classList.add('open'); } function closeCheckout() { document.getElementById('checkoutOverlay').classList.remove('open'); } function switchTab(tab) { currentTab = tab; ['card','virement','konnect'].forEach(t => { document.getElementById('tab' + t.charAt(0).toUpperCase() + t.slice(1))?.classList.toggle('active', t === tab); document.getElementById('pay' + t.charAt(0).toUpperCase() + t.slice(1)).style.display = t === tab ? '' : 'none'; }); } function formatCard(input) { let v = input.value.replace(/\D/g, '').slice(0,16); input.value = v.replace(/(.{4})/g, '$1 ').trim(); const icon = document.getElementById('cardTypeIcon'); if (v.startsWith('4')) icon.textContent = '💙'; // Visa else if (v.startsWith('5')) icon.textContent = '🟠'; // Mastercard else icon.textContent = '💳'; } function formatExpiry(input) { let v = input.value.replace(/\D/g,'').slice(0,4); if (v.length >= 2) v = v.slice(0,2) + '/' + v.slice(2); input.value = v; } async function processPayment() { const btn = document.getElementById('checkoutPayBtn'); const errEl = document.getElementById('checkoutError'); errEl.style.display = 'none'; // ── Free plan — no payment needed ── if (currentPlan.price === 0) { activatePlan('Starter'); return; } // ── Virement mode ── if (currentTab === 'virement') { btn.disabled = true; btn.innerHTML = '⏳ Confirmation envoyée…'; setTimeout(() => { btn.disabled = false; btn.innerHTML = currentPlan.payLabel; showSuccessStep('Virement'); }, 1500); return; } // ── Konnect mode ── if (currentTab === 'konnect') { btn.disabled = true; btn.innerHTML = '↗️ Redirection vers Konnect…'; // In production: redirect to Konnect payment link // window.location.href = `https://app.konnect.network/payment/...`; setTimeout(() => { btn.disabled = false; btn.innerHTML = currentPlan.payLabel; // Simulate success for demo activatePlan(currentPlan.label); }, 1800); return; } // ── Card payment validation ── const name = document.getElementById('cardName').value.trim(); const number = document.getElementById('cardNumber').value.replace(/\s/g,''); const expiry = document.getElementById('cardExpiry').value.trim(); const cvv = document.getElementById('cardCvv').value.trim(); if (!name) return showCheckoutError('Veuillez entrer le nom du titulaire.'); if (number.length < 16) return showCheckoutError('Numéro de carte invalide.'); if (!/^\d{2}\/\d{2}$/.test(expiry)) return showCheckoutError('Date d\'expiration invalide (MM/AA).'); if (cvv.length < 3) return showCheckoutError('CVV invalide.'); // Validate expiry not in past const [mm, yy] = expiry.split('/').map(Number); const now = new Date(); if (mm < 1 || mm > 12 || yy + 2000 < now.getFullYear() || (yy + 2000 === now.getFullYear() && mm < now.getMonth() + 1)) { return showCheckoutError('Carte expirée. Veuillez utiliser une carte valide.'); } btn.disabled = true; btn.innerHTML = ' Traitement en cours…'; // Simulate payment processing (replace with real gateway: Stripe, Konnect, etc.) await new Promise(r => setTimeout(r, 2000)); btn.disabled = false; btn.innerHTML = currentPlan.payLabel; activatePlan(currentPlan.label); } function showCheckoutError(msg) { const errEl = document.getElementById('checkoutError'); errEl.textContent = '⚠️ ' + msg; errEl.style.display = 'block'; errEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function activatePlan(planName) { // Update user plan if (currentUser) { currentUser.plan = planName; try { sessionStorage.setItem('cofyndo_user', JSON.stringify(currentUser)); } catch(e){} } // Update plan badge in nav const planBadge = document.querySelector('.myp-badge.plan'); if (planBadge) planBadge.textContent = `⚡ Plan ${planName}`; showSuccessStep(planName === 'Starter' ? null : 'card'); } function showSuccessStep(method) { document.getElementById('checkoutStep1').style.display = 'none'; document.getElementById('checkoutStep2').style.display = 'block'; const planName = currentPlan.label; const price = currentPlan.price; document.getElementById('successMsg').textContent = price === 0 ? `Votre plan ${planName} est activé. Bonne exploration !` : method === 'Virement' ? `Votre demande est enregistrée. Activation sous 24h après réception du virement.` : `Votre plan ${planName} est actif. Profitez de toutes les fonctionnalités !`; const featEl = document.getElementById('successFeatures'); featEl.innerHTML = currentPlan.features.map(f => `
${f}
`).join(''); // Toast showToast(`🎉 Plan ${planName} activé !`, 'rgba(62,255,199,.3)'); // Update upgrade button in dropdown const upBtn = document.querySelector('.ud-item:nth-child(3)'); if (upBtn && planName !== 'Starter') upBtn.textContent = `⚡ Plan actuel : ${planName}`; } // Check pending plan after login function checkPendingPlan() { try { const pending = sessionStorage.getItem('pendingPlan'); if (pending && currentUser) { sessionStorage.removeItem('pendingPlan'); setTimeout(() => startPurchase(pending, PLANS[pending]?.price || 0), 600); } } catch(e){} } /* ---- Restore session on page loa

Hello world!

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

Commentaires

Une réponse à “Hello world!”

  1. Avatar de A WordPress Commenter

    Hi, this is a comment.
    To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
    Commenter avatars come from Gravatar.

Répondre à A WordPress Commenter Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Plus de publications