Svenn-Konkurransesøk
by SvennMats
HTML
<div id="svenn-market-analyzer">
<div class="analyzer-container">
<div class="search-section manual-box">
<h3>Sjekk markedet i din kommune</h3>
<p class="search-subtitle">Få full oversikt over konkurrenter, prisnivå og muligheter der du bor.</p>
<div class="manual-grid">
<div class="input-wrapper">
<label class="field-label">Søk og velg Kommune</label>
<input type="text" id="muniSearch" placeholder="F.eks. Trondheim..." autocomplete="off">
<div id="muniResults" class="autocomplete-dropdown"></div>
</div>
<div class="input-wrapper dropdown-wrapper">
<label class="field-label">Velg Bransje</label>
<input type="text" id="industrySearch" placeholder="Velg fra listen..." autocomplete="off" readonly onfocus="this.removeAttribute('readonly');">
<span class="dropdown-arrow">▼</span>
<div id="industryResults" class="autocomplete-dropdown"></div>
</div>
</div>
<button id="executeManualSearch" class="svenn-btn-main">Analyser markedet</button>
</div>
<div id="resultsArea" class="hidden">
<div class="result-card">
<div id="resultHeaderInfo" class="header-info"></div>
<div class="count-section">
<span class="res-label">Konkurrenter i ditt område:</span>
<div id="resCount" class="res-number">0</div>
</div>
<div id="dynamicAnalysis" class="analysis-box"></div>
<div class="market-stats">
<div class="stat-box">
<span class="stat-val" id="countAS">-</span>
<span class="stat-label">Aktive AS</span>
</div>
<div class="stat-box">
<span class="stat-val" id="countENK">-</span>
<span class="stat-label">Aktive ENK</span>
</div>
<div class="stat-box">
<span class="stat-val"...
CSS
:root {
--svenn-orange: #F96529;
--svenn-dark: #241D2D;
--svenn-res-bg: #EDF3E3;
--svenn-bg: #F8F9FA;
--white: #FFFFFF;
--font-h2: 28px;
--font-h3: 24px;
--font-h4: 18px;
--font-body: 16px;
}
body { background-color: #fff; padding: 20px; font-family: 'Magnet', 'Inter', sans-serif; }
#svenn-market-analyzer { color: var(--svenn-dark); max-width: 1000px; margin: 0 auto; }
.analyzer-container { background: var(--white); border-radius: 12px; box-shadow: none; padding: 40px 0; }
/* TYPOGRAFI & LUFT */
h2 { font-size: var(--font-h2); color: var(--svenn-dark); margin: 0 0 10px 0; text-align: center; }
h3 { font-size: var(--font-h3); color: var(--svenn-dark); margin-bottom: 25px; text-align: center; }
.section-container > p, header > p { text-align: center; color: #666; font-size: var(--font-body); margin-bottom: 30px; max-width: 600px; margin-left: auto; margin-right: auto; line-height: 1.5; }
h4 { font-size: var(--font-h4); color: var(--svenn-dark); margin-bottom: 5px; }
/* SECTIONS */
.section-container { margin-top: 60px; padding: 0 30px; }
/* SØKESEKSJON */
.manual-box { background: var(--svenn-bg); padding: 30px; border-radius: 12px; }
.manual-box h3 { font-size: 26px; margin-bottom: 10px; }
.search-subtitle { margin-bottom: 40px; color: #666; font-size: 16px; text-align: center; max-width: 600px; margin-left: auto; margin-right: auto; line-height: 1.5; }
.manual-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.input-wrapper { position: relative; margin-bottom: 15px; }
.field-label { display: block; font-size: 12px; font-weight: bold; margin-bottom: 5px; text-transform: uppercase; color: #777; }
input[type="text"] { width: 100%; padding: 15px; border: 2px solid #EEE; border-radius: 8px; font-size: 16px; box-sizing: border-box; color: var(--svenn-dark); box-shadow: none; -webkit-appearance: none; appearance: none; }
input[type="text"]:focus { border-color: var(--svenn-orange);...
JavaScript
// --- 1. KONFIGURASJON ---
let currentSearchId = 0;
// BRANSJELISTE (DIN NYE OPPDATERTE LISTE)
const industryMap = [
{ label: "Tømrer / Snekker", codes: ['41.000', '43.320', '41.200'], avgPrice: 50000 },
{ label: "Maler / Tapetserer", codes: ['43.340', '43.341'], avgPrice: 25000 },
{ label: "Murer", codes: ['43.910', '43.990'], avgPrice: 60000 },
{ label: "Gulv, Flis & Tapet", codes: ['43.330'], avgPrice: 30000 },
{ label: "Taktekker / Blikkenslager", codes: ['43.410', '43.911', '43.912'], avgPrice: 80000 },
{ label: "Anleggsgartner", codes: ['81.300'], avgPrice: 70000 }
];
// Fjerner punktum for å sammenligne koder trygt
function normalizeCode(code) {
if (!code) return "";
return code.replace(/\./g, '');
}
// Hjelpefunksjon for dato (12 mnd siden) i YYYY-MM-DD format
function getOneYearAgoDate() {
const d = new Date();
d.setFullYear(d.getFullYear() - 1);
return d.toISOString().split('T')[0];
}
// --- FAST FETCH (RACE MODE) ---
async function fetchFastest(targetUrl) {
const encodedUrl = encodeURIComponent(targetUrl);
// Definer alle metodene vi vil prøve
const promises = [
fetch("https://corsproxy.io/?" + encodedUrl).then(async r => {
if(!r.ok) throw new Error("CP failed");
return r.json();
}),
fetch("https://api.allorigins.win/get?url=" + encodedUrl).then(async r => {
if(!r.ok) throw new Error("AO failed");
const data = await r.json();
if (data.contents) {
return typeof data.contents === 'string' ? JSON.parse(data.contents) : data.contents;
}
throw new Error("AO no content");
}),
fetch("https://api.codetabs.com/v1/proxy?quest=" + encodedUrl).then(async r => {
if(!r.ok) throw new Error("CT failed");
return r.json();
})
];
try {
return await Promise.any(promises);
}...