Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Quran Explainer</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <style> | |
| :root { | |
| --bg-dark: #1a202c; | |
| --card-bg: #2d3748; | |
| --text-primary: #edf2f7; | |
| --text-secondary: #a0aec0; | |
| --accent-color: #d69e2e; | |
| --border-color: #4a5568; | |
| --skeleton-color: #4a5568; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| line-height: 1.6; | |
| color: var(--text-primary); | |
| background-color: var(--bg-dark); | |
| margin: 0; | |
| padding: 20px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| min-height: 100vh; | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| background: var(--card-bg); | |
| padding: 40px; | |
| border-radius: 12px; | |
| border: 1px solid var(--border-color); | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.25); | |
| } | |
| h1 { text-align: center; font-size: 2.25rem; margin: 0; } | |
| .subtitle { text-align: center; color: var(--text-secondary); margin-top: 8px; margin-bottom: 40px; } | |
| form { display: flex; flex-direction: column; gap: 15px; margin-bottom: 30px; } | |
| .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; } | |
| input, select { | |
| background-color: var(--bg-dark); | |
| color: var(--text-primary); | |
| border: 1px solid var(--border-color); | |
| border-radius: 8px; | |
| padding: 12px 15px; | |
| font-size: 16px; | |
| transition: border-color 0.3s, box-shadow 0.3s; | |
| width: 100%; | |
| box-sizing: border-box; | |
| } | |
| input:focus, select:focus { | |
| outline: none; | |
| border-color: var(--accent-color); | |
| box-shadow: 0 0 0 3px rgba(214, 158, 46, 0.2); | |
| } | |
| button { | |
| background-color: var(--accent-color); | |
| color: #1a202c; | |
| font-weight: 500; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| padding: 14px 25px; | |
| transition: transform 0.2s, background-color 0.3s; | |
| width: 100%; | |
| } | |
| button:hover { background-color: #e5b342; transform: translateY(-2px); } | |
| .initial-info { display: none; } | |
| .disclaimer { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| text-align: center; | |
| } | |
| .loader { | |
| display: none; | |
| padding: 20px; | |
| border: 1px solid var(--border-color); | |
| border-radius: 8px; | |
| background-color: var(--card-bg); | |
| } | |
| .skeleton { | |
| background-color: var(--skeleton-color); | |
| border-radius: 4px; | |
| animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| .skeleton-title { height: 28px; width: 40%; margin-bottom: 20px; } | |
| .skeleton-line { height: 16px; margin-bottom: 12px; } | |
| .skeleton-line-1 { width: 90%; } | |
| .skeleton-line-2 { width: 100%; } | |
| .skeleton-line-3 { width: 75%; } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 0.6; } | |
| 50% { opacity: 1; } | |
| } | |
| #response-container { margin-top: 20px; } | |
| #response-container h3 { color: var(--accent-color); } | |
| #response-container blockquote { | |
| margin: 20px 0; padding: 15px; background-color: var(--bg-dark); | |
| border-left: 4px solid var(--accent-color); font-style: italic; | |
| } | |
| /* Table styles */ | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-bottom: 20px; | |
| } | |
| th, td { | |
| text-align: left; | |
| padding: 12px; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| th { color: var(--accent-color); } | |
| td { color: var(--text-secondary); } | |
| </style> | |
| </head> | |
| <body> | |
| <main class="container"> | |
| <h1>Quran Explainer</h1> | |
| <p class="subtitle">Select a verse and a scholarly source for a detailed explanation.</p> | |
| <form id="tafsir-form"> | |
| <div class="form-row"> | |
| <input type="number" id="surah" name="surah" placeholder="Surah No." required> | |
| <input type="number" id="ayah" name="ayah" placeholder="Ayah No." required> | |
| </div> | |
| <select id="tafsir_slug" name="tafsir_slug" required> | |
| <option value="" disabled selected>Select a Tafsir Source...</option> | |
| </select> | |
| <input type="text" id="question" name="question" placeholder="Optional: Ask a specific question about this verse..."> | |
| <button type="submit">Get Explanation</button> | |
| </form> | |
| <div class="initial-info" id="initial-info"> | |
| <p><strong>Please Note:</strong> The depth of commentary can vary. For some verses, certain classical sources may not have a specific entry. If you find the explanation is brief, please try selecting another Tafsir for a different perspective.</p> | |
| <!-- <h3>Available Tafsir Sources</h3> | |
| <div id="tafsir-table-container"></div> --> | |
| </div> | |
| <div class="loader" id="loader"> | |
| <div class="skeleton skeleton-title"></div> | |
| <div class="skeleton skeleton-line skeleton-line-1"></div> | |
| <div class="skeleton skeleton-line skeleton-line-2"></div> | |
| <div class="skeleton skeleton-line skeleton-line-3"></div> | |
| </div> | |
| <div id="response-container"></div> | |
| <footer> | |
| <hr> | |
| <p class="disclaimer">This is an AI-powered tool for study and reference. Always consult with a qualified scholar for definitive religious guidance. | |
| <br>GitHub repo <a href="https://github.com/spa5k/tafsir_api/tree/main">tafsir_api</a> is used to get the tasfir from different sources. | |
| </p> | |
| </footer> | |
| </main> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', async function() { | |
| try { | |
| const response = await fetch('/tafsirs'); | |
| const tafsirs = await response.json(); | |
| const select = document.getElementById('tafsir_slug'); | |
| const tableContainer = document.getElementById('tafsir-table-container'); | |
| let tableHTML = '<table><thead><tr><th>Name</th><th>Author</th><th>Highlight</th></tr></thead><tbody>'; | |
| tafsirs.forEach(tafsir => { | |
| const option = document.createElement('option'); | |
| option.value = tafsir.slug; | |
| option.textContent = `${tafsir.name} - ${tafsir.author}`; | |
| select.appendChild(option); | |
| tableHTML += `<tr><td>${tafsir.name}</td><td>${tafsir.author}</td><td>${tafsir.highlight}</td></tr>`; | |
| }); | |
| tableHTML += '</tbody></table>'; | |
| tableContainer.innerHTML = tableHTML; | |
| } catch (error) { | |
| console.error("Failed to load Tafsir list:", error); | |
| } | |
| }); | |
| document.getElementById('tafsir-form').addEventListener('submit', async function(event) { | |
| event.preventDefault(); | |
| const surah = document.getElementById('surah').value; | |
| const ayah = document.getElementById('ayah').value; | |
| const tafsir_slug = document.getElementById('tafsir_slug').value; | |
| const question = document.getElementById('question').value; | |
| const responseContainer = document.getElementById('response-container'); | |
| const loader = document.getElementById('loader'); | |
| const initialInfo = document.getElementById('initial-info'); | |
| if (!tafsir_slug) { | |
| alert("Please select a Tafsir source."); | |
| return; | |
| } | |
| responseContainer.innerHTML = ''; | |
| initialInfo.style.display = 'block'; | |
| loader.style.display = 'block'; | |
| try { | |
| const response = await fetch('/explain', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ surah, ayah, tafsir_slug, question }) | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.error || 'An unknown error occurred.'); | |
| } | |
| const data = await response.json(); | |
| responseContainer.innerHTML = marked.parse(data.explanation); | |
| } catch (error) { | |
| responseContainer.innerHTML = `<p class="error">Error: ${error.message}</p>`; | |
| } finally { | |
| loader.style.display = 'none'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |