<?xml version="1.0" encoding="UTF-8" ?>On passe au html du thème
<!DOCTYPE html>
<html b:version='2' class='v2' expr:dir='data:blog.languageDirection' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
<head>
<meta charset='UTF-8'/>
<meta content='width=device-width, initial-scale=1' name='viewport'/>
<title><data:blog.pageTitle/></title>
<b:skin><![CDATA[ ]]></b:skin>
</head>
<body>
<div id='site-header'>
<div>
<h1 id='site-title'>💬 ForumFrance</h1>
<p id='header-sub'>Chargement…</p>
</div>
<div id='header-user'></div>
</div>
<div id='forum-root'></div>
<div id='site-footer'>ForumFrance © 2025</div>
<script type='module'>
//<![CDATA[
// ── CSS injecte via JS ──────────────────────────────────────────
const st = document.createElement('style');
st.textContent = `
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,sans-serif;background:#f4f4f8;min-height:100vh}
#site-header{background:linear-gradient(135deg,#7F77DD,#D4537E 50%,#BA7517);padding:1.2rem 1.5rem;color:#fff;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:100;box-shadow:0 2px 12px rgba(0,0,0,.15)}
#site-title{font-size:22px;font-weight:700}
#header-sub{font-size:12px;opacity:.8;margin-top:2px}
#header-user{display:flex;align-items:center;gap:10px}
#forum-root{max-width:780px;margin:0 auto;padding:1.5rem 1rem 3rem}
#site-footer{text-align:center;color:#bbb;font-size:12px;padding:2rem 0 1rem}
.fc{background:#fff;border-radius:14px;border:1px solid #ececec;padding:16px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.04)}
.fi,.fta,.fse{width:100%;padding:9px 12px;border-radius:10px;border:1.5px solid #e0e0e0;font-size:14px;outline:none;font-family:inherit;background:#fff}
.fta{resize:vertical}
.fb{padding:9px 20px;border-radius:10px;border:none;font-weight:700;font-size:14px;cursor
.fbg{background:linear-gradient(90deg,#7F77DD,#D4537E);color:#fff}
.fbo{background:#f0f0f0;color:#666}
.fbsm{padding:5px 14px;font-size:13px}
.fbgl{background:#fff;color:#333;border:1.5px solid #ddd;display:flex;align-items:center;gap:8px;justify-content:center;width:100%;margin-top:10px;padding:9px 20px;border-radius:10px;font-weight:700;font-size:14px;cursor
.ft{padding:8px 20px;border-radius:10px;border:none;font-weight:700;font-size:14px;cursor
.ft.on{background:#7F77DD;color:#fff}
.ft.off{background:#f0f0f0;color:#888}
.fcb{padding:5px 14px;border-radius:20px;font-size:13px;cursor
.fcb.on{font-weight:700}
.ftag{display:inline-block;padding:2px 10px;border-radius:20px;font-size:11px;font-weight:600;margin-right:6px}
.fpin{display:inline-flex;align-items:center;gap:4px;background:#FAEEDA;color:#BA7517;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;margin-right:6px}
.fav{border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;flex-shrink:0;overflow:hidden}
.fav img{width:100%;height:100%;object-fit:cover}
.flk{display:flex;align-items:center;gap:4px;padding:4px 12px;border-radius:20px;border:1.5px solid #eee;background:#fafafa;color:#aaa;font-size:13px;font-weight:600;cursor
.flk.on{border-color:#D4537E;background:#FBEAF0;color:#D4537E}
.ferr{color:#c0392b;font-size:13px;margin-top:6px}
.fsh{background:#EEEDFE;border-radius:10px;padding:10px 12px;font-size:12px;color:#7F77DD;margin-bottom:12px}
.flbl{font-size:13px;font-weight:600;color:#555;display:block;margin-bottom:5px;margin-top:10px}
.fmu{color:#aaa;font-size:12px}
.frw{display:flex;align-items:center;gap:8px}
.fbk{background:none;border:none;color:#7F77DD;font-weight:700;font-size:14px;cursor
.ftb{display:flex;gap:8px;align-items:center;margin-bottom:12px}
.fcts{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.fst{background:#fafafa;border-radius:10px;padding:12px;text-align:center}
.fg2{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:16px}
.faw{max-width:420px;margin:2rem auto}
.tpc:hover{box-shadow:0 4px 16px rgba(127,119,221,.12);border-color:#d0cef8;transition:all .15s}
`;
document.head.appendChild(st);
// ── Firebase ────────────────────────────────────────────────────
import { initializeApp }
from "https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js";
import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword,
signInWithPopup, GoogleAuthProvider, signOut, onAuthStateChanged,
updateProfile }
from "https://www.gstatic.com/firebasejs/10.12.0/firebase-auth.js";
import { getFirestore, collection, addDoc, doc, updateDoc, increment,
query, orderBy, onSnapshot, serverTimestamp, getDoc, setDoc, getDocs }
from "https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js";
const firebaseConfig = {
apiKey: "AIzaSyAGAlQFAO2z1K5iQA5h6JHPeX_zaMOkb5Q",
authDomain: "forumfrance-6de67.firebaseapp.com",
projectId: "forumfrance-6de67",
storageBucket: "forumfrance-6de67.firebasestorage.app",
messagingSenderId: "246371695638",
appId: "1:246371695638:web:f48296c3cb65953cf88937"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const gProvider = new GoogleAuthProvider();
// ── Categories ──────────────────────────────────────────────────
const CATS = [
{ id:"all", label:"Tout", color:"#7F77DD", bg:"#EEEDFE" },
{ id:"actu", label:"Actualite", color:"#185FA5", bg:"#E6F1FB" },
{ id:"culture", label:"Culture", color:"#D4537E", bg:"#FBEAF0" },
{ id:"tech", label:"Tech", color:"#0F6E56", bg:"#E1F5EE" },
{ id:"humour", label:"Humour", color:"#BA7517", bg:"#FAEEDA" },
{ id:"sport", label:"Sport", color:"#A32D2D", bg:"#FCEBEB" },
];
const catOf = id => CATS.find(c => c.id === id) || CATS[0];
const ts2str = ts => {
if (!ts) return "";
const d = ts.toDate ? ts.toDate() : new Date(ts);
return d.toLocaleDateString("fr-FR", { day:"2-digit", month:"short", hour:"2-digit", minute:"2-digit" });
};
// ── State ───────────────────────────────────────────────────────
let S = {
screen:"loading", authTab:"login", user:null,
topics:[], replies:{}, liked:{},
activeCat:"all", search:"", view:"list",
activeTopic:null, activeAuthor:null, showForm:false,
lEmail:"", lPass:"",
rPseudo:"", rEmail:"", rPass:"", rPass2:"",
nTitle:"", nContent:"", nCat:"actu", replyText:"",
error:"", loading:false,
};
const root = document.getElementById("forum-root");
const hSub = document.getElementById("header-sub");
const hUser = document.getElementById("header-user");
function setState(patch, skip) {
S = Object.assign({}, S, patch);
if (!skip) render();
}
// ── Avatar ──────────────────────────────────────────────────────
function av(user, size) {
size = size || 36;
if (user && user.photoURL)
return '<div class="fav" style="width:' + size + 'px;height:' + size + 'px"><img src="' + user.photoURL + '" referrerpolicy="no-referrer"></div>';
const colors = ["#7F77DD","#D4537E","#0F6E56","#BA7517","#185FA5","#A32D2D"];
const bgs = ["#EEEDFE","#FBEAF0","#E1F5EE","#FAEEDA","#E6F1FB","#FCEBEB"];
const name = (user && (user.displayName || user.pseudo)) || "?";
const idx = name.charCodeAt(0) % colors.length;
return '<div class="fav" style="width:' + size + 'px;height:' + size + 'px;background:' + bgs[idx] + ';color:' + colors[idx] + ';font-size:' + (size > 40 ? 20 : 14) + 'px">' + name[0].toUpperCase() + '</div>';
}
// ── Render ──────────────────────────────────────────────────────
function render() {
updateHeader();
if (S.screen === "loading") { root.innerHTML = '<div style="text-align:center;padding:60px 0;color:#aaa"><div style="font-size:40px">💬</div><p style="margin-top:12px">Chargement…</p></div>'; return; }
if (S.screen === "auth") { renderAuth(); return; }
if (S.view === "list") { renderList(); return; }
if (S.view === "topic") { renderTopic(); return; }
if (S.view === "profile") { renderProfile(); return; }
}
function updateHeader() {
if (!S.user) { hSub.textContent = "Connectez-vous pour participer"; hUser.innerHTML = ""; return; }
hSub.textContent = S.topics.length + " sujets · Bonjour, " + (S.user.displayName || "?") + " !";
hUser.innerHTML = '<div id="hav" style="cursor
document.getElementById("hav")?.addEventListener("click", () => setState({ view:"profile", activeAuthor:S.user.uid }));
document.getElementById("hlo")?.addEventListener("click", () => signOut(auth));
}
// ── Google icon ─────────────────────────────────────────────────
function gico() {
return '<svg width="18" height="18" viewBox="0 0 48 48"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/></svg>';
}
// ── AUTH ────────────────────────────────────────────────────────
function renderAuth() {
const login = S.authTab === "login";
root.innerHTML =
'<div class="faw"><div class="fc">' +
'<div class="frw" style="margin-bottom:20px;gap:8px">' +
'<button class="ft ' + (login?"on":"off") + '" id="tl">Connexion</button>' +
'<button class="ft ' + (!login?"on":"off") + '" id="tr">Inscription</button>' +
'</div>' +
(login ?
'<label class="flbl">Email</label>' +
'<input class="fi" id="le" type="email" placeholder="vous@exemple.com" value="' + S.lEmail + '">' +
'<label class="flbl">Mot de passe</label>' +
'<input class="fi" id="lp" type="password" placeholder="••••••" value="' + S.lPass + '">' +
(S.error ? '<div class="ferr">⚠ ' + S.error + '</div>' : '') +
'<button class="fb fbg" id="bli" style="width:100%;margin-top:14px">' + (S.loading?"Connexion…":"Se connecter") + '</button>' +
'<button class="fbgl" id="bgl">' + gico() + ' Continuer avec Google</button>' +
'<p style="text-align:center;margin-top:12px;font-size:13px;color:#aaa">Pas de compte ? <a href="#" id="gor" style="color:#7F77DD;font-weight:600">S\'inscrire</a></p>'
:
'<label class="flbl">Pseudo (3-20 car.)</label>' +
'<input class="fi" id="rp" placeholder="MonPseudo" value="' + S.rPseudo + '">' +
'<label class="flbl">Email</label>' +
'<input class="fi" id="re" type="email" placeholder="vous@exemple.com" value="' + S.rEmail + '">' +
'<label class="flbl">Mot de passe (6 car. min.)</label>' +
'<input class="fi" id="rpa" type="password" placeholder="••••••" value="' + S.rPass + '">' +
'<label class="flbl">Confirmer</label>' +
'<input class="fi" id="rp2" type="password" placeholder="••••••" value="' + S.rPass2 + '">' +
'<div class="fsh" style="margin-top:12px">🛡 Protection anti-abus activee.</div>' +
(S.error ? '<div class="ferr">⚠ ' + S.error + '</div>' : '') +
'<button class="fb fbg" id="brg" style="width:100%;margin-top:10px">' + (S.loading?"Inscription…":"Creer mon compte") + '</button>' +
'<button class="fbgl" id="bgr">' + gico() + ' Continuer avec Google</button>' +
'<p style="text-align:center;margin-top:12px;font-size:13px;color:#aaa">Deja membre ? <a href="#" id="gol" style="color:#7F77DD;font-weight:600">Se connecter</a></p>'
) +
'</div></div>';
document.getElementById("tl")?.addEventListener("click", () => setState({ authTab:"login", error:"" }));
document.getElementById("tr")?.addEventListener("click", () => setState({ authTab:"register", error:"" }));
document.getElementById("gol")?.addEventListener("click", e => { e.preventDefault(); setState({ authTab:"login", error:"" }); });
document.getElementById("gor")?.addEventListener("click", e => { e.preventDefault(); setState({ authTab:"register", error:"" }); });
document.getElementById("bli")?.addEventListener("click", doLogin);
document.getElementById("brg")?.addEventListener("click", doRegister);
document.getElementById("bgl")?.addEventListener("click", doGoogle);
document.getElementById("bgr")?.addEventListener("click", doGoogle);
const bind = (id, key) => document.getElementById(id)?.addEventListener("input", e => setState({ [key]: e.target.value }, true));
bind("le","lEmail"); bind("lp","lPass");
bind("rp","rPseudo"); bind("re","rEmail"); bind("rpa","rPass"); bind("rp2","rPass2");
}
// ── Auth actions ────────────────────────────────────────────────
function valPseudo(p) {
if (!p || p.length < 3 || p.length > 20) return "Pseudo : 3 a 20 caracteres.";
if (!/^[a-zA-Z0-9_\-]+$/.test(p)) return "Caracteres invalides.";
if (["admin","moderateur","root","system"].some(w => p.toLowerCase().includes(w))) return "Pseudo reserve.";
return null;
}
async function doLogin() {
const email = document.getElementById("le")?.value.trim();
const pass = document.getElementById("lp")?.value;
if (!email || !pass) { setState({ error:"Remplissez tous les champs." }); return; }
setState({ loading:true, error:"" });
try {
await signInWithEmailAndPassword(auth, email, pass);
} catch(e) {
const msg = e.code === "auth/invalid-credential" ? "Email ou mot de passe incorrect."
: e.code === "auth/too-many-requests" ? "Trop de tentatives. Reessayez plus tard."
: e.message;
setState({ loading:false, error:msg });
}
}
async function doRegister() {
const pseudo = document.getElementById("rp")?.value.trim();
const email = document.getElementById("re")?.value.trim();
const pass = document.getElementById("rpa")?.value;
const pass2 = document.getElementById("rp2")?.value;
const err = valPseudo(pseudo);
if (err) { setState({ error:err }); return; }
if (!email) { setState({ error:"Email requis." }); return; }
if (pass.length < 6){ setState({ error:"Mot de passe : 6 caracteres minimum." }); return; }
if (pass !== pass2) { setState({ error:"Les mots de passe ne correspondent pas." }); return; }
setState({ loading:true, error:"" });
try {
const cred = await createUserWithEmailAndPassword(auth, email, pass);
await updateProfile(cred.user, { displayName: pseudo });
await cred.user.getIdToken(true);
await setDoc(doc(db, "users", cred.user.uid), {
pseudo, email,
joined: new Date().toLocaleDateString("fr-FR", { month:"long", year:"numeric" }),
posts:0, role:"member",
});
} catch(e) {
const msg = e.code === "auth/email-already-in-use" ? "Cet email est deja utilise." : e.message;
setState({ loading:false, error:msg });
}
}
async function doGoogle() {
try {
const result = await signInWithPopup(auth, gProvider);
const u = result.user;
const ref = doc(db, "users", u.uid);
const snap = await getDoc(ref);
if (!snap.exists()) {
await setDoc(ref, {
pseudo: u.displayName || "Utilisateur", email: u.email,
joined: new Date().toLocaleDateString("fr-FR", { month:"long", year:"numeric" }),
posts:0, role:"member",
});
}
} catch(e) {
setState({ error:"Connexion Google annulee ou bloquee." });
}
}
// ── LIST ────────────────────────────────────────────────────────
function renderList() {
const filtered = S.topics
.filter(t => (S.activeCat === "all" || t.category === S.activeCat) && (!S.search || t.title.toLowerCase().includes(S.search.toLowerCase())))
.sort((a,b) => (b.pinned?1:0) - (a.pinned?1:0));
let thtml = filtered.length === 0
? '<p style="text-align:center;color:#bbb;padding:2rem 0">Aucun sujet trouve.</p>'
: filtered.map(function(t) {
const cat = catOf(t.category);
return '<div class="fc tpc" data-id="' + t.id + '" style="cursor
'<div class="frw" style="align-items:flex-start;gap:10px">' +
'<div class="alnk" data-uid="' + t.authorId + '" style="cursor
'<div style="flex:1;min-width:0">' +
(t.pinned ? '<span class="fpin">📌</span>' : '') +
'<span class="ftag" style="background:' + cat.bg + ';color:' + cat.color + '">' + cat.label + '</span>' +
'<div style="font-size:15px;font-weight:600;color:#222;margin:4px 0 2px;line-height:1.3">' + t.title + '</div>' +
'<div class="fmu">par <b style="color:#888;cursor
'</div></div>' +
'<div class="frw" style="margin-top:10px">' +
'<button class="flk' + (S.liked[t.id]?" on":"") + ' lkb" data-id="' + t.id + '">❤ ' + (t.likes||0) + '</button>' +
'<span class="fmu" style="margin-left:8px">💬 ' + (t.replyCount||0) + '</span>' +
'</div></div>';
}).join("");
let form = "";
if (S.showForm) {
form = '<div class="fc" style="border:1.5px solid #e0e0e0;margin-bottom:16px">' +
'<div style="font-size:16px;font-weight:700;color:#333;margin-bottom:10px">Creer un sujet</div>' +
'<label class="flbl">Titre</label><input class="fi" id="nt" placeholder="Titre du sujet" value="' + S.nTitle + '">' +
'<label class="flbl">Categorie</label>' +
'<select class="fse" id="nc" style="margin-top:5px">' +
CATS.filter(c => c.id !== "all").map(c => '<option value="' + c.id + '"' + (c.id === S.nCat?" selected":"") + '>' + c.label + '</option>').join("") +
'</select>' +
'<label class="flbl">Contenu</label><textarea class="fta" id="nco" rows="4" placeholder="Decrivez votre sujet">' + S.nContent + '</textarea>' +
'<div class="frw" style="margin-top:10px;gap:8px">' +
'<button class="fb fbg" id="bpt">Publier</button>' +
'<button class="fb fbo" id="bcl">Annuler</button>' +
'</div></div>';
}
root.innerHTML =
'<div class="ftb">' +
'<input class="fi" id="sr" placeholder="🔍 Rechercher" value="' + S.search + '" style="flex:1">' +
'<button class="fb fbg" id="bnt">+ Nouveau sujet</button>' +
'</div>' +
form +
'<div class="fcts">' +
CATS.map(function(c) {
const on = S.activeCat === c.id;
return '<button class="fcb' + (on?" on":"") + '" data-cat="' + c.id + '" style="' + (on?"border-color:"+c.color+";background:"+c.bg+";color:"+c.color+";font-weight:700":"") + '">' + c.label + '</button>';
}).join("") +
'</div>' + thtml;
document.getElementById("sr")?.addEventListener("input", e => setState({ search:e.target.value }));
document.getElementById("bnt")?.addEventListener("click", () => setState({ showForm:!S.showForm }));
document.querySelectorAll(".fcb").forEach(b => b.addEventListener("click", () => setState({ activeCat:b.dataset.cat })));
document.querySelectorAll(".tpc").forEach(el => el.addEventListener("click", function() {
const t = S.topics.find(x => x.id === el.dataset.id);
if (t) setState({ view:"topic", activeTopic:t });
}));
document.querySelectorAll(".alnk").forEach(el => el.addEventListener("click", function(e) {
e.stopPropagation();
setState({ view:"profile", activeAuthor:el.dataset.uid });
}));
document.querySelectorAll(".lkb").forEach(btn => btn.addEventListener("click", async function(e) {
e.stopPropagation();
const id = btn.dataset.id;
const on = !S.liked[id];
const liked = Object.assign({}, S.liked);
liked[id] = on;
setState({ liked:liked });
await updateDoc(doc(db,"topics",id), { likes:increment(on?1:-1) });
}));
if (S.showForm) {
document.getElementById("nt")?.addEventListener("input", e => setState({ nTitle:e.target.value }, true));
document.getElementById("nco")?.addEventListener("input", e => setState({ nContent:e.target.value }, true));
document.getElementById("nc")?.addEventListener("change", e => setState({ nCat:e.target.value }, true));
document.getElementById("bcl")?.addEventListener("click", () => setState({ showForm:false }));
document.getElementById("bpt")?.addEventListener("click", doPost);
}
}
async function doPost() {
const title = document.getElementById("nt")?.value.trim();
const content = document.getElementById("nco")?.value.trim();
const cat = document.getElementById("nc")?.value;
if (!title || !content) return;
const u = S.user;
await addDoc(collection(db,"topics"), {
title, content, category:cat,
author:u.displayName||"Anonyme", authorId:u.uid,
photoURL:u.photoURL||null,
likes:0, replyCount:0, pinned:false,
createdAt:serverTimestamp(),
});
setState({ showForm:false, nTitle:"", nContent:"", nCat:"actu" });
}
// ── TOPIC ───────────────────────────────────────────────────────
function renderTopic() {
const t = S.activeTopic;
if (!t) { setState({ view:"list" }); return; }
const cat = catOf(t.category);
const reps = S.replies[t.id] || [];
root.innerHTML =
'<button class="fbk" id="bk">← Retour</button>' +
'<div class="fc">' +
'<span class="ftag" style="background:' + cat.bg + ';color:' + cat.color + '">' + cat.label + '</span>' +
(t.pinned ? '<span class="fpin">📌 Epingle</span>' : '') +
'<div style="font-size:20px;font-weight:700;color:#222;margin:8px 0 6px">' + t.title + '</div>' +
'<div class="frw" style="margin-bottom:10px">' +
av({displayName:t.author, photoURL:t.photoURL}, 28) +
'<span class="fmu">par <b style="color:#555">' + t.author + '</b> · ' + ts2str(t.createdAt) + '</span>' +
'</div>' +
'<div style="font-size:15px;color:#444;line-height:1.6;margin-bottom:12px">' + t.content + '</div>' +
'<div class="frw">' +
'<button class="flk' + (S.liked[t.id]?" on":"") + '" id="tlk">❤ ' + (t.likes||0) + '</button>' +
'<span class="fmu" style="margin-left:8px">💬 ' + reps.length + ' reponse(s)</span>' +
'</div></div>' +
'<div style="font-size:14px;font-weight:700;color:#555;margin:12px 0 8px">Reponses (' + reps.length + ')</div>' +
(reps.length === 0 ? '<p style="color:#bbb;font-size:13px;margin-bottom:12px">Aucune reponse. Soyez le premier !</p>' : '') +
reps.map(function(r) {
return '<div class="fc" style="background:#fafafa">' +
'<div class="frw" style="margin-bottom:6px">' +
av({displayName:r.author, photoURL:r.photoURL}, 28) +
'<b style="font-size:13px;color:#555">' + r.author + '</b>' +
'<span class="fmu">· ' + ts2str(r.createdAt) + '</span>' +
'</div><div style="font-size:14px;color:#333;line-height:1.5">' + r.content + '</div></div>';
}).join("") +
'<div style="margin-top:14px">' +
'<textarea class="fta" id="rt" rows="3" placeholder="Ecrire une reponse">' + S.replyText + '</textarea>' +
'<button class="fb fbg" id="bry" style="margin-top:8px">Envoyer</button>' +
'</div>';
document.getElementById("bk")?.addEventListener("click", () => setState({ view:"list", activeTopic:null }));
document.getElementById("tlk")?.addEventListener("click", async function() {
const on = !S.liked[t.id];
const liked = Object.assign({}, S.liked);
liked[t.id] = on;
setState({ liked:liked });
await updateDoc(doc(db,"topics",t.id), { likes:increment(on?1:-1) });
});
document.getElementById("rt")?.addEventListener("input", e => setState({ replyText:e.target.value }, true));
document.getElementById("bry")?.addEventListener("click", doReply);
}
async function doReply() {
const content = document.getElementById("rt")?.value.trim();
if (!content) return;
const u = S.user;
const t = S.activeTopic;
await addDoc(collection(db,"topics",t.id,"replies"), {
content, author:u.displayName||"Anonyme", authorId:u.uid,
photoURL:u.photoURL||null, createdAt:serverTimestamp(),
});
await updateDoc(doc(db,"topics",t.id), { replyCount:increment(1) });
const snap = await getDocs(query(collection(db,"topics",t.id,"replies"), orderBy("createdAt")));
const reps = snap.docs.map(d => Object.assign({}, d.data(), { id:d.id }));
const replies = Object.assign({}, S.replies);
replies[t.id] = reps;
setState({ replies:replies, replyText:"" });
}
// ── PROFILE ─────────────────────────────────────────────────────
async function renderProfile() {
const uid = S.activeAuthor;
const isMe = uid === S.user?.uid;
let uData = { pseudo:S.user?.displayName||"?", joined:"?" };
try { const d = await getDoc(doc(db,"users",uid)); if (d.exists()) uData = d.data(); } catch(e) {}
const userTopics = S.topics.filter(t => t.authorId === uid);
root.innerHTML =
'<button class="fbk" id="bk">← Retour</button>' +
'<div class="fc" style="text-align:center;padding:24px">' +
'<div style="margin:0 auto 12px;width:64px">' + av({displayName:uData.pseudo, photoURL:isMe?S.user.photoURL:null}, 64) + '</div>' +
'<div style="font-size:20px;font-weight:700;color:#222">' + uData.pseudo + '</div>' +
(isMe ? '<div style="display:inline-block;padding:2px 10px;border-radius:20px;background:#EEEDFE;color:#7F77DD;font-size:12px;font-weight:700;margin-top:6px">👤 Vous</div>' : '') +
'<div class="fmu" style="margin-top:6px">Membre depuis ' + (uData.joined||"?") + '</div>' +
'<div class="fg2">' +
'<div class="fst"><b style="display:block;font-size:22px;font-weight:700;color:#7F77DD">' + userTopics.length + '</b><span class="fmu">Sujets</span></div>' +
'<div class="fst"><b style="display:block;font-size:22px;font-weight:700;color:#D4537E">' + userTopics.reduce(function(a,t){return a+(t.likes||0);},0) + '</b><span class="fmu">Likes recus</span></div>' +
'</div>' +
(isMe ? '<button class="fb fbo" id="blo" style="width:100%;margin-top:16px">Se deconnecter</button>' : '') +
(userTopics.length > 0 ?
'<div style="text-align:left;margin-top:18px">' +
'<div style="font-size:14px;font-weight:700;color:#555;margin-bottom:8px">Sujets de ' + uData.pseudo + '</div>' +
userTopics.map(function(t){
const cat = catOf(t.category);
return '<div class="fc tpc" data-id="' + t.id + '" style="cursor
'<span class="ftag" style="background:' + cat.bg + ';color:' + cat.color + '">' + cat.label + '</span>' +
'<div style="font-size:15px;font-weight:600;color:#222;margin-top:4px">' + t.title + '</div></div>';
}).join("") +
'</div>' : '') +
'</div>';
document.getElementById("bk")?.addEventListener("click", () => setState({ view:"list", activeAuthor:null }));
document.getElementById("blo")?.addEventListener("click", () => signOut(auth));
document.querySelectorAll(".tpc").forEach(el => el.addEventListener("click", function() {
const t = S.topics.find(x => x.id === el.dataset.id);
if (t) setState({ view:"topic", activeTopic:t });
}));
}
// ── Seed demo ───────────────────────────────────────────────────
async function seedDemo() {
const snap = await getDocs(collection(db,"topics"));
if (!snap.empty) return;
const DEMO = [
{ title:"Quels sont vos films preferes de 2025 ?", category:"culture", content:"Je cherche des recommandations pour mes soirees du week-end !", pinned:true, likes:47, replyCount:24 },
{ title:"L'IA va-t-elle remplacer les developpeurs ?", category:"tech", content:"Un debat qui revient souvent. Vos avis ?", pinned:false, likes:102, replyCount:58 },
{ title:"Blague du jour - partagez les votres !", category:"humour", content:"Pourquoi les plongeurs plongent-ils toujours en arriere ?", pinned:false, likes:213, replyCount:89 },
{ title:"Resultats de la Ligue des Champions", category:"sport", content:"Quelle soiree ! Vos reactions ?", pinned:false, likes:88, replyCount:132 },
{ title:"Elections europeennes - analyse et perspectives", category:"actu", content:"Un decryptage des resultats et de leurs implications.", pinned:false, likes:54, replyCount:76 },
];
for (let i = 0; i < DEMO.length; i++) {
await addDoc(collection(db,"topics"), Object.assign({}, DEMO, { author:"Equipe Forum", authorId:"demo", photoURL:null, createdAt:serverTimestamp() }));
}
}
// ── Firebase listener ───────────────────────────────────────────
onAuthStateChanged(auth, async function(user) {
if (user) {
setState({ user:user, screen:"forum" }, true);
await seedDemo();
onSnapshot(query(collection(db,"topics"), orderBy("createdAt","desc")), function(snap) {
const topics = snap.docs.map(d => Object.assign({}, d.data(), { id:d.id }));
setState({ topics:topics });
});
} else {
setState({ user:null, screen:"auth", view:"list", topics:[] });
}
});
//]]>
</script>
</body>
</html>
