#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
bilstolbarn.no — statisk sidebygger
Leser JollyRoom-produktfeeden (CSV, tab-separert, enkeltfnutt som quote)
og genererer ferdige HTML-sider, sitemap, robots.txt for alle bilstol-kategorier.

Kjør på nytt hver gang du laster ned ny feed:
    python3 bygg.py feed.csv site
"""
import csv, html, json, os, re, shutil, sys
from datetime import date

csv.field_size_limit(10_000_000)

FEED = sys.argv[1] if len(sys.argv) > 1 else "feed.csv"
OUT = sys.argv[2] if len(sys.argv) > 2 else "site"

SITE = "https://bilstolbarn.no"
GSC_TAG = "JG7_UqLHYgt9JlxRKApbi3hIUUqAfPulipWWa10qzB8"
today = date.today()
MND = {1:"januar",2:"februar",3:"mars",4:"april",5:"mai",6:"juni",7:"juli",
       8:"august",9:"september",10:"oktober",11:"november",12:"desember"}
OPPDATERT = f"{today.day}. {MND[today.month]} {today.year}"

if os.path.exists(OUT):
    shutil.rmtree(OUT)
os.makedirs(OUT)
os.makedirs(os.path.join(OUT, "artikler"))
os.makedirs(os.path.join(OUT, "assets"))

# ─────────────────────────────────────────────────────────────
# 1. Les feed og filtrer til bilstol-relevante kategorier
# ─────────────────────────────────────────────────────────────
def cat_match(cat):
    """Returner slug for kategori-siden en rad hører til, eller None."""
    if not cat:
        return None
    if cat.startswith("Bilstoler - Babybilstoler"):
        if "Bilstolposer" in cat:
            return "tilbehor"
        return "babybilstol"
    if cat == "Bilstoler - Bilstoler - Bakovervendte":
        return "bakovervendt"
    if cat == "Bilstoler - Bilstoler - Vendbare":
        return "vendbar"
    if cat == "Bilstoler - Bilstoler - I Size":
        return "isize"
    if cat in ("Bilstoler - Bilstoler - ISOFIX", "Bilstoler - Bilstoler"):
        return "vendbar"  # kombinerte/isofix-vendbare modeller uten egen finkategori
    if cat.startswith("Bilstoler - Beltestoler") or cat == "Bilstoler - Beltestoler":
        return "beltestol"
    if cat == "Bilstoler - Bilstolpakker":
        return "bilstolpakke"
    if cat == "Bilstoler - Belteputer":
        return "belteputer"
    if "Bilstolsadaptere" in cat:
        return "adapter"
    if cat.startswith("Bilstoler - Tilbehør"):
        return "tilbehor"
    return None

rows_by_slug = {}
brands_seen = set()
total = 0
with open(FEED, encoding="utf-8") as f:
    reader = csv.DictReader(f, delimiter="\t", quotechar="'")
    for row in reader:
        cat = row.get("Category") or ""
        slug = cat_match(cat)
        if not slug:
            continue
        try:
            price = float(row.get("Price") or 0)
        except ValueError:
            price = 0
        try:
            orig = float(row.get("OriginalPrice") or 0)
        except ValueError:
            orig = 0
        if row.get("Instock", "").strip().lower() not in ("no", "yes", "1", "true"):
            pass  # felt "Instock" i denne feeden ser ut til å angi land ('no'); vi filtrerer ikke bort på dette
        item = {
            "sku": row.get("SKU", ""),
            "name": (row.get("Name") or "").strip(),
            "desc": (row.get("Description") or "").strip(),
            "cat": cat,
            "price": price,
            "orig": orig,
            "currency": row.get("Currency", "NOK"),
            "url": row.get("ProductUrl", ""),
            "img": row.get("ImageUrl", ""),
            "track": row.get("TrackingUrl", ""),
            "brand": (row.get("Brand") or "").strip() or "Ukjent",
        }
        if not item["name"] or price <= 0 or not item["track"]:
            continue
        rows_by_slug.setdefault(slug, []).append(item)
        brands_seen.add(item["brand"])
        total += 1

for slug in rows_by_slug:
    rows_by_slug[slug].sort(key=lambda r: r["price"])

print("Importerte produkter per kategori:")
for slug, items in sorted(rows_by_slug.items(), key=lambda x: -len(x[1])):
    print(f"  {slug}: {len(items)}")
print("TOTALT:", total, "| merker:", len(brands_seen))

# ─────────────────────────────────────────────────────────────
# 2. Kategori-metadata (redaksjonelt innhold, SEO/AEO)
# ─────────────────────────────────────────────────────────────
KATEGORIER = {
  "babybilstol": {
    "tittel": "Babybilstol",
    "h1": "Babybilstoler sammenlignet",
    "meta_title": "Babybilstol 2026 – Sammenlign priser på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Alle babybilstoler (gruppe 0+) hos norske nettbutikker samlet på én side. Sammenlign pris, merke og godkjenning. Oppdatert {OPPDATERT}.",
    "kicker": "0–13 kg · fra fødsel",
    "ingress": (
      "En babybilstol (også kalt skålstol eller «infant carrier») er den første bilstolen de fleste "
      "barn bruker, fra de kommer hjem fra sykehuset. Den monteres alltid bakovervendt, enten med "
      "bilbeltet eller på en egen ISOFIX-base som blir stående fast i bilen. Under har vi samlet "
      "modeller fra norske nettbutikker med pris og lenke, sortert etter pris."
    ),
    "fakta": [
      ("Alltid bakovervendt", "Alle babybilstoler monteres bakovervendt. Det er også lovpålagt å sikre spedbarn bakovervendt til minst 15 måneder etter i-Size-regelverket (ECE R129)."),
      ("i-Size vs. R44", "Nyere babybilstoler er godkjent etter i-Size (ECE R129) og måles etter barnets lengde, typisk 40–75 cm. Eldre R44-godkjente stoler måles etter vekt, opptil ca. 13 kg. R44-stoler er ikke i salg lenger, men fortsatt lovlige å bruke."),
      ("Base er ikke alltid inkludert", "Mange modeller selges uten ISOFIX-base — sjekk om basen følger med eller må kjøpes separat, siden dette ofte utgjør en stor del av totalprisen."),
    ],
    "faq": [
      ("Hvor lenge kan barnet sitte i en babybilstol?", "De fleste babybilstoler er godkjent til ca. 75–87 cm eller rundt 13 kg, avhengig av modell. De fleste barn vokser ut av en babybilstol i løpet av det første leveåret."),
      ("Må jeg kjøpe egen base?", "Nei, de fleste babybilstoler kan monteres med bilbeltet. En ISOFIX-base gjør montering enklere og tryggere, men koster som regel ekstra."),
      ("Kan babybilstolen brukes i forsetet?", "Kun hvis airbagen i forsetet er slått av. En bakovervendt bilstol skal aldri brukes foran et aktivt kollisjonspute ifølge Trygg Trafikk og bilprodusentene."),
    ],
  },
  "bakovervendt": {
    "tittel": "Bakovervendt bilstol",
    "h1": "Bakovervendte bilstoler sammenlignet",
    "meta_title": "Bakovervendt bilstol 2026 – Sammenlign pris på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Bakovervendte bilstoler for større barn (ca. 9 mnd–4/7 år) samlet fra norske nettbutikker. Sammenlign pris og merke. Oppdatert {OPPDATERT}.",
    "kicker": "Ca. 9 mnd–7 år · lengst mulig bakovervendt",
    "ingress": (
      "Både NAF og Trygg Trafikk anbefaler at barn sitter bakovervendt så lenge som mulig — minimum til "
      "fylte 4 år. Disse stolene er laget for å romme større barn bakovervendt enn en vanlig babybilstol, "
      "gjerne opp til 105 cm, 18 kg, eller i noen tilfeller helt til 5–7-årsalder."
    ),
    "fakta": [
      ("Hvorfor bakovervendt er tryggest", "Kollisjonstester og reelle ulykker viser at bakovervendt sitteplassering gir betydelig bedre beskyttelse av nakke og rygg ved frontkollisjon, som er den vanligste ulykkestypen."),
      ("i-Size-godkjenning", "De fleste av disse stolene er godkjent etter ECE R129 (i-Size) og angis etter barnets høyde i stedet for vekt, med obligatorisk sidekollisjonstest."),
      ("Unntak finnes", "Noen stoler med spesiell airbagteknologi i selve stolen tillater forovervendt bruk allerede fra ca. 15 måneder — dette er unntaket, ikke hovedregelen."),
    ],
    "faq": [
      ("Når må jeg snu bilstolen?", "Det er ikke noe krav om å snu bilstolen tidlig — tvert imot anbefales det å vente. Loven krever bakovervendt til minst 15 måneder, men NAF anbefaler minimum 4 år."),
      ("Kan disse brukes fra fødsel?", "Noen kombinasjonsstoler i denne kategorien dekker fra fødsel til rundt 4–7 år, mens andre starter fra ca. 6 måneder. Sjekk høyde-/vektspennet på hver enkelt modell."),
      ("Er bakovervendt lovlig i forsetet?", "Kun dersom airbagen i forsetet er deaktivert. Ellers skal bakovervendte stoler alltid stå i baksetet."),
    ],
  },
  "vendbar": {
    "tittel": "Vendbar bilstol",
    "h1": "Vendbare bilstoler sammenlignet",
    "meta_title": "Vendbar bilstol 2026 – Sammenlign pris på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Vendbare (roterende) bilstoler samlet fra norske nettbutikker. Sammenlign pris, merke og funksjoner. Oppdatert {OPPDATERT}.",
    "kicker": "360°-rotasjon · enklere i- og utstigning",
    "ingress": (
      "Vendbare bilstoler kan roteres, ofte 360 grader, slik at det blir enklere å få barnet inn og ut av "
      "bilen uten å bøye seg langt inn i kupeen. De fleste kan brukes både bakovervendt og forovervendt "
      "i samme stol, og monteres nesten alltid med ISOFIX."
    ),
    "fakta": [
      ("Rotasjonen er en komfortfunksjon", "Selve rotasjonsmekanismen påvirker ikke sikkerheten — det som betyr noe er at stolen er riktig godkjent og montert. Rotasjon gjør derimot hverdagsbruken enklere, spesielt i trange parkeringsplasser."),
      ("ISOFIX og støtteben", "De aller fleste vendbare stoler festes med ISOFIX og har et støtteben mot gulvet, som forhindrer at stolen roterer forover ved en kollisjon."),
      ("Ofte lang brukstid", "Mange vendbare i-Size-modeller er laget for å vare fra ca. 6 måneder til 4 år bakovervendt, og videre forovervendt opp mot 105 cm."),
    ],
    "faq": [
      ("Er vendbare bilstoler like trygge som andre typer?", "Ja, sikkerheten avgjøres av godkjenningen (i-Size/ECE R129) og riktig montering, ikke av om stolen roterer eller ikke."),
      ("Kan jeg bruke en vendbar stol fra fødsel?", "De fleste kan brukes fra ca. 6 måneder eller 40 cm, avhengig av modell — sjekk spesifikasjonene på den enkelte stolen."),
      ("Trenger jeg spesielle seter i bilen?", "De fleste nyere biler har i-Size-godkjente ISOFIX-punkter som passer til vendbare stoler. Sjekk bilens håndbok eller stolens typeliste ved usikkerhet."),
    ],
  },
  "isize": {
    "tittel": "i-Size bilstol",
    "h1": "i-Size-godkjente bilstoler sammenlignet",
    "meta_title": "i-Size bilstol 2026 – Sammenlign pris på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Alle i-Size-godkjente (ECE R129) bilstoler samlet fra norske nettbutikker, med pris og lenke. Oppdatert {OPPDATERT}.",
    "kicker": "ECE R129 · klassifisert etter høyde",
    "ingress": (
      "i-Size (ECE-regulativ 129) er dagens standard for godkjenning av bilstoler i EU/EØS. I motsetning "
      "til den eldre R44-standarden, som klassifiserer stoler etter barnets vekt, klassifiserer i-Size "
      "stolene etter barnets høyde og stiller krav om obligatorisk sidekollisjonstest."
    ),
    "fakta": [
      ("Høyde i stedet for vekt", "i-Size deler ikke inn i grupper (0+/1/2/3) slik R44 gjør. I stedet er hver stol godkjent for et bestemt høydespenn, som ofte gir en tryggere match mellom barn og stol."),
      ("Krav om bakovervendt til 15 måneder", "Regelverket krever at produsenten tilbyr mulighet for bakovervendt sikring til barnet er minst 15 måneder gammelt."),
      ("Kun ISOFIX", "i-Size-stoler festes utelukkende med ISOFIX (pluss støtteben eller topptether), ikke med bilbelte alene, noe som reduserer risikoen for feilmontering."),
    ],
    "faq": [
      ("Er i-Size bedre enn R44?", "i-Size er en nyere og mer grundig testet standard, blant annet med obligatorisk sidekollisjonstest. R44-stoler i god stand er fortsatt lovlige, men produseres ikke lenger."),
      ("Passer i-Size-stoler i alle biler?", "De fleste biler produsert etter ca. 2013 har i-Size-godkjente ISOFIX-punkter. Sjekk bilens håndbok ved tvil."),
      ("Hva betyr høydespennet på stolen?", "Det angir hvilken høyde på barnet stolen er testet og godkjent for — ikke alder. Bruk alltid stolens eget mål, ikke barnets alder, for å avgjøre om det er tid for å bytte stol."),
    ],
  },
  "beltestol": {
    "tittel": "Beltestol",
    "h1": "Beltestoler sammenlignet",
    "meta_title": "Beltestol barn 2026 – Sammenlign pris på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Beltestoler (med rygg og hodestøtte) for større barn samlet fra norske nettbutikker. Sammenlign pris og merke. Oppdatert {OPPDATERT}.",
    "kicker": "Ca. 100–150 cm · bruker bilens sikkerhetsbelte",
    "ingress": (
      "En beltestol har rygg, hodestøtte og setepute, og bruker bilens vanlige trepunktsbelte til å sikre "
      "barnet — i motsetning til en enkel belteputer uten rygg. Mange beltestoler har i tillegg ISOFIX-"
      "feste som stabiliserer stolen, selv om det er bilbeltet som gjør selve sikringsjobben."
    ),
    "fakta": [
      ("Beltet skal gå riktig", "Skulderbeltet skal gå rett inn i skulderhøyde, ikke i en bue rundt nakken. De fleste beltestoler har justerbar hodestøtte nettopp for å følge barnets vekst og sikre riktig belteføring."),
      ("Lovkrav i Norge", "Barn under 135 cm skal alltid sikres i godkjent barnesikringsutstyr. Barn mellom 135 og 150 cm skal bruke slikt utstyr dersom det finnes tilgjengelig i bilen."),
      ("ISOFIX gir mer stabilitet", "En beltestol med ISOFIX-feste sitter roligere i bilen når den ikke er i bruk, og kan gjøre montering enklere — men det er fortsatt bilbeltet, ikke ISOFIX-en, som sikrer barnet."),
    ],
    "faq": [
      ("Fra hvilken alder kan barnet bruke beltestol?", "Det avgjøres av høyde, ikke alder — de fleste beltestoler er godkjent fra rundt 100 cm og oppover, gjerne til 12 år eller 150 cm."),
      ("Er beltestol tryggere enn belteputer?", "En beltestol med rygg og justerbar hodestøtte gir generelt bedre sidebeskyttelse og riktigere belteføring enn en enkel belteputer."),
      ("Må jeg feste ISOFIX-en hver gang?", "Nei, ISOFIX-festet på en beltestol er for stabilitet når stolen ikke er i bruk. Det er bilbeltet rundt barnet som må festes riktig hver gang."),
    ],
  },
  "bilstolpakke": {
    "tittel": "Bilstolpakke",
    "h1": "Bilstolpakker sammenlignet",
    "meta_title": "Bilstolpakke 2026 – Sammenlign pris på komplette pakker | Bilstolbarn.no",
    "meta_desc": f"Bilstolpakker med base, tilbehør eller flere stoler samlet fra norske nettbutikker. Sammenlign pris. Oppdatert {OPPDATERT}.",
    "kicker": "Stol + base/tilbehør i én pakke",
    "ingress": (
      "Bilstolpakker samler gjerne en bilstol med tilhørende base, eller flere produkter i samme serie, "
      "til en pakkepris. Dette kan gi bedre totalpris enn å kjøpe delene separat — men sjekk alltid at "
      "pakken faktisk inneholder det du trenger, spesielt om basen er inkludert."
    ),
    "fakta": [
      ("Sjekk hva som faktisk følger med", "Produktnavnet forteller ofte om basen er inkludert eller ikke — les tittelen og produktsiden nøye før kjøp."),
      ("Sammenlign mot enkeltkjøp", "Det lønner seg å sjekke prisen på stolen og basen hver for seg på tilhørende kategorisider, for å se om pakken faktisk er billigere."),
    ],
    "faq": [
      ("Er en bilstolpakke alltid billigere enn å kjøpe delene hver for seg?", "Ikke nødvendigvis — det varierer fra forhandler til forhandler. Sammenlign gjerne med enkeltprisen på stol og base."),
      ("Passer basen i pakken til andre bilstoler?", "Som regel er basen produsentspesifikk og passer kun til stolen(e) den selges sammen med. Sjekk kompatibilitet hos forhandler ved tvil."),
    ],
  },
  "belteputer": {
    "tittel": "Belteputer",
    "h1": "Belteputer sammenlignet",
    "meta_title": "Belteputer barn 2026 – Sammenlign pris på alle modeller | Bilstolbarn.no",
    "meta_desc": f"Belteputer (booster) for større barn samlet fra norske nettbutikker med pris og lenke. Oppdatert {OPPDATERT}.",
    "kicker": "Uten rygg · løfter barnet til riktig beltehøyde",
    "ingress": (
      "En belteputer er en enkel sitteforhøyning uten rygg, som løfter barnet slik at bilens ordinære "
      "sikkerhetsbelte får riktig føring over skulder og hofte. Den passer for eldre barn som har vokst "
      "fra beltestol med rygg, men fortsatt er under høyde-/vektgrensen for å bruke bilbeltet alene."
    ),
    "fakta": [
      ("Ingen sidekollisjonsbeskyttelse", "Til forskjell fra en beltestol med rygg gir en belteputer ingen ekstra beskyttelse ved sidekollisjon. Trygg Trafikk og de fleste produsenter anbefaler beltestol med rygg så lenge det er praktisk mulig."),
      ("Fortsatt lovpålagt for barn under 135 cm", "Barn under 135 cm skal sikres med godkjent utstyr — en belteputa regnes som slikt utstyr, men bør velges når barnet har vokst fra beltestol med rygg, ikke som førstevalg."),
    ],
    "faq": [
      ("Når kan barnet gå over til belteputer uten rygg?", "Vanligvis når barnet har vokst fra en beltestol med rygg, ofte rundt 125–135 cm, avhengig av modell og barnets bredde/høyde."),
      ("Er belteputer trygge nok alene?", "Ja, godkjente belteputer oppfyller lovkravet, men gir mindre sidebeskyttelse enn en beltestol med rygg. Velg beltestol med rygg så lenge barnet har plass i den."),
    ],
  },
  "adapter": {
    "tittel": "Bilstoladapter til barnevogn",
    "h1": "Bilstoladaptere til barnevogn",
    "meta_title": "Bilstoladapter barnevogn 2026 – Sammenlign pris | Bilstolbarn.no",
    "meta_desc": f"Adaptere for å feste babybilstol på barnevogn, samlet fra norske nettbutikker med pris og lenke. Oppdatert {OPPDATERT}.",
    "kicker": "Fest babybilstolen på understellet",
    "ingress": (
      "En bilstoladapter lar deg feste babybilstolen direkte på barnevognunderstellet, slik at du kan "
      "flytte et sovende barn fra bil til vogn uten å vekke det. Adapteren må passe både til stolmerket "
      "og understellsmerket — sjekk alltid kompatibilitet før kjøp."
    ),
    "fakta": [
      ("Merke må matche på begge sider", "Adapteren er spesifikk for kombinasjonen av bilstol- og barnevognmerke. Se produkttittelen for hvilke merker den passer til."),
    ],
    "faq": [
      ("Passer alle adaptere på alle vogner?", "Nei, adapteren må passe både bilstolens og understellets merke og modell. Sjekk produktbeskrivelsen hos forhandler før kjøp."),
    ],
  },
  "tilbehor": {
    "tittel": "Tilbehør til bilstol",
    "h1": "Tilbehør til bilstol",
    "meta_title": "Tilbehør til bilstol 2026 – Sparketrekk, solskjerm, setetrekk | Bilstolbarn.no",
    "meta_desc": f"Sparketrekk, solskjermer, setetrekk og annet tilbehør til bilstol samlet fra norske nettbutikker. Oppdatert {OPPDATERT}.",
    "kicker": "Sparketrekk, solskjerm, setetrekk og mer",
    "ingress": (
      "Her finner du tilbehør som gjør bilturen mer behagelig for barnet, som sparketrekk til vinterbruk, "
      "solskjermer, ekstra setetrekk og bilspeil for å ha øye på et bakovervendt barn. Vær obs på at ikke "
      "alt tilbehør er testet sammen med selve bilstolen — følg alltid stolprodusentens anbefalinger."
    ),
    "fakta": [
      ("Sjekk kompatibilitet med bilstolen", "Uoriginalt tilbehør som legges mellom barnet og selene (f.eks. tykke vinterdresser eller enkelte setetrekk) kan i verste fall svekke sikringen ved en kollisjon. Følg alltid bilstolprodusentens egne anbefalinger."),
    ],
    "faq": [
      ("Er sparketrekk trygt å bruke i en bilstol?", "De fleste sparketrekk som er laget spesifikt for bilbruk og godkjent av bilstolprodusenten er trygge, fordi selene fortsatt går utenpå trekket. Løse tepper eller tykke dresser under selene bør unngås."),
    ],
  },
}

CATEGORI_ORDER = ["babybilstol","bakovervendt","vendbar","isize","beltestol","bilstolpakke","belteputer","adapter","tilbehor"]

# ─────────────────────────────────────────────────────────────
# 3. HTML-bygging
# ─────────────────────────────────────────────────────────────
def esc(s):
    return html.escape(s or "", quote=True)

def nok(p):
    return f"{p:,.0f}".replace(",", " ") + " kr"

CSS = """
:root{--bg:#f7f9fb;--card:#ffffff;--ink:#132030;--ink2:#4a5b6b;--line:#e2e8ee;
--brand:#1c5a86;--brand2:#0e3a57;--accent:#e8734a;--good:#1f8a55;--radius:12px;}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
background:var(--bg);color:var(--ink);line-height:1.55}
a{color:var(--brand)}
.wrap{max-width:1180px;margin:0 auto;padding:0 20px}
header.site{background:var(--brand2);color:#fff;padding:14px 0}
header.site .wrap{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
.logo{font-weight:800;font-size:20px;color:#fff;text-decoration:none;letter-spacing:.2px}
.logo span{color:#8fd0ff}
nav.top{display:flex;gap:16px;flex-wrap:wrap;font-size:14px}
nav.top a{color:#dceefc;text-decoration:none}
nav.top a:hover{text-decoration:underline}
.hero{background:linear-gradient(135deg,var(--brand) 0%,var(--brand2) 100%);color:#fff;padding:44px 0 36px}
.hero h1{font-size:30px;margin:0 0 10px;line-height:1.2}
.hero .kicker{display:inline-block;background:rgba(255,255,255,.15);padding:4px 12px;border-radius:20px;font-size:13px;margin-bottom:14px}
.hero p{max-width:760px;color:#e7f1fa;font-size:16px}
.updated{font-size:13px;color:#c8e0f2;margin-top:10px}
main{padding:32px 0 60px}
.grid-cats{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px;margin:24px 0 8px}
.cat-card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:18px;text-decoration:none;color:var(--ink);transition:.15s}
.cat-card:hover{border-color:var(--brand);box-shadow:0 4px 14px rgba(19,32,48,.08)}
.cat-card .n{color:var(--brand);font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:.4px}
.cat-card h3{margin:6px 0 4px;font-size:18px}
.cat-card p{margin:0;color:var(--ink2);font-size:13.5px}
.cat-card .count{display:inline-block;margin-top:10px;font-size:12.5px;background:#eef4f9;color:var(--brand2);padding:3px 9px;border-radius:20px}
section.intro-box{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:22px 24px;margin:26px 0}
section.intro-box h2{margin-top:0;font-size:20px}
.fakta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin:18px 0}
.fakta-grid .f{background:#f2f7fb;border-radius:10px;padding:14px 16px}
.fakta-grid .f h4{margin:0 0 6px;font-size:15px;color:var(--brand2)}
.fakta-grid .f p{margin:0;font-size:13.5px;color:var(--ink2)}
.toolbar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin:22px 0 16px;background:var(--card);
border:1px solid var(--line);border-radius:var(--radius);padding:12px 14px}
.toolbar input[type=search]{flex:1;min-width:180px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;font-size:14px}
.toolbar select{padding:9px 10px;border:1px solid var(--line);border-radius:8px;font-size:14px;background:#fff}
.toolbar label{font-size:13px;color:var(--ink2)}
.count-line{font-size:13px;color:var(--ink2);margin:0 0 14px}
.product-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:16px}
.pcard{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column}
.pcard .imgwrap{background:#fff;aspect-ratio:1/1;display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--line);padding:10px}
.pcard img{max-width:100%;max-height:100%;object-fit:contain}
.pcard .body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:6px;flex:1}
.pcard .brand{font-size:11.5px;text-transform:uppercase;letter-spacing:.4px;color:var(--brand);font-weight:700}
.pcard h3{font-size:14px;margin:0;line-height:1.35;min-height:38px}
.pcard h3 a{color:var(--ink);text-decoration:none}
.pcard h3 a:hover{color:var(--brand)}
.price-row{display:flex;align-items:baseline;gap:8px;margin-top:auto}
.price{font-size:18px;font-weight:800;color:var(--brand2)}
.price-old{font-size:12.5px;color:#9aa7b2;text-decoration:line-through}
.badge-sale{background:#fdeee7;color:var(--accent);font-size:11px;font-weight:700;padding:2px 7px;border-radius:6px}
.btn{display:inline-block;margin-top:8px;background:var(--brand);color:#fff;text-align:center;padding:9px 12px;
border-radius:8px;text-decoration:none;font-size:13.5px;font-weight:600}
.btn:hover{background:var(--brand2)}
.hidden{display:none !important}
.empty-msg{padding:30px;text-align:center;color:var(--ink2)}
footer{background:var(--brand2);color:#cfe3f2;padding:34px 0;margin-top:50px;font-size:13.5px}
footer a{color:#9fd0f2}
footer .disclosure{background:rgba(255,255,255,.06);padding:14px 16px;border-radius:10px;margin-bottom:18px}
.breadcrumb{font-size:13px;color:var(--ink2);margin:16px 0 0}
.breadcrumb a{color:var(--ink2)}
.article-body{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:26px 28px;max-width:780px}
.article-body h2{font-size:20px;color:var(--brand2)}
.article-body p{font-size:15.5px;color:#2b3947}
.faq{margin-top:20px}
.faq details{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:12px 16px;margin-bottom:10px}
.faq summary{cursor:pointer;font-weight:600;font-size:15px}
.faq p{margin:10px 0 2px;color:var(--ink2);font-size:14.5px}
.related-arts{display:flex;gap:14px;flex-wrap:wrap;margin:20px 0}
.related-arts a{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:10px 14px;text-decoration:none;color:var(--brand2);font-size:14px;font-weight:600}
@media (max-width:640px){.hero h1{font-size:24px}}
"""

def cat_meta(slug):
    return KATEGORIER[slug]

def nav_html(active=None):
    links = []
    for slug in CATEGORI_ORDER:
        m = KATEGORIER[slug]
        cls = ""
        links.append(f'<a href="/{slug}-bilstol.html" {cls}>{esc(m["tittel"])}</a>' if slug not in ("babybilstol","adapter","tilbehor") else f'<a href="/{slug}{"-bilstol" if slug=="babybilstol" else ""}.html">{esc(m["tittel"])}</a>')
    return links

def page_slug_filename(slug):
    mapping = {
        "babybilstol": "babybilstol.html",
        "bakovervendt": "bakovervendt-bilstol.html",
        "vendbar": "vendbar-bilstol.html",
        "isize": "isize-bilstol.html",
        "beltestol": "beltestol-barn.html",
        "bilstolpakke": "bilstolpakke.html",
        "belteputer": "belteputer.html",
        "adapter": "bilstoladapter-barnevogn.html",
        "tilbehor": "bilstol-tilbehor.html",
    }
    return mapping[slug]

NAV_ITEMS = [(s, page_slug_filename(s), KATEGORIER[s]["tittel"]) for s in CATEGORI_ORDER]

def header_block(active_file=None):
    nav = "\n".join(
        f'<a href="/{fn}"{" style=\"color:#fff;font-weight:700\"" if fn==active_file else ""}>{esc(t)}</a>'
        for s, fn, t in NAV_ITEMS
    )
    return f"""<header class="site">
  <div class="wrap">
    <a class="logo" href="/">Bilstol<span>barn</span>.no</a>
    <nav class="top">{nav}</nav>
  </div>
</header>"""

def footer_block():
    return f"""<footer>
  <div class="wrap">
    <div class="disclosure">Bilstolbarn.no inneholder annonselenker. Når du handler via lenkene på siden kan vi motta en provisjon fra forhandleren — det koster deg ikke noe ekstra. Priser og lagerstatus hentes fra forhandlernes produktfeeder og kan avvike fra faktisk pris i nettbutikken; sjekk alltid pris hos forhandler før kjøp.</div>
    <p>Bilstolbarn.no er en uavhengig sammenligningstjeneste for bilstoler og tilbehør til barn. Vi selger ikke produkter selv, men lenker videre til forhandlere. Faktaopplysninger om regelverk er hentet fra offentlige kilder som Trygg Trafikk og NAF — sjekk alltid gjeldende regelverk og produsentens egen bruksanvisning før montering.</p>
    <p>&copy; {today.year} Bilstolbarn.no · Oppdatert {OPPDATERT}</p>
  </div>
</footer>"""

def head_block(title, desc, canonical, extra_schema="", is_home=False):
    verification = f'<meta name="google-site-verification" content="{GSC_TAG}" />\n  ' if is_home else ""
    return f"""<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{esc(title)}</title>
<meta name="description" content="{esc(desc)}">
<link rel="canonical" href="{canonical}">
<meta property="og:type" content="website">
<meta property="og:title" content="{esc(title)}">
<meta property="og:description" content="{esc(desc)}">
<meta property="og:url" content="{canonical}">
<meta property="og:locale" content="nb_NO">
<meta name="robots" content="index, follow">
{verification}<link rel="stylesheet" href="/assets/style.css">
{extra_schema}</head>
<body>
"""

def faq_schema(faq_list):
    if not faq_list:
        return ""
    entities = []
    for q, a in faq_list:
        entities.append({
            "@type": "Question",
            "name": q,
            "acceptedAnswer": {"@type": "Answer", "text": a}
        })
    data = {"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": entities}
    return f'<script type="application/ld+json">{json.dumps(data, ensure_ascii=False)}</script>\n'

def breadcrumb_schema(items):
    le = []
    for i, (name, url) in enumerate(items, start=1):
        le.append({"@type": "ListItem", "position": i, "name": name, "item": url})
    data = {"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": le}
    return f'<script type="application/ld+json">{json.dumps(data, ensure_ascii=False)}</script>\n'

def product_card(p):
    sale = p["orig"] > p["price"] and p["orig"] > 0
    sale_badge = f'<span class="badge-sale">Spar {nok(p["orig"]-p["price"])}</span>' if sale else ""
    old = f'<span class="price-old">{nok(p["orig"])}</span>' if sale else ""
    name = p["name"]
    if len(name) > 90:
        name = name[:87] + "…"
    return f"""<div class="pcard" data-name="{esc(p['name'].lower())}" data-brand="{esc(p['brand'].lower())}" data-price="{p['price']:.0f}">
  <div class="imgwrap"><img src="{esc(p['img'])}" alt="{esc(p['name'])}" loading="lazy"></div>
  <div class="body">
    <span class="brand">{esc(p['brand'])}</span>
    <h3><a href="{esc(p['track'])}" rel="nofollow sponsored noopener" target="_blank">{esc(name)}</a></h3>
    <div class="price-row"><span class="price">{nok(p['price'])}</span>{old}{sale_badge}</div>
    <a class="btn" href="{esc(p['track'])}" rel="nofollow sponsored noopener" target="_blank">Se pris →</a>
  </div>
</div>"""

FILTER_JS = """
<script>
(function(){
  const search = document.getElementById('q');
  const sortSel = document.getElementById('sort');
  const brandSel = document.getElementById('brandFilter');
  const grid = document.getElementById('grid');
  const countLine = document.getElementById('countLine');
  const empty = document.getElementById('emptyMsg');
  if(!grid) return;
  const cards = Array.from(grid.children);

  function applyFilters(){
    const q = (search.value||'').toLowerCase().trim();
    const brand = brandSel ? brandSel.value : '';
    let visible = 0;
    cards.forEach(c=>{
      const matchQ = !q || c.dataset.name.includes(q) || c.dataset.brand.includes(q);
      const matchBrand = !brand || c.dataset.brand === brand;
      if(matchQ && matchBrand){ c.classList.remove('hidden'); visible++; }
      else { c.classList.add('hidden'); }
    });
    countLine.textContent = visible + ' produkter';
    empty.style.display = visible === 0 ? 'block' : 'none';
    sortGrid();
  }
  function sortGrid(){
    const val = sortSel.value;
    const visibleCards = cards.filter(c=>!c.classList.contains('hidden'));
    visibleCards.sort((a,b)=>{
      if(val === 'price-asc') return (+a.dataset.price) - (+b.dataset.price);
      if(val === 'price-desc') return (+b.dataset.price) - (+a.dataset.price);
      if(val === 'name-asc') return a.dataset.name.localeCompare(b.dataset.name);
      return 0;
    });
    visibleCards.forEach(c=>grid.appendChild(c));
  }
  search.addEventListener('input', applyFilters);
  sortSel.addEventListener('change', applyFilters);
  if(brandSel) brandSel.addEventListener('change', applyFilters);
  applyFilters();
})();
</script>
"""

def build_category_page(slug):
    meta = KATEGORIER[slug]
    items = rows_by_slug.get(slug, [])
    fn = page_slug_filename(slug)
    canonical = f"{SITE}/{fn}"
    brands = sorted(set(p["brand"] for p in items))
    brand_options = "".join(f'<option value="{esc(b.lower())}">{esc(b)}</option>' for b in brands)

    schema_list = {
        "@context": "https://schema.org",
        "@type": "ItemList",
        "name": meta["h1"],
        "itemListOrder": "https://schema.org/ItemListOrderAscending",
        "numberOfItems": len(items),
        "itemListElement": [
            {"@type": "ListItem", "position": i+1, "name": p["name"], "url": p["url"]}
            for i, p in enumerate(items[:30])
        ]
    }
    extra_schema = (
        f'<script type="application/ld+json">{json.dumps(schema_list, ensure_ascii=False)}</script>\n'
        + faq_schema(meta.get("faq", []))
        + breadcrumb_schema([("Bilstolbarn.no", SITE + "/"), (meta["tittel"], canonical)])
    )

    fakta_html = "".join(
        f'<div class="f"><h4>{esc(t)}</h4><p>{esc(d)}</p></div>' for t, d in meta.get("fakta", [])
    )
    faq_html = "".join(
        f'<details><summary>{esc(q)}</summary><p>{esc(a)}</p></details>' for q, a in meta.get("faq", [])
    )
    cards_html = "\n".join(product_card(p) for p in items)

    body = f"""{header_block(fn)}
<section class="hero">
  <div class="wrap">
    <span class="kicker">{esc(meta['kicker'])}</span>
    <h1>{esc(meta['h1'])}</h1>
    <p>{esc(meta['ingress'])}</p>
    <div class="updated">Oppdatert {OPPDATERT} · {len(items)} modeller fra {len(brands)} merker</div>
  </div>
</section>
<main class="wrap">
  <p class="breadcrumb"><a href="/">Forside</a> / {esc(meta['tittel'])}</p>

  <section class="intro-box">
    <h2>Dette bør du vite om {esc(meta['tittel'].lower())}</h2>
    <div class="fakta-grid">{fakta_html}</div>
  </section>

  <div class="toolbar">
    <input type="search" id="q" placeholder="Søk etter modell eller merke…">
    {'<select id="brandFilter"><option value="">Alle merker</option>' + brand_options + '</select>' if brands else ''}
    <select id="sort">
      <option value="price-asc">Pris: lav til høy</option>
      <option value="price-desc">Pris: høy til lav</option>
      <option value="name-asc">Navn A–Å</option>
    </select>
  </div>
  <p class="count-line" id="countLine">{len(items)} produkter</p>
  <div class="product-grid" id="grid">
    {cards_html}
  </div>
  <p class="empty-msg" id="emptyMsg" style="display:none">Ingen treff. Prøv et annet søk.</p>

  <section class="faq">
    <h2>Ofte stilte spørsmål</h2>
    {faq_html}
  </section>
</main>
{footer_block()}
{FILTER_JS}
</body>
</html>"""
    html_doc = head_block(meta["meta_title"], meta["meta_desc"], canonical, extra_schema) + body
    with open(os.path.join(OUT, fn), "w", encoding="utf-8") as f:
        f.write(html_doc)
    return fn

# ─────────────────────────────────────────────────────────────
# 4. Forside
# ─────────────────────────────────────────────────────────────
def build_index():
    cat_cards = ""
    for slug in CATEGORI_ORDER:
        meta = KATEGORIER[slug]
        n = len(rows_by_slug.get(slug, []))
        fn = page_slug_filename(slug)
        cat_cards += f"""<a class="cat-card" href="/{fn}">
  <span class="n">Bilstol</span>
  <h3>{esc(meta['tittel'])}</h3>
  <p>{esc(meta['kicker'])}</p>
  <span class="count">{n} modeller</span>
</a>"""

    art_cards = """<a class="cat-card" href="/artikler/hvordan-velge-bilstol.html"><span class="n">Guide</span><h3>Hvordan velge riktig bilstol</h3><p>Steg for steg: fra babybilstol til beltestol.</p></a>
<a class="cat-card" href="/artikler/nar-snu-bilstol-forovervendt.html"><span class="n">Guide</span><h3>Når kan du snu bilstolen?</h3><p>Lovkrav og anbefalinger fra NAF og Trygg Trafikk.</p></a>
<a class="cat-card" href="/artikler/isofix-eller-belte.html"><span class="n">Guide</span><h3>ISOFIX eller bilbelte?</h3><p>Forskjellen forklart, og hva som passer din bil.</p></a>"""

    home_faq = [
        ("Hva er forskjellen på i-Size (R129) og R44?", "i-Size klassifiserer bilstoler etter barnets høyde og krever obligatorisk sidekollisjonstest, mens den eldre R44-standarden klassifiserer etter vekt. R44-stoler selges ikke lenger nye, men er fortsatt lovlige å bruke."),
        ("Hvor lenge skal barn sitte bakovervendt i bil?", "Loven krever bakovervendt sikring til minst 15 måneder, men NAF og Trygg Trafikk anbefaler at barn sitter bakovervendt så lenge som mulig, minimum til fylte 4 år."),
        ("Hvilken bilstol trenger barnet mitt nå?", "Det avhenger av barnets høyde, ikke alder. Bruk kategoriene over — babybilstol for de minste, bakovervendt eller vendbar for barn i barnehagealder, og beltestol eller belteputer for eldre barn opp mot 135–150 cm."),
    ]
    extra_schema = (
        faq_schema(home_faq)
        + f'<script type="application/ld+json">{json.dumps({"@context":"https://schema.org","@type":"WebSite","name":"Bilstolbarn.no","url":SITE},ensure_ascii=False)}</script>\n'
    )
    home_faq_html = "".join(f'<details><summary>{esc(q)}</summary><p>{esc(a)}</p></details>' for q, a in home_faq)

    body = f"""{header_block(None)}
<section class="hero">
  <div class="wrap">
    <span class="kicker">Bilstoler til barn</span>
    <h1>Sammenlign bilstoler fra alle store merker – én oversikt, alle priser</h1>
    <p>Vi har samlet {total} bilstoler, pakker og tilbehør fra norske nettbutikker på ett sted, sortert etter kategori og pris, slik at du slipper å lete rundt på flere sider. Alle lenker går videre til forhandler.</p>
    <div class="updated">Oppdatert {OPPDATERT} · {len(brands_seen)} merker · {total} produkter totalt</div>
  </div>
</section>
<main class="wrap">
  <section class="intro-box">
    <h2>Finn riktig type bilstol</h2>
    <p>Bilstoler er delt inn etter barnets størrelse og bruksområde, ikke bare merke og pris. Velg kategorien som passer barnets høyde og alder under, og sammenlign modeller innad i den kategorien.</p>
  </section>
  <div class="grid-cats">{cat_cards}</div>

  <section class="intro-box" style="margin-top:36px">
    <h2>Guider og regelverk</h2>
    <p>Kort forklart om hva loven krever og hva sikkerhetsorganisasjonene anbefaler.</p>
  </section>
  <div class="grid-cats">{art_cards}</div>

  <section class="faq" style="margin-top:36px">
    <h2>Ofte stilte spørsmål om bilstol</h2>
    {home_faq_html}
  </section>
</main>
{footer_block()}
</body>
</html>"""
    html_doc = head_block(
        "Bilstol til barn 2026 – Sammenlign alle typer og priser | Bilstolbarn.no",
        f"Sammenlign {total} bilstoler, pakker og tilbehør fra norske nettbutikker: babybilstol, bakovervendt, vendbar, i-Size, beltestol og mer. Oppdatert {OPPDATERT}.",
        SITE + "/",
        extra_schema,
        is_home=True,
    ) + body
    with open(os.path.join(OUT, "index.html"), "w", encoding="utf-8") as f:
        f.write(html_doc)

# ─────────────────────────────────────────────────────────────
# 5. Artikler (guider)
# ─────────────────────────────────────────────────────────────
ARTICLES = {
  "hvordan-velge-bilstol.html": {
    "title": "Hvordan velge riktig bilstol til barnet – komplett guide 2026 | Bilstolbarn.no",
    "desc": "Steg-for-steg-guide til å velge riktig bilstol etter barnets høyde: babybilstol, bakovervendt, vendbar, beltestol og belteputer.",
    "h1": "Hvordan velge riktig bilstol",
    "body": f"""
<p>Den viktigste faktoren når du skal velge bilstol er <strong>barnets høyde</strong> — ikke alder, og ikke merke. De fleste bilstoler i dag er godkjent etter i-Size-regelverket (ECE R129), som angir hvilket høydespenn stolen er testet for.</p>
<h2>1. Fra fødsel til ca. 75–87 cm: babybilstol</h2>
<p>De første månedene bruker de fleste en <a href="/babybilstol.html">babybilstol</a>, som monteres bakovervendt med bilbelte eller en egen ISOFIX-base. Se etter i-Size-godkjenning og sjekk om basen følger med i prisen.</p>
<h2>2. Fra ca. 6 måneder til 4–7 år: bakovervendt eller vendbar bilstol</h2>
<p>Når barnet vokser fra babybilstolen, anbefaler både NAF og Trygg Trafikk at det fortsetter å sitte <a href="/bakovervendt-bilstol.html">bakovervendt</a> så lenge som mulig — minimum til fylte 4 år. Mange velger en <a href="/vendbar-bilstol.html">vendbar bilstol</a> i denne perioden, som gjør det enklere å få barnet inn og ut, og som kan snus fremover når barnet er stort nok.</p>
<h2>3. Fra ca. 100 cm: beltestol med rygg</h2>
<p>Når barnet blir for stort for en bakovervendt/vendbar bilstol, er neste steg en <a href="/beltestol-barn.html">beltestol</a> med rygg og justerbar hodestøtte. Denne bruker bilens vanlige sikkerhetsbelte, men gir bedre sidebeskyttelse enn en enkel belteputer.</p>
<h2>4. Fra ca. 125–135 cm: belteputer</h2>
<p>De siste årene før barnet kan bruke bilbeltet alene, holder det ofte med en <a href="/belteputer.html">belteputer</a> uten rygg. Norsk lov krever godkjent sikringsutstyr for barn under 135 cm, og anbefaler det også for barn mellom 135 og 150 cm der slikt utstyr finnes tilgjengelig i bilen.</p>
<h2>Sjekkliste før du kjøper</h2>
<p>Sjekk at stolen er i-Size-godkjent (ECE R129) eller R44/04-godkjent, at høydespennet passer barnet i dag <em>og</em> en stund fremover, at den passer i din bil (ISOFIX-punkter, i-Size-merking), og at eventuell base eller adapter følger med eller er tilgjengelig separat.</p>
""",
    "faq": [
      ("Bør jeg velge etter alder eller høyde?", "Høyde. Bilstoler er i dag godkjent etter barnets høyde, ikke alder, så bruk alltid stolens eget høydespenn som rettesnor, ikke bursdagen."),
      ("Kan jeg kjøpe brukt bilstol?", "Det anbefales kun dersom du kjenner hele historikken til stolen og er sikker på at den aldri har vært i en kollisjon eller skadet. Ved tvil, velg ny."),
    ],
  },
  "nar-snu-bilstol-forovervendt.html": {
    "title": "Når kan du snu bilstolen forovervendt? Lovkrav og anbefaling | Bilstolbarn.no",
    "desc": "Lovkravet er bakovervendt til minst 15 måneder, men NAF og Trygg Trafikk anbefaler lenger. Se hva som faktisk gjelder.",
    "h1": "Når kan du snu bilstolen forovervendt?",
    "body": """
<p>Etter dagens i-Size-regelverk (ECE R129) er kravet at barn skal sikres bakovervendt til de er <strong>minst 15 måneder</strong> gamle. Det er imidlertid et minstekrav, ikke en anbefalt praksis.</p>
<h2>Hva anbefaler NAF og Trygg Trafikk?</h2>
<p>Både NAF og Trygg Trafikk anbefaler at barn sitter bakovervendt så lenge som mulig, og peker på minimum fylte 4 år som et godt utgangspunkt. Enkelte i-Size-stoler er godkjent for bakovervendt bruk helt opp til 5–7-årsalder.</p>
<h2>Hvorfor er bakovervendt tryggere?</h2>
<p>Ved en frontkollisjon — den vanligste ulykkestypen — fordeler en bakovervendt stol kreftene over hele ryggen og støtter hode og nakke, som hos små barn er tunge i forhold til resten av kroppen og ikke tåler samme belastning som hos voksne.</p>
<h2>Finnes det unntak?</h2>
<p>Noen bilstoler har egen airbagteknologi som gjør at barnet kan sitte trygt forovervendt allerede fra rundt 15 måneder. Dette er et unntak fra hovedregelen, ikke standarden, og gjelder kun de spesifikke modellene som er testet og godkjent for det.</p>
""",
    "faq": [
      ("Er det ulovlig å snu bilstolen tidligere enn 4 år?", "Nei, lovens minstekrav er 15 måneder. Anbefalingen om 4 år kommer fra trafikksikkerhetsorganisasjoner, ikke fra selve loven."),
      ("Kan jeg bruke en bakovervendt stol i forsetet?", "Kun hvis airbagen i forsetet er slått av eller deaktivert. Ellers skal bakovervendte bilstoler alltid plasseres i baksetet."),
    ],
  },
  "isofix-eller-belte.html": {
    "title": "ISOFIX eller bilbelte til bilstol – hva er forskjellen? | Bilstolbarn.no",
    "desc": "ISOFIX og bilbelte er to måter å montere bilstol på. Se hva som er forskjellen, og hvilke bilstoler som krever hva.",
    "h1": "ISOFIX eller bilbelte – hva bør du velge?",
    "body": """
<p>De fleste bilstoler i dag festes enten med <strong>ISOFIX</strong> (faste festepunkter i bilen) eller med bilens ordinære <strong>trepunktsbelte</strong>. Noen modeller kan begge deler.</p>
<h2>ISOFIX</h2>
<p>ISOFIX-fester gir en fast, standardisert forbindelse mellom bilstolen og karosseriet, ofte supplert med et støtteben eller en topptether. Fordelen er redusert risiko for feilmontering, siden det er færre trinn som kan gjøres feil sammenlignet med å stramme et bilbelte riktig hver gang.</p>
<h2>Bilbelte</h2>
<p>Bilstoler som festes med bilbelte kan brukes i så godt som alle biler, også eldre modeller uten ISOFIX-punkter. Riktig montering er avgjørende — beltet må strammes godt og følge stolens beltefører nøyaktig slik bruksanvisningen viser.</p>
<h2>Hva krever i-Size?</h2>
<p>Bilstoler godkjent etter i-Size (ECE R129) skal festes med ISOFIX, i kombinasjon med støtteben eller topptether — ikke med bilbelte alene. Den eldre R44-standarden tillater begge festemetoder, avhengig av modell.</p>
<h2>Beltestoler er litt spesielt</h2>
<p>På en <a href="/beltestol-barn.html">beltestol</a> med rygg brukes ofte ISOFIX kun til å stabilisere selve stolen når den ikke er i bruk — det er fortsatt bilens sikkerhetsbelte rundt barnet som gjør selve sikringsjobben.</p>
""",
    "faq": [
      ("Er ISOFIX tryggere enn bilbelte?", "Begge metoder er trygge når de brukes riktig. ISOFIX reduserer risikoen for feilmontering, men riktig strammet bilbelte gir tilsvarende beskyttelse."),
      ("Har alle biler ISOFIX?", "De fleste personbiler produsert etter ca. 2013 har ISOFIX-punkter i baksetet, men sjekk alltid bilens håndbok for å være sikker."),
    ],
  },
}

def build_articles():
    for fn, a in ARTICLES.items():
        canonical = f"{SITE}/artikler/{fn}"
        faq_html = "".join(f'<details><summary>{esc(q)}</summary><p>{esc(ans)}</p></details>' for q, ans in a.get("faq", []))
        extra_schema = (
            faq_schema(a.get("faq", []))
            + breadcrumb_schema([("Bilstolbarn.no", SITE + "/"), ("Guider", SITE + "/#guider"), (a["h1"], canonical)])
            + f'<script type="application/ld+json">{json.dumps({"@context":"https://schema.org","@type":"Article","headline":a["h1"],"datePublished":str(today),"dateModified":str(today),"author":{"@type":"Organization","name":"Bilstolbarn.no"}},ensure_ascii=False)}</script>\n'
        )
        related = "".join(
            f'<a href="/artikler/{other}">{ARTICLES[other]["h1"]}</a>'
            for other in ARTICLES if other != fn
        )
        body = f"""{header_block(None)}
<main class="wrap">
  <p class="breadcrumb"><a href="/">Forside</a> / <a href="/#guider">Guider</a> / {esc(a['h1'])}</p>
  <h1 style="font-size:28px;margin:14px 0 18px">{esc(a['h1'])}</h1>
  <div class="article-body">
    {a['body']}
  </div>
  <section class="faq" style="max-width:780px">
    <h2>Ofte stilte spørsmål</h2>
    {faq_html}
  </section>
  <div class="related-arts">{related}</div>
</main>
{footer_block()}
</body>
</html>"""
        html_doc = head_block(a["title"], a["desc"], canonical, extra_schema) + body
        with open(os.path.join(OUT, "artikler", fn), "w", encoding="utf-8") as f:
            f.write(html_doc)

# ─────────────────────────────────────────────────────────────
# 6. Bygg alt
# ─────────────────────────────────────────────────────────────
build_index()
all_pages = [("index.html", 1.0, str(today))]
for slug in CATEGORI_ORDER:
    fn = build_category_page(slug)
    all_pages.append((fn, 0.8, str(today)))
build_articles()
for fn in ARTICLES:
    all_pages.append((f"artikler/{fn}", 0.6, str(today)))

with open(os.path.join(OUT, "assets", "style.css"), "w", encoding="utf-8") as f:
    f.write(CSS)

sitemap = ['<?xml version="1.0" encoding="UTF-8"?>',
           '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">']
for path, prio, lastmod in all_pages:
    loc = f"{SITE}/{path}" if path != "index.html" else f"{SITE}/"
    sitemap.append(f"  <url><loc>{loc}</loc><lastmod>{lastmod}</lastmod><priority>{prio}</priority></url>")
sitemap.append("</urlset>")
with open(os.path.join(OUT, "sitemap.xml"), "w", encoding="utf-8") as f:
    f.write("\n".join(sitemap))

with open(os.path.join(OUT, "robots.txt"), "w", encoding="utf-8") as f:
    f.write(f"User-agent: *\nAllow: /\nSitemap: {SITE}/sitemap.xml\n")

print("\nFerdig. Sider generert:", len(all_pages) + 0)
