All checks were successful
Build and Deploy BlaaAi / build-and-deploy (push) Successful in 4m38s
- Remove min-height:100vh + padding-bottom:22rem that caused overflow - Replace with calc(100vh - 60px) to center form without scroll - Add html,body height:100% and overflow-x:hidden
543 lines
23 KiB
HTML
543 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="da">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>BlaaAi — Find den bedste annonce</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
fontFamily: { sans: ['Space Grotesk', 'sans-serif'] },
|
||
colors: {
|
||
ink: { DEFAULT: '#09090b', 50: '#f4f4f5', 100: '#e4e4e7', 200: '#27272a', 300: '#3f3f46' }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style>
|
||
html, body { height: 100%; }
|
||
body { background: #fafaf9; color: #0c0a09; font-family: 'Space Grotesk', sans-serif; overflow-x: hidden; }
|
||
.card-enter { animation: fadeUp .25s ease both; }
|
||
@keyframes fadeUp { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
|
||
.spinner { animation: spin .9s linear infinite; }
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
.score-fill { background: #0c0a09; transition: width .6s cubic-bezier(.4,0,.2,1); }
|
||
@keyframes shimmer { 0%{background-position:-400% 0} 100%{background-position:400% 0} }
|
||
.skeleton { background:linear-gradient(90deg,#f0efed 25%,#e8e5e1 50%,#f0efed 75%);background-size:400% 100%;animation:shimmer 1.6s ease-in-out infinite;border-radius:4px; }
|
||
input, textarea {
|
||
background: #fff;
|
||
border: 1px solid #e7e5e4;
|
||
color: #0c0a09;
|
||
outline: none;
|
||
transition: border-color .15s;
|
||
}
|
||
input:focus, textarea:focus { border-color: #a8a29e; }
|
||
input::placeholder, textarea::placeholder { color: #a8a29e; }
|
||
.btn-primary {
|
||
background: #0c0a09; color: #fafaf9;
|
||
font-weight: 600; letter-spacing: -.01em;
|
||
transition: background .15s, transform .1s;
|
||
}
|
||
.btn-primary:hover:not(:disabled) { background: #292524; }
|
||
.btn-primary:active:not(:disabled) { transform: scale(.98); }
|
||
.btn-primary:disabled { opacity: .4; cursor: default; }
|
||
.card {
|
||
background: #fff;
|
||
border: 1px solid #e7e5e4;
|
||
transition: border-color .15s, box-shadow .15s;
|
||
}
|
||
.card:hover { border-color: #d6d3d1; box-shadow: 0 1px 8px rgba(0,0,0,.05); }
|
||
/* Video modal */
|
||
#video-modal { display:none; position:fixed; inset:0; z-index:50; align-items:center; justify-content:center; }
|
||
#video-modal.open { display:flex; }
|
||
#video-backdrop { position:absolute; inset:0; background:rgba(0,0,0,.7); backdrop-filter:blur(4px); }
|
||
#video-box {
|
||
position:relative; z-index:1; width:min(860px,92vw);
|
||
background:#0f172a; border-radius:16px; overflow:hidden;
|
||
box-shadow:0 24px 80px rgba(0,0,0,.6);
|
||
animation: modalIn .2s ease both;
|
||
}
|
||
@keyframes modalIn { from{opacity:0;transform:scale(.96)} to{opacity:1;transform:scale(1)} }
|
||
#video-box video { display:block; width:100%; }
|
||
#video-close {
|
||
position:absolute; top:12px; right:12px; z-index:2;
|
||
background:rgba(255,255,255,.1); border:none; color:#fff;
|
||
width:32px; height:32px; border-radius:50%; cursor:pointer;
|
||
font-size:1rem; display:flex; align-items:center; justify-content:center;
|
||
transition:background .15s;
|
||
}
|
||
#video-close:hover { background:rgba(255,255,255,.25); }
|
||
.btn-help {
|
||
display:inline-flex; align-items:center; gap:6px;
|
||
font-size:.75rem; font-weight:500; color:#78716c;
|
||
border:1px solid #e7e5e4; border-radius:8px;
|
||
padding:6px 12px; cursor:pointer; background:transparent;
|
||
transition:color .15s, border-color .15s;
|
||
}
|
||
.btn-help:hover { color:#0c0a09; border-color:#a8a29e; }
|
||
.tag-top { background:#f0fdf4; color:#15803d; border:1px solid #bbf7d0; }
|
||
.tag-good { background:#eff6ff; color:#1d4ed8; border:1px solid #bfdbfe; }
|
||
.tag-mid { background:#fefce8; color:#a16207; border:1px solid #fde68a; }
|
||
a.annonce-link {
|
||
color: #a8a29e; font-size:.75rem; letter-spacing:.02em;
|
||
text-decoration: none; transition: color .15s;
|
||
}
|
||
a.annonce-link:hover { color: #0c0a09; }
|
||
header { border-bottom: 1px solid #e7e5e4; background: #fafaf9; }
|
||
</style>
|
||
</head>
|
||
<body style="
|
||
background-color: #fafaf9;
|
||
background-image: url('/static/background.png');
|
||
background-repeat: no-repeat;
|
||
background-position: bottom center;
|
||
background-size: clamp(600px, 80vw, 1100px) auto;
|
||
background-attachment: fixed;
|
||
">
|
||
|
||
<!-- Top bar (always visible) -->
|
||
<header>
|
||
<div class="max-w-2xl mx-auto px-6 py-4 flex items-center justify-between">
|
||
<button onclick="resetForm()" class="flex items-center gap-2 hover:opacity-60 transition-opacity">
|
||
<span style="font-size:1.1rem;line-height:1">◈</span>
|
||
<span class="font-semibold tracking-tight">BlaaAi</span>
|
||
</button>
|
||
<div class="flex items-center gap-3">
|
||
<span id="header-status" class="text-xs" style="color:#a8a29e"></span>
|
||
<button class="btn-help" onclick="openVideoModal()">
|
||
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8" fill="currentColor" stroke="none"/>
|
||
</svg>
|
||
Video hjælp
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ── VIDEO MODAL ── -->
|
||
<div id="video-modal" role="dialog" aria-modal="true" aria-label="Video hjælp">
|
||
<div id="video-backdrop" onclick="closeVideoModal()"></div>
|
||
<div id="video-box">
|
||
<button id="video-close" onclick="closeVideoModal()" aria-label="Luk video">✕</button>
|
||
<video id="help-video" controls preload="metadata"
|
||
poster=""
|
||
src="/static/blaaai_tutorial.mp4">
|
||
</video>
|
||
</div>
|
||
</div>
|
||
|
||
<main class="max-w-2xl mx-auto px-6" style="display:flex;flex-direction:column;justify-content:center;padding-top:2rem;padding-bottom:4rem;min-height:calc(100vh - 60px)">
|
||
|
||
<!-- ── FORM ── -->
|
||
<section id="form-section" class="py-12 relative">
|
||
<p class="text-xs font-medium tracking-widest uppercase mb-5" style="color:#a8a29e">AI-powered annonce analyse</p>
|
||
<h1 class="text-4xl font-bold tracking-tight leading-tight mb-10" style="letter-spacing:-.03em">
|
||
Find den bedste<br>DBA-annonce.
|
||
</h1>
|
||
|
||
<div class="space-y-3">
|
||
<input
|
||
id="url-input"
|
||
type="url"
|
||
placeholder="https://www.dba.dk/mobility/search/…"
|
||
class="w-full px-4 py-3 rounded-lg text-sm"
|
||
/>
|
||
|
||
<div id="prefs-section" class="hidden">
|
||
<textarea
|
||
id="prefs-input"
|
||
rows="2"
|
||
placeholder="Dine præferencer — fx 'ingen franske biler, helst automatgear'"
|
||
class="w-full px-4 py-3 rounded-lg text-sm resize-none"
|
||
></textarea>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-3 pt-1">
|
||
<button
|
||
id="submit-btn"
|
||
onclick="submitSearch()"
|
||
class="btn-primary px-6 py-3 rounded-lg text-sm flex items-center gap-2"
|
||
>
|
||
<span>Analyser</span>
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
||
</svg>
|
||
</button>
|
||
<button onclick="togglePrefs()" id="prefs-btn"
|
||
class="text-sm px-4 py-3 rounded-lg transition-colors"
|
||
style="color:#78716c;border:1px solid #e7e5e4">
|
||
+ Præferencer
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-xs mt-8 leading-relaxed" style="color:#a8a29e">
|
||
Paste en DBA søge-URL — AI'en gennemgår alle annoncer og rangerer dem efter pris, stand og kvalitet.<br>
|
||
<span style="color:#d6d3d1">AI kan tage fejl. Brug det som inspiration, ikke som facit.</span>
|
||
</p>
|
||
</section>
|
||
|
||
<!-- ── STATUS ── -->
|
||
<section id="status-section" class="hidden pt-8 pb-20">
|
||
<div class="flex items-center gap-3 mb-1">
|
||
<svg class="spinner w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" style="color:#a8a29e">
|
||
<circle class="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"/>
|
||
<path class="opacity-80" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/>
|
||
</svg>
|
||
<p id="status-text" class="font-medium text-sm">Henter annoncer…</p>
|
||
<span id="status-progress" class="text-xs ml-auto" style="color:#a8a29e"></span>
|
||
</div>
|
||
<p id="status-sub" class="text-xs mb-6 ml-7" style="color:#a8a29e;min-height:1.2em"></p>
|
||
<div id="skeleton-container" class="space-y-2"></div>
|
||
</section>
|
||
|
||
<!-- ── RESULTS ── -->
|
||
<section id="results-section" class="hidden pt-8 pb-20">
|
||
|
||
<div class="flex items-baseline justify-between mb-8">
|
||
<div>
|
||
<h2 class="text-xl font-bold tracking-tight">Resultater</h2>
|
||
<p id="result-count" class="text-xs mt-1" style="color:#a8a29e"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="listings-container" class="space-y-2"></div>
|
||
|
||
<!-- Email -->
|
||
<div class="mt-12 pt-8" style="border-top:1px solid #e7e5e4">
|
||
<p class="text-sm font-medium mb-3">Send top-10 på email</p>
|
||
<div class="flex gap-2">
|
||
<input
|
||
id="email-input"
|
||
type="email"
|
||
placeholder="din@email.dk"
|
||
class="flex-1 px-4 py-2.5 rounded-lg text-sm"
|
||
/>
|
||
<button
|
||
onclick="sendEmail()"
|
||
id="email-btn"
|
||
class="btn-primary px-5 py-2.5 rounded-lg text-sm"
|
||
>Send</button>
|
||
</div>
|
||
<p id="email-status" class="text-xs mt-2 hidden" style="color:#71717a"></p>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<script>
|
||
function openVideoModal() {
|
||
document.getElementById('video-modal').classList.add('open');
|
||
document.body.style.overflow = 'hidden';
|
||
const v = document.getElementById('help-video');
|
||
v.currentTime = 0;
|
||
v.play();
|
||
}
|
||
function closeVideoModal() {
|
||
document.getElementById('video-modal').classList.remove('open');
|
||
document.body.style.overflow = '';
|
||
const v = document.getElementById('help-video');
|
||
v.pause();
|
||
v.currentTime = 0;
|
||
}
|
||
document.getElementById('help-video').addEventListener('ended', closeVideoModal);
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key === 'Escape') closeVideoModal();
|
||
});
|
||
|
||
const PRELOAD_ID = {{ search_id | tojson if search_id is defined else 'null' }};
|
||
let currentSearchId = null;
|
||
let pollInterval = null;
|
||
let skeletonsRendered = false;
|
||
let msgInterval = null;
|
||
|
||
const statusMessages = {
|
||
queued: ["Stiller i kø…", "Venter på ledig plads"],
|
||
fetching: ["Henter annoncer fra DBA…", "Indlæser titler, priser og beskrivelser"],
|
||
scoring: ["AI analyserer annoncerne…", ""],
|
||
ready: ["Analyse færdig", ""],
|
||
error: ["Noget gik galt", "Tjek søgelinket og prøv igen"],
|
||
};
|
||
|
||
const funMessages = [
|
||
"Vurderer bilers pålidelighed og historik…",
|
||
"Sammenligner kilometertal og prisforhold…",
|
||
"Tjekker om sælger lyder troværdig…",
|
||
"Vurderer forventede vedligeholdelsesomkostninger…",
|
||
"Sammenligner mod aktuelle markedspriser…",
|
||
"Analyserer annoncebeskrivelserne for røde flag…",
|
||
"Overvejer årstal og udstyrsgrad…",
|
||
"Ranker efter kvalitet og pris…",
|
||
"Tjekker km-stand mod forventet for alderen…",
|
||
"Næsten færdig — finpudser rangeringen…",
|
||
];
|
||
|
||
function togglePrefs() {
|
||
const s = document.getElementById("prefs-section");
|
||
const b = document.getElementById("prefs-btn");
|
||
s.classList.toggle("hidden");
|
||
b.textContent = s.classList.contains("hidden") ? "+ Præferencer" : "− Præferencer";
|
||
}
|
||
|
||
async function submitSearch() {
|
||
const url = document.getElementById("url-input").value.trim();
|
||
const prefs = document.getElementById("prefs-input").value.trim();
|
||
|
||
if (!url.startsWith("https://www.dba.dk")) {
|
||
alert("Indsæt et gyldigt DBA søgelink");
|
||
return;
|
||
}
|
||
|
||
const btn = document.getElementById("submit-btn");
|
||
btn.disabled = true;
|
||
btn.innerHTML = `<svg class="spinner w-4 h-4" fill="none" viewBox="0 0 24 24"><circle class="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"/><path class="opacity-80" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/></svg><span>Sender…</span>`;
|
||
|
||
try {
|
||
const res = await fetch("/api/searches", {
|
||
method: "POST",
|
||
headers: {"Content-Type": "application/json"},
|
||
body: JSON.stringify({url, prefs}),
|
||
});
|
||
const data = await res.json();
|
||
currentSearchId = data.id;
|
||
|
||
showStatus();
|
||
startPolling(data.id);
|
||
} catch(e) {
|
||
alert("Fejl: " + e.message);
|
||
btn.disabled = false;
|
||
btn.innerHTML = `<span>Analyser</span><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>`;
|
||
}
|
||
}
|
||
|
||
function showStatus() {
|
||
document.getElementById("form-section").classList.add("hidden");
|
||
document.getElementById("status-section").classList.remove("hidden");
|
||
document.getElementById("results-section").classList.add("hidden");
|
||
}
|
||
|
||
function startPolling(id) {
|
||
history.pushState(null, "", `/search/${id}`);
|
||
pollInterval = setInterval(() => poll(id), 2000);
|
||
}
|
||
|
||
function startFunMessages() {
|
||
let i = 0;
|
||
const el = document.getElementById("status-sub");
|
||
el.textContent = funMessages[0];
|
||
msgInterval = setInterval(() => {
|
||
i = (i + 1) % funMessages.length;
|
||
el.textContent = funMessages[i];
|
||
}, 3500);
|
||
}
|
||
|
||
function renderSkeletons(count) {
|
||
if (skeletonsRendered) return;
|
||
skeletonsRendered = true;
|
||
const container = document.getElementById("skeleton-container");
|
||
container.innerHTML = "";
|
||
for (let i = 0; i < count; i++) {
|
||
const card = document.createElement("div");
|
||
card.className = "card rounded-xl p-5";
|
||
card.style.animationDelay = `${i * 40}ms`;
|
||
card.innerHTML = `
|
||
<div class="flex items-start gap-4">
|
||
<div class="skeleton shrink-0 mt-1" style="width:1rem;height:.75rem;border-radius:3px"></div>
|
||
<div class="flex-1 space-y-2.5">
|
||
<div class="flex gap-2">
|
||
<div class="skeleton" style="height:.8rem;width:7rem;border-radius:3px"></div>
|
||
<div class="skeleton" style="height:.8rem;width:4rem;border-radius:3px"></div>
|
||
</div>
|
||
<div class="skeleton" style="height:.4rem;width:100%;border-radius:9999px"></div>
|
||
<div class="skeleton" style="height:.7rem;width:75%;border-radius:3px"></div>
|
||
<div class="skeleton" style="height:.7rem;width:55%;border-radius:3px"></div>
|
||
</div>
|
||
<div class="shrink-0 ml-2 space-y-2">
|
||
<div class="skeleton" style="height:.9rem;width:5rem;border-radius:3px"></div>
|
||
<div class="skeleton" style="height:.7rem;width:3rem;border-radius:3px;margin-left:auto"></div>
|
||
</div>
|
||
</div>`;
|
||
container.appendChild(card);
|
||
}
|
||
}
|
||
|
||
function stopFunMessages() {
|
||
if (msgInterval) { clearInterval(msgInterval); msgInterval = null; }
|
||
}
|
||
|
||
async function poll(id) {
|
||
try {
|
||
const res = await fetch(`/api/searches/${id}`);
|
||
const data = await res.json();
|
||
|
||
const total = data.listing_count || 0;
|
||
const scored = data.scored_count || 0;
|
||
|
||
// Show skeletons as soon as we know the count
|
||
if (total > 0) renderSkeletons(total);
|
||
|
||
// Update status text
|
||
const [main] = statusMessages[data.status] || ["Arbejder…"];
|
||
document.getElementById("status-text").textContent = main;
|
||
document.getElementById("header-status").textContent = data.status === "ready" ? "" : main;
|
||
|
||
// Progress counter during scoring
|
||
if (data.status === "scoring" && total > 0) {
|
||
document.getElementById("status-progress").textContent = `${scored}/${total}`;
|
||
if (!msgInterval) startFunMessages();
|
||
} else if (data.status !== "scoring") {
|
||
const [, sub] = statusMessages[data.status] || ["", ""];
|
||
document.getElementById("status-sub").textContent = sub;
|
||
}
|
||
|
||
if (data.status === "ready") {
|
||
stopFunMessages();
|
||
clearInterval(pollInterval);
|
||
showResults(data);
|
||
} else if (data.status === "error") {
|
||
stopFunMessages();
|
||
clearInterval(pollInterval);
|
||
}
|
||
} catch(e) {
|
||
console.error("Poll fejl:", e);
|
||
}
|
||
}
|
||
|
||
function showResults(data) {
|
||
document.getElementById("status-section").classList.add("hidden");
|
||
document.getElementById("results-section").classList.remove("hidden");
|
||
document.getElementById("header-status").textContent = "";
|
||
|
||
const listings = data.listings || [];
|
||
document.getElementById("result-count").textContent =
|
||
`${listings.length} annoncer analyseret · sorteret efter AI-score`;
|
||
|
||
const container = document.getElementById("listings-container");
|
||
container.innerHTML = "";
|
||
|
||
listings.forEach((item, i) => {
|
||
const score = item.ai_score || 0;
|
||
const pct = Math.round(score * 10);
|
||
const warn = item.ai_warnings
|
||
? `<p class="text-xs mt-2" style="color:#dc2626">↑ ${item.ai_warnings}</p>` : "";
|
||
const qualityFlags = (item.data_quality_flags || []).length > 0
|
||
? `<div class="mt-2 rounded-lg px-3 py-2" style="background:#fff7ed;border:1px solid #fed7aa">
|
||
${item.data_quality_flags.map(f => `<p class="text-xs" style="color:#c2410c">${f}</p>`).join("")}
|
||
</div>` : "";
|
||
const tag = rankTag(score);
|
||
|
||
const card = document.createElement("div");
|
||
card.className = "card rounded-xl p-5 card-enter";
|
||
card.style.animationDelay = `${i * 30}ms`;
|
||
card.innerHTML = `
|
||
<div class="flex items-start gap-4">
|
||
<span class="text-xs font-mono pt-0.5 shrink-0" style="color:#d6d3d1;min-width:1.5rem">${i+1}</span>
|
||
<div class="flex-1 min-w-0">
|
||
<div class="flex items-center gap-2 mb-2 flex-wrap">
|
||
<span class="font-semibold text-sm tracking-tight">${item.name}</span>
|
||
<span class="text-xs" style="color:#a8a29e">${item.description || ""}</span>
|
||
${tag}
|
||
</div>
|
||
<div class="flex items-center gap-3 mb-3">
|
||
<div class="flex-1 rounded-full" style="background:#f5f5f4;height:3px">
|
||
<div class="score-fill rounded-full" style="height:3px;width:${pct}%"></div>
|
||
</div>
|
||
<span class="text-xs font-bold tabular-nums shrink-0">${score.toFixed(1)}<span style="color:#d6d3d1">/10</span></span>
|
||
</div>
|
||
<p class="text-xs leading-relaxed" style="color:#78716c">${item.ai_reason || ""}</p>
|
||
${warn}
|
||
${qualityFlags}
|
||
</div>
|
||
<div class="shrink-0 text-right ml-2">
|
||
<p class="font-semibold text-sm tabular-nums">${Number(item.price_dkk || 0).toLocaleString("da-DK")}<span class="text-xs font-normal ml-0.5" style="color:#a8a29e">kr</span></p>
|
||
<a href="${item.url}" target="_blank" class="annonce-link mt-1.5 block">Se →</a>
|
||
</div>
|
||
</div>`;
|
||
container.appendChild(card);
|
||
});
|
||
}
|
||
|
||
function rankTag(score) {
|
||
if (score >= 8.5) return '<span class="tag-top text-xs px-2 py-0.5 rounded font-medium">Topvalg</span>';
|
||
if (score >= 7.5) return '<span class="tag-good text-xs px-2 py-0.5 rounded font-medium">Godt køb</span>';
|
||
if (score >= 6.5) return '<span class="tag-mid text-xs px-2 py-0.5 rounded font-medium">Middel</span>';
|
||
return '';
|
||
}
|
||
|
||
async function sendEmail() {
|
||
const email = document.getElementById("email-input").value.trim();
|
||
const btn = document.getElementById("email-btn");
|
||
const status = document.getElementById("email-status");
|
||
|
||
if (!email) { alert("Indtast en email-adresse"); return; }
|
||
if (!currentSearchId) return;
|
||
|
||
btn.disabled = true;
|
||
btn.textContent = "Sender…";
|
||
|
||
try {
|
||
const res = await fetch(`/api/searches/${currentSearchId}/email`, {
|
||
method: "POST",
|
||
headers: {"Content-Type": "application/json"},
|
||
body: JSON.stringify({email}),
|
||
});
|
||
const data = await res.json();
|
||
status.classList.remove("hidden");
|
||
if (res.ok) {
|
||
status.style.color = "#4ade80";
|
||
status.textContent = `Sendt til ${email}`;
|
||
} else {
|
||
status.style.color = "#f87171";
|
||
status.textContent = data.detail || "Fejl ved afsendelse";
|
||
btn.disabled = false; btn.textContent = "Send";
|
||
}
|
||
} catch(e) {
|
||
status.classList.remove("hidden");
|
||
status.style.color = "#f87171";
|
||
status.textContent = "Netværksfejl — prøv igen";
|
||
btn.disabled = false; btn.textContent = "Send";
|
||
}
|
||
}
|
||
|
||
function resetForm() {
|
||
if (pollInterval) clearInterval(pollInterval);
|
||
stopFunMessages();
|
||
skeletonsRendered = false;
|
||
currentSearchId = null;
|
||
document.getElementById("form-section").classList.remove("hidden");
|
||
document.getElementById("status-section").classList.add("hidden");
|
||
document.getElementById("results-section").classList.add("hidden");
|
||
document.getElementById("skeleton-container").innerHTML = "";
|
||
document.getElementById("status-progress").textContent = "";
|
||
document.getElementById("listings-container").innerHTML = "";
|
||
document.getElementById("url-input").value = "";
|
||
document.getElementById("prefs-input").value = "";
|
||
document.getElementById("email-input").value = "";
|
||
document.getElementById("header-status").textContent = "";
|
||
document.getElementById("email-status").classList.add("hidden");
|
||
const btn = document.getElementById("submit-btn");
|
||
btn.disabled = false;
|
||
btn.innerHTML = `<span>Analyser</span><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"/></svg>`;
|
||
history.pushState(null, "", "/");
|
||
}
|
||
|
||
if (PRELOAD_ID) {
|
||
currentSearchId = PRELOAD_ID;
|
||
showStatus();
|
||
poll(PRELOAD_ID).then(() => {
|
||
if (!document.getElementById("status-section").classList.contains("hidden")) {
|
||
startPolling(PRELOAD_ID);
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|