Spaces:
Sleeping
Sleeping
| <html lang="fr" class="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Scrap-Dji</title> | |
| <style> | |
| :root { | |
| /* OpenAI / Apple Dark Mode Palette */ | |
| --bg-primary: #000000; | |
| --bg-secondary: #121212; | |
| --text-primary: #ffffff; | |
| --text-secondary: #8e8e8e; | |
| --accent: #3b82f6; | |
| /* Subtle Blue Like Apple */ | |
| --border: #333333; | |
| --hover: #1f1f1f; | |
| --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| outline: none; | |
| } | |
| body { | |
| background-color: var(--bg-primary); | |
| color: var(--text-primary); | |
| font-family: var(--font-main); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-x: hidden; | |
| } | |
| /* --- Header / Navigation --- */ | |
| nav { | |
| position: fixed; | |
| top: 0; | |
| width: 100%; | |
| padding: 20px 40px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| z-index: 100; | |
| background: rgba(0, 0, 0, 0.8); | |
| backdrop-filter: blur(20px); | |
| transition: var(--transition); | |
| } | |
| .logo { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| letter-spacing: -0.5px; | |
| color: var(--text-primary); | |
| text-decoration: none; | |
| opacity: 0.9; | |
| } | |
| /* --- Main Layout --- */ | |
| main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| transition: var(--transition); | |
| min-height: 100vh; | |
| } | |
| /* Transition state: Results Mode */ | |
| body.has-results main { | |
| justify-content: flex-start; | |
| padding-top: 100px; | |
| min-height: auto; | |
| } | |
| /* --- Search Container --- */ | |
| .search-container { | |
| width: 100%; | |
| max-width: 680px; | |
| text-align: center; | |
| transition: var(--transition); | |
| } | |
| body.has-results .search-container { | |
| position: fixed; | |
| top: 15px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 50%; | |
| max-width: 600px; | |
| z-index: 101; | |
| margin: 0; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| font-weight: 600; | |
| letter-spacing: -1.5px; | |
| margin-bottom: 40px; | |
| transition: var(--transition); | |
| } | |
| body.has-results h1 { | |
| opacity: 0; | |
| pointer-events: none; | |
| position: absolute; | |
| transform: scale(0.9); | |
| } | |
| /* --- Search Input --- */ | |
| .input-wrapper { | |
| position: relative; | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| /* Apple rounded corners */ | |
| padding: 6px; | |
| display: flex; | |
| align-items: center; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); | |
| transition: var(--transition); | |
| } | |
| .input-wrapper:focus-within { | |
| border-color: #555; | |
| background: #1a1a1a; | |
| box-shadow: 0 8px 30px rgba(0, 0, 0, 0.6); | |
| } | |
| .search-icon { | |
| padding: 0 15px; | |
| color: var(--text-secondary); | |
| } | |
| input[type="text"] { | |
| flex: 1; | |
| background: transparent; | |
| border: none; | |
| padding: 14px 0; | |
| font-size: 1.1rem; | |
| color: var(--text-primary); | |
| font-weight: 400; | |
| } | |
| input[type="text"]::placeholder { | |
| color: #555; | |
| } | |
| /* Filter Select - Minimalist */ | |
| select { | |
| background: transparent; | |
| border: none; | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| padding: 0 15px; | |
| cursor: pointer; | |
| transition: color 0.2s; | |
| margin-right: 10px; | |
| border-left: 1px solid var(--border); | |
| } | |
| select:hover { | |
| color: var(--text-primary); | |
| } | |
| /* --- Key Visual Hints --- */ | |
| .shortcuts { | |
| margin-top: 20px; | |
| font-size: 0.85rem; | |
| color: #444; | |
| transition: var(--transition); | |
| } | |
| body.has-results .shortcuts { | |
| opacity: 0; | |
| } | |
| /* --- Results Area --- */ | |
| #results { | |
| width: 100%; | |
| max-width: 680px; | |
| margin-top: 40px; | |
| opacity: 0; | |
| transform: translateY(20px); | |
| transition: opacity 0.4s ease 0.2s, transform 0.4s ease 0.2s; | |
| } | |
| body.has-results #results { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| /* --- Result Card (Apple-style) --- */ | |
| .result-card { | |
| padding: 20px 0; | |
| border-bottom: 1px solid #1a1a1a; | |
| transition: var(--transition); | |
| animation: fadeIn 0.5s ease forwards; | |
| } | |
| .result-card:last-child { | |
| border-bottom: none; | |
| } | |
| /* Source URL */ | |
| .source-pill { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| margin-bottom: 8px; | |
| } | |
| .source-icon { | |
| width: 16px; | |
| height: 16px; | |
| background: #333; | |
| border-radius: 50%; | |
| } | |
| .result-card h3 { | |
| font-size: 1.25rem; | |
| font-weight: 500; | |
| color: #3b82f6; | |
| /* Modern Blue Link */ | |
| margin-bottom: 8px; | |
| letter-spacing: -0.01em; | |
| cursor: pointer; | |
| } | |
| .result-card h3:hover { | |
| text-decoration: underline; | |
| } | |
| .result-card p { | |
| font-size: 0.95rem; | |
| line-height: 1.6; | |
| color: #b0b0b0; | |
| } | |
| .meta-tag { | |
| display: inline-block; | |
| margin-top: 10px; | |
| font-size: 0.75rem; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| background: #1a1a1a; | |
| color: #666; | |
| border: 1px solid #222; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| /* --- Mobile --- */ | |
| @media (max-width: 768px) { | |
| h1 { | |
| font-size: 2rem; | |
| } | |
| body.has-results .search-container { | |
| width: 90%; | |
| max-width: none; | |
| top: 10px; | |
| } | |
| body.has-results main { | |
| padding-top: 90px; | |
| } | |
| .input-wrapper { | |
| padding: 4px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <nav> | |
| <a href="#" class="logo" onclick="resetApp()">Scrap-Dji</a> | |
| </nav> | |
| <main> | |
| <div class="search-container"> | |
| <h1>Que cherchez-vous ?</h1> | |
| <form id="searchForm"> | |
| <div class="input-wrapper"> | |
| <!-- Scalable Vector Graphic Search Icon --> | |
| <div class="search-icon"> | |
| <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | |
| d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> | |
| </svg> | |
| </div> | |
| <input type="text" id="query" placeholder="Rechercher sur le Togo..." autocomplete="off" required /> | |
| <select id="pays"> | |
| <option value="">Tout</option> | |
| <option value="Togo" selected>Togo</option> | |
| <option value="Sénégal">Sénégal</option> | |
| <option value="Mali">Mali</option> | |
| </select> | |
| </div> | |
| </form> | |
| <div class="shortcuts"> | |
| Appuyez sur <strong>Entrée</strong> pour rechercher | |
| </div> | |
| </div> | |
| <div id="results"></div> | |
| </main> | |
| <script> | |
| const form = document.getElementById('searchForm'); | |
| const queryInput = document.getElementById('query'); | |
| const resultsDiv = document.getElementById('results'); | |
| // Focus input on load | |
| window.addEventListener('load', () => queryInput.focus()); | |
| // Reset App State | |
| function resetApp() { | |
| document.body.classList.remove('has-results'); | |
| resultsDiv.innerHTML = ''; | |
| queryInput.value = ''; | |
| queryInput.focus(); | |
| } | |
| form.onsubmit = async function (e) { | |
| e.preventDefault(); | |
| const q = queryInput.value.trim(); | |
| const pays = document.getElementById('pays').value; | |
| if (!q) return; | |
| // Transition UI | |
| document.body.classList.add('has-results'); | |
| // Loading State (Skeleton or Spinner equivalent) | |
| resultsDiv.innerHTML = ` | |
| <div style="text-align:center; padding: 40px; color: #444;"> | |
| <svg width="30" height="30" viewBox="0 0 50 50" style="animation: spin 1s linear infinite;"> | |
| <circle cx="25" cy="25" r="20" fill="none" stroke="#333" stroke-width="4"></circle> | |
| <circle cx="25" cy="25" r="20" fill="none" stroke="#fff" stroke-width="4" stroke-dasharray="80" stroke-dashoffset="60"></circle> | |
| </svg> | |
| <style>@keyframes spin { 100% { transform: rotate(360deg); } }</style> | |
| </div> | |
| `; | |
| let url = `http://localhost:8000/search?q=${encodeURIComponent(q)}`; | |
| if (pays) url += `&pays=${encodeURIComponent(pays)}`; | |
| try { | |
| const res = await fetch(url); | |
| const data = await res.json(); | |
| // Clear loading | |
| resultsDiv.innerHTML = ""; | |
| if (data.length === 0) { | |
| resultsDiv.innerHTML = ` | |
| <div style="text-align:center; color:#555; padding-top:40px;"> | |
| <p>Aucun résultat trouvé pour "${q}"</p> | |
| </div>`; | |
| return; | |
| } | |
| // Render Results with Animation Delay | |
| data.forEach((hit, index) => { | |
| const doc = hit.document || hit; | |
| const domain = new URL(doc.source_url).hostname.replace('www.', ''); | |
| const card = document.createElement('div'); | |
| card.className = 'result-card'; | |
| card.style.animationDelay = `${index * 0.05}s`; | |
| // Truncate text intelligently | |
| const snippet = doc.texte.length > 250 ? doc.texte.substring(0, 250) + "..." : doc.texte; | |
| card.innerHTML = ` | |
| <div class="source-pill"> | |
| <span class="source-icon"></span> | |
| <span>${domain}</span> | |
| </div> | |
| <a href="${doc.source_url}" target="_blank" style="text-decoration:none;"> | |
| <h3>${doc.titre}</h3> | |
| </a> | |
| <p>${snippet}</p> | |
| <div class="meta-tag"> | |
| ${doc.pays} • ${new Date(doc.date).toLocaleDateString()} | |
| </div> | |
| `; | |
| resultsDiv.appendChild(card); | |
| }); | |
| } catch (err) { | |
| console.error(err); | |
| resultsDiv.innerHTML = ` | |
| <div style="color:#ef4444; text-align:center; padding:20px;"> | |
| Erreur de connexion au serveur. | |
| </div>`; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |