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}
Connect
Voir le profil
`).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 retourparmi 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 lesbonnes 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 lancementen 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 launchin 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 Launchin 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 lanzamientoen 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 lancioin 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!
Laisser un commentaire