Skip to main content
Glama
Ripnrip

Quake Coding Arena MCP

by Ripnrip
widget.widget10.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Quake Coding Arena Chat Widget</title> <style> :root { font-family: 'Space Grotesk', 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, sans-serif; background: radial-gradient(circle at top, #1e1b4b, #08070f 55%); color: #f8fafc; min-height: 100%; } * { box-sizing: border-box; } body { margin: 0; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 40px 16px; background: radial-gradient(circle at top, #2f1f71, #07040f 60%); } .widget { width: min(960px, 100%); background: rgba(7, 6, 18, 0.9); border: 1px solid rgba(114, 85, 255, 0.4); border-radius: 18px; padding: 24px; backdrop-filter: blur(20px); box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6); display: flex; flex-direction: column; gap: 20px; } .widget__header { display: flex; justify-content: space-between; align-items: center; gap: 16px; } .widget__header h1 { margin: 0; font-size: clamp(1.5rem, 3vw, 2.3rem); letter-spacing: 1px; } .widget__status { padding: 6px 14px; border-radius: 999px; background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.4); font-size: 0.9rem; } .chat-log { min-height: 420px; max-height: 55vh; border: 1px solid rgba(255, 255, 255, 0.06); border-radius: 16px; padding: 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; background: rgba(12, 11, 27, 0.8); } .chat-bubble { padding: 12px 16px; border-radius: 14px; max-width: 80%; line-height: 1.4; animation: slide-in 0.2s ease; } .chat-bubble.user { align-self: flex-end; background: linear-gradient(135deg, #7c3aed, #a855f7); border-bottom-right-radius: 4px; } .chat-bubble.bot { align-self: flex-start; background: rgba(63, 63, 70, 0.6); border-bottom-left-radius: 4px; } .chat-bubble small { display: block; opacity: 0.7; font-size: 0.75rem; margin-bottom: 4px; } .chat-input { display: flex; gap: 10px; } .chat-input input { flex: 1; padding: 14px 16px; border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.12); background: rgba(15, 15, 29, 0.8); color: inherit; font-size: 1rem; } .chat-input button { padding: 0 28px; border-radius: 14px; border: none; background: linear-gradient(135deg, #f97316, #ef4444); color: white; font-weight: 600; cursor: pointer; } .sound-panel { border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 18px; background: rgba(18, 18, 36, 0.7); } .sound-panel h2 { margin-top: 0; margin-bottom: 12px; font-size: 1.2rem; } .sound-panel__controls { display: flex; flex-wrap: wrap; gap: 10px; } .sound-panel select, .sound-panel input { padding: 10px 12px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.12); background: rgba(10, 10, 22, 0.9); color: inherit; } .sound-panel button { padding: 10px 18px; border-radius: 12px; border: none; background: linear-gradient(135deg, #22c55e, #16a34a); color: white; font-weight: 600; cursor: pointer; } @keyframes slide-in { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @media (max-width: 720px) { body { padding: 24px 12px; } .chat-bubble { max-width: 100%; } .sound-panel__controls { flex-direction: column; align-items: stretch; } } </style> </head> <body> <main class="widget"> <header class="widget__header"> <div> <h1>Quake Coding Arena</h1> <p>ChatGPT replies + Quake announcements.</p> </div> <div class="widget__status" id="status">Ready</div> </header> <section class="chat-log" id="chat-log"></section> <form class="chat-input" id="chat-form"> <input id="message-input" type="text" placeholder="Ask something and press Enter" autocomplete="off" required /> <button type="submit">Send</button> </form> <section class="sound-panel"> <h2>Instant Quake Achievements</h2> <div class="sound-panel__controls"> <select id="sound-select"> <option value="">Select achievement…</option> <option value="FIRST BLOOD">FIRST BLOOD</option> <option value="HEADSHOT">HEADSHOT</option> <option value="DOUBLE KILL">DOUBLE KILL</option> <option value="TRIPLE KILL">TRIPLE KILL</option> <option value="RAMPAGE">RAMPAGE</option> <option value="DOMINATING">DOMINATING</option> <option value="UNSTOPPABLE">UNSTOPPABLE</option> <option value="GODLIKE">GODLIKE</option> <option value="MONSTER KILL">MONSTER KILL</option> <option value="WICKED SICK">WICKED SICK</option> <option value="EXCELLENT">EXCELLENT</option> <option value="PERFECT">PERFECT</option> <option value="PLAY">PLAY</option> </select> <select id="voice-select"> <option value="">Auto voice</option> <option value="female">Female</option> <option value="male">Male</option> </select> <input id="volume-input" type="number" min="0" max="100" value="80" /> <button type="button" id="play-sound">Play</button> </div> </section> </main> <script> // 🎭 The Widget Configuration Alchemist - Extract settings from URL or use defaults const config = (() => { const params = new URLSearchParams(window.location.search); const scriptTag = document.querySelector('script[data-api-url]'); return { apiUrl: scriptTag?.dataset.apiUrl || params.get('apiUrl') || window.location.origin, chatEndpoint: scriptTag?.dataset.chatEndpoint || params.get('chatEndpoint') || '/chat', quakeEndpoint: scriptTag?.dataset.quakeEndpoint || params.get('quakeEndpoint') || '/quake-sound', }; })(); const chatLog = document.getElementById("chat-log"); const statusEl = document.getElementById("status"); const form = document.getElementById("chat-form"); const input = document.getElementById("message-input"); const soundSelect = document.getElementById("sound-select"); const voiceSelect = document.getElementById("voice-select"); const volumeInput = document.getElementById("volume-input"); const playButton = document.getElementById("play-sound"); appendMessage("System", "Widget ready. Ask something epic! 🎮", "bot"); form.addEventListener("submit", async (event) => { event.preventDefault(); const text = input.value.trim(); if (!text) return; input.value = ""; await sendMessage(text); }); playButton.addEventListener("click", async () => { const achievement = soundSelect.value; if (!achievement) { setStatus("Pick an achievement first."); return; } setStatus(`Playing ${achievement}…`); try { await triggerSound({ achievement, voiceGender: voiceSelect.value || undefined, volume: volumeInput.value, }); setStatus(`${achievement} triggered.`); } catch (error) { console.error(error); setStatus(error.message || "Failed to trigger sound"); appendMessage("System", `⚠️ ${error.message || "Failed to trigger sound."}`, "bot"); } }); async function sendMessage(text) { appendMessage("You", text, "user"); setStatus("Thinking…"); try { const response = await fetch(`${config.apiUrl}${config.chatEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text }), }); const payload = await response.json(); if (!response.ok) { throw new Error(payload.error || "Chat endpoint failed"); } appendMessage("AI", payload.reply, "bot"); if (payload.quakeSound) { setStatus(`Triggering ${payload.quakeSound}…`); try { await triggerSound({ achievement: payload.quakeSound }); setStatus(`${payload.quakeSound} played!`); } catch (soundError) { console.error(soundError); setStatus("Chat ok, but failed to play sound."); appendMessage( "System", `⚠️ Unable to play ${payload.quakeSound}: ${soundError.message}`, "bot" ); } } else { setStatus("Reply delivered."); } } catch (error) { console.error(error); setStatus(error.message || "Something went wrong"); appendMessage("System", `⚠️ ${error.message || "ChatGPT request failed."}`, "bot"); } } async function triggerSound({ achievement, voiceGender, volume }) { const params = new URLSearchParams({ achievement }); if (voiceGender) params.set("voiceGender", voiceGender); if (volume) params.set("volume", volume); const response = await fetch(`${config.apiUrl}${config.quakeEndpoint}?${params.toString()}`); if (!response.ok) { const payload = await response.json().catch(() => ({})); throw new Error(payload.error || "Sound endpoint failed"); } } function appendMessage(author, text, variant) { const bubble = document.createElement("div"); bubble.className = `chat-bubble ${variant}`; const label = document.createElement("small"); label.textContent = author; bubble.appendChild(label); const paragraph = document.createElement("p"); paragraph.textContent = text; bubble.appendChild(paragraph); chatLog.appendChild(bubble); chatLog.scrollTop = chatLog.scrollHeight; } function setStatus(text) { statusEl.textContent = text; } </script> </body> </html>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ripnrip/Quake-Coding-Arena-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server