Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Sanskrit Text Parser</title> | |
| <style> | |
| :root { | |
| --primary: #1e4d6b; | |
| --secondary: #8b6914; | |
| --accent: #8b3a1a; | |
| --bg: #faf8f3; | |
| --card-bg: #ffffff; | |
| --text: #1a1a1a; | |
| --muted: #5a5a5a; | |
| --border-soft: #d4d4d4; | |
| --border-medium: #b8b8b8; | |
| --entry-border: #e8e5df; | |
| } | |
| body { | |
| font-family: "Crimson Pro", "Georgia", "Times New Roman", serif; | |
| background: linear-gradient(to bottom, #f5f2eb 0%, #faf8f3 100%); | |
| color: var(--text); | |
| margin: 0; | |
| padding: 0; | |
| -webkit-font-smoothing: antialiased; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| max-width: 960px; | |
| margin: auto; | |
| padding: 32px 24px; | |
| } | |
| /* ===== NAVBAR ===== */ | |
| nav { | |
| background: var(--card-bg); | |
| border-bottom: 1px solid var(--border-medium); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .nav-container { | |
| max-width: 960px; | |
| margin: 0 auto; | |
| padding: 0 24px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| height: 64px; | |
| } | |
| .nav-brand { | |
| font-family: "Crimson Pro", Georgia, serif; | |
| font-size: 1.35rem; | |
| font-weight: 700; | |
| color: var(--primary); | |
| text-decoration: none; | |
| } | |
| .nav-links { | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| } | |
| .nav-links a { | |
| font-family: "Inter", system-ui, sans-serif; | |
| font-size: 0.95rem; | |
| font-weight: 600; | |
| color: var(--muted); | |
| text-decoration: none; | |
| padding: 8px 20px; | |
| border-radius: 8px; | |
| transition: all 0.2s ease; | |
| } | |
| .nav-links a:hover { | |
| color: var(--primary); | |
| background: #f5f2eb; | |
| } | |
| .nav-links a.active { | |
| color: white; | |
| background: var(--primary); | |
| } | |
| /* ===== INPUT SECTION ===== */ | |
| .input-section { | |
| background: var(--card-bg); | |
| padding: 22px 26px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border-medium); | |
| box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); | |
| margin-bottom: 32px; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 120px; | |
| padding: 16px; | |
| font-size: 1.1rem; | |
| border: 1.5px solid var(--border-soft); | |
| border-radius: 8px; | |
| margin-bottom: 16px; | |
| box-sizing: border-box; | |
| font-family: "Crimson Pro", serif; | |
| background: #fafafa; | |
| transition: all 0.2s ease; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| background: white; | |
| } | |
| button { | |
| padding: 12px 28px; | |
| background: var(--primary); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-family: "Inter", sans-serif; | |
| font-size: 0.95rem; | |
| } | |
| /* ===== PARSED TEXT ===== */ | |
| #parsed-text { | |
| background: var(--card-bg); | |
| padding: 42px 48px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border-medium); | |
| box-shadow: 0 2px 16px rgba(0, 0, 0, 0.05); | |
| font-size: 1.65em; | |
| line-height: 2.1em; | |
| margin-bottom: 32px; | |
| min-height: 100px; | |
| } | |
| .word-token { | |
| cursor: pointer; | |
| color: var(--primary); | |
| border-bottom: 1.5px dotted var(--secondary); | |
| transition: 0.2s; | |
| display: inline-block; | |
| padding: 0 4px; | |
| } | |
| .word-token:hover { | |
| color: var(--accent); | |
| background-color: #f5f2eb; | |
| } | |
| /* ===== MODAL ===== */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 1000; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| backdrop-filter: blur(4px); | |
| } | |
| .modal-content { | |
| background: var(--bg); | |
| margin: 5% auto; | |
| padding: 30px; | |
| width: 85%; | |
| max-width: 800px; | |
| border-radius: 16px; | |
| position: relative; | |
| max-height: 85vh; | |
| overflow-y: auto; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| .close-btn { | |
| position: absolute; | |
| right: 20px; | |
| top: 15px; | |
| font-size: 35px; | |
| cursor: pointer; | |
| color: var(--accent); | |
| line-height: 1; | |
| } | |
| /* ===== COMPOUND UI ===== */ | |
| .split-container { | |
| display: flex; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| gap: 12px; | |
| margin: 22px 0; | |
| } | |
| .part-btn { | |
| background: white; | |
| border: 1.5px solid var(--primary); | |
| color: var(--primary); | |
| padding: 8px 18px; | |
| border-radius: 999px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 0.95em; | |
| } | |
| .part-btn.active { | |
| background: var(--primary); | |
| color: white; | |
| } | |
| /* ===== RESULTS ===== */ | |
| .meaning-card { | |
| background: white; | |
| border-radius: 10px; | |
| padding: 28px 32px; | |
| margin-top: 20px; | |
| border: 1px solid var(--border-medium); | |
| box-shadow: 0 1px 8px rgba(0, 0, 0, 0.04); | |
| } | |
| .label-pill { | |
| display: inline-block; | |
| background: var(--primary); | |
| color: white; | |
| padding: 6px 14px; | |
| border-radius: 6px; | |
| font-size: 0.7em; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| font-family: "Inter", sans-serif; | |
| margin-bottom: 16px; | |
| } | |
| .dict-title { | |
| display: block; | |
| margin-top: 1.6em; | |
| margin-bottom: 0.5em; | |
| padding-top: 1.2em; | |
| border-top: 2px solid var(--entry-border); | |
| font-weight: 700; | |
| font-size: 1.1em; | |
| color: #2d3748; | |
| font-family: "Inter", system-ui, sans-serif; | |
| } | |
| .dict-title:first-of-type { | |
| margin-top: 0.8em; | |
| padding-top: 0; | |
| border-top: none; | |
| } | |
| .def-content { | |
| margin-top: 12px; | |
| color: #2d3748; | |
| line-height: 1.75; | |
| font-family: "Crimson Pro", Georgia, serif; | |
| } | |
| .def-content .skt { | |
| font-weight: 600; | |
| color: #1a1a1a; | |
| } | |
| .def-content .abbr { | |
| font-size: 0.9em; | |
| font-weight: 600; | |
| color: #4a5568; | |
| } | |
| @media (max-width: 640px) { | |
| .nav-container { | |
| flex-direction: column; | |
| height: auto; | |
| padding: 16px 24px; | |
| gap: 12px; | |
| } | |
| .nav-links { | |
| width: 100%; | |
| justify-content: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <nav> | |
| <div class="nav-container"> | |
| <a href="../../../index.html" class="nav-brand" | |
| >Sanskrit Smart Reader</a | |
| > | |
| <div class="nav-links"> | |
| <a href="../../../index.html">Home</a> | |
| <a href="about.html">About</a> | |
| <a href="text_parser.html" class="active">Parser</a> | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="container"> | |
| <h2 style="color: var(--primary); letter-spacing: -0.02em"> | |
| Sanskrit Text Parser | |
| </h2> | |
| <div class="input-section"> | |
| <p style="margin-top: 0; color: var(--muted); font-weight: 600"> | |
| Enter Sanskrit text below and click Analyze. | |
| </p> | |
| <textarea id="input-text" placeholder="Enter text here..."></textarea> | |
| <button onclick="transformText()">Analyze</button> | |
| </div> | |
| <div id="parsed-text"></div> | |
| </div> | |
| <div id="wordModal" class="modal"> | |
| <div class="modal-content"> | |
| <span class="close-btn" onclick="closeModal()">×</span> | |
| <div id="modal-body"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const base_api = "https://psugam-sanskrit-parser-api.hf.space/"; | |
| console.log("Using API base:", base_api); | |
| const parsedTextContainer = document.getElementById("parsed-text"); | |
| const modal = document.getElementById("wordModal"); | |
| const modalBody = document.getElementById("modal-body"); | |
| // Close modal logic | |
| window.onclick = (e) => { | |
| if (e.target == modal) closeModal(); | |
| }; | |
| function closeModal() { | |
| modal.style.display = "none"; | |
| modalBody.innerHTML = ""; | |
| } | |
| function sanitizeHTML(str) { | |
| const div = document.createElement("div"); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| function sanitizeAllowHTML(input) { | |
| const decoder = document.createElement("textarea"); | |
| decoder.innerHTML = input; | |
| let html = decoder.value; | |
| html = html | |
| .replace(/<div[^>]*\/>/gi, "<br>") | |
| .replace(/<lbinfo[^>]*\/>/gi, "") | |
| .replace(/<\/?meta[^>]*>/gi, ""); | |
| const replacements = { | |
| s: 'span class="skt"', | |
| ab: 'span class="abbr"', | |
| lex: 'span class="pos"', | |
| ls: 'span class="source"', | |
| }; | |
| for (const [tag, repl] of Object.entries(replacements)) { | |
| html = html.replace(new RegExp(`<${tag}[^>]*>`, "gi"), `<${repl}>`); | |
| html = html.replace( | |
| new RegExp(`</${tag}>`, "gi"), | |
| `</${repl.split(" ")[0]}>` | |
| ); | |
| } | |
| html = html.replace( | |
| /<(?!\/?(br|b|i|em|strong|sup|sub|span)\b)[^>]+>/gi, | |
| "" | |
| ); | |
| return html; | |
| } | |
| function transformText() { | |
| const input = document.getElementById("input-text").value; | |
| parsedTextContainer.innerHTML = ""; | |
| const tokens = input.split(/(\s+|[।.।])/); | |
| tokens.forEach((token) => { | |
| if (token.trim()) { | |
| const clean = token.replace(/[।.।\s]/g, ""); | |
| if (clean) { | |
| const span = document.createElement("span"); | |
| span.className = "word-token"; | |
| span.textContent = sanitizeHTML(token); | |
| span.onclick = () => initiateProcess(clean); | |
| parsedTextContainer.appendChild(span); | |
| } else { | |
| parsedTextContainer.appendChild( | |
| document.createTextNode(sanitizeHTML(token)) | |
| ); | |
| } | |
| } | |
| }); | |
| } | |
| async function initiateProcess(word) { | |
| modalBody.innerHTML = `<p style="color: var(--muted)">Analyzing <b>${sanitizeHTML( | |
| word | |
| )}</b>…</p>`; | |
| modal.style.display = "block"; | |
| const res = await fetch( | |
| `${base_api}/split?word=${encodeURIComponent(word)}` | |
| ); | |
| const data = await res.json(); | |
| if (data.is_compound) | |
| renderCompoundUI(word, data.components, modalBody); | |
| else renderDirectMeaning(word, modalBody); | |
| } | |
| function renderCompoundUI(word, parts, target) { | |
| target.innerHTML = ` | |
| <h3 style="color: var(--primary)">${sanitizeHTML(word)}</h3> | |
| <div class="split-container"> | |
| ${parts | |
| .map( | |
| (p) => | |
| `<button class="part-btn" onclick="fetchPartMeaning('${sanitizeHTML( | |
| p | |
| )}','compound-res',this)">${sanitizeHTML(p)}</button>` | |
| ) | |
| .join("")} | |
| </div> | |
| <div id="compound-res"></div>`; | |
| } | |
| async function renderDirectMeaning(word, target) { | |
| target.innerHTML = `<h3 style="color: var(--primary)">${sanitizeHTML( | |
| word | |
| )}</h3><div id="direct-res"></div>`; | |
| fetchPartMeaning(word, "direct-res"); | |
| } | |
| async function fetchPartMeaning(word, id, btn = null) { | |
| if (btn) { | |
| btn.parentElement | |
| .querySelectorAll(".part-btn") | |
| .forEach((b) => b.classList.remove("active")); | |
| btn.classList.add("active"); | |
| } | |
| const display = document.getElementById(id); | |
| display.innerHTML = "Fetching dictionary…"; | |
| const res = await fetch( | |
| `${base_api}/meaning?word=${encodeURIComponent(word)}` | |
| ); | |
| const data = await res.json(); | |
| const sourceMap = { | |
| mw: "Monier-Williams", | |
| ap90: "Apte", | |
| cae: "Cappeller", | |
| bhs: "Buddhist", | |
| }; | |
| display.innerHTML = data | |
| .map((e) => { | |
| // Format the grammar tags: e.g., "Nom, Sg | Acc, Sg" | |
| const tags = e.detected_tags.map((t) => t.join(", ")).join(" | "); | |
| return ` | |
| <div class="meaning-card"> | |
| <span class="label-pill">${sanitizeHTML(e.type)}</span> | |
| <div class="grammar-tags" style="color: var(--accent); font-weight: bold; margin-bottom: 10px; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.5px;"> | |
| ${sanitizeHTML(tags)} | |
| </div> | |
| <div style="margin-bottom: 10px;"><b>Stem:</b> ${sanitizeHTML( | |
| e.stem | |
| )}</div> | |
| ${Object.entries(e.definitions) | |
| .map( | |
| ([src, defs]) => ` | |
| <span class="dict-title">${sourceMap[src] || src}</span> | |
| <div class="def-content"> | |
| ${defs.map((d) => `• ${sanitizeAllowHTML(d)}`).join("<br>")} | |
| </div>` | |
| ) | |
| .join("")} | |
| </div>`; | |
| }) | |
| .join(""); | |
| } | |
| </script> | |
| </body> | |
| </html> | |