Weblfg Games
Many WebLFG communities feature reputation systems or "karma" scores. If you ghost a group or intentionally throw a match, the community knows. This social contract forces players to behave better than anonymous matchmaking ever could.
Below is a fully functional front-end prototype simulating the "Create & Find LFG" feature using vanilla JavaScript. Data is stored in localStorage so it persists across page refreshes.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WebLFG · Find your squad</title> <style> * box-sizing: border-box; font-family: system-ui, -apple-system, 'Segoe UI', Roboto; body background: #0b1120; color: #e2e8f0; padding: 2rem; margin: 0; .container max-width: 1400px; margin: 0 auto;/* header & forms */ .hero margin-bottom: 2rem; .hero h1 font-size: 2.5rem; background: linear-gradient(135deg, #a855f7, #3b82f6); -webkit-background-clip: text; background-clip: text; color: transparent; .card background: #1e293b; border-radius: 1.5rem; padding: 1.5rem; box-shadow: 0 8px 20px rgba(0,0,0,0.3); margin-bottom: 2rem; .form-grid display: flex; flex-wrap: wrap; gap: 1rem; align-items: end; .field flex: 1; min-width: 150px; label display: block; font-size: 0.75rem; text-transform: uppercase; font-weight: bold; color: #94a3b8; margin-bottom: 0.25rem; input, select, textarea width: 100%; background: #0f172a; border: 1px solid #334155; color: white; padding: 0.6rem 1rem; border-radius: 2rem; outline: none; button background: #3b82f6; border: none; padding: 0.6rem 1.5rem; border-radius: 2rem; font-weight: bold; color: white; cursor: pointer; transition: 0.2s; button.danger background: #ef4444; button.secondary background: #334155; /* filters bar */ .filters display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; background: #0f172a; padding: 1rem; border-radius: 2rem; /* LFG grid */ .lfg-grid display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; .lfg-card background: #1e293b; border-radius: 1.25rem; padding: 1.2rem; border-left: 5px solid #3b82f6; transition: 0.1s; .lfg-card h3 margin: 0 0 0.25rem 0; display: flex; justify-content: space-between; .badge background: #0f172a; padding: 0.2rem 0.6rem; border-radius: 2rem; font-size: 0.7rem; font-weight: normal; .slots font-size: 0.85rem; color: #cbd5e1; margin: 0.5rem 0; .desc color: #94a3b8; font-size: 0.85rem; margin: 0.5rem 0; .footer-card display: flex; justify-content: space-between; align-items: center; margin-top: 1rem; .join-btn background: #10b981; padding: 0.3rem 1rem; font-size: 0.8rem; .copy-code font-family: monospace; background: #0f172a; padding: 0.2rem 0.6rem; border-radius: 0.5rem; font-size: 0.7rem; cursor: pointer; .expiry font-size: 0.7rem; color: #f97316; hr border-color: #334155; margin: 1rem 0; </style></head> <body> <div class="container"> <div class="hero"> <h1>🎮 weblfg · games</h1> <p>Find teammates instantly. No Discord required — just pure LFG.</p> </div> weblfg games
<!-- CREATE POST CARD --> <div class="card"> <h2>➕ Create LFG post</h2> <div class="form-grid"> <div class="field"><label>Game</label><select id="gameSelect"><option>Valorant</option><option>World of Warcraft</option><option>League of Legends</option><option>Apex Legends</option><option>Fortnite</option></select></div> <div class="field"><label>Title</label><input type="text" id="titleInput" placeholder="e.g., Need 2 for ranked"></div> <div class="field"><label>Region</label><select id="regionSelect"><option>NA</option><option>EU</option><option>Asia</option></select></div> <div class="field"><label>Mic required?</label><select id="micSelect"><option>No</option><option>Yes</option></select></div> <div class="field"><label>Max players</label><select id="maxMembers"><option>2</option><option selected>4</option><option>5</option><option>6</option></select></div> <div class="field"><label>Lobby code (optional)</label><input id="lobbyCode" placeholder="XXXX-XXXX"></div> </div> <div class="field" style="margin-top: 0.75rem;"><label>Description</label><textarea id="descInput" rows="2" placeholder="Chill games, 18+, etc..."></textarea></div> <div style="margin-top: 1rem;"><button id="publishBtn">🚀 Publish LFG</button></div> </div> <!-- FILTERS & FEED --> <div class="filters"> <select id="filterGame"><option value="all">All games</option><option>Valorant</option><option>World of Warcraft</option><option>League of Legends</option><option>Apex Legends</option><option>Fortnite</option></select> <select id="filterRegion"><option value="all">All regions</option><option>NA</option><option>EU</option><option>Asia</option></select> <select id="filterMic"><option value="all">Mic any</option><option value="true">Mic required</option><option value="false">No mic needed</option></select> <button id="refreshBtn" class="secondary">⟳ Refresh</button> <span style="flex:1; text-align:right; font-size:0.8rem;">⚡ Auto-delete after 30 min</span> </div> <div id="lfgContainer" class="lfg-grid"> <!-- dynamic cards will appear here --> <div style="text-align: center; grid-column: span 3;">Loading LFG posts...</div> </div></div>
<script> // --- STORAGE & STATE --- let posts = []; </div> <script> // --- STORAGE & STATE ---
// Load initial mock data if empty function loadPosts() const stored = localStorage.getItem("weblfg_posts"); if(stored) posts = JSON.parse(stored); // filter expired (>30 min) const now = Date.now(); posts = posts.filter(p => (now - p.createdAt) < 30 * 60 * 1000); savePosts(); else // seed some demo posts posts = [ id: "1", game: "Valorant", title: "Gold rank push", host: "ViperMain", current: 2, max: 5, micReq: true, region: "NA", desc: "need smokes and duelist", lobbyCode: "VAL2024", createdAt: Date.now() - 1000*60*5 , id: "2", game: "World of Warcraft", title: "M+ key farm", host: "Tankadin", current: 1, max: 5, micReq: true, region: "EU", desc: "RSham / any dps", lobbyCode: "WOWKEYS", createdAt: Date.now() - 1000*60*12 , id: "3", game: "Fortnite", title: "Zero Build trios", host: "FazeKnock", current: 2, max: 3, micReq: false, region: "NA", desc: "just have fun", lobbyCode: "", createdAt: Date.now() - 1000*60*20 ]; savePosts(); renderLFG(); function savePosts() localStorage.setItem("weblfg_posts", JSON.stringify(posts)); function addPost(post) posts.unshift(post); savePosts(); renderLFG(); function deletePost(id) posts = posts.filter(p => p.id !== id); savePosts(); renderLFG(); // Helper to get relative time function timeAgo(ms) let minutes = Math.floor((Date.now() - ms) / 60000); if(minutes < 1) return "just now"; if(minutes === 1) return "1 min ago"; return `$minutes min ago`; // copy to clipboard function copyCode(code) navigator.clipboard.writeText(code); alert(`🎉 Lobby code copied: $code`); // render with filters function renderLFG() const gameFilter = document.getElementById("filterGame").value; const regionFilter = document.getElementById("filterRegion").value; const micFilter = document.getElementById("filterMic").value; let filtered = [...posts]; if(gameFilter !== "all") filtered = filtered.filter(p => p.game === gameFilter); if(regionFilter !== "all") filtered = filtered.filter(p => p.region === regionFilter); if(micFilter !== "all") filtered = filtered.filter(p => p.micReq === (micFilter === "true")); const container = document.getElementById("lfgContainer"); if(filtered.length === 0) container.innerHTML = `<div style="grid-column: span 3; text-align:center;">😞 No LFG posts match filters. Create one!</div>`; return; container.innerHTML = filtered.map(post => const expiresInMin = Math.max(0, 30 - Math.floor((Date.now() - post.createdAt) / 60000)); return ` <div class="lfg-card"> <h3>$escapeHtml(post.title) <span class="badge">$post.game</span></h3> <div class="slots">👥 $post.current/$post.max players · 🎙️ $post.micReq ? "Mic required" : "Mic optional" · 🌍 $post.region</div> <div class="desc">$escapeHtml(post.desc.substring(0, 100))</div> <div class="footer-card"> <div><span style="font-size:0.7rem;">👤 $escapeHtml(post.host)</span><br> <span class="expiry">⏱️ expires in $expiresInMin min</span></div> <div> $post.lobbyCode ? `<button class="join-btn secondary" onclick="copyCode('$post.lobbyCode')">📋 Copy code</button>` : `<button class="join-btn" onclick="alert('🔗 Contact host: $post.host in-game or DM for invite.')">✉️ Request invite</button>` <button class="danger" style="margin-left:0.5rem; background:#991b1b; padding:0.3rem 0.8rem;" onclick="deletePost('$post.id')">🗑️</button> </div> </div> </div> `; ).join(""); function escapeHtml(str) return str.replace(/[&<>]/g, function(m)if(m==='&') return '&'; if(m==='<') return '<'; if(m==='>') return '>'; return m;); // create new LFG post document.getElementById("publishBtn").addEventListener("click", () => const game = document.getElementById("gameSelect").value; let title = document.getElementById("titleInput").value.trim(); if(title === "") title = `$game group`; const region = document.getElementById("regionSelect").value; const micReq = document.getElementById("micSelect").value === "Yes"; const maxMembers = parseInt(document.getElementById("maxMembers").value); const lobbyCode = document.getElementById("lobbyCode").value.trim(); const description = document.getElementById("descInput").value.trim() ); document.getElementById("refreshBtn").addEventListener("click", () => loadPosts(); ); // auto cleanup + rerender every 15 seconds setInterval(() => const now = Date.now(); const before = posts.length; posts = posts.filter(p => (now - p.createdAt) < 30 * 60 * 1000); if(posts.length !== before) savePosts(); renderLFG(); , 15000); window.copyCode = copyCode; window.deletePost = deletePost; loadPosts();
</script> </body> </html>
While you can use WebLFG for almost any multiplayer title, certain genres absolutely rely on it: