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}`; // meta fade-in animation metaBox.style.opacity = "0"; metaBox.style.transform = "translateY(10px)"; requestAnimationFrame(() => { metaBox.style.transition = "all .6s ease"; metaBox.style.opacity = "1"; metaBox.style.transform = "translateY(0)"; }); } 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, transition: {duration: 500, easing: "cubic-in-out"} }; Plotly.newPlot(pieDiv, data, layout, {displayModeBar:false, responsive:true}).then(() => { pieDiv.style.opacity = "0"; pieDiv.style.transform = "scale(0.9)"; requestAnimationFrame(() => { pieDiv.style.transition = "all .6s ease"; pieDiv.style.opacity = "1"; pieDiv.style.transform = "scale(1)"; }); }); } function renderLine(rolling) { const data = [{ x: [...Array(rolling.length).keys()].map(i => i+1), y: rolling, type: 'scatter', mode: 'lines+markers', line: {shape: 'spline', smoothing: 1.3} }]; 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%'}, transition: {duration: 600, easing: "cubic-in-out"} }; Plotly.newPlot(lineDiv, data, layout, {displayModeBar:false, responsive:true}).then(() => { lineDiv.style.opacity = "0"; lineDiv.style.transform = "translateY(20px)"; requestAnimationFrame(() => { lineDiv.style.transition = "all .7s ease"; lineDiv.style.opacity = "1"; lineDiv.style.transform = "translateY(0)"; }); }); } function renderTable(rows) { tableDiv.innerHTML = ""; rows.forEach((r, i) => { 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); // add animation row.style.opacity = "0"; row.style.transform = "translateX(-15px)"; setTimeout(() => { row.style.transition = "all .4s ease"; row.style.opacity = "1"; row.style.transform = "translateX(0)"; }, 100 * i); 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"); } });