Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>Sentiment Analyzer</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet"/> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| --bg: #08090a; | |
| --surface: #111214; | |
| --surface2: #1a1c1f; | |
| --border: rgba(255,255,255,0.07); | |
| --border2: rgba(255,255,255,0.13); | |
| --text: #f0ede8; | |
| --muted: #7a7875; | |
| --accent: #c8f060; | |
| --accent-dim: rgba(200,240,96,0.1); | |
| --accent-glow: rgba(200,240,96,0.25); | |
| --pos: #4ade80; | |
| --pos-bg: rgba(74,222,128,0.08); | |
| --neg: #f87171; | |
| --neg-bg: rgba(248,113,113,0.08); | |
| --font-display: 'Syne', sans-serif; | |
| --font-body: 'DM Sans', sans-serif; | |
| } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: var(--font-body); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 3rem 1.5rem 5rem; | |
| } | |
| .noise { | |
| position: fixed; | |
| inset: 0; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| z-index: 0; | |
| opacity: 0.4; | |
| } | |
| .orb { | |
| position: fixed; | |
| width: 600px; | |
| height: 600px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(200,240,96,0.04) 0%, transparent 70%); | |
| top: -200px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .wrap { | |
| width: 100%; | |
| max-width: 720px; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| header { | |
| text-align: center; | |
| margin-bottom: 3.5rem; | |
| animation: fadeUp 0.7s ease both; | |
| } | |
| .badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| background: var(--accent-dim); | |
| border: 1px solid var(--accent-glow); | |
| color: var(--accent); | |
| font-family: var(--font-body); | |
| font-size: 11px; | |
| font-weight: 500; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| padding: 5px 12px; | |
| border-radius: 100px; | |
| margin-bottom: 1.2rem; | |
| } | |
| .badge-dot { | |
| width: 6px; | |
| height: 6px; | |
| background: var(--accent); | |
| border-radius: 50%; | |
| animation: pulse 2s infinite; | |
| } | |
| h1 { | |
| font-family: var(--font-display); | |
| font-size: clamp(2.5rem, 6vw, 4rem); | |
| font-weight: 800; | |
| line-height: 1.05; | |
| letter-spacing: -0.02em; | |
| color: var(--text); | |
| margin-bottom: 0.75rem; | |
| } | |
| h1 span { color: var(--accent); } | |
| .subtitle { | |
| font-size: 15px; | |
| color: var(--muted); | |
| font-weight: 300; | |
| line-height: 1.6; | |
| } | |
| .card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| margin-bottom: 1.5rem; | |
| animation: fadeUp 0.7s ease 0.1s both; | |
| transition: border-color 0.3s; | |
| } | |
| .card:hover { border-color: var(--border2); } | |
| .input-label { | |
| font-family: var(--font-display); | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| margin-bottom: 0.75rem; | |
| display: block; | |
| } | |
| textarea { | |
| width: 100%; | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| color: var(--text); | |
| font-family: var(--font-body); | |
| font-size: 15px; | |
| font-weight: 300; | |
| line-height: 1.7; | |
| padding: 1rem 1.25rem; | |
| resize: none; | |
| outline: none; | |
| transition: border-color 0.25s, box-shadow 0.25s; | |
| height: 130px; | |
| } | |
| textarea::placeholder { color: var(--muted); opacity: 0.6; } | |
| textarea:focus { | |
| border-color: rgba(200,240,96,0.35); | |
| box-shadow: 0 0 0 3px rgba(200,240,96,0.06); | |
| } | |
| .char-count { | |
| text-align: right; | |
| font-size: 12px; | |
| color: var(--muted); | |
| margin-top: 6px; | |
| } | |
| .btn-row { | |
| display: flex; | |
| gap: 10px; | |
| margin-top: 1.25rem; | |
| } | |
| .btn { | |
| flex: 1; | |
| padding: 14px 20px; | |
| border-radius: 12px; | |
| font-family: var(--font-display); | |
| font-size: 14px; | |
| font-weight: 700; | |
| letter-spacing: 0.03em; | |
| cursor: pointer; | |
| border: none; | |
| transition: all 0.2s; | |
| } | |
| .btn-primary { | |
| background: var(--accent); | |
| color: #0a0a0a; | |
| } | |
| .btn-primary:hover { background: #d4f570; transform: translateY(-1px); } | |
| .btn-primary:active { transform: translateY(0); } | |
| .btn-primary:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-ghost { | |
| background: var(--surface2); | |
| color: var(--muted); | |
| border: 1px solid var(--border); | |
| flex: 0 0 auto; | |
| padding: 14px 18px; | |
| } | |
| .btn-ghost:hover { color: var(--text); border-color: var(--border2); } | |
| .result-card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 2rem; | |
| margin-bottom: 1.5rem; | |
| display: none; | |
| animation: fadeUp 0.4s ease both; | |
| } | |
| .result-card.show { display: block; } | |
| .result-inner { | |
| display: flex; | |
| align-items: center; | |
| gap: 1.25rem; | |
| } | |
| .result-icon { | |
| width: 64px; | |
| height: 64px; | |
| border-radius: 16px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 28px; | |
| flex-shrink: 0; | |
| } | |
| .result-icon.pos { background: var(--pos-bg); } | |
| .result-icon.neg { background: var(--neg-bg); } | |
| .result-label { | |
| font-family: var(--font-body); | |
| font-size: 12px; | |
| font-weight: 500; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| margin-bottom: 4px; | |
| } | |
| .result-sentiment { | |
| font-family: var(--font-display); | |
| font-size: 2rem; | |
| font-weight: 800; | |
| letter-spacing: -0.02em; | |
| } | |
| .result-sentiment.pos { color: var(--pos); } | |
| .result-sentiment.neg { color: var(--neg); } | |
| .result-text { | |
| font-size: 13px; | |
| color: var(--muted); | |
| margin-top: 4px; | |
| font-weight: 300; | |
| line-height: 1.5; | |
| max-width: 400px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .loader { | |
| display: none; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 1.5rem 2rem; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| margin-bottom: 1.5rem; | |
| color: var(--muted); | |
| font-size: 14px; | |
| } | |
| .loader.show { display: flex; } | |
| .spinner { | |
| width: 18px; | |
| height: 18px; | |
| border: 2px solid var(--border2); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: spin 0.7s linear infinite; | |
| flex-shrink: 0; | |
| } | |
| .history-section { | |
| animation: fadeUp 0.7s ease 0.2s both; | |
| } | |
| .section-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 1rem; | |
| } | |
| .section-title { | |
| font-family: var(--font-display); | |
| font-size: 13px; | |
| font-weight: 700; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| } | |
| .clear-btn { | |
| background: none; | |
| border: none; | |
| color: var(--muted); | |
| font-family: var(--font-body); | |
| font-size: 12px; | |
| cursor: pointer; | |
| padding: 4px 8px; | |
| border-radius: 6px; | |
| transition: color 0.2s, background 0.2s; | |
| } | |
| .clear-btn:hover { color: var(--neg); background: var(--neg-bg); } | |
| .history-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .history-item { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| padding: 1rem 1.25rem; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 12px; | |
| cursor: pointer; | |
| transition: border-color 0.2s, background 0.2s; | |
| animation: fadeUp 0.35s ease both; | |
| } | |
| .history-item:hover { | |
| border-color: var(--border2); | |
| background: var(--surface2); | |
| } | |
| .history-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| margin-top: 5px; | |
| flex-shrink: 0; | |
| } | |
| .history-dot.pos { background: var(--pos); } | |
| .history-dot.neg { background: var(--neg); } | |
| .history-content { flex: 1; min-width: 0; } | |
| .history-text { | |
| font-size: 14px; | |
| color: var(--text); | |
| font-weight: 300; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| margin-bottom: 4px; | |
| } | |
| .history-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 11px; | |
| color: var(--muted); | |
| } | |
| .history-sentiment { | |
| font-weight: 500; | |
| font-size: 11px; | |
| padding: 2px 8px; | |
| border-radius: 100px; | |
| } | |
| .history-sentiment.pos { color: var(--pos); background: var(--pos-bg); } | |
| .history-sentiment.neg { color: var(--neg); background: var(--neg-bg); } | |
| .empty-state { | |
| text-align: center; | |
| padding: 2.5rem 1rem; | |
| color: var(--muted); | |
| font-size: 13px; | |
| font-weight: 300; | |
| border: 1px dashed var(--border); | |
| border-radius: 14px; | |
| } | |
| .empty-icon { font-size: 28px; margin-bottom: 0.5rem; opacity: 0.4; } | |
| @keyframes fadeUp { | |
| from { opacity: 0; transform: translateY(14px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.4; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="noise"></div> | |
| <div class="orb"></div> | |
| <div class="wrap"> | |
| <header> | |
| <div class="badge"> | |
| <span class="badge-dot"></span> | |
| AI Powered | |
| </div> | |
| <h1>Sentiment<br/><span>Analyzer</span></h1> | |
| <p class="subtitle">Understand the emotion behind any text — instantly.</p> | |
| </header> | |
| <div class="card"> | |
| <label class="input-label" for="textInput">Your text</label> | |
| <textarea id="textInput" placeholder="Type or paste any text here — a review, tweet, message..." maxlength="1000"></textarea> | |
| <div class="char-count"><span id="charCount">0</span> / 1000</div> | |
| <div class="btn-row"> | |
| <button class="btn btn-ghost" onclick="clearAll()">Clear</button> | |
| <button class="btn btn-primary" id="analyzeBtn" onclick="analyze()">Analyze Sentiment →</button> | |
| </div> | |
| </div> | |
| <div class="loader" id="loader"> | |
| <div class="spinner"></div> | |
| Analyzing sentiment... | |
| </div> | |
| <div class="result-card" id="resultCard"> | |
| <div class="result-inner"> | |
| <div class="result-icon" id="resultIcon"></div> | |
| <div> | |
| <div class="result-label">Result</div> | |
| <div class="result-sentiment" id="resultSentiment"></div> | |
| <div class="result-text" id="resultText"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="history-section" id="historySection" style="display:none"> | |
| <div class="section-header"> | |
| <span class="section-title">History</span> | |
| <button class="clear-btn" onclick="clearHistory()">Clear all</button> | |
| </div> | |
| <div class="history-list" id="historyList"></div> | |
| </div> | |
| <div class="history-section" id="emptyHistory"> | |
| <div class="section-header"> | |
| <span class="section-title">History</span> | |
| </div> | |
| <div class="empty-state"> | |
| <div class="empty-icon">◎</div> | |
| Your analysis history will appear here | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_URL = "http://127.0.0.1:8000/predict"; | |
| let history = JSON.parse(localStorage.getItem("sa_history") || "[]"); | |
| const textarea = document.getElementById("textInput"); | |
| const charCount = document.getElementById("charCount"); | |
| const loader = document.getElementById("loader"); | |
| const resultCard = document.getElementById("resultCard"); | |
| const analyzeBtn = document.getElementById("analyzeBtn"); | |
| textarea.addEventListener("input", () => { | |
| charCount.textContent = textarea.value.length; | |
| }); | |
| function formatDate(iso) { | |
| const d = new Date(iso); | |
| const now = new Date(); | |
| const diff = now - d; | |
| if (diff < 60000) return "just now"; | |
| if (diff < 3600000) return Math.floor(diff/60000) + "m ago"; | |
| if (diff < 86400000) return Math.floor(diff/3600000) + "h ago"; | |
| return d.toLocaleDateString("en-IN", { day:"numeric", month:"short", year:"numeric" }) + | |
| " · " + d.toLocaleTimeString("en-IN", { hour:"2-digit", minute:"2-digit" }); | |
| } | |
| async function analyze() { | |
| const text = textarea.value.trim(); | |
| if (!text) { textarea.focus(); return; } | |
| analyzeBtn.disabled = true; | |
| resultCard.classList.remove("show"); | |
| loader.classList.add("show"); | |
| try { | |
| const res = await fetch(API_URL, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ text }) | |
| }); | |
| const data = await res.json(); | |
| const sentiment = data.sentiment?.toLowerCase() || "unknown"; | |
| showResult(sentiment, text); | |
| addToHistory(text, sentiment); | |
| } catch (e) { | |
| showResult("error", text); | |
| } finally { | |
| loader.classList.remove("show"); | |
| analyzeBtn.disabled = false; | |
| } | |
| } | |
| function showResult(sentiment, text) { | |
| const isPos = sentiment === "positive"; | |
| const icon = document.getElementById("resultIcon"); | |
| const label = document.getElementById("resultSentiment"); | |
| const sub = document.getElementById("resultText"); | |
| icon.className = "result-icon " + (isPos ? "pos" : "neg"); | |
| icon.textContent = isPos ? "😊" : "😞"; | |
| label.className = "result-sentiment " + (isPos ? "pos" : "neg"); | |
| label.textContent = isPos ? "Positive" : "Negative"; | |
| sub.textContent = '"' + text + '"'; | |
| resultCard.classList.add("show"); | |
| } | |
| function addToHistory(text, sentiment) { | |
| history.unshift({ text, sentiment, date: new Date().toISOString() }); | |
| if (history.length > 20) history = history.slice(0, 20); | |
| localStorage.setItem("sa_history", JSON.stringify(history)); | |
| renderHistory(); | |
| } | |
| function renderHistory() { | |
| const list = document.getElementById("historyList"); | |
| const section = document.getElementById("historySection"); | |
| const empty = document.getElementById("emptyHistory"); | |
| if (!history.length) { | |
| section.style.display = "none"; | |
| empty.style.display = "block"; | |
| return; | |
| } | |
| section.style.display = "block"; | |
| empty.style.display = "none"; | |
| list.innerHTML = ""; | |
| history.forEach((item, i) => { | |
| const cls = item.sentiment === "positive" ? "pos" : "neg"; | |
| const el = document.createElement("div"); | |
| el.className = "history-item"; | |
| el.style.animationDelay = (i * 0.04) + "s"; | |
| el.innerHTML = ` | |
| <div class="history-dot ${cls}"></div> | |
| <div class="history-content"> | |
| <div class="history-text">${escHtml(item.text)}</div> | |
| <div class="history-meta"> | |
| <span class="history-sentiment ${cls}">${item.sentiment}</span> | |
| <span>${formatDate(item.date)}</span> | |
| </div> | |
| </div> | |
| `; | |
| el.onclick = () => { | |
| textarea.value = item.text; | |
| charCount.textContent = item.text.length; | |
| window.scrollTo({ top: 0, behavior: "smooth" }); | |
| }; | |
| list.appendChild(el); | |
| }); | |
| } | |
| function clearHistory() { | |
| history = []; | |
| localStorage.removeItem("sa_history"); | |
| renderHistory(); | |
| } | |
| function clearAll() { | |
| textarea.value = ""; | |
| charCount.textContent = "0"; | |
| resultCard.classList.remove("show"); | |
| textarea.focus(); | |
| } | |
| function escHtml(str) { | |
| return str.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""); | |
| } | |
| renderHistory(); | |
| textarea.addEventListener("keydown", e => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === "Enter") analyze(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |