Scrap-Dji / frontend /index.html
joel
Initial deployment: Scrap-Dji with API
dfdddb1
<!DOCTYPE html>
<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>