From 17a247127bd4798a221abf4c88d34c88bedb04ce Mon Sep 17 00:00:00 2001 From: Ben de Roo Date: Wed, 24 Jun 2026 21:13:21 +0200 Subject: [PATCH] Add domme-converters/assets/js/pages.js --- domme-converters/assets/js/pages.js | 575 ++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 domme-converters/assets/js/pages.js diff --git a/domme-converters/assets/js/pages.js b/domme-converters/assets/js/pages.js new file mode 100644 index 0000000..3652f9c --- /dev/null +++ b/domme-converters/assets/js/pages.js @@ -0,0 +1,575 @@ +"use strict"; + +/** + * Pagina-views. Elke view geeft een object terug: + * { title, html, onMount? } + * - title : documenttitel. + * - html : markup voor de #app-container. + * - onMount: optionele functie die na rendering events koppelt. + * + * Alle inhoud komt uit de centrale databronnen (PRODUCTS, CATEGORIES, CONTENT). + */ +(function () { + const CL = (window.CL = window.CL || {}); + const { formatPrice, escapeHtml, stars } = CL.fmt; + const { productGrid, pageHead, crumb, productThumb } = CL.components; + const { href } = CL.nav; + + const PRODUCTS = window.PRODUCTS || []; + const CATEGORIES = window.CATEGORIES || [{ key: "all", label: "Alles" }]; + const CONTENT = window.CONTENT || {}; + + const bySlug = (slug) => PRODUCTS.find((p) => p.slug === slug); + const categoryLabel = (key) => (CATEGORIES.find((c) => c.key === key) || { label: key }).label; + const discount = (p) => Math.round((1 - p.price / p.old) * 100); + + // ---- Home ----------------------------------------------------------------- + function home() { + const featured = PRODUCTS.slice(0, 4); + return { + title: "Converterland — Adapters en signaalconverters", + html: ` +
+
+
+ Universele signaalconversie +

Elk signaal naar elk medium. Theoretisch.

+

+ Converterland levert adapters voor verbindingen die niet bestaan en + problemen die u niet heeft. Een complete catalogus aan fictieve + converters, professioneel gepresenteerd en volledig onbruikbaar. +

+ +
+
+
Ondersteunde signalen
+
Gemiddelde latency3 werkdagen
+
Compatibiliteit0 / 0
+
Garantiegeen
+
Retourtermijnn.v.t.
+
+
+
+ +
+
+
Niet op voorraad
Elk product, altijd.
+
Geen verzendkosten
Want geen verzending.
+
Veilig afrekenen
Er gebeurt niets.
+
Support 0/7
Wij nemen niet op.
+
+
+ +
+
+
+

Uitgelicht

+

Een greep uit onze meest besproken converters.

+
+ Alle producten → +
+ ${productGrid(featured)} +
+ `, + }; + } + + // ---- Producten ------------------------------------------------------------ + function producten() { + return { + title: "Producten — Converterland", + html: ` + ${pageHead({ + eyebrow: "Assortiment", + title: "Producten", + intro: "Het volledige assortiment fictieve signaalconverters, overzichtelijk gepresenteerd.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Producten" }]), + })} +
+

Alle converters

${PRODUCTS.length} producten

+ ${productGrid(PRODUCTS)} +
+ `, + }; + } + + // ---- Catalogus (zoeken / sorteren / filteren) ----------------------------- + function catalogus() { + const filterButtons = CATEGORIES.map( + (c) => `` + ).join(""); + + const maxPrice = Math.ceil(Math.max(...PRODUCTS.map((p) => p.price))); + + return { + title: "Catalogus — Converterland", + html: ` + ${pageHead({ + eyebrow: "Catalogus", + title: "Catalogus", + intro: "Doorzoek, sorteer en filter het volledige assortiment.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Catalogus" }]), + })} +
+
+
+ + +
+
+
${filterButtons}
+ +
+
+

+
${productGrid(PRODUCTS)}
+
+ `, + onMount() { + const state = { q: "", sort: "featured", category: "all", maxPrice }; + const gridEl = document.getElementById("catalogGrid"); + const countEl = document.getElementById("resultCount"); + const searchEl = document.getElementById("searchInput"); + const sortEl = document.getElementById("sortSelect"); + const rangeEl = document.getElementById("priceRange"); + const priceOut = document.getElementById("priceOut"); + const filtersEl = document.getElementById("filters"); + + function apply() { + let list = PRODUCTS.filter((p) => { + if (state.category !== "all" && p.category !== state.category) return false; + if (p.price > state.maxPrice) return false; + if (state.q) { + const hay = (p.name + " " + p.from + " " + p.to + " " + p.desc).toLowerCase(); + if (!hay.includes(state.q)) return false; + } + return true; + }); + switch (state.sort) { + case "price-asc": list.sort((a, b) => a.price - b.price); break; + case "price-desc": list.sort((a, b) => b.price - a.price); break; + case "rating": list.sort((a, b) => b.rating - a.rating || b.reviews - a.reviews); break; + case "name": list.sort((a, b) => a.name.localeCompare(b.name, "nl")); break; + } + gridEl.innerHTML = productGrid(list); + countEl.textContent = list.length + " van " + PRODUCTS.length + " producten"; + } + + searchEl.addEventListener("input", () => { state.q = searchEl.value.trim().toLowerCase(); apply(); }); + sortEl.addEventListener("change", () => { state.sort = sortEl.value; apply(); }); + rangeEl.addEventListener("input", () => { + state.maxPrice = Number(rangeEl.value); + priceOut.textContent = formatPrice(state.maxPrice); + apply(); + }); + filtersEl.addEventListener("click", (e) => { + const btn = e.target.closest("[data-filter]"); + if (!btn) return; + state.category = btn.dataset.filter; + filtersEl.querySelectorAll(".filter").forEach((b) => b.classList.toggle("active", b === btn)); + apply(); + }); + apply(); + }, + }; + } + + // ---- Specificaties -------------------------------------------------------- + function specificaties() { + const rows = PRODUCTS.map( + (p) => ` + + ${escapeHtml(p.name)}${escapeHtml(p.id)} + ${escapeHtml(p.from)} → ${escapeHtml(p.to)} + ${escapeHtml(categoryLabel(p.category))} + ${escapeHtml(p.stock.label)} + ${formatPrice(p.price)} + ` + ).join(""); + + return { + title: "Specificaties — Converterland", + html: ` + ${pageHead({ + eyebrow: "Technische gegevens", + title: "Specificaties", + intro: "Een vergelijkend overzicht van het volledige assortiment. Alle waarden zijn fictief.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Specificaties" }]), + })} +
+
+ + + + + ${rows} +
ProductConversieCategorieBeschikbaarheidPrijs
+
+
+

Algemene kenmerken

+
+
Ondersteunde signalen
+
Gemiddelde latency3 werkdagen
+
Compatibiliteit0 / 0
+
Garantiegeen
+
Retourtermijnn.v.t.
+
Certificeringzelfverklaard
+
+
+
+ `, + }; + } + + // ---- Aanbiedingen --------------------------------------------------------- + function aanbiedingen() { + const list = [...PRODUCTS].sort((a, b) => discount(b) - discount(a)); + const cards = list + .map((p) => { + const fallback = + `${escapeHtml(p.from)}${escapeHtml(p.to)}`; + return ` +
+ +
+ -${discount(p)}% + ${productThumb(p)} +
+
+
${escapeHtml(p.id)}${stars(p.rating)}(${p.reviews})
+

${escapeHtml(p.name)}

+

${escapeHtml(p.desc)}

+
+
+
+ ${formatPrice(p.price)}${formatPrice(p.old)} + +
+
`; + }) + .join(""); + + return { + title: "Aanbiedingen — Converterland", + html: ` + ${pageHead({ + eyebrow: "Tijdelijk voordeel", + title: "Aanbiedingen", + intro: "Alle converters zijn afgeprijsd, want niets is iets waard. Gesorteerd op korting.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Aanbiedingen" }]), + })} +
+
${cards}
+
+ `, + }; + } + + // ---- Bedrijf -------------------------------------------------------------- + function bedrijf() { + const c = CONTENT.company; + const values = c.values + .map((v) => `

${escapeHtml(v.title)}

${escapeHtml(v.text)}

`) + .join(""); + return { + title: "Bedrijf — Converterland", + html: ` + ${pageHead({ + eyebrow: "Over de onderneming", + title: "Bedrijf", + intro: c.intro, + crumb: crumb([{ label: "Home", path: "/" }, { label: "Bedrijf" }]), + })} +
+
+
${escapeHtml(c.founded)}Opgericht
+
${escapeHtml(c.employees)}Medewerkers
+
${PRODUCTS.length}Producten
+
0Leveringen
+
+
${values}
+ +
+ `, + }; + } + + // ---- Over ons ------------------------------------------------------------- + function overOns() { + const c = CONTENT.company; + const timeline = c.history + .map((h) => `
  • ${escapeHtml(h.year)}${escapeHtml(h.text)}
  • `) + .join(""); + return { + title: "Over ons — Converterland", + html: ` + ${pageHead({ + eyebrow: "Onze geschiedenis", + title: "Over ons", + intro: c.intro, + crumb: crumb([{ label: "Home", path: "/" }, { label: "Bedrijf", path: "/bedrijf" }, { label: "Over ons" }]), + })} +
    +
    +

    Hoofdkantoor

    +

    ${escapeHtml(c.headquarters)}

    +
    +

    Tijdlijn

    +
      ${timeline}
    +
    + `, + }; + } + + // ---- Contact -------------------------------------------------------------- + function contact() { + const c = CONTENT.contact; + return { + title: "Contact — Converterland", + html: ` + ${pageHead({ + eyebrow: "Neem contact op", + title: "Contact", + intro: "Stuur ons een bericht. Het formulier verwerkt geen gegevens en verstuurt niets.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Contact" }]), + })} +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +

    +
    + +
    +
    + `, + onMount() { + const form = document.getElementById("contactForm"); + const note = document.getElementById("contactNote"); + form.addEventListener("submit", (e) => { + e.preventDefault(); + if (!form.checkValidity()) { form.reportValidity(); return; } + form.reset(); + note.textContent = "Bericht ontvangen in deze demonstratie. Er is niets verzonden of opgeslagen."; + }); + }, + }; + } + + // ---- Vacatures ------------------------------------------------------------ + function vacatures() { + const items = CONTENT.jobs + .map( + (j) => ` +
    + + +
    ` + ) + .join(""); + return { + title: "Vacatures — Converterland", + html: ` + ${pageHead({ + eyebrow: "Werken bij", + title: "Vacatures", + intro: "Wij groeien al jaren niet, maar er is altijd plek voor talent dat van niets houdt.", + crumb: crumb([{ label: "Home", path: "/" }, { label: "Bedrijf", path: "/bedrijf" }, { label: "Vacatures" }]), + })} +
    +
    ${items}
    +
    + `, + onMount() { + const jobs = document.querySelector(".jobs"); + jobs.addEventListener("click", (e) => { + const head = e.target.closest("[data-job]"); + if (!head) return; + const body = head.nextElementSibling; + const open = head.getAttribute("aria-expanded") === "true"; + head.setAttribute("aria-expanded", String(!open)); + head.querySelector(".job-toggle").textContent = open ? "+" : "\u2212"; + body.hidden = open; + }); + }, + }; + } + + // ---- Juridische pagina's -------------------------------------------------- + function legalPage(key) { + const data = CONTENT.legal[key]; + const sections = data.sections + .map((s) => `

    ${escapeHtml(s.h)}

    ${escapeHtml(s.p)}

    `) + .join(""); + return { + title: data.title + " — Converterland", + html: ` + ${pageHead({ + eyebrow: "Juridisch", + title: data.title, + intro: data.intro, + crumb: crumb([{ label: "Home", path: "/" }, { label: data.title }]), + })} +
    +
    ${sections}
    +
    + `, + }; + } + + // ---- Productdetail -------------------------------------------------------- + function productDetail(slug) { + const p = bySlug(slug); + if (!p) return notFound(); + + const specRows = Object.entries(p.specs) + .map(([k, v]) => `
    ${escapeHtml(k)}${escapeHtml(v)}
    `) + .join(""); + const features = p.features.map((f) => `
  • ${escapeHtml(f)}
  • `).join(""); + const fallback = + `${escapeHtml(p.from)}${escapeHtml(p.to)}`; + const related = PRODUCTS.filter((x) => x.category === p.category && x.id !== p.id).slice(0, 3); + const relatedHtml = related.length ? `${CL.components.productGrid(related)}` : ""; + + return { + title: p.name + " — Converterland", + html: ` + ${pageHead({ + crumb: crumb([ + { label: "Home", path: "/" }, + { label: "Producten", path: "/producten" }, + { label: p.name }, + ]), + title: p.name, + })} +
    +
    +
    +
    ${productThumb(p)}
    +
    + ${escapeHtml(categoryLabel(p.category))} + ${escapeHtml(p.from)} → ${escapeHtml(p.to)} +
    +
    +
    +
    + ${escapeHtml(p.id)} + ${stars(p.rating)}(${p.reviews} beoordelingen) +
    +
    + ${formatPrice(p.price)}${formatPrice(p.old)} + U bespaart ${formatPrice(p.old - p.price)} +
    +

    ${escapeHtml(p.stock.label)}

    +

    ${escapeHtml(p.long)}

    +
    + + Verder winkelen +
    +
    +

    Eigenschappen

    +
      ${features}
    +
    +
    +
    + +
    +

    Technische specificaties

    +
    ${specRows}
    +
    + + ${ + related.length + ? `` + : "" + } +
    + `, + }; + } + + // ---- 404 ------------------------------------------------------------------ + function notFound() { + return { + title: "Niet gevonden — Converterland", + html: ` + ${pageHead({ eyebrow: "Fout 404", title: "Pagina niet gevonden", intro: "Deze pagina bestaat net zomin als onze producten." })} +
    + Terug naar de startpagina +
    + `, + }; + } + + CL.pages = { + home, + producten, + catalogus, + specificaties, + aanbiedingen, + bedrijf, + overOns, + contact, + vacatures, + voorwaarden: () => legalPage("voorwaarden"), + privacy: () => legalPage("privacy"), + disclaimer: () => legalPage("disclaimer"), + productDetail, + notFound, + }; +})();