const form = document.getElementById("analyze-form"); const statusBox = document.getElementById("status"); const metaBox = document.getElementById("meta"); const mHashtag = document.getElementById("m-hashtag"); const mGemini = document.getElementById("m-gemini"); const mFallback = document.getElementById("m-fallback"); const mModels = document.getElementById("m-models"); const pieDiv = document.getElementById("pie"); const lineDiv = document.getElementById("line"); const tableDiv = document.getElementById("table"); // Parallax cursor const cursor = document.getElementById("parallax-cursor"); window.addEventListener("mousemove", (e) => { const x = e.clientX, y = e.clientY; cursor.style.opacity = ".9"; cursor.style.left = x + "px"; cursor.style.top = y + "px"; }); // Helpers function fmtPct(n){ return (Math.round(n * 100) / 100).toFixed(2); } function renderMeta(meta) { mHashtag.textContent = meta.hashtag; mGemini.textContent = `Gemini: ${meta.generated_by.gemini}`; mFallback.textContent = `Fallback: ${meta.generated_by.fallback}`; mModels.textContent = `Gen: ${meta.model.generation} • Sentiment: ${meta.model.sentiment}`; } function renderPie(percent) { const data = [{ values: [percent.positive, percent.neutral, percent.negative], labels: ['Positive', 'Neutral', 'Negative'], type: 'pie', textinfo: 'label+percent', hoverinfo: 'label+percent', hole: .35 }]; const layout = { paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', font: {color: '#eaf2ff'}, margin: {l: 4, r: 4, t: 0, b: 0}, showlegend: false }; Plotly.newPlot(pieDiv, data, layout, {displayModeBar:false, responsive:true}); } function renderLine(rolling) { const data = [{ x: [...Array(rolling.length).keys()].map(i => i+1), y: rolling, type: 'scatter', mode: 'lines+markers' }]; const layout = { paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', font: {color: '#eaf2ff'}, margin: {l: 30, r: 10, t: 0, b: 24}, yaxis: {range:[0,1], tickformat: '.0%'}, }; Plotly.newPlot(lineDiv, data, layout, {displayModeBar:false, responsive:true}); } function renderTable(rows) { tableDiv.innerHTML = ""; rows.forEach(r => { const row = document.createElement("div"); row.className = "row"; // text const c1 = document.createElement("div"); c1.className = "cell"; c1.textContent = r.text; // source chip const c2 = document.createElement("div"); c2.className = "cell"; const chip = document.createElement("span"); chip.className = "chip " + (r.source === "gemini" ? "chip-gemini" : "chip-fallback"); chip.textContent = r.source === "gemini" ? "Gemini" : "Fallback"; c2.appendChild(chip); // sentiment badge const c3 = document.createElement("div"); c3.className = "cell"; const badge = document.createElement("span"); const s = r.sentiment; badge.className = "badge " + (s === "POSITIVE" ? "pos" : s === "NEGATIVE" ? "neg" : "neu"); badge.textContent = s + " " + (r.score.toFixed(2)); c3.appendChild(badge); row.appendChild(c1); row.appendChild(c2); row.appendChild(c3); tableDiv.appendChild(row); }); } form.addEventListener("submit", async (e) => { e.preventDefault(); const hashtag = document.getElementById("hashtag").value.trim(); const count = parseInt(document.getElementById("count").value || "20", 10); if(!hashtag){ alert("Please enter a hashtag (e.g., #gla)"); return; } statusBox.classList.remove("hidden"); metaBox.classList.add("hidden"); try { const resp = await fetch("/api/analyze", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({hashtag, count}) }); if(!resp.ok){ const err = await resp.json().catch(()=>({})); throw new Error(err.error || `HTTP ${resp.status}`); } const data = await resp.json(); // META renderMeta(data.meta); metaBox.classList.remove("hidden"); // CHARTS renderPie(data.aggregate.percent); renderLine(data.aggregate.rolling); // TABLE renderTable(data.rows); } catch (err) { console.error(err); alert("Failed: " + err.message); } finally { statusBox.classList.add("hidden"); } });