/* Aqua Ruimteplanner app (HTML) — v9, urinoirrij + compacte AI-beschrijving, 20-10-2026 */ window.HB_RP_HTML="\u003c!doctype html\u003e\n\u003chtml lang=\"nl\" data-theme=\"light\"\u003e\n\u003chead\u003e\u003cscript\u003ewindow.HB_RP_DICT={\"e\":[[\"Vloeren\",[\"Sols\",\"Böden\",\"Floors\"]],[\"Dikte 17 mm\",[\"Épaisseur 17 mm\",\"Stärke 17 mm\",\"Thickness 17 mm\"]],[\"Wit\",[\"Blanc\",\"Weiß\",\"White\"]],[\"Zwart\",[\"Noir\",\"Schwarz\",\"Black\"]],[\"Toepassing\",[\"Application\",\"Anwendung\",\"Application\"]],[\"Serie\",[\"Série\",\"Serie\",\"Range\"]],[\"Decor\",[\"Décor\",\"Dekor\",\"Decor\"]],[\"Accessoires\",[\"Accessoires\",\"Zubehör\",\"Accessories\"]],[\"Overzicht\",[\"Récapitulatif\",\"Übersicht\",\"Summary\"]],[\"excl. btw\",[\"HTVA\",\"zzgl. MwSt.\",\"excl. VAT\"]],[\"op aanvraag\",[\"sur demande\",\"auf Anfrage\",\"on request\"]],[\"m² netto +\",[\"m² net +\",\"m² netto +\",\"m² net +\"]],[\"m² geleverd\",[\"m² livrés\",\"m² geliefert\",\"m² supplied\"]],[\"Formaat\",[\"Format\",\"Format\",\"Size\"]],[\"m² per pak\",[\"m² par paquet\",\"m² pro Paket\",\"m² per pack\"]],[\"decors\",[\"décors\",\"Dekore\",\"decors\"]],[\"· per stuk\",[\"· par pièce\",\"· pro Stück\",\"· per piece\"]],[\"· vanaf\",[\"· à partir de\",\"· ab\",\"· from\"]],[\"per stuk\",[\"par pièce\",\"pro Stück\",\"per piece\"]],[\"per m²\",[\"par m²\",\"pro m²\",\"per m²\"]],[\"formaten\",[\"formats\",\"Formate\",\"sizes\"]],[\"AI-render\",[\"Rendu IA\",\"KI-Rendering\",\"AI render\"]],[\"Klaar.\",[\"Terminé.\",\"Fertig.\",\"Done.\"]],[\"· pak van\",[\"· paquet de\",\"· Paket mit\",\"· pack of\"]],[\"ander decor\",[\"autre décor\",\"anderes Dekor\",\"other decor\"]],[\"Geleverd (\",[\"Livré (\",\"Geliefert (\",\"Supplied (\"]],[\"incl. btw\",[\"TVA comprise\",\"inkl. MwSt.\",\"incl. VAT\"]],[\"gekozen\",[\"choisi(s)\",\"ausgewählt\",\"selected\"]],[\"artikelen\",[\"articles\",\"Artikel\",\"items\"]],[\"advies\",[\"conseil\",\"Empfehlung\",\"advice\"]],[\"Postcode *\",[\"Code postal *\",\"Postleitzahl *\",\"Postcode *\"]],[\"Plaats *\",[\"Localité *\",\"Ort *\",\"Town *\"]],[\"Aantal\",[\"Quantité\",\"Anzahl\",\"Quantity\"]],[\"Levering:\",[\"Livraison :\",\"Lieferung:\",\"Delivery:\"]],[\"Aanvraag-\",[\"Demande-\",\"Anfrage-\",\"Request-\"]],[\"Artikelnr.\",[\"Réf. article\",\"Art.-Nr.\",\"Item no.\"]],[\"· Levering\",[\"· Livraison\",\"· Lieferung\",\"· Delivery\"]],[\"ref.\",[\"réf.\",\"Ref.\",\"ref.\"]],[\"regel\",[\"ligne\",\"Zeile\",\"line\"]],[\"Stappen\",[\"Étapes\",\"Schritte\",\"Steps\"]],[\"Visgraat\",[\"Point de Hongrie\",\"Fischgrät\",\"Herringbone\"]],[\"Visgraat Superior\",[\"Point de Hongrie Superior\",\"Fischgrät Superior\",\"Herringbone Superior\"]],[\"Tegels\",[\"Carreaux\",\"Fliesen\",\"Tiles\"]],[\"Panelen\",[\"Panneaux\",\"Paneele\",\"Panels\"]],[\"Wandpanelen\",[\"Panneaux muraux\",\"Wandpaneele\",\"Wall panels\"]],[\"Vensterbanken\",[\"Appuis de fenêtre\",\"Fensterbänke\",\"Windowsills\"]],[\"Gevelpaneel\",[\"Panneau de façade\",\"Fassadenpaneel\",\"Cladding panel\"]],[\"Ondervloeren\",[\"Sous-couches\",\"Unterlagen\",\"Underlays\"]],[\"Lijm\",[\"Colle\",\"Kleber\",\"Adhesive\"]],[\"Hoeveelheid\",[\"Quantité\",\"Menge\",\"Quantity\"]],[\"Levering\",[\"Livraison\",\"Lieferung\",\"Delivery\"]],[\"Bezorgen\",[\"Livraison\",\"Zustellung\",\"Delivery\"]],[\"Zelf afhalen\",[\"Enlèvement sur place\",\"Selbstabholung\",\"Collect yourself\"]],[\"Vloer\",[\"Sol\",\"Boden\",\"Floor\"]],[\"Wand\",[\"Mur\",\"Wand\",\"Wall\"]],[\"Vensterbank\",[\"Appui de fenêtre\",\"Fensterbank\",\"Windowsill\"]],[\"Gevelbekleding\",[\"Bardage\",\"Fassadenverkleidung\",\"Cladding\"]],[\"Alleen accessoires\",[\"Accessoires uniquement\",\"Nur Zubehör\",\"Accessories only\"]],[\"Referentie\",[\"Référence\",\"Referenz\",\"Reference\"]],[\"Download render\",[\"Télécharger le rendu\",\"Rendering herunterladen\",\"Download render\"]],[\"Decorstaal\",[\"Échantillon de décor\",\"Dekormuster\",\"Decor sample\"]],[\"Totale lengte\",[\"Longueur totale\",\"Gesamtlänge\",\"Total length\"]],[\"Dekkende printbreedte\",[\"Largeur d'impression couvrante\",\"Deckende Druckbreite\",\"Covering print width\"]],[\"Paneeloppervlak\",[\"Surface de panneaux\",\"Paneelfläche\",\"Panel area\"]],[\"Netto oppervlak\",[\"Surface nette\",\"Nettofläche\",\"Net area\"]],[\"Inclusief\",[\"Y compris\",\"Inklusive\",\"Including\"]],[\"Adviesprijs per\",[\"Prix conseillé par\",\"Preisempfehlung pro\",\"Recommended price per\"]],[\"Aantal stuks\",[\"Nombre de pièces\",\"Stückzahl\",\"Number of pieces\"]],[\"Aantal wandbeelden\",[\"Nombre de visuels muraux\",\"Anzahl Wandmotive\",\"Number of wall visuals\"]],[\"Aantal pakken\",[\"Nombre de paquets\",\"Anzahl Pakete\",\"Number of packs\"]],[\"Volvlaks verlijmd\",[\"Entièrement collé\",\"Vollflächig verklebt\",\"Fully glued\"]],[\"Annuleren\",[\"Annuler\",\"Abbrechen\",\"Cancel\"]],[\"Losadres\",[\"Adresse de déchargement\",\"Abladeadresse\",\"Unloading address\"]],[\"Land\",[\"Pays\",\"Land\",\"Country\"]],[\"Telefoon contactpersoon\",[\"Téléphone de la personne de contact\",\"Telefon Ansprechpartner\",\"Contact person phone\"]],[\"Nee\",[\"Non\",\"Nein\",\"No\"]],[\"Ja\",[\"Oui\",\"Ja\",\"Yes\"]],[\"Artikel\",[\"Article\",\"Artikel\",\"Item\"]],[\"Verwijderen\",[\"Supprimer\",\"Entfernen\",\"Remove\"]],[\"Subtotaal\",[\"Sous-total\",\"Zwischensumme\",\"Subtotal\"]],[\"Bedrijf\",[\"Entreprise\",\"Firma\",\"Company\"]],[\"Naam\",[\"Nom\",\"Name\",\"Name\"]],[\"Telefoon\",[\"Téléphone\",\"Telefon\",\"Phone\"]],[\"Postcode\",[\"Code postal\",\"Postleitzahl\",\"Postcode\"]],[\"Plaats\",[\"Localité\",\"Ort\",\"Town\"]],[\"Je gegevens\",[\"Vos coordonnées\",\"Ihre Daten\",\"Your details\"]],[\"Aanvraag versturen\",[\"Envoyer la demande\",\"Anfrage senden\",\"Send request\"]],[\"Bestelling plaatsen\",[\"Passer la commande\",\"Bestellung aufgeben\",\"Place order\"]],[\"Referentie klant\",[\"Référence client\",\"Kundenreferenz\",\"Customer reference\"]],[\"Debiteurennummer\",[\"Numéro client\",\"Kundennummer\",\"Customer number\"]],[\"Contact gewenst\",[\"Contact souhaité\",\"Kontakt gewünscht\",\"Contact requested\"]],[\"Artikelen\",[\"Articles\",\"Artikel\",\"Items\"]],[\"Artikelnummer\",[\"Référence d'article\",\"Artikelnummer\",\"Item number\"]],[\"Afbeelding\",[\"Image\",\"Bild\",\"Image\"]],[\"Wijze\",[\"Mode\",\"Art\",\"Method\"]],[\"Contactpersoon losadres\",[\"Contact adresse de déchargement\",\"Ansprechpartner Abladeadresse\",\"Contact at unloading address\"]],[\"Heftruck aanwezig\",[\"Chariot élévateur disponible\",\"Gabelstapler vorhanden\",\"Forklift available\"]],[\"Klantgegevens\",[\"Coordonnées client\",\"Kundendaten\",\"Customer details\"]],[\"Bedrijfsadres\",[\"Adresse de l'entreprise\",\"Firmenadresse\",\"Company address\"]],[\"Opmerking\",[\"Remarque\",\"Anmerkung\",\"Comment\"]],[\"Algemene voorwaarden\",[\"Conditions générales\",\"Allgemeine Geschäftsbedingungen\",\"Terms and conditions\"]],[\"Condities\",[\"Conditions\",\"Konditionen\",\"Terms\"]],[\"Bestelling\",[\"Commande\",\"Bestellung\",\"Order\"]],[\"Aanvraagnummer\",[\"Numéro de demande\",\"Anfragenummer\",\"Request number\"]],[\"Bestelnummer\",[\"Numéro de commande\",\"Bestellnummer\",\"Order number\"]],[\"Je referentie\",[\"Votre référence\",\"Ihre Referenz\",\"Your reference\"]],[\"Contact losadres\",[\"Contact déchargement\",\"Kontakt Abladeadresse\",\"Unloading contact\"]],[\"Je opmerking\",[\"Votre remarque\",\"Ihre Anmerkung\",\"Your comment\"]],[\"Bestelbevestiging\",[\"Confirmation de commande\",\"Auftragsbestätigung\",\"Order confirmation\"]],[\"Afdrukken\",[\"Imprimer\",\"Drucken\",\"Print\"]],[\"Nieuwe bestelling\",[\"Nouvelle commande\",\"Neue Bestellung\",\"New order\"]],[\"Je selectie\",[\"Votre sélection\",\"Ihre Auswahl\",\"Your selection\"]],[\"Vrijblijvende aanvraag\",[\"Demande sans engagement\",\"Unverbindliche Anfrage\",\"No-obligation request\"]],[\"Plank Superior\",[\"Lame Superior\",\"Diele Superior\",\"Plank Superior\"]],[\"Toepassing\",[\"Application\",\"Anwendung\",\"Application\"]],[\"Van binnen\",[\"De l'intérieur\",\"Von innen\",\"From inside\"]],[\"Ruimte\",[\"Pièce\",\"Raum\",\"Room\"]],[\"Wanden\",[\"Murs\",\"Wände\",\"Walls\"]],[\"Inrichting\",[\"Aménagement\",\"Einrichtung\",\"Furnishing\"]],[\"Maten in cm\",[\"Dimensions en cm\",\"Maße in cm\",\"Dimensions in cm\"]],[\"Product\",[\"Produit\",\"Produkt\",\"Product\"]],[\"Tot plafond\",[\"Jusqu'au plafond\",\"Bis zur Decke\",\"Up to ceiling\"]],[\"+ Deur\",[\"+ Porte\",\"+ Tür\",\"+ Door\"]],[\"+ Raam\",[\"+ Fenêtre\",\"+ Fenster\",\"+ Window\"]],[\"Leggen\",[\"Pose\",\"Verlegung\",\"Laying\"]],[\"Ondervloer\",[\"Sous-couche\",\"Unterlage\",\"Underlay\"]],[\"3D-beeld\",[\"Vue 3D\",\"3D-Ansicht\",\"3D view\"]],[\"Schroeven\",[\"Vis\",\"Schrauben\",\"Screws\"]],[\"Bestellijst\",[\"Liste de commande\",\"Bestellliste\",\"Order list\"]],[\"Naam *\",[\"Nom *\",\"Name *\",\"Name *\"]],[\"E-mail *\",[\"E-mail *\",\"E-Mail *\",\"Email *\"]],[\"3D-weergave\",[\"Vue 3D\",\"3D-Ansicht\",\"3D view\"]],[\"Camera\",[\"Caméra\",\"Kamera\",\"Camera\"]],[\"Inzoomen\",[\"Zoom avant\",\"Vergrößern\",\"Zoom in\"]],[\"Uitzoomen\",[\"Zoom arrière\",\"Verkleinern\",\"Zoom out\"]],[\"Weergave\",[\"Affichage\",\"Ansicht\",\"View\"]],[\"m² per\",[\"m² par\",\"m² pro\",\"m² per\"]],[\"Rechthoek\",[\"Rectangle\",\"Rechteck\",\"Rectangle\"]],[\"Breedte\",[\"Largeur\",\"Breite\",\"Width\"]],[\"Diepte\",[\"Profondeur\",\"Tiefe\",\"Depth\"]],[\"Met nis\",[\"Avec niche\",\"Mit Nische\",\"With recess\"]],[\"Breedte nis\",[\"Largeur de la niche\",\"Breite Nische\",\"Recess width\"]],[\"Diepte nis\",[\"Profondeur de la niche\",\"Tiefe Nische\",\"Recess depth\"]],[\"Met koof\",[\"Avec coffrage\",\"Mit Kofferung\",\"With boxing\"]],[\"Diepte koof\",[\"Profondeur du coffrage\",\"Tiefe Kofferung\",\"Boxing depth\"]],[\"Marmer\",[\"Marbre\",\"Marmor\",\"Marble\"]],[\"Egaal\",[\"Uni\",\"Uni\",\"Plain\"]],[\"Hout\",[\"Bois\",\"Holz\",\"Wood\"]],[\"Print\",[\"Impression\",\"Druck\",\"Print\"]],[\"Badkamer\",[\"Salle de bains\",\"Badezimmer\",\"Bathroom\"]],[\"Hangtoilet\",[\"WC suspendu\",\"Wand-WC\",\"Wall-hung toilet\"]],[\"Inbouwbad\",[\"Baignoire encastrée\",\"Einbauwanne\",\"Built-in bath\"]],[\"Spiegel\",[\"Miroir\",\"Spiegel\",\"Mirror\"]],[\"Spiegelkast\",[\"Armoire de toilette\",\"Spiegelschrank\",\"Mirror cabinet\"]],[\"Hangkast\",[\"Meuble suspendu\",\"Hängeschrank\",\"Wall cabinet\"]],[\"Kolomkast\",[\"Colonne\",\"Hochschrank\",\"Tall cabinet\"]],[\"Woonkamer\",[\"Séjour\",\"Wohnzimmer\",\"Living room\"]],[\"Kast\",[\"Armoire\",\"Schrank\",\"Cabinet\"]],[\"Bank\",[\"Canapé\",\"Sofa\",\"Sofa\"]],[\"Fauteuil\",[\"Fauteuil\",\"Sessel\",\"Armchair\"]],[\"Tafel\",[\"Table\",\"Tisch\",\"Table\"]],[\"Stoel\",[\"Chaise\",\"Stuhl\",\"Chair\"]],[\"Plant\",[\"Plante\",\"Pflanze\",\"Plant\"]],[\"Bed\",[\"Lit\",\"Bett\",\"Bed\"]],[\"Bureau\",[\"Bureau\",\"Schreibtisch\",\"Desk\"]],[\"Keuken\",[\"Cuisine\",\"Küche\",\"Kitchen\"]],[\"Eiken\",[\"Chêne\",\"Eiche\",\"Oak\"]],[\"Noten\",[\"Noyer\",\"Nussbaum\",\"Walnut\"]],[\"Lichtgrijs\",[\"Gris clair\",\"Hellgrau\",\"Light grey\"]],[\"Salie\",[\"Sauge\",\"Salbei\",\"Sage\"]],[\"Nachtblauw\",[\"Bleu nuit\",\"Nachtblau\",\"Midnight blue\"]],[\"Chroom\",[\"Chrome\",\"Chrom\",\"Chrome\"]],[\"Goud\",[\"Or\",\"Gold\",\"Gold\"]],[\"Helder glas\",[\"Verre clair\",\"Klarglas\",\"Clear glass\"]],[\"Rookglas\",[\"Verre fumé\",\"Rauchglas\",\"Smoked glass\"]],[\"Melkglas\",[\"Verre dépoli\",\"Milchglas\",\"Frosted glass\"]],[\"Toilet\",[\"Toilettes\",\"WC\",\"Toilet\"]],[\"Wasruimte\",[\"Buanderie\",\"Waschraum\",\"Utility room\"]],[\"Slaapkamer\",[\"Chambre\",\"Schlafzimmer\",\"Bedroom\"]],[\"Hal\",[\"Hall\",\"Flur\",\"Hallway\"]],[\"Soort\",[\"Type\",\"Art\",\"Type\"]],[\"Deur\",[\"Porte\",\"Tür\",\"Door\"]],[\"Raam\",[\"Fenêtre\",\"Fenster\",\"Window\"]],[\"Nis\",[\"Niche\",\"Nische\",\"Recess\"]],[\"Hoogte\",[\"Hauteur\",\"Höhe\",\"Height\"]],[\"Vanaf links\",[\"Depuis la gauche\",\"Von links\",\"From left\"]],[\"Vanaf vloer\",[\"Depuis le sol\",\"Vom Boden\",\"From floor\"]],[\"Verwijder\",[\"Supprimer\",\"Entfernen\",\"Remove\"]],[\"Lengte\",[\"Longueur\",\"Länge\",\"Length\"]],[\"Indicatie\",[\"Indication\",\"Richtwert\",\"Estimate\"]],[\"Ruimte:\",[\"Pièce :\",\"Raum:\",\"Room:\"]],[\"Vorm:\",[\"Forme :\",\"Form:\",\"Shape:\"]],[\"Vloer:\",[\"Sol :\",\"Boden:\",\"Floor:\"]],[\"Subtotaal:\",[\"Sous-total :\",\"Zwischensumme:\",\"Subtotal:\"]],[\"Kleur\",[\"Couleur\",\"Farbe\",\"Colour\"]],[\"Draai 90°\",[\"Pivoter de 90°\",\"90° drehen\",\"Rotate 90°\"]],[\"Dupliceer\",[\"Dupliquer\",\"Duplizieren\",\"Duplicate\"]],[\"geplaatst\",[\"placé\",\"platziert\",\"placed\"]],[\"België\",[\"Belgique\",\"Belgien\",\"Belgium\"]],[\"Vorm\",[\"Forme\",\"Form\",\"Shape\"]],[\"Afmetingen\",[\"Dimensions\",\"Maße\",\"Dimensions\"]],[\"ca.\",[\"env.\",\"ca.\",\"approx.\"]],[\"Aanvrager\",[\"Demandeur\",\"Anfragender\",\"Requested by\"]],[\"Prijs\",[\"Prix\",\"Preis\",\"Price\"]],[\"Totaal\",[\"Total\",\"Gesamt\",\"Total\"]],[\"Pagina\",[\"Page\",\"Seite\",\"Page\"]],[\"van\",[\"sur\",\"von\",\"of\"]],[\"Pdf maken…\",[\"Création du PDF…\",\"PDF wird erstellt…\",\"Creating PDF…\"]],[\"Bezig…\",[\"En cours…\",\"Bitte warten…\",\"Working…\"]],[\"Sluiten\",[\"Fermer\",\"Schließen\",\"Close\"]],[\"Overzicht\",[\"Vue d'ensemble\",\"Übersicht\",\"Overview\"]],[\"Ruimteplanner\",[\"Planificateur de pièce\",\"Raumplaner\",\"Room Planner\"]],[\"L-vorm\",[\"Forme en L\",\"L-Form\",\"L-shape\"]]],\"s\":[[\"Stel je bestelling samen\",[\"Composez votre commande\",\"Stellen Sie Ihre Bestellung zusammen\",\"Put together your order\"]],[\"Kies toepassing, serie en decor en vul je oppervlakte of het aantal pakken in. Dealers plaatsen hier rechtstreeks hun bestelling en ontvangen zo spoedig mogelijk een tegenbevestiging met de inkoopprijzen. Particulieren kunnen de tool vrijblijvend gebruiken; hun aanvraag gaat naar een dealer bij hen in de buurt. Alle getoonde prijzen zijn adviesverkoopprijzen voor consumenten, inclusief btw.\",[\"Choisissez l'application, la série et le décor, puis indiquez votre surface ou le nombre de paquets. Les revendeurs passent ici directement leur commande et reçoivent dans les meilleurs délais une confirmation avec les prix d'achat. Les particuliers peuvent utiliser l'outil sans engagement ; leur demande est transmise à un revendeur près de chez eux. Tous les prix affichés sont des prix de vente conseillés pour les consommateurs, TVA comprise.\",\"Wählen Sie Anwendung, Serie und Dekor und geben Sie Ihre Fläche oder die Anzahl der Pakete ein. Händler geben hier direkt ihre Bestellung auf und erhalten schnellstmöglich eine Auftragsbestätigung mit den Einkaufspreisen. Privatkunden können das Tool unverbindlich nutzen; ihre Anfrage geht an einen Händler in ihrer Nähe. Alle angezeigten Preise sind unverbindliche Verkaufspreise für Verbraucher inklusive MwSt.\",\"Choose the application, range and decor, then enter your area or the number of packs. Dealers place their order here directly and receive a confirmation with purchase prices as soon as possible. Consumers can use the tool without obligation; their request goes to a dealer near them. All prices shown are recommended retail prices for consumers, including VAT.\"]],[\"prijslijst 07-2026\",[\"liste de prix 07-2026\",\"Preisliste 07-2026\",\"price list 07-2026\"]],[\"Massieve waterdichte SPC-kern met Luxury Wood Embossed houtreliëf en micro V-groef. Slechts 4 mm dik, daarom bij uitstek geschikt voor renovatie.\",[\"Noyau SPC massif et étanche avec relief bois Luxury Wood Embossed et micro-chanfrein en V. Seulement 4 mm d'épaisseur, idéal pour la rénovation.\",\"Massiver, wasserdichter SPC-Kern mit Luxury Wood Embossed Holzprägung und Micro-V-Fuge. Nur 4 mm stark und daher ideal für Renovierungen.\",\"Solid waterproof SPC core with Luxury Wood Embossed texture and micro V-groove. Only 4 mm thick, making it ideal for renovation.\"]],[\"Dikte 4,0 mm\",[\"Épaisseur 4,0 mm\",\"Stärke 4,0 mm\",\"Thickness 4.0 mm\"]],[\"Slijtlaag 0,3 mm (klasse 32)\",[\"Couche d'usure 0,3 mm (classe 32)\",\"Nutzschicht 0,3 mm (Klasse 32)\",\"Wear layer 0.3 mm (class 32)\"]],[\"Oppervlak: Luxury Wood Embossed\",[\"Surface : Luxury Wood Embossed\",\"Oberfläche: Luxury Wood Embossed\",\"Surface: Luxury Wood Embossed\"]],[\"Plaatsing: zwevend of volvlaks verlijmd\",[\"Pose : flottante ou entièrement collée\",\"Verlegung: schwimmend oder vollflächig verklebt\",\"Installation: floating or fully glued\"]],[\"Zwaardere uitvoering met Embossed in Register: het houtreliëf loopt exact in lijn met het decor. Dikkere slijtlaag, geschikt voor intensief gebruik.\",[\"Version plus robuste avec Embossed in Register : le relief bois suit exactement le décor. Couche d’usure plus épaisse, adaptée à un usage intensif.\",\"Robustere Ausführung mit Embossed in Register: Die Holzprägung verläuft exakt synchron zum Dekor. Dickere Nutzschicht, geeignet für intensive Nutzung.\",\"Heavier-duty version with Embossed in Register: the wood texture follows the decor exactly. Thicker wear layer, suitable for intensive use.\"]],[\"Dikte 4,5 mm\",[\"Épaisseur 4,5 mm\",\"Stärke 4,5 mm\",\"Thickness 4.5 mm\"]],[\"Slijtlaag 0,5 mm (klasse 33)\",[\"Couche d'usure 0,5 mm (classe 33)\",\"Nutzschicht 0,5 mm (Klasse 33)\",\"Wear layer 0.5 mm (class 33)\"]],[\"Oppervlak: Embossed in Register\",[\"Surface : Embossed in Register\",\"Oberfläche: Embossed in Register\",\"Surface: Embossed in Register\"]],[\"Klassiek visgraatmotief in SPC, met geverfde V-groef en Drop Lock-klik. Houd rekening met meer snijverlies dan bij een rechte plank.\",[\"Motif point de Hongrie classique en SPC, avec chanfrein en V peint et clic Drop Lock. Prévoyez davantage de chutes qu'avec une lame droite.\",\"Klassisches Fischgrätmuster in SPC mit lackierter V-Fuge und Drop-Lock-Klick. Rechnen Sie mit mehr Verschnitt als bei einer geraden Diele.\",\"Classic herringbone pattern in SPC, with painted V-groove and Drop Lock click. Allow for more cutting waste than with a straight plank.\"]],[\"Visgraat met Embossed in Register: het reliëf volgt de houtnerf van het decor exact. De meest verfijnde uitvoering uit de vloercollectie.\",[\"Point de Hongrie avec Embossed in Register : le relief suit exactement le veinage du décor. La version la plus raffinée de la collection de sols.\",\"Fischgrät mit Embossed in Register: Die Prägung folgt exakt der Holzmaserung des Dekors. Die edelste Ausführung der Bodenkollektion.\",\"Herringbone with Embossed in Register: the texture follows the wood grain of the decor exactly. The most refined version in the floor collection.\"]],[\"Tiles 61 × 30,5 cm\",[\"Tiles 61 × 30,5 cm\",\"Tiles 61 × 30,5 cm\",\"Tiles 61 × 30.5 cm\"]],[\"Tegelformaat met natuurgetrouwe steenstructuur. Door de dikte van 4 mm vaak rechtstreeks over bestaande vloer- of wandtegels te plaatsen, zonder breekwerk.\",[\"Format carreau avec texture de pierre naturelle. Grâce à leur épaisseur de 4 mm, ils se posent souvent directement sur le carrelage de sol ou mural existant, sans démolition.\",\"Fliesenformat mit naturgetreuer Steinstruktur. Dank der Stärke von 4 mm oft direkt über vorhandene Boden- oder Wandfliesen verlegbar, ohne Abbrucharbeiten.\",\"Tile format with a true-to-nature stone texture. At 4 mm thick, they can often be installed directly over existing floor or wall tiles, without demolition.\"]],[\"Oppervlak: Mat steenstructuur\",[\"Surface : texture pierre mate\",\"Oberfläche: matte Steinstruktur\",\"Surface: matt stone texture\"]],[\"Plaatsing: zwevend of verlijmd, vloer én wand\",[\"Pose : flottante ou collée, sol et mur\",\"Verlegung: schwimmend oder verklebt, Boden und Wand\",\"Installation: floating or glued, floor and wall\"]],[\"Tiles XL 95 × 47,5 cm\",[\"Tiles XL 95 × 47,5 cm\",\"Tiles XL 95 × 47,5 cm\",\"Tiles XL 95 × 47.5 cm\"]],[\"Groot tegelformaat voor een rustiger beeld met minder voegen. Zelfde opbouw als de standaardtegel, zelfde kliksysteem.\",[\"Grand format de carreau pour un rendu plus calme avec moins de joints. Même composition que le carreau standard, même système de clic.\",\"Großes Fliesenformat für ein ruhigeres Bild mit weniger Fugen. Gleicher Aufbau wie die Standardfliese, gleiches Klicksystem.\",\"Large tile format for a calmer look with fewer joints. Same build-up as the standard tile, same click system.\"]],[\"Panels 260,5 × 48,2 cm\",[\"Panels 260,5 × 48,2 cm\",\"Panels 260,5 × 48,2 cm\",\"Panels 260.5 × 48.2 cm\"]],[\"Wandpaneel over de volle hoogte met Click \u0026 Screw-verbinding. Naadloos beeld, 100% waterdicht, ook in douche- en badruimtes.\",[\"Panneau mural toute hauteur avec système Click \u0026 Screw. Rendu sans joints, 100 % étanche, même dans les douches et salles de bains.\",\"Wandpaneel über die volle Höhe mit Click \u0026 Screw-Verbindung. Fugenloses Bild, 100 % wasserdicht, auch in Dusche und Bad.\",\"Full-height wall panel with Click \u0026 Screw joint. Seamless look, 100% waterproof, even in showers and bathrooms.\"]],[\"Slijtlaag klasse 32\",[\"Couche d'usure classe 32\",\"Nutzschicht Klasse 32\",\"Wear layer class 32\"]],[\"Oppervlak: Ultramat / Minipearl / houtstructuur\",[\"Surface : ultra-mate / minipearl / structure bois\",\"Oberfläche: Ultramatt / Minipearl / Holzstruktur\",\"Surface: ultra-matt / minipearl / wood texture\"]],[\"Plaatsing: verlijmd of geschroefd\",[\"Pose : collée ou vissée\",\"Montage: verklebt oder verschraubt\",\"Installation: glued or screwed\"]],[\"Combi-Panels 122 × 32 cm\",[\"Combi-Panels 122 × 32 cm\",\"Combi-Panels 122 × 32 cm\",\"Combi-Panels 122 × 32 cm\"]],[\"Combi-panelen\",[\"Combi-panels\",\"Combi-Panels\",\"Combi-panels\"]],[\"Korter paneelformaat voor accentwanden en lagere vlakken, met dezelfde naadloze Click \u0026 Screw-verbinding als de grote panelen.\",[\"Format de panneau plus court pour les murs d’accent et les surfaces basses, avec le même système Click \u0026 Screw sans joints que les grands panneaux.\",\"Kürzeres Paneelformat für Akzentwände und niedrigere Flächen, mit derselben fugenlosen Click \u0026 Screw-Verbindung wie die großen Paneele.\",\"Shorter panel format for feature walls and lower surfaces, with the same seamless Click \u0026 Screw joint as the large panels.\"]],[\"Oppervlak: Steenstructuur / Minipearl / houtstructuur\",[\"Surface : structure pierre / minipearl / structure bois\",\"Oberfläche: Steinstruktur / Minipearl / Holzstruktur\",\"Surface: stone texture / minipearl / wood texture\"]],[\"Digi-Panels — Horizon collectie\",[\"Digi-Panels — collection Horizon\",\"Digi-Panels — Horizon Kollektion\",\"Digi-Panels — Horizon collection\"]],[\"Doorlopende digitale print over een volledige wand, zonder voegen of onderbrekingen. Per wandbeeld besteld, in twee breedtes.\",[\"Impression numérique continue sur tout un mur, sans joints ni interruptions. Commandé par visuel mural, en deux largeurs.\",\"Durchgehender Digitaldruck über eine ganze Wand, ohne Fugen oder Unterbrechungen. Pro Wandmotiv bestellt, in zwei Breiten.\",\"Continuous digital print across an entire wall, without joints or interruptions. Ordered per wall visual, in two widths.\"]],[\"Oppervlak: Digitale print, meerlaags afgewerkt\",[\"Surface : impression numérique, finition multicouche\",\"Oberfläche: Digitaldruck, mehrschichtig veredelt\",\"Surface: digital print, multi-layer finish\"]],[\"Digi-Panels — Flow collectie\",[\"Digi-Panels — collection Flow\",\"Digi-Panels — Flow Kollektion\",\"Digi-Panels — Flow collection\"]],[\"Doorlopend printdecor op panelen van 48,2 cm, te combineren tot elke wandbreedte. Bestelbaar per pak van 2 panelen.\",[\"Décor imprimé continu sur des panneaux de 48,2 cm, combinables pour toute largeur de mur. Commande par paquet de 2 panneaux.\",\"Durchgehendes Druckdekor auf Paneelen von 48,2 cm, kombinierbar für jede Wandbreite. Bestellbar pro Paket mit 2 Paneelen.\",\"Continuous printed decor on 48.2 cm panels, combinable for any wall width. Ordered per pack of 2 panels.\"]],[\"Boards 260,5 × 96,5 cm\",[\"Boards 260,5 × 96,5 cm\",\"Boards 260,5 × 96,5 cm\",\"Boards 260.5 × 96.5 cm\"]],[\"Grote plaat van bijna een meter breed: de snelste weg naar een vlakke, voegloze wand. Wordt met polymeerlijm bevestigd.\",[\"Grande plaque de près d'un mètre de large : le moyen le plus rapide d'obtenir un mur plan et sans joints. Fixée avec de la colle polymère.\",\"Große Platte mit fast einem Meter Breite: der schnellste Weg zu einer ebenen, fugenlosen Wand. Wird mit Polymerkleber befestigt.\",\"Large board almost one metre wide: the fastest route to a flat, joint-free wall. Fixed with polymer adhesive.\"]],[\"Plaatsing: verlijmd\",[\"Pose : collée\",\"Montage: verklebt\",\"Installation: glued\"]],[\"Per 2 stuks in doos\",[\"Par 2 pièces en carton\",\"2 Stück pro Karton\",\"2 pieces per box\"]],[\"Vensterbanken 290 × 30 cm\",[\"Windowsills 290 × 30 cm\",\"Windowsills 290 × 30 cm\",\"Windowsills 290 × 30 cm\"]],[\"Slechts 5 mm dik en daarmee rechtstreeks over de bestaande vensterbank te plaatsen. Neus van 36 mm, af te werken met bijpassend zelfklevend kantenband.\",[\"Seulement 5 mm d'épaisseur, à poser directement sur l'appui de fenêtre existant. Nez de 36 mm, à finir avec une bande de chant autocollante assortie.\",\"Nur 5 mm stark und dadurch direkt über die vorhandene Fensterbank montierbar. Nase von 36 mm, mit passendem selbstklebendem Kantenband abzuschließen.\",\"Only 5 mm thick, so it can be installed directly over the existing windowsill. 36 mm nosing, finished with matching self-adhesive edge tape.\"]],[\"Dikte 5,0 mm\",[\"Épaisseur 5,0 mm\",\"Stärke 5,0 mm\",\"Thickness 5.0 mm\"]],[\"Oppervlak: Minipearl / Luxury Wood Embossed\",[\"Surface : minipearl / Luxury Wood Embossed\",\"Oberfläche: Minipearl / Luxury Wood Embossed\",\"Surface: minipearl / Luxury Wood Embossed\"]],[\"Plaatsing: verlijmd met polymeerlijm\",[\"Pose : collée avec de la colle polymère\",\"Montage: mit Polymerkleber verklebt\",\"Installation: glued with polymer adhesive\"]],[\"Gevelpaneel 180 mm\",[\"Panneau de façade 180 mm\",\"Fassadenpaneel 180 mm\",\"Cladding panel 180 mm\"]],[\"Kunststof gevelpaneel van 6 meter met kleurechte structuurfolie, zonder schaduwvoeg. Duits fabricaat met 10 jaar garantie op kleurechtheid en slagvastheid.\",[\"Panneau de façade en PVC de 6 mètres avec film structuré grand teint, sans joint creux. Fabrication allemande avec 10 ans de garantie sur la tenue des couleurs et la résistance aux chocs.\",\"Kunststoff-Fassadenpaneel von 6 Metern mit farbechter Strukturfolie, ohne Schattenfuge. Deutsches Fabrikat mit 10 Jahren Garantie auf Farbechtheit und Schlagfestigkeit.\",\"6-metre PVC cladding panel with colourfast textured foil, without shadow gap. German-made, with a 10-year guarantee on colourfastness and impact resistance.\"]],[\"Oppervlak: Structuurfolie\",[\"Surface : film structuré\",\"Oberfläche: Strukturfolie\",\"Surface: textured foil\"]],[\"Plaatsing: horizontaal of verticaal, op regelwerk\",[\"Pose : horizontale ou verticale, sur lattage\",\"Montage: horizontal oder vertikal, auf Lattung\",\"Installation: horizontal or vertical, on battens\"]],[\"Gevelpaneel DUO 200 mm\",[\"Panneau de façade DUO 200 mm\",\"Fassadenpaneel DUO 200 mm\",\"Cladding panel DUO 200 mm\"]],[\"Gevelpaneel DUO\",[\"Panneau de façade DUO\",\"Fassadenpaneel DUO\",\"Cladding panel DUO\"]],[\"Gevelpaneel van 6 meter met antracieten schaduwvoeg, werkende maat 200 mm. Houtdecors met kleurechte structuurfolie.\",[\"Panneau de façade de 6 mètres avec joint creux anthracite, largeur utile 200 mm. Décors bois avec film structuré grand teint.\",\"Fassadenpaneel von 6 Metern mit anthrazitfarbener Schattenfuge, Nutzbreite 200 mm. Holzdekore mit farbechter Strukturfolie.\",\"6-metre cladding panel with anthracite shadow gap, working width 200 mm. Wood decors with colourfast textured foil.\"]],[\"Oppervlak: Structuurfolie, voeg antraciet\",[\"Surface : film structuré, joint anthracite\",\"Oberfläche: Strukturfolie, Fuge anthrazit\",\"Surface: textured foil, anthracite joint\"]],[\"Voor zwevend gelegde vloeren. Zorgt voor een egale basis en voldoende druksterkte onder de klikverbinding.\",[\"Pour les sols en pose flottante. Assure une base uniforme et une résistance à la compression suffisante sous le système de clic.\",\"Für schwimmend verlegte Böden. Sorgt für einen ebenen Untergrund und ausreichende Druckfestigkeit unter der Klickverbindung.\",\"For floating floors. Provides an even base and sufficient compressive strength under the click joint.\"]],[\"rol van 15 m² — hoogste druksterkte\",[\"rouleau de 15 m² — résistance à la compression la plus élevée\",\"Rolle mit 15 m² — höchste Druckfestigkeit\",\"15 m² roll — highest compressive strength\"]],[\"pak van 14,85 m²\",[\"paquet de 14,85 m²\",\"Paket mit 14,85 m²\",\"pack of 14.85 m²\"]],[\"Hybride polymeerlijm voor tegels, panelen en vensterbanken. Ontvet vooraf zowel de ondergrond als de achterzijde.\",[\"Colle polymère hybride pour carreaux, panneaux et appuis de fenêtre. Dégraissez au préalable le support et le dos du produit.\",\"Hybrid-Polymerkleber für Fliesen, Paneele und Fensterbänke. Vorher sowohl den Untergrund als auch die Rückseite entfetten.\",\"Hybrid polymer adhesive for tiles, panels and windowsills. Degrease both the substrate and the back of the product first.\"]],[\"Hybride polymeerlijm — koker 290 ml\",[\"Colle polymère hybride — cartouche 290 ml\",\"Hybrid-Polymerkleber — Kartusche 290 ml\",\"Hybrid polymer adhesive — 290 ml cartridge\"]],[\"verbruik ca. 1 koker per 2 m²\",[\"consommation env. 1 cartouche par 2 m²\",\"Verbrauch ca. 1 Kartusche pro 2 m²\",\"coverage approx. 1 cartridge per 2 m²\"]],[\"Hybride polymeerlijm — emmer 5 kg\",[\"Colle polymère hybride — seau de 5 kg\",\"Hybrid-Polymerkleber — Eimer 5 kg\",\"Hybrid polymer adhesive — 5 kg bucket\"]],[\"volvlaks met lijmkam, 0,8 kg per m²\",[\"sur toute la surface avec une spatule crantée, 0,8 kg par m²\",\"vollflächig mit Zahnspachtel, 0,8 kg pro m²\",\"full-surface with a notched trowel, 0.8 kg per m²\"]],[\"Kantenband voor vensterbanken\",[\"Bande de chant pour appuis de fenêtre\",\"Kantenband für Fensterbänke\",\"Edge tape for windowsills\"]],[\"Zelfklevende strip van 300 × 35 mm om de open zijden netjes af te werken.\",[\"Bande autocollante de 300 × 35 mm pour une finition soignée des côtés ouverts.\",\"Selbstklebender Streifen von 300 × 35 mm für einen sauberen Abschluss der offenen Seiten.\",\"Self-adhesive 300 × 35 mm strip for a neat finish on the open sides.\"]],[\"PVC-profielen 2600 mm\",[\"Profilés PVC 2600 mm\",\"PVC-Profile 2600 mm\",\"PVC profiles 2600 mm\"]],[\"Binnen- en buitenhoeken en U-profielen voor een nette rand, per stuk te bestellen.\",[\"Angles intérieurs et extérieurs et profilés en U pour une finition nette, à commander à la pièce.\",\"Innen- und Außenecken sowie U-Profile für einen sauberen Abschluss, einzeln bestellbar.\",\"Inside and outside corners and U-profiles for a neat edge, ordered per piece.\"]],[\"Aluminium profielen 2700 mm\",[\"Profilés aluminium 2700 mm\",\"Aluminiumprofile 2700 mm\",\"Aluminium profiles 2700 mm\"]],[\"Aluminium hoek- en startprofielen in vier afwerkingen, per stuk te bestellen.\",[\"Profilés d’angle et de départ en aluminium en quatre finitions, à commander à la pièce.\",\"Aluminium-Eck- und Startprofile in vier Ausführungen, einzeln bestellbar.\",\"Aluminium corner and starter profiles in four finishes, ordered per piece.\"]],[\"Exterior Line — hoek- en verbindingsprofielen\",[\"Exterior Line — profilés d’angle et de jonction\",\"Exterior Line — Eck- und Verbindungsprofile\",\"Exterior Line — corner and connecting profiles\"]],[\"6 meter lang, in dezelfde kleurechte structuurfolie als de gevelpanelen.\",[\"6 mètres de long, dans le même film structuré grand teint que les panneaux de façade.\",\"6 Meter lang, in derselben farbechten Strukturfolie wie die Fassadenpaneele.\",\"6 metres long, in the same colourfast textured foil as the cladding panels.\"]],[\"Exterior Line — start- en ventilatieprofielen\",[\"Exterior Line — profilés de départ et de ventilation\",\"Exterior Line — Start- und Lüftungsprofile\",\"Exterior Line — starter and ventilation profiles\"]],[\"De basis van het montagesysteem; ventilatieprofielen houden de luchtspouw achter de gevel open.\",[\"La base du système de montage ; les profilés de ventilation maintiennent la lame d'air derrière la façade ouverte.\",\"Die Basis des Montagesystems; Lüftungsprofile halten die Luftschicht hinter der Fassade offen.\",\"The basis of the mounting system; ventilation profiles keep the air cavity behind the cladding open.\"]],[\"Exterior Line — boeidelen en toebehoren\",[\"Exterior Line — bandeaux de rive et accessoires\",\"Exterior Line — Stirnbretter und Zubehör\",\"Exterior Line — fascia boards and accessories\"]],[\"Sluit de gevel strak af tot aan de dakgoot.\",[\"Finit proprement la façade jusqu'à la gouttière.\",\"Schließt die Fassade sauber bis zur Dachrinne ab.\",\"Gives the cladding a neat finish up to the gutter.\"]],[\"De getoonde prijzen zijn adviesverkoopprijzen voor consumenten, inclusief btw (\",[\"Les prix affichés sont des prix de vente conseillés pour les consommateurs, TVA comprise (\",\"Die angezeigten Preise sind unverbindliche Verkaufspreise für Verbraucher inklusive MwSt. (\",\"The prices shown are recommended retail prices for consumers, including VAT (\"]],[\"). Je inkoopprijzen volgens de geldende prijsafspraken staan op onze tegenbevestiging.\",[\"). Vos prix d’achat selon les accords tarifaires en vigueur figurent sur notre confirmation de commande.\",\"). Ihre Einkaufspreise gemäß den geltenden Preisvereinbarungen stehen auf unserer Auftragsbestätigung.\",\"). Your purchase prices according to the applicable price agreements are shown on our order confirmation.\"]],[\"Ik ben nieuw\",[\"Je suis nouveau\",\"Ich bin neu\",\"I'm new\"]],[\"via de dealer\",[\"via le revendeur\",\"über den Händler\",\"via the dealer\"]],[\"afhalen (kosteloos)\",[\"enlèvement (gratuit)\",\"Abholung (kostenlos)\",\"collection (free of charge)\"]],[\"volgens prijsafspraak\",[\"selon accord tarifaire\",\"gemäß Preisvereinbarung\",\"as per price agreement\"]],[\"Adviesprijzen voor consumenten, inclusief btw (\",[\"Prix conseillés pour les consommateurs, TVA comprise (\",\"Unverbindliche Preisempfehlung für Verbraucher inkl. MwSt. (\",\"Recommended consumer prices, including VAT (\"]],[\"). HB Composite levert niet rechtstreeks aan particulieren: je aanvraag gaat naar een HB Composite-dealer bij je in de buurt. De dealer neemt contact met je op over de levering. De definitieve prijs ontvang je van de dealer.\",[\"). HB Composite ne livre pas directement aux particuliers : votre demande est transmise à un revendeur HB Composite près de chez vous. Le revendeur vous contacte au sujet de la livraison. Vous recevez le prix définitif du revendeur.\",\"). HB Composite liefert nicht direkt an Privatkunden: Ihre Anfrage geht an einen HB Composite-Händler in Ihrer Nähe. Der Händler setzt sich wegen der Lieferung mit Ihnen in Verbindung. Den endgültigen Preis erhalten Sie vom Händler.\",\"). HB Composite does not deliver directly to consumers: your request goes to an HB Composite dealer near you. The dealer will contact you about delivery. You will receive the final price from the dealer.\"]],[\"Op al onze leveringen zijn onze\",[\"Toutes nos livraisons sont soumises à nos\",\"Für alle unsere Lieferungen gelten unsere\",\"All our deliveries are subject to our\"]],[\"algemene leverings- en verkoopvoorwaarden\",[\"conditions générales de livraison et de vente\",\"Allgemeinen Liefer- und Verkaufsbedingungen\",\"general terms and conditions of delivery and sale\"]],[\"van toepassing.\",[\".\",\".\",\".\"]],[\"Plank, visgraat of tegels — zwevend of volvlaks verlijmd.\",[\"Lame, point de Hongrie ou carreaux — flottant ou entièrement collé.\",\"Diele, Fischgrät oder Fliesen — schwimmend oder vollflächig verklebt.\",\"Plank, herringbone or tiles — floating or fully glued.\"]],[\"Panelen, boards, tegels en digitale printwanden. 100% waterdicht.\",[\"Panneaux, plaques, carreaux et murs à impression numérique. 100 % étanche.\",\"Paneele, Platten, Fliesen und digital bedruckte Wände. 100 % wasserdicht.\",\"Panels, boards, tiles and digitally printed walls. 100% waterproof.\"]],[\"5 mm dik, rechtstreeks over de bestaande vensterbank.\",[\"5 mm d'épaisseur, directement sur l'appui de fenêtre existant.\",\"5 mm stark, direkt über die vorhandene Fensterbank.\",\"5 mm thick, directly over the existing windowsill.\"]],[\"Exterior Line: kunststof gevelpanelen van 6 meter.\",[\"Exterior Line : panneaux de façade en PVC de 6 mètres.\",\"Exterior Line: Kunststoff-Fassadenpaneele von 6 Metern.\",\"Exterior Line: 6-metre PVC cladding panels.\"]],[\"Lijm, profielen, kantenband en ondervloeren uit het hele assortiment.\",[\"Colle, profilés, bande de chant et sous-couches pour toute la gamme.\",\"Kleber, Profile, Kantenband und Unterlagen für das gesamte Sortiment.\",\"Adhesive, profiles, edge tape and underlays for the whole range.\"]],[\"m totale lengte\",[\"m de longueur totale\",\"m Gesamtlänge\",\"m total length\"]],[\"m² (rechtstreeks\",[\"m² (directement\",\"m² (direkt\",\"m² (directly\"]],[\"% snijverlies →\",[\"% de chutes →\",\"% Verschnitt →\",\"% cutting waste →\"]],[\"m² paneeloppervlak\",[\"m² de surface de panneaux\",\"m² Paneelfläche\",\"m² panel area\"]],[\"← Vorige stap\",[\"← Étape précédente\",\"← Vorheriger Schritt\",\"← Previous step\"]],[\"Volgende stap →\",[\"Étape suivante →\",\"Nächster Schritt →\",\"Next step →\"]],[\"Welkom bij het Aqua Composite bestelformulier\",[\"Bienvenue sur le bon de commande Aqua Composite\",\"Willkommen beim Aqua Composite Bestellformular\",\"Welcome to the Aqua Composite order form\"]],[\"Ben je dealer van HB Composite, dan plaats je hier rechtstreeks je bestelling. Alle getoonde prijzen zijn adviesverkoopprijzen voor consumenten, inclusief btw.\",[\"Vous êtes revendeur HB Composite ? Passez ici directement votre commande. Tous les prix affichés sont des prix de vente conseillés pour les consommateurs, TVA comprise.\",\"Sind Sie Händler von HB Composite? Dann geben Sie hier direkt Ihre Bestellung auf. Alle angezeigten Preise sind unverbindliche Verkaufspreise für Verbraucher inklusive MwSt.\",\"Are you an HB Composite dealer? Place your order here directly. All prices shown are recommended retail prices for consumers, including VAT.\"]],[\"Particulier? Maak hier je eigen offerte: je verstuurt een vrijblijvende aanvraag die naar een dealer bij je in de buurt gaat.\",[\"Particulier ? Établissez ici votre propre devis : vous envoyez une demande sans engagement, transmise à un revendeur près de chez vous.\",\"Privatkunde? Erstellen Sie hier Ihr eigenes Angebot: Sie senden eine unverbindliche Anfrage, die an einen Händler in Ihrer Nähe geht.\",\"Private customer? Create your own quote here: you send a no-obligation request that goes to a dealer near you.\"]],[\"Ik ben dealer van HB Composite\",[\"Je suis revendeur HB Composite\",\"Ich bin Händler von HB Composite\",\"I'm an HB Composite dealer\"]],[\"Bestelling plaatsen. Je ontvangt van ons een tegenbevestiging met je inkoopprijzen.\",[\"Passer une commande. Vous recevrez de notre part une confirmation avec vos prix d’achat.\",\"Bestellung aufgeben. Sie erhalten von uns eine Auftragsbestätigung mit Ihren Einkaufspreisen.\",\"Place an order. You'll receive a confirmation from us with your purchase prices.\"]],[\"Maak hier je eigen offerte\",[\"Établissez ici votre propre devis\",\"Erstellen Sie hier Ihr eigenes Angebot\",\"Create your own quote here\"]],[\"Vrijblijvend, met adviesverkoopprijzen incl. btw. Er wordt geen bestelling geplaatst; je aanvraag gaat naar een dealer bij je in de buurt.\",[\"Sans engagement, avec prix de vente conseillés TVA comprise. Aucune commande n'est passée ; votre demande est transmise à un revendeur près de chez vous.\",\"Unverbindlich, mit unverbindlichen Verkaufspreisen inkl. MwSt. Es wird keine Bestellung aufgegeben; Ihre Anfrage geht an einen Händler in Ihrer Nähe.\",\"No obligation, with recommended retail prices incl. VAT. No order is placed; your request goes to a dealer near you.\"]],[\"Je ordernummer of referentie\",[\"Votre numéro de commande ou référence\",\"Ihre Bestellnummer oder Referenz\",\"Your order number or reference\"]],[\"bijv. inkoopordernummer, projectnaam of klantnaam\",[\"p. ex. numéro de bon de commande, nom du projet ou du client\",\"z. B. Bestellnummer, Projektname oder Kundenname\",\"e.g. purchase order number, project name or customer name\"]],[\"Debiteurennummer (optioneel)\",[\"Numéro client (facultatif)\",\"Kundennummer (optional)\",\"Customer number (optional)\"]],[\"staat op je factuur\",[\"figure sur votre facture\",\"steht auf Ihrer Rechnung\",\"shown on your invoice\"]],[\"Deze referentie vermelden wij op de orderbevestiging, de pakbon en de factuur.\",[\"Nous mentionnons cette référence sur la confirmation de commande, le bon de livraison et la facture.\",\"Diese Referenz geben wir auf der Auftragsbestätigung, dem Lieferschein und der Rechnung an.\",\"We will state this reference on the order confirmation, delivery note and invoice.\"]],[\"Ja, neem na mijn aanvraag contact met mij op.\",[\"Oui, contactez-moi après ma demande.\",\"Ja, kontaktieren Sie mich nach meiner Anfrage.\",\"Yes, please contact me after my request.\"]],[\"Kies eerst of je klant bent of de tool wilt proberen.\",[\"Choisissez d'abord si vous êtes client ou si vous souhaitez essayer l'outil.\",\"Wählen Sie zuerst, ob Sie Kunde sind oder das Tool ausprobieren möchten.\",\"First choose whether you're a customer or want to try the tool.\"]],[\"Waarvoor zoek je materiaal?\",[\"Pour quelle application cherchez-vous du matériau ?\",\"Wofür suchen Sie Material?\",\"What do you need material for?\"]],[\"Kies de toepassing. Je kunt meerdere producten in één bestelling combineren.\",[\"Choisissez l'application. Vous pouvez combiner plusieurs produits dans une seule commande.\",\"Wählen Sie die Anwendung. Sie können mehrere Produkte in einer Bestellung kombinieren.\",\"Choose the application. You can combine several products in one order.\"]],[\"Niet te combineren met wat al in je selectie staat.\",[\"Non combinable avec ce qui figure déjà dans votre sélection.\",\"Nicht kombinierbar mit dem, was bereits in Ihrer Auswahl ist.\",\"Can't be combined with what's already in your selection.\"]],[\"Door naar levering →\",[\"Passer à la livraison →\",\"Weiter zur Lieferung →\",\"Continue to delivery →\"]],[\"Kies een serie\",[\"Choisissez une série\",\"Wählen Sie eine Serie\",\"Choose a range\"]],[\"series geschikt voor\",[\"séries adaptées pour\",\"Serien geeignet für\",\"ranges suitable for\"]],[\"· per wandbeeld\",[\"· par visuel mural\",\"· pro Wandmotiv\",\"· per wall visual\"]],[\"Kies je decor\",[\"Choisissez votre décor\",\"Wählen Sie Ihr Dekor\",\"Choose your decor\"]],[\"decors. De afbeelding toont het decor;\",[\"décors. L'image montre le décor ;\",\"Dekore. Das Bild zeigt das Dekor;\",\"decors. The image shows the decor;\"]],[\"vraag bij twijfel een staal aan.\",[\"en cas de doute, demandez un échantillon.\",\"fordern Sie im Zweifel ein Muster an.\",\"if in doubt, request a sample.\"]],[\"Bekijk dit decor in een AI-render\",[\"Voir ce décor dans un rendu IA\",\"Dieses Dekor in einem KI-Rendering ansehen\",\"See this decor in an AI render\"]],[\"Een fotorealistisch voorbeeld van\",[\"Un aperçu photoréaliste de\",\"Eine fotorealistische Vorschau von\",\"A photorealistic preview of\"]],[\", gemaakt door AI. Dit duurt meestal 30 tot 90 seconden.\",[\", réalisé par IA. Cela prend généralement 30 à 90 secondes.\",\", erstellt mit KI. Das dauert meist 30 bis 90 Sekunden.\",\", created by AI. This usually takes 30 to 90 seconds.\"]],[\"AI-impressie: kleur, patroon en maatvoering kunnen afwijken van het echte product. Vraag een staal aan voor de juiste kleur.\",[\"Impression IA : la couleur, le motif et les dimensions peuvent différer du produit réel. Demandez un échantillon pour la couleur exacte.\",\"KI-Eindruck: Farbe, Muster und Maße können vom echten Produkt abweichen. Fordern Sie für die richtige Farbe ein Muster an.\",\"AI impression: colour, pattern and dimensions may differ from the real product. Request a sample for the exact colour.\"]],[\"De AI maakt je render…\",[\"L’IA réalise votre rendu…\",\"Die KI erstellt Ihr Rendering…\",\"The AI is creating your render…\"]],[\"De AI-render is niet gelukt. Probeer het opnieuw.\",[\"Le rendu IA n'a pas abouti. Veuillez réessayer.\",\"Das KI-Rendering ist fehlgeschlagen. Bitte versuchen Sie es erneut.\",\"The AI render didn't work. Please try again.\"]],[\"Nieuwe render maken\",[\"Créer un nouveau rendu\",\"Neues Rendering erstellen\",\"Create a new render\"]],[\"De AI-render is op dit moment niet beschikbaar.\",[\"Le rendu IA n'est pas disponible pour le moment.\",\"Das KI-Rendering ist derzeit nicht verfügbar.\",\"The AI render is not available at the moment.\"]],[\"Hoeveel heb je nodig?\",[\"De quelle quantité avez-vous besoin ?\",\"Wie viel benötigen Sie?\",\"How much do you need?\"]],[\"· wandbeeld van\",[\"· visuel mural de\",\"· Wandmotiv von\",\"· wall visual of\"]],[\"Kleurweergave bij benadering — vraag een staal aan\",[\"Rendu des couleurs approximatif — demandez un échantillon\",\"Farbdarstellung annähernd — fordern Sie ein Muster an\",\"Approximate colour rendering — request a sample\"]],[\"Ingevoerd als aantal pakken\",[\"Saisi en nombre de paquets\",\"Als Anzahl Pakete eingegeben\",\"Entered as number of packs\"]],[\"% snijverlies\",[\"% de chutes\",\"% Verschnitt\",\"% cutting waste\"]],[\"Totaal incl. btw\",[\"Total TVA comprise\",\"Gesamt inkl. MwSt.\",\"Total incl. VAT\"]],[\"Minimale afname:\",[\"Commande minimale :\",\"Mindestabnahme:\",\"Minimum order:\"]],[\"Het wandbeeld wordt als één doorlopende print geleverd; kies de breedte die het dichtst bij je wand ligt.\",[\"Le visuel mural est livré en une seule impression continue ; choisissez la largeur la plus proche de celle de votre mur.\",\"Das Wandmotiv wird als ein durchgehender Druck geliefert; wählen Sie die Breite, die Ihrer Wand am nächsten kommt.\",\"The wall visual is supplied as one continuous print; choose the width closest to your wall.\"]],[\"Vensterbanken worden per stuk geleverd, elk 290 cm lang.\",[\"Les Windowsills sont livrés à la pièce, chacun de 290 cm de long.\",\"Windowsills werden einzeln geliefert, jeweils 290 cm lang.\",\"Windowsills are supplied per piece, each 290 cm long.\"]],[\"Geef aan hoeveel wandbeelden je nodig hebt.\",[\"Indiquez le nombre de visuels muraux dont vous avez besoin.\",\"Geben Sie an, wie viele Wandmotive Sie benötigen.\",\"Enter how many wall visuals you need.\"]],[\"Vul het aantal pakken in, of het netto oppervlak: dan rekenen wij het om naar hele pakken.\",[\"Indiquez le nombre de paquets ou la surface nette : nous la convertissons en paquets entiers.\",\"Geben Sie die Anzahl Pakete oder die Nettofläche ein: Wir rechnen sie in ganze Pakete um.\",\"Enter the number of packs, or the net area: we'll convert it to whole packs.\"]],[\"of totale lengte (cm)\",[\"ou longueur totale (cm)\",\"oder Gesamtlänge (cm)\",\"or total length (cm)\"]],[\"of oppervlak in m²\",[\"ou surface en m²\",\"oder Fläche in m²\",\"or area in m²\"]],[\"of lengte × breedte (m)\",[\"ou longueur × largeur (m)\",\"oder Länge × Breite (m)\",\"or length × width (m)\"]],[\"Snijverlies (bij invoer in m²)\",[\"Chutes (si saisie en m²)\",\"Verschnitt (bei Eingabe in m²)\",\"Cutting waste (when entered in m²)\"]],[\"Voor een visgraatpatroon adviseren wij 10% of meer; bij veel hoeken en nissen 15%.\",[\"Pour un motif en point de Hongrie, nous conseillons 10 % ou plus ; 15 % en cas de nombreux angles et niches.\",\"Für ein Fischgrätmuster empfehlen wir 10 % oder mehr; bei vielen Ecken und Nischen 15 %.\",\"For a herringbone pattern we recommend 10% or more; 15% with many corners and recesses.\"]],[\"5% volstaat bij een rechthoekige ruimte. Kies 10% bij veel hoeken, nissen of diagonaal leggen.\",[\"5 % suffisent pour une pièce rectangulaire. Choisissez 10 % en cas de nombreux angles, de niches ou de pose en diagonale.\",\"5 % genügen bei einem rechteckigen Raum. Wählen Sie 10 % bei vielen Ecken, Nischen oder diagonaler Verlegung.\",\"5% is enough for a rectangular room. Choose 10% for many corners, recesses or diagonal laying.\"]],[\"Zwevend op ondervloer\",[\"Flottant sur sous-couche\",\"Schwimmend auf Unterlage\",\"Floating on underlay\"]],[\"Weet ik nog niet\",[\"Je ne sais pas encore\",\"Weiß ich noch nicht\",\"I don't know yet\"]],[\"Hoe leg je de vloer?\",[\"Comment posez-vous le sol ?\",\"Wie verlegen Sie den Boden?\",\"How will you lay the floor?\"]],[\"Bepaalt welke ondervloer of lijm wij voorstellen.\",[\"Détermine la sous-couche ou la colle que nous proposons.\",\"Bestimmt, welche Unterlage oder welchen Kleber wir vorschlagen.\",\"Determines which underlay or adhesive we suggest.\"]],[\"Verlijmen met kokers\",[\"Collage avec cartouches\",\"Verkleben mit Kartuschen\",\"Glue with cartridges\"]],[\"Volvlaks met lijmkam\",[\"Sur toute la surface avec spatule crantée\",\"Vollflächig mit Zahnspachtel\",\"Full-surface with notched trowel\"]],[\"Schroeven op regelwerk\",[\"Vissage sur lattage\",\"Verschrauben auf Lattung\",\"Screw onto battens\"]],[\"Hoe bevestig je?\",[\"Comment fixez-vous ?\",\"Wie befestigen Sie?\",\"How will you fix it?\"]],[\"Bepaalt hoeveel lijm wij voorstellen.\",[\"Détermine la quantité de colle que nous proposons.\",\"Bestimmt, wie viel Kleber wir vorschlagen.\",\"Determines how much adhesive we suggest.\"]],[\"Wijziging opslaan →\",[\"Enregistrer la modification →\",\"Änderung speichern →\",\"Save change →\"]],[\"Toevoegen en verder →\",[\"Ajouter et continuer →\",\"Hinzufügen und weiter →\",\"Add and continue →\"]],[\"Afwerking en accessoires\",[\"Finition et accessoires\",\"Abschluss und Zubehör\",\"Finishing and accessories\"]],[\"Het volledige accessoire-assortiment. Alles is per stuk te bestellen.\",[\"L'ensemble de la gamme d'accessoires. Tout peut être commandé à la pièce.\",\"Das komplette Zubehörsortiment. Alles ist einzeln bestellbar.\",\"The complete accessory range. Everything can be ordered per piece.\"]],[\"Wat je nodig hebt staat open; de rest kun je uitklappen. Alles is per stuk te bestellen.\",[\"Ce dont vous avez besoin est déjà ouvert ; vous pouvez déplier le reste. Tout peut être commandé à la pièce.\",\"Was Sie benötigen, ist geöffnet; den Rest können Sie aufklappen. Alles ist einzeln bestellbar.\",\"What you need is open; you can expand the rest. Everything can be ordered per piece.\"]],[\"+ Product toevoegen\",[\"+ Ajouter un produit\",\"+ Produkt hinzufügen\",\"+ Add product\"]],[\"SPC is zwaar materiaal. Geef aan hoe en waar je het wilt ontvangen.\",[\"Le SPC est un matériau lourd. Indiquez comment et où vous souhaitez le recevoir.\",\"SPC ist ein schweres Material. Geben Sie an, wie und wo Sie es erhalten möchten.\",\"SPC is a heavy material. Tell us how and where you want to receive it.\"]],[\"Levering via een dealer bij je in de buurt.\",[\"Livraison via un revendeur près de chez vous.\",\"Lieferung über einen Händler in Ihrer Nähe.\",\"Delivery through a dealer near you.\"]],[\"HB Composite levert niet rechtstreeks aan particulieren. Je aanvraag gaat naar een dealer in je regio. De dealer neemt contact met je op over de levering.\",[\"HB Composite ne livre pas directement aux particuliers. Votre demande est transmise à un revendeur de votre région. Le revendeur vous contacte au sujet de la livraison.\",\"HB Composite liefert nicht direkt an Privatkunden. Ihre Anfrage geht an einen Händler in Ihrer Region. Der Händler setzt sich wegen der Lieferung mit Ihnen in Verbindung.\",\"HB Composite does not deliver directly to consumers. Your request goes to a dealer in your region. The dealer will contact you about delivery.\"]],[\"Afhalen gebeurt op afspraak. De afhaallocatie en het tijdstip bevestigen wij in onze orderbevestiging.\",[\"L'enlèvement se fait sur rendez-vous. Nous confirmons le lieu et l'heure d'enlèvement dans notre confirmation de commande.\",\"Die Abholung erfolgt nach Vereinbarung. Abholort und Uhrzeit bestätigen wir in unserer Auftragsbestätigung.\",\"Collection is by appointment. We confirm the collection address and time in our order confirmation.\"]],[\"— de gevelpanelen zijn 6 meter lang.\",[\"— les panneaux de façade mesurent 6 mètres de long.\",\"— die Fassadenpaneele sind 6 Meter lang.\",\"— the cladding panels are 6 metres long.\"]],[\"Een gevelorder kan niet samen met SPC-producten worden bezorgd.\",[\"Une commande de façade ne peut pas être livrée avec des produits SPC.\",\"Ein Fassadenauftrag kann nicht zusammen mit SPC-Produkten geliefert werden.\",\"A cladding order cannot be delivered together with SPC products.\"]],[\"Het losadres is gelijk aan ons bedrijfsadres (vul je in bij de volgende stap).\",[\"L'adresse de déchargement est identique à notre adresse d'entreprise (à remplir à l'étape suivante).\",\"Die Abladeadresse entspricht unserer Firmenadresse (im nächsten Schritt auszufüllen).\",\"The unloading address is the same as our company address (fill it in at the next step).\"]],[\"Straat en huisnummer *\",[\"Rue et numéro *\",\"Straße und Hausnummer *\",\"Street and number *\"]],[\"Contactpersoon op het losadres\",[\"Personne de contact à l'adresse de déchargement\",\"Ansprechpartner an der Abladeadresse\",\"Contact person at the unloading address\"]],[\"Is er een heftruck aanwezig om te lossen?\",[\"Un chariot élévateur est-il disponible pour décharger ?\",\"Ist ein Gabelstapler zum Abladen vorhanden?\",\"Is a forklift available for unloading?\"]],[\"Vul het losadres in, of vink aan dat het gelijk is aan je bedrijfsadres.\",[\"Indiquez l'adresse de déchargement ou cochez qu'elle est identique à votre adresse d'entreprise.\",\"Geben Sie die Abladeadresse ein oder kreuzen Sie an, dass sie Ihrer Firmenadresse entspricht.\",\"Enter the unloading address, or tick that it is the same as your company address.\"]],[\"Naar het overzicht →\",[\"Vers le récapitulatif →\",\"Zur Übersicht →\",\"To the summary →\"]],[\"Adviesprijs incl. btw\",[\"Prix conseillé TVA comprise\",\"Preisempfehlung inkl. MwSt.\",\"Recommended price incl. VAT\"]],[\"aantal wijzigen\",[\"modifier la quantité\",\"Anzahl ändern\",\"change quantity\"]],[\"Btw-nummer (optioneel)\",[\"Numéro de TVA (facultatif)\",\"USt-IdNr. (optional)\",\"VAT number (optional)\"]],[\"Straat en huisnummer\",[\"Rue et numéro\",\"Straße und Hausnummer\",\"Street and number\"]],[\"Controleer de samenstelling en vul je gegevens in. Dit is een vrijblijvende aanvraag; er wordt geen bestelling geplaatst.\",[\"Vérifiez la composition et complétez vos coordonnées. Il s'agit d'une demande sans engagement ; aucune commande n'est passée.\",\"Prüfen Sie die Zusammenstellung und geben Sie Ihre Daten ein. Dies ist eine unverbindliche Anfrage; es wird keine Bestellung aufgegeben.\",\"Check the selection and fill in your details. This is a no-obligation request; no order is placed.\"]],[\"Controleer de samenstelling en vul je gegevens in. Na het plaatsen sturen wij je zo spoedig mogelijk een tegenbevestiging met de inkoopprijzen.\",[\"Vérifiez la composition et complétez vos coordonnées. Après validation, nous vous envoyons dans les meilleurs délais une confirmation avec les prix d'achat.\",\"Prüfen Sie die Zusammenstellung und geben Sie Ihre Daten ein. Nach dem Absenden senden wir Ihnen schnellstmöglich eine Auftragsbestätigung mit den Einkaufspreisen.\",\"Check the selection and fill in your details. Once placed, we'll send you a confirmation with purchase prices as soon as possible.\"]],[\"Je selectie is nog leeg. Voeg eerst een product of accessoire toe.\",[\"Votre sélection est encore vide. Ajoutez d'abord un produit ou un accessoire.\",\"Ihre Auswahl ist noch leer. Fügen Sie zuerst ein Produkt oder Zubehör hinzu.\",\"Your selection is still empty. Add a product or accessory first.\"]],[\"Je referentie:\",[\"Votre référence :\",\"Ihre Referenz:\",\"Your reference:\"]],[\"Debiteurennummer:\",[\"Numéro client :\",\"Kundennummer:\",\"Customer number:\"]],[\"referentie wijzigen\",[\"modifier la référence\",\"Referenz ändern\",\"change reference\"]],[\"Opmerking of vraag\",[\"Remarque ou question\",\"Anmerkung oder Frage\",\"Comment or question\"]],[\"bijv. gewenste leverweek, afleverinstructie, staalaanvraag\",[\"p. ex. semaine de livraison souhaitée, instructions de livraison, demande d'échantillon\",\"z. B. gewünschte Lieferwoche, Lieferhinweise, Musteranfrage\",\"e.g. preferred delivery week, delivery instructions, sample request\"]],[\"De getoonde prijzen zijn adviesverkoopprijzen voor consumenten (incl. btw); je inkoopprijzen volgens de geldende prijsafspraken staan op onze tegenbevestiging.\",[\"Les prix affichés sont des prix de vente conseillés pour les consommateurs (TVA comprise) ; vos prix d'achat selon les accords tarifaires en vigueur figurent sur notre confirmation de commande.\",\"Die angezeigten Preise sind unverbindliche Verkaufspreise für Verbraucher (inkl. MwSt.); Ihre Einkaufspreise gemäß den geltenden Preisvereinbarungen stehen auf unserer Auftragsbestätigung.\",\"The prices shown are recommended retail prices for consumers (incl. VAT); your purchase prices according to the applicable price agreements are shown on our order confirmation.\"]],[\"Ik ga akkoord met de\",[\"J'accepte les\",\"Ich stimme den\",\"I agree to the\"]],[\"en plaats een bestelling.\",[\"et je passe commande.\",\"zu und gebe eine Bestellung auf.\",\"and place an order.\"]],[\"Mijn gegevens en losadres onthouden op dit apparaat voor een volgende bestelling.\",[\"Mémoriser mes coordonnées et l’adresse de déchargement sur cet appareil pour une prochaine commande.\",\"Meine Daten und Abladeadresse auf diesem Gerät für eine nächste Bestellung speichern.\",\"Remember my details and unloading address on this device for a next order.\"]],[\"Je gegevens zijn ingevuld vanuit een eerdere bestelling op dit apparaat.\",[\"Vos coordonnées ont été reprises d'une commande précédente sur cet appareil.\",\"Ihre Daten wurden aus einer früheren Bestellung auf diesem Gerät übernommen.\",\"Your details were filled in from a previous order on this device.\"]],[\"Opgeslagen gegevens wissen\",[\"Effacer les données enregistrées\",\"Gespeicherte Daten löschen\",\"Clear saved details\"]],[\"Je selectie is nog leeg.\",[\"Votre sélection est encore vide.\",\"Ihre Auswahl ist noch leer.\",\"Your selection is still empty.\"]],[\"Vul bedrijf, naam en e-mailadres in.\",[\"Indiquez l'entreprise, le nom et l'adresse e-mail.\",\"Geben Sie Firma, Name und E-Mail-Adresse ein.\",\"Enter company, name and email address.\"]],[\"Vul het bedrijfsadres in.\",[\"Indiquez l'adresse de l'entreprise.\",\"Geben Sie die Firmenadresse ein.\",\"Enter the company address.\"]],[\"Vink aan dat je akkoord gaat met de voorwaarden.\",[\"Cochez que vous acceptez les conditions.\",\"Kreuzen Sie an, dass Sie den Bedingungen zustimmen.\",\"Tick to confirm you agree to the terms.\"]],[\"n.v.t. (afhalen)\",[\"n/a (enlèvement)\",\"entfällt (Abholung)\",\"n/a (collection)\"]],[\"gelijk aan bedrijfsadres\",[\"identique à l'adresse de l'entreprise\",\"wie Firmenadresse\",\"same as company address\"]],[\"Bezig met versturen…\",[\"Envoi en cours…\",\"Wird gesendet…\",\"Sending…\"]],[\"Het formulier kon niet worden verstuurd. Controleer de gemarkeerde velden hieronder.\",[\"Le formulaire n'a pas pu être envoyé. Vérifiez les champs signalés ci-dessous.\",\"Das Formular konnte nicht gesendet werden. Prüfen Sie die markierten Felder unten.\",\"The form could not be sent. Please check the highlighted fields below.\"]],[\"Je bestelling wordt verstuurd, dit duurt iets langer dan normaal.\",[\"Votre commande est en cours d'envoi, cela prend un peu plus de temps que d'habitude.\",\"Ihre Bestellung wird gesendet, das dauert etwas länger als üblich.\",\"Your order is being sent; this is taking a little longer than usual.\"]],[\"Sluit deze pagina niet.\",[\"Ne fermez pas cette page.\",\"Schließen Sie diese Seite nicht.\",\"Don't close this page.\"]],[\"Het versturen is niet bevestigd. Controleer je internetverbinding en probeer het opnieuw,\",[\"L'envoi n'a pas été confirmé. Vérifiez votre connexion internet et réessayez,\",\"Der Versand wurde nicht bestätigt. Prüfen Sie Ihre Internetverbindung und versuchen Sie es erneut,\",\"Sending was not confirmed. Check your internet connection and try again,\"]],[\"of mail je bestelling naar\",[\"ou envoyez votre commande par e-mail à\",\"oder senden Sie Ihre Bestellung per E-Mail an\",\"or email your order to\"]],[\"Je btw-nummer\",[\"Votre numéro de TVA\",\"Ihre USt-IdNr.\",\"Your VAT number\"]],[\"Hartelijk dank voor je interesse in Aqua Composite. Hieronder vind je een overzicht van je aanvraag. Dit is een vrijblijvende aanvraag; er is geen bestelling geplaatst.\",[\"Merci pour votre intérêt pour Aqua Composite. Vous trouverez ci-dessous un récapitulatif de votre demande. Il s'agit d'une demande sans engagement ; aucune commande n'a été passée.\",\"Vielen Dank für Ihr Interesse an Aqua Composite. Nachfolgend finden Sie eine Übersicht Ihrer Anfrage. Dies ist eine unverbindliche Anfrage; es wurde keine Bestellung aufgegeben.\",\"Thank you for your interest in Aqua Composite. Below is an overview of your request. This is a no-obligation request; no order has been placed.\"]],[\"Wij nemen zo spoedig mogelijk contact met je op om je aanvraag te bespreken.\",[\"Nous vous contacterons dans les meilleurs délais pour discuter de votre demande.\",\"Wir melden uns schnellstmöglich bei Ihnen, um Ihre Anfrage zu besprechen.\",\"We'll contact you as soon as possible to discuss your request.\"]],[\"Je hebt aangegeven niet benaderd te willen worden. Heb je toch vragen, dan horen wij graag van je.\",[\"Vous avez indiqué ne pas souhaiter être contacté. Si vous avez tout de même des questions, n'hésitez pas à nous écrire.\",\"Sie haben angegeben, nicht kontaktiert werden zu wollen. Haben Sie dennoch Fragen, hören wir gerne von Ihnen.\",\"You indicated you don't want to be contacted. If you do have questions, we'd be happy to hear from you.\"]],[\"Hartelijk dank voor je bestelling. Hieronder vind je een overzicht van de door jou bestelde artikelen.\",[\"Merci pour votre commande. Vous trouverez ci-dessous un récapitulatif des articles commandés.\",\"Vielen Dank für Ihre Bestellung. Nachfolgend finden Sie eine Übersicht der von Ihnen bestellten Artikel.\",\"Thank you for your order. Below is an overview of the items you ordered.\"]],[\"Wij sturen je zo spoedig mogelijk een tegenbevestiging met de inkoopprijzen en de verwachte leverdatum.\",[\"Nous vous enverrons dans les meilleurs délais une confirmation avec les prix d'achat et la date de livraison prévue.\",\"Wir senden Ihnen schnellstmöglich eine Auftragsbestätigung mit den Einkaufspreisen und dem voraussichtlichen Liefertermin.\",\"We'll send you a confirmation with purchase prices and the expected delivery date as soon as possible.\"]],[\"· adviesprijs\",[\"· prix conseillé\",\"· Preisempfehlung\",\"· recommended price\"]],[\"Bevestiging van je aanvraag\",[\"Confirmation de votre demande\",\"Bestätigung Ihrer Anfrage\",\"Confirmation of your request\"]],[\"Geachte heer, mevrouw\",[\"Madame, Monsieur,\",\"Sehr geehrte Damen und Herren,\",\"Dear Sir or Madam,\"]],[\"Bestelbevestiging-\",[\"Confirmation-de-commande-\",\"Bestellbestaetigung-\",\"Order-confirmation-\"]],[\"Met vriendelijke groet,\",[\"Cordialement,\",\"Mit freundlichen Grüßen\",\"Kind regards,\"]],[\"PDF wordt gemaakt…\",[\"Création du PDF…\",\"PDF wird erstellt…\",\"Creating PDF…\"]],[\"is verstuurd. Hieronder zie je de bevestiging; je kunt die als PDF downloaden of afdrukken.\",[\"a été envoyée. Vous trouverez ci-dessous la confirmation ; vous pouvez la télécharger en PDF ou l'imprimer.\",\"wurde gesendet. Unten sehen Sie die Bestätigung; Sie können sie als PDF herunterladen oder drucken.\",\"has been sent. You can see the confirmation below; you can download it as a PDF or print it.\"]],[\"is verstuurd. Je ontvangt ook een bevestiging per e-mail. Hieronder zie je de bestelbevestiging; je kunt die als PDF downloaden of afdrukken.\",[\"a été envoyée. Vous recevrez également une confirmation par e-mail. Vous trouverez ci-dessous la confirmation de commande ; vous pouvez la télécharger en PDF ou l'imprimer.\",\"wurde gesendet. Sie erhalten auch eine Bestätigung per E-Mail. Unten sehen Sie die Auftragsbestätigung; Sie können sie als PDF herunterladen oder drucken.\",\"has been sent. You'll also receive a confirmation by email. You can see the order confirmation below; you can download it as a PDF or print it.\"]],[\"Hartelijk dank voor je interesse in Aqua Composite. Hieronder vind je een overzicht van je aanvraag.\",[\"Merci pour votre intérêt pour Aqua Composite. Vous trouverez ci-dessous un récapitulatif de votre demande.\",\"Vielen Dank für Ihr Interesse an Aqua Composite. Nachfolgend finden Sie eine Übersicht Ihrer Anfrage.\",\"Thank you for your interest in Aqua Composite. Below is an overview of your request.\"]],[\"Dit is een vrijblijvende aanvraag; er is geen bestelling geplaatst.\",[\"Il s'agit d'une demande sans engagement ; aucune commande n'a été passée.\",\"Dies ist eine unverbindliche Anfrage; es wurde keine Bestellung aufgegeben.\",\"This is a no-obligation request; no order has been placed.\"]],[\"Wij sturen je\",[\"Nous vous enverrons\",\"Wir senden Ihnen\",\"We'll send you\"]],[\"zo spoedig mogelijk een tegenbevestiging met de inkoopprijzen\",[\"dans les meilleurs délais une confirmation avec les prix d'achat\",\"schnellstmöglich eine Auftragsbestätigung mit den Einkaufspreisen\",\"a confirmation with purchase prices as soon as possible\"]],[\"en de verwachte leverdatum.\",[\"et la date de livraison prévue.\",\"und dem voraussichtlichen Liefertermin.\",\"and the expected delivery date.\"]],[\"PDF downloaden\",[\"Télécharger le PDF\",\"PDF herunterladen\",\"Download PDF\"]],[\"[...] Volledige specificatie op aanvraag.\",[\"[...] Spécification complète sur demande.\",\"[...] Vollständige Spezifikation auf Anfrage.\",\"[...] Full specification on request.\"]],[\"Je e-mailprogramma wordt geopend.\",[\"Votre programme de messagerie va s’ouvrir.\",\"Ihr E-Mail-Programm wird geöffnet.\",\"Your email program will open.\"]],[\"Gebeurt dat niet?\",[\"Rien ne se passe ?\",\"Passiert das nicht?\",\"Didn't happen?\"]],[\"Kopieer dan de onderstaande tekst en mail die naar\",[\"Copiez alors le texte ci-dessous et envoyez-le par e-mail à\",\"Kopieren Sie dann den folgenden Text und senden Sie ihn per E-Mail an\",\"Then copy the text below and email it to\"]],[\"nog niets gekozen\",[\"rien de choisi pour le moment\",\"noch nichts ausgewählt\",\"nothing chosen yet\"]],[\"adviestotaal incl. btw · levering:\",[\"total conseillé TVA comprise · livraison :\",\"Empfehlungssumme inkl. MwSt. · Lieferung:\",\"recommended total incl. VAT · delivery:\"]],[\"Doorloop de stappen links. Zodra je een product toevoegt,\",[\"Suivez les étapes à gauche. Dès que vous ajoutez un produit,\",\"Gehen Sie die Schritte links durch. Sobald Sie ein Produkt hinzufügen,\",\"Work through the steps on the left. As soon as you add a product,\"]],[\"zie je hier het aantal pakken meelopen.\",[\"vous voyez ici le nombre de paquets évoluer.\",\"sehen Sie hier die Anzahl der Pakete mitlaufen.\",\"you'll see the number of packs update here.\"]],[\"2 delig hoekprofiel folie\",[\"Profilé d'angle 2 parties film\",\"2-teiliges Eckprofil Folie\",\"2-part corner profile foil\"]],[\"2 delig verbindingsprofiel folie\",[\"Profilé de jonction 2 parties film\",\"2-teiliges Verbindungsprofil Folie\",\"2-part connecting profile foil\"]],[\"2 delig startprofiel folie\",[\"Profilé de départ 2 parties film\",\"2-teiliges Startprofil Folie\",\"2-part starter profile foil\"]],[\"Startprofiel Gevelpaneel\",[\"Profilé de départ panneau de façade\",\"Startprofil Fassadenpaneel\",\"Starter profile cladding panel\"]],[\"gevelbekleding en de Aqua Composite SPC-producten kunnen niet in één levering worden gecombineerd. Rond deze bestelling af en plaats voor het andere een aparte bestelling.\",[\"(bardage) et les produits SPC Aqua Composite ne peuvent pas être combinés dans une même livraison. Finalisez cette commande et passez une commande séparée pour le reste.\",\"(Fassadenverkleidung) und die Aqua Composite SPC-Produkte können nicht in einer Lieferung kombiniert werden. Schließen Sie diese Bestellung ab und geben Sie für das andere eine separate Bestellung auf.\",\"cladding and Aqua Composite SPC products cannot be combined in one delivery. Complete this order and place a separate order for the rest.\"]],[\"Aqua Ruimteplanner\",[\"Aqua Planificateur de pièce\",\"Aqua Raumplaner\",\"Aqua Room Planner\"]],[\"Teken je ruimte, van badkamer en toilet tot keuken of woonkamer. Kies per wand een product en decor, bekijk het resultaat in 3D en zie precies wat je nodig hebt.\",[\"Dessinez votre pièce, de la salle de bains et des toilettes à la cuisine ou au séjour. Choisissez un produit et un décor pour chaque mur, visualisez le résultat en 3D et voyez exactement ce dont vous avez besoin.\",\"Zeichnen Sie Ihren Raum, vom Bad und WC bis zur Küche oder zum Wohnzimmer. Wählen Sie für jede Wand ein Produkt und Dekor, sehen Sie das Ergebnis in 3D und genau, was Sie benötigen.\",\"Draw your room, from bathroom and toilet to kitchen or living room. Choose a product and decor for each wall, view the result in 3D and see exactly what you need.\"]],[\"360° rondkijken\",[\"Vue à 360°\",\"360°-Rundblick\",\"Look around 360°\"]],[\"Gekozen wand\",[\"Mur sélectionné\",\"Gewählte Wand\",\"Selected wall\"]],[\"AI-render van dit standpunt\",[\"Rendu IA de ce point de vue\",\"KI-Rendering aus diesem Blickwinkel\",\"AI render from this viewpoint\"]],[\"Sleep om te draaien · knijp of scroll om te zoomen · tik op een wand\",[\"Faites glisser pour tourner · pincez ou faites défiler pour zoomer · touchez un mur\",\"Ziehen zum Drehen · zoomen mit zwei Fingern oder Scrollen · auf eine Wand tippen\",\"Drag to rotate · pinch or scroll to zoom · tap a wall\"]],[\"Vorm en maten\",[\"Forme et dimensions\",\"Form und Maße\",\"Shape and dimensions\"]],[\"Soort ruimte\",[\"Type de pièce\",\"Raumtyp\",\"Room type\"]],[\"Vorm van de ruimte\",[\"Forme de la pièce\",\"Raumform\",\"Room shape\"]],[\"⇋ Spiegelen (links ↔ rechts)\",[\"⇋ Inverser (gauche ↔ droite)\",\"⇋ Spiegeln (links ↔ rechts)\",\"⇋ Mirror (left ↔ right)\"]],[\"Plafondhoogte (cm)\",[\"Hauteur sous plafond (cm)\",\"Deckenhöhe (cm)\",\"Ceiling height (cm)\"]],[\"Verder: wanden inrichten\",[\"Suivant : aménager les murs\",\"Weiter: Wände gestalten\",\"Next: set up the walls\"]],[\"Per wand inrichten\",[\"Aménager mur par mur\",\"Wand für Wand gestalten\",\"Set up wall by wall\"]],[\"Deze wand bekleden\",[\"Revêtir ce mur\",\"Diese Wand verkleiden\",\"Clad this wall\"]],[\"Dit product en decor op alle wanden\",[\"Ce produit et ce décor sur tous les murs\",\"Dieses Produkt und Dekor auf allen Wänden\",\"This product and decor on all walls\"]],[\"Hoogte bekleding\",[\"Hauteur du revêtement\",\"Höhe der Verkleidung\",\"Cladding height\"]],[\"Halfhoog 120 cm\",[\"Mi-hauteur 120 cm\",\"Halbhoch 120 cm\",\"Half height 120 cm\"]],[\"Eigen hoogte\",[\"Hauteur personnalisée\",\"Eigene Höhe\",\"Custom height\"]],[\"Deuren, ramen en nissen in deze wand\",[\"Portes, fenêtres et niches dans ce mur\",\"Türen, Fenster und Nischen in dieser Wand\",\"Doors, windows and recesses in this wall\"]],[\"+ Nis / uitsparing\",[\"+ Niche / découpe\",\"+ Nische / Aussparung\",\"+ Recess / cut-out\"]],[\"Wandaanzicht en indeling\",[\"Vue du mur et disposition\",\"Wandansicht und Aufteilung\",\"Wall view and layout\"]],[\"‹ Vorige wand\",[\"‹ Mur précédent\",\"‹ Vorherige Wand\",\"‹ Previous wall\"]],[\"Volgende wand ›\",[\"Mur suivant ›\",\"Nächste Wand ›\",\"Next wall ›\"]],[\"De vloer bekleden\",[\"Revêtir le sol\",\"Den Boden belegen\",\"Cover the floor\"]],[\"Volledig verlijmd\",[\"Entièrement collé\",\"Vollflächig verklebt\",\"Fully glued\"]],[\"€ 6,72 / m², hoogste druksterkte\",[\"€ 6,72 / m², résistance à la compression la plus élevée\",\"€ 6,72 / m², höchste Druckfestigkeit\",\"€ 6.72 / m², highest compressive strength\"]],[\"Verder: inrichting\",[\"Suivant : aménagement\",\"Weiter: Einrichtung\",\"Next: furnishing\"]],[\"Werk wand per wand: kies een wand, tik daarna op een object in de bibliotheek. Het komt op die wand. Schuif het in het wandaanzicht naar de juiste plek, of typ de afstanden in.\",[\"Travaillez mur par mur : choisissez un mur, puis touchez un objet dans la bibliothèque. Il est placé sur ce mur. Faites-le glisser au bon endroit dans la vue du mur, ou saisissez les distances.\",\"Arbeiten Sie Wand für Wand: Wählen Sie eine Wand und tippen Sie dann auf ein Objekt in der Bibliothek. Es wird an dieser Wand platziert. Verschieben Sie es in der Wandansicht an die richtige Stelle oder geben Sie die Abstände ein.\",\"Work wall by wall: choose a wall, then tap an object in the library. It goes on that wall. Slide it into place in the wall view, or type in the distances.\"]],[\"In de ruimte\",[\"Dans la pièce\",\"Im Raum\",\"In the room\"]],[\"De inrichting is alleen ter illustratie en komt niet op de bestellijst.\",[\"L'aménagement est uniquement illustratif et ne figure pas sur la liste de commande.\",\"Die Einrichtung dient nur der Veranschaulichung und erscheint nicht auf der Bestellliste.\",\"The furnishing is for illustration only and doesn't appear on the order list.\"]],[\"Bekijk overzicht\",[\"Voir le récapitulatif\",\"Übersicht ansehen\",\"View summary\"]],[\"Overzicht en bestellijst\",[\"Récapitulatif et liste de commande\",\"Übersicht und Bestellliste\",\"Summary and order list\"]],[\"Fotorealistische impressie\",[\"Impression photoréaliste\",\"Fotorealistischer Eindruck\",\"Photorealistic impression\"]],[\"De AI maakt een foto-echte impressie van je ontwerp, met de echte decors.\",[\"L'IA crée une impression photoréaliste de votre projet, avec les vrais décors.\",\"Die KI erstellt einen fotorealistischen Eindruck Ihres Entwurfs mit den echten Dekoren.\",\"The AI creates a photo-real impression of your design, with the real decors.\"]],[\"Stel eerst het beeld voor de render in.\",[\"Réglez d'abord la vue pour le rendu.\",\"Stellen Sie zuerst die Ansicht für das Rendering ein.\",\"First set up the view for the render.\"]],[\"Klik boven de 3D-weergave op\",[\"Au-dessus de la vue 3D, cliquez sur\",\"Klicken Sie über der 3D-Ansicht auf\",\"Above the 3D view, click\"]],[\", draai en zoom naar het beeld dat je wilt en klik dan op\",[\", tournez et zoomez jusqu’à la vue souhaitée, puis cliquez sur\",\", drehen und zoomen Sie zur gewünschten Ansicht und klicken Sie dann auf\",\", rotate and zoom to the view you want, then click\"]],[\"Naar 360° rondkijken\",[\"Vers la vue à 360°\",\"Zum 360°-Rundblick\",\"Go to 360° view\"]],[\"Je hebt nog geen standpunt gekozen in\",[\"Vous n'avez pas encore choisi de point de vue dans\",\"Sie haben noch keinen Blickwinkel gewählt in\",\"You haven't chosen a viewpoint yet in\"]],[\". Dan maakt de planner een foto vanuit Overzicht, met zicht op de wand waar je in de 3D-weergave naar kijkt.\",[\". Le planificateur prend alors une photo depuis Vue d’ensemble, face au mur que vous regardez dans la vue 3D.\",\". Dann erstellt der Planer ein Bild aus der Übersicht, mit Blick auf die Wand, die Sie in der 3D-Ansicht betrachten.\",\". The planner will then take a picture from Overview, facing the wall you are looking at in the 3D view.\"]],[\"Toch renderen vanuit Overzicht\",[\"Rendre quand même depuis la vue d’ensemble\",\"Trotzdem aus der Übersicht rendern\",\"Render from Overview anyway\"]],[\"Maak AI-render\",[\"Créer un rendu IA\",\"KI-Rendering erstellen\",\"Create AI render\"]],[\"Render opslaan\",[\"Enregistrer le rendu\",\"Rendering speichern\",\"Save render\"]],[\"Een AI-render is een impressie; kleine afwijkingen in vormen en details zijn mogelijk.\",[\"Un rendu IA est une impression ; de légers écarts dans les formes et les détails sont possibles.\",\"Ein KI-Rendering ist ein Eindruck; kleine Abweichungen bei Formen und Details sind möglich.\",\"An AI render is an impression; small deviations in shapes and details are possible.\"]],[\"Panelen bevestigen\",[\"Fixation des panneaux\",\"Paneele befestigen\",\"Fixing the panels\"]],[\"Polymeerlijm\",[\"Colle polymère\",\"Polymerkleber\",\"Polymer adhesive\"]],[\"Afwerkprofielen\",[\"Profilés de finition\",\"Abschlussprofile\",\"Trim profiles\"]],[\"Zelf afhalen op afspraak\",[\"Enlèvement sur rendez-vous\",\"Selbstabholung nach Vereinbarung\",\"Collection by appointment\"]],[\"Offerte aanvragen\",[\"Demander un devis\",\"Angebot anfordern\",\"Request a quote\"]],[\"Download pdf\",[\"Télécharger le PDF\",\"PDF herunterladen\",\"Download PDF\"]],[\"Kopieer overzicht\",[\"Copier le récapitulatif\",\"Übersicht kopieren\",\"Copy summary\"]],[\"We sturen je ontwerp als pdf mee met je aanvraag en nemen zo snel mogelijk contact met je op.\",[\"Nous joignons votre projet en PDF à votre demande et vous contactons dans les meilleurs délais.\",\"Wir senden Ihren Entwurf als PDF mit Ihrer Anfrage mit und melden uns schnellstmöglich bei Ihnen.\",\"We'll attach your design as a PDF to your request and get in touch as soon as possible.\"]],[\"Postcode en plaats\",[\"Code postal et localité\",\"Postleitzahl und Ort\",\"Postcode and town\"]],[\"We gebruiken je gegevens alleen om je aanvraag te beantwoorden.\",[\"Nous utilisons vos données uniquement pour répondre à votre demande.\",\"Wir verwenden Ihre Daten nur zur Beantwortung Ihrer Anfrage.\",\"We only use your details to respond to your request.\"]],[\"De 3D-weergave is een impressie; kleur en structuur van het echte product kunnen afwijken door scherm en belichting. Vraag gerust een staal aan via\",[\"La vue 3D est une impression ; la couleur et la structure du produit réel peuvent différer en raison de l'écran et de l'éclairage. N'hésitez pas à demander un échantillon via\",\"Die 3D-Ansicht ist ein Eindruck; Farbe und Struktur des echten Produkts können durch Bildschirm und Beleuchtung abweichen. Fordern Sie gerne ein Muster an über\",\"The 3D view is an impression; the colour and texture of the real product may differ due to screen and lighting. Feel free to request a sample via\"]],[\"Plattegrond, tik op een wand om hem te kiezen\",[\"Plan, touchez un mur pour le sélectionner\",\"Grundriss, tippen Sie auf eine Wand, um sie zu wählen\",\"Floor plan, tap a wall to select it\"]],[\"Camera hoger\",[\"Caméra plus haut\",\"Kamera höher\",\"Camera up\"]],[\"Camera lager\",[\"Caméra plus bas\",\"Kamera tiefer\",\"Camera down\"]],[\"Samenstellen\",[\"Composer\",\"Zusammenstellen\",\"Compose\"]],[\"Hoogte bekleding in cm\",[\"Hauteur du revêtement en cm\",\"Höhe der Verkleidung in cm\",\"Cladding height in cm\"]],[\"Wandaanzicht\",[\"Vue du mur\",\"Wandansicht\",\"Wall view\"]],[\"Fotorealistische AI-render van de ruimte\",[\"Rendu IA photoréaliste de la pièce\",\"Fotorealistisches KI-Rendering des Raums\",\"Photorealistic AI render of the room\"]],[\"Momentopname van de 3D-weergave\",[\"Instantané de la vue 3D\",\"Momentaufnahme der 3D-Ansicht\",\"Snapshot of the 3D view\"]],[\"Overzicht als tekst\",[\"Récapitulatif en texte\",\"Übersicht als Text\",\"Summary as text\"]],[\"Ruimteplanner AI-render\",[\"Rendu IA du planificateur de pièce\",\"Raumplaner KI-Rendering\",\"Room planner AI render\"]],[\"· prijs op aanvraag na controle van de afbeelding\",[\"· prix sur demande après vérification de l'image\",\"· Preis auf Anfrage nach Prüfung des Bildes\",\"· price on request after checking the image\"]],[\"Hybride polymeerlijm, koker 290 ml\",[\"Colle polymère hybride, cartouche 290 ml\",\"Hybrid-Polymerkleber, Kartusche 290 ml\",\"Hybrid polymer adhesive, 290 ml cartridge\"]],[\"ca. 1 koker per 2 m² paneel\",[\"env. 1 cartouche par 2 m² de panneau\",\"ca. 1 Kartusche pro 2 m² Paneel\",\"approx. 1 cartridge per 2 m² of panel\"]],[\"Schroeven voor Click \u0026 Screw\",[\"Vis pour Click \u0026 Screw\",\"Schrauben für Click \u0026 Screw\",\"Screws for Click \u0026 Screw\"]],[\"aantal en prijs bevestigen we in de offerte\",[\"nous confirmons la quantité et le prix dans le devis\",\"Menge und Preis bestätigen wir im Angebot\",\"we'll confirm quantity and price in the quote\"]],[\"Hybride polymeerlijm, emmer 5 kg (wand)\",[\"Colle polymère hybride, seau de 5 kg (mur)\",\"Hybrid-Polymerkleber, Eimer 5 kg (Wand)\",\"Hybrid polymer adhesive, 5 kg bucket (wall)\"]],[\"tegels op de wand volvlaks verlijmen met lijmkam\",[\"coller les carreaux au mur sur toute la surface avec une spatule crantée\",\"Fliesen an der Wand vollflächig mit Zahnspachtel verkleben\",\"glue tiles to the wall full-surface with a notched trowel\"]],[\"hoeken tussen twee beklede wanden\",[\"angles entre deux murs revêtus\",\"Ecken zwischen zwei verkleideten Wänden\",\"corners between two clad walls\"]],[\"koven, nissen en uitspringende hoeken\",[\"coffrages, niches et angles saillants\",\"Kofferungen, Nischen und vorspringende Ecken\",\"boxings, recesses and protruding corners\"]],[\"open randen, bovenrand en rond deuren en ramen\",[\"bords ouverts, bord supérieur et autour des portes et fenêtres\",\"offene Kanten, Oberkante und um Türen und Fenster\",\"open edges, top edge and around doors and windows\"]],[\"Hybride polymeerlijm, emmer 5 kg (vloer)\",[\"Colle polymère hybride, seau de 5 kg (sol)\",\"Hybrid-Polymerkleber, Eimer 5 kg (Boden)\",\"Hybrid polymer adhesive, 5 kg bucket (floor)\"]],[\"vloer volvlaks verlijmen met lijmkam\",[\"coller le sol sur toute la surface avec une spatule crantée\",\"Boden vollflächig mit Zahnspachtel verkleben\",\"glue the floor full-surface with a notched trowel\"]],[\"Grigio Imperiale\",[\"Grigio Imperiale\",\"Grigio Imperiale\",\"Grigio Imperiale\"]],[\"0,3 mm, klasse 32\",[\"0,3 mm, classe 32\",\"0,3 mm, Klasse 32\",\"0.3 mm, class 32\"]],[\"0,5 mm, klasse 33\",[\"0,5 mm, classe 33\",\"0,5 mm, Klasse 33\",\"0.5 mm, class 33\"]],[\"Breedte uitsparing\",[\"Largeur de la découpe\",\"Breite Aussparung\",\"Cut-out width\"]],[\"Diepte uitsparing\",[\"Profondeur de la découpe\",\"Tiefe Aussparung\",\"Cut-out depth\"]],[\"De muur tot de uitsparing moet minstens 20 cm zijn.\",[\"Le mur jusqu'à la découpe doit mesurer au moins 20 cm.\",\"Die Wand bis zur Aussparung muss mindestens 20 cm lang sein.\",\"The wall up to the cut-out must be at least 20 cm.\"]],[\"De uitsparing moet minstens 20 cm breed zijn: maak de breedte uitsparing kleiner dan de totale breedte.\",[\"La découpe doit avoir au moins 20 cm de large : réduisez la largeur de la découpe par rapport à la largeur totale.\",\"Die Aussparung muss mindestens 20 cm breit sein: Machen Sie die Breite der Aussparung kleiner als die Gesamtbreite.\",\"The cut-out must be at least 20 cm wide: make the cut-out width smaller than the total width.\"]],[\"De uitsparing moet minstens 20 cm minder diep zijn dan de ruimte.\",[\"La découpe doit être au moins 20 cm moins profonde que la pièce.\",\"Die Aussparung muss mindestens 20 cm weniger tief sein als der Raum.\",\"The cut-out must be at least 20 cm shallower than the room.\"]],[\"Nis vanaf links\",[\"Niche depuis la gauche\",\"Nische von links\",\"Recess from left\"]],[\"De nis moet minstens 10 cm van beide hoeken blijven.\",[\"La niche doit rester à au moins 10 cm des deux angles.\",\"Die Nische muss mindestens 10 cm von beiden Ecken entfernt bleiben.\",\"The recess must stay at least 10 cm from both corners.\"]],[\"Breedte koof\",[\"Largeur du coffrage\",\"Breite Kofferung\",\"Boxing width\"]],[\"Koof vanaf links\",[\"Coffrage depuis la gauche\",\"Kofferung von links\",\"Boxing from left\"]],[\"De koof past niet in de breedte.\",[\"Le coffrage ne tient pas dans la largeur.\",\"Die Kofferung passt nicht in die Breite.\",\"The boxing doesn't fit in the width.\"]],[\"De koof is te diep voor de ruimte.\",[\"Le coffrage est trop profond pour la pièce.\",\"Die Kofferung ist zu tief für den Raum.\",\"The boxing is too deep for the room.\"]],[\"Een gewone rechthoekige ruimte met 4 wanden.\",[\"Une pièce rectangulaire classique avec 4 murs.\",\"Ein normaler rechteckiger Raum mit 4 Wänden.\",\"A standard rectangular room with 4 walls.\"]],[\"Eén hoek van de ruimte springt naar binnen, bijvoorbeeld door een trapgat of een kast van de buren. De breedte uitsparing meet je langs de muur, van de hoek tot aan de uitsparing. Met Spiegelen zit de uitsparing links of rechts.\",[\"Un angle de la pièce rentre vers l'intérieur, par exemple à cause d'une trémie d'escalier ou d'un placard voisin. Mesurez la largeur de la découpe le long du mur, de l'angle jusqu'à la découpe. Avec Inverser, la découpe se trouve à gauche ou à droite.\",\"Eine Ecke des Raums springt nach innen, zum Beispiel durch eine Treppenöffnung oder einen Schrank der Nachbarn. Die Breite der Aussparung messen Sie entlang der Wand, von der Ecke bis zur Aussparung. Mit Spiegeln liegt die Aussparung links oder rechts.\",\"One corner of the room steps inwards, for example due to a stairwell or a neighbour’s cupboard. Measure the cut-out width along the wall, from the corner to the cut-out. With Mirror, the cut-out is on the left or right.\"]],[\"Een nis in de achterwand, bijvoorbeeld voor een inloopdouche of een kast. Met Nis vanaf links schuif je de nis langs de wand.\",[\"Une niche dans le mur du fond, par exemple pour une douche à l'italienne ou un placard. Avec Niche depuis la gauche, vous déplacez la niche le long du mur.\",\"Eine Nische in der Rückwand, zum Beispiel für eine begehbare Dusche oder einen Schrank. Mit Nische von links verschieben Sie die Nische entlang der Wand.\",\"A recess in the back wall, for example for a walk-in shower or a cupboard. With Recess from left you slide the recess along the wall.\"]],[\"Een koof is een ingebouwde leidingkoker of schacht tegen de achterwand. Staat hij in de hoek (Koof vanaf links = 0), dan heeft de ruimte 6 wanden: 4 wanden van de ruimte en 2 zijden van de koof. Staat hij verder van de hoek, dan zijn het er 8. Met Spiegelen zit hij in de rechterhoek.\",[\"Un coffrage est une gaine technique intégrée contre le mur du fond. S'il est dans l'angle (Coffrage depuis la gauche = 0), la pièce a 6 murs : 4 murs de la pièce et 2 côtés du coffrage. S'il est plus éloigné de l'angle, il y en a 8. Avec Inverser, il se trouve dans l'angle droit.\",\"Eine Kofferung ist ein eingebauter Leitungsschacht an der Rückwand. Steht sie in der Ecke (Kofferung von links = 0), hat der Raum 6 Wände: 4 Wände des Raums und 2 Seiten der Kofferung. Steht sie weiter von der Ecke entfernt, sind es 8. Mit Spiegeln liegt sie in der rechten Ecke.\",\"A boxing is a built-in pipe duct or shaft against the back wall. If it is in the corner (Boxing from left = 0), the room has 6 walls: 4 room walls and 2 sides of the boxing. If it is further from the corner, there are 8. With Mirror it sits in the right-hand corner.\"]],[\"Beton \u0026 steen\",[\"Béton \u0026 pierre\",\"Beton \u0026 Stein\",\"Concrete \u0026 stone\"]],[\"Inloopdouche\",[\"Douche à l'italienne\",\"Begehbare Dusche\",\"Walk-in shower\"]],[\"Vrijstaand bad\",[\"Baignoire îlot\",\"Freistehende Wanne\",\"Freestanding bath\"]],[\"Badmeubel met wastafel\",[\"Meuble vasque avec lavabo\",\"Waschtischunterschrank mit Waschbecken\",\"Vanity unit with basin\"]],[\"Handdoekradiator\",[\"Sèche-serviettes\",\"Handtuchheizkörper\",\"Towel radiator\"]],[\"Tv-meubel met tv\",[\"Meuble TV avec téléviseur\",\"TV-Möbel mit Fernseher\",\"TV unit with TV\"]],[\"Slaapkamer en werkplek\",[\"Chambre et bureau\",\"Schlafzimmer und Arbeitsplatz\",\"Bedroom and workspace\"]],[\"Keukenblok met bovenkasten\",[\"Bloc cuisine avec meubles hauts\",\"Küchenzeile mit Oberschränken\",\"Kitchen unit with wall cabinets\"]],[\"RVS geborsteld\",[\"Inox brossé\",\"Edelstahl gebürstet\",\"Brushed stainless steel\"]],[\"Bedrijfsruimte\",[\"Local professionnel\",\"Gewerberaum\",\"Commercial space\"]],[\"De 3D-weergave kon niet laden. Controleer je internetverbinding en laad de pagina opnieuw.\",[\"La vue 3D n'a pas pu se charger. Vérifiez votre connexion internet et rechargez la page.\",\"Die 3D-Ansicht konnte nicht geladen werden. Prüfen Sie Ihre Internetverbindung und laden Sie die Seite neu.\",\"The 3D view couldn't load. Check your internet connection and reload the page.\"]],[\"Je browser ondersteunt geen 3D-weergave (WebGL). Probeer een recente versie van Chrome, Edge of Safari.\",[\"Votre navigateur ne prend pas en charge la vue 3D (WebGL). Essayez une version récente de Chrome, Edge ou Safari.\",\"Ihr Browser unterstützt keine 3D-Ansicht (WebGL). Versuchen Sie eine aktuelle Version von Chrome, Edge oder Safari.\",\"Your browser doesn't support 3D view (WebGL). Try a recent version of Chrome, Edge or Safari.\"]],[\"Sleep om rond te kijken · scroll, knijp of + − om te zoomen · dubbelklik op de vloer om erheen te lopen\",[\"Faites glisser pour regarder autour · défilez, pincez ou utilisez + − pour zoomer · double-cliquez sur le sol pour vous y déplacer\",\"Ziehen zum Umsehen · scrollen, mit zwei Fingern oder + − zoomen · Doppelklick auf den Boden, um dorthin zu gehen\",\"Drag to look around · scroll, pinch or + − to zoom · double-click the floor to walk there\"]],[\"Sleep om rond te kijken\",[\"Faites glisser pour regarder autour\",\"Ziehen zum Umsehen\",\"Drag to look around\"]],[\"recht van voren\",[\"vue de face\",\"direkt von vorne\",\"straight on\"]],[\"Geen deuren of ramen in deze wand.\",[\"Aucune porte ni fenêtre dans ce mur.\",\"Keine Türen oder Fenster in dieser Wand.\",\"No doors or windows in this wall.\"]],[\"Kies hierboven een product en decor voor deze wand.\",[\"Choisissez ci-dessus un produit et un décor pour ce mur.\",\"Wählen Sie oben ein Produkt und Dekor für diese Wand.\",\"Choose a product and decor for this wall above.\"]],[\"Deze wand wordt niet bekleed.\",[\"Ce mur n'est pas revêtu.\",\"Diese Wand wird nicht verkleidet.\",\"This wall is not clad.\"]],[\"Kies hierboven een product en decor voor de vloer.\",[\"Choisissez ci-dessus un produit et un décor pour le sol.\",\"Wählen Sie oben ein Produkt und Dekor für den Boden.\",\"Choose a product and decor for the floor above.\"]],[\"De vloer wordt niet bekleed.\",[\"Le sol n'est pas revêtu.\",\"Der Boden wird nicht belegt.\",\"The floor is not covered.\"]],[\"Aanvraag via Ruimteplanner\",[\"Demande via le planificateur de pièce\",\"Anfrage über den Raumplaner\",\"Request via Room Planner\"]],[\"Vloer: niet bekleed\",[\"Sol : non revêtu\",\"Boden: nicht belegt\",\"Floor: not covered\"]],[\"Bestellijst (adviesprijzen incl. btw,\",[\"Liste de commande (prix conseillés TVA comprise,\",\"Bestellliste (Preisempfehlungen inkl. MwSt.,\",\"Order list (recommended prices incl. VAT,\"]],[\"Totaal incl. btw:\",[\"Total TVA comprise :\",\"Gesamt inkl. MwSt.:\",\"Total incl. VAT:\"]],[\"Eigen ontwerp: afbeelding stuur ik mee per e-mail\",[\"Design personnalisé : j'envoie l'image par e-mail\",\"Eigenes Design: Bild sende ich per E-Mail\",\"Own design: I will send the image by email\"]],[\"Tegen de wand\",[\"Contre le mur\",\"An der Wand\",\"Against the wall\"]],[\"Kleur profiel\",[\"Couleur du profilé\",\"Profilfarbe\",\"Profile colour\"]],[\"Schuif het object in het wandaanzicht, of versleep het in de 3D-weergave.\",[\"Déplacez l'objet dans la vue du mur ou faites-le glisser dans la vue 3D.\",\"Verschieben Sie das Objekt in der Wandansicht oder ziehen Sie es in der 3D-Ansicht.\",\"Move the object in the wall view, or drag it in the 3D view.\"]],[\"De 3D-weergave is niet beschikbaar in deze browser.\",[\"La vue 3D n'est pas disponible dans ce navigateur.\",\"Die 3D-Ansicht ist in diesem Browser nicht verfügbar.\",\"The 3D view isn't available in this browser.\"]],[\"De AI maakt je render. Dit duurt meestal 30 tot 90 seconden…\",[\"L'IA réalise votre rendu. Cela prend généralement 30 à 90 secondes…\",\"Die KI erstellt Ihr Rendering. Das dauert meist 30 bis 90 Sekunden…\",\"The AI is creating your render. This usually takes 30 to 90 seconds…\"]],[\"De AI-render werkt zodra de plugin Ruimteplanner AI-render op de website actief is. In deze voorvertoning zie je alleen de momentopname.\",[\"Le rendu IA fonctionne dès que le plugin Ruimteplanner AI-render est actif sur le site. Dans cet aperçu, vous ne voyez que l'instantané.\",\"Das KI-Rendering funktioniert, sobald das Plugin Ruimteplanner AI-render auf der Website aktiv ist. In dieser Vorschau sehen Sie nur die Momentaufnahme.\",\"The AI render works once the Ruimteplanner AI-render plugin is active on the website. In this preview you only see the snapshot.\"]],[\"3D-beeld opslaan\",[\"Enregistrer l'image 3D\",\"3D-Bild speichern\",\"Save 3D image\"]],[\"3D-beeld opgeslagen\",[\"Image 3D enregistrée\",\"3D-Bild gespeichert\",\"3D image saved\"]],[\"BTW BE 0437 961 334\",[\"TVA BE 0437 961 334\",\"USt BE 0437 961 334\",\"VAT BE 0437 961 334\"]],[\"Offerte-aanvraag Ruimteplanner\",[\"Demande de devis – planificateur de pièce\",\"Angebotsanfrage Raumplaner\",\"Quote request – Room Planner\"]],[\"Besteloverzicht Ruimteplanner\",[\"Récapitulatif de commande – planificateur de pièce\",\"Bestellübersicht Raumplaner\",\"Order summary – Room Planner\"]],[\"AI-impressie van je ontwerp. Een impressie; kleur en structuur van het echte product kunnen afwijken.\",[\"Impression IA de votre projet. Il s’agit d’une impression ; la couleur et la structure du produit réel peuvent différer.\",\"KI-Eindruck Ihres Entwurfs. Ein Eindruck; Farbe und Struktur des echten Produkts können abweichen.\",\"AI impression of your design. An impression; the colour and texture of the real product may differ.\"]],[\"3D-overzicht en plattegrond met wandnummers, zoals in de tabel hieronder.\",[\"Vue 3D et plan avec numéros de murs, comme dans le tableau ci-dessous.\",\"3D-Übersicht und Grundriss mit Wandnummern, wie in der Tabelle unten.\",\"3D overview and floor plan with wall numbers, as in the table below.\"]],[\"m, plafondhoogte\",[\"m, hauteur sous plafond\",\"m, Deckenhöhe\",\"m, ceiling height\"]],[\"Wandbekleding\",[\"Revêtement mural\",\"Wandverkleidung\",\"Wall cladding\"]],[\"niet bekleed\",[\"non revêtu\",\"nicht verkleidet\",\"not clad\"]],[\"Bevestiging panelen\",[\"Fixation des panneaux\",\"Befestigung der Paneele\",\"Panel fixing\"]],[\"zelf afhalen op afspraak\",[\"enlèvement sur rendez-vous\",\"Selbstabholung nach Vereinbarung\",\"collection by appointment\"]],[\"Omschrijving\",[\"Description\",\"Beschreibung\",\"Description\"]],[\"* Posten op aanvraag zijn niet in het totaal opgenomen; die prijzen staan in de offerte.\",[\"* Les postes sur demande ne sont pas inclus dans le total ; leurs prix figurent dans le devis.\",\"* Positionen auf Anfrage sind nicht im Gesamtbetrag enthalten; diese Preise stehen im Angebot.\",\"* Items on request are not included in the total; their prices are in the quote.\"]],[\"Inrichting (ter illustratie, niet in de prijs):\",[\"Aménagement (à titre illustratif, non compris dans le prix) :\",\"Einrichtung (zur Veranschaulichung, nicht im Preis):\",\"Furnishing (for illustration, not included in the price):\"]],[\"Deze aanvraag is verstuurd via de Ruimteplanner op\",[\"Cette demande a été envoyée via le planificateur de pièce le\",\"Diese Anfrage wurde über den Raumplaner gesendet am\",\"This request was sent via the Room Planner on\"]],[\"Offerte aanvragen of vragen? Gebruik de knop Offerte aanvragen in de Ruimteplanner of mail ons op\",[\"Demander un devis ou des questions ? Utilisez le bouton Demander un devis dans le planificateur de pièce ou écrivez-nous à\",\"Angebot anfordern oder Fragen? Nutzen Sie die Schaltfläche Angebot anfordern im Raumplaner oder schreiben Sie uns an\",\"Request a quote or have questions? Use the Request a quote button in the Room Planner or email us at\"]],[\"HB-Composite-besteloverzicht-\",[\"HB-Composite-recapitulatif-\",\"HB-Composite-Bestelluebersicht-\",\"HB-Composite-order-summary-\"]],[\"Pdf gedownload\",[\"PDF téléchargé\",\"PDF heruntergeladen\",\"PDF downloaded\"]],[\"De pdf kon niet gemaakt worden. Controleer je internetverbinding en probeer opnieuw.\",[\"Le PDF n'a pas pu être créé. Vérifiez votre connexion internet et réessayez.\",\"Das PDF konnte nicht erstellt werden. Prüfen Sie Ihre Internetverbindung und versuchen Sie es erneut.\",\"The PDF couldn't be created. Check your internet connection and try again.\"]],[\"Het aanvraagformulier op deze pagina mist velden.\",[\"Il manque des champs dans le formulaire de demande de cette page.\",\"Im Anfrageformular auf dieser Seite fehlen Felder.\",\"The request form on this page is missing fields.\"]],[\"Het versturen is niet gelukt.\",[\"L'envoi a échoué.\",\"Das Senden ist fehlgeschlagen.\",\"Sending failed.\"]],[\"Het formulier is geweigerd:\",[\"Le formulaire a été refusé :\",\"Das Formular wurde abgelehnt:\",\"The form was rejected:\"]],[\"controleer je gegevens\",[\"vérifiez vos coordonnées\",\"prüfen Sie Ihre Daten\",\"check your details\"]],[\"Het versturen duurt te lang.\",[\"L'envoi prend trop de temps.\",\"Das Senden dauert zu lange.\",\"Sending is taking too long.\"]],[\"Het aanvraagformulier heeft geen verzendknop.\",[\"Le formulaire de demande n'a pas de bouton d'envoi.\",\"Das Anfrageformular hat keine Senden-Schaltfläche.\",\"The request form has no submit button.\"]],[\"Vul je naam in.\",[\"Indiquez votre nom.\",\"Geben Sie Ihren Namen ein.\",\"Enter your name.\"]],[\"Vul een geldig e-mailadres in.\",[\"Indiquez une adresse e-mail valide.\",\"Geben Sie eine gültige E-Mail-Adresse ein.\",\"Enter a valid email address.\"]],[\"Pdf van je ontwerp maken…\",[\"Création du PDF de votre projet…\",\"PDF Ihres Entwurfs wird erstellt…\",\"Creating a PDF of your design…\"]],[\"Pdf uploaden…\",[\"Envoi du PDF…\",\"PDF wird hochgeladen…\",\"Uploading PDF…\"]],[\"Aanvraag versturen…\",[\"Envoi de la demande…\",\"Anfrage wird gesendet…\",\"Sending request…\"]],[\"Bedankt, je aanvraag\",[\"Merci, votre demande\",\"Danke, Ihre Anfrage\",\"Thank you, your request\"]],[\"is verstuurd. We nemen zo snel mogelijk contact met je op.\",[\"a été envoyée. Nous vous contacterons dans les meilleurs délais.\",\"wurde gesendet. Wir melden uns schnellstmöglich bei Ihnen.\",\"has been sent. We'll be in touch as soon as possible.\"]],[\"(pdf bijvoegen)\",[\"(joindre le PDF)\",\"(PDF anhängen)\",\"(attach PDF)\"]],[\"Versturen kan hier niet rechtstreeks.\",[\"L'envoi direct n'est pas possible ici.\",\"Direktes Senden ist hier nicht möglich.\",\"Sending directly isn't possible here.\"]],[\"stuur hem naar\",[\"envoyez-le à\",\"senden Sie es an\",\"send it to\"]],[\"Draai en zoom naar het beeld dat je wilt en klik op ‘AI-render van dit standpunt’\",[\"Tournez et zoomez jusqu'à la vue souhaitée, puis cliquez sur ‘Rendu IA de ce point de vue’\",\"Drehen und zoomen Sie zur gewünschten Ansicht und klicken Sie auf ‘KI-Rendering aus diesem Blickwinkel’\",\"Rotate and zoom to the view you want and click ‘AI render from this viewpoint’\"]],[\"Toegepast op alle wanden\",[\"Appliqué à tous les murs\",\"Auf alle Wände angewendet\",\"Applied to all walls\"]],[\"Deze afbeelding kan niet worden geopend. Kies een JPG of PNG.\",[\"Impossible d'ouvrir cette image. Choisissez un JPG ou un PNG.\",\"Dieses Bild kann nicht geöffnet werden. Wählen Sie ein JPG oder PNG.\",\"This image can't be opened. Choose a JPG or PNG.\"]],[\"Tekst geselecteerd, kopieer met Ctrl/Cmd+C\",[\"Texte sélectionné, copiez avec Ctrl/Cmd+C\",\"Text markiert, kopieren mit Strg/Cmd+C\",\"Text selected, copy with Ctrl/Cmd+C\"]],[\"Overzicht gekopieerd\",[\"Récapitulatif copié\",\"Übersicht kopiert\",\"Summary copied\"]],[\"cm hoog, gezien vanuit de ruimte.\",[\"cm de haut, vu depuis la pièce.\",\"cm hoch, vom Raum aus gesehen.\",\"cm high, seen from the room.\"]],[\"Adviesprijzen voor consumenten incl. btw\",[\"Prix conseillés pour les consommateurs TVA comprise\",\"Unverbindliche Verbraucherpreise inkl. MwSt.\",\"Recommended consumer prices incl. VAT\"]],[\"We rekenen in hele pakken, inclusief snijverlies.\",[\"Nous calculons en paquets entiers, chutes comprises.\",\"Wir rechnen in ganzen Paketen, inklusive Verschnitt.\",\"We calculate in whole packs, including cutting waste.\"]],[\"Wij controleren de maten en hoeveelheden bij je aanvraag; de definitieve prijs staat in de offerte.\",[\"Nous vérifions les dimensions et les quantités lors de votre demande ; le prix définitif figure dans le devis.\",\"Wir prüfen Maße und Mengen bei Ihrer Anfrage; der endgültige Preis steht im Angebot.\",\"We check the dimensions and quantities with your request; the final price is in the quote.\"]],[\"Nog niets op deze wand.\",[\"Rien sur ce mur pour le moment.\",\"Noch nichts an dieser Wand.\",\"Nothing on this wall yet.\"]],[\"Sleep een object om het te verschuiven.\",[\"Faites glisser un objet pour le déplacer.\",\"Ziehen Sie ein Objekt, um es zu verschieben.\",\"Drag an object to move it.\"]],[\"zelf afhalen op afspraak\",[\"enlèvement sur rendez-vous\",\"Selbstabholung nach Vereinbarung\",\"collection by appointment\"]],[\"Levering via een dealer bij je in de buurt.\",[\"Livraison via un revendeur près de chez vous.\",\"Lieferung über einen Händler in Ihrer Nähe.\",\"Delivery through a dealer near you.\"]]],\"w\":[[\"Kantenband\",[\"Bande de chant\",\"Kantenband\",\"Edge tape\"]],[\"Tweedelige binnenhoek\",[\"Angle intérieur en deux parties\",\"Zweiteiliges Innenecke-Profil\",\"Two-part inside corner\"]],[\"Binnenhoek\",[\"Angle intérieur\",\"Innenecke\",\"Inside corner\"]],[\"Buitenhoek\",[\"Angle extérieur\",\"Außenecke\",\"Outside corner\"]],[\"U-Profiel\",[\"Profilé en U\",\"U-Profil\",\"U-profile\"]],[\"Eindprofiel\",[\"Profilé de finition\",\"Abschlussprofil\",\"End profile\"]],[\"Wit\",[\"blanc\",\"weiß\",\"white\"]],[\"Zwart\",[\"noir\",\"schwarz\",\"black\"]],[\"Grijs\",[\"gris\",\"grau\",\"grey\"]],[\"2 delig U profiel folie\",[\"Profilé en U 2 parties film\",\"2-teiliges U-Profil Folie\",\"2-part U-profile foil\"]],[\"Antraciet\",[\"anthracite\",\"anthrazit\",\"anthracite\"]],[\"Crème\",[\"crème\",\"creme\",\"cream\"]],[\"Lariks\",[\"mélèze\",\"Lärche\",\"larch\"]],[\"U profiel\",[\"Profilé en U\",\"U-Profil\",\"U-profile\"]],[\"folie\",[\"film\",\"Folie\",\"foil\"]],[\"Z profiel\",[\"Profilé en Z\",\"Z-Profil\",\"Z-profile\"]],[\"Ventilatiehoek\",[\"Angle de ventilation\",\"Lüftungswinkel\",\"Ventilation angle\"]],[\"Ventilatieprofiel\",[\"Profilé de ventilation\",\"Lüftungsprofil\",\"Ventilation profile\"]],[\"Hoekstuk boeideel\",[\"Pièce d'angle bandeau de rive\",\"Eckstück Stirnbrett\",\"Fascia corner piece\"]],[\"Verbinding boeideel\",[\"Jonction bandeau de rive\",\"Verbinder Stirnbrett\",\"Fascia connector\"]],[\"Boeideel\",[\"Bandeau de rive\",\"Stirnbrett\",\"Fascia board\"]],[\"panelen\",[\"panneaux\",\"Paneele\",\"panels\"]],[\"pakken\",[\"paquets\",\"Pakete\",\"packs\"]],[\"bijv.\",[\"p. ex.\",\"z. B.\",\"e.g.\"]],[\"Je aanvraag\",[\"Votre demande\",\"Ihre Anfrage\",\"Your request\"]],[\"Je bestelling\",[\"Votre commande\",\"Ihre Bestellung\",\"Your order\"]],[\"Adviesprijs per pak incl. btw\",[\"Prix conseillé par paquet TVA comprise\",\"Preisempfehlung pro Paket inkl. MwSt.\",\"Recommended price per pack incl. VAT\"]],[\"Adviesprijs per stuk incl. btw\",[\"Prix conseillé par pièce TVA comprise\",\"Preisempfehlung pro Stück inkl. MwSt.\",\"Recommended price per piece incl. VAT\"]],[\"Adviesprijs per\",[\"Prix conseillé par\",\"Preisempfehlung pro\",\"Recommended price per\"]],[\"incl. btw\",[\"TVA comprise\",\"inkl. MwSt.\",\"incl. VAT\"]],[\"excl. btw\",[\"HTVA\",\"zzgl. MwSt.\",\"excl. VAT\"]],[\"pak van\",[\"paquet de\",\"Paket mit\",\"pack of\"]],[\"per pak\",[\"par paquet\",\"pro Paket\",\"per pack\"]],[\"per m²\",[\"par m²\",\"pro m²\",\"per m²\"]],[\"per stuk\",[\"par pièce\",\"pro Stück\",\"per piece\"]],[\"Inclusief\",[\"Y compris\",\"Inklusive\",\"Including\"]],[\"Geleverd\",[\"Livré\",\"Geliefert\",\"Supplied\"]],[\"lengte\",[\"longueur\",\"Länge\",\"length\"]],[\"breedte\",[\"largeur\",\"Breite\",\"width\"]],[\"Start\",[\"Début\",\"Start\",\"Start\"]],[\"stuks\",[\"pièces\",\"Stück\",\"pieces\"]],[\"stuk\",[\"pièce\",\"Stück\",\"piece\"]],[\"pakken\",[\"paquets\",\"Pakete\",\"packs\"]],[\"pak\",[\"paquet\",\"Paket\",\"pack\"]],[\"artikelen\",[\"articles\",\"Artikel\",\"items\"]],[\"artikel\",[\"article\",\"Artikel\",\"item\"]],[\"advies\",[\"conseil\",\"Empfehlung\",\"advice\"]],[\"gekozen\",[\"choisi(s)\",\"ausgewählt\",\"selected\"]],[\"Vrijblijvende aanvraag\",[\"Demande sans engagement\",\"Unverbindliche Anfrage\",\"No-obligation request\"]],[\"regels\",[\"lignes\",\"Zeilen\",\"lines\"]],[\"regel\",[\"ligne\",\"Zeile\",\"line\"]],[\"Bestelling\",[\"Commande\",\"Bestellung\",\"Order\"]],[\"netto\",[\"net\",\"netto\",\"net\"]],[\"geleverd\",[\"livrés\",\"geliefert\",\"supplied\"]],[\"wijzigen\",[\"modifier\",\"ändern\",\"change\"]],[\"Levering\",[\"Livraison\",\"Lieferung\",\"Delivery\"]],[\"Bedrijf\",[\"Entreprise\",\"Firma\",\"Company\"]],[\"Naam\",[\"Nom\",\"Name\",\"Name\"]],[\"E-mailadres\",[\"Adresse e-mail\",\"E-Mail-Adresse\",\"Email address\"]],[\"Telefoon\",[\"Téléphone\",\"Telefon\",\"Phone\"]],[\"set van 4 panelen\",[\"set de 4 panneaux\",\"Set mit 4 Paneelen\",\"set of 4 panels\"]],[\"set van 6 panelen\",[\"set de 6 panneaux\",\"Set mit 6 Paneelen\",\"set of 6 panels\"]],[\"m² per set\",[\"m² par set\",\"m² pro Set\",\"m² per set\"]],[\"m² per pak\",[\"m² par paquet\",\"m² pro Paket\",\"m² per pack\"]],[\"m² nodig\",[\"m² nécessaires\",\"m² benötigt\",\"m² needed\"]],[\"rol van\",[\"rouleau de\",\"Rolle mit\",\"roll of\"]],[\"hoogste druksterkte\",[\"résistance à la compression la plus élevée\",\"höchste Druckfestigkeit\",\"highest compressive strength\"]],[\"pak van\",[\"paquet de\",\"Paket mit\",\"pack of\"]],[\"per m²\",[\"par m²\",\"pro m²\",\"per m²\"]],[\"eigen ontwerp\",[\"design personnalisé\",\"eigenes Design\",\"own design\"]],[\"Eigen ontwerp\",[\"Design personnalisé\",\"Eigenes Design\",\"Own design\"]],[\"binnenhoek\",[\"angle intérieur\",\"Innenecke\",\"inside corner\"]],[\"buitenhoek\",[\"angle extérieur\",\"Außenecke\",\"outside corner\"]],[\"startprofiel\",[\"profilé de départ\",\"Startprofil\",\"starter profile\"]],[\"U-profiel\",[\"profilé en U\",\"U-Profil\",\"U-profile\"]],[\"Mat wit\",[\"Blanc mat\",\"Matt weiß\",\"Matt white\"]],[\"Mat zwart\",[\"Noir mat\",\"Matt schwarz\",\"Matt black\"]],[\"Zilver brush\",[\"Argent brossé\",\"Silber gebürstet\",\"Brushed silver\"]],[\"Aluminium-look\",[\"Aspect aluminium\",\"Aluminium-Optik\",\"Aluminium look\"]],[\"wand\",[\"mur\",\"Wand\",\"wall\"]],[\"st.\",[\"pce\",\"Stk.\",\"pcs\"]],[\"koker\",[\"cartouche\",\"Kartusche\",\"cartridge\"]],[\"paneel\",[\"panneau\",\"Paneel\",\"panel\"]],[\"m² vloer\",[\"m² de sol\",\"m² Boden\",\"m² floor\"]],[\"klasse\",[\"classe\",\"Klasse\",\"class\"]],[\"cm breed\",[\"cm de large\",\"cm breit\",\"cm wide\"]],[\"links\",[\"gauche\",\"links\",\"left\"]],[\"rechts\",[\"droite\",\"rechts\",\"right\"]],[\"m² bekleding\",[\"m² de revêtement\",\"m² Verkleidung\",\"m² cladding\"]],[\"rijen\",[\"rangées\",\"Reihen\",\"rows\"]],[\"wanden\",[\"murs\",\"Wände\",\"walls\"]],[\"m² wandbekleding\",[\"m² de revêtement mural\",\"m² Wandverkleidung\",\"m² wall cladding\"]],[\"m² wand\",[\"m² de mur\",\"m² Wand\",\"m² wall\"]],[\"totaal incl. btw\",[\"total TVA comprise\",\"gesamt inkl. MwSt.\",\"total incl. VAT\"]],[\"vloer\",[\"sol\",\"Boden\",\"floor\"]],[\"Bezorgen kost\",[\"La livraison coûte\",\"Die Zustellung kostet\",\"Delivery costs\"]],[\"vanaf\",[\"à partir de\",\"ab\",\"from\"]],[\"leveren we franco\",[\"nous livrons franco\",\"liefern wir frachtfrei\",\"we deliver free of charge\"]],[\"plafondhoogte\",[\"hauteur sous plafond\",\"Deckenhöhe\",\"ceiling height\"]],[\"niet bekleed\",[\"non revêtu\",\"nicht verkleidet\",\"not clad\"]],[\"op aanvraag\",[\"sur demande\",\"auf Anfrage\",\"on request\"]],[\"adviesprijzen incl. btw\",[\"prix conseillés TVA comprise\",\"Preisempfehlungen inkl. MwSt.\",\"recommended prices incl. VAT\"]],[\"via de dealer\",[\"via le revendeur\",\"über den Händler\",\"via the dealer\"]],[\"afhalen\",[\"enlèvement\",\"Abholung\",\"collection\"]]],\"r\":[[\"Aanzicht wand (\\\\d+)\",[\"Vue du mur $1\",\"Ansicht Wand $1\",\"Wall view $1\"]],[\"\\\\bWand (\\\\d+)\\\\b\",[\"Mur $1\",\"Wand $1\",\"Wall $1\"]],[\"(\\\\d+) objecten staan los in de ruimte\\\\.\",[\"$1 objets sont libres dans la pièce.\",\"$1 Objekte stehen frei im Raum.\",\"$1 objects are free-standing in the room.\"]],[\"(\\\\d+) object staat los in de ruimte\\\\.\",[\"$1 objet est libre dans la pièce.\",\"$1 Objekt steht frei im Raum.\",\"$1 object is free-standing in the room.\"]],[\"Klaar\\\\. Je kunt vandaag nog (\\\\d+) renders? maken\\\\.\",[\"Terminé. Vous pouvez encore réaliser $1 rendu(s) aujourd'hui.\",\"Fertig. Sie können heute noch $1 Rendering(s) erstellen.\",\"Done. You can create $1 more render(s) today.\"]],[\"Minimale afname: ([\\\\d,.]+) pakken\\\\.\",[\"Commande minimale : $1 paquets.\",\"Mindestabnahme: $1 Pakete.\",\"Minimum order: $1 packs.\"]]]};\n\n/* HB i18n: vertaalt de interface naar FR/DE/EN op basis van de paginataal */\n(function(){\n var D=window.HB_RP_DICT; if(!D) return;\n function taal(){var l='';try{l=document.documentElement.lang||'';if((!l||/^nl/i.test(l))\u0026\u0026window.parent\u0026\u0026window.parent!==window){l=window.parent.document.documentElement.lang||l;}}catch(e){}return (l||'nl').slice(0,2).toLowerCase();}\n var L=taal(), I={fr:0,de:1,en:2}[L]; if(I==null) return;\n window.HB_LANG=L;\n var E={}, S=[], W=[], R=[];\n D.e.forEach(function(x){E[x[0]]=x[1][I];});\n D.s.forEach(function(x){S.push([x[0],x[1][I]]);}); S.sort(function(a,b){return b[0].length-a[0].length;});\n D.w.forEach(function(x){W.push([new RegExp('(^|[^A-Za-zÀ-ÿ])'+x[0].replace(/[.*+?^${}()|[\\]\\\\]/g,'\\\\$\u0026')+'(?=$|[^A-Za-zÀ-ÿ])','g'),x[1][I],x[0].length]);}); W.sort(function(a,b){return b[2]-a[2];});\n D.r.forEach(function(x){R.push([new RegExp(x[0],'g'),x[1][I]]);});\n function tr(t){\n if(!t||!/[A-Za-zÀ-ÿ]/.test(t)) return t;\n var c=t.trim(); if(E.hasOwnProperty(c)) return t.replace(c,E[c]);\n var o=t;\n R.forEach(function(r){o=o.replace(r[0],r[1]);});\n S.forEach(function(s){if(o.indexOf(s[0])\u003e-1) o=o.split(s[0]).join(s[1]);});\n W.forEach(function(w){o=o.replace(w[0],function(m,p){return p+w[1];});});\n return o;\n }\n window.HB_T=tr;\n var done=new WeakMap();\n var ATTR=['placeholder','title','aria-label','alt'];\n function doNode(n){\n if(n.nodeType===3){var v=n.nodeValue; if(done.get(n)===v) return; var t=tr(v); if(t!==v) n.nodeValue=t; done.set(n,n.nodeValue); return;}\n if(n.nodeType!==1||n.tagName==='SCRIPT'||n.tagName==='STYLE') return;\n ATTR.forEach(function(a){var v=n.getAttribute(a); if(v){var t=tr(v); if(t!==v) n.setAttribute(a,t);}});\n if(n.tagName==='TEXTAREA') return;\n if(n.tagName==='INPUT'\u0026\u0026/^(button|submit)$/i.test(n.type)\u0026\u0026n.value){var t2=tr(n.value); if(t2!==n.value) n.value=t2;}\n for(var c=n.firstChild;c;c=c.nextSibling) doNode(c);\n }\n var A=window.alert, C=window.confirm;\n window.alert=function(m){return A.call(window,tr(String(m)));};\n window.confirm=function(m){return C.call(window,tr(String(m)));};\n function pdfHook(){var J=window.jspdf\u0026\u0026window.jspdf.jsPDF; if(!J||J.__hb) return; J.__hb=1; var A=J.API;\n var T0=A.text; A.text=function(t){var a=[].slice.call(arguments); if(typeof t==='string') a[0]=tr(t); else if(Array.isArray(t)) a[0]=t.map(function(x){return typeof x==='string'?tr(x):x;}); return T0.apply(this,a);};\n var AT=A.autoTable; if(AT){A.autoTable=function(o){function m(r){return (r||[]).map(function(row){return (row||[]).map(function(c){if(typeof c==='string') return tr(c); if(c\u0026\u0026typeof c.content==='string'){c.content=tr(c.content);} return c;});});} if(o){if(o.head) o.head=m(o.head); if(o.body) o.body=m(o.body); if(o.foot) o.foot=m(o.foot);} return AT.apply(this,arguments);};}\n }\n setInterval(pdfHook,1000);\n function start(){\n var root=document.querySelector('body')||document.body;\n root.setAttribute('data-no-translation','');\n doNode(root);\n new MutationObserver(function(ms){ms.forEach(function(m){\n if(m.type==='characterData') doNode(m.target);\n else if(m.type==='attributes') doNode(m.target);\n else m.addedNodes.forEach(doNode);\n });}).observe(root,{childList:true,subtree:true,characterData:true,attributes:true,attributeFilter:ATTR});\n }\n if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',start); else start();\n})();\n\u003c/script\u003e\n\u003cmeta charset=\"utf-8\"\u003e\n\u003cmeta name=\"viewport\" content=\"width=device-width,initial-scale=1\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ctitle\u003eAqua Ruimteplanner\u003c/title\u003e\n\u003cmeta name=\"description\" content=\"Ontwerp elke ruimte wand voor wand met Aqua Composite panelen, tegels en vloeren, bekijk het in 3D en bereken wat je nodig hebt.\"\u003e\n\u003clink rel=\"preconnect\" href=\"https://fonts.googleapis.com\"\u003e\n\u003clink rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin\u003e\n\u003clink rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,600;12..96,750\u0026family=Hanken+Grotesk:wght@400;500;600;700\u0026family=IBM+Plex+Mono:wght@500\u0026display=swap\"\u003e\n\u003cstyle\u003e\n/* Layout: werkbank — links 3D-weergave met plattegrond (sticky), rechts vier stappen; op telefoon gestapeld */\n:root{\n --bg:#eef1f0; --surface:#ffffff; --sunk:#f5f7f6; --ink:#15222a; --muted:#5a6a70; --line:#d5dddb;\n --accent:#0d6a6e; --accent-ink:#ffffff; --accent-soft:#d9ecea; --warn:#9a5b12;\n --f-display:\"Bricolage Grotesque\",\"Hanken Grotesk\",system-ui,sans-serif;\n --f-body:\"Hanken Grotesk\",system-ui,-apple-system,\"Segoe UI\",sans-serif;\n --f-mono:\"IBM Plex Mono\",ui-monospace,SFMono-Regular,Menlo,monospace;\n --r:10px;\n}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){\n --bg:#0e1517; --surface:#162024; --sunk:#111a1d; --ink:#e4ecea; --muted:#93a4a7; --line:#2a383c;\n --accent:#4cb2ad; --accent-ink:#06211f; --accent-soft:#183a3a; --warn:#e3a55a; color-scheme:dark}}\n:root[data-theme=\"dark\"]{\n --bg:#0e1517; --surface:#162024; --sunk:#111a1d; --ink:#e4ecea; --muted:#93a4a7; --line:#2a383c;\n --accent:#4cb2ad; --accent-ink:#06211f; --accent-soft:#183a3a; --warn:#e3a55a; color-scheme:dark}\n*,*::before,*::after{box-sizing:border-box}\n[hidden]{display:none!important}\nhtml,body{margin:0}\nbody{background:var(--bg);color:var(--ink);font:15px/1.5 var(--f-body);-webkit-font-smoothing:antialiased}\nbutton,input,select{font:inherit;color:inherit}\n.app{max-width:1280px;margin:0 auto;padding:22px clamp(16px,3vw,32px) 48px}\nheader.top{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:8px 24px;margin-bottom:18px}\n.eyebrow{font:600 12px/1 var(--f-body);letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}\nh1{font:750 clamp(30px,4.4vw,46px)/1.02 var(--f-display);letter-spacing:-.02em;margin:6px 0 0;text-wrap:balance}\n.lede{max-width:48ch;color:var(--muted);margin:0}\n.layout{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);gap:24px;align-items:start}\n@media (max-width:920px){.layout{grid-template-columns:minmax(0,1fr)}}\n.stage{position:sticky;top:calc(env(safe-area-inset-top,0px) + 12px);display:grid;gap:10px;min-width:0}\n@media (max-width:920px){.stage{position:static}}\n.embedded .stage{position:relative;z-index:6;will-change:transform}\n.embedded .viewer{max-height:var(--pvh,none)}\n.embedded .stage.pinned{background:var(--bg);box-shadow:0 10px 18px -14px rgba(10,30,35,.55);border-radius:0 0 12px 12px}\n.bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between}\n.seg{display:inline-flex;flex-wrap:wrap;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:3px;gap:2px}\n.seg button{border:0;background:none;padding:7px 13px;border-radius:999px;cursor:pointer;font-weight:600;font-size:14px;color:var(--muted)}\n.seg button[aria-pressed=\"true\"]{background:var(--ink);color:var(--surface)}\n.viewer{position:relative;border-radius:var(--r);overflow:hidden;background:#dcdfdd;box-shadow:0 1px 0 var(--line),0 12px 30px -18px rgba(10,30,35,.45);aspect-ratio:4/3;width:100%;touch-action:none}\n@media (max-width:600px){.viewer{aspect-ratio:1/1}.viewer .hint{left:10px;right:auto;bottom:10px;max-width:calc(100% - 20px)}}\n.viewer canvas{display:block;width:100%!important;height:100%!important}\n.viewer .r360ctl{position:absolute;right:10px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:6px;z-index:3}\n.viewer .r360ctl button{width:38px;height:38px;border-radius:50%;border:1px solid var(--line);background:rgba(255,255,255,.92);color:#15222a;font:600 18px/1 system-ui;cursor:pointer;box-shadow:0 3px 10px -5px rgba(0,0,0,.4)}\n.viewer .r360ctl button:active{background:var(--accent-soft)}\n.viewer.is360{overflow:hidden}.viewer.is360 .minimap{z-index:2;cursor:crosshair}\n.viewer .r360frame{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);width:100%;aspect-ratio:3/2;max-height:100%;box-sizing:border-box;border:2px dashed rgba(255,255,255,.95);box-shadow:0 0 0 2000px rgba(12,24,28,.30);pointer-events:none;z-index:1}\n.viewer .r360frame span{position:absolute;left:50%;bottom:6px;transform:translateX(-50%);font:600 11px/1 system-ui,sans-serif;color:#fff;background:rgba(13,106,110,.88);padding:4px 8px;border-radius:999px;white-space:nowrap}\n.viewer .r360bar{position:absolute;top:10px;right:10px;left:calc(min(34%,190px) + 24px);display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:5px;z-index:3}\n.viewer .r360bar .lbl{font:600 12px/1 system-ui,sans-serif;color:#15222a;background:rgba(255,255,255,.9);padding:9px 10px;border-radius:999px}\n.viewer .r360bar button{min-width:34px;height:34px;padding:0 9px;border-radius:999px;border:1px solid var(--line);background:rgba(255,255,255,.95);color:#15222a;font:600 15px/1 system-ui,sans-serif;cursor:pointer;box-shadow:0 3px 10px -5px rgba(0,0,0,.4)}\n.viewer .r360bar button:hover,.viewer .r360bar button:active{background:var(--accent-soft)}\n.viewer .r360bar .sep{width:6px}\n.viewer .r360bar[hidden],.viewer .r360frame[hidden]{display:none}\n@media (max-width:600px){.viewer .r360bar{top:auto;bottom:10px;left:10px;right:10px;justify-content:center}.viewer .r360bar .lbl{display:none}.viewer.is360 .hint{display:none}.viewer .r360frame span{display:none}}\n.viewer .r360btn{position:absolute;left:10px;bottom:10px;z-index:3;box-shadow:0 4px 14px -6px rgba(0,0,0,.45)}\n@media (max-width:600px){.viewer .r360btn{left:auto;right:10px;bottom:auto;top:10px}}\n.viewer .hint{position:absolute;right:10px;bottom:10px;font-size:12px;color:#33413f;background:rgba(255,255,255,.82);padding:4px 9px;border-radius:999px;pointer-events:none}\n.viewer .err{position:absolute;inset:0;display:grid;place-items:center;text-align:center;padding:24px;color:#33413f;font-weight:600}\n.minimap{position:absolute;left:10px;top:10px;width:min(34%,190px);background:rgba(255,255,255,.88);border-radius:8px;padding:6px;box-shadow:0 2px 8px rgba(0,0,0,.12)}\n.minimap svg{display:block;width:100%;height:auto}\n.status{display:flex;flex-wrap:wrap;gap:4px 14px;align-items:baseline;font-size:14px;color:var(--muted)}\n.status b{color:var(--ink);font-weight:600}\n.mono{font-family:var(--f-mono);font-size:.92em;font-variant-numeric:tabular-nums}\n.btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--surface);padding:8px 13px;border-radius:8px;cursor:pointer;font-weight:600;font-size:14px;text-decoration:none;color:var(--ink)}\n.btn:hover{border-color:var(--accent)}\n.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}\n.btn.small{padding:5px 10px;font-size:13px}\n.btn:disabled{opacity:.45;cursor:not-allowed}\n:focus-visible{outline:2.5px solid var(--accent);outline-offset:2px}\n.row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}\n.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);min-width:0}\n.steps{display:grid;grid-template-columns:repeat(5,1fr);border-bottom:1px solid var(--line)}\n.steps button{border:0;background:none;padding:12px 6px 11px;cursor:pointer;display:grid;justify-items:center;gap:2px;color:var(--muted);font-size:13px;font-weight:600;border-bottom:2.5px solid transparent;margin-bottom:-1px}\n.steps button .n{font:500 11px var(--f-mono);letter-spacing:.06em}\n.steps button[aria-selected=\"true\"]{color:var(--ink);border-bottom-color:var(--accent)}\n.step{padding:18px;display:grid;gap:18px}\n.step h2{font:700 20px/1.2 var(--f-display);margin:0;letter-spacing:-.01em}\n.field-label{font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:0 0 8px}\n.grid2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}\n.grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}\n.num{display:grid;gap:4px;font-size:13px;color:var(--muted)}\n.req{border:1px solid var(--accent);border-radius:var(--r);padding:16px;display:grid;gap:12px;background:var(--surface)}\n.req h3{margin:0;font:700 17px/1.25 var(--f-display)}\n.req .num input,.req .num textarea{font-family:inherit;font-size:15px}\n.req .num textarea{width:100%;min-height:76px;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--sunk);color:var(--ink);resize:vertical}\n.req .msg{font-size:14px;margin:0}\n.req .msg.err{color:#b3261e;font-weight:600}\n.req .msg.ok{color:var(--accent);font-weight:600}\n.num input,.num select{width:100%;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--sunk);font-family:var(--f-mono);font-size:15px;color:var(--ink)}\n.chips{display:flex;flex-wrap:wrap;gap:6px}\n.chip{border:1px solid var(--line);background:var(--sunk);border-radius:999px;padding:6px 12px;cursor:pointer;font-size:13.5px;font-weight:600;color:var(--ink)}\n.chip[aria-pressed=\"true\"]{background:var(--ink);border-color:var(--ink);color:var(--surface)}\n.chip small{font-weight:500;opacity:.75}\n.shapes{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}\n.shape{border:1px solid var(--line);background:var(--sunk);border-radius:9px;padding:8px;cursor:pointer;display:grid;gap:4px;justify-items:center;font-size:13px;font-weight:600;color:var(--ink)}\n.shape svg{width:64px;height:48px}\n.shape[aria-pressed=\"true\"]{border-color:var(--accent);background:var(--accent-soft)}\n.walltabs{display:flex;flex-wrap:wrap;gap:6px}\n.wt{border:1px solid var(--line);background:var(--sunk);border-radius:9px;padding:5px 9px 5px 5px;cursor:pointer;display:flex;gap:7px;align-items:center;font-weight:700;font-size:14px;color:var(--ink)}\n.wt i{width:22px;height:28px;border-radius:4px;display:block;background-size:cover;background-position:center;outline:1px solid rgba(0,0,0,.15)}\n.wt.off{opacity:.55}\n.wt.off i{background:repeating-linear-gradient(45deg,var(--line) 0 3px,transparent 3px 6px)!important}\n.wt[aria-pressed=\"true\"]{border-color:var(--accent);background:var(--accent-soft);box-shadow:inset 0 0 0 1px var(--accent)}\n.wallhead{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px;align-items:center}\n.wallhead h3{font:700 18px/1.2 var(--f-display);margin:0}\n.check{display:flex;gap:9px;align-items:center;font-size:14.5px;cursor:pointer}\n.check input{width:18px;height:18px;accent-color:var(--accent)}\n.decor-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(78px,1fr));gap:9px}\n.decor{border:0;background:none;padding:0;cursor:pointer;text-align:left;display:grid;gap:4px;font-size:12px;font-weight:600;line-height:1.2;color:var(--ink)}\n.decor .sw{aspect-ratio:3/4;width:100%;border-radius:7px;overflow:hidden;background:var(--sunk);outline:1px solid var(--line);outline-offset:-1px;display:grid;place-items:center;color:var(--muted);font-size:24px}\n.decor img{width:100%;height:100%;object-fit:cover;display:block}\n.decor[aria-pressed=\"true\"] .sw{outline:3px solid var(--accent);outline-offset:2px}\n.openings{display:grid;gap:8px}\n.opening{display:grid;grid-template-columns:96px repeat(4,minmax(0,1fr)) 34px;gap:6px;align-items:end;background:var(--sunk);border:1px solid var(--line);border-radius:8px;padding:8px}\n.opening .num{font-size:11.5px}\n.opening .num input,.opening .num select{padding:6px 7px;font-size:14px;background:var(--surface)}\n.opening button{height:34px;border:1px solid var(--line);background:var(--surface);border-radius:7px;cursor:pointer;font-size:16px;color:var(--ink)}\n@media (max-width:520px){.opening{grid-template-columns:repeat(2,minmax(0,1fr))}.opening button{grid-column:1/-1}}\n.elev{background:var(--sunk);border:1px solid var(--line);border-radius:8px;padding:8px;overflow-x:auto}\n.elev svg{display:block;width:100%;height:auto;min-width:280px}\n.note{font-size:13px;color:var(--muted);margin:0}\n.note.warn{color:var(--warn);font-weight:600}\n.next{display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap}\n.list{display:grid;border-top:1px solid var(--line)}\n.list\u003ediv{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:9px 0;border-bottom:1px solid var(--line);font-size:14.5px}\n.list .pr{font-family:var(--f-mono);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;min-width:6.5em;font-weight:600}\n.list .code{font-family:var(--f-mono);font-size:11.5px;color:var(--muted)}\n.totals{display:grid;gap:4px;justify-content:end;font-size:14.5px}\n.totals div{display:grid;grid-template-columns:auto 8em;gap:16px;text-align:right}\n.totals div span:last-child{font-family:var(--f-mono);font-variant-numeric:tabular-nums}\n.totals .grand{font:700 20px/1.3 var(--f-display);border-top:1px solid var(--line);padding-top:6px;margin-top:2px}\n.list .q{font-family:var(--f-mono);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}\n.list .sub{grid-column:1/-1;color:var(--muted);font-size:12.5px;margin-top:-6px}\n.tablewrap{overflow-x:auto}\ntable.walls{width:100%;border-collapse:collapse;font-size:13.5px}\ntable.walls th,table.walls td{text-align:left;padding:7px 8px 7px 0;border-bottom:1px solid var(--line);white-space:nowrap}\ntable.walls th{font-size:11.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);font-weight:600}\ntable.walls td.n{font-family:var(--f-mono);text-align:right}\n.big{display:flex;flex-wrap:wrap;gap:20px}\n.big div{display:grid}\n.big b{font:700 28px/1 var(--f-display);font-variant-numeric:tabular-nums}\n.big span{font-size:12.5px;color:var(--muted)}\n.toast{position:fixed;left:50%;bottom:calc(20px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);background:var(--ink);color:var(--surface);padding:10px 16px;border-radius:8px;font-weight:600;font-size:14px;opacity:0;pointer-events:none;transition:opacity .2s;z-index:5}\n.toast.on{opacity:1}\ntextarea.copybox{width:100%;min-height:150px;font:13px/1.45 var(--f-mono);padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--sunk);color:var(--ink)}\n.vh{position:absolute!important;width:1px;height:1px;opacity:0;overflow:hidden;clip:rect(0 0 0 0);pointer-events:none}\n.libcat{display:grid;gap:8px}\n.aitip{border:1px solid var(--accent);background:var(--accent-soft);border-radius:8px;padding:10px 12px;display:grid;gap:8px;font-size:14px;color:var(--ink)}\n.aitip p{margin:0}\n.ai{display:grid;gap:10px;border:1px solid var(--line);background:var(--sunk);border-radius:10px;padding:14px}\n.aiimg{display:block;width:100%;height:auto;border-radius:8px;background:#dfe3e1}\n.grid4{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}\n@media (max-width:520px){.grid4{grid-template-columns:repeat(2,minmax(0,1fr))}}\n.itemed .num input{background:var(--surface)}\n.swatches{display:flex;flex-wrap:wrap;gap:6px}\n.swatch{border:1px solid var(--line);background:var(--surface);border-radius:999px;padding:4px 10px 4px 5px;display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:12.5px;font-weight:600;color:var(--ink)}\n.swatch i{width:16px;height:16px;border-radius:50%;border:1px solid rgba(0,0,0,.2);display:block}\n.swatch[aria-pressed=\"true\"]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}\n.libgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:8px}\n.libitem{border:1px solid var(--line);background:var(--sunk);border-radius:9px;padding:9px 10px;cursor:pointer;text-align:left;display:grid;gap:2px;color:var(--ink)}\n.libitem b{font-size:13.5px}\n.libitem span{font-family:var(--f-mono);font-size:11.5px;color:var(--muted)}\n.libitem:hover{border-color:var(--accent)}\n.elevbox{background:var(--sunk);border:1px solid var(--line);border-radius:10px;padding:10px}\n#iElev{display:block;width:100%;height:auto;max-height:340px;touch-action:none;user-select:none}\n#iElev .ev-wall{fill:var(--surface);stroke:var(--muted);stroke-width:.8}\n#iElev .ev-floor{stroke:var(--ink);stroke-width:1.6}\n#iElev .ev-open{fill:var(--sunk);stroke:var(--muted);stroke-width:.6;stroke-dasharray:3 2}\n#iElev .ev-item{fill:var(--accent-soft);stroke:var(--accent);stroke-width:.8;cursor:grab}\n#iElev .ev-item.sel{fill:var(--accent);fill-opacity:.35;stroke-width:1.6}\n#iElev text{fill:var(--ink);font-family:var(--f-body,system-ui);pointer-events:none}\n#iElev .ev-dim{stroke:var(--accent);stroke-width:.6}\n#iElev .ev-dimt{fill:var(--accent);font-weight:700}\n.itemed{display:grid;gap:12px;background:var(--accent-soft);border:1px solid var(--accent);border-radius:10px;padding:12px}\n.itemed h3{font:700 17px/1.2 var(--f-display);margin:0}\nfooter.foot{margin-top:22px;font-size:12.5px;color:var(--muted);max-width:80ch}\n@media (prefers-reduced-motion:reduce){*{transition:none!important}}\n\u003c/style\u003e\n\n\u003cdiv class=\"app\"\u003e\n \u003cheader class=\"top\"\u003e\n \u003cdiv\u003e\n \u003cdiv class=\"eyebrow\"\u003eHB Composite · Aqua Composite\u003c/div\u003e\n \u003ch1\u003eRuimteplanner\u003c/h1\u003e\n \u003c/div\u003e\n \u003cp class=\"lede\"\u003eTeken je ruimte, van badkamer en toilet tot keuken of woonkamer. Kies per wand een product en decor, bekijk het resultaat in 3D en zie precies wat je nodig hebt.\u003c/p\u003e\n \u003c/header\u003e\n\n \u003cdiv class=\"layout\"\u003e\n \u003csection class=\"stage\" aria-label=\"3D-weergave\"\u003e\n \u003cdiv class=\"bar\"\u003e\n \u003cdiv class=\"seg\" role=\"group\" aria-label=\"Camera\"\u003e\n \u003cbutton id=\"vOver\" aria-pressed=\"true\" type=\"button\"\u003eOverzicht\u003c/button\u003e\n \u003cbutton id=\"vIn\" aria-pressed=\"false\" type=\"button\"\u003eVan binnen\u003c/button\u003e\n \u003cbutton id=\"v360\" aria-pressed=\"false\" type=\"button\"\u003e360° rondkijken\u003c/button\u003e\n \u003cbutton id=\"vWall\" aria-pressed=\"false\" type=\"button\"\u003eGekozen wand\u003c/button\u003e\n \u003c/div\u003e\n \u003cdiv class=\"status\" id=\"statusLine\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"viewer\" id=\"viewer\"\u003e\n \u003cdiv class=\"minimap\" id=\"minimap\" aria-label=\"Plattegrond, tik op een wand om hem te kiezen\"\u003e\u003c/div\u003e\n \u003cbutton class=\"btn small primary r360btn\" id=\"v360Render\" type=\"button\" hidden\u003eAI-render van dit standpunt\u003c/button\u003e\n \u003cdiv class=\"r360ctl\" id=\"r360ctl\" hidden\u003e\u003cbutton type=\"button\" data-z=\"1\" aria-label=\"Inzoomen\" title=\"Inzoomen\"\u003e+\u003c/button\u003e\u003cbutton type=\"button\" data-z=\"-1\" aria-label=\"Uitzoomen\" title=\"Uitzoomen\"\u003e−\u003c/button\u003e\u003cbutton type=\"button\" data-h=\"1\" aria-label=\"Camera hoger\" title=\"Camera hoger\"\u003e▲\u003c/button\u003e\u003cbutton type=\"button\" data-h=\"-1\" aria-label=\"Camera lager\" title=\"Camera lager\"\u003e▼\u003c/button\u003e\u003c/div\u003e\u003cdiv class=\"r360bar\" id=\"r360bar\" hidden\u003e\u003c/div\u003e\u003cdiv class=\"r360frame\" id=\"r360frame\" hidden aria-hidden=\"true\"\u003e\u003cspan\u003eKader AI-render\u003c/span\u003e\u003c/div\u003e\n \u003cspan class=\"hint\" id=\"viewHint\"\u003eSleep om te draaien · knijp of scroll om te zoomen · tik op een wand\u003c/span\u003e\n \u003c/div\u003e\n \u003c/section\u003e\n\n \u003csection class=\"panel\" aria-label=\"Samenstellen\"\u003e\n \u003cdiv class=\"steps\" role=\"tablist\"\u003e\n \u003cbutton role=\"tab\" data-step=\"1\" aria-selected=\"true\"\u003e\u003cspan class=\"n\"\u003e01\u003c/span\u003eRuimte\u003c/button\u003e\n \u003cbutton role=\"tab\" data-step=\"2\" aria-selected=\"false\"\u003e\u003cspan class=\"n\"\u003e02\u003c/span\u003eWanden\u003c/button\u003e\n \u003cbutton role=\"tab\" data-step=\"3\" aria-selected=\"false\"\u003e\u003cspan class=\"n\"\u003e03\u003c/span\u003eVloer\u003c/button\u003e\n \u003cbutton role=\"tab\" data-step=\"4\" aria-selected=\"false\"\u003e\u003cspan class=\"n\"\u003e04\u003c/span\u003eInrichting\u003c/button\u003e\n \u003cbutton role=\"tab\" data-step=\"5\" aria-selected=\"false\"\u003e\u003cspan class=\"n\"\u003e05\u003c/span\u003eOverzicht\u003c/button\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"step\" data-pane=\"1\"\u003e\n \u003ch2\u003eVorm en maten\u003c/h2\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eSoort ruimte\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"roomType\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eVorm van de ruimte\u003c/p\u003e\n \u003cdiv class=\"shapes\" id=\"shapes\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eMaten in cm\u003c/p\u003e\n \u003cp class=\"note\" id=\"shapeInfo\"\u003e\u003c/p\u003e\n \u003cdiv class=\"grid2\" id=\"params\"\u003e\u003c/div\u003e\n \u003cdiv class=\"chips\" id=\"mirrorBox\" style=\"margin-top:8px\"\u003e\u003cbutton class=\"chip\" id=\"mirrorBtn\" type=\"button\" aria-pressed=\"false\"\u003e⇋ Spiegelen (links ↔ rechts)\u003c/button\u003e\u003c/div\u003e\n \u003cp class=\"note warn\" id=\"shapeErr\" hidden\u003e\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv id=\"cabBox\"\u003e\n \u003cp class=\"field-label\"\u003eToiletcabines\u003c/p\u003e\n \u003cdiv class=\"chips\"\u003e\u003cbutton class=\"chip\" id=\"cabOn\" type=\"button\" aria-pressed=\"false\"\u003eToiletcabines plaatsen\u003c/button\u003e\u003c/div\u003e\n \u003cp class=\"note\" id=\"cabInfo\"\u003eVoor een toiletgroep: een rij cabines met tussenwanden tot het plafond. Per cabine kies je met deur (toilet) of open (urinoir). De tussenwanden worden extra wanden die je in stap 2 per kant bekleedt.\u003c/p\u003e\n \u003cdiv class=\"grid2\" id=\"cabParams\" hidden\u003e\u003c/div\u003e\n \u003cp class=\"note warn\" id=\"cabErr\" hidden\u003e\u003c/p\u003e\n \u003c/div\u003e\n \u003clabel class=\"num\" style=\"max-width:220px\"\u003ePlafondhoogte (cm)\u003cinput id=\"ceilH\" type=\"number\" min=\"150\" max=\"400\" step=\"1\"\u003e\u003c/label\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eWanden\u003c/p\u003e\n \u003cp class=\"note\" id=\"wallSummary\"\u003e\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv class=\"next\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cbutton class=\"btn primary\" data-go=\"2\" type=\"button\"\u003eVerder: wanden inrichten\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"step\" data-pane=\"2\" hidden\u003e\n \u003ch2\u003ePer wand inrichten\u003c/h2\u003e\n \u003cdiv class=\"walltabs\" id=\"wallTabs\"\u003e\u003c/div\u003e\n \u003cdiv class=\"wallhead\"\u003e\n \u003ch3 id=\"wallTitle\"\u003eWand 1\u003c/h3\u003e\n \u003clabel class=\"check\"\u003e\u003cinput type=\"checkbox\" id=\"wOn\"\u003e Deze wand bekleden\u003c/label\u003e\n \u003c/div\u003e\n \u003cdiv id=\"wallEditor\"\u003e\n \u003cdiv style=\"display:grid;gap:16px\"\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eProduct\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"wProd\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv id=\"wSizeBox\" hidden\u003e\n \u003cp class=\"field-label\"\u003eFormaat\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"wSize\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\" id=\"wDecorLabel\"\u003eDecor\u003c/p\u003e\n \u003cdiv class=\"decor-grid\" id=\"wDecor\"\u003e\u003c/div\u003e\n \u003cdiv class=\"row\" style=\"margin-top:10px\"\u003e\n \u003cbutton class=\"btn small\" id=\"copyAll\" type=\"button\"\u003eDit product en decor op alle wanden\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eHoogte bekleding\u003c/p\u003e\n \u003cdiv class=\"row\"\u003e\n \u003cdiv class=\"chips\" id=\"wCover\"\u003e\n \u003cbutton class=\"chip\" data-cover=\"full\" type=\"button\"\u003eTot plafond\u003c/button\u003e\n \u003cbutton class=\"chip\" data-cover=\"120\" type=\"button\"\u003eHalfhoog 120 cm\u003c/button\u003e\n \u003cbutton class=\"chip\" data-cover=\"custom\" type=\"button\"\u003eEigen hoogte\u003c/button\u003e\n \u003c/div\u003e\n \u003clabel class=\"num\" id=\"wCoverNumBox\" style=\"width:110px\"\u003e\u003cinput id=\"wCoverNum\" type=\"number\" min=\"20\" max=\"400\" step=\"1\" aria-label=\"Hoogte bekleding in cm\"\u003e\u003c/label\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eDeuren, ramen en nissen in deze wand\u003c/p\u003e\n \u003cdiv class=\"openings\" id=\"openings\"\u003e\u003c/div\u003e\n \u003cdiv class=\"row\" style=\"margin-top:8px\"\u003e\n \u003cbutton class=\"btn small\" data-add=\"deur\" type=\"button\"\u003e+ Deur\u003c/button\u003e\n \u003cbutton class=\"btn small\" data-add=\"raam\" type=\"button\"\u003e+ Raam\u003c/button\u003e\n \u003cbutton class=\"btn small\" data-add=\"nis\" type=\"button\"\u003e+ Nis / uitsparing\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eWandaanzicht en indeling\u003c/p\u003e\n \u003cdiv class=\"elev\" id=\"elev\"\u003e\u003c/div\u003e\n \u003cp class=\"note\" id=\"wallCalc\" style=\"margin-top:8px\"\u003e\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv class=\"next\"\u003e\u003cbutton class=\"btn\" id=\"prevWall\" type=\"button\"\u003e‹ Vorige wand\u003c/button\u003e\u003cbutton class=\"btn primary\" id=\"nextWall\" type=\"button\"\u003eVolgende wand ›\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"step\" data-pane=\"3\" hidden\u003e\n \u003ch2\u003eVloer\u003c/h2\u003e\n \u003clabel class=\"check\"\u003e\u003cinput type=\"checkbox\" id=\"fOn\"\u003e De vloer bekleden\u003c/label\u003e\n \u003cdiv id=\"floorEditor\" style=\"display:grid;gap:16px\"\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eProduct\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"fProd\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv id=\"fSizeBox\" hidden\u003e\n \u003cp class=\"field-label\"\u003eFormaat\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"fSize\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\" id=\"fDecorLabel\"\u003eDecor\u003c/p\u003e\n \u003cdiv class=\"decor-grid\" id=\"fDecor\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eLeggen\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"fMount\"\u003e\n \u003cbutton class=\"chip\" data-fm=\"zwevend\" type=\"button\"\u003eZwevend op ondervloer\u003c/button\u003e\n \u003cbutton class=\"chip\" data-fm=\"lijm\" type=\"button\"\u003eVolledig verlijmd\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv id=\"fUnderBox\"\u003e\n \u003cp class=\"field-label\"\u003eOndervloer\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"fUnder\"\u003e\n \u003cbutton class=\"chip\" data-under=\"xpo\" type=\"button\"\u003eAC XPO Underlay \u003csmall\u003e€ 5,30 / m²\u003c/small\u003e\u003c/button\u003e\n \u003cbutton class=\"chip\" data-under=\"supreme\" type=\"button\"\u003eAC Supreme Underlay \u003csmall\u003e€ 6,72 / m², hoogste druksterkte\u003c/small\u003e\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cp class=\"note\" id=\"floorCalc\"\u003e\u003c/p\u003e\n \u003cdiv class=\"next\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cbutton class=\"btn primary\" data-go=\"4\" type=\"button\"\u003eVerder: inrichting\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"step\" data-pane=\"4\" hidden\u003e\n \u003ch2\u003eInrichting\u003c/h2\u003e\n \u003cp class=\"note\"\u003eWerk wand per wand: kies een wand, tik daarna op een object in de bibliotheek. Het komt op die wand. Schuif het in het wandaanzicht naar de juiste plek, of typ de afstanden in.\u003c/p\u003e\n \u003cdiv\u003e\u003cp class=\"field-label\"\u003eWand\u003c/p\u003e\u003cdiv class=\"chips\" id=\"iWalls\"\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv class=\"elevbox\"\u003e\u003csvg id=\"iElev\" role=\"img\" aria-label=\"Wandaanzicht\"\u003e\u003c/svg\u003e\u003cp class=\"note\" id=\"iElevNote\"\u003e\u003c/p\u003e\u003c/div\u003e\n \u003cdiv class=\"itemed\" id=\"itemEd\" hidden\u003e\u003c/div\u003e\n \u003cdiv id=\"placedBox\"\u003e\u003cp class=\"field-label\"\u003eIn de ruimte\u003c/p\u003e\u003cdiv class=\"chips\" id=\"placed\"\u003e\u003c/div\u003e\u003c/div\u003e\n \u003cdiv id=\"lib\" style=\"display:grid;gap:16px\"\u003e\u003c/div\u003e\n \u003cp class=\"note\"\u003eDe inrichting is alleen ter illustratie en komt niet op de bestellijst.\u003c/p\u003e\n \u003cdiv class=\"next\"\u003e\u003cspan\u003e\u003c/span\u003e\u003cbutton class=\"btn primary\" data-go=\"5\" type=\"button\"\u003eBekijk overzicht\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n\n \u003cdiv class=\"step\" data-pane=\"5\" hidden\u003e\n \u003ch2\u003eOverzicht en bestellijst\u003c/h2\u003e\n \u003cdiv class=\"ai\"\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eFotorealistische impressie\u003c/p\u003e\n \u003cp class=\"note\"\u003eDe AI maakt een foto-echte impressie van je ontwerp, met de echte decors.\u003c/p\u003e\n \u003c/div\u003e\n \u003cdiv class=\"aitip\" role=\"note\"\u003e\n \u003cp\u003e\u003cb\u003eStel eerst het beeld voor de render in.\u003c/b\u003e Klik boven de 3D-weergave op \u003cb\u003e360° rondkijken\u003c/b\u003e, draai en zoom naar het beeld dat je wilt en klik dan op \u003cb\u003eAI-render van dit standpunt\u003c/b\u003e.\u003c/p\u003e\n \u003cdiv class=\"row\"\u003e\u003cbutton class=\"btn small primary\" data-go360 type=\"button\"\u003eNaar 360° rondkijken\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"aitip\" id=\"aiAsk\" hidden\u003e\n \u003cp\u003eJe hebt nog geen standpunt gekozen in \u003cb\u003e360° rondkijken\u003c/b\u003e. Dan maakt de planner een foto vanuit Overzicht, met zicht op de wand waar je in de 3D-weergave naar kijkt.\u003c/p\u003e\n \u003cdiv class=\"row\"\u003e\u003cbutton class=\"btn small primary\" data-go360 type=\"button\"\u003eNaar 360° rondkijken\u003c/button\u003e\u003cbutton class=\"btn small\" id=\"aiAskGo\" type=\"button\"\u003eToch renderen vanuit Overzicht\u003c/button\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"row\"\u003e\u003cbutton class=\"btn primary\" id=\"aiBtn\" type=\"button\"\u003eMaak AI-render\u003c/button\u003e\u003c/div\u003e\n \u003cp class=\"note\" id=\"aiStatus\" role=\"status\" aria-live=\"polite\"\u003e\u003c/p\u003e\n \u003cdiv id=\"aiOut\" hidden\u003e\n \u003cdiv class=\"seg\" role=\"group\" aria-label=\"Weergave\" id=\"aiToggle\" style=\"margin-bottom:8px\"\u003e\u003cbutton data-ai=\"after\" type=\"button\" aria-pressed=\"true\"\u003eAI-render\u003c/button\u003e\u003cbutton data-ai=\"before\" type=\"button\" aria-pressed=\"false\"\u003e3D-beeld\u003c/button\u003e\u003c/div\u003e\n \u003cimg id=\"aiAfter\" alt=\"Fotorealistische AI-render van de ruimte\" class=\"aiimg\" hidden\u003e\n \u003cimg id=\"aiBefore\" alt=\"Momentopname van de 3D-weergave\" class=\"aiimg\"\u003e\n \u003cdiv class=\"row\" style=\"margin-top:8px\"\u003e\u003cbutton class=\"btn\" id=\"aiSave\" type=\"button\"\u003eRender opslaan\u003c/button\u003e\u003c/div\u003e\n \u003cp class=\"note\" style=\"margin-top:6px\"\u003eEen AI-render is een impressie; kleine afwijkingen in vormen en details zijn mogelijk.\u003c/p\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"big\" id=\"bigNums\"\u003e\u003c/div\u003e\n \u003cdiv class=\"tablewrap\"\u003e\u003ctable class=\"walls\" id=\"wallTable\"\u003e\u003c/table\u003e\u003c/div\u003e\n \u003cdiv class=\"grid2\"\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003ePanelen bevestigen\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"mount\"\u003e\n \u003cbutton class=\"chip\" data-mount=\"lijm\" type=\"button\"\u003ePolymeerlijm\u003c/button\u003e\n \u003cbutton class=\"chip\" data-mount=\"schroef\" type=\"button\"\u003eSchroeven\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eAfwerkprofielen\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"prof\"\u003e\n \u003cbutton class=\"chip\" data-prof=\"PVC\" type=\"button\"\u003ePVC\u003c/button\u003e\n \u003cbutton class=\"chip\" data-prof=\"Aluminium\" type=\"button\"\u003eAluminium\u003c/button\u003e\n \u003c/div\u003e\n \u003cdiv class=\"chips\" id=\"profColor\" style=\"margin-top:6px\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eLevering\u003c/p\u003e\n \u003cdiv class=\"chips\" id=\"lev\"\u003e\n \u003cbutton class=\"chip\" data-lev=\"bezorgen\" type=\"button\"\u003eBezorgen\u003c/button\u003e\n \u003cbutton class=\"chip\" data-lev=\"afhalen\" type=\"button\"\u003eZelf afhalen op afspraak\u003c/button\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv\u003e\n \u003cp class=\"field-label\"\u003eBestellijst\u003c/p\u003e\n \u003cdiv class=\"list\" id=\"matList\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv class=\"totals\" id=\"totals\"\u003e\u003c/div\u003e\n \u003cp class=\"note\" id=\"priceNote\"\u003e\u003c/p\u003e\n \u003cdiv class=\"row\"\u003e\n \u003cbutton class=\"btn primary\" id=\"reqBtn\" type=\"button\" aria-expanded=\"false\" aria-controls=\"reqForm\"\u003eOfferte aanvragen\u003c/button\u003e\n \u003cbutton class=\"btn\" id=\"pdfBtn\" type=\"button\"\u003eDownload pdf\u003c/button\u003e\n \u003cbutton class=\"btn\" id=\"copyBtn\" type=\"button\"\u003eKopieer overzicht\u003c/button\u003e\n \u003c/div\u003e\n \u003cform class=\"req\" id=\"reqForm\" hidden novalidate\u003e\n \u003ch3\u003eOfferte aanvragen\u003c/h3\u003e\n \u003cp class=\"note\"\u003eWe sturen je ontwerp als pdf mee met je aanvraag en nemen zo snel mogelijk contact met je op.\u003c/p\u003e\n \u003cdiv class=\"grid2\"\u003e\n \u003clabel class=\"num\"\u003eNaam *\u003cinput id=\"rqNaam\" type=\"text\" autocomplete=\"name\" required maxlength=\"120\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eE-mail *\u003cinput id=\"rqMail\" type=\"email\" autocomplete=\"email\" required maxlength=\"160\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eTelefoon\u003cinput id=\"rqTel\" type=\"tel\" autocomplete=\"tel\" maxlength=\"40\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003ePostcode en plaats\u003cinput id=\"rqPlaats\" type=\"text\" autocomplete=\"address-level2\" maxlength=\"120\"\u003e\u003c/label\u003e\n \u003c/div\u003e\n \u003clabel class=\"num\"\u003eOpmerking of vraag\u003ctextarea id=\"rqOpm\" maxlength=\"2000\"\u003e\u003c/textarea\u003e\u003c/label\u003e\n \u003cp class=\"note\"\u003eWe gebruiken je gegevens alleen om je aanvraag te beantwoorden.\u003c/p\u003e\n \u003cp class=\"msg\" id=\"rqMsg\" role=\"status\" aria-live=\"polite\"\u003e\u003c/p\u003e\n \u003cdiv class=\"row\"\u003e\n \u003cbutton class=\"btn primary\" id=\"rqSend\" type=\"submit\"\u003eAanvraag versturen\u003c/button\u003e\n \u003cbutton class=\"btn\" id=\"rqCancel\" type=\"button\"\u003eAnnuleren\u003c/button\u003e\n \u003c/div\u003e\n \u003c/form\u003e\n \u003ctextarea class=\"copybox\" id=\"copyBox\" readonly aria-label=\"Overzicht als tekst\"\u003e\u003c/textarea\u003e\n \u003c/div\u003e\n \u003c/section\u003e\n \u003c/div\u003e\n \u003cfooter class=\"foot\"\u003eDe 3D-weergave is een impressie; kleur en structuur van het echte product kunnen afwijken door scherm en belichting. Vraag gerust een staal aan via \u003cspan class=\"mono\"\u003einfo@hbcomposite.com\u003c/span\u003e.\u003c/footer\u003e\n\u003c/div\u003e\n\u003cinput type=\"file\" id=\"customIn\" class=\"vh\" accept=\"image/*\" tabindex=\"-1\" aria-hidden=\"true\"\u003e\n\u003cdiv class=\"toast\" id=\"toast\" role=\"status\" aria-live=\"polite\"\u003e\u003c/div\u003e\n\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/three@0.147.0/examples/js/controls/OrbitControls.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n(()=\u003e{\n\"use strict\";\n/* ================= CATALOGUS (bron: hbcomposite.com) ================= */\nconst UP=\"https://hbcomposite.com/wp-content/uploads/\";\nconst PDF_UPLOAD_URL=\"/wp-json/ruimteplanner/v1/aanvraag-pdf\"; // WordPress-plugin \"Ruimteplanner AI-render\" (vanaf 1.2.0)\nconst PRICE={\"versie\":\"prijslijst 07-2026\",\"franco\":500.0,\"levering\":75.0,\"series\":{\"panel\":{\"pakM2\":2.511,\"stuks\":2,\"minpak\":1,\"verlies\":5,\"items\":{\"Anthracite Stone\":{\"c\":\"ACP0100\",\"eh\":39.9,\"pak\":100.2},\"Concrete Light\":{\"c\":\"ACP0101\",\"eh\":39.9,\"pak\":100.2},\"Sand Beige\":{\"c\":\"ACP0102\",\"eh\":39.9,\"pak\":100.2},\"Marble Black\":{\"c\":\"ACP0103\",\"eh\":39.9,\"pak\":100.2},\"Marble Grey\":{\"c\":\"ACP0104\",\"eh\":39.9,\"pak\":100.2},\"Marble Beige\":{\"c\":\"ACP0105\",\"eh\":39.9,\"pak\":100.2},\"Concrete Dark\":{\"c\":\"ACP0107\",\"eh\":39.9,\"pak\":100.2},\"Concrete Black\":{\"c\":\"ACP0108\",\"eh\":39.9,\"pak\":100.2},\"Limestone\":{\"c\":\"ACP0110\",\"eh\":39.9,\"pak\":100.2},\"Marmo Luna\":{\"c\":\"ACP0112\",\"eh\":39.9,\"pak\":100.2},\"Matt White\":{\"c\":\"ACP0400\",\"eh\":39.9,\"pak\":100.2},\"Timeless Oak\":{\"c\":\"ACP0600\",\"eh\":39.9,\"pak\":100.2},\"Residential Oak\":{\"c\":\"ACP0605\",\"eh\":39.9,\"pak\":100.2},\"Eternal Oak\":{\"c\":\"ACP0606\",\"eh\":39.9,\"pak\":100.2},\"Grigio Imperiale\":{\"c\":\"ACP0111\",\"eh\":45.9,\"pak\":115.27},\"Marmo Luce\":{\"c\":\"ACP0113\",\"eh\":45.9,\"pak\":115.27},\"Marmo Notte\":{\"c\":\"ACP0114\",\"eh\":45.9,\"pak\":115.27},\"Platinum Grey\":{\"c\":\"ACP0402\",\"eh\":45.9,\"pak\":115.27},\"Light Grey\":{\"c\":\"ACP0403\",\"eh\":45.9,\"pak\":115.27},\"Graphite Black\":{\"c\":\"ACP0404\",\"eh\":45.9,\"pak\":115.27}}},\"board\":{\"pakM2\":2.514,\"stuks\":1,\"minpak\":1,\"verlies\":5,\"items\":{\"Concrete Light\":{\"c\":\"ACB0101\",\"eh\":53.9,\"pak\":135.5},\"Sand Beige\":{\"c\":\"ACB0102\",\"eh\":53.9,\"pak\":135.5},\"Marble Beige\":{\"c\":\"ACB0105\",\"eh\":53.9,\"pak\":135.5},\"Marmo Luna\":{\"c\":\"ACB0112\",\"eh\":53.9,\"pak\":135.5},\"Matt White\":{\"c\":\"ACB0400\",\"eh\":53.9,\"pak\":135.5},\"Eternal Oak\":{\"c\":\"ACB0606\",\"eh\":53.9,\"pak\":135.5},\"Marmo Luce\":{\"c\":\"ACB0113\",\"eh\":59.9,\"pak\":150.58},\"Marmo Notte\":{\"c\":\"ACB0114\",\"eh\":59.9,\"pak\":150.58}}},\"tileS\":{\"pakM2\":2.23,\"stuks\":12,\"minpak\":1,\"verlies\":5,\"items\":{\"Anthracite Stone\":{\"c\":\"ACT0100\",\"eh\":35.9,\"pak\":80.06},\"Concrete Light\":{\"c\":\"ACT0101\",\"eh\":35.9,\"pak\":80.06},\"Sand Beige\":{\"c\":\"ACT0102\",\"eh\":35.9,\"pak\":80.06},\"Marble Black\":{\"c\":\"ACT0103\",\"eh\":35.9,\"pak\":80.06},\"Marble Grey\":{\"c\":\"ACT0104\",\"eh\":35.9,\"pak\":80.06},\"Marble Beige\":{\"c\":\"ACT0105\",\"eh\":35.9,\"pak\":80.06},\"Sandstone Lines\":{\"c\":\"ACT0106\",\"eh\":35.9,\"pak\":80.06},\"Concrete Dark\":{\"c\":\"ACT0107\",\"eh\":35.9,\"pak\":80.06},\"Concrete Black\":{\"c\":\"ACT0108\",\"eh\":35.9,\"pak\":80.06},\"Marmo Luna\":{\"c\":\"ACT0112\",\"eh\":35.9,\"pak\":80.06},\"Matt White\":{\"c\":\"ACT0400\",\"eh\":35.9,\"pak\":80.06},\"Grigio Imperiale\":{\"c\":\"ACT0111\",\"eh\":35.9,\"pak\":80.06}}},\"tileL\":{\"pakM2\":2.26,\"stuks\":5,\"minpak\":1,\"verlies\":5,\"items\":{\"Anthracite Stone\":{\"c\":\"ACTXL0100\",\"eh\":39.0,\"pak\":88.14},\"Concrete Light\":{\"c\":\"ACTXL0101\",\"eh\":39.0,\"pak\":88.14},\"Sand Beige\":{\"c\":\"ACTXL0102\",\"eh\":39.0,\"pak\":88.14},\"Marble Black\":{\"c\":\"ACTXL0103\",\"eh\":39.0,\"pak\":88.14},\"Marble Grey\":{\"c\":\"ACTXL0104\",\"eh\":39.0,\"pak\":88.14},\"Marble Beige\":{\"c\":\"ACTXL0105\",\"eh\":39.0,\"pak\":88.14},\"Sandstone Lines\":{\"c\":\"ACTXL0106\",\"eh\":39.0,\"pak\":88.14},\"Concrete Dark\":{\"c\":\"ACTXL0107\",\"eh\":39.0,\"pak\":88.14},\"Concrete Black\":{\"c\":\"ACTXL0108\",\"eh\":39.0,\"pak\":88.14},\"Limestone\":{\"c\":\"ACTXL0110\",\"eh\":39.0,\"pak\":88.14},\"Marmo Luna\":{\"c\":\"ACTXL0112\",\"eh\":39.0,\"pak\":88.14},\"Matt White\":{\"c\":\"ACTXL0400\",\"eh\":39.0,\"pak\":88.14},\"Grigio Imperiale\":{\"c\":\"ACTXL0111\",\"eh\":45.0,\"pak\":101.7},\"Marmo Luce\":{\"c\":\"ACTXL0113\",\"eh\":45.0,\"pak\":101.7},\"Marmo Notte\":{\"c\":\"ACTXL0114\",\"eh\":45.0,\"pak\":101.7},\"Marmo Oro\":{\"c\":\"ACTXL0115\",\"eh\":45.0,\"pak\":101.7}}},\"plank\":{\"pakM2\":2.23,\"stuks\":8,\"minpak\":1,\"verlies\":5,\"items\":{\"Timeless Oak\":{\"c\":\"ACF0600\",\"eh\":29.9,\"pak\":66.68},\"Residential Oak\":{\"c\":\"ACF0605\",\"eh\":29.9,\"pak\":66.68},\"Eternal Oak\":{\"c\":\"ACF0606\",\"eh\":29.9,\"pak\":66.68},\"Traditional Oak\":{\"c\":\"ACF0607\",\"eh\":29.9,\"pak\":66.68},\"Bohemian Oak\":{\"c\":\"ACF0608\",\"eh\":29.9,\"pak\":66.68},\"Heritage Oak\":{\"c\":\"ACF0609\",\"eh\":29.9,\"pak\":66.68},\"Majestic Oak\":{\"c\":\"ACF0610\",\"eh\":29.9,\"pak\":66.68},\"Savannah Oak\":{\"c\":\"ACF0611\",\"eh\":29.9,\"pak\":66.68}}},\"plankSup\":{\"pakM2\":2.23,\"stuks\":8,\"minpak\":1,\"verlies\":5,\"items\":{\"Oak Dream\":{\"c\":\"ACSF0601\",\"eh\":37.89,\"pak\":84.5},\"Oak Paradise\":{\"c\":\"ACSF0602\",\"eh\":37.89,\"pak\":84.5},\"Oak Wonder\":{\"c\":\"ACSF0603\",\"eh\":37.89,\"pak\":84.5},\"Oak Glory\":{\"c\":\"ACSF0604\",\"eh\":37.89,\"pak\":84.5}}},\"herring\":{\"pakM2\":2.03,\"stuks\":18,\"minpak\":1,\"verlies\":10,\"items\":{\"Timeless Oak\":{\"c\":\"ACHF0600-A\",\"eh\":37.9,\"pak\":76.94},\"Residential Oak\":{\"c\":\"ACHF0605-A\",\"eh\":37.9,\"pak\":76.94},\"Eternal Oak\":{\"c\":\"ACHF0606-A\",\"eh\":37.9,\"pak\":76.94},\"Traditional Oak\":{\"c\":\"ACHF0607-A\",\"eh\":37.9,\"pak\":76.94}}},\"herringSup\":{\"pakM2\":1.8,\"stuks\":16,\"minpak\":1,\"verlies\":10,\"items\":{\"Oak Dream\":{\"c\":\"ACHF0601\",\"eh\":41.9,\"pak\":75.42},\"Oak Paradise\":{\"c\":\"ACHF0602\",\"eh\":41.9,\"pak\":75.42},\"Oak Wonder\":{\"c\":\"ACHF0603\",\"eh\":41.9,\"pak\":75.42},\"Oak Glory\":{\"c\":\"ACHF0604\",\"eh\":41.9,\"pak\":75.42}}},\"flow\":{\"pakM2\":2.511,\"stuks\":2,\"minpak\":2,\"verlies\":5,\"items\":{\"Granito Muro\":{\"c\":\"QD01.3.003.01\",\"eh\":145.0,\"pak\":364.1},\"Terrazzo Beige\":{\"c\":\"QD01.3.038.01\",\"eh\":145.0,\"pak\":364.1},\"Terrazzo Grey\":{\"c\":\"QD01.3.039.01\",\"eh\":145.0,\"pak\":364.1},\"Bianco Spina\":{\"c\":\"QD02.2.017.01\",\"eh\":145.0,\"pak\":364.1},\"Iron Patina\":{\"c\":\"QD04.4.017.01\",\"eh\":145.0,\"pak\":364.1}}},\"horizon\":{\"eh\":145.0,\"items\":{\"Bamboo Leaves\":{\"set4\":{\"c\":\"HBC0.20.01.01.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.01.02\",\"pak\":1092.44,\"m2\":7.534}},\"Forest Waterfall\":{\"set4\":{\"c\":\"HBC0.20.01.02.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.02.02\",\"pak\":1092.44,\"m2\":7.534}},\"Wild Grass\":{\"set4\":{\"c\":\"HBC0.20.01.03.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.03.02\",\"pak\":1092.44,\"m2\":7.534}},\"Jungle Stream\":{\"set4\":{\"c\":\"HBC0.20.01.04.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.04.02\",\"pak\":1092.44,\"m2\":7.534}},\"Ice Cave\":{\"set4\":{\"c\":\"HBC0.20.01.05.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.05.02\",\"pak\":1092.44,\"m2\":7.534}},\"Dandelion\":{\"set4\":{\"c\":\"HBC0.20.01.07.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.07.02\",\"pak\":1092.44,\"m2\":7.534}},\"Sunlight in the Forest\":{\"set4\":{\"c\":\"HBC0.20.01.08.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.08.02\",\"pak\":1092.44,\"m2\":7.534}},\"Vertical Garden\":{\"set4\":{\"c\":\"HBC0.20.01.09.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.01.09.02\",\"pak\":1092.44,\"m2\":7.534}},\"Sand Dunes\":{\"set4\":{\"c\":\"HBC0.20.02.02.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.02.02.02\",\"pak\":1092.44,\"m2\":7.534}},\"Ocean Wave\":{\"set4\":{\"c\":\"HBC0.20.02.03.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.02.03.02\",\"pak\":1092.44,\"m2\":7.534}},\"Tropical Pier\":{\"set4\":{\"c\":\"HBC0.20.02.04.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.02.04.02\",\"pak\":1092.44,\"m2\":7.534}},\"Red Rocks\":{\"set4\":{\"c\":\"HBC0.20.02.05.01\",\"pak\":728.2,\"m2\":5.022},\"set6\":{\"c\":\"HBC0.20.02.05.02\",\"pak\":1092.44,\"m2\":7.534}}}}},\"lijm\":{\"ACHPL0029\":{\"code\":\"ACHPL0029\",\"naam\":\"Hybride polymeerlijm — koker 290 ml\",\"sub\":\"verbruik ca. 1 koker per 2 m²\",\"prijs\":13.75,\"verbruik\":0.5},\"ACHPL5000\":{\"code\":\"ACHPL5000\",\"naam\":\"Hybride polymeerlijm — emmer 5 kg\",\"sub\":\"volvlaks met lijmkam, 0,8 kg per m²\",\"prijs\":98.94,\"verbruik\":0.16}},\"onder\":{\"OVSPC001\":{\"code\":\"OVSPC001\",\"naam\":\"AC Supreme Underlay\",\"sub\":\"rol van 15 m² — hoogste druksterkte · € 6,72 per m²\",\"prijs\":100.8,\"per\":15.0},\"OVSPC002\":{\"code\":\"OVSPC002\",\"naam\":\"AC XPO Underlay\",\"sub\":\"pak van 14,85 m² · € 5,30 per m²\",\"prijs\":78.7,\"per\":14.85}},\"pvc\":{\"EBIHP001\":11.88,\"EBIHP002\":11.88,\"EBIHP003\":11.88,\"EBIHP004\":12.48,\"TBIHP001\":17.3,\"TBIHP002\":17.3,\"TBIHP003\":17.3,\"TBIHP004\":22.4,\"EBUHP001\":11.88,\"EBUHP002\":11.88,\"EBUHP003\":11.88,\"EBUHP004\":12.48,\"EUP001\":9,\"EUP002\":9,\"EUP003\":9,\"EUP004\":9.98},\"alu\":{\"ABIP001\":14.95,\"ABIP002\":14.95,\"ABIP003\":14.95,\"ABIP004\":14.95,\"ABULP001\":12.5,\"ABULP002\":12.5,\"ABULP003\":12.5,\"ABULP004\":12.5,\"ABUSP001\":9.95,\"ABUSP002\":9.95,\"ABUSP003\":9.95,\"ABUSP004\":9.95,\"ASP001\":12.5,\"ASP002\":12.5,\"ASP003\":12.5,\"ASP004\":12.5}};\n\n/* ---------- prijzen (consumentenadviesprijzen incl. btw) ---------- */\nconst PROF={\n PVC:{len:260,src:\"pvc\",colors:[[\"Wit\",\"001\"],[\"Zwart\",\"002\"],[\"Grijs\",\"003\"],[\"Aluminium-look\",\"004\"]],code:{binnen:c=\u003e\"EBIHP\"+c,buiten:c=\u003e\"EBUHP\"+c,u:c=\u003e\"EUP\"+c},\n label:{binnen:\"PVC binnenhoek 2600 mm\",buiten:\"PVC buitenhoek 2600 mm\",u:\"PVC U-profiel 2600 mm\"}},\n Aluminium:{len:270,src:\"alu\",colors:[[\"Mat wit\",\"001\"],[\"Mat zwart\",\"002\"],[\"Zilver brush\",\"003\"],[\"Champagne\",\"004\"]],code:{binnen:c=\u003e\"ABIP\"+c,buiten:c=\u003e\"ABULP\"+c,u:c=\u003e\"ASP\"+c},\n label:{binnen:\"Aluminium binnenhoek 2700 mm\",buiten:\"Aluminium buitenhoek 10 × 10 mm, 2700 mm\",u:\"Aluminium startprofiel 2700 mm\"}}\n};\nconst eur=v=\u003e\"€ \"+v.toLocaleString(\"nl-NL\",{minimumFractionDigits:2,maximumFractionDigits:2});\nconst normName=n=\u003eString(n).toLowerCase().replace(\"imperale\",\"imperiale\");\nfunction findItem(sk,decor){ const it=PRICE.series[sk]\u0026\u0026PRICE.series[sk].items; if(!it) return null; const k=Object.keys(it).find(n=\u003enormName(n)===normName(decor)); return k?it[k]:null; }\nfunction seriesKey(kind,prodKey,size,decor){\n if(prodKey===\"tile\") return size===\"s\"?\"tileS\":\"tileL\";\n if(kind===\"wall\"){ if(prodKey===\"digi\"){ if(decor===\"Eigen ontwerp\") return null; return findItem(\"horizon\",decor)?\"horizon\":\"flow\"; } return prodKey; }\n return prodKey;\n}\nfunction availDecors(kind,prodKey,size){ const p=(kind===\"wall\"?WALL:FLOOR)[prodKey];\n return p.decors.filter(d=\u003e{ const sk=seriesKey(kind,prodKey,size,d.name); return sk\u0026\u0026findItem(sk,d.name); }); }\nfunction fixDecors(){ // decor moet in de prijslijst bestaan voor dit product en formaat\n S.walls.forEach(w=\u003e{ if(w.decor===\"Eigen ontwerp\"\u0026\u0026WALL[w.prod].custom) return; if(!w.decor) return; const l=availDecors(\"wall\",w.prod,w.size); if(l.length\u0026\u0026!l.some(d=\u003ed.name===w.decor)) w.decor=null; });\n const F=S.floor, l=availDecors(\"floor\",F.prod,F.size); if(F.decor\u0026\u0026l.length\u0026\u0026!l.some(d=\u003ed.name===F.decor)) F.decor=null;\n}\nfunction horizonSets(L){ let cols=Math.ceil(L/46.7-1e-6), s4=0, s6=0; while(cols\u003e6){ s6++; cols-=6; } if(cols\u003e0){ if(cols\u003c=4) s4++; else s6++; } return {s4,s6}; }\nfunction wallPrice(c){ // indicatie per wand\n if(!c.cover) return null; const sk=seriesKey(\"wall\",c.w.prod,c.w.size,c.w.decor); if(!sk) return null;\n if(sk===\"horizon\"){ const it=findItem(\"horizon\",c.w.decor), h=horizonSets(c.gw.L); return h.s4*(it.set4?it.set4.pak:0)+h.s6*(it.set6?it.set6.pak:0); }\n const se=PRICE.series[sk], it=findItem(sk,c.w.decor); if(!it) return null;\n if(c.p.layout===\"tiles\") return c.net*(1+se.verlies/100)*it.eh;\n return c.count/se.stuks*it.pak;\n}\nfunction priceAll(t){\n const out=[]; let sub=0, onRequest=false;\n const add=(naam,code,qty,unit,unitPrice,detail)=\u003e{ const tot=unitPrice==null?null:qty*unitPrice; if(tot==null) onRequest=true; else sub+=tot; out.push({naam,code,qty,unit,unitPrice,tot,detail}); };\n // wandbekleding\n const groups=new Map();\n t.wc.forEach(c=\u003e{ if(!c.cover) return; const sk=seriesKey(\"wall\",c.w.prod,c.w.size,c.w.decor), k=(sk||\"custom\")+\"|\"+c.w.decor;\n const g=groups.get(k)||{sk,c0:c,decor:c.w.decor,pieces:0,m2:0,walls:[],s4:0,s6:0}; g.pieces+=c.count; g.m2+=c.net; g.walls.push(c.gw.i+1);\n if(sk===\"horizon\"){ const h=horizonSets(c.gw.L); g.s4+=h.s4; g.s6+=h.s6; } groups.set(k,g); });\n groups.forEach(g=\u003e{ const p=g.c0.p, sz=g.c0.sz, wl=`wand ${g.walls.join(\", \")}`;\n if(!g.sk){ add(`${p.name} — eigen ontwerp`,\"\",g.pieces,\"panelen\",null,`${fmt(g.m2)} m² · ${wl} · prijs op aanvraag na controle van de afbeelding`); return; }\n if(g.sk===\"horizon\"){ const it=findItem(\"horizon\",g.decor);\n if(g.s4\u0026\u0026it.set4) add(`Digi-Panels Horizon \"${g.decor}\" — set van 4 panelen`,it.set4.c,g.s4,\"set\",it.set4.pak,`${fmt(it.set4.m2,2)} m² per set · ${wl}`);\n if(g.s6\u0026\u0026it.set6) add(`Digi-Panels Horizon \"${g.decor}\" — set van 6 panelen`,it.set6.c,g.s6,\"set\",it.set6.pak,`${fmt(it.set6.m2,2)} m² per set · ${wl}`); return; }\n const se=PRICE.series[g.sk], it=findItem(g.sk,g.decor);\n let packs; if(p.layout===\"tiles\") packs=Math.ceil(g.m2*(1+se.verlies/100)/se.pakM2-1e-9); else packs=Math.ceil(g.pieces/se.stuks-1e-9);\n packs=Math.max(se.minpak,packs);\n add(`${p.name} \"${g.decor}\"`,it.c,packs,\"pak\",it.pak,`${sizeTxt(p,sz)} · ${se.stuks} st. = ${fmt(se.pakM2,2)} m² per pak · ${fmt(g.m2)} m² nodig · ${wl}`);\n });\n // lijm\n const koker=PRICE.lijm.ACHPL0029, emmer=PRICE.lijm.ACHPL5000;\n if(S.mount===\"lijm\"\u0026\u0026t.panelNet\u003e0) add(\"Hybride polymeerlijm, koker 290 ml\",koker.code,Math.ceil(t.panelNet*koker.verbruik-1e-9),\"stuk\",koker.prijs,\"ca. 1 koker per 2 m² paneel\");\n if(S.mount===\"schroef\"\u0026\u0026t.panelNet\u003e0) add(\"Schroeven voor Click \u0026 Screw\",\"\",1,\"set\",null,\"aantal en prijs bevestigen we in de offerte\");\n if(t.tileNet\u003e0) add(\"Hybride polymeerlijm, emmer 5 kg (wand)\",emmer.code,Math.ceil(t.tileNet*emmer.verbruik-1e-9),\"stuk\",emmer.prijs,\"tegels op de wand volvlaks verlijmen met lijmkam\");\n // profielen\n const pm=PROF[S.prof], col=pm.colors[S.profColor||0], src=PRICE[pm.src];\n [[\"binnen\",t.inner],[\"buiten\",t.outer],[\"u\",t.u]].forEach(([k,n])=\u003e{ if(!n) return; const code=pm.code[k](col[1]); add(`${pm.label[k]}, ${col[0].toLowerCase()}`,code,n,\"stuk\",src[code]!=null?src[code]:null,k===\"binnen\"?\"hoeken tussen twee beklede wanden\":k===\"buiten\"?\"koven, nissen en uitspringende hoeken\":\"open randen, bovenrand en rond deuren en ramen\"); });\n // vloer\n if(floorOn()){ const fp=t.fp, sk=seriesKey(\"floor\",S.floor.prod,S.floor.size,S.floor.decor), se=PRICE.series[sk], it=findItem(sk,S.floor.decor);\n if(it){ const packs=Math.max(se.minpak,Math.ceil(t.floorA*(1+se.verlies/100)/se.pakM2-1e-9));\n add(`${fp.name} \"${S.floor.decor}\"`,it.c,packs,\"pak\",it.pak,`${sizeTxt(fp,t.fs)} · ${se.stuks} st. = ${fmt(se.pakM2,2)} m² per pak · ${fmt(t.floorA)} m² vloer + ${se.verlies}% snijverlies`); }\n if(S.floor.mount===\"zwevend\"){ const u=PRICE.onder[S.floor.under===\"supreme\"?\"OVSPC001\":\"OVSPC002\"]; add(u.naam,u.code,Math.ceil(t.floorA*1.05/u.per-1e-9),u.code===\"OVSPC001\"?\"rol\":\"pak\",u.prijs,`${fmt(u.per,2)} m² per ${u.code===\"OVSPC001\"?\"rol\":\"pak\"}`); }\n else add(\"Hybride polymeerlijm, emmer 5 kg (vloer)\",emmer.code,Math.ceil(t.floorA*emmer.verbruik-1e-9),\"stuk\",emmer.prijs,\"vloer volvlaks verlijmen met lijmkam\");\n }\n const lev=0;\n return {lines:out,sub,lev,total:sub+lev,onRequest};\n}\nconst D=(name,file)=\u003e({name,file:file.startsWith(\"http\")?file:UP+\"2026/04/\"+file});\nconst WALL={\n panel:{name:\"Aqua Composite Panels\",short:\"Panels\",w:48.2,l:260.5,thick:\"4,5 mm\",unit:\"panelen\",layout:\"panels\",\n decors:[D(\"Sand Beige\",\"Sand-Beige.webp\"),D(\"Concrete Light\",\"Concrete-Light.webp\"),D(\"Limestone\",\"Limestone-7.webp\"),D(\"Marble Beige\",\"Marble-Beige-5.webp\"),D(\"Marble Grey\",\"Marble-Grey.webp\"),D(\"Marble Black\",\"Marble-Black.webp\"),D(\"Marmo Luna\",\"Marmo-Luna.webp\"),D(\"Marmo Luce\",\"Marmo-Luce-1.webp\"),D(\"Marmo Notte\",\"Marmo-Notte.webp\"),D(\"Grigio Imperiale\",\"Grigio-Imperiale.webp\"),D(\"Concrete Dark\",\"Concrete-Dark.webp\"),D(\"Concrete Black\",\"Concrete-Black.webp\"),D(\"Matt White\",\"Matt-White.webp\"),D(\"Anthracite Stone\",\"Anthracite-Stone-zwarter.webp\"),D(\"Timeless Oak\",\"Timeless-Oak.webp\"),D(\"Residential Oak\",\"Residential-Oak.webp\"),D(\"Eternal Oak\",\"Eternal-Oak.webp\"),D(\"Light Grey\",\"Light-Grey.webp\"),D(\"Platinum Grey\",\"Platinum-Grey-7036.webp\"),D(\"Graphite Black\",\"Graphite-Black.webp\")]},\n board:{name:\"Aqua Composite Boards\",short:\"Boards\",w:96.5,l:260.5,thick:\"4,5 mm\",unit:\"boards\",layout:\"panels\",\n decors:[D(\"Sand Beige\",\"Sand-Beige.webp\"),D(\"Concrete Light\",\"Concrete-Light.webp\"),D(\"Matt White\",\"Matt-White.webp\"),D(\"Marmo Notte\",\"Marmo-Notte-1.webp\"),D(\"Marmo Luce\",\"Marmo-Luce.webp\"),D(\"Marble Beige\",\"Marble-Beige-5.webp\"),D(\"Eternal Oak\",\"Eternal-Oak.webp\"),D(\"Marmo Luna\",\"Marmo-Luna-1.webp\")]},\n digi:{name:\"Aqua Composite Digi-panels\",short:\"Digi-panels\",w:46.7,l:260.5,thick:\"4,5 mm\",unit:\"panelen\",layout:\"mural\",custom:true,\n decors:[D(\"Sunlight in the Forest\",UP+\"2026/06/Aqua-Composite-DigiPanels_Sunlight-In-The-Forest-1024x787.jpg\"),D(\"Ice Cave\",UP+\"2026/06/Aqua-Composite-DigiPanels_Ice-Cave_2-1024x718.jpg\"),D(\"Dandelion\",UP+\"2026/06/Aqua-Composite-DigiPanels_Dandelion-1024x718.jpg\"),D(\"Terrazzo Grey\",UP+\"2026/07/TerrazoGrey_Panels2-819x1024.png\")]},\n tile:{name:\"Aqua Composite Tiles\",short:\"Tiles\",sizes:[{id:\"s\",w:61,l:30.5},{id:\"l\",w:95,l:47.5}],thick:\"4 mm\",unit:\"tegels\",layout:\"tiles\",\n decors:[D(\"Sand Beige\",\"Sand-Beige-681x1024.webp\"),D(\"Concrete Light\",\"Concrete-Light.webp\"),D(\"Limestone\",\"Limestone-7.webp\"),D(\"Marmo Notte\",\"Marmo-Notte.webp\"),D(\"Marmo Luna\",\"Marmo-Luna.webp\"),D(\"Marmo Oro\",\"Marmo-Oro-9.webp\"),D(\"Marble Grey\",\"Marble-Grey.webp\"),D(\"Marble Black\",\"Marble-Black.webp\"),D(\"Marble Beige\",\"Marble-Beige-5.webp\"),D(\"Concrete Dark\",\"Concrete-Dark.webp\"),D(\"Concrete Black\",\"Concrete-Black.webp\"),D(\"Anthracite Stone\",\"Anthracite-Stone-zwarter.webp\"),D(\"Grigio Imperiale\",\"Grigio-Imperiale.webp\"),D(\"Sandstone Lines\",\"Sandstone-Lines.webp\"),D(\"Matt White\",\"Matt-White.webp\"),D(\"Marmo Luce\",\"Marmo-Luce.webp\")]}\n};\nconst PLANK_STD=[D(\"Timeless Oak\",\"Timeless-Oak-1.webp\"),D(\"Heritage Oak\",\"Heritage-Oak-kl.webp\"),D(\"Eternal Oak\",\"Eternal-Oak-kl.webp\"),D(\"Bohemian Oak\",\"Bohemian-Oak-kl.webp\"),D(\"Traditional Oak\",\"Traditional-Oak-kl.webp\"),D(\"Savannah Oak\",\"Savannah-Oak-kl.webp\"),D(\"Residential Oak\",\"Residential-Oak-kl.webp\"),D(\"Majestic Oak\",\"Majestic-Oak-kl.webp\")];\nconst PLANK_SUP=[D(\"Oak Dream\",\"Plank-OakDream-kl.webp\"),D(\"Oak Glory\",\"Plank-OakGlory-kl.webp\"),D(\"Oak Paradise\",\"Plank-OakParadise-kl.webp\"),D(\"Oak Wonder\",\"Plank-OakWonder-kl.webp\")];\nconst FLOOR={\n tile:{...WALL.tile},\n plank:{name:\"Aqua Composite Flooring Plank\",short:\"Plank\",w:122,l:22.8,thick:\"4 mm\",wear:\"0,3 mm, klasse 32\",unit:\"planken\",layout:\"planks\",decors:PLANK_STD},\n plankSup:{name:\"Aqua Composite Plank Superior\",short:\"Plank Superior\",w:122,l:22.8,thick:\"4,5 mm\",wear:\"0,5 mm, klasse 33\",unit:\"planken\",layout:\"planks\",decors:PLANK_SUP},\n herring:{name:\"Aqua Composite Herringbone\",short:\"Herringbone\",w:75,l:15,thick:\"4,5 mm\",wear:\"0,5 mm, klasse 33\",unit:\"stroken\",layout:\"herring\",decors:[PLANK_STD[2],PLANK_STD[6],PLANK_STD[0],PLANK_STD[4]]},\n herringSup:{name:\"Aqua Composite Herringbone Superior\",short:\"Herringbone Superior\",w:75,l:15,thick:\"4,5 mm\",wear:\"0,5 mm, klasse 33\",unit:\"stroken\",layout:\"herring\",decors:PLANK_SUP}\n};\nconst LOOK={\n \"Marmo Luna\":[\"marble\",[238,237,234],[128,131,137]],\"Marmo Luce\":[\"marble\",[241,238,231],[188,158,104]],\"Marmo Notte\":[\"marble\",[40,41,45],[186,176,158]],\n \"Marmo Oro\":[\"marble\",[240,235,226],[178,140,66]],\"Marble Beige\":[\"marble\",[229,219,201],[166,140,104]],\"Marble Grey\":[\"marble\",[206,206,204],[102,104,107]],\n \"Marble Black\":[\"marble\",[29,29,31],[214,209,199]],\"Grigio Imperiale\":[\"marble\",[138,138,136],[226,224,218]],\"Grigio Imperiale\":[\"marble\",[138,138,136],[226,224,218]],\n \"Concrete Light\":[\"concrete\",[200,197,190]],\"Concrete Dark\":[\"concrete\",[122,120,116]],\"Concrete Black\":[\"concrete\",[60,60,60]],\n \"Limestone\":[\"stone\",[217,209,191]],\"Sand Beige\":[\"stone\",[214,200,175]],\"Anthracite Stone\":[\"stone\",[58,59,61]],\"Sandstone Lines\":[\"lines\",[206,190,163],[176,158,128]],\n \"Matt White\":[\"solid\",[242,241,238]],\"Light Grey\":[\"solid\",[200,201,200]],\"Platinum Grey\":[\"solid\",[154,152,151]],\"Graphite Black\":[\"solid\",[43,44,46]],\n \"Timeless Oak\":[\"wood\",[196,160,118],[146,108,72]],\"Residential Oak\":[\"wood\",[216,190,152],[168,136,96]],\"Eternal Oak\":[\"wood\",[150,113,80],[95,67,45]],\n \"Heritage Oak\":[\"wood\",[172,132,92],[120,88,58]],\"Bohemian Oak\":[\"wood\",[192,172,142],[140,120,95]],\"Traditional Oak\":[\"wood\",[182,142,98],[130,95,60]],\n \"Savannah Oak\":[\"wood\",[206,176,136],[160,128,90]],\"Majestic Oak\":[\"wood\",[122,96,76],[80,60,45]],\"Oak Dream\":[\"wood\",[202,172,132],[150,118,82]],\n \"Oak Glory\":[\"wood\",[178,152,120],[128,104,78]],\"Oak Paradise\":[\"wood\",[216,198,170],[170,150,120]],\"Oak Wonder\":[\"wood\",[142,112,84],[96,72,52]],\n \"Sunlight in the Forest\":[\"forest\",[70,96,52],[236,214,140]],\"Ice Cave\":[\"ice\",[40,96,140],[210,236,246]],\"Dandelion\":[\"dandelion\",[44,52,48],[240,240,232]],\"Terrazzo Grey\":[\"terrazzo\",[186,184,180]],\n \"Eigen ontwerp\":[\"solid\",[210,210,210]]\n};\nconst SHAPES={\n rect:{name:\"Rechthoek\",params:[[\"A\",\"Breedte\",240],[\"B\",\"Diepte\",180]],\n pts:p=\u003e[[0,p.B],[p.A,p.B],[p.A,0],[0,0]],valid:p=\u003e\"\"},\n L:{name:\"L-vorm\",params:[[\"A\",\"Breedte\",300],[\"B\",\"Diepte\",240],[\"E\",\"Breedte uitsparing\",190],[\"D\",\"Diepte uitsparing\",90]],\n pts:p=\u003e[[0,p.B],[p.E,p.B],[p.E,p.B-p.D],[p.A,p.B-p.D],[p.A,0],[0,0]], // E = muur vanaf de hoek tot de uitsparing\n valid:p=\u003ep.E\u003c20?\"De muur tot de uitsparing moet minstens 20 cm zijn.\":p.E\u003ep.A-20?\"De uitsparing moet minstens 20 cm breed zijn: maak de breedte uitsparing kleiner dan de totale breedte.\":p.D\u003e=p.B-20?\"De uitsparing moet minstens 20 cm minder diep zijn dan de ruimte.\":\"\"},\n nis:{name:\"Met nis\",params:[[\"A\",\"Breedte\",260],[\"B\",\"Diepte\",200],[\"W\",\"Breedte nis\",90],[\"N\",\"Diepte nis\",35],[\"X\",\"Nis vanaf links\",110]],\n pts:p=\u003e[[0,p.B],[p.X,p.B],[p.X,p.B+p.N],[p.X+p.W,p.B+p.N],[p.X+p.W,p.B],[p.A,p.B],[p.A,0],[0,0]],\n valid:p=\u003ep.X\u003c10||p.X+p.W\u003ep.A-10?\"De nis moet minstens 10 cm van beide hoeken blijven.\":\"\"},\n koof:{name:\"Met koof\",params:[[\"A\",\"Breedte\",260],[\"B\",\"Diepte\",200],[\"W\",\"Breedte koof\",40],[\"N\",\"Diepte koof\",30],[\"X\",\"Koof vanaf links\",0]],\n pts:p=\u003ep.X\u003c=0?[[0,p.B-p.N],[p.W,p.B-p.N],[p.W,p.B],[p.A,p.B],[p.A,0],[0,0]]:[[0,p.B],[p.X,p.B],[p.X,p.B-p.N],[p.X+p.W,p.B-p.N],[p.X+p.W,p.B],[p.A,p.B],[p.A,0],[0,0]],\n valid:p=\u003ep.X+p.W\u003ep.A-10?\"De koof past niet in de breedte.\":p.N\u003e=p.B-40?\"De koof is te diep voor de ruimte.\":\"\"}\n};\nconst SHAPE_INFO={rect:\"Een gewone rechthoekige ruimte met 4 wanden.\",\n L:\"Eén hoek van de ruimte springt naar binnen, bijvoorbeeld door een trapgat of een kast van de buren. De breedte uitsparing meet je langs de muur, van de hoek tot aan de uitsparing. Met Spiegelen zit de uitsparing links of rechts.\",\n nis:\"Een nis in de achterwand, bijvoorbeeld voor een inloopdouche of een kast. Met Nis vanaf links schuif je de nis langs de wand.\",\n koof:\"Een koof is een ingebouwde leidingkoker of schacht tegen de achterwand. Staat hij in de hoek (Koof vanaf links = 0), dan heeft de ruimte 6 wanden: 4 wanden van de ruimte en 2 zijden van de koof. Staat hij verder van de hoek, dan zijn het er 8. Met Spiegelen zit hij in de rechterhoek.\"};\nconst CAT={marble:\"Marmer\",concrete:\"Beton \u0026 steen\",stone:\"Beton \u0026 steen\",lines:\"Beton \u0026 steen\",solid:\"Egaal\",wood:\"Hout\",forest:\"Print\",ice:\"Print\",dandelion:\"Print\",terrazzo:\"Print\"};\n/* ===================================================================== */\n\n/* ================= BIBLIOTHEEK: sanitair en meubels (maten in cm) ================= */\n// h = hoogte, z = ophanghoogte vanaf de vloer, pal = kleurenpalet\nconst LIB={\n \"Badkamer\":[\n {id:\"toilet\",name:\"Hangtoilet\",w:37,d:54,wall:true},\n {id:\"douche\",name:\"Inloopdouche\",w:90,d:120,h:200,pal:\"shower\",sizes:[[90,120],[120,90],[100,100],[140,90]]},\n {id:\"bad\",name:\"Inbouwbad\",w:170,d:75,sizes:[[160,70],[170,75],[180,80]]},\n {id:\"vbad\",name:\"Vrijstaand bad\",w:170,d:80},\n {id:\"meubel\",name:\"Badmeubel met wastafel\",w:80,d:46,h:45,z:40,wall:true,pal:\"cab\",sizes:[[60,46],[80,46],[100,46],[120,46]]},\n {id:\"spiegel\",name:\"Spiegel\",w:80,d:3,h:80,z:110,wall:true,sizes:[[60,3],[80,3],[100,3],[120,3]]},\n {id:\"spiegelkast\",name:\"Spiegelkast\",w:80,d:15,h:70,z:115,wall:true,pal:\"cab\",sizes:[[60,15],[80,15],[100,15],[120,15]]},\n {id:\"wandkast\",name:\"Hangkast\",w:60,d:22,h:70,z:150,wall:true,pal:\"cab\",sizes:[[40,22],[60,22],[80,22],[120,22]]},\n {id:\"kolom\",name:\"Kolomkast\",w:35,d:35,h:170,z:30,wall:true,pal:\"cab\"},\n {id:\"radiator\",name:\"Handdoekradiator\",w:50,d:10,h:120,z:30,wall:true}],\n \"Toiletgroep\":[\n {id:\"toilet\",name:\"Hangtoilet\",w:37,d:54,wall:true},\n {id:\"urinoir\",name:\"Urinoir\",w:36,d:32,wall:true},\n {id:\"schot\",name:\"Urinoirschot\",w:2,d:45,wall:true},\n {id:\"fontein\",name:\"Fonteintje\",w:40,d:25,wall:true}],\n \"Woonkamer\":[\n {id:\"tv\",name:\"Tv-meubel met tv\",w:180,d:40,wall:true,pal:\"cab\",sizes:[[140,40],[180,40],[220,40]]},\n {id:\"kast\",name:\"Kast\",w:100,d:60,h:210,wall:true,pal:\"cab\",sizes:[[50,60],[100,60],[150,60],[200,60]]},\n {id:\"bank\",name:\"Bank\",w:220,d:90,sizes:[[160,90],[220,90],[260,95]]},\n {id:\"fauteuil\",name:\"Fauteuil\",w:85,d:85},\n {id:\"tafel\",name:\"Tafel\",w:160,d:90,pal:\"cab\",sizes:[[80,80],[120,80],[160,90],[200,100]]},\n {id:\"stoel\",name:\"Stoel\",w:45,d:52,pal:\"cab\"},\n {id:\"plant\",name:\"Plant\",w:40,d:40}],\n \"Slaapkamer en werkplek\":[\n {id:\"bed\",name:\"Bed\",w:160,d:200,pal:\"cab\",sizes:[[90,200],[140,200],[160,200],[180,210]]},\n {id:\"bureau\",name:\"Bureau\",w:120,d:60,wall:true,pal:\"cab\",sizes:[[100,60],[120,60],[160,70]]}],\n \"Keuken\":[\n {id:\"keuken\",name:\"Keukenblok met bovenkasten\",w:240,d:60,wall:true,pal:\"cab\",sizes:[[180,60],[240,60],[300,60]]}]\n};\nconst LIBALL=Object.values(LIB).flat().filter((x,i,a)=\u003ea.findIndex(y=\u003ey.id===x.id)===i), libDef=id=\u003eLIBALL.find(x=\u003ex.id===id);\nconst PAL={\n cab:[[\"Wit\",0xf4f4f1],[\"Eiken\",0xb58a5d],[\"Noten\",0x6e4a32],[\"Lichtgrijs\",0xc4c6c4],[\"Antraciet\",0x3b3f43],[\"Zwart\",0x1f2124],[\"Salie\",0x8fa58c],[\"Nachtblauw\",0x34495e]],\n shower:[[\"Chroom\",0xc9ced1],[\"Mat zwart\",0x1e1f21],[\"Goud\",0xc8a35a],[\"RVS geborsteld\",0xa7aaa8],[\"Wit\",0xf2f2f0]]\n};\nconst GLASS=[[\"Helder glas\",0xcfe6ee,.26],[\"Rookglas\",0x4f575c,.5],[\"Melkglas\",0xe9eef0,.78]];\nconst hex=n=\u003e\"#\"+n.toString(16).padStart(6,\"0\");\n\nconst clad=w=\u003e!!(w\u0026\u0026w.on\u0026\u0026w.decor); // bekleed = aangevinkt én decor gekozen\nconst floorOn=()=\u003e!!(S.floor.on\u0026\u0026S.floor.decor);\nconst newWall=()=\u003e({on:true,pick:false,prod:\"panel\",size:\"s\",decor:null,cover:\"full\",coverCm:120,openings:[]});\nconst ROOMS=[\"Badkamer\",\"Toilet\",\"Keuken\",\"Wasruimte\",\"Woonkamer\",\"Slaapkamer\",\"Hal\",\"Bedrijfsruimte\"];\nconst S={room:\"Badkamer\",step:1,shape:\"rect\",params:{A:240,B:180,C:110,E:190,D:90,W:90,N:35,X:110},H:260,walls:[],sel:0,items:[],selItem:-1,\n floor:{on:false,pick:false,prod:\"tile\",size:\"l\",decor:null,mount:\"zwevend\",under:\"xpo\"},mount:\"lijm\",prof:\"PVC\",profColor:0,delivery:\"bezorgen\",view:\"over\",customImg:null,customThumb:\"\",customId:0,\n cab:{on:false,n:2,w:90,d:150,t:10,door:70,dh:211,wall:0,x:0,open:[],ur:{on:false,n:6,hoh:60,schot:true}},cabMem:[]};\n\nconst $=s=\u003edocument.querySelector(s), $$=s=\u003e[...document.querySelectorAll(s)];\nconst fmt=(n,d=1)=\u003en.toLocaleString(\"nl-NL\",{maximumFractionDigits:d});\nconst clamp=(x,a,b)=\u003ex\u003ca?a:x\u003eb?b:x;\nconst esc=s=\u003eString(s).replace(/[\u0026\u003c\u003e\"]/g,c=\u003e({\"\u0026\":\"\u0026amp;\",\"\u003c\":\"\u0026lt;\",\"\u003e\":\"\u0026gt;\",'\"':\"\u0026quot;\"}[c]));\nfunction toast(t){const el=$(\"#toast\");el.textContent=t;el.classList.add(\"on\");clearTimeout(toast.t);toast.t=setTimeout(()=\u003eel.classList.remove(\"on\"),1800);}\n\n/* ---------- geometrie van de plattegrond ---------- */\nfunction mirrorPts(pts){ const mx=Math.max(...pts.map(q=\u003eq[0])); let m=pts.map(([x,y])=\u003e[mx-x,y]).reverse(); // spiegelen links ↔ rechts, met de klok mee houden\n const my=Math.max(...m.map(q=\u003eq[1])); let k=0; m.forEach((q,i)=\u003e{ if(q[1]===my\u0026\u0026(m[k][1]!==my||q[0]\u003cm[k][0])) k=i; }); return m.slice(k).concat(m.slice(0,k)); }\nfunction plan(){ const sh=SHAPES[S.shape], p={}; sh.params.forEach(([k])=\u003ep[k]=S.params[k]); let pts=sh.pts(p); if(S.mirror\u0026\u0026S.shape!==\"rect\") pts=mirrorPts(pts); return {pts, err:sh.valid(p)}; }\nlet PTS=[]; // in cm\nfunction area2(pts){ let a=0; for(let i=0;i\u003cpts.length;i++){const p=pts[i],q=pts[(i+1)%pts.length];a+=p[0]*q[1]-q[0]*p[1];} return a; }\nfunction perimGeo(){ const n=PTS.length, s=Math.sign(area2(PTS))||1; const out=[];\n for(let i=0;i\u003cn;i++){ const p=PTS[i],q=PTS[(i+1)%n],dx=q[0]-p[0],dy=q[1]-p[1],L=Math.hypot(dx,dy),d=[dx/L,dy/L];\n const nin=s\u003e0?[-d[1],d[0]]:[d[1],-d[0]]; out.push({i,p,q,L,d,nin}); }\n return out; }\nfunction wallsGeo(){ const out=perimGeo(), c=cabGeo(out); if(!c) return out; c.faces.forEach((f,j)=\u003ef.i=out.length+j); return out.concat(c.faces); }\n/* ---------- toiletcabines: rij cabines met tussenwanden tot het plafond ---------- */\nlet CABERR=\"\";\nfunction cabFace(a,b,nin,label,u){ const s=Math.sign(area2(PTS))||1, d=s\u003e0?[nin[1],-nin[0]]:[-nin[1],nin[0]];\n if((b[0]-a[0])*d[0]+(b[1]-a[1])*d[1]\u003c0){ const t=a; a=b; b=t; }\n return {p:a,q:b,L:Math.hypot(b[0]-a[0],b[1]-a[1]),d,nin,inner:true,label,doors:[]}; }\nfunction cabGeo(per){ CABERR=\"\"; const C=S.cab; if(!C||!C.on||S.shape!==\"rect\"||!per||per.length\u003c4) return null;\n const n0=per.length, wi=((C.wall|0)%n0+n0)%n0, bw=per[wi], L=bw.L, t=Math.max(1,C.t), w=C.w, D=C.d, N=clamp(Math.round(C.n)||1,1,12);\n const prevW=(wi-1+n0)%n0, nextW=(wi+1)%n0, depthMax=per[nextW].L;\n const P=(s,u)=\u003e[bw.p[0]+bw.d[0]*s+bw.nin[0]*u, bw.p[1]+bw.d[1]*s+bw.nin[1]*u];\n const leftP=C.x\u003e0.5, s0=leftP?C.x+t:0, end0=s0+N*w+(N-1)*t, gap=L-end0, rightP=gap\u003et+0.5, wl=(!rightP\u0026\u0026gap\u003e0)?w+gap:w, end=s0+(N-1)*(w+t)+wl, rowA=leftP?C.x:0, rowB=rightP?end+t:end, wk=k=\u003ek===N-1?wl:w;\n if(w\u003c60){ CABERR=\"Een cabine moet binnenwerks minstens 60 cm breed zijn.\"; return null; }\n if(D\u003c80){ CABERR=\"Een cabine moet minstens 80 cm diep zijn.\"; return null; }\n if(end0\u003eL+0.5||rowB\u003eL+0.5){ CABERR=`De cabines passen niet tegen wand ${wi+1}: samen ${fmt(rowB,0)} cm, de wand is ${fmt(L,0)} cm. Maak ze smaller, verminder het aantal of verklein de afstand vanaf de hoek.`; return null; }\n if(D+t\u003edepthMax-([...Array(N)].some((_,k)=\u003e!(C.open\u0026\u0026C.open[k]))?60:40)){ CABERR=`De cabines zijn te diep: de ruimte is ${fmt(depthMax,0)} cm diep en voor de deuren moet minstens 60 cm vrij blijven.`; return null; }\n if([...Array(N)].some((_,k)=\u003e!(C.open\u0026\u0026C.open[k]))\u0026\u0026C.door\u003ew-10){ CABERR=\"De deur is te breed voor de cabine: houd minstens 10 cm wand over.\"; return null; }\n const faces=[], corners=[], solids=[], dirN=[-bw.d[0],-bw.d[1]], dirP=bw.d, out=[-bw.nin[0],-bw.nin[1]];\n const add=f=\u003e{ faces.push(f); return n0+faces.length-1; };\n const cs=k=\u003es0+k*(w+t); // binnenkant links van cabine k\n const op=k=\u003e!!(C.open\u0026\u0026C.open[k]); // open cabine: geen voorwand en geen deur\n const sideL=[], sideR=[], pR=[], pL=[]; // per cabine: zijwand links/rechts; per tussenwand: vlak naar links/rechts\n // tussenwanden\n const parts=[]; if(leftP) parts.push({a:C.x,left:-1,right:0}); for(let k=1;k\u003cN;k++) parts.push({a:cs(k)-t,left:k-1,right:k}); if(rightP) parts.push({a:end,left:N-1,right:-1});\n let outerL=null, outerR=null;\n parts.forEach(pt=\u003e{ const cL=pt.left\u003e=0\u0026\u0026!op(pt.left), cR=pt.right\u003e=0\u0026\u0026!op(pt.right); // dichte cabine aan die kant\n const lenL=(!cL\u0026\u0026cR)?D+t:D, lenR=(!cR\u0026\u0026cL)?D+t:D; // voorwand loopt over de kop van de tussenwand\n const fL=add(cabFace(P(pt.a,0),P(pt.a,lenL),dirN,pt.left\u003c0?\"buitenkant cabines\":`zijwand cabine ${pt.left+1}`));\n const fR=add(cabFace(P(pt.a+t,0),P(pt.a+t,lenR),dirP,pt.right\u003c0?\"buitenkant cabines\":`zijwand cabine ${pt.right+1}`));\n corners.push([wi,fL,\"binnen\"],[wi,fR,\"binnen\"]);\n if(pt.left\u003e=0) sideR[pt.left]=fL; else outerL=fL;\n if(pt.right\u003e=0) sideL[pt.right]=fR; else outerR=fR;\n if(pt.left\u003e=0) pL[pt.left]=fL; if(pt.right\u003e=0) pR[pt.right]=fR;\n if(!cL\u0026\u0026!cR) corners.push([fL,fR,\"eind\"]); // vrije kop van de tussenwand\n solids.push({s0:pt.a,s1:pt.a+t,u0:0,u1:D,z0:0,z1:S.H}); });\n // dichte cabines: binnenkant voorwand met deur\n for(let k=0;k\u003cN;k++){ if(op(k)) continue; const f=add(cabFace(P(cs(k),D),P(cs(k)+wk(k),D),out,`binnenkant voorwand cabine ${k+1}`)); faces[f-n0].doors.push(P(cs(k)+wk(k)/2,D));\n corners.push([f,sideL[k]!=null?sideL[k]:prevW,\"binnen\"],[f,sideR[k]!=null?sideR[k]:nextW,\"binnen\"]); }\n // voorkant: één vlak per aaneengesloten reeks dichte cabines\n const dh=Math.min(C.dh||211,S.H);\n for(let a=0;a\u003cN;a++){ if(op(a)) continue; let b=a; while(b+1\u003cN\u0026\u0026!op(b+1)) b++;\n const xa=a===0?rowA:cs(a)-t, xb=b===N-1?rowB:cs(b)+wk(b)+t;\n const fo=add(cabFace(P(xa,D+t),P(xb,D+t),bw.nin,N===1||(a===0\u0026\u0026b===N-1)?\"voorkant cabines\":`voorkant cabine ${a+1}${b\u003ea?\"–\"+(b+1):\"\"}`));\n for(let k=a;k\u003c=b;k++) faces[fo-n0].doors.push(P(cs(k)+wk(k)/2,D+t));\n corners.push(a===0?(leftP?[fo,outerL,\"buiten\"]:[fo,prevW,\"binnen\"]):[fo,pL[a-1],\"buiten\"], b===N-1?(rightP?[fo,outerR,\"buiten\"]:[fo,nextW,\"binnen\"]):[fo,pR[b+1],\"buiten\"]);\n let x=xa; for(let k=a;k\u003c=b;k++){ const d0=cs(k)+(wk(k)-C.door)/2, d1=d0+C.door; solids.push({s0:x,s1:d0,u0:D,u1:D+t,z0:0,z1:S.H}); if(dh\u003cS.H) solids.push({s0:d0,s1:d1,u0:D,u1:D+t,z0:dh,z1:S.H}); x=d1; }\n solids.push({s0:x,s1:xb,u0:D,u1:D+t,z0:0,z1:S.H}); a=b; }\n return {faces,corners,solids,P,bw,wi,N,rowA,rowB,D,t,wl,s0}; }\nfunction inCab(pt){ const c=cabGeo(perimGeo()); if(!c) return false; const rx=pt[0]-c.bw.p[0], ry=pt[1]-c.bw.p[1], s=rx*c.bw.d[0]+ry*c.bw.d[1], u=rx*c.bw.nin[0]+ry*c.bw.nin[1]; return u\u003cc.D+c.t+15\u0026\u0026s\u003ec.rowA-15\u0026\u0026s\u003cc.rowB+15; }\nfunction urS(){ const C=S.cab; if(!C.ur) C.ur={on:false,n:6,hoh:60,schot:true}; return C.ur; }\nfunction urCalc(cg){ const U=urS(), per=perimGeo(); if(!cg||!U.on||per.length\u003c4) return null; const oi=(cg.wi+2)%per.length, L=per[oi].L, hoh=clamp(U.hoh||60,50,120), max=Math.max(1,Math.floor(L/hoh)), n=clamp(Math.round(U.n)||1,1,max), dep=U.schot?45:32, aisle=per[(cg.wi+1)%per.length].L-(cg.D+cg.t)-dep;\n return {oi,L,hoh,n,max,cut:(Math.round(U.n)||1)\u003emax,start:(L-n*hoh)/2,aisle}; }\nfunction urInfo(){ const el=$(\"#urInfo\"); if(!el) return; const C=S.cab, U=urS(); if(!C.on||!U.on){ el.hidden=true; return; } const r=urCalc(cabGeo(perimGeo())); el.hidden=false;\n if(!r){ el.className=\"note warn\"; el.textContent=\"De urinoirrij verschijnt zodra de cabines passen.\"; return; }\n const m=[`${r.n} urinoir${r.n\u003e1?\"s\":\"\"} op wand ${r.oi+1}, gecentreerd, ${fmt(r.hoh,0)} cm hart-op-hart (samen ${fmt(r.n*r.hoh,0)} cm van ${fmt(r.L,0)} cm).`];\n if(r.cut) m.push(`Er passen er maximaal ${r.max} op deze wand.`);\n m.push(`Vrije looproute tussen cabines en urinoirs: ${fmt(r.aisle,0)} cm${r.aisle\u003c110?\" – dat is krap, houd liefst minstens 110 cm aan.\":\".\"}`);\n el.className=\"note\"+(r.cut||r.aisle\u003c110?\" warn\":\"\"); el.textContent=m.join(\" \"); }\nfunction urPlace(cg){ const U=urS(), r=urCalc(cg); if(!r) return; for(let k=0;k\u003cr.n;k++){ const it=newItem(\"urinoir\",{cabAuto:true,urAuto:true}); putOnWall(it,r.oi,r.start+r.hoh*(k+.5)); S.items.push(it); }\n if(U.schot) for(let k=1;k\u003cr.n;k++){ const it=newItem(\"schot\",{cabAuto:true,urAuto:true}); putOnWall(it,r.oi,r.start+r.hoh*k); S.items.push(it); } }\nfunction cabToilets(){ const C=S.cab; S.items=S.items.filter(it=\u003e!it.cabAuto); const cg0=C.on?cabGeo(perimGeo()):null; if(cg0) urPlace(cg0); urInfo(); if(C.on\u0026\u0026C.autoT){ const cg=cg0; if(cg) for(let k=0;k\u003ccg.N;k++){ const it=newItem(C.open\u0026\u0026C.open[k]?\"urinoir\":\"toilet\",{cabAuto:true}); putOnWall(it,cg.wi,cg.s0+k*(C.w+C.t)+(k===cg.N-1?cg.wl:C.w)/2); S.items.push(it); } }\n S.selItem=-1; renderItemsUI(); rebuildItems(); renderPlan(); }\nfunction cabSync(g){ // deuren in de cabinewanden bijhouden\n const C=S.cab; g.forEach(gw=\u003e{ if(!gw.inner) return; const w=S.walls[gw.i]; if(!w) return;\n w.openings=w.openings.filter(o=\u003e!o.cab).concat(gw.doors.map(c=\u003e{ const s=(c[0]-gw.p[0])*gw.d[0]+(c[1]-gw.p[1])*gw.d[1]; return {type:\"deur\",cab:true,w:C.door,h:Math.min(C.dh||211,S.H),x:Math.round((s-C.door/2)*10)/10,y:0}; })); }); }\nfunction cabDesc(){ const C=S.cab, c=C\u0026\u0026C.on?cabGeo(perimGeo()):null; if(!c) return \"\";\n const fl=c.faces.map((f,j)=\u003eperimGeo().length+j+1);\n return ` The room contains ${c.N} toilet cubicle${c.N\u003e1?\"s\":\"\"} in a row against wall ${c.wi+1}: each about ${C.w} cm wide inside and ${C.d} cm deep, with full-height ${C.t} cm partition walls up to the ceiling and a closed ${C.door} cm wide door in the front wall of each closed cubicle. Walls ${fl[0]}–${fl[fl.length-1]} are the faces of these partitions. ${(C.open||[]).slice(0,c.N).some(Boolean)?` Cubicle${(C.open||[]).slice(0,c.N).filter(Boolean).length\u003e1?\"s\":\"\"} ${(C.open||[]).slice(0,c.N).map((o,k)=\u003eo?k+1:0).filter(Boolean).join(\", \")} ${(C.open||[]).slice(0,c.N).filter(Boolean).length\u003e1?\"are\":\"is an\"} open urinal bay${(C.open||[]).slice(0,c.N).filter(Boolean).length\u003e1?\"s\":\"\"} with side partitions only: no front wall and no door.`:\"\"} Keep the cubicles and their doors clearly visible.`+(()=\u003e{ const r=urCalc(c); if(!r) return \"\"; return ` Opposite the cubicles, against wall ${r.oi+1}, there is a row of ${r.n} white wall-hung urinal${r.n\u003e1?\"s\":\"\"}, spaced ${r.hoh} cm centre to centre${urS().schot\u0026\u0026r.n\u003e1?\", with small privacy screens between them\":\"\"}.`; })(); }\nfunction cornerType(i){ // hoek aan het begin van wand i (tussen wand i-1 en i)\n const n=PTS.length,a=PTS[(i-1+n)%n],b=PTS[i],c=PTS[(i+1)%n],cr=(b[0]-a[0])*(c[1]-b[1])-(b[1]-a[1])*(c[0]-b[0]);\n return cr*(Math.sign(area2(PTS))||1)\u003e0?\"binnen\":\"buiten\"; }\nfunction syncWalls(){ const pl=plan(); $(\"#shapeErr\").hidden=!pl.err; $(\"#shapeErr\").textContent=pl.err; if(pl.err) return false;\n PTS=pl.pts; const n0=PTS.length, n=wallsGeo().length; if($(\"#cabErr\")){ $(\"#cabErr\").hidden=!CABERR; $(\"#cabErr\").textContent=CABERR; }\n if(S.walls.length\u003en) S.walls.slice(Math.max(n,n0)).forEach((w,j)=\u003eS.cabMem[Math.max(n,n0)-n0+j]=w);\n while(S.walls.length\u003cn){ const k=S.walls.length; S.walls.push((k\u003e=n0\u0026\u0026S.cabMem[k-n0])||newWall()); } // nieuwe wanden: nog geen keuze ingevuld\n S.walls.length=n; if(S.sel\u003e=n) S.sel=0;\n const g=wallsGeo(); cabSync(g); S.walls.forEach((w,i)=\u003ew.openings.forEach(o=\u003e{o.w=Math.min(o.w,g[i].L);o.x=clamp(o.x,0,Math.max(0,g[i].L-o.w));}));\n return true; }\n\n/* ---------- decors: echte afbeelding of ingebouwde voorvertoning ---------- */\nfunction hash2(x,y,s){let h=Math.imul(x|0,374761393)^Math.imul(y|0,668265263)^Math.imul(s|0,-2048144789);h=Math.imul(h^(h\u003e\u003e\u003e13),1274126177);h^=h\u003e\u003e\u003e16;return (h\u003e\u003e\u003e0)/4294967295;}\nfunction vnoise(x,y,s){const xi=Math.floor(x),yi=Math.floor(y),xf=x-xi,yf=y-yi,u=xf*xf*(3-2*xf),v=yf*yf*(3-2*yf);const a=hash2(xi,yi,s),b=hash2(xi+1,yi,s),c=hash2(xi,yi+1,s),d=hash2(xi+1,yi+1,s);return a+(b-a)*u+(c-a)*v+(a-b-c+d)*u*v;}\nfunction fbm(x,y,s,o){let t=0,a=.5,f=1,n=0;for(let i=0;i\u003co;i++){t+=a*vnoise(x*f,y*f,s+i*31);n+=a;a*=.5;f*=2.03;}return t/n;}\nfunction seedOf(n){let h=7;for(const ch of n)h=Math.imul(h^ch.charCodeAt(0),16777619);return (h\u003e\u003e\u003e0)%9973;}\nfunction genDecor(name,W,H){\n const [t,B,V0]=LOOK[name]||[\"solid\",[200,200,200]],V=V0||B,seed=seedOf(name);\n const c=document.createElement(\"canvas\");c.width=W;c.height=H;const g=c.getContext(\"2d\");const im=g.createImageData(W,H),p=im.data;const mix=(a,b,k)=\u003ea+(b-a)*k;\n for(let y=0;y\u003cH;y++)for(let x=0;x\u003cW;x++){const u=x/W,v=y/W;let k,r,gg,b;\n if(t===\"marble\"){const n=fbm(u*2.6,v*2.6,seed,5),v1=Math.pow(1-Math.abs(Math.sin((u*1.9+v*.75+n*3.4)*Math.PI*1.15)),14),n2=fbm(u*6,v*6,seed+7,4),v2=Math.pow(1-Math.abs(Math.sin((-u*1.2+v*1.6+n2*2.6)*Math.PI*2.4)),26)*.55,cl=(fbm(u*1.3,v*1.3,seed+3,3)-.5)*.1;k=clamp(v1*.85+v2,0,1);r=mix(B[0]*(1+cl),V[0],k);gg=mix(B[1]*(1+cl),V[1],k);b=mix(B[2]*(1+cl),V[2],k);}\n else if(t===\"concrete\"){k=.86+.26*fbm(u*2.4,v*2.4,seed,5)+.05*(hash2(x,y,seed+5)-.5)+.06*(fbm(u*14,v*14,seed+9,2)-.5);if(hash2(x\u003e\u003e1,y\u003e\u003e1,seed+11)\u003e.9975)k*=.72;r=B[0]*k;gg=B[1]*k;b=B[2]*k;}\n else if(t===\"stone\"){const sp=hash2(x,y,seed+2);k=.94+.10*fbm(u*3,v*3,seed,4)+(sp-.5)*.1;if(sp\u003e.985)k*=1.12;if(sp\u003c.012)k*=.82;r=B[0]*k;gg=B[1]*k;b=B[2]*k;}\n else if(t===\"lines\"){const n=fbm(u*3,v*3,seed,4);k=.5+.5*Math.sin((v*22+n*3)*Math.PI);k=Math.pow(k,3)*.6+(hash2(x,y,seed)-.5)*.12;r=mix(B[0],V[0],k);gg=mix(B[1],V[1],k);b=mix(B[2],V[2],k);}\n else if(t===\"wood\"){const g0=fbm(u*2.2,v*.35,seed,4),rings=.5+.5*Math.sin(u*48+g0*10),fine=fbm(u*140,v*3,seed+2,2),lg=fbm(u*.8,v*.6,seed+4,3);k=clamp(rings*.45+fine*.35+(lg-.5)*.6+.1,0,1);r=mix(B[0],V[0],k);gg=mix(B[1],V[1],k);b=mix(B[2],V[2],k);}\n else if(t===\"forest\"){const ray=Math.max(0,Math.sin((u*3.2-v*1.6)*Math.PI*1.4))*Math.max(0,1-v/(H/W))*.8,tr=Math.abs(Math.sin(u*Math.PI*7+fbm(u*2,v*2,seed,3)*3))\u003c.12?.55:1,n=fbm(u*5,v*5,seed,4);k=clamp(ray*.6+n*.3,0,1);r=mix(B[0],V[0],k)*tr;gg=mix(B[1],V[1],k)*tr;b=mix(B[2],V[2],k)*tr;}\n else if(t===\"ice\"){const n=fbm(u*2.5,v*2.5,seed,5),cr=Math.pow(1-Math.abs(Math.sin((u*2+n*4)*Math.PI*2)),18);k=clamp(n*1.1-.15+cr*.5,0,1);r=mix(B[0],V[0],k);gg=mix(B[1],V[1],k);b=mix(B[2],V[2],k);}\n else if(t===\"dandelion\"){const cx=u-.55,cy=v-.4,d=Math.hypot(cx,cy),ang=Math.atan2(cy,cx),sp=Math.pow(Math.max(0,Math.cos(ang*36)),40)*(d\u003c.3?1:0)*(1-d/.3),hd=d\u003c.035?1:0,bk=fbm(u*2,v*2,seed,3);k=clamp(sp*1.2+hd+(hash2(x,y,seed)\u003e.9993?1:0),0,1);r=mix(B[0]*(.8+.4*bk),V[0],k);gg=mix(B[1]*(.8+.4*bk),V[1],k);b=mix(B[2]*(.8+.4*bk),V[2],k);}\n else if(t===\"terrazzo\"){const h=hash2(Math.floor(u*40+fbm(u*9,v*9,seed,2)*.8),Math.floor(v*40+fbm(v*9,u*9,seed,2)*.8),seed);let col=B;if(h\u003e.86)col=[96,98,100];else if(h\u003e.78)col=[232,230,226];else if(h\u003e.72)col=[150,140,128];k=.97+(hash2(x,y,seed)-.5)*.06;r=col[0]*k;gg=col[1]*k;b=col[2]*k;}\n else{k=.975+.05*fbm(u*6,v*6,seed,3)+(hash2(x,y,seed)-.5)*.02;r=B[0]*k;gg=B[1]*k;b=B[2]*k;}\n const i=(y*W+x)*4;p[i]=r;p[i+1]=gg;p[i+2]=b;p[i+3]=255;}\n g.putImageData(im,0,0);return c;}\nconst MURAL=n=\u003e[\"forest\",\"ice\",\"dandelion\",\"terrazzo\"].includes((LOOK[n]||[])[0]);\nconst procCache=new Map(),imgCache=new Map(),thumbCache=new Map();\nfunction proc(name){if(!procCache.has(name))procCache.set(name,MURAL(name)?genDecor(name,640,520):genDecor(name,240,1300));return procCache.get(name);}\nfunction relUrl(u){ try{ const x=new URL(u); return x.pathname; }catch(_){ return u; } }\nfunction source(d){\n if(d.custom) return S.customImg||proc(\"Eigen ontwerp\");\n const st=imgCache.get(d.file);\n if(st\u0026\u0026st.ok) return st.img;\n if(!st){ imgCache.set(d.file,{ok:false});\n const tryLoad=(url,cors,next)=\u003e{ const img=new Image(); if(cors) img.crossOrigin=\"anonymous\"; img.decoding=\"async\";\n img.onload=()=\u003e{ if(img.naturalWidth\u003e0){ imgCache.set(d.file,{ok:true,img}); invalidateTextures(); } };\n img.onerror=next; img.src=url; };\n // eerst via CORS (werkt overal), daarna als pad op dezelfde website\n const base=document.baseURI||location.href; let same=null; try{ const bu=new URL(base); if(/^https?:$/.test(bu.protocol)) same=new URL(relUrl(d.file),bu.origin).href; }catch(_){}\n tryLoad(d.file,true,()=\u003e{ if(same\u0026\u0026same!==d.file) tryLoad(same,true,()=\u003eimgCache.set(d.file,{ok:false,failed:true})); else imgCache.set(d.file,{ok:false,failed:true}); });\n }\n return proc(d.name);\n}\nfunction thumb(name){if(!thumbCache.has(name))thumbCache.set(name,genDecor(name,90,120).toDataURL(\"image/png\"));return thumbCache.get(name);}\nfunction thumbUrl(d){ if(d.custom) return S.customThumb||thumb(\"Eigen ontwerp\"); const st=imgCache.get(d.file); return st\u0026\u0026st.failed?thumb(d.name):d.file; }\n\n/* ---------- texturen ---------- */\nfunction srcDims(s){return[s.naturalWidth||s.width,s.naturalHeight||s.height];}\nfunction drawPiece(g,src,x,y,w,h,seed){\n const[sw,sh]=srcDims(src),sP=sh\u003e=sw,pP=h\u003e=w,long=Math.max(w,h),short=Math.min(w,h),sLong=sP?sh:sw,sShort=sP?sw:sh;\n let cs=Math.min(sShort*.9,sLong*short/long),cl=cs*long/short;if(cl\u003esLong){cl=sLong;cs=cl*short/long;}\n const cw=sP?cs:cl,ch=sP?cl:cs,ox=hash2(seed,17,3)*(sw-cw),oy=hash2(seed,29,5)*(sh-ch);\n if(sP===pP)g.drawImage(src,ox,oy,cw,ch,x,y,w+.5,h+.5);\n else{g.save();g.translate(x,y+h);g.rotate(-Math.PI/2);g.drawImage(src,ox,oy,cw,ch,0,0,h+.5,w+.5);g.restore();}\n}\nlet JOINT=.5; // sterkte van de voegen in 3D (halve sterkte); voor de AI-render bijna nul\nfunction paintTex(Wcm,Hcm,d,lay,src){\n const k=clamp(1400/Math.max(Wcm,Hcm),.8,4.5); // px per cm\n const c=document.createElement(\"canvas\");c.width=Math.max(4,Math.round(Wcm*k));c.height=Math.max(4,Math.round(Hcm*k));\n const g=c.getContext(\"2d\"),W=c.width,H=c.height,[sw,sh]=srcDims(src);\n const joint=(x,y,w,h)=\u003e{g.strokeStyle=`rgba(0,0,0,${.28*JOINT})`;g.lineWidth=Math.max(1,k*.22*Math.max(.5,JOINT));g.strokeRect(x,y,w,h);g.strokeStyle=`rgba(255,255,255,${.1*JOINT})`;g.lineWidth=1;g.strokeRect(x+1,y+1,w-2,h-2);};\n if(lay.kind===\"panels\"||lay.kind===\"mural\"){\n const pw=lay.w*k,pl=lay.l*k,cols=Math.ceil(W/pw-1e-6),rows=Math.ceil(H/pl-1e-6);\n if(lay.kind===\"mural\"){const s=Math.max(W/sw,H/sh);g.drawImage(src,(W-sw*s)/2,(H-sh*s)/2,sw*s,sh*s);}\n else for(let ci=0;ci\u003ccols;ci++)for(let ri=0;ri\u003crows;ri++){const x=ci*pw,y=H-(ri+1)*pl,tw=pw,th=tw*sh/sw,off=hash2(ci,ri,3)*th;\n g.save();g.beginPath();g.rect(x,y,pw,pl);g.clip();\n for(let t=-1,ty=y-off;ty\u003cy+pl;t++,ty+=th){g.save();g.translate(x+(ci%2?tw:0),ty+(t%2?th:0));g.scale(ci%2?-1:1,t%2?-1:1);g.drawImage(src,0,0,tw,th+.5);g.restore();}\n g.restore();}\n g.fillStyle=`rgba(0,0,0,${.16*JOINT})`;for(let ci=1;ci\u003ccols;ci++)g.fillRect(Math.round(ci*pw),0,Math.max(1,k*.15),H);for(let ri=1;ri\u003crows;ri++)g.fillRect(0,Math.round(H-ri*pl),W,Math.max(1,k*.15));\n } else if(lay.kind===\"tiles\"||lay.kind===\"planks\"){\n const tw=lay.w*k,th=lay.l*k;let n=0;\n for(let ri=0,y=H-th;y\u003e-th;ri++,y-=th){const off=lay.kind===\"planks\"?hash2(ri,5,9)*tw:(lay.neutral?0:(ri%2?tw/2:0)); /* tegels halfsteens */for(let x=-off;x\u003cW;x+=tw){drawPiece(g,src,x,y,tw,th,++n*7+ri);joint(x,y,tw,th);}}\n } else if(lay.kind===\"herring\"){\n const a=lay.w*k,b=lay.l*k,R=Math.hypot(W,H)/2+a*2;g.save();g.translate(W/2,H/2);g.rotate(-Math.PI/4);const J=Math.ceil(R/a)+2,I=Math.ceil(2*R/b)+6;let n=0;\n for(let j=-J;j\u003c=J;j++)for(let i=-I;i\u003c=I;i++){const ox=i*b+j*a,oy=i*b-j*a;if(Math.abs(ox)\u003eR+a||Math.abs(oy)\u003eR+a)continue;drawPiece(g,src,ox,oy,a,b,++n);joint(ox,oy,a,b);drawPiece(g,src,ox+a,oy+b-a,b,a,++n);joint(ox+a,oy+b-a,b,a);}\n g.restore();\n }\n return c;\n}\nconst texCache=new Map();\nfunction buildTex(Wcm,Hcm,d,lay){\n const st=d\u0026\u0026!d.custom?imgCache.get(d.file):null;\n const key=[Math.round(Wcm),Math.round(Hcm),d.name,d.file||\"\",st\u0026\u0026st.ok?1:0,d.custom?S.customId:\"\",JSON.stringify(lay),JOINT].join(\"|\");\n if(texCache.has(key)) return texCache.get(key);\n let c=paintTex(Wcm,Hcm,d,lay,lay.neutral?proc(\"Light Grey\"):source(d));\n try{ c.getContext(\"2d\").getImageData(0,0,1,1); }catch(_){ // afbeelding mag niet in 3D (geen CORS): ingebouwde versie gebruiken\n if(d.file) imgCache.set(d.file,{ok:false,failed:true}); c=paintTex(Wcm,Hcm,d,lay,proc(d.name)); }\n if(texCache.size\u003e40) texCache.delete(texCache.keys().next().value);\n texCache.set(key,c); return c;\n}\nfunction prodSize(p,id){ if(p.sizes){const s=p.sizes.find(x=\u003ex.id===id)||p.sizes[0];return{w:s.w,l:s.l};} return{w:p.w,l:p.l}; }\nfunction sizeTxt(p,sz){ const mm=v=\u003eMath.round(v*10); return (p.layout===\"panels\"||p.layout===\"mural\")?`${mm(sz.l)} × ${mm(sz.w)} mm`:`${mm(sz.w)} × ${mm(sz.l)} mm`; }\nfunction decorOf(p,name){ if(name===\"Eigen ontwerp\"\u0026\u0026p.custom) return {name,custom:true}; return p.decors.find(d=\u003ed.name===name)||p.decors[0]; }\nfunction layFor(p,size){ const s=prodSize(p,size); return {kind:p.layout,w:s.w,l:s.l}; }\n\n/* ---------- 3D ---------- */\nconst viewer=$(\"#viewer\"); let R=null;\nfunction init3D(){\n if(!window.THREE){ viewer.insertAdjacentHTML(\"beforeend\",'\u003cdiv class=\"err\"\u003eDe 3D-weergave kon niet laden. Controleer je internetverbinding en laad de pagina opnieuw.\u003c/div\u003e'); return; }\n let renderer;\n try{ renderer=new THREE.WebGLRenderer({antialias:true,preserveDrawingBuffer:false}); }catch(e){ viewer.insertAdjacentHTML(\"beforeend\",'\u003cdiv class=\"err\"\u003eJe browser ondersteunt geen 3D-weergave (WebGL). Probeer een recente versie van Chrome, Edge of Safari.\u003c/div\u003e'); return; }\n renderer.setPixelRatio(Math.min(2,window.devicePixelRatio||1)); renderer.outputEncoding=THREE.sRGBEncoding;\n viewer.prepend(renderer.domElement);\n const scene=new THREE.Scene(); scene.background=new THREE.Color(0xdfe3e1);\n const camera=new THREE.PerspectiveCamera(50,4/3,.05,100);\n const controls=new THREE.OrbitControls(camera,renderer.domElement); controls.enableDamping=false; controls.screenSpacePanning=true;\n scene.add(new THREE.HemisphereLight(0xffffff,0x9a9a92,.85));\n const dl=new THREE.DirectionalLight(0xffffff,.55); dl.position.set(3,6,4); scene.add(dl);\n scene.add(new THREE.AmbientLight(0xffffff,.18));\n const root=new THREE.Group(); scene.add(root);\n const items=new THREE.Group(); scene.add(items);\n R={renderer,scene,camera,controls,root,items,walls:[],ceil:null};\n controls.addEventListener(\"change\",()=\u003e{ cutaway(); draw(); });\n const ro=new ResizeObserver(()=\u003eresize()); ro.observe(viewer); resize();\n // tik op een wand om hem te kiezen\n let down=null, drag=null; const ray=new THREE.Raycaster(), floorPlane=new THREE.Plane(new THREE.Vector3(0,1,0),0), hitP=new THREE.Vector3();\n const ndc=e=\u003e{const r=renderer.domElement.getBoundingClientRect();return new THREE.Vector2(((e.clientX-r.left)/r.width)*2-1,-((e.clientY-r.top)/r.height)*2+1);};\n const floorAt=e=\u003e{ ray.setFromCamera(ndc(e),camera); return ray.ray.intersectPlane(floorPlane,hitP)?[hitP.x*100,-hitP.z*100]:null; };\n viewer.addEventListener(\"pointerdown\",e=\u003e{ if(e.target!==renderer.domElement) return; down=[e.clientX,e.clientY];\n if(S.view===\"r360\") return; // in 360°-modus is slepen altijd rondkijken\n ray.setFromCamera(ndc(e),camera); const hit=ray.intersectObjects(items.children,true).find(h=\u003eh.object.userData.item);\n if(!hit) return; e.stopPropagation(); const i=hit.object.userData.idx, it=S.items[i], f=floorAt(e)||[it.x,it.y];\n drag={i,off:[it.x-f[0],it.y-f[1]],moved:false}; controls.enabled=false; try{viewer.setPointerCapture(e.pointerId);}catch(_){}\n if(S.selItem!==i){ S.selItem=i; rebuildItems(); renderItemsUI(); } },{capture:true});\n viewer.addEventListener(\"pointermove\",e=\u003e{ if(!drag) return; const f=floorAt(e); if(!f) return; const it=S.items[drag.i];\n it.x=f[0]+drag.off[0]; it.y=f[1]+drag.off[1]; drag.moved=true; const g=items.children.find(c=\u003ec.isGroup\u0026\u0026c.userData.idx===drag.i); if(g){ g.position.copy(W3(it.x,it.y,0)); } draw(); });\n const endDrag=()=\u003e{ if(!drag) return; const it=S.items[drag.i]; if(drag.moved){ it.att=null; snapItem(it,false); S.defaultItems=false; } drag=null; down=null; controls.enabled=true; rebuildItems(); renderPlan(); renderItemsUI(); if(S.step!==4) goStep(4); };\n viewer.addEventListener(\"pointerup\",endDrag); viewer.addEventListener(\"pointercancel\",endDrag);\n renderer.domElement.addEventListener(\"pointerup\",e=\u003e{ if(S.view===\"r360\"||drag||!down||Math.hypot(e.clientX-down[0],e.clientY-down[1])\u003e6) return; pick(e); });\n}\nfunction resize(){ if(!R) return; const w=viewer.clientWidth,h=viewer.clientHeight; if(!w||!h) return; R.renderer.setSize(w,h,false); R.camera.aspect=w/h; R.camera.updateProjectionMatrix(); draw(); }\nlet drawPending=false; function draw(){ if(!R||drawPending) return; drawPending=true; requestAnimationFrame(()=\u003e{drawPending=false; R.renderer.render(R.scene,R.camera);}); }\nconst W3=(x,y,h)=\u003enew THREE.Vector3(x/100,h/100,-y/100); // plan-cm → wereld-m\nfunction canvasTex(c){ const t=new THREE.CanvasTexture(c); t.encoding=THREE.sRGBEncoding; t.anisotropy=R.renderer.capabilities.getMaxAnisotropy(); return t; }\nfunction disposeGroup(g){ g.traverse(o=\u003e{ if(o.geometry) o.geometry.dispose(); if(o.material){ (Array.isArray(o.material)?o.material:[o.material]).forEach(m=\u003e{ if(m.map) m.map.dispose(); m.dispose(); }); } }); }\nfunction labelSprite(txt,active){\n const c=document.createElement(\"canvas\");c.width=c.height=96;const g=c.getContext(\"2d\");\n g.beginPath();g.arc(48,48,40,0,7);g.fillStyle=active?\"#0d6a6e\":\"#ffffff\";g.fill();g.lineWidth=5;g.strokeStyle=active?\"#ffffff\":\"#0d6a6e\";g.stroke();\n g.fillStyle=active?\"#ffffff\":\"#0d6a6e\";g.font=\"700 46px system-ui,sans-serif\";g.textAlign=\"center\";g.textBaseline=\"middle\";g.fillText(txt,48,51);\n const t=new THREE.CanvasTexture(c); const s=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false,transparent:true})); s.scale.set(.32,.32,1); s.renderOrder=10; return s;\n}\nfunction wallMesh(gw,w){\n const H=S.H, cover=clad(w)?coverOf(w):0, L=gw.L;\n const xs=new Set([0,L]), ys=new Set([0,H]); if(cover\u003e0\u0026\u0026cover\u003cH) ys.add(cover);\n const ops=w.openings.map(o=\u003e({x0:clamp(o.x,0,L),x1:clamp(o.x+o.w,0,L),y0:clamp(o.y,0,H),y1:clamp(o.y+o.h,0,H),t:o.type})).filter(o=\u003eo.x1\u003eo.x0\u0026\u0026o.y1\u003eo.y0);\n ops.forEach(o=\u003e{xs.add(o.x0);xs.add(o.x1);ys.add(o.y0);ys.add(o.y1);});\n const X=[...xs].sort((a,b)=\u003ea-b),Y=[...ys].sort((a,b)=\u003ea-b);\n const bufs={cov:{p:[],uv:[]},top:{p:[],uv:[]}};\n const P=(s,t)=\u003eW3(gw.p[0]+gw.d[0]*s,gw.p[1]+gw.d[1]*s,t);\n for(let a=0;a\u003cX.length-1;a++)for(let b=0;b\u003cY.length-1;b++){\n const x0=X[a],x1=X[a+1],y0=Y[b],y1=Y[b+1],cx=(x0+x1)/2,cy=(y0+y1)/2;\n if(ops.some(o=\u003ecx\u003eo.x0\u0026\u0026cx\u003co.x1\u0026\u0026cy\u003eo.y0\u0026\u0026cy\u003co.y1)) continue;\n const reg=cy\u003ccover?\"cov\":\"top\",B=bufs[reg],ch=reg===\"cov\"?cover:H;\n const q=[[x0,y0],[x1,y0],[x1,y1],[x0,y0],[x1,y1],[x0,y1]];\n q.forEach(([s,t])=\u003e{const v=P(s,t);B.p.push(v.x,v.y,v.z);B.uv.push(s/L,t/ch);});\n }\n const g=new THREE.Group();\n const mk=(B,mat)=\u003e{ if(!B.p.length) return; const geo=new THREE.BufferGeometry(); geo.setAttribute(\"position\",new THREE.Float32BufferAttribute(B.p,3)); geo.setAttribute(\"uv\",new THREE.Float32BufferAttribute(B.uv,2)); geo.computeVertexNormals(); const m=new THREE.Mesh(geo,mat); m.userData.wall=gw.i; g.add(m); };\n if(cover\u003e0){ const p=WALL[w.prod],d=decorOf(p,w.decor); mk(bufs.cov,new THREE.MeshLambertMaterial({map:canvasTex(buildTex(L,cover,d,layFor(p,w.size))),side:THREE.DoubleSide})); }\n mk(bufs.top,new THREE.MeshLambertMaterial({color:0xeceae5,side:THREE.DoubleSide}));\n // deuren en ramen: vlak net achter de wand\n ops.forEach(o=\u003e{ const off=-3, c=[(o.x0+o.x1)/2,(o.y0+o.y1)/2];\n if(o.t===\"nis\"){ // nis: achterwand met hetzelfde decor iets terug\n const geo=new THREE.PlaneGeometry((o.x1-o.x0)/100,(o.y1-o.y0)/100); const mat=cover\u003e0\u0026\u0026c[1]\u003ccover?new THREE.MeshLambertMaterial({map:canvasTex(buildTex(o.x1-o.x0,o.y1-o.y0,decorOf(WALL[w.prod],w.decor),layFor(WALL[w.prod],w.size))),side:THREE.DoubleSide,color:0xd8d8d8}):new THREE.MeshLambertMaterial({color:0xd9d7d2,side:THREE.DoubleSide});\n const m=new THREE.Mesh(geo,mat); placeOnWall(m,gw,c,-12); g.add(m); return; }\n const geo=new THREE.PlaneGeometry((o.x1-o.x0)/100,(o.y1-o.y0)/100);\n const mat=o.t===\"raam\"?new THREE.MeshBasicMaterial({color:0xcfe4ee,side:THREE.DoubleSide}):new THREE.MeshLambertMaterial({color:0xf3f1ec,side:THREE.DoubleSide});\n const m=new THREE.Mesh(geo,mat); placeOnWall(m,gw,c,off); g.add(m);\n const fr=new THREE.LineSegments(new THREE.EdgesGeometry(geo),new THREE.LineBasicMaterial({color:0x5b6366})); placeOnWall(fr,gw,c,-.5); g.add(fr);\n if(o.t===\"deur\"){ const hnd=new THREE.Mesh(new THREE.BoxGeometry(.12,.025,.04),new THREE.MeshLambertMaterial({color:0x9aa0a3})); placeOnWall(hnd,gw,[o.x1-9,o.y0+105],2); g.add(hnd); }\n });\n // bovenrand\n const e=[P(0,H),P(L,H)]; g.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(e),new THREE.LineBasicMaterial({color:0x7d8587})));\n if(cover\u003e0\u0026\u0026cover\u003cH){ const e2=[P(0,cover),P(L,cover)]; g.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(e2),new THREE.LineBasicMaterial({color:0x8d9496}))); }\n return g;\n}\nfunction placeOnWall(obj,gw,c,offCm){ // c=[s,t] in cm langs de wand; offCm\u003e0 = de kamer in\n const pos=W3(gw.p[0]+gw.d[0]*c[0]+gw.nin[0]*offCm,gw.p[1]+gw.d[1]*c[0]+gw.nin[1]*offCm,c[1]); obj.position.copy(pos);\n const nW=new THREE.Vector3(gw.nin[0],0,-gw.nin[1]); obj.lookAt(pos.clone().add(nW)); }\n\n/* ---------- 3D-objecten uit de bibliotheek (bouwstenen in cm) ---------- */\nfunction newItem(id,extra){ const def=libDef(id); const it={id,w:def.w,d:def.d,phi:0,x:0,y:0};\n if(def.h!=null) it.h=def.h; if(def.z!=null) it.z=def.z; if(def.pal) it.color=def.pal===\"cab\"?(id===\"tafel\"||id===\"stoel\"||id===\"bed\"||id===\"tv\"?1:0):0; if(id===\"douche\") it.glass=0;\n return Object.assign(it,extra||{}); }\nfunction itemGroup(it){\n const g=new THREE.Group(), w=it.w, d=it.d, def=libDef(it.id), Mat=(c,o)=\u003enew THREE.MeshLambertMaterial(Object.assign({color:c},o||{}));\n const pal=def.pal?PAL[def.pal][it.color||0][1]:0xf4f4f1, H=it.h!=null?it.h:def.h, Z=it.z!=null?it.z:def.z;\n const white=Mat(0xf6f6f3), cab=Mat(pal), dark=Mat(0x2e3236), chrome=Mat(0xb9bec1), fabric=Mat(0x8b9aa3), stone=Mat(0x3d4144),\n line=Mat(pal\u003c0x606060?0x8a8f92:0x2e3236), mirror=new THREE.MeshBasicMaterial({color:0xcdd9dd});\n const box=(bw,bh,bd,x,y,z,m)=\u003e{const o=new THREE.Mesh(new THREE.BoxGeometry(Math.max(.1,bw),Math.max(.1,bh),Math.max(.1,bd)),m);o.position.set(x,y+bh/2,z);g.add(o);return o;};\n const cyl=(rt,rb,h,x,y,z,m,sx=1,sz=1)=\u003e{const o=new THREE.Mesh(new THREE.CylinderGeometry(rt,rb,h,32),m);o.position.set(x,y+h/2,z);o.scale.set(sx,1,sz);g.add(o);return o;};\n const legs=(lw,ld,h,m,t=5)=\u003e{[[-1,-1],[1,-1],[-1,1],[1,1]].forEach(([a,b])=\u003ebox(t,h,t,a*(lw/2-t/2-2),0,b*(ld/2-t/2-2),m));};\n const doors=(bw,bh,y,z,n)=\u003e{ for(let k=1;k\u003cn;k++) box(.6,bh-4,.6,-bw/2+k*bw/n,y+2,z,line); };\n switch(it.id){\n case \"toilet\": box(16,22,1.5,0,92,-d/2+.8,white); box(w*.7,26,10,0,14,-d/2+5,white); cyl(w/2,w/2*.78,26,0,14,-d/2+(d-6)/2+6,white,1,(d-6)/w); cyl(w/2,w/2,2.5,0,40,-d/2+(d-6)/2+6,Mat(0xffffff),1,(d-6)/w); break;\n case \"douche\": { const prof=Mat(PAL.shower[it.color||0][1]), gl=GLASS[it.glass||0], glass=new THREE.MeshLambertMaterial({color:gl[1],transparent:true,opacity:gl[2],side:THREE.DoubleSide});\n box(w,3,d,0,0,0,Mat(0xd6d9da)); box(w*.7,.6,4,0,3,-d/2+8,prof);\n box(1,H,d*.75,w/2-1,3,-d/2+d*.375,glass); box(2,2,d*.75,w/2-1,H+3,-d/2+d*.375,prof); box(2.5,H,2.5,w/2-1,3,-d/2+d*.75,prof);\n box(2,2,32,-w/4,H+5,-d/2+16,prof); cyl(13,13,1.5,-w/4,H+3,-d/2+32,prof); box(2,110,2,-w/4,95,-d/2+1,prof); } break;\n case \"bad\": box(w,56,d,0,0,0,white); box(w-14,.6,d-14,0,56,0,Mat(0xdfe6e8)); cyl(1.4,1.4,14,0,56,-d/2+4,chrome); break;\n case \"vbad\": cyl(d/2,d/2*.84,58,0,0,0,white,w/d,1); cyl(d/2-6,d/2-6,.6,0,58,0,Mat(0xdfe6e8),(w-12)/(d-12),1); box(2,90,2,w/2+10,0,0,chrome); break;\n case \"meubel\": box(w,H,d,0,Z,0,cab); box(w-4,.6,.6,0,Z+H*.5,d/2+.3,line); box(w,4,d+1,0,Z+H,0,white);\n { const r=Math.min(w*.3,22); cyl(r,r,.8,0,Z+H+4,2,Mat(0xe2e6e7),1,.75); } cyl(1.2,1.2,16,0,Z+H+4,-d/2+7,chrome); box(2,2,10,0,Z+H+18,-d/2+12,chrome); break;\n case \"spiegel\": box(w,H,2,0,Z,-.5,mirror); box(w+1,H+1,1,0,Z-.5,-1.5,dark); break;\n case \"spiegelkast\": box(w,H,d-1,0,Z,-.5,cab); { const n=Math.max(1,Math.round(w/40)); for(let k=0;k\u003cn;k++) box(w/n-1,H-2,.6,-w/2+(k+.5)*w/n,Z+1,d/2,mirror); } box(w-6,1,4,0,Z-1,d/2-2,Mat(0xfff4d6)); break;\n case \"wandkast\": box(w,H,d,0,Z,0,cab); doors(w,H,Z,d/2+.3,Math.max(1,Math.round(w/40))); break;\n case \"kolom\": box(w,H,d,0,Z,0,cab); box(.6,H-4,.6,w*.3,Z+2,d/2+.3,line); break;\n case \"radiator\": box(2.5,H,2.5,-w/2+1.5,Z,0,white); box(2.5,H,2.5,w/2-1.5,Z,0,white); for(let i=0;i\u003cMath.floor(H/11);i++) box(w,2.2,2.2,0,Z+2+i*11,0,white); break;\n case \"tv\": box(w,42,d,0,10,0,cab); legs(w,d,10,dark,3); { const tw=Math.min(w*.8,165),th=tw*.57; box(8,20,8,0,52,-d/2+8,dark); box(tw,th,4,0,72,-d/2+8,dark); box(tw-3,th-3,.4,0,73.5,-d/2+10.2,Mat(0x1a1f23)); } break;\n case \"kast\": box(w,H,d,0,0,0,cab); doors(w,H,0,d/2+.3,Math.max(1,Math.round(w/50))); break;\n case \"bank\": box(w,40,d,0,4,0,fabric); box(w,42,18,0,44,-d/2+9,fabric); box(16,22,d,-w/2+8,44,0,fabric); box(16,22,d,w/2-8,44,0,fabric); legs(w,d,4,dark,4);\n for(let k=1;k\u003c3;k++) box(.8,1,d-22,-w/2+16+k*(w-32)/3,44,6,Mat(0x77868f)); break;\n case \"fauteuil\": box(w,38,d,0,6,0,fabric); box(w,46,16,0,44,-d/2+8,fabric); box(13,22,d,-w/2+6.5,44,0,fabric); box(13,22,d,w/2-6.5,44,0,fabric); legs(w,d,6,dark,4); break;\n case \"tafel\": box(w,4,d,0,71,0,cab); legs(w,d,71,cab,6); break;\n case \"stoel\": box(w,4,d,0,44,0,cab); legs(w,d,44,dark,3); box(w,42,3,0,48,-d/2+1.5,cab); break;\n case \"plant\": cyl(15,11,32,0,0,0,Mat(0x4a4f52)); { const s1=new THREE.Mesh(new THREE.SphereGeometry(24,20,14),Mat(0x5c8a4d)); s1.position.set(0,62,0); s1.scale.set(1,1.25,1); g.add(s1); const s2=new THREE.Mesh(new THREE.SphereGeometry(15,16,12),Mat(0x6f9c5c)); s2.position.set(8,88,4); g.add(s2); } break;\n case \"urinoir\": box(w*.86,62,7,0,48,-d/2+3.5,white); cyl(w/2,w/2*.62,46,0,54,-d/2+d*.55,white,1,(d*.8)/w); box(w*.5,10,d*.35,0,58,-d/2+d*.62,Mat(0xe8e8e4)); box(10,14,1.2,0,122,-d/2+.6,chrome); break;\n case \"schot\": box(w,85,d-1,0,45,.5,Mat(0xdcdedb)); box(Math.max(w,3),6,2,0,90,-d/2+1,chrome); break;\n case \"fontein\": box(w,13,d,0,80,0,white); box(w-6,2,d-7,0,91.5,2,Mat(0xe4e6e5)); cyl(1.1,1.1,12,0,93,-d/2+4,chrome); box(2,2,7,0,103,-d/2+7,chrome); cyl(1.6,1.6,30,0,50,-d/2+4,chrome); break;\n case \"bed\": box(w,28,d,0,0,0,cab); box(w-4,20,d-8,0,28,4,Mat(0xf4f4f1)); box(w-2,6,d*.62,0,47,d*.18,fabric); box(w*.4,10,30,-w*.24,48,-d/2+24,white); box(w*.4,10,30,w*.24,48,-d/2+24,white); box(w,105,6,0,0,-d/2-3,Mat(0x6c7a82)); break;\n case \"bureau\": box(w,3,d,0,72,0,cab); legs(w,d,72,dark,4); break;\n case \"keuken\": box(w,82,d-3,0,8,-1.5,cab); box(w,8,d-8,0,0,-4,dark); box(w,4,d,0,90,0,stone); box(w,70,35,0,145,-d/2+17.5,cab);\n box(60,.6,50,w/4,94,0,Mat(0x111416)); box(50,.8,40,-w/4,93.7,0,chrome); cyl(1.2,1.2,26,-w/4,94,-d/2+6,chrome);\n { const n=Math.max(1,Math.round(w/60)); for(let k=1;k\u003cn;k++){ box(.6,78,.6,-w/2+k*w/n,10,d/2-2.6,line); box(.6,66,.6,-w/2+k*w/n,147,-d/2+35.3,line); } } break;\n }\n g.scale.setScalar(.01); g.position.copy(W3(it.x,it.y,0)); g.rotation.y=it.phi; return g;\n}\nfunction itemAxes(it){ return {x:[Math.cos(it.phi),Math.sin(it.phi)], f:[Math.sin(it.phi),-Math.cos(it.phi)]}; }\nfunction snapItem(it,force){\n const def=libDef(it.id), g=wallsGeo(); let best=null;\n g.forEach(gw=\u003e{ const rx=it.x-gw.p[0], ry=it.y-gw.p[1], sAlong=rx*gw.d[0]+ry*gw.d[1], dist=rx*gw.nin[0]+ry*gw.nin[1];\n if(sAlong\u003c-30||sAlong\u003egw.L+30||dist\u003c-30) return; const sc=Math.abs(dist-it.d/2); if(!best||sc\u003cbest.sc) best={gw,sAlong,dist,sc}; });\n if(!best) return; if(!(force||def.wall) \u0026\u0026 best.dist-it.d/2\u003e25) return;\n const gw=best.gw, s=clamp(best.sAlong,Math.min(it.w/2,gw.L/2),Math.max(gw.L-it.w/2,gw.L/2));\n it.phi=Math.atan2(gw.nin[0],-gw.nin[1]); it.x=gw.p[0]+gw.d[0]*s+gw.nin[0]*(it.d/2+.5); it.y=gw.p[1]+gw.d[1]*s+gw.nin[1]*(it.d/2+.5); it.att={i:gw.i,s};\n}\nfunction reattach(){ // na een maatwijziging: objecten blijven aan hun wand, losse objecten blijven binnen de ruimte\n const g=wallsGeo(), xs=PTS.map(p=\u003ep[0]), ys=PTS.map(p=\u003ep[1]);\n S.items.forEach(it=\u003e{ const gw=it.att\u0026\u0026g[it.att.i];\n if(gw){ const s=clamp(it.att.s,Math.min(it.w/2,gw.L/2),Math.max(gw.L-it.w/2,gw.L/2)); it.phi=Math.atan2(gw.nin[0],-gw.nin[1]); it.x=gw.p[0]+gw.d[0]*s+gw.nin[0]*(it.d/2+.5); it.y=gw.p[1]+gw.d[1]*s+gw.nin[1]*(it.d/2+.5); }\n else { it.att=null; it.x=clamp(it.x,Math.min(...xs)+10,Math.max(...xs)-10); it.y=clamp(it.y,Math.min(...ys)+10,Math.max(...ys)-10); } }); }\nfunction rebuildItems(){\n if(!R) return; disposeGroup(R.items); R.items.clear();\n S.items.forEach((it,i)=\u003e{ const g=itemGroup(it); g.userData.idx=i; g.traverse(o=\u003e{ if(o.isMesh){ o.userData.item=true; o.userData.idx=i; } }); R.items.add(g);\n if(i===S.selItem){ const h=new THREE.BoxHelper(g,0x0d6a6e); h.material.depthTest=false; h.renderOrder=11; h.userData.helper=true; R.items.add(h); } });\n draw(); if(!EV) renderIElev();\n}\nfunction addItem(id){\n const c=centroid(), it=newItem(id,{x:c[0],y:c[1]});\n if(id!==\"vbad\"\u0026\u0026id!==\"tafel\"\u0026\u0026id!==\"stoel\"\u0026\u0026id!==\"plant\"\u0026\u0026id!==\"fauteuil\") putOnWall(it,S.sel,freeSpot(it,S.sel));\n S.items.push(it); S.selItem=S.items.length-1; S.defaultItems=false; rebuildItems(); renderItemsUI(); renderPlan();\n}\n/* ---------- 3D-weergave loopt mee bij scrollen (in de websitepagina) ---------- */\n(()=\u003e{ const fe=window.frameElement; if(!fe) return; let P; try{ P=window.parent; void P.document.body; }catch(_){ return; }\n document.documentElement.classList.add(\"embedded\"); const stage=$(\".stage\"), layout=$(\".layout\");\n const hdr=()=\u003e{ let h=0; P.document.querySelectorAll(\"#wpadminbar,header,.site-header\").forEach(el=\u003e{ const cs=P.getComputedStyle(el); if(cs.position===\"fixed\"||cs.position===\"sticky\"){ const r=el.getBoundingClientRect(); if(r.top\u003c=1\u0026\u0026r.bottom\u003eh\u0026\u0026r.bottom\u003cP.innerHeight*.4) h=r.bottom; } }); return h; };\n let last=-1, lastV=\"\";\n const upd=()=\u003e{ const stacked=window.innerWidth\u003c=920;\n const v=stacked?Math.round(P.innerHeight*.42)+\"px\":\"\"; if(v!==lastV){ document.documentElement.style.setProperty(\"--pvh\",v||\"none\"); lastV=v; }\n const fr=fe.getBoundingClientRect(), lt=layout.getBoundingClientRect().top+window.scrollY;\n let t=-fr.top+hdr()+8-lt; t=Math.round(Math.max(0,Math.min(t,layout.offsetHeight-stage.offsetHeight)));\n if(t!==last){ stage.style.transform=t\u003e0?`translateY(${t}px)`:\"\"; stage.classList.toggle(\"pinned\",stacked\u0026\u0026t\u003e0); last=t; } };\n P.addEventListener(\"scroll\",upd,{passive:true}); P.addEventListener(\"resize\",upd); window.addEventListener(\"resize\",upd); setInterval(upd,400); upd(); })();\n\n/* ---------- plaatsen per wand ---------- */\nconst leftFlip=gw=\u003e{ const lx=gw.nin[1], ly=-gw.nin[0]; return gw.d[0]*lx+gw.d[1]*ly\u003e0; }; // true: begin van de wand ligt rechts\nfunction putOnWall(it,i,s){ const gw=wallsGeo()[i]; if(!gw) return; s=clamp(s,Math.min(it.w/2,gw.L/2),Math.max(gw.L-it.w/2,gw.L/2));\n it.att={i,s}; it.phi=Math.atan2(gw.nin[0],-gw.nin[1]); it.x=gw.p[0]+gw.d[0]*s+gw.nin[0]*(it.d/2+.5); it.y=gw.p[1]+gw.d[1]*s+gw.nin[1]*(it.d/2+.5); }\nconst leftOf=(gw,it)=\u003eleftFlip(gw)?gw.L-it.att.s-it.w/2:it.att.s-it.w/2; // afstand linkerhoek → linkerzijde object\nconst sFromLeft=(gw,xl,w)=\u003eleftFlip(gw)?gw.L-(xl+w/2):xl+w/2;\nconst VFALL={toilet:[30,80],douche:[0,210],bad:[0,58],vbad:[0,62],tv:[0,170],kast:[0,210],bank:[0,85],fauteuil:[0,85],tafel:[0,75],stoel:[0,90],plant:[0,110],bed:[0,55],bureau:[0,75],keuken:[0,215]};\nfunction vExt(it,i){ // onder- en bovenkant in cm\n if(R){ const g=R.items.children.find(o=\u003eo.userData.idx===i\u0026\u0026!o.userData.helper); if(g){ const b=new THREE.Box3().setFromObject(g); if(!b.isEmpty()) return [Math.round(b.min.y*100),Math.round(b.max.y*100)]; } }\n const def=libDef(it.id), z=it.z!=null?it.z:(def.z||0), h=it.h!=null?it.h:def.h; if(h!=null) return [z,z+h]; return VFALL[it.id]||[0,80]; }\nfunction freeSpot(it,i){ const gw=wallsGeo()[i], others=S.items.filter(o=\u003eo!==it\u0026\u0026o.att\u0026\u0026o.att.i===i);\n const ex=vExt(it,-1); let best=null;\n for(let c=it.w/2;c\u003c=Math.max(it.w/2,gw.L-it.w/2)+.1;c+=5){ let ov=0; others.forEach(o=\u003e{ const oe=vExt(o,S.items.indexOf(o)); if(oe[1]\u003c=ex[0]||oe[0]\u003e=ex[1]) return; ov+=Math.max(0,Math.min(c+it.w/2,o.att.s+o.w/2)-Math.max(c-it.w/2,o.att.s-o.w/2)); });\n if(!best||ov\u003cbest.ov) best={c,ov}; if(ov===0) break; }\n return best?best.c:gw.L/2; }\nlet EV=null; // sleepstatus in het wandaanzicht\nfunction renderIElev(){\n const svg=$(\"#iElev\"); if(!svg) return; const g=wallsGeo(); if(S.sel\u003e=g.length) S.sel=0; const gw=g[S.sel];\n $(\"#iWalls\").innerHTML=g.map(w=\u003e`\u003cbutton class=\"chip\" data-iwall=\"${w.i}\" aria-pressed=\"${w.i===S.sel}\" type=\"button\"\u003eWand ${w.i+1}\u003c/button\u003e`).join(\"\");\n if(!gw){ svg.innerHTML=\"\"; return; }\n const L=gw.L, H=S.H, fs=Math.max(7,(L+40)/34), pad=fs*1.4, flip=leftFlip(gw), X=x=\u003eflip?L-x:x;\n svg.setAttribute(\"viewBox\",`${-pad} ${-pad} ${L+2*pad} ${H+pad+fs*3.2}`);\n let o=`\u003crect class=\"ev-wall\" x=\"0\" y=\"0\" width=\"${L}\" height=\"${H}\"/\u003e`;\n (S.walls[S.sel].openings||[]).forEach(op=\u003e{ const x0=flip?L-op.x-op.w:op.x; o+=`\u003crect class=\"ev-open\" x=\"${x0}\" y=\"${H-op.y-op.h}\" width=\"${op.w}\" height=\"${op.h}\"/\u003e\u003ctext x=\"${x0+op.w/2}\" y=\"${H-op.y-op.h/2}\" font-size=\"${fs*.85}\" text-anchor=\"middle\" fill-opacity=\".6\"\u003e${op.type===\"deur\"?\"deur\":\"raam\"}\u003c/text\u003e`; });\n const on=S.items.map((it,i)=\u003e({it,i})).filter(({it})=\u003eit.att\u0026\u0026it.att.i===S.sel);\n on.sort((a,b)=\u003e(a.i===S.selItem)-(b.i===S.selItem));\n on.forEach(({it,i})=\u003e{ const [z0,z1]=vExt(it,i), x0=leftOf(gw,it), sel=i===S.selItem;\n o+=`\u003crect class=\"ev-item${sel?\" sel\":\"\"}\" data-ei=\"${i}\" x=\"${x0}\" y=\"${H-z1}\" width=\"${it.w}\" height=\"${Math.max(2,z1-z0)}\" rx=\"1.5\"/\u003e`;\n if(z1-z0\u003efs*1.2\u0026\u0026it.w\u003efs*2.2) o+=`\u003ctext x=\"${x0+it.w/2}\" y=\"${H-(z0+z1)/2+fs*.35}\" font-size=\"${Math.min(fs,it.w/6)}\" text-anchor=\"middle\"\u003e${esc(libDef(it.id).name)}\u003c/text\u003e`; });\n o+=`\u003cline class=\"ev-floor\" x1=\"${-pad*.6}\" y1=\"${H}\" x2=\"${L+pad*.6}\" y2=\"${H}\"/\u003e`;\n const si=S.items[S.selItem];\n if(si\u0026\u0026si.att\u0026\u0026si.att.i===S.sel){ const [z0,z1]=vExt(si,S.selItem), x0=leftOf(gw,si), x1=x0+si.w, y=H+fs*1.3, r=L-x1;\n const dim=(a,b,t)=\u003eb-a\u003c1?\"\":`\u003cline class=\"ev-dim\" x1=\"${a}\" y1=\"${y}\" x2=\"${b}\" y2=\"${y}\"/\u003e\u003cline class=\"ev-dim\" x1=\"${a}\" y1=\"${y-fs*.4}\" x2=\"${a}\" y2=\"${y+fs*.4}\"/\u003e\u003cline class=\"ev-dim\" x1=\"${b}\" y1=\"${y-fs*.4}\" x2=\"${b}\" y2=\"${y+fs*.4}\"/\u003e\u003ctext class=\"ev-dimt\" x=\"${(a+b)/2}\" y=\"${y+fs*1.35}\" font-size=\"${fs*.95}\" text-anchor=\"middle\"\u003e${t}\u003c/text\u003e`;\n o+=dim(0,x0,Math.round(x0)+\" cm\")+dim(x1,L,Math.round(r)+\" cm\");\n if(z0\u003e2) o+=`\u003cline class=\"ev-dim\" x1=\"${x1+fs*.6}\" y1=\"${H}\" x2=\"${x1+fs*.6}\" y2=\"${H-z0}\"/\u003e\u003ctext class=\"ev-dimt\" x=\"${x1+fs*.9}\" y=\"${H-z0/2+fs*.35}\" font-size=\"${fs*.95}\"\u003e${Math.round(z0)} cm\u003c/text\u003e`; }\n o+=`\u003ctext x=\"0\" y=\"${-pad*.3}\" font-size=\"${fs*.85}\" fill-opacity=\".6\"\u003elinks\u003c/text\u003e\u003ctext x=\"${L}\" y=\"${-pad*.3}\" font-size=\"${fs*.85}\" text-anchor=\"end\" fill-opacity=\".6\"\u003erechts\u003c/text\u003e`;\n svg.innerHTML=o;\n const loose=S.items.filter(it=\u003e!it.att).length;\n $(\"#iElevNote\").textContent=`Wand ${S.sel+1} · ${fmt(L,0)} cm breed · ${S.H} cm hoog, gezien vanuit de ruimte. ${on.length?\"Sleep een object om het te verschuiven.\":\"Nog niets op deze wand.\"}${loose?` ${loose} object${loose\u003e1?\"en staan\":\" staat\"} los in de ruimte.`:\"\"}`;\n}\n$(\"#iWalls\").addEventListener(\"click\",e=\u003e{ const b=e.target.closest(\"[data-iwall]\"); if(!b) return; S.sel=+b.dataset.iwall; const si=S.items[S.selItem]; if(si\u0026\u0026(!si.att||si.att.i!==S.sel)) S.selItem=-1; rebuildItems(); renderItemsUI(); renderPlan(); });\n(()=\u003e{ const svg=$(\"#iElev\"), pt=e=\u003e{ const m=svg.getScreenCTM(); if(!m) return null; const q=new DOMPoint(e.clientX,e.clientY).matrixTransform(m.inverse()); return q; };\n let raf=0; const redraw=()=\u003e{ if(raf) return; raf=requestAnimationFrame(()=\u003e{ raf=0; rebuildItems(); }); };\n svg.addEventListener(\"pointerdown\",e=\u003e{ const r=e.target.closest(\"[data-ei]\"); if(!r) return; const i=+r.dataset.ei, it=S.items[i], q=pt(e); if(!q) return; const gw=wallsGeo()[it.att.i], [z0,z1]=vExt(it,i);\n EV={i,dx:q.x-leftOf(gw,it),dy:q.y-(S.H-z1),h:z1-z0,vert:it.z!=null,moved:false}; svg.setPointerCapture(e.pointerId);\n if(S.selItem!==i){ S.selItem=i; rebuildItems(); renderItemsUI(); } e.preventDefault(); });\n svg.addEventListener(\"pointermove\",e=\u003e{ if(!EV) return; const q=pt(e); if(!q) return; const it=S.items[EV.i], gw=wallsGeo()[it.att.i];\n let xl=Math.round(q.x-EV.dx); xl=clamp(xl,0,Math.max(0,gw.L-it.w)); if(xl\u003c3) xl=0; if(gw.L-it.w-xl\u003c3) xl=Math.max(0,gw.L-it.w);\n putOnWall(it,it.att.i,sFromLeft(gw,xl,it.w));\n if(EV.vert){ const top=q.y-EV.dy, z=Math.round(clamp(S.H-top-EV.h,0,Math.max(0,S.H-EV.h))); it.z=z; }\n EV.moved=true; S.defaultItems=false; redraw(); });\n const up=()=\u003e{ if(!EV) return; const m=EV.moved; EV=null; if(m){ rebuildItems(); renderItemsUI(); renderPlan(); } };\n svg.addEventListener(\"pointerup\",up); svg.addEventListener(\"pointercancel\",up); })();\nfunction coverOf(w){ return w.cover===\"full\"?S.H:Math.min(S.H,w.cover===\"120\"?120:w.coverCm); }\nfunction rebuild3D(){\n if(!R) return; disposeGroup(R.root); R.root.clear(); R.walls=[];\n const geo=wallsGeo();\n // vloer\n const shape=new THREE.Shape(PTS.map(p=\u003enew THREE.Vector2(p[0]/100,p[1]/100)));\n const fg=new THREE.ShapeGeometry(shape); fg.rotateX(-Math.PI/2);\n const xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]),mx=Math.min(...xs),my=Math.min(...ys),bw=Math.max(...xs)-mx,bd=Math.max(...ys)-my;\n const pos=fg.attributes.position,uv=[]; for(let i=0;i\u003cpos.count;i++){ const x=pos.getX(i)*100,y=-pos.getZ(i)*100; uv.push((x-mx)/bw,(y-my)/bd); } fg.setAttribute(\"uv\",new THREE.Float32BufferAttribute(uv,2));\n const F=S.floor, fp=FLOOR[F.prod];\n const fTex=floorOn()?buildTex(bw,bd,decorOf(fp,F.decor),layFor(fp,F.size)):buildTex(bw,bd,{name:\"Light Grey\"},{kind:\"tiles\",w:60,l:60,neutral:true});\n const floor=new THREE.Mesh(fg,new THREE.MeshLambertMaterial({map:canvasTex(fTex),side:THREE.DoubleSide})); floor.userData.floor=true; R.root.add(floor);\n // plafond (alleen zichtbaar van binnen)\n const cg=new THREE.ShapeGeometry(shape); cg.rotateX(-Math.PI/2); cg.translate(0,S.H/100,0);\n R.ceil=new THREE.Mesh(cg,new THREE.MeshLambertMaterial({color:0xf4f3f0,side:THREE.DoubleSide})); R.ceil.visible=S.view!==\"over\"; R.root.add(R.ceil);\n // wanden\n geo.forEach(gw=\u003e{ const g=wallMesh(gw,S.walls[gw.i]); const lab=labelSprite(String(gw.i+1),gw.i===S.sel);\n const lp=W3(gw.p[0]+gw.d[0]*gw.L/2+gw.nin[0]*22,gw.p[1]+gw.d[1]*gw.L/2+gw.nin[1]*22,8); lab.position.copy(lp); R.root.add(lab);\n R.root.add(g); R.walls.push({g,gw,lab}); });\n R.cab=[]; { const cg=cabGeo(perimGeo()); if(cg){ const mat=new THREE.MeshLambertMaterial({color:0xdcd9d3}), ang=Math.atan2(cg.bw.d[1],cg.bw.d[0]);\n cg.solids.forEach(r=\u003e{ const ins=.4, bwid=r.s1-r.s0-2*ins, bdep=r.u1-r.u0-2*ins, bh=r.z1-r.z0-(r.z1\u003e=S.H?.2:0); if(bwid\u003c=0||bdep\u003c=0||bh\u003c=0) return;\n const m=new THREE.Mesh(new THREE.BoxGeometry(bwid/100,bh/100,bdep/100),mat), c=cg.P((r.s0+r.s1)/2,(r.u0+r.u1)/2); m.position.copy(W3(c[0],c[1],r.z0+bh/2)); m.rotation.y=ang; R.root.add(m); R.cab.push(m); }); } }\n rebuildItems(); cutaway(); draw();\n}\nfunction cutaway(){ if(!R) return; const cam=R.camera.position;\n R.walls.forEach(({lab})=\u003e{ if(lab) lab.visible=S.view!==\"r360\"; }); (R.cab||[]).forEach(m=\u003em.visible=S.view!==\"wall\");\n R.walls.forEach(({g,gw})=\u003e{ const mid=W3(gw.p[0]+gw.d[0]*gw.L/2,gw.p[1]+gw.d[1]*gw.L/2,S.H/2), n=new THREE.Vector3(gw.nin[0],0,-gw.nin[1]);\n g.visible=S.view===\"wall\"?gw.i===S.sel:n.dot(cam.clone().sub(mid))\u003e-0.02; }); }\nfunction centroid(){ let x=0,y=0; PTS.forEach(p=\u003e{x+=p[0];y+=p[1];}); return [x/PTS.length,y/PTS.length]; }\nfunction inPoly(pt){ let ins=false; for(let i=0,j=PTS.length-1;i\u003cPTS.length;j=i++){ const a=PTS[i],b=PTS[j]; if((a[1]\u003ept[1])!==(b[1]\u003ept[1])\u0026\u0026pt[0]\u003c(b[0]-a[0])*(pt[1]-a[1])/(b[1]-a[1])+a[0]) ins=!ins; } return ins; }\nfunction insidePt(c){ if(inPoly(c)) return c; const xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]); let best=null;\n for(let x=Math.min(...xs)+20;x\u003c=Math.max(...xs)-20;x+=10) for(let y=Math.min(...ys)+20;y\u003c=Math.max(...ys)-20;y+=10){ if(!inPoly([x,y])) continue; const d=Math.hypot(x-c[0],y-c[1]); if(!best||d\u003cbest.d) best={x,y,d}; }\n return best?[best.x,best.y]:c; }\nconst L360={yaw:0,pitch:0,pos:null,drag:null,pts:0};\nfunction okPos(pt){ if(!inPoly(pt)) return false; return wallsGeo().every(w=\u003e{ const rx=pt[0]-w.p[0],ry=pt[1]-w.p[1],s=clamp(rx*w.d[0]+ry*w.d[1],0,w.L); return Math.hypot(rx-w.d[0]*s,ry-w.d[1]*s)\u003e=18; }); }\nfunction segX(a,b,c,d){ const o=(p,q,r)=\u003e(q[0]-p[0])*(r[1]-p[1])-(q[1]-p[1])*(r[0]-p[0]); const d1=o(a,b,c),d2=o(a,b,d),d3=o(c,d,a),d4=o(c,d,b); return ((d1\u003e0)!==(d2\u003e0))\u0026\u0026((d3\u003e0)!==(d4\u003e0))\u0026\u0026Math.abs(d1)\u003e1e-6\u0026\u0026Math.abs(d2)\u003e1e-6; }\nfunction inItem(pt,m){ return S.items.some(it=\u003e{ const ax=itemAxes(it), rx=pt[0]-it.x, ry=pt[1]-it.y; return Math.abs(rx*ax.x[0]+ry*ax.x[1])\u003cit.w/2+m\u0026\u0026Math.abs(rx*ax.f[0]+ry*ax.f[1])\u003cit.d/2+m; }); }\nfunction pose360(){ // kies het standpunt op ooghoogte dat de meeste inrichting en ruimte laat zien (hoekfoto)\n const g=wallsGeo(), xs=PTS.map(q=\u003eq[0]),ys=PTS.map(q=\u003eq[1]), cc=[(Math.min(...xs)+Math.max(...xs))/2,(Math.min(...ys)+Math.max(...ys))/2];\n const pts=S.items.map(it=\u003e[it.x,it.y]), tgt=pts.length?[pts.reduce((a,q)=\u003ea+q[0],0)/pts.length*.6+cc[0]*.4,pts.reduce((a,q)=\u003ea+q[1],0)/pts.length*.6+cc[1]*.4]:cc;\n const sel=g[S.sel], m0=[sel.p[0]+sel.d[0]*sel.L/2,sel.p[1]+sel.d[1]*sel.L/2];\n const sees=(e,q)=\u003e!g.some(w=\u003esegX(e,q,w.p,w.q));\n let best=null; const cands=[];\n g.filter(w=\u003e!w.inner).forEach((w,_,pg)=\u003e{ const pv=pg[(w.i-1+pg.length)%pg.length]; if(cornerType(w.i)!==\"binnen\") return; let bx=w.nin[0]+pv.nin[0], by=w.nin[1]+pv.nin[1]; const l=Math.hypot(bx,by)||1; bx/=l; by/=l;\n for(const k of [35,55,80]) cands.push([w.p[0]+bx*k,w.p[1]+by*k]); });\n for(const e of cands){ const x=e[0], y=e[1];\n if(!okPos(e)||inItem(e,15)||inCab(e)) continue; const dir=Math.atan2(tgt[1]-y,tgt[0]-x), dist=Math.hypot(tgt[0]-x,tgt[1]-y); if(dist\u003c60) continue;\n let n=0; pts.forEach(q=\u003e{ let a=Math.atan2(q[1]-y,q[0]-x)-dir; a=Math.atan2(Math.sin(a),Math.cos(a)); if(Math.abs(a)\u003c.72\u0026\u0026Math.hypot(q[0]-x,q[1]-y)\u003e45\u0026\u0026sees(e,q)) n++; });\n const sc=n*1000+dist+(sees(e,m0)?150:0)-Math.hypot(x-m0[0],y-m0[1])*.05; if(!best||sc\u003ebest.sc) best={sc,e,dir}; }\n if(!best){ const e=insidePt(cc); best={e,dir:Math.atan2(m0[1]-e[1],m0[0]-e[0])}; }\n const dx=Math.cos(best.dir), dy=Math.sin(best.dir);\n return {pos:W3(best.e[0],best.e[1],Math.min(125,S.H*.5)), yaw:Math.atan2(-dx,dy), pitch:-.06}; }\nfunction facingWall(){ // in Overzicht: de wand waar de bezoeker in 3D naar kijkt (niet de geselecteerde wand)\n if(!R) return S.sel; const f=new THREE.Vector3(); R.camera.getWorldDirection(f); const fx=f.x, fy=-f.z, fl=Math.hypot(fx,fy)||1;\n const g=wallsGeo(), maxL=Math.max(...g.map(w=\u003ew.L)); let best=S.sel, bs=-1e9;\n g.forEach(w=\u003e{ const sc=-(fx*w.nin[0]+fy*w.nin[1])/fl+0.25*w.L/maxL; if(sc\u003ebs){ bs=sc; best=w.i; } }); return best; }\nfunction poseShowroom(iw){ // ooghoogte, net buiten de ruimte achter de tegenoverliggende wand (die wordt weggelaten): de hele ruimte in beeld\n const g0=wallsGeo()[iw==null?S.sel:iw], D=Math.max(...PTS.map(q=\u003e(q[0]-g0.p[0])*g0.nin[0]+(q[1]-g0.p[1])*g0.nin[1]));\n const xs=PTS.map(q=\u003eq[0]),ys=PTS.map(q=\u003eq[1]), cc=[(Math.min(...xs)+Math.max(...xs))/2,(Math.min(...ys)+Math.max(...ys))/2];\n const s0=(cc[0]-g0.p[0])*g0.d[0]+(cc[1]-g0.p[1])*g0.d[1], base=[g0.p[0]+g0.d[0]*s0,g0.p[1]+g0.d[1]*s0];\n const along=PTS.map(q=\u003e(q[0]-g0.p[0])*g0.d[0]+(q[1]-g0.p[1])*g0.d[1]), Wd=Math.max(...along)-Math.min(...along);\n const hf=1.5*Math.tan(27.5*Math.PI/180); // horizontale beeldhoek bij 55° verticaal en 3:2\n const back=D+Math.max(80,(Wd/2)/hf*1.12), e=[base[0]+g0.nin[0]*back,base[1]+g0.nin[1]*back], h=Math.min(140,S.H*.55);\n const dx=cc[0]-e[0], dy=cc[1]-e[1], dist=Math.hypot(dx,dy)/100;\n return {pos:W3(e[0],e[1],h), yaw:Math.atan2(-dx,dy), pitch:-Math.atan2((h-S.H*.42)/100,dist)}; }\nfunction apply360(){ if(!R||!L360.pos) return; const cam=R.camera; cam.rotation.order=\"YXZ\"; cam.position.copy(L360.pos); cam.rotation.set(L360.pitch,L360.yaw,0); cam.updateMatrixWorld(); cutaway(); draw(); miniCam(); }\n/* 360°: standpunten per wand, draaiknoppen, recht zetten, camera op de plattegrond */\nvar PLANT=null;\nfunction bar360(){ const el=document.getElementById(\"r360bar\"); if(!el) return; const g=wallsGeo();\n el.innerHTML='\u003cspan class=\"lbl\"\u003eKijk naar wand\u003c/span\u003e'+g.map(w=\u003e'\u003cbutton type=\"button\" data-face=\"'+w.i+'\" title=\"Kijk naar wand '+(w.i+1)+'\"\u003e'+(w.i+1)+'\u003c/button\u003e').join(\"\")+'\u003cspan class=\"sep\"\u003e\u003c/span\u003e\u003cbutton type=\"button\" data-rot=\"1\" title=\"Draai naar links\" aria-label=\"Draai naar links\"\u003e↺\u003c/button\u003e\u003cbutton type=\"button\" data-rot=\"-1\" title=\"Draai naar rechts\" aria-label=\"Draai naar rechts\"\u003e↻\u003c/button\u003e\u003cbutton type=\"button\" data-level=\"1\" class=\"txt\" title=\"Camera recht zetten: rechte wanden in de render\"\u003eRecht\u003c/button\u003e'; }\nfunction face360(i){ if(!R) return; const ps=poseShowroom(i); L360.pos=ps.pos; L360.yaw=ps.yaw; L360.pitch=ps.pitch; R.camera.fov=55; R.camera.updateProjectionMatrix(); apply360(); }\nfunction miniCam(){ const svg=document.querySelector(\"#minimap svg\"); if(!svg) return; let g=svg.querySelector(\"#camMk\");\n if(S.view!==\"r360\"||!L360.pos||!PLANT||!R){ if(g) g.remove(); return; }\n if(!g){ g=document.createElementNS(\"http://www.w3.org/2000/svg\",\"g\"); g.id=\"camMk\"; g.setAttribute(\"pointer-events\",\"none\"); svg.appendChild(g); }\n const P=PLANT, T=p=\u003e[P.pad+(p[0]-P.mx)*P.s,P.H-P.pad-(p[1]-P.my)*P.s], c0=T([L360.pos.x*100,-L360.pos.z*100]), c=[clamp(c0[0],6,194),clamp(c0[1],6,P.H-6)];\n const a=Math.atan2(-Math.cos(L360.yaw),-Math.sin(L360.yaw)), cam=R.camera, hf=Math.min(1.45,Math.atan(Math.tan(cam.fov*Math.PI/360)*Math.min(cam.aspect,1.5))), L=30;\n const p1=[c[0]+L*Math.cos(a-hf),c[1]+L*Math.sin(a-hf)], p2=[c[0]+L*Math.cos(a+hf),c[1]+L*Math.sin(a+hf)], f=n=\u003en.toFixed(1);\n g.innerHTML='\u003cpath d=\"M'+f(c[0])+' '+f(c[1])+' L'+f(p1[0])+' '+f(p1[1])+' A'+L+' '+L+' 0 0 1 '+f(p2[0])+' '+f(p2[1])+' Z\" fill=\"#0d6a6e\" fill-opacity=\".28\" stroke=\"#0d6a6e\" stroke-width=\"1\"/\u003e\u003ccircle cx=\"'+f(c[0])+'\" cy=\"'+f(c[1])+'\" r=\"4.5\" fill=\"#15222a\" stroke=\"#fff\" stroke-width=\"1.5\"/\u003e'; }\nfunction mini360(e){ const svg=document.querySelector(\"#minimap svg\"); if(!svg||!PLANT||!R||!L360.pos) return false;\n const wl=e.target.closest(\"[data-wall]\");\n if(wl){ const gw=wallsGeo()[+wl.dataset.wall]; if(!gw) return true; const m=[gw.p[0]+gw.d[0]*gw.L/2,gw.p[1]+gw.d[1]*gw.L/2], x=L360.pos.x*100, y=-L360.pos.z*100;\n L360.yaw=Math.atan2(-(m[0]-x),m[1]-y); L360.pitch=0; apply360(); return true; }\n const pt=svg.createSVGPoint(); pt.x=e.clientX; pt.y=e.clientY; const ctm=svg.getScreenCTM(); if(!ctm) return false; const q=pt.matrixTransform(ctm.inverse()), P=PLANT;\n let p=[(q.x-P.pad)/P.s+P.mx,(P.H-P.pad-q.y)/P.s+P.my]; if(!inPoly(p)) return false;\n if(!okPos(p)||inItem(p,15)){ let ok=null; for(let rr=8;rr\u003c=160\u0026\u0026!ok;rr+=8) for(let k=0;k\u003c24;k++){ const an=k*Math.PI/12, r=[p[0]+Math.cos(an)*rr,p[1]+Math.sin(an)*rr]; if(okPos(r)\u0026\u0026!inItem(r,15)){ ok=r; break; } } if(!ok) return true; p=ok; }\n const cam=R.camera; if(cam.fov\u003e90){ cam.fov=90; cam.updateProjectionMatrix(); }\n L360.pos=W3(p[0],p[1],clamp(L360.pos.y*100,60,S.H-30)); apply360(); return true; }\n(()=\u003e{ const el=document.getElementById(\"r360bar\"); if(!el) return;\n el.addEventListener(\"click\",e=\u003e{ const b=e.target.closest(\"button\"); if(!b) return; e.stopPropagation();\n if(b.dataset.face) face360(+b.dataset.face); else if(b.dataset.rot){ L360.yaw+=(+b.dataset.rot)*Math.PI/8; apply360(); } else if(b.dataset.level){ L360.pitch=0; apply360(); } });\n el.addEventListener(\"pointerdown\",e=\u003ee.stopPropagation()); })();\nfunction setView(v){\n S.view=v; if(R) R.controls.enabled=v!==\"r360\"; [\"over\",\"in\",\"r360\",\"wall\"].forEach(k=\u003e$(\"#v\"+{over:\"Over\",in:\"In\",r360:\"360\",wall:\"Wall\"}[k]).setAttribute(\"aria-pressed\",String(k===v)));\n $(\"#v360Render\").hidden=v!==\"r360\"; $(\"#r360ctl\").hidden=v!==\"r360\"; $(\"#r360bar\").hidden=v!==\"r360\"; $(\"#r360frame\").hidden=v!==\"r360\"; $(\"#viewer\").classList.toggle(\"is360\",v===\"r360\"); if(v===\"r360\") bar360(); miniCam();\n if(!R) return; const c=centroid(), C=R.controls, cam=R.camera;\n const xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]),span=Math.max(Math.max(...xs)-Math.min(...xs),Math.max(...ys)-Math.min(...ys),S.H)/100;\n if(R.ceil) R.ceil.visible=v!==\"over\";\n if(v===\"over\"){ C.enableZoom=true;C.enablePan=true;C.rotateSpeed=1;C.minDistance=1;C.maxDistance=20;C.maxPolarAngle=Math.PI*.49;\n C.minPolarAngle=0; C.target.copy(W3(c[0],c[1],S.H*.38)); cam.fov=45; cam.position.copy(C.target.clone().add(new THREE.Vector3(span*.85,span*1.25,span*1.75))); $(\"#viewHint\").textContent=\"Sleep om te draaien · knijp of scroll om te zoomen · tik op een wand\"; }\n else if(v===\"r360\"){ // in de ruimte staan en rondkijken (eigen besturing, zonder OrbitControls)\n const g0=wallsGeo()[S.sel], D=Math.max(...PTS.map(q=\u003e(q[0]-g0.p[0])*g0.nin[0]+(q[1]-g0.p[1])*g0.nin[1])), m0=[g0.p[0]+g0.d[0]*g0.L/2,g0.p[1]+g0.d[1]*g0.L/2];\n const ps=pose360(); L360.pos=ps.pos; L360.yaw=ps.yaw; L360.pitch=ps.pitch; cam.fov=65;\n $(\"#viewHint\").textContent=\"Sleep om rond te kijken · tik op de plattegrond om te lopen\"; }\n else if(v===\"in\"){ C.minPolarAngle=0; C.enableZoom=false;C.enablePan=false;C.rotateSpeed=-.35;C.minDistance=0;C.maxDistance=.2;C.maxPolarAngle=Math.PI*.85;\n const g0=wallsGeo()[S.sel], dW=(c[0]-g0.p[0])*g0.nin[0]+(c[1]-g0.p[1])*g0.nin[1], eye=[c[0]+g0.nin[0]*dW*.55,c[1]+g0.nin[1]*dW*.55]; C.target.copy(W3(eye[0],eye[1],160)); cam.fov=75; cam.position.copy(C.target.clone().add(new THREE.Vector3(g0.nin[0]*.05,0,-g0.nin[1]*.05))); $(\"#viewHint\").textContent=\"Sleep om rond te kijken\"; }\n else { C.minPolarAngle=0; const gw=wallsGeo()[S.sel], mid=[gw.p[0]+gw.d[0]*gw.L/2,gw.p[1]+gw.d[1]*gw.L/2];\n C.enableZoom=true;C.enablePan=true;C.rotateSpeed=.6;C.minDistance=.5;C.maxDistance=12;C.maxPolarAngle=Math.PI*.6;\n C.target.copy(W3(mid[0],mid[1],S.H/2)); cam.fov=50; const tf=2*Math.tan(25*Math.PI/180), dist=Math.max(S.H/100/tf,gw.L/100/(tf*cam.aspect))*1.18;\n cam.position.copy(W3(mid[0]+gw.nin[0]*dist*100,mid[1]+gw.nin[1]*dist*100,S.H*.55)); $(\"#viewHint\").textContent=`Wand ${S.sel+1} recht van voren`; }\n cam.updateProjectionMatrix(); if(v===\"r360\"){ apply360(); return; } C.update(); cutaway(); draw();\n}\nfunction pick(e){ if(!R) return; const r=R.renderer.domElement.getBoundingClientRect(), m=new THREE.Vector2(((e.clientX-r.left)/r.width)*2-1,-((e.clientY-r.top)/r.height)*2+1);\n const ray=new THREE.Raycaster(); ray.setFromCamera(m,R.camera); const objs=[]; R.walls.forEach(w=\u003e{ if(w.g.visible) w.g.traverse(o=\u003e{ if(o.isMesh\u0026\u0026o.userData.wall!=null) objs.push(o); }); });\n R.root.children.forEach(o=\u003e{ if(o.userData\u0026\u0026o.userData.floor) objs.push(o); });\n const hit=ray.intersectObjects(objs,false)[0]; if(!hit) return;\n if(hit.object.userData.floor){ goStep(3); return; }\n S.sel=hit.object.userData.wall; goStep(2); renderAll(false); }\n\n/* ---------- berekening ---------- */\nfunction wallCalc(i){\n const gw=wallsGeo()[i], w=S.walls[i], p=WALL[w.prod], sz=prodSize(p,w.size), cover=clad(w)?coverOf(w):0, L=gw.L;\n const ops=w.openings.map(o=\u003e({x0:clamp(o.x,0,L),x1:clamp(o.x+o.w,0,L),y0:clamp(o.y,0,cover),y1:clamp(o.y+o.h,0,cover),t:o.type}));\n const openA=ops.reduce((s,o)=\u003es+Math.max(0,o.x1-o.x0)*Math.max(0,o.y1-o.y0),0);\n const gross=L*cover/1e4, net=Math.max(0,(L*cover-openA)/1e4);\n // nis: achterwand en zijkanten krijgen ook bekleding\n let extra=0; w.openings.filter(o=\u003eo.type===\"nis\").forEach(o=\u003e{ const y1=Math.min(o.y+o.h,cover); if(y1\u003eo.y) extra+=(o.w*(y1-o.y)+2*(o.d||10)*(y1-o.y)+2*(o.d||10)*o.w)/1e4; });\n let count=0, rows=0, cols=0;\n if(!clad(w)||cover\u003c=0) return {gw,w,p,sz,cover:0,gross:0,net:0,count:0,area:0};\n if(p.layout===\"tiles\"){ count=Math.ceil((net+extra)*1.05/(sz.w*sz.l/1e4)-1e-6); }\n else { cols=Math.ceil(L/sz.w-1e-6); rows=Math.ceil(cover/sz.l-1e-6);\n for(let c=0;c\u003ccols;c++)for(let r=0;r\u003crows;r++){ const x0=c*sz.w,x1=Math.min(L,x0+sz.w),y0=r*sz.l,y1=Math.min(cover,y0+sz.l);\n if(!ops.some(o=\u003eo.t!==\"nis\"\u0026\u0026o.x0\u003c=x0+.5\u0026\u0026o.x1\u003e=x1-.5\u0026\u0026o.y0\u003c=y0+.5\u0026\u0026o.y1\u003e=y1-.5)) count++; }\n if(extra\u003e0) count+=Math.ceil(extra/(sz.w*sz.l/1e4)*1.15);\n if(p.layout===\"mural\"\u0026\u0026count%2) count++; }\n return {gw,w,p,sz,cover,gross,net:net+extra,count,rows,cols,ops};\n}\nfunction totals(){\n const n=PTS.length, wc=[...Array(S.walls.length)].map((_,i)=\u003ewallCalc(i)), lines=new Map(); let net=0, panelNet=0, tileNet=0;\n wc.forEach(c=\u003e{ if(!c.cover) return; net+=c.net; if(c.p.layout===\"tiles\") tileNet+=c.net; else panelNet+=c.net;\n const key=[c.p.name,sizeTxt(c.p,c.sz),c.w.decor].join(\"|\"); const L=lines.get(key)||{p:c.p,sz:c.sz,decor:c.w.decor,count:0,net:0,walls:[]}; L.count+=c.count; L.net+=c.net; L.walls.push(c.gw.i+1); lines.set(key,L); });\n // profielen\n const PL=PROF[S.prof].len; let inner=0,outer=0,uLen=0; const per=h=\u003eMath.ceil(h/PL-1e-6);\n for(let i=0;i\u003cn;i++){ const a=wc[(i-1+n)%n],b=wc[i],h=Math.max(a.cover,b.cover); if(!h) continue;\n if(a.cover\u0026\u0026b.cover){ if(cornerType(i)===\"binnen\") inner+=per(h); else outer+=per(h); } else uLen+=h; }\n { const cg=cabGeo(perimGeo()); if(cg) cg.corners.forEach(([ia,ib,ty])=\u003e{ const a=wc[ia],b=wc[ib]; if(!a||!b) return; const h=Math.max(a.cover,b.cover); if(!h) return; if(ty===\"eind\"){ uLen+=h; return; } if(a.cover\u0026\u0026b.cover){ if(ty===\"binnen\") inner+=per(h); else outer+=per(h); } else uLen+=h; }); }\n const uPieces=[]; if(uLen) uPieces.push(uLen);\n wc.forEach(c=\u003e{ if(!c.cover) return; if(c.cover\u003cS.H-1) uPieces.push(c.gw.L);\n c.w.openings.forEach(o=\u003e{ const y1=Math.min(o.y+o.h,c.cover); if(y1\u003c=o.y) return; const hh=y1-o.y;\n if(o.type===\"raam\") uPieces.push(2*hh+(o.y\u003e0?o.w:0)+(o.y+o.h\u003c=c.cover?o.w:0)); else if(o.type===\"deur\") uPieces.push(2*hh+(o.y+o.h\u003c=c.cover?o.w:0)); else outer+=0; }); });\n const u=uPieces.reduce((s,l)=\u003es+Math.ceil(l/PL-1e-6),0);\n const nisOuter=wc.reduce((s,c)=\u003es+(c.cover?c.w.openings.filter(o=\u003eo.type===\"nis\"\u0026\u0026o.y\u003cc.cover).length*2:0),0); outer+=nisOuter;\n const r={wc,lines:[...lines.values()],net,inner,outer,u,\n panelNet, tileNet};\n if(floorOn()){ const fp=FLOOR[S.floor.prod],fs=prodSize(fp,S.floor.size),fa=Math.abs(area2(PTS))/2/1e4,waste=fp.layout===\"herring\"?1.12:1.08;\n r.fp=fp;r.fs=fs;r.floorA=fa;r.floorN=Math.ceil(fa*waste/(fs.w*fs.l/1e4)-1e-6);r.floorLijm=S.floor.mount===\"lijm\"?Math.ceil(fa*.8/5-1e-6):0;r.underlay=S.floor.mount===\"zwevend\"?Math.ceil(fa*1.05-1e-6):0; }\n return r;\n}\n\n/* ---------- UI ---------- */\nfunction setPressed(sel,attr,val){$$(sel).forEach(b=\u003eb.setAttribute(\"aria-pressed\",String(b.dataset[attr]===val)));}\nfunction goStep(n){S.step=n;$$(\".steps button\").forEach(b=\u003eb.setAttribute(\"aria-selected\",String(+b.dataset.step===n)));$$(\"[data-pane]\").forEach(p=\u003ep.hidden=+p.dataset.pane!==n); if(n===4) renderIElev();}\nfunction cabUI(){ const C=S.cab, box=$(\"#cabBox\"); if(!box) return; box.hidden=S.shape!==\"rect\"; $(\"#cabOn\").setAttribute(\"aria-pressed\",String(!!C.on)); $(\"#cabParams\").hidden=!C.on;\n const num=(k,l,mn,mx)=\u003e`\u003clabel class=\"num\"\u003e${l}\u003cinput type=\"number\" data-cab=\"${k}\" min=\"${mn}\" max=\"${mx}\" step=\"1\" value=\"${C[k]}\"\u003e\u003c/label\u003e`;\n $(\"#cabParams\").innerHTML=`\u003clabel class=\"num\"\u003eTegen wand\u003cselect data-cab=\"wall\"\u003e${[0,1,2,3].map(i=\u003e`\u003coption value=\"${i}\" ${i===C.wall?\"selected\":\"\"}\u003eWand ${i+1}\u003c/option\u003e`).join(\"\")}\u003c/select\u003e\u003c/label\u003e`\n +num(\"n\",\"Aantal cabines\",1,12)+num(\"x\",\"Afstand vanaf de hoek\",0,1200)+num(\"w\",\"Breedte cabine (binnenwerks)\",60,300)+num(\"d\",\"Diepte cabine (binnenwerks)\",80,400)+num(\"t\",\"Dikte tussenwand\",4,30)+num(\"door\",\"Breedte deur\",50,120)+num(\"dh\",\"Hoogte deur\",180,260)\n +`\u003cdiv style=\"grid-column:1/-1\"\u003e\u003cp class=\"field-label\"\u003ePer cabine\u003c/p\u003e${[...Array(clamp(Math.round(C.n)||1,1,12))].map((_,k)=\u003e`\u003cdiv class=\"chips\" style=\"margin:4px 0\"\u003e\u003cspan class=\"note\" style=\"min-width:74px;align-self:center\"\u003eCabine ${k+1}\u003c/span\u003e\u003cbutton class=\"chip\" type=\"button\" data-cabopen=\"${k}\" data-v=\"0\" aria-pressed=\"${!(C.open\u0026\u0026C.open[k])}\"\u003eMet deur\u003c/button\u003e\u003cbutton class=\"chip\" type=\"button\" data-cabopen=\"${k}\" data-v=\"1\" aria-pressed=\"${!!(C.open\u0026\u0026C.open[k])}\"\u003eOpen (urinoir)\u003c/button\u003e\u003c/div\u003e`).join(\"\")}\u003c/div\u003e`\n +urUI(); urInfo(); }\nfunction urUI(){ const U=urS(); return `\u003cdiv style=\"grid-column:1/-1\"\u003e\u003cp class=\"field-label\"\u003eUrinoirs tegenover de cabines\u003c/p\u003e\u003cdiv class=\"chips\"\u003e\u003cbutton class=\"chip\" type=\"button\" data-ur=\"on\" aria-pressed=\"${!!U.on}\"\u003eUrinoirrij plaatsen\u003c/button\u003e${U.on?`\u003cbutton class=\"chip\" type=\"button\" data-ur=\"schot\" aria-pressed=\"${!!U.schot}\"\u003eSchotjes tussen de urinoirs\u003c/button\u003e`:\"\"}\u003c/div\u003e`\n +(U.on?`\u003cdiv class=\"grid2\" style=\"margin-top:6px\"\u003e\u003clabel class=\"num\"\u003eAantal urinoirs\u003cinput type=\"number\" data-urn=\"n\" min=\"1\" max=\"20\" step=\"1\" value=\"${U.n}\"\u003e\u003c/label\u003e\u003clabel class=\"num\"\u003eHart-op-hart (cm)\u003cinput type=\"number\" data-urn=\"hoh\" min=\"50\" max=\"120\" step=\"1\" value=\"${U.hoh}\"\u003e\u003c/label\u003e\u003c/div\u003e`:\"\")\n +`\u003cp class=\"note\" id=\"urInfo\" hidden\u003e\u003c/p\u003e\u003c/div\u003e`; }\nfunction cabFit(){ // bij aanzetten: aantal en diepte passend maken in de ruimte\n const C=S.cab, per=perimGeo(), wi=C.wall%per.length, L=per[wi].L, dep=per[(wi+1)%per.length].L;\n C.d=Math.max(80,Math.min(C.d,Math.floor(dep-C.t-90))); C.n=Math.max(1,Math.min(C.n,Math.floor((L-C.x+C.t)/(C.w+C.t)))); }\nfunction shapeIcon(k){ const sh=SHAPES[k],p={};sh.params.forEach(([kk,_,v])=\u003ep[kk]=v);const pts=sh.pts(p),xs=pts.map(q=\u003eq[0]),ys=pts.map(q=\u003eq[1]),mx=Math.min(...xs),Mx=Math.max(...xs),my=Math.min(...ys),My=Math.max(...ys),s=Math.min(56/(Mx-mx),40/(My-my));\n return `\u003csvg viewBox=\"0 0 64 48\"\u003e\u003cpolygon points=\"${pts.map(q=\u003e`${4+(q[0]-mx)*s},${44-(q[1]-my)*s}`).join(\" \")}\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/\u003e\u003c/svg\u003e`; }\nfunction buildShapes(){ $(\"#shapes\").innerHTML=Object.entries(SHAPES).map(([k,s])=\u003e`\u003cbutton class=\"shape\" data-shape=\"${k}\" aria-pressed=\"${k===S.shape}\" type=\"button\"\u003e${shapeIcon(k)}${s.name}\u003csmall class=\"note\"\u003e${s.pts(Object.fromEntries(s.params.map(([a,_,v])=\u003e[a,v]))).length} wanden\u003c/small\u003e\u003c/button\u003e`).join(\"\");\n $(\"#shapeInfo\").textContent=SHAPE_INFO[S.shape]||\"\"; $(\"#mirrorBox\").hidden=S.shape===\"rect\"; $(\"#mirrorBtn\").setAttribute(\"aria-pressed\",String(!!S.mirror));\n $(\"#params\").innerHTML=SHAPES[S.shape].params.map(([k,l])=\u003e`\u003clabel class=\"num\"\u003e${l}\u003cinput type=\"number\" data-param=\"${k}\" min=\"0\" max=\"1200\" step=\"1\" value=\"${S.params[k]}\"\u003e\u003c/label\u003e`).join(\"\");\n $(\"#ceilH\").value=S.H; cabUI();\n $(\"#roomType\").innerHTML=ROOMS.map(r=\u003e`\u003cbutton class=\"chip\" data-room=\"${r}\" aria-pressed=\"${r===S.room}\" type=\"button\"\u003e${r}\u003c/button\u003e`).join(\"\"); }\nfunction planSVG(sel,interactive){\n const xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]),mx=Math.min(...xs),Mx=Math.max(...xs),my=Math.min(...ys),My=Math.max(...ys),pad=26,W=200,s=(W-2*pad)/Math.max(Mx-mx,(My-my)*1.1),H=(My-my)*s+2*pad;\n const T=p=\u003e[pad+(p[0]-mx)*s,H-pad-(p[1]-my)*s]; const g=wallsGeo(); if(interactive) PLANT={mx,my,s,pad,H};\n let o=`\u003csvg viewBox=\"0 0 ${W} ${H.toFixed(1)}\" role=\"img\" aria-label=\"Plattegrond\"\u003e\u003cpolygon points=\"${PTS.map(p=\u003eT(p).join(\",\")).join(\" \")}\" fill=\"#f3f2ee\" stroke=\"none\"/\u003e`;\n { const cg=cabGeo(perimGeo()); if(cg) cg.solids.filter(r=\u003er.z0===0).forEach(r=\u003e{ o+=`\u003cpolygon points=\"${[[r.s0,r.u0],[r.s1,r.u0],[r.s1,r.u1],[r.s0,r.u1]].map(([s,u])=\u003eT(cg.P(s,u)).join(\",\")).join(\" \")}\" fill=\"#5f6b6e\" stroke=\"none\"/\u003e`; }); }\n g.forEach(gw=\u003e{ const w=S.walls[gw.i],a=T(gw.p),b=T(gw.q),on=clad(w),isSel=gw.i===sel;\n o+=`\u003cline x1=\"${a[0]}\" y1=\"${a[1]}\" x2=\"${b[0]}\" y2=\"${b[1]}\" stroke=\"${isSel?\"#15222a\":on?\"#0d6a6e\":\"#9aa5a8\"}\" stroke-width=\"${isSel?6:on?4.5:2}\" ${on||isSel?\"\":'stroke-dasharray=\"4 3\"'} stroke-linecap=\"round\"/\u003e`;\n w.openings.forEach(op=\u003e{ if(op.type===\"nis\") return; const s0=clamp(op.x,0,gw.L),s1=clamp(op.x+op.w,0,gw.L),p0=T([gw.p[0]+gw.d[0]*s0,gw.p[1]+gw.d[1]*s0]),p1=T([gw.p[0]+gw.d[0]*s1,gw.p[1]+gw.d[1]*s1]);\n o+=`\u003cline x1=\"${p0[0]}\" y1=\"${p0[1]}\" x2=\"${p1[0]}\" y2=\"${p1[1]}\" stroke=\"${op.type===\"raam\"?\"#8cc0d6\":\"#ffffff\"}\" stroke-width=\"${isSel?6:4.5}\"/\u003e`; });\n const m=T([gw.p[0]+gw.d[0]*gw.L/2+gw.nin[0]*0,gw.p[1]+gw.d[1]*gw.L/2]), off=gw.inner?[gw.nin[0]*9,-gw.nin[1]*9]:[-gw.nin[0]*13,gw.nin[1]*13]; // label buiten de muur\n o+=`\u003cg ${interactive?`data-wall=\"${gw.i}\" style=\"cursor:pointer\"`:\"\"}\u003e\u003cline x1=\"${a[0]}\" y1=\"${a[1]}\" x2=\"${b[0]}\" y2=\"${b[1]}\" stroke=\"transparent\" stroke-width=\"16\"/\u003e\u003ccircle cx=\"${m[0]+off[0]}\" cy=\"${m[1]+off[1]}\" r=\"9\" fill=\"${isSel?\"#15222a\":\"#ffffff\"}\" stroke=\"#0d6a6e\" stroke-width=\"1.5\"/\u003e\u003ctext x=\"${m[0]+off[0]}\" y=\"${m[1]+off[1]+.5}\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-family=\"system-ui,sans-serif\" font-weight=\"700\" font-size=\"10\" fill=\"${isSel?\"#fff\":\"#0d6a6e\"}\"\u003e${gw.i+1}\u003c/text\u003e\u003c/g\u003e`;\n });\n S.items.forEach((it,k)=\u003e{ const a=itemAxes(it), hw=it.w/2, hd=it.d/2, c=[[-hw,-hd],[hw,-hd],[hw,hd],[-hw,hd]].map(([u,v])=\u003eT([it.x+a.x[0]*u+a.f[0]*v,it.y+a.x[1]*u+a.f[1]*v]));\n o+=`\u003cpolygon points=\"${c.map(q=\u003eq.join(\",\")).join(\" \")}\" fill=\"${k===S.selItem?\"#0d6a6e\":\"#b9c2c4\"}\" fill-opacity=\"${k===S.selItem?.55:.7}\" stroke=\"#6b787b\" stroke-width=\".8\"/\u003e`; });\n return o+\"\u003c/svg\u003e\";\n}\nfunction renderPlan(){ $(\"#minimap\").innerHTML=planSVG(S.sel,true); miniCam(); if(S.view===\"r360\") bar360();\n const g=wallsGeo(); $(\"#wallSummary\").innerHTML=g.map(gw=\u003e`\u003cspan class=\"mono\"\u003eWand ${gw.i+1}${gw.label?` (${gw.label})`:\"\"}: ${fmt(gw.L,0)} cm\u003c/span\u003e`).join(\" · \")+` · \u003cspan class=\"mono\"\u003evloer ${fmt(Math.abs(area2(PTS))/2/1e4)} m²\u003c/span\u003e`; }\nfunction decorGrid(el,p,cur,onPick,avail){\n const list=avail||p.decors; let html=list.map(d=\u003e`\u003cbutton class=\"decor\" data-decor=\"${esc(d.name)}\" aria-pressed=\"${d.name===cur}\" type=\"button\"\u003e\u003cspan class=\"sw\"\u003e\u003cimg alt=\"\" loading=\"lazy\" src=\"${thumbUrl(d)}\" data-fb=\"${esc(d.name)}\"\u003e\u003c/span\u003e${esc(d.name)}\u003c/button\u003e`).join(\"\");\n if(p.custom) html+=`\u003cbutton class=\"decor\" data-decor=\"Eigen ontwerp\" aria-pressed=\"${cur===\"Eigen ontwerp\"}\" type=\"button\"\u003e\u003cspan class=\"sw\"\u003e${S.customImg?`\u003cimg alt=\"\" src=\"${S.customThumb}\"\u003e`:\"+\"}\u003c/span\u003e${S.customImg\u0026\u0026cur===\"Eigen ontwerp\"?\"Eigen ontwerp \u003csmall\u003e(klik om te wijzigen)\u003c/small\u003e\":\"Eigen ontwerp\"}\u003c/button\u003e`;\n el.innerHTML=html; el.querySelectorAll(\"img[data-fb]\").forEach(img=\u003eimg.addEventListener(\"error\",()=\u003e{img.src=thumb(img.dataset.fb);},{once:true}));\n el.onclick=e=\u003e{const b=e.target.closest(\"[data-decor]\");if(!b)return;onPick(b.dataset.decor);}; }\nfunction prodChips(el,cat,cur,onPick){ el.innerHTML=Object.entries(cat).map(([k,p])=\u003e`\u003cbutton class=\"chip\" data-p=\"${k}\" aria-pressed=\"${k===cur}\" type=\"button\"\u003e${p.short} \u003csmall\u003e${p.sizes?p.sizes.map(s=\u003e`${s.w*10}×${s.l*10}`).join(\" / \"):sizeTxt(p,p).replace(\" mm\",\"\")}\u003c/small\u003e\u003c/button\u003e`).join(\"\");\n el.onclick=e=\u003e{const b=e.target.closest(\"[data-p]\");if(b)onPick(b.dataset.p);}; }\nfunction sizeChips(box,el,p,cur,onPick){ box.hidden=!p.sizes; if(!p.sizes) return; el.innerHTML=p.sizes.map(s=\u003e`\u003cbutton class=\"chip mono\" data-s=\"${s.id}\" aria-pressed=\"${s.id===cur}\" type=\"button\"\u003e${s.w*10} × ${s.l*10} mm\u003c/button\u003e`).join(\"\"); el.onclick=e=\u003e{const b=e.target.closest(\"[data-s]\");if(b)onPick(b.dataset.s);}; }\nfunction wallTabs(){ $(\"#wallTabs\").innerHTML=S.walls.map((w,i)=\u003e{ const d=decorOf(WALL[w.prod],w.decor); return `\u003cbutton class=\"wt ${clad(w)?\"\":\"off\"}\" data-w=\"${i}\" aria-pressed=\"${i===S.sel}\" type=\"button\"\u003e\u003ci style=\"background-image:url('${thumbUrl(d)}'),url('${thumb(d.name)}')\"\u003e\u003c/i\u003e${i+1}\u003c/button\u003e`; }).join(\"\"); }\nfunction renderWallEditor(){\n const i=S.sel,w=S.walls[i],gw=wallsGeo()[i],p=WALL[w.prod];\n $(\"#wallTitle\").textContent=`Wand ${i+1}${gw.label?\" · \"+gw.label:\"\"} · ${fmt(gw.L,0)} cm`; $(\"#wOn\").checked=w.on; $(\"#wallEditor\").hidden=false; // decors altijd zichtbaar; kiezen zet de wand aan\n prodChips($(\"#wProd\"),WALL,(w.pick||w.decor)?w.prod:null,k=\u003e{ w.on=true; w.pick=true; w.prod=k; const np=WALL[k]; if(w.decor\u0026\u0026!np.decors.some(d=\u003ed.name===w.decor)\u0026\u0026!(np.custom\u0026\u0026w.decor===\"Eigen ontwerp\")) w.decor=null; changed(); });\n sizeChips($(\"#wSizeBox\"),$(\"#wSize\"),p,w.size,s=\u003e{w.on=true;w.size=s;changed();});\n const wl=availDecors(\"wall\",w.prod,w.size);\n $(\"#wDecorLabel\").textContent=`Decor ${p.short}${p.sizes?\" \"+sizeTxt(p,prodSize(p,w.size)):\"\"} (${wl.length}${p.custom?\" + eigen ontwerp\":\"\"})`;\n decorGrid($(\"#wDecor\"),p,clad(w)?w.decor:null,n=\u003e{ if(n===\"Eigen ontwerp\"\u0026\u0026(!S.customImg||(clad(w)\u0026\u0026w.decor===\"Eigen ontwerp\"))){ $(\"#customIn\").value=\"\"; $(\"#customIn\").click(); return; } w.on=true; w.decor=n; changed(); },wl);\n setPressed(\"#wCover [data-cover]\",\"cover\",w.cover); $(\"#wCoverNumBox\").hidden=w.cover!==\"custom\"; $(\"#wCoverNum\").value=w.coverCm;\n $(\"#openings\").innerHTML=w.openings.length?w.openings.map((o,k)=\u003e`\u003cdiv class=\"opening\" data-o=\"${k}\"\u003e\n \u003clabel class=\"num\"\u003eSoort\u003cselect data-f=\"type\"\u003e\u003coption value=\"deur\"${o.type===\"deur\"?\" selected\":\"\"}\u003eDeur\u003c/option\u003e\u003coption value=\"raam\"${o.type===\"raam\"?\" selected\":\"\"}\u003eRaam\u003c/option\u003e\u003coption value=\"nis\"${o.type===\"nis\"?\" selected\":\"\"}\u003eNis\u003c/option\u003e\u003c/select\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eBreedte\u003cinput type=\"number\" data-f=\"w\" value=\"${o.w}\" min=\"5\" max=\"1000\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eHoogte\u003cinput type=\"number\" data-f=\"h\" value=\"${o.h}\" min=\"5\" max=\"400\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eVanaf links\u003cinput type=\"number\" data-f=\"x\" value=\"${o.x}\" min=\"0\" max=\"1000\"\u003e\u003c/label\u003e\n \u003clabel class=\"num\"\u003eVanaf vloer\u003cinput type=\"number\" data-f=\"y\" value=\"${o.y}\" min=\"0\" max=\"400\"\u003e\u003c/label\u003e\n \u003cbutton type=\"button\" data-del=\"${k}\" aria-label=\"Verwijder\"\u003e×\u003c/button\u003e\u003c/div\u003e`).join(\"\"):`\u003cp class=\"note\"\u003eGeen deuren of ramen in deze wand.\u003c/p\u003e`;\n renderElev(); }\nfunction renderElev(){\n const c=wallCalc(S.sel), gw=c.gw, w=c.w, H=S.H, L=gw.L, pad=22, sc=Math.min(560/L,260/H), Wp=L*sc+2*pad, Hp=H*sc+2*pad;\n const X=s=\u003epad+s*sc, Y=t=\u003epad+(H-t)*sc, d=decorOf(WALL[w.prod],w.decor), base=(LOOK[d.name]||[\"solid\",[200,200,200]])[1];\n let o=`\u003csvg viewBox=\"0 0 ${Wp.toFixed(0)} ${Hp.toFixed(0)}\" role=\"img\" aria-label=\"Aanzicht wand ${S.sel+1}\"\u003e\u003crect x=\"${X(0)}\" y=\"${Y(H)}\" width=\"${L*sc}\" height=\"${H*sc}\" fill=\"#eceae5\" stroke=\"#9aa5a8\"/\u003e`;\n if(c.cover){ o+=`\u003crect x=\"${X(0)}\" y=\"${Y(c.cover)}\" width=\"${L*sc}\" height=\"${c.cover*sc}\" fill=\"rgb(${base.join(\",\")})\" opacity=\".9\"/\u003e`;\n const sz=c.sz, ln=[]; const lc=(base[0]+base[1]+base[2])/3\u003c110?\"rgba(255,255,255,.55)\":\"rgba(0,0,0,.4)\";\n if(c.p.layout===\"tiles\"){ // halfsteens: elke rij een halve tegel verspringen\n for(let r=0,y0=0;y0\u003cc.cover;r++,y0+=sz.l){ const y1=Math.min(c.cover,y0+sz.l); for(let x=(r%2?sz.w/2:sz.w);x\u003cL;x+=sz.w) ln.push([x,y0,x,y1]); }\n for(let y=sz.l;y\u003cc.cover;y+=sz.l) ln.push([0,y,L,y]); }\n else { for(let x=sz.w;x\u003cL-.5;x+=sz.w) ln.push([x,0,x,c.cover]); for(let y=sz.l;y\u003cc.cover-.5;y+=sz.l) ln.push([0,y,L,y]); }\n o+=ln.map(l=\u003e`\u003cline x1=\"${X(l[0])}\" y1=\"${Y(l[1])}\" x2=\"${X(l[2])}\" y2=\"${Y(l[3])}\" stroke=\"${lc}\" stroke-width=\"1\"/\u003e`).join(\"\"); }\n w.openings.forEach(op=\u003e{ o+=`\u003crect x=\"${X(op.x)}\" y=\"${Y(op.y+op.h)}\" width=\"${op.w*sc}\" height=\"${op.h*sc}\" fill=\"${op.type===\"raam\"?\"#d4e8f1\":op.type===\"nis\"?\"#d9d7d2\":\"#ffffff\"}\" stroke=\"#5b6366\"/\u003e\u003ctext x=\"${X(op.x+op.w/2)}\" y=\"${Y(op.y+op.h/2)}\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-family=\"system-ui,sans-serif\" font-size=\"11\" fill=\"#33413f\"\u003e${op.type}\u003c/text\u003e`; });\n o+=`\u003ctext x=\"${X(L/2)}\" y=\"${Hp-5}\" text-anchor=\"middle\" font-family=\"IBM Plex Mono,monospace\" font-size=\"11\" fill=\"#5a6a70\"\u003e${fmt(L,0)} cm\u003c/text\u003e\u003ctext x=\"9\" y=\"${Y(H/2)}\" font-family=\"IBM Plex Mono,monospace\" font-size=\"11\" fill=\"#5a6a70\" transform=\"rotate(-90 9 ${Y(H/2)})\" text-anchor=\"middle\"\u003e${fmt(H,0)} cm\u003c/text\u003e\u003c/svg\u003e`;\n $(\"#elev\").innerHTML=o;\n $(\"#wallCalc\").textContent=c.cover?`${fmt(c.net)} m² bekleding · ${c.count} ${c.p.unit} ${sizeTxt(c.p,c.sz)}${c.p.layout!==\"tiles\"?` (${c.cols} banen${c.rows\u003e1?` × ${c.rows} rijen`:\"\"})`:\"\"}${c.rows\u003e1?\" · let op: wand hoger dan paneel, horizontale naad\":\"\"}`:(w.on?\"Kies hierboven een product en decor voor deze wand.\":\"Deze wand wordt niet bekleed.\");\n}\nfunction renderFloor(){\n const F=S.floor,p=FLOOR[F.prod]; $(\"#fOn\").checked=F.on; $(\"#floorEditor\").hidden=false;\n prodChips($(\"#fProd\"),FLOOR,(F.pick||F.decor)?F.prod:null,k=\u003e{F.on=true;F.pick=true;F.prod=k;const np=FLOOR[k];if(F.decor\u0026\u0026!np.decors.some(d=\u003ed.name===F.decor))F.decor=null;changed();});\n sizeChips($(\"#fSizeBox\"),$(\"#fSize\"),p,F.size,s=\u003e{F.on=true;F.size=s;changed();});\n const fl=availDecors(\"floor\",F.prod,F.size);\n $(\"#fDecorLabel\").textContent=`Decor ${p.short}${p.sizes?\" \"+sizeTxt(p,prodSize(p,F.size)):\"\"} (${fl.length})`;\n decorGrid($(\"#fDecor\"),p,floorOn()?F.decor:null,n=\u003e{F.on=true;F.decor=n;changed();},fl); setPressed(\"#fMount [data-fm]\",\"fm\",F.mount);\n $(\"#fUnderBox\").hidden=F.mount!==\"zwevend\"; setPressed(\"#fUnder [data-under]\",\"under\",F.under);\n const t=totals(); $(\"#floorCalc\").textContent=floorOn()?`${fmt(t.floorA)} m² vloer · ${t.floorN} ${t.fp.unit} ${sizeTxt(t.fp,t.fs)}`:(F.on?\"Kies hierboven een product en decor voor de vloer.\":\"De vloer wordt niet bekleed.\");\n}\nconst WN=i=\u003e`wand ${i+1}`;\nfunction renderSummary(){\n const t=totals(), pr=priceAll(t); setPressed(\"#mount [data-mount]\",\"mount\",S.mount); setPressed(\"#prof [data-prof]\",\"prof\",S.prof); setPressed(\"#lev [data-lev]\",\"lev\",S.delivery);\n const pm=PROF[S.prof]; $(\"#profColor\").innerHTML=pm.colors.map(([n],k)=\u003e`\u003cbutton class=\"chip\" data-pc=\"${k}\" aria-pressed=\"${k===(S.profColor||0)}\" type=\"button\"\u003e${n}\u003c/button\u003e`).join(\"\");\n $(\"#statusLine\").innerHTML=`\u003cspan\u003e\u003cb\u003e${S.walls.length} wanden\u003c/b\u003e · ${fmt(t.net)} m² wand${floorOn()?` · ${fmt(t.floorA)} m² vloer`:\"\"}\u003c/span\u003e\u003cspan class=\"mono\"\u003e\u003cb\u003e${eur(pr.total)}\u003c/b\u003e${pr.onRequest?\" + op aanvraag\":\"\"}\u003c/span\u003e`;\n $(\"#bigNums\").innerHTML=`\u003cdiv\u003e\u003cb\u003e${eur(pr.total)}\u003c/b\u003e\u003cspan\u003etotaal incl. btw${pr.onRequest?\" (excl. posten op aanvraag)\":\"\"}\u003c/span\u003e\u003c/div\u003e\u003cdiv\u003e\u003cb\u003e${fmt(t.net)}\u003c/b\u003e\u003cspan\u003em² wandbekleding\u003c/span\u003e\u003c/div\u003e${floorOn()?`\u003cdiv\u003e\u003cb\u003e${fmt(t.floorA)}\u003c/b\u003e\u003cspan\u003em² vloer\u003c/span\u003e\u003c/div\u003e`:\"\"}`;\n $(\"#wallTable\").innerHTML=`\u003cthead\u003e\u003ctr\u003e\u003cth\u003eWand\u003c/th\u003e\u003cth\u003eLengte\u003c/th\u003e\u003cth\u003eProduct\u003c/th\u003e\u003cth\u003eDecor\u003c/th\u003e\u003cth\u003em²\u003c/th\u003e\u003cth\u003eIndicatie\u003c/th\u003e\u003c/tr\u003e\u003c/thead\u003e\u003ctbody\u003e`+t.wc.map(c=\u003e{ const wp=wallPrice(c);\n return `\u003ctr\u003e\u003ctd\u003e\u003cb\u003e${c.gw.i+1}\u003c/b\u003e\u003c/td\u003e\u003ctd class=\"n\"\u003e${fmt(c.gw.L,0)} cm\u003c/td\u003e\u003ctd\u003e${c.cover?c.p.short:\"—\"}\u003c/td\u003e\u003ctd\u003e${c.cover?esc(c.w.decor):\"niet bekleed\"}\u003c/td\u003e\u003ctd class=\"n\"\u003e${c.cover?fmt(c.net):\"\"}\u003c/td\u003e\u003ctd class=\"n\"\u003e${c.cover?(wp==null?\"op aanvraag\":eur(wp)):\"\"}\u003c/td\u003e\u003c/tr\u003e`; }).join(\"\")+`\u003c/tbody\u003e`;\n $(\"#matList\").innerHTML=pr.lines.map(l=\u003e`\u003cdiv\u003e\u003cspan\u003e${esc(l.naam)}${l.code?` \u003cspan class=\"code\"\u003e${esc(l.code)}\u003c/span\u003e`:\"\"}\u003c/span\u003e\u003cspan class=\"q\"\u003e${l.qty} ${l.unit}\u003c/span\u003e\u003cspan class=\"pr\"\u003e${l.tot==null?\"op aanvraag\":eur(l.tot)}\u003c/span\u003e\u003cspan class=\"sub\"\u003e${l.unitPrice!=null?`${eur(l.unitPrice)} per ${l.unit} · `:\"\"}${esc(l.detail)}\u003c/span\u003e\u003c/div\u003e`).join(\"\");\n const levTxt=S.delivery===\"afhalen\"?\"afhalen\":(pr.sub\u003c=0?\"—\":\"via de dealer\");\n $(\"#totals\").innerHTML=`\u003cdiv\u003e\u003cspan\u003eSubtotaal\u003c/span\u003e\u003cspan\u003e${eur(pr.sub)}\u003c/span\u003e\u003c/div\u003e\u003cdiv\u003e\u003cspan\u003eLevering\u003c/span\u003e\u003cspan\u003e${levTxt}\u003c/span\u003e\u003c/div\u003e\u003cdiv class=\"grand\"\u003e\u003cspan\u003eTotaal incl. btw\u003c/span\u003e\u003cspan\u003e${eur(pr.total)}\u003c/span\u003e\u003c/div\u003e`;\n $(\"#priceNote\").textContent=`Adviesprijzen voor consumenten incl. btw (${PRICE.versie}). We rekenen in hele pakken, inclusief snijverlies. Levering via een dealer bij je in de buurt. Wij controleren de maten en hoeveelheden bij je aanvraag; de definitieve prijs staat in de offerte.`;\n const txt=[\"Aanvraag via Ruimteplanner\",`Ruimte: ${S.room}`,`Vorm: ${SHAPES[S.shape].name}, plafondhoogte ${S.H} cm`,\n ...t.wc.map(c=\u003e`Wand ${c.gw.i+1} (${fmt(c.gw.L,0)} cm): ${c.cover?`${c.p.short} ${sizeTxt(c.p,c.sz)}, ${c.w.decor}, hoogte ${fmt(c.cover,0)} cm, ${fmt(c.net)} m²${c.w.openings.length?`, ${c.w.openings.map(o=\u003e`${o.type} ${o.w}×${o.h}`).join(\", \")}`:\"\"}`:\"niet bekleed\"}`),\n S.items.length?`Inrichting (ter illustratie): ${S.items.map(it=\u003e{const def=libDef(it.id);return `${def.name} ${Math.round(it.w)}×${Math.round(it.d)}${it.h!=null?`×${Math.round(it.h)}`:\"\"}${def.pal?` ${PAL[def.pal][it.color||0][0].toLowerCase()}`:\"\"}${it.id===\"douche\"?` ${GLASS[it.glass||0][0].toLowerCase()}`:\"\"}`;}).join(\", \")}`:\"\",\n floorOn()?`Vloer: ${t.fp.short} ${sizeTxt(t.fp,t.fs)}, ${S.floor.decor}, ${fmt(t.floorA)} m², ${S.floor.mount===\"lijm\"?\"verlijmd\":\"zwevend\"}`:\"Vloer: niet bekleed\",\n \"\",\"Bestellijst (adviesprijzen incl. btw, \"+PRICE.versie+\"):\",\n ...pr.lines.map(l=\u003e`- ${l.code?l.code+\" \":\"\"}${l.naam}: ${l.qty} ${l.unit}${l.tot==null?\" (op aanvraag)\":` × ${eur(l.unitPrice)} = ${eur(l.tot)}`}`),\n `Subtotaal: ${eur(pr.sub)}`,`Levering: ${S.delivery===\"afhalen\"?\"zelf afhalen op afspraak\":(pr.sub\u003c=0?\"—\":\"via de dealer\")}`,`Totaal incl. btw: ${eur(pr.total)}${pr.onRequest?\" + posten op aanvraag\":\"\"}`];\n if(t.wc.some(c=\u003ec.cover\u0026\u0026c.w.decor===\"Eigen ontwerp\")) txt.push(\"Eigen ontwerp: afbeelding stuur ik mee per e-mail\");\n const s=txt.filter(x=\u003ex!==\"\").join(\"\\n\"); $(\"#copyBox\").value=s;\n}\nfunction renderAll(geom){ if(!syncWalls()) return; reattach(); fixDecors(); renderPlan(); wallTabs(); renderWallEditor(); renderFloor(); renderSummary(); if(geom!==false||true) rebuild3D(); }\nlet changeT=0; function changed(){ fixDecors(); renderPlan(); wallTabs(); renderWallEditor(); renderFloor(); renderSummary(); clearTimeout(changeT); changeT=setTimeout(rebuild3D,60); }\nfunction invalidateTextures(){ texCache.clear(); clearTimeout(invalidateTextures.t); invalidateTextures.t=setTimeout(()=\u003e{ rebuild3D(); },150); }\n\n\nfunction renderLib(){ $(\"#lib\").innerHTML=Object.entries(LIB).map(([cat,list])=\u003e`\u003cdiv class=\"libcat\"\u003e\u003cp class=\"field-label\"\u003e${cat}\u003c/p\u003e\u003cdiv class=\"libgrid\"\u003e${list.map(x=\u003e`\u003cbutton class=\"libitem\" data-lib=\"${x.id}\" type=\"button\"\u003e\u003cb\u003e${x.name}\u003c/b\u003e\u003cspan\u003e${x.w} × ${x.d}${x.h?` × ${x.h}`:\"\"} cm\u003c/span\u003e\u003c/button\u003e`).join(\"\")}\u003c/div\u003e\u003c/div\u003e`).join(\"\"); }\nfunction renderItemsUI(){\n $(\"#placedBox\").hidden=!S.items.length;\n $(\"#placed\").innerHTML=S.items.map((it,i)=\u003e`\u003cbutton class=\"chip\" data-item=\"${i}\" aria-pressed=\"${i===S.selItem}\" type=\"button\"\u003e${libDef(it.id).name}\u003c/button\u003e`).join(\"\");\n const it=S.items[S.selItem], ed=$(\"#itemEd\"); ed.hidden=!it; if(!it) return; const def=libDef(it.id);\n const num=(k,label,min,max)=\u003e`\u003clabel class=\"num\"\u003e${label}\u003cinput type=\"number\" data-dim=\"${k}\" value=\"${Math.round(it[k])}\" min=\"${min}\" max=\"${max}\" step=\"1\"\u003e\u003c/label\u003e`;\n ed.innerHTML=`\u003ch3\u003e${def.name}\u003c/h3\u003e\n \u003cdiv class=\"grid4\"\u003e${num(\"w\",\"Breedte (cm)\",10,600)}${num(\"d\",\"Diepte (cm)\",2,400)}${it.h!=null?num(\"h\",\"Hoogte (cm)\",10,300):\"\"}${it.z!=null?num(\"z\",\"Vanaf vloer (cm)\",0,250):\"\"}\u003c/div\u003e\n ${(()=\u003e{ const g=wallsGeo(); if(!it.att) return `\u003cp class=\"note\"\u003eDit object staat los in de ruimte. Kies \"Tegen de wand\" om het aan de gekozen wand ${S.sel+1} te zetten.\u003c/p\u003e`; const gw=g[it.att.i], l=Math.round(leftOf(gw,it)), r=Math.round(gw.L-l-it.w);\n return `\u003cdiv class=\"grid4\"\u003e\u003clabel class=\"num\"\u003eOp wand\u003cselect data-wallsel\u003e${g.map(w=\u003e`\u003coption value=\"${w.i}\"${w.i===it.att.i?\" selected\":\"\"}\u003eWand ${w.i+1}\u003c/option\u003e`).join(\"\")}\u003c/select\u003e\u003c/label\u003e\u003clabel class=\"num\"\u003eVanaf linkerhoek (cm)\u003cinput type=\"number\" data-pos=\"l\" value=\"${l}\" min=\"0\" max=\"${Math.round(gw.L)}\" step=\"1\"\u003e\u003c/label\u003e\u003clabel class=\"num\"\u003eTot rechterhoek (cm)\u003cinput type=\"number\" data-pos=\"r\" value=\"${r}\" min=\"0\" max=\"${Math.round(gw.L)}\" step=\"1\"\u003e\u003c/label\u003e\u003c/div\u003e`; })()}\n ${def.sizes?`\u003cdiv class=\"chips\"\u003e${def.sizes.map(([a,b])=\u003e`\u003cbutton class=\"chip mono\" data-isize=\"${a}x${b}\" aria-pressed=\"${a===it.w\u0026\u0026b===it.d}\" type=\"button\"\u003e${a} × ${b}\u003c/button\u003e`).join(\"\")}\u003c/div\u003e`:\"\"}\n ${def.pal?`\u003cdiv\u003e\u003cp class=\"field-label\"\u003e${it.id===\"douche\"?\"Kleur profiel\":\"Kleur\"}\u003c/p\u003e\u003cdiv class=\"swatches\"\u003e${PAL[def.pal].map(([n,c],k)=\u003e`\u003cbutton class=\"swatch\" data-color=\"${k}\" aria-pressed=\"${k===(it.color||0)}\" type=\"button\"\u003e\u003ci style=\"background:${hex(c)}\"\u003e\u003c/i\u003e${n}\u003c/button\u003e`).join(\"\")}\u003c/div\u003e\u003c/div\u003e`:\"\"}\n ${it.id===\"douche\"?`\u003cdiv\u003e\u003cp class=\"field-label\"\u003eGlas\u003c/p\u003e\u003cdiv class=\"swatches\"\u003e${GLASS.map(([n,c],k)=\u003e`\u003cbutton class=\"swatch\" data-glass=\"${k}\" aria-pressed=\"${k===(it.glass||0)}\" type=\"button\"\u003e\u003ci style=\"background:${hex(c)}\"\u003e\u003c/i\u003e${n}\u003c/button\u003e`).join(\"\")}\u003c/div\u003e\u003c/div\u003e`:\"\"}\n \u003cdiv class=\"row\"\u003e\u003cbutton class=\"btn small\" data-act=\"rot\" type=\"button\"\u003eDraai 90°\u003c/button\u003e\u003cbutton class=\"btn small\" data-act=\"wall\" type=\"button\"\u003eTegen de wand\u003c/button\u003e\u003cbutton class=\"btn small\" data-act=\"dup\" type=\"button\"\u003eDupliceer\u003c/button\u003e\u003cbutton class=\"btn small\" data-act=\"del\" type=\"button\"\u003eVerwijder\u003c/button\u003e\u003c/div\u003e\n \u003cp class=\"note\"\u003eSchuif het object in het wandaanzicht, of versleep het in de 3D-weergave.\u003c/p\u003e`;\n}\n$(\"#lib\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-lib]\");if(b){addItem(b.dataset.lib);toast(`${libDef(b.dataset.lib).name} geplaatst`);}});\n$(\"#placed\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-item]\");if(!b)return;S.selItem=+b.dataset.item;rebuildItems();renderItemsUI();renderPlan();});\nfunction itemChanged(it,resnap){ if(resnap\u0026\u0026it.att) putOnWall(it,it.att.i,it.att.s); S.defaultItems=false; rebuildItems(); renderPlan(); }\n$(\"#itemEd\").addEventListener(\"input\",e=\u003e{const it=S.items[S.selItem];if(!it)return;\n if(e.target.dataset.pos\u0026\u0026it.att){ const gw=wallsGeo()[it.att.i], v=parseFloat(e.target.value); if(isNaN(v)) return; const xl=e.target.dataset.pos===\"l\"?v:gw.L-it.w-v;\n putOnWall(it,it.att.i,sFromLeft(gw,clamp(xl,0,Math.max(0,gw.L-it.w)),it.w)); S.defaultItems=false; clearTimeout(itemChanged.t); itemChanged.t=setTimeout(()=\u003e{ rebuildItems(); renderPlan(); const o=$(e.target.dataset.pos===\"l\"?'[data-pos=\"r\"]':'[data-pos=\"l\"]'); if(o) o.value=Math.round(e.target.dataset.pos===\"l\"?gw.L-leftOf(gw,it)-it.w:leftOf(gw,it)); },120); return; }\n const k=e.target.dataset.dim;if(!k)return;const v=parseFloat(e.target.value);if(isNaN(v))return;\n const lim={w:[10,600],d:[2,400],h:[10,300],z:[0,250]}[k]; it[k]=clamp(v,lim[0],lim[1]); clearTimeout(itemChanged.t); itemChanged.t=setTimeout(()=\u003eitemChanged(it,k===\"w\"||k===\"d\"),150);});\n$(\"#itemEd\").addEventListener(\"change\",e=\u003e{ const it=S.items[S.selItem]; if(!it||!e.target.matches(\"[data-wallsel]\")) return; const i=+e.target.value; S.sel=i; putOnWall(it,i,freeSpot(it,i)); S.defaultItems=false; rebuildItems(); renderItemsUI(); renderPlan(); });\n$(\"#itemEd\").addEventListener(\"click\",e=\u003e{const it=S.items[S.selItem];if(!it)return;const sz=e.target.closest(\"[data-isize]\"),act=e.target.closest(\"[data-act]\"),col=e.target.closest(\"[data-color]\"),gl=e.target.closest(\"[data-glass]\");\n if(sz){const[a,b]=sz.dataset.isize.split(\"x\").map(Number);it.w=a;it.d=b;if(it.att)putOnWall(it,it.att.i,it.att.s);else if(libDef(it.id).wall)snapItem(it,true);}\n else if(col) it.color=+col.dataset.color;\n else if(gl) it.glass=+gl.dataset.glass;\n else if(act){const a=act.dataset.act;\n if(a===\"rot\"){ it.phi+=Math.PI/2; it.att=null; } else if(a===\"wall\") putOnWall(it,S.sel,freeSpot(it,S.sel));\n else if(a===\"dup\"){const c={...it,att:null,x:it.x+20,y:it.y-20}; if(it.att) putOnWall(c,it.att.i,freeSpot(c,it.att.i)); else snapItem(c,false); S.items.push(c);S.selItem=S.items.length-1;}\n else if(a===\"del\"){S.items.splice(S.selItem,1);S.selItem=-1;}}\n else return; S.defaultItems=false; rebuildItems();renderItemsUI();renderPlan();});\nfunction placeDefaults(){ // voorbeeldinrichting badkamer\n S.items=[]; const g=perimGeo(); if(g.length\u003c4) return; const back=g[0], left=g[g.length-1];\n const at=(id,gw,s,extra)=\u003e{const it=newItem(id,extra);it.x=gw.p[0]+gw.d[0]*s+gw.nin[0]*40;it.y=gw.p[1]+gw.d[1]*s+gw.nin[1]*40;snapItem(it,true);S.items.push(it);return it;};\n if(back.L\u003e=200){ at(\"douche\",back,back.L-45,{w:90,d:120}); at(\"meubel\",back,60); at(\"spiegel\",back,60); }\n else { at(\"meubel\",back,Math.min(50,back.L/2)); at(\"spiegel\",back,Math.min(50,back.L/2)); }\n if(left.L\u003e=120){ at(\"toilet\",left,left.L*.45); at(\"wandkast\",left,left.L*.45,{z:150}); }\n S.selItem=-1; S.defaultItems=true; }\n\n/* ---------- AI-render ---------- */\nconst RENDER_URL=\"/wp-json/ruimteplanner/v1/render\"; // WordPress-plugin \"Ruimteplanner AI-render\"\nconst ROOM_EN={Badkamer:\"bathroom\",Toilet:\"toilet room\",Keuken:\"kitchen\",Wasruimte:\"laundry room\",Woonkamer:\"living room\",Slaapkamer:\"bedroom\",Hal:\"hallway\",Bedrijfsruimte:\"commercial interior\"};\nconst LOOK_EN={marble:\"marble look\",concrete:\"concrete look\",stone:\"natural stone look\",lines:\"layered sandstone look\",solid:\"solid matt colour\",wood:\"oak wood look\",forest:\"printed forest photo mural\",ice:\"printed ice cave photo mural\",dandelion:\"printed dandelion photo mural\",terrazzo:\"printed terrazzo pattern\"};\nconst ITEM_EN={toilet:\"wall-hung white toilet with flush plate\",douche:\"walk-in shower: flush floor-level shower tray, ONE fixed glass side panel (no door, no hinges, open entrance), wall-mounted rain shower head and hand shower\",bad:\"built-in white bathtub\",vbad:\"freestanding white bathtub\",meubel:\"wall-hung vanity unit with washbasin\",spiegel:\"wall mirror\",spiegelkast:\"mirror cabinet\",wandkast:\"wall-hung cabinet with doors\",kolom:\"tall wall-hung column cabinet\",radiator:\"white towel radiator\",tv:\"TV cabinet with flat-screen TV\",kast:\"cabinet with doors\",bank:\"grey fabric sofa\",fauteuil:\"grey fabric armchair\",tafel:\"table\",stoel:\"chair\",plant:\"potted plant\",bed:\"double bed with bedding\",bureau:\"desk\",keuken:\"kitchen unit with worktop, hob, sink and wall cabinets\"};\nconst COL_EN={\"Wit\":\"white\",\"Eiken\":\"light oak\",\"Noten\":\"walnut\",\"Lichtgrijs\":\"light grey\",\"Antraciet\":\"anthracite\",\"Zwart\":\"black\",\"Salie\":\"sage green\",\"Nachtblauw\":\"navy blue\",\"Chroom\":\"chrome\",\"Mat zwart\":\"matt black\",\"Goud\":\"brushed gold\",\"RVS geborsteld\":\"brushed stainless steel\",\"Helder glas\":\"clear glass\",\"Rookglas\":\"smoked grey glass\",\"Melkglas\":\"frosted glass\"};\nconst enN=(n,d=1)=\u003eString(Math.round(n*Math.pow(10,d))/Math.pow(10,d));\nfunction surfaceDesc(p,sz,d){\n const look=LOOK_EN[(LOOK[d.name]||[\"solid\"])[0]]||\"decor\";\n if(d.custom) return `Digi-panels printed with the customer's own image as one continuous picture across the wall`;\n if(p.layout===\"mural\") return `Aqua Composite Digi-panels \"${d.name}\" (${look}): one continuous printed image across the whole wall that reads as seamless; the panel joints are practically invisible`;\n if(p.layout===\"tiles\") return `Aqua Composite Tiles \"${d.name}\" (${look}), ${enN(sz.w,1)} × ${enN(sz.l,1)} cm tiles laid horizontally in a half bond (running bond: every row is offset by half a tile length, NOT a stacked grid), with practically invisible joints in the exact tile colour (no grout lines)`;\n if(p.layout===\"planks\") return `\"${d.name}\" vinyl plank floor (${look}), planks ${enN(sz.w,0)} × ${enN(sz.l,1)} cm`;\n if(p.layout===\"herring\") return `\"${d.name}\" herringbone vinyl floor (${look}), strips ${enN(sz.w,0)} × ${enN(sz.l,0)} cm`;\n return `Aqua Composite ${p.short} \"${d.name}\" (${look}), large panels ${enN(sz.w,1)} cm wide and ${enN(sz.l,1)} cm high; the wall reads as one calm, seamless surface and the panel joints are practically invisible`;\n}\n/* beeldanalyse van de momentopname: waar staat elk object in het beeld (in % van links en van boven) */\nlet SNAP=null;\nfunction snapAnalyse(cam){\n cam.updateMatrixWorld(); const inv=cam.matrixWorldInverse;\n const area=pts=\u003e{ const vis=[]; pts.forEach(v=\u003e{ const c=v.clone().applyMatrix4(inv); if(c.z\u003e-0.05) return; const p=v.clone().project(cam); if(Math.abs(p.x)\u003c=1.02\u0026\u0026Math.abs(p.y)\u003c=1.02) vis.push(p); });\n if(!vis.length) return null; const cl=v=\u003eMath.max(0,Math.min(100,Math.round(v)));\n const xs=vis.map(p=\u003e(p.x+1)*50), ys=vis.map(p=\u003e(1-p.y)*50);\n return {x0:cl(Math.min(...xs)),x1:cl(Math.max(...xs)),y0:cl(Math.min(...ys)),y1:cl(Math.max(...ys)),frac:vis.length/pts.length}; };\n const wallPts=(gw,h0,h1,a0,a1)=\u003e{ const out=[]; for(let i=0;i\u003c=8;i++) for(let j=0;j\u003c=4;j++){ const s=a0+(a1-a0)*i/8, h=h0+(h1-h0)*j/4; out.push(W3(gw.p[0]+gw.d[0]*s+gw.nin[0]*2,gw.p[1]+gw.d[1]*s+gw.nin[1]*2,h)); } return out; };\n const walls={}, openings=[], items={}, cut={};\n R.walls.forEach(({g,gw})=\u003e{ if(!g.visible){ cut[gw.i]=true; return; } const a=area(wallPts(gw,0,S.H,0,gw.L)); if(a) walls[gw.i]=a;\n (S.walls[gw.i].openings||[]).forEach(o=\u003e{ const b=area(wallPts(gw,o.y,o.y+o.h,o.x,o.x+o.w)); if(b) openings.push({wall:gw.i,o,a:b}); }); });\n R.items.children.forEach(g=\u003e{ if(g.userData.helper||g.userData.idx==null||!g.visible) return; const box=new THREE.Box3().setFromObject(g); if(box.isEmpty()) return;\n const pts=[]; for(let i=0;i\u003c=2;i++) for(let j=0;j\u003c=2;j++) for(let k=0;k\u003c=2;k++) pts.push(new THREE.Vector3(box.min.x+(box.max.x-box.min.x)*i/2,box.min.y+(box.max.y-box.min.y)*j/2,box.min.z+(box.max.z-box.min.z)*k/2));\n const a=area(pts), ctr=box.getCenter(new THREE.Vector3()); items[g.userData.idx]={a,hcm:Math.round((box.max.y-box.min.y)*100),zcm:Math.round(box.min.y*100),dist:ctr.distanceTo(cam.position)}; });\n const ds=[]; PTS.forEach(p=\u003eds.push(W3(p[0],p[1],S.H/2).distanceTo(cam.position)));\n return {walls,openings,items,cut,dmin:Math.min(...ds),dmax:Math.max(...ds)};\n}\nconst where=a=\u003e{ const xc=(a.x0+a.x1)/2, h=xc\u003c20?\"far left\":xc\u003c40?\"left\":xc\u003c60?\"centre\":xc\u003c80?\"right\":\"far right\";\n return `${h} of image 1, x ${a.x0}–${a.x1}%, y ${a.y0}–${a.y1}%${a.frac\u003c.5?\", partly cut off by the image edge\":\"\"}`; };\nfunction touchingWalls(it){ const ax=itemAxes(it), hw=it.w/2, hd=it.d/2;\n const cs=[[-1,-1],[1,-1],[1,1],[-1,1]].map(([a,b])=\u003e[it.x+ax.x[0]*hw*a+ax.f[0]*hd*b,it.y+ax.x[1]*hw*a+ax.f[1]*hd*b]);\n return wallsGeo().filter(gw=\u003ecs.some(c=\u003e{ const rx=c[0]-gw.p[0],ry=c[1]-gw.p[1],sA=rx*gw.d[0]+ry*gw.d[1],dist=Math.abs(rx*gw.nin[0]+ry*gw.nin[1]); return dist\u003c8\u0026\u0026sA\u003e-5\u0026\u0026sA\u003cgw.L+5; })).map(gw=\u003egw.i+1); }\nfunction shapeDesc(){ return shapeDesc0()+cabDesc(); }\nfunction shapeDesc0(){ const P=S.params, g=wallsGeo(), xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]), x0=Math.min(...xs),x1=Math.max(...xs),y0=Math.min(...ys),y1=Math.max(...ys);\n const inner=PTS.filter(p=\u003ep[0]\u003ex0+.5\u0026\u0026p[0]\u003cx1-.5\u0026\u0026p[1]\u003ey0+.5\u0026\u0026p[1]\u003cy1-.5), touch=g.filter(w=\u003einner.some(p=\u003e(p[0]===w.p[0]\u0026\u0026p[1]===w.p[1])||(p[0]===w.q[0]\u0026\u0026p[1]===w.q[1]))).map(w=\u003ew.i+1);\n const lst=a=\u003ea.length\u003e1?a.slice(0,-1).join(\", \")+\" and \"+a[a.length-1]:String(a[0]);\n if(S.shape===\"nis\"){ const nw=g.filter(w=\u003eMath.max(w.p[1],w.q[1])\u003eP.B+.5).map(w=\u003ew.i+1); return `The floor plan is a rectangle with a recessed niche in the back: walls ${lst(nw)} form the niche (${P.W} cm wide, ${P.N} cm deep). Keep this recess clearly visible.`; }\n if(S.shape===\"koof\") return `There is a boxed-in column (${P.W} × ${P.N} cm) protruding from the back wall into the room; walls ${lst(touch)} are its faces. Keep it clearly visible.`;\n if(S.shape===\"L\") return `The room is L-shaped: walls ${lst(touch)} form an outside corner that protrudes into the room; keep it clearly visible.`;\n return `The floor plan is a simple rectangle.`; }\nfunction shapeDescOld(){ const P=S.params;\n if(S.shape===\"nis\") return `The floor plan is a rectangle with a recessed niche in the back: walls 2, 3 and 4 form the niche (${P.W} cm wide, ${P.N} cm deep); walls 1 and 5 are the parts of the back wall on either side of it. Keep this recess clearly visible.`;\n if(S.shape===\"koof\") return `There is a boxed-in column (${P.W} × ${P.N} cm) protruding from the back wall into the room; keep it clearly visible.`;\n if(S.shape===\"L\") return `The room is L-shaped: walls 2 and 3 form an outside corner that protrudes into the room; keep it clearly visible.`;\n return `The floor plan is a simple rectangle.`; }\nfunction roomSize(){ const xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]); const a=(Math.max(...xs)-Math.min(...xs))/100,b=(Math.max(...ys)-Math.min(...ys))/100;\n return {a,b,m2:Math.abs(area2(PTS))/2/10000}; }\nfunction buildRender(){\n const refs=[], refIdx=new Map(), addRef=d=\u003e{ if(!d.file||!/^https:\\/\\/(www\\.)?hbcomposite\\.com\\//.test(d.file)) return \"\"; if(!refIdx.has(d.file)){ if(refs.length\u003e=10) return \"\"; refs.push(d.file); refIdx.set(d.file,refs.length+1); } return ` (texture reference: image ${refIdx.get(d.file)})`; };\n const SN=SNAP||{walls:{},openings:[],items:{}}, n=S.walls.length;\n const lines=[], cabLines=[];\n S.walls.forEach((w,i)=\u003e{ const a=SN.walls[i]; if(!a) return; let t;\n if(clad(w)){ const p=WALL[w.prod],sz=prodSize(p,w.size),d=decorOf(p,w.decor),c=coverOf(w); t=`${surfaceDesc(p,sz,d)}${addRef(d)}${c\u003cS.H-1?`, cladding up to ${Math.round(c)} cm height with plain white painted wall above`:\", floor to ceiling\"}`; }\n else t=\"plain white painted plaster\";\n if(i\u003e=perimGeo().length){ const g=cabLines.find(x=\u003ex.t===t); if(g) g.w.push(i+1); else cabLines.push({t,w:[i+1]}); return; }\n lines.push(`- Wall ${i+1} (${where(a)}), ${Math.round(wallsGeo()[i].L)} cm long: ${t}.`); });\n cabLines.forEach(g=\u003elines.push(`- ${g.w.length\u003e1?\"Walls \"+g.w.join(\", \"):\"Wall \"+g.w[0]} (faces of the toilet cubicle partitions and front walls): ${g.t}.`));\n const hidden=S.walls.map((w,i)=\u003eSN.walls[i]||(SN.cut||{})[i]?null:i+1).filter(Boolean), cutW=Object.keys(SN.cut||{}).map(k=\u003e+k+1);\n const xy=a=\u003e`x ${a.x0}–${a.x1}%`, dGrp=[]; SN.openings.forEach(({wall,o,a})=\u003e{ if(o.type!==\"deur\") return; const k=o.w+\"x\"+o.h, g=dGrp.find(x=\u003ex.k===k); if(g) g.l.push([wall,a]); else dGrp.push({k,o,l:[[wall,a]]}); });\n const ops=dGrp.filter(g=\u003eg.l.length\u003e1).map(g=\u003e`- ${g.l.length} door openings ${g.o.w} × ${g.o.h} cm, in ${g.l.map(([wl,a])=\u003e`wall ${wl+1} (${xy(a)})`).join(\", \")}: show white flush interior doors with slim frames, clearly visible at exactly these places.`)\n .concat(SN.openings.filter(({o})=\u003eo.type!==\"deur\"||(dGrp.find(x=\u003ex.k===o.w+\"x\"+o.h)||{l:[]}).l.length\u003c2).map(({wall,o,a})=\u003eo.type===\"deur\"\n ?`- Door opening ${o.w} × ${o.h} cm in wall ${wall+1} (${where(a)}): show a white flush interior door with a slim frame, clearly visible at exactly this place.`\n :`- Window ${o.w} × ${o.h} cm in wall ${wall+1}, ${o.y} cm above the floor (${where(a)}): white frame with soft daylight.`));\n const F=S.floor, fp=FLOOR[F.prod], fd=decorOf(fp,F.decor);\n const floor=floorOn()?`${surfaceDesc(fp,prodSize(fp,F.size),fd)}${addRef(fd)}`:\"light grey large-format floor tiles\";\n const vis=[], visG=[], off=[], rng=Math.max(.01,(SN.dmax||1)-(SN.dmin||0));\n const WALLHUNG=[\"toilet\",\"meubel\",\"spiegel\",\"spiegelkast\",\"wandkast\",\"kolom\",\"radiator\"];\n S.items.forEach((it,i)=\u003e{ const def=libDef(it.id), info=SN.items[i]||{}; let t=ITEM_EN[it.id]||def.name;\n if(def.pal){ const c=COL_EN[PAL[def.pal][it.color||0][0]]; t+=it.id===\"douche\"?`, ${c} profiles`:`, ${c} finish`; } if(it.id===\"douche\") t+=`, ${COL_EN[GLASS[it.glass||0][0]]}`;\n const H=(it.h!=null||def.h!=null)?(info.hcm||it.h||def.h):null, Z=it.z!=null?it.z:def.z;\n t+=`, ${Math.round(it.w)} cm wide × ${Math.round(it.d)} cm deep${H?` × ${H} cm high`:\"\"}`;\n if(Z) t+=`, wall-hung ${Math.round(Z)} cm above the floor`;\n else if(WALLHUNG.includes(it.id)) t+=\", wall-hung\";\n else t+=\", standing on the floor (not wall-hung)\";\n if(it.id===\"douche\"){ const tw=touchingWalls(it); if(tw.length) t+=`, built into the space between walls ${tw.join(\", \")}`; }\n else if(it.att){ t+=(SN.cut||{})[it.att.i]?`, with its back against wall ${it.att.i+1}, which is cut away and not shown (so the object stands free in front of the camera)`:`, against wall ${it.att.i+1}`; }\n let dz=\"\"; if(info.a\u0026\u0026info.dist!=null){ const r=(info.dist-(SN.dmin||0))/rng; dz=r\u003c.35?\"foreground\":r\u003c.7?\"middle\":\"background\"; }\n if(info.a){ const g=visG.find(x=\u003ex.t===t); if(g) g.l.push([info.a,dz]); else visG.push({t,l:[[info.a,dz]]}); } else off.push(t); });\n const DZ={foreground:\", in the foreground close to the camera\",middle:\", in the middle of the room\",background:\", in the background\"};\n visG.forEach(g=\u003e{ if(g.l.length===1){ const [a,dz]=g.l[0]; vis.push(`- ${g.t}${DZ[dz]||\"\"} — ${where(a)}.`); } else vis.push(`- ${g.l.length} × ${g.t}, side by side at: ${g.l.map(([a])=\u003exy(a)).join(\"; \")}.`); });\n const visN=visG.reduce((s,g)=\u003es+g.l.length,0);\n const rs=roomSize();\n const prompt=[\n `Turn image 1, a simplified 3D visualisation, into a photorealistic interior photograph of a modern ${ROOM_EN[S.room]||\"room\"}.`,\n `This is a SMALL room: about ${enN(rs.a)} × ${enN(rs.b)} m (floor area ${enN(rs.m2)} m²), ceiling height ${S.H} cm. Keep these real proportions. Do not make the room look bigger, wider or deeper, do not widen the field of view and do not lower or move the camera: use exactly the same camera position, angle, perspective and framing as image 1.`,\n shapeDesc(),\n `Keep the room shape, every wall, niche and corner exactly as in image 1. Walls that are cut away in image 1 are behind the camera; do not add them. Ignore any numbered circles or outlines; they are interface markers.`,\n `Walls (positions are given as % from the left and from the top of image 1). Keep these exact decors and patterns:`, ...lines,\n cutW.length?`Walls ${cutW.join(\", \")} are cut away (they are behind or beside the camera) and must not be shown.`:\"\",\n hidden.length?`Walls ${hidden.join(\", \")} are hidden from this viewpoint by the perspective.`:\"\",\n ops.length?`Doors and windows, keep them visible in exactly these places:\\n${ops.join(\"\\n\")}`:\"There are no visible doors or windows; do not add any.\",\n `Floor: ${floor}.`,\n vis.length?`Furniture and fixtures: image 1 contains exactly ${visN} object${visN\u003e1?\"s\":\"\"}. Every one of them must appear exactly once, at the same place, with the same size, proportions and colour (make them realistic and good quality). Do not duplicate, move or invent cabinets, shelves or other objects; walls without objects in image 1 stay empty:\\n${vis.join(\"\\n\")}`:\"No furniture; do not add any.\",\n `Joints: in reality the joints between panels and tiles are practically invisible from normal viewing distance. Render the wall surfaces as calm, continuous surfaces; at most an extremely faint hairline in exactly the surface colour, and when in doubt leave the joint out. Never draw dark, light, wide, shadowed or grout-style lines, and do not add joints that are not in image 1.`,\n `Light: soft natural light with subtle warm ceiling spots, realistic reflections and soft shadows. Style: high-end architectural interior photography, straight verticals, sharp and clean. No people, no text, no logos, no watermarks.`\n ].filter(Boolean).join(\"\\n\");\n return {prompt:fitPrompt(prompt),refs};\n}\nfunction fitPrompt(p){ const MAX=7800; if(p.length\u003c=MAX) return p;\n p=p.replace(/, y \\d+(?:\\.\\d+)?–\\d+(?:\\.\\d+)?%/g,\"\"); if(p.length\u003c=MAX) return p;\n p=p.replace(/ \\((?:far left|left|centre|right|far right) of image 1, x [^)]*\\)/g,\"\"); if(p.length\u003c=MAX) return p;\n return p.slice(0,MAX); }\nfunction snapshot(){\n const r=R.renderer, cam=R.camera, el=r.domElement, w0=el.width, h0=el.height, pr=r.getPixelRatio();\n JOINT=.05; rebuild3D(); // voegen nauwelijks zichtbaar in de momentopname voor de AI\n const eye=S.view===\"over\"||S.view===\"wall\", keep={pos:cam.position.clone(),q:cam.quaternion.clone(),order:cam.rotation.order,fov:cam.fov,ceil:R.ceil?R.ceil.visible:false};\n if(eye){ const ps=poseShowroom(S.view===\"over\"?facingWall():S.sel); cam.position.copy(ps.pos); cam.rotation.order=\"YXZ\"; cam.rotation.set(ps.pitch,ps.yaw,0); cam.fov=55; cam.updateMatrixWorld(); if(R.ceil) R.ceil.visible=true; }\n const hidden=[]; R.scene.traverse(o=\u003e{ if(o.isSprite\u0026\u0026o.visible){ o.visible=false; hidden.push(o); } }); R.items.children.forEach(o=\u003e{ if(o.userData.helper\u0026\u0026o.visible){ o.visible=false; hidden.push(o); } });\n r.setPixelRatio(1); r.setSize(1536,1024,false); const asp=cam.aspect, fov0=cam.fov; if(S.view===\"r360\"\u0026\u0026!eye\u0026\u0026asp\u003e0) cam.fov=2*Math.atan(Math.tan(fov0*Math.PI/360)*Math.min(asp,1.5)/1.5)*180/Math.PI; cam.aspect=1.5; cam.updateProjectionMatrix(); cutaway();\n R.scene.traverse(o=\u003e{ if(o.isSprite\u0026\u0026o.visible){ o.visible=false; hidden.push(o); } }); // cutaway zet wandnummers terug; opnieuw verbergen\n const cutW=new Set(R.walls.filter(w=\u003e!w.g.visible).map(w=\u003ew.gw.i)); // meubels tegen een weggelaten wand niet zwevend in beeld\n R.items.children.forEach(o=\u003e{ const it=S.items[o.userData.idx]; if(o.visible\u0026\u0026it\u0026\u0026it.att\u0026\u0026cutW.has(it.att.i)){ o.visible=false; hidden.push(o); } });\n if(false){ // (overzicht wordt niet meer gebruikt voor de render)\n let m=0; PTS.forEach(p=\u003e[0,S.H].forEach(h=\u003e{ const v=W3(p[0],p[1],h).project(cam); m=Math.max(m,Math.abs(v.x),Math.abs(v.y)); }));\n if(m\u003e0\u0026\u0026m\u003c.95){ cam.fov=2*Math.atan(Math.tan(cam.fov*Math.PI/360)*m*1.08)*180/Math.PI; cam.updateProjectionMatrix(); } }\n r.render(R.scene,cam); const url=el.toDataURL(\"image/jpeg\",.9);\n try{ SNAP=snapAnalyse(cam); }catch(e){ SNAP=null; }\n hidden.forEach(o=\u003eo.visible=true); r.setPixelRatio(pr); cam.fov=fov0; cam.aspect=asp;\n JOINT=.5; rebuild3D();\n if(eye){ cam.position.copy(keep.pos); cam.rotation.order=keep.order; cam.quaternion.copy(keep.q); cam.fov=keep.fov; if(R.ceil) R.ceil.visible=keep.ceil; cam.updateMatrixWorld(); }\n cutaway();\n cam.updateProjectionMatrix(); resize(); return url;\n}\n/* HB-logo klein linksonder op elke AI-render */\nasync function stampLogo(src){ try{\n const load=(u,cors)=\u003enew Promise((ok,no)=\u003e{ const i=new Image(); if(cors) i.crossOrigin=\"anonymous\"; i.onload=()=\u003eok(i); i.onerror=no; i.src=u; });\n const img=await load(src,false); let origin=\"\"; try{ origin=new URL(document.baseURI).origin; }catch(_){}\n const path=\"/wp-content/uploads/2026/04/HB-logo-Colour-2.png\"; let logo=null;\n for(const u of [origin\u0026\u0026/^https?:/.test(origin)?origin+path:null,\"https://hbcomposite.com\"+path].filter(Boolean)){ try{ logo=await load(u,true); break; }catch(_){} }\n const c=document.createElement(\"canvas\"); c.width=img.naturalWidth; c.height=img.naturalHeight; const g=c.getContext(\"2d\"); g.drawImage(img,0,0);\n if(logo){ const w=Math.round(c.width*0.075), h=Math.round(w*logo.naturalHeight/logo.naturalWidth), m=Math.round(c.width*0.018);\n g.save(); g.shadowColor=\"rgba(0,0,0,.35)\"; g.shadowBlur=Math.round(w*0.08); g.drawImage(logo,m,c.height-h-m,w,h); g.restore(); }\n return c.toDataURL(\"image/jpeg\",0.92);\n }catch(_){ return src; } }\nasync function runRender(){\n const btn=$(\"#aiBtn\"), st=$(\"#aiStatus\"), out=$(\"#aiOut\");\n if(!R){ st.textContent=\"De 3D-weergave is niet beschikbaar in deze browser.\"; return; }\n const snap=snapshot(), {prompt,refs}=buildRender();\n out.hidden=false; $(\"#aiBefore\").src=snap; $(\"#aiAfter\").removeAttribute(\"src\"); showAi(\"before\");\n btn.disabled=true; let sec=0; st.textContent=\"De AI maakt je render. Dit duurt meestal 30 tot 90 seconden…\";\n const tick=setInterval(()=\u003e{ sec+=1; st.textContent=`De AI maakt je render… ${sec} s`; },1000);\n try{\n const res=await fetch(RENDER_URL,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({image:snap,prompt,refs})});\n let data=null; try{ data=await res.json(); }catch(_){}\n if(!res.ok||!data||!data.image) throw new Error((data\u0026\u0026data.error)||\"De AI-render is niet gelukt. Probeer het opnieuw.\");\n $(\"#aiAfter\").src=await stampLogo(data.image); showAi(\"after\");\n st.textContent=data.remaining!=null?`Klaar. Je kunt vandaag nog ${data.remaining} render${data.remaining===1?\"\":\"s\"} maken.`:\"Klaar.\";\n }catch(err){\n st.textContent=(err\u0026\u0026err.message\u0026\u0026!/Failed to fetch|NetworkError|Load failed/i.test(err.message))?err.message:\"De AI-render werkt zodra de plugin Ruimteplanner AI-render op de website actief is. In deze voorvertoning zie je alleen de momentopname.\";\n }finally{ clearInterval(tick); btn.disabled=false; }\n}\nfunction showAi(which){ $(\"#aiBefore\").hidden=which!==\"before\"; $(\"#aiAfter\").hidden=which!==\"after\"||!$(\"#aiAfter\").getAttribute(\"src\"); setPressed(\"#aiToggle [data-ai]\",\"ai\",which);\n const isAi=which===\"after\"\u0026\u0026!!$(\"#aiAfter\").getAttribute(\"src\"); $(\"#aiSave\").textContent=isAi?\"Render opslaan\":\"3D-beeld opslaan\"; }\nconst stamp=()=\u003e{ const d=new Date(),z=n=\u003eString(n).padStart(2,\"0\"); return `${d.getFullYear()}${z(d.getMonth()+1)}${z(d.getDate())}-${z(d.getHours())}${z(d.getMinutes())}`; };\nasync function saveFile(url,name){ let href=url, blobUrl=null;\n try{ if(url.startsWith(\"data:\")){ const b=await (await fetch(url)).blob(); blobUrl=href=URL.createObjectURL(b); } }catch(_){}\n const a=document.createElement(\"a\"); a.href=href; a.download=name; a.rel=\"noopener\"; document.body.appendChild(a); a.click(); a.remove();\n if(blobUrl) setTimeout(()=\u003eURL.revokeObjectURL(blobUrl),4000); }\n$(\"#aiSave\").addEventListener(\"click\",()=\u003e{ const ai=$(\"#aiAfter\").getAttribute(\"src\"), showAiImg=ai\u0026\u0026!$(\"#aiAfter\").hidden;\n const src=showAiImg?ai:$(\"#aiBefore\").getAttribute(\"src\"); if(!src) return;\n saveFile(src,`ruimteplanner-${showAiImg?\"render\":\"3d\"}-${stamp()}.jpg`); toast(showAiImg?\"Render opgeslagen\":\"3D-beeld opgeslagen\"); });\n\n/* ---------- pdf van het besteloverzicht (HB Composite-opmaak) ---------- */\nconst HB={naam:\"HB Composite NV\",adres:\"Lindelsebaan 47\",plaats:\"B-3900 Pelt\",land:\"België\",btw:\"BTW BE 0437 961 334\",mail:\"info@hbcomposite.com\",web:\"www.hbcomposite.com\",\n logo:\"/wp-content/uploads/2026/04/HB-logo-Colour-2.png\"};\nconst PDF_LIBS=[\"https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js\",\"https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js\"];\nfunction loadScript(src){ return new Promise((ok,fail)=\u003e{ if(document.querySelector(`script[src=\"${src}\"]`)) return ok(); const el=document.createElement(\"script\"); el.src=src; el.onload=ok; el.onerror=()=\u003efail(new Error(\"script\")); document.head.appendChild(el); }); }\nfunction imgData(url){ return new Promise(ok=\u003e{ const im=new Image(); im.crossOrigin=\"anonymous\";\n im.onload=()=\u003e{ try{ const c=document.createElement(\"canvas\"); c.width=im.naturalWidth; c.height=im.naturalHeight; c.getContext(\"2d\").drawImage(im,0,0); ok({url:c.toDataURL(\"image/png\"),w:im.naturalWidth,h:im.naturalHeight}); }catch(_){ ok(null); } };\n im.onerror=()=\u003eok(null); im.src=url; }); }\nasync function hbLogo(){ const base=(()=\u003e{ try{ return new URL(document.baseURI).origin; }catch(_){ return \"\"; } })();\n const tries=[]; if(/hbcomposite\\.com$/.test(new URL(base||\"https://x.invalid\").hostname)) tries.push(base+HB.logo); tries.push(\"https://hbcomposite.com\"+HB.logo);\n for(const u of tries){ const r=await imgData(u); if(r) return r; } return null; }\nfunction overviewShot(){ // vogelvlucht van de hele ruimte voor de pdf, met wandnummers\n const r=R.renderer, cam=R.camera, el=r.domElement, pr=r.getPixelRatio(), view=S.view;\n const keep={pos:cam.position.clone(),q:cam.quaternion.clone(),order:cam.rotation.order,fov:cam.fov,asp:cam.aspect,ceil:R.ceil?R.ceil.visible:false};\n const hid=[]; R.items.children.forEach(o=\u003e{ if(o.userData.helper\u0026\u0026o.visible){ o.visible=false; hid.push(o); } });\n const c=centroid(), xs=PTS.map(p=\u003ep[0]),ys=PTS.map(p=\u003ep[1]), span=Math.max(Math.max(...xs)-Math.min(...xs),Math.max(...ys)-Math.min(...ys),S.H)/100;\n const tg=W3(c[0],c[1],S.H*.38); cam.position.copy(tg.clone().add(new THREE.Vector3(span*.85,span*1.25,span*1.75))); cam.rotation.order=\"XYZ\"; cam.up.set(0,1,0); cam.lookAt(tg);\n S.view=\"over\"; if(R.ceil) R.ceil.visible=false; r.setPixelRatio(1); r.setSize(1536,1024,false); cam.aspect=1.5; cam.fov=45; cam.updateProjectionMatrix(); cam.updateMatrixWorld(); cutaway();\n let m=0; PTS.forEach(p=\u003e[0,S.H].forEach(h=\u003e{ const v=W3(p[0],p[1],h).project(cam); m=Math.max(m,Math.abs(v.x),Math.abs(v.y)); }));\n if(m\u003e0){ cam.fov=2*Math.atan(Math.tan(cam.fov*Math.PI/360)*m*1.1)*180/Math.PI; cam.updateProjectionMatrix(); }\n r.render(R.scene,cam); const url=el.toDataURL(\"image/jpeg\",.9);\n hid.forEach(o=\u003eo.visible=true); S.view=view; cam.position.copy(keep.pos); cam.rotation.order=keep.order; cam.quaternion.copy(keep.q); cam.fov=keep.fov; cam.aspect=keep.asp; if(R.ceil) R.ceil.visible=keep.ceil;\n r.setPixelRatio(pr); cam.updateProjectionMatrix(); cam.updateMatrixWorld(); cutaway(); resize(); return url; }\nfunction planPng(){ return new Promise(ok=\u003e{ try{\n let svg=planSVG(-1,false); if(!/xmlns=/.test(svg)) svg=svg.replace(\"\u003csvg \",'\u003csvg xmlns=\"http://www.w3.org/2000/svg\" ');\n const vb=(svg.match(/viewBox=\"0 0 ([\\d.]+) ([\\d.]+)\"/)||[0,200,200]), w=+vb[1], h=+vb[2], im=new Image();\n im.onload=()=\u003e{ const k=4, c=document.createElement(\"canvas\"); c.width=w*k; c.height=h*k; const g=c.getContext(\"2d\"); g.fillStyle=\"#fff\"; g.fillRect(0,0,c.width,c.height); g.drawImage(im,0,0,c.width,c.height); ok({url:c.toDataURL(\"image/png\"),r:w/h}); };\n im.onerror=()=\u003eok(null); im.src=\"data:image/svg+xml;charset=utf-8,\"+encodeURIComponent(svg); }catch(_){ ok(null); } }); }\nasync function buildPdf(klant){\n for(const u of PDF_LIBS) await loadScript(u);\n const {jsPDF}=window.jspdf; const doc=new jsPDF({unit:\"mm\",format:\"a4\"}); const W=210, M=16, ACC=[13,106,110], INK=[21,34,42], MUT=[90,106,112];\n const t=totals(), pr=priceAll(t), ref=\"RP-\"+stamp(), today=new Date().toLocaleDateString(\"nl-BE\",{day:\"numeric\",month:\"long\",year:\"numeric\"});\n const nr=v=\u003ev.toLocaleString(\"nl-NL\",{minimumFractionDigits:2,maximumFractionDigits:2}), E=v=\u003e\"€ \"+nr(v);\n const fx=v=\u003eString(v).replace(/²/g,\"2\").replace(/[\\u202f\\u00a0]/g,\" \"); // standaardlettertype kent geen ²\n const fixCell=d=\u003e{ d.cell.text=d.cell.text.map(fx); };\n // kop\n const logo=await hbLogo(); let y=14;\n if(logo){ const h=16, w=Math.min(60,h*logo.w/logo.h); doc.addImage(logo.url,\"PNG\",M,y,w,w*logo.h/logo.w); }\n else { doc.setFont(\"helvetica\",\"bold\"); doc.setFontSize(20); doc.setTextColor(...ACC); doc.text(\"HB Composite\",M,y+10); }\n doc.setFont(\"helvetica\",\"normal\"); doc.setFontSize(8.5); doc.setTextColor(...MUT);\n [HB.naam,HB.adres,HB.plaats+\", \"+HB.land,HB.btw,HB.mail+\" · \"+HB.web].forEach((l,i)=\u003edoc.text(l,W-M,y+2+i*4,{align:\"right\"}));\n y=40; doc.setDrawColor(...ACC); doc.setLineWidth(.8); doc.line(M,y,W-M,y);\n y+=10; doc.setFont(\"helvetica\",\"bold\"); doc.setFontSize(18); doc.setTextColor(...INK); doc.text(klant?\"Offerte-aanvraag Ruimteplanner\":\"Besteloverzicht Ruimteplanner\",M,y);\n doc.setFont(\"helvetica\",\"normal\"); doc.setFontSize(9.5); doc.setTextColor(...MUT); doc.text(`Referentie ${ref} · ${today}`,M,y+6);\n // beelden: AI-render (als die er is), 3D-overzicht met wandnummers en plattegrond\n const ai=$(\"#aiAfter\").getAttribute(\"src\"), ov=R?overviewShot():null, plan=await planPng();\n y+=12; doc.setFont(\"helvetica\",\"normal\");\n if(ai){ const ih=88, iw=ih*1.5, ix=(W-iw)/2; doc.addImage(ai,\"JPEG\",ix,y,iw,ih); y+=ih+4; doc.setFontSize(8); doc.setTextColor(...MUT); doc.text(\"AI-impressie van je ontwerp. Een impressie; kleur en structuur van het echte product kunnen afwijken.\",ix,y); y+=7; }\n if(ov||plan){ if(ai\u0026\u0026y\u003e200){ doc.addPage(); y=20; }\n const rh=ai?62:84, ow=rh*1.5; let x=M;\n if(ov){ doc.addImage(ov,\"JPEG\",x,y,ow,rh); x+=ow+6; }\n if(plan){ const pw=Math.min(W-M-x,rh*plan.r), ph=pw/plan.r; doc.addImage(plan.url,\"PNG\",x,y+(rh-ph)/2,pw,ph); }\n y+=rh+4; doc.setFontSize(8); doc.setTextColor(...MUT); doc.text(\"3D-overzicht en plattegrond met wandnummers, zoals in de tabel hieronder.\",M,y); y+=7; }\n // ruimte\n const rs=roomSize();\n const info=[[\"Ruimte\",S.room],[\"Vorm\",SHAPES[S.shape].name],[\"Afmetingen\",`ca. ${fmt(rs.a,2)} × ${fmt(rs.b,2)} m, plafondhoogte ${S.H} cm`],[\"Wandbekleding\",`${fmt(t.net)} m² op ${t.wc.filter(c=\u003ec.cover).length} van ${S.walls.length} wanden`],[\"Vloer\",S.floor.on?`${t.fp.short} ${sizeTxt(t.fp,t.fs)}, ${S.floor.decor}, ${fmt(t.floorA)} m², ${S.floor.mount===\"lijm\"?\"verlijmd\":\"zwevend\"}`:\"niet bekleed\"],[\"Bevestiging panelen\",S.mount===\"lijm\"?\"polymeerlijm\":\"schroeven\"],[\"Afwerkprofielen\",`${S.prof}, ${PROF[S.prof].colors[S.profColor||0][0].toLowerCase()}`],[\"Levering\",S.delivery===\"afhalen\"?\"zelf afhalen op afspraak\":\"bezorgen\"]];\n if(klant){ info.unshift([\"Aanvrager\",[klant.naam,klant.mail,klant.tel,klant.plaats].filter(Boolean).join(\" · \")]); if(klant.opm) info.push([\"Opmerking\",klant.opm]); }\n const head={fillColor:ACC,textColor:255,fontStyle:\"bold\",fontSize:9}, base={font:\"helvetica\",fontSize:9,textColor:INK,cellPadding:2,lineColor:[213,221,219],lineWidth:.1};\n doc.autoTable({startY:y,margin:{left:M,right:M},theme:\"plain\",styles:Object.assign({},base,{cellPadding:1.4}),columnStyles:{0:{fontStyle:\"bold\",cellWidth:45,textColor:MUT}},body:info,didParseCell:fixCell});\n y=doc.lastAutoTable.finalY+6;\n doc.autoTable({startY:y,margin:{left:M,right:M},theme:\"grid\",styles:base,headStyles:head,\n head:[[\"Wand\",\"Lengte\",\"Product\",\"Decor\",\"m2\",\"Indicatie\"]],\n body:t.wc.map(c=\u003e{ const wp=wallPrice(c); return [String(c.gw.i+1),fmt(c.gw.L,0)+\" cm\",c.cover?c.p.short:\"—\",c.cover?c.w.decor:\"niet bekleed\",c.cover?fmt(c.net):\"\",c.cover?(wp==null?\"op aanvraag\":E(wp)):\"\"]; }),\n columnStyles:{0:{halign:\"center\",cellWidth:14},1:{halign:\"right\",cellWidth:20},4:{halign:\"right\",cellWidth:16},5:{halign:\"right\",cellWidth:26}},didParseCell:fixCell});\n y=doc.lastAutoTable.finalY+8;\n if(y\u003e250){ doc.addPage(); y=20; }\n doc.setFont(\"helvetica\",\"bold\"); doc.setFontSize(12); doc.setTextColor(...INK); doc.text(\"Bestellijst\",M,y); y+=3;\n doc.autoTable({startY:y,margin:{left:M,right:M,bottom:22},theme:\"grid\",styles:base,headStyles:head,\n head:[[\"Artikel\",\"Omschrijving\",\"Aantal\",\"Prijs\",\"Totaal\"]],\n body:pr.lines.map(l=\u003e[l.code||\"\",{content:`${l.naam}\\n${l.detail}`,styles:{}},`${l.qty} ${l.unit}`,l.unitPrice!=null?E(l.unitPrice):\"\",l.tot==null?\"op aanvraag\":E(l.tot)]),\n columnStyles:{0:{cellWidth:26,fontSize:8},2:{halign:\"right\",cellWidth:20},3:{halign:\"right\",cellWidth:24},4:{halign:\"right\",cellWidth:26}},\n didParseCell:d=\u003e{ fixCell(d); if(d.section===\"body\"\u0026\u0026d.column.index===1) d.cell.styles.fontSize=8.5; }});\n y=doc.lastAutoTable.finalY+2;\n const levTxt=S.delivery===\"afhalen\"?\"afhalen\":\"via de dealer\";\n doc.autoTable({startY:y,margin:{left:W-M-80,right:M},theme:\"plain\",styles:Object.assign({},base,{fontSize:10}),\n body:[[\"Subtotaal\",E(pr.sub)],[\"Levering\",levTxt],[{content:\"Totaal incl. btw\",styles:{fontStyle:\"bold\"}},{content:E(pr.total)+(pr.onRequest?\" *\":\"\"),styles:{fontStyle:\"bold\"}}]],\n columnStyles:{1:{halign:\"right\"}},didParseCell:d=\u003e{ if(d.row.index===2){ d.cell.styles.fillColor=[217,236,234]; } }});\n y=doc.lastAutoTable.finalY+6;\n const notes=[$(\"#priceNote\").textContent];\n if(pr.onRequest) notes.push(\"* Posten op aanvraag zijn niet in het totaal opgenomen; die prijzen staan in de offerte.\");\n if(S.items.length) notes.push(\"Inrichting (ter illustratie, niet in de prijs): \"+S.items.map(it=\u003e{const def=libDef(it.id);return `${def.name} ${Math.round(it.w)}×${Math.round(it.d)}${it.h!=null?`×${Math.round(it.h)}`:\"\"} cm`;}).join(\", \")+\".\");\n notes.push(klant?\"Deze aanvraag is verstuurd via de Ruimteplanner op \"+HB.web+\". Wij controleren de maten en hoeveelheden en sturen je een offerte.\":\"Offerte aanvragen of vragen? Gebruik de knop Offerte aanvragen in de Ruimteplanner of mail ons op \"+HB.mail+\".\");\n doc.setFont(\"helvetica\",\"normal\"); doc.setFontSize(8.5); doc.setTextColor(...MUT);\n notes.forEach(n=\u003e{ const ls=doc.splitTextToSize(fx(n),W-2*M); if(y+ls.length*4\u003e275){ doc.addPage(); y=20; } doc.text(ls,M,y); y+=ls.length*4+2; });\n // voettekst op elke pagina\n const pages=doc.getNumberOfPages();\n for(let i=1;i\u003c=pages;i++){ doc.setPage(i); doc.setDrawColor(...ACC); doc.setLineWidth(.4); doc.line(M,283,W-M,283);\n doc.setFontSize(7.5); doc.setTextColor(...MUT); doc.text(`${HB.naam} · ${HB.adres} · ${HB.plaats} · ${HB.btw} · ${HB.mail} · ${HB.web}`,M,288);\n doc.text(`Pagina ${i} van ${pages}`,W-M,288,{align:\"right\"}); }\n return {doc,ref};\n}\nfunction downloadPdf(doc,name){ const url=URL.createObjectURL(doc.output(\"blob\"));\n const a=document.createElement(\"a\"); a.href=url; a.download=name; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=\u003eURL.revokeObjectURL(url),8000); }\nasync function makePdf(){\n const btn=$(\"#pdfBtn\"); btn.disabled=true; const old=btn.textContent; btn.textContent=\"Pdf maken…\";\n try{\n const {doc,ref}=await buildPdf(null); downloadPdf(doc,`HB-Composite-besteloverzicht-${ref}.pdf`);\n toast(\"Pdf gedownload\");\n }catch(err){ console.error(err); toast(\"De pdf kon niet gemaakt worden. Controleer je internetverbinding en probeer opnieuw.\"); }\n finally{ btn.disabled=false; btn.textContent=old; }\n}\n$(\"#pdfBtn\").addEventListener(\"click\",makePdf);\n\n/* ---------- offerte aanvragen via het WPForms-formulier op de pagina ---------- */\nfunction hostDoc(){ try{ if(window.frameElement\u0026\u0026parent.document) return parent.document; }catch(_){} return document; }\nfunction wpfForm(){ const d=hostDoc(); return d.querySelector(\".rp-wpforms form.wpforms-form\")||d.querySelector(\"form.wpforms-form\"); }\nfunction wpfFields(f){ const map={}, keys=[[\"email\",/^e-?mail(adres)?$/i,\"email\"],[\"onderwerp\",/^(onderwerp|subject)$/i,null],[\"naam\",/^(naam|name|volledige naam|je naam)$/i,null],[\"bericht\",/^(bericht|message|opmerking|uw bericht|je bericht)$/i,null]];\n const W=f.ownerDocument.defaultView;\n f.querySelectorAll(\".wpforms-field\").forEach(c=\u003e{ const inp=c.querySelector(\"input:not([type=hidden]),textarea\"); if(!inp) return;\n const cs=W.getComputedStyle(c); if(cs.position===\"absolute\"||cs.position===\"fixed\"||c.style.display===\"none\"||/\\bwpforms-field-hp\\b/.test(c.className)) return; // spamval van WPForms overslaan\n const lab=(((c.querySelector(\".wpforms-field-label\")||{}).textContent||\"\").replace(/\\*/g,\"\").trim())||(inp.placeholder||\"\").trim();\n let hit=null; for(const [k,re,type] of keys){ if(re.test(lab)||(type\u0026\u0026inp.type===type)){ hit=k; break; } }\n if(!hit\u0026\u0026inp.tagName===\"TEXTAREA\") hit=\"bericht\";\n if(hit\u0026\u0026!map[hit]) map[hit]=inp; else inp.dataset.rpSkip=\"1\"; });\n return map; }\nfunction wpfSet(n,v){ if(!n) return; const W=n.ownerDocument.defaultView; n.value=v; n.dispatchEvent(new W.Event(\"input\",{bubbles:true})); n.dispatchEvent(new W.Event(\"change\",{bubbles:true})); }\nfunction wpfSubmit(f,vals){ return new Promise((ok,fail)=\u003e{\n const map=wpfFields(f); if(!map.bericht||!map.email) return fail(new Error(\"Het aanvraagformulier op deze pagina mist velden.\"));\n wpfSet(map.naam,vals.naam); wpfSet(map.email,vals.mail); wpfSet(map.onderwerp,vals.onderwerp); wpfSet(map.bericht,vals.bericht);\n // andere (spamval)velden leeg laten, anders weigert WPForms de inzending\n f.querySelectorAll(\"input[type=text],input[type=email],input[type=tel],input[type=url],textarea\").forEach(n=\u003e{ if(Object.values(map).includes(n)) return; n.value=\"\"; });\n const W=f.ownerDocument.defaultView, holder=f.closest(\".wpforms-container\")||f.parentNode, t0=Date.now(); let done=false;\n const fin=(good,msg)=\u003e{ if(done) return; done=true; clearInterval(poll); good?ok():fail(new Error(msg||\"Het versturen is niet gelukt.\")); };\n if(W.jQuery){ W.jQuery(f).one(\"wpformsAjaxSubmitSuccess\",()=\u003esetTimeout(()=\u003efin(true),50)); W.jQuery(f).one(\"wpformsAjaxSubmitFailed wpformsAjaxSubmitError\",()=\u003esetTimeout(()=\u003efin(false),50)); }\n const poll=setInterval(()=\u003e{ const conf=holder.querySelector(\".wpforms-confirmation-container, .wpforms-confirmation-container-full\")||!f.isConnected;\n const err=f.querySelector(\".wpforms-error:not(:empty), label.wpforms-error, .wpforms-has-error\");\n if(conf) fin(true); else if(err\u0026\u0026Date.now()-t0\u003e1500) fin(false,\"Het formulier is geweigerd: \"+(err.textContent||\"controleer je gegevens\").trim()); else if(Date.now()-t0\u003e45000) fin(false,\"Het versturen duurt te lang.\"); },400);\n const btn=f.querySelector(\"button[type=submit], input[type=submit]\"); if(!btn) return fin(false,\"Het aanvraagformulier heeft geen verzendknop.\");\n btn.click(); }); }\nfunction reqOpen(on){ const f=$(\"#reqForm\"); f.hidden=!on; $(\"#reqBtn\").setAttribute(\"aria-expanded\",String(on)); if(on){ $(\"#rqMsg\").textContent=\"\"; $(\"#rqMsg\").className=\"msg\"; f.scrollIntoView({behavior:\"smooth\",block:\"nearest\"}); setTimeout(()=\u003e$(\"#rqNaam\").focus(),250); } }\n$(\"#reqBtn\").addEventListener(\"click\",()=\u003ereqOpen($(\"#reqForm\").hidden));\n$(\"#rqCancel\").addEventListener(\"click\",()=\u003ereqOpen(false));\n$(\"#reqForm\").addEventListener(\"submit\",async e=\u003e{ e.preventDefault();\n const msg=$(\"#rqMsg\"), say=(t,c)=\u003e{ msg.textContent=t; msg.className=\"msg\"+(c?\" \"+c:\"\"); };\n const k={naam:$(\"#rqNaam\").value.trim(),mail:$(\"#rqMail\").value.trim(),tel:$(\"#rqTel\").value.trim(),plaats:$(\"#rqPlaats\").value.trim(),opm:$(\"#rqOpm\").value.trim()};\n if(!k.naam){ say(\"Vul je naam in.\",\"err\"); $(\"#rqNaam\").focus(); return; }\n if(!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/.test(k.mail)){ say(\"Vul een geldig e-mailadres in.\",\"err\"); $(\"#rqMail\").focus(); return; }\n const send=$(\"#rqSend\"); send.disabled=true; const old=send.textContent; send.textContent=\"Bezig…\";\n let pdf=null, id=null;\n try{\n say(\"Pdf van je ontwerp maken…\"); pdf=await buildPdf(k);\n const f=wpfForm();\n if(!f) throw Object.assign(new Error(\"geen formulier\"),{noForm:true});\n say(\"Pdf uploaden…\");\n try{ const r=await fetch(PDF_UPLOAD_URL,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({ref:pdf.ref,pdf:pdf.doc.output(\"datauristring\")})});\n const j=await r.json().catch(()=\u003e({})); if(r.ok\u0026\u0026j.id) id=j.id; else console.warn(\"pdf-upload\",r.status,j.error); }catch(err){ console.warn(\"pdf-upload\",err); }\n const onderwerp=`Offerte-aanvraag Ruimteplanner ${pdf.ref} – ${k.naam}`;\n const bericht=[`Offerte-aanvraag via de Ruimteplanner (${pdf.ref})`,\"\",`Naam: ${k.naam}`,`E-mail: ${k.mail}`,k.tel?`Telefoon: ${k.tel}`:null,k.plaats?`Postcode en plaats: ${k.plaats}`:null,k.opm?`Opmerking: ${k.opm}`:null,\"\",\n id?`[rp-pdf:${id}]`:\"Let op: de pdf kon niet automatisch worden meegestuurd; de klant heeft hem zelf gedownload.\",\"\",$(\"#copyBox\").value].filter(x=\u003ex!=null).filter((x,i,a)=\u003ex!==\"\"||a[i-1]!==\"\").join(\"\\n\");\n say(\"Aanvraag versturen…\");\n await wpfSubmit(f,{naam:k.naam,mail:k.mail,onderwerp,bericht});\n reqOpen(true); $(\"#reqForm\").querySelectorAll(\"input,textarea\").forEach(n=\u003en.disabled=true); send.hidden=true; $(\"#rqCancel\").textContent=\"Sluiten\";\n say(`Bedankt, je aanvraag ${pdf.ref} is verstuurd. We nemen zo snel mogelijk contact met je op.`,\"ok\");\n downloadPdf(pdf.doc,`HB-Composite-offerte-aanvraag-${pdf.ref}.pdf`);\n }catch(err){ console.error(err);\n if(pdf) downloadPdf(pdf.doc,`HB-Composite-offerte-aanvraag-${pdf.ref}.pdf`);\n const body=encodeURIComponent(`Offerte-aanvraag Ruimteplanner${pdf?\" \"+pdf.ref:\"\"}\\n\\nNaam: ${k.naam}\\nE-mail: ${k.mail}\\nTelefoon: ${k.tel}\\nPostcode en plaats: ${k.plaats}\\nOpmerking: ${k.opm}\\n\\n(pdf bijvoegen)\\n\\n`+$(\"#copyBox\").value.slice(0,1500));\n msg.className=\"msg err\"; msg.innerHTML=(err.noForm?\"Versturen kan hier niet rechtstreeks.\":esc(err.message||\"Het versturen is niet gelukt.\"))+` ${pdf?\"De pdf is gedownload; \":\"\"}stuur hem naar \u003ca href=\"mailto:${HB.mail}?subject=${encodeURIComponent(\"Offerte-aanvraag Ruimteplanner\")}\u0026body=${body}\"\u003e${HB.mail}\u003c/a\u003e.`;\n }finally{ send.disabled=false; send.textContent=old; }\n});\n$(\"#aiBtn\").addEventListener(\"click\",()=\u003e{ if(S.view===\"r360\"){ $(\"#aiAsk\").hidden=true; runRender(); return; } $(\"#aiAsk\").hidden=false; $(\"#aiAsk\").scrollIntoView({behavior:\"smooth\",block:\"nearest\"}); });\n$(\"#aiAskGo\").addEventListener(\"click\",()=\u003e{ $(\"#aiAsk\").hidden=true; runRender(); });\nfunction go360(){ $(\"#aiAsk\").hidden=true; setView(\"r360\"); const v=$(\"#viewer\"); if(v) v.scrollIntoView({behavior:\"smooth\",block:\"start\"}); toast(\"Draai en zoom naar het beeld dat je wilt en klik op ‘AI-render van dit standpunt’\"); }\n$$(\"[data-go360]\").forEach(b=\u003eb.addEventListener(\"click\",go360));\n$(\"#aiToggle\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-ai]\");if(b) showAi(b.dataset.ai);});\n\n/* events */\n$$(\".steps button\").forEach(b=\u003eb.addEventListener(\"click\",()=\u003egoStep(+b.dataset.step)));\n$$(\"[data-go]\").forEach(b=\u003eb.addEventListener(\"click\",()=\u003egoStep(+b.dataset.go)));\nfunction defaultOpenings(){ // lege ruimte: deuren en ramen voegt de gebruiker zelf toe per wand\n S.walls.forEach(w=\u003ew.openings=[]); }\n$(\"#shapes\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-shape]\");if(!b)return;S.shape=b.dataset.shape;SHAPES[S.shape].params.forEach(([k,_,v])=\u003e{ if(![\"A\",\"B\"].includes(k)) S.params[k]=v; }); if(S.shape===\"L\") S.params.E=Math.max(20,S.params.A-110);\n if(!syncWalls()){ SHAPES[S.shape].params.forEach(([k,_,v])=\u003eS.params[k]=v); syncWalls(); }\n defaultOpenings(); buildShapes(); renderAll(); setView(S.view);});\n$(\"#mirrorBtn\").addEventListener(\"click\",()=\u003e{ S.mirror=!S.mirror; if(!syncWalls()){ S.mirror=!S.mirror; syncWalls(); return; } defaultOpenings(); reattach(); buildShapes(); renderAll(); setView(S.view); });\n$(\"#cabOn\").addEventListener(\"click\",()=\u003e{ const C=S.cab; C.on=!C.on;\n if(C.on){ cabFit(); if(syncWalls()){ const cg=cabGeo(perimGeo()); if(cg\u0026\u0026!S.items.some(it=\u003eit.id===\"toilet\"||it.id===\"urinoir\")){ C.autoT=true; S.defaultItems=false; } } if(S.room===\"Badkamer\"){ S.room=\"Toilet\"; setPressed(\"#roomType [data-room]\",\"room\",S.room); } }\n else { C.autoT=false; S.sel=Math.min(S.sel,PTS.length-1); S.items=S.items.filter(it=\u003e!it.cabAuto\u0026\u0026(!it.att||it.att.i\u003cPTS.length)); S.selItem=-1; renderItemsUI(); }\n cabUI(); renderAll(); cabToilets(); if(S.view!==\"over\") setView(S.view); });\n$(\"#cabParams\").addEventListener(\"click\",e=\u003e{ const u=e.target.closest(\"[data-ur]\"); if(u){ const U=urS(); U[u.dataset.ur]=!U[u.dataset.ur]; cabUI(); cabToilets(); return; } const b=e.target.closest(\"[data-cabopen]\"); if(!b) return; const C=S.cab; C.open=C.open||[]; C.open[+b.dataset.cabopen]=b.dataset.v===\"1\"; cabUI(); if(S.sel\u003e=PTS.length) S.sel=Math.min(S.sel,PTS.length-1); renderAll(); cabToilets(); });\n$(\"#cabParams\").addEventListener(\"input\",e=\u003e{ const uk=e.target.dataset.urn; if(uk){ const v=parseFloat(e.target.value); if(isNaN(v)) return; const U=urS(); U[uk]=uk===\"n\"?clamp(Math.round(v),1,20):clamp(v,50,120); cabToilets(); return; } const k=e.target.dataset.cab; if(!k) return; const v=parseFloat(e.target.value); if(isNaN(v)) return; const C=S.cab;\n const lim={wall:[0,3],n:[1,12],x:[0,1200],w:[1,400],d:[1,400],t:[4,30],door:[40,150],dh:[150,300]}[k]; C[k]=clamp(k===\"wall\"||k===\"n\"?Math.round(v):v,lim[0],lim[1]);\n if(k===\"n\"){ const box=$(\"#cabParams\"), foc=document.activeElement===e.target; cabUI(); if(foc){ const i=box.querySelector('[data-cab=\"n\"]'); if(i){ i.focus(); } } }\n if(S.sel\u003e=PTS.length) S.sel=Math.min(S.sel,PTS.length-1); renderAll(); cabToilets(); });\n$(\"#params\").addEventListener(\"input\",e=\u003e{const k=e.target.dataset.param,v=parseFloat(e.target.value);if(!k||isNaN(v))return;S.params[k]=clamp(v,0,1200);if(syncWalls()){renderAll(); if(S.cab.on) cabToilets();}});\n$(\"#roomType\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-room]\");if(!b)return;S.room=b.dataset.room;setPressed(\"#roomType [data-room]\",\"room\",S.room);if(S.defaultItems){ if(S.room===\"Badkamer\") placeDefaults(); else { S.items=[]; S.selItem=-1; } rebuildItems(); renderItemsUI(); renderPlan(); }renderSummary();});\n$(\"#ceilH\").addEventListener(\"input\",e=\u003e{const v=parseFloat(e.target.value);if(isNaN(v)||v\u003c150)return;S.H=clamp(v,150,400);renderAll();});\n$(\"#wallTabs\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-w]\");if(!b)return;S.sel=+b.dataset.w;changed();if(S.view!==\"over\")setView(S.view);});\n$(\"#minimap\").addEventListener(\"click\",e=\u003e{if(S.view===\"r360\"\u0026\u0026mini360(e))return;const g=e.target.closest(\"[data-wall]\");if(!g)return;S.sel=+g.dataset.wall;goStep(2);changed();if(S.view!==\"over\")setView(S.view);});\n$(\"#wOn\").addEventListener(\"change\",e=\u003e{S.walls[S.sel].on=e.target.checked;changed();});\n$(\"#wCover\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-cover]\");if(!b)return;S.walls[S.sel].cover=b.dataset.cover;changed();});\n$(\"#wCoverNum\").addEventListener(\"input\",e=\u003e{const v=parseFloat(e.target.value);if(isNaN(v)||v\u003c20)return;S.walls[S.sel].coverCm=v;changed();});\n$(\"#copyAll\").addEventListener(\"click\",()=\u003e{const w=S.walls[S.sel];S.walls.forEach(x=\u003e{x.on=w.on;x.pick=w.pick;x.prod=w.prod;x.size=w.size;x.decor=w.decor;});changed();toast(\"Toegepast op alle wanden\");});\n$$(\"[data-add]\").forEach(b=\u003eb.addEventListener(\"click\",()=\u003e{const w=S.walls[S.sel],gw=wallsGeo()[S.sel],t=b.dataset.add;\n const o=t===\"deur\"?{type:\"deur\",w:85,h:211,x:Math.max(0,Math.round(gw.L/2-42)),y:0}:t===\"raam\"?{type:\"raam\",w:60,h:60,x:Math.max(0,Math.round(gw.L/2-30)),y:150}:{type:\"nis\",w:60,h:40,x:Math.max(0,Math.round(gw.L/2-30)),y:110,d:10};\n o.w=Math.min(o.w,gw.L);w.openings.push(o);changed();}));\n$(\"#openings\").addEventListener(\"input\",e=\u003e{const row=e.target.closest(\"[data-o]\");if(!row)return;const o=S.walls[S.sel].openings[+row.dataset.o],f=e.target.dataset.f;\n if(f===\"type\"){o.type=e.target.value;if(o.type===\"deur\")o.y=0;changed();return;} const v=parseFloat(e.target.value);if(isNaN(v))return;o[f]=Math.max(0,v);\n clearTimeout(changeT);renderPlan();renderElev();renderSummary();changeT=setTimeout(rebuild3D,250);});\n$(\"#openings\").addEventListener(\"change\",e=\u003e{ if(e.target.dataset.f\u0026\u0026e.target.dataset.f!==\"type\") changed(); });\n$(\"#openings\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-del]\");if(!b)return;S.walls[S.sel].openings.splice(+b.dataset.del,1);changed();});\n$(\"#prevWall\").addEventListener(\"click\",()=\u003e{S.sel=(S.sel-1+S.walls.length)%S.walls.length;changed();if(S.view!==\"over\")setView(S.view);});\n$(\"#nextWall\").addEventListener(\"click\",()=\u003e{S.sel=(S.sel+1)%S.walls.length;changed();if(S.view!==\"over\")setView(S.view);});\n$(\"#fOn\").addEventListener(\"change\",e=\u003e{S.floor.on=e.target.checked;changed();});\n$(\"#fMount\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-fm]\");if(!b)return;S.floor.mount=b.dataset.fm;changed();});\n$(\"#mount\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-mount]\");if(!b)return;S.mount=b.dataset.mount;renderSummary();});\n$(\"#prof\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-prof]\");if(!b)return;S.prof=b.dataset.prof;S.profColor=0;renderSummary();});\n$(\"#profColor\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-pc]\");if(!b)return;S.profColor=+b.dataset.pc;renderSummary();});\n$(\"#lev\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-lev]\");if(!b)return;S.delivery=b.dataset.lev;renderSummary();});\n$(\"#fUnder\").addEventListener(\"click\",e=\u003e{const b=e.target.closest(\"[data-under]\");if(!b)return;S.floor.under=b.dataset.under;changed();});\n$(\"#vOver\").addEventListener(\"click\",()=\u003esetView(\"over\"));$(\"#vIn\").addEventListener(\"click\",()=\u003esetView(\"in\"));$(\"#v360\").addEventListener(\"click\",()=\u003esetView(\"r360\"));\nfunction move360(dz,dh){ if(!R||S.view!==\"r360\"||!L360.pos) return; // dz cm vooruit (+) of achteruit (−), dh cm omhoog of omlaag\n const p=L360.pos.clone(); p.x+=-Math.sin(L360.yaw)*dz/100; p.z+=-Math.cos(L360.yaw)*dz/100;\n if(dz\u0026\u0026!okPos([p.x*100,-p.z*100])) return; p.y=clamp(p.y+dh/100,.6,(S.H-30)/100); L360.pos=p; apply360(); }\nfunction outDist(px,py){ if(inPoly([px,py])) return 0; let m=1e9; for(let k=0;k\u003cPTS.length;k++){ const a=PTS[k],b=PTS[(k+1)%PTS.length],dx=b[0]-a[0],dy=b[1]-a[1],t=clamp(((px-a[0])*dx+(py-a[1])*dy)/(dx*dx+dy*dy||1),0,1); m=Math.min(m,Math.hypot(a[0]+dx*t-px,a[1]+dy*t-py)); } return m; }\nfunction zoom360(d){ if(!R||S.view!==\"r360\"||!L360.pos) return; // d\u003e0 uit, d\u003c0 in; 12° ≈ 7× vergroot\n const cam=R.camera, f=new THREE.Vector3(-Math.sin(L360.yaw),0,-Math.cos(L360.yaw)), out=outDist(L360.pos.x*100,-L360.pos.z*100), step=0.025*Math.abs(d), MAXOUT=150;\n if(d\u003e0){ // uitzoomen: eerst breder tot 90°, dan achteruit tot 1,5 m achter de wand (die wordt weggelaten)\n if(cam.fov\u003c90){ cam.fov=Math.min(90,cam.fov*Math.pow(1.012,d)); cam.updateProjectionMatrix(); draw(); return; }\n if(out\u003cMAXOUT){ const np=L360.pos.clone().addScaledVector(f,-step); if(outDist(np.x*100,-np.z*100)\u003c=MAXOUT){ L360.pos.copy(np); apply360(); return; } }\n if(out\u003c1){ cam.fov=Math.min(110,cam.fov*Math.pow(1.012,d)); cam.updateProjectionMatrix(); draw(); } return; } // achter de wand niet breder: dan blijft de ruimte groot in beeld\n // inzoomen: eerst smaller tot 90°, dan terug de ruimte in, dan verder inzoomen tot 12°\n if(cam.fov\u003e90){ cam.fov=Math.max(90,cam.fov*Math.pow(1.012,d)); cam.updateProjectionMatrix(); draw(); return; }\n if(out\u003e0){ L360.pos.addScaledVector(f,step); apply360(); return; }\n cam.fov=clamp(cam.fov*Math.pow(1.012,d),12,110); cam.updateProjectionMatrix(); draw(); }\nfunction walkTo(e){ // dubbelklik/-tik op de vloer: daarheen lopen\n const r=R.renderer.domElement.getBoundingClientRect(), m=new THREE.Vector2(((e.clientX-r.left)/r.width)*2-1,-((e.clientY-r.top)/r.height)*2+1);\n const ray=new THREE.Raycaster(); ray.setFromCamera(m,R.camera); const hit=new THREE.Vector3();\n if(!ray.ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0,1,0),0),hit)) return;\n let pt=[hit.x*100,-hit.z*100]; if(!okPos(pt)){ // net naast een wand of meubel: tot vlak ervoor lopen\n const c=[L360.pos.x*100,-L360.pos.z*100]; let ok=null; for(let t=.95;t\u003e0;t-=.05){ const q=[c[0]+(pt[0]-c[0])*t,c[1]+(pt[1]-c[1])*t]; if(okPos(q)){ ok=q; break; } } if(!ok) return; pt=ok; }\n const p=L360.pos.clone(); p.x=pt[0]/100; p.z=-pt[1]/100; L360.pos=p; apply360(); }\n$(\"#viewer\").addEventListener(\"wheel\",e=\u003e{ if(S.view!==\"r360\"||!R) return; e.preventDefault(); zoom360(Math.sign(e.deltaY)*9); },{passive:false});\n(()=\u003e{ let pd=0; const vw=$(\"#viewer\"), dist=t=\u003eMath.hypot(t[0].clientX-t[1].clientX,t[0].clientY-t[1].clientY);\n vw.addEventListener(\"touchstart\",e=\u003e{ if(S.view===\"r360\"\u0026\u0026e.touches.length===2) pd=dist(e.touches); },{passive:true});\n vw.addEventListener(\"touchmove\",e=\u003e{ if(S.view!==\"r360\"||e.touches.length!==2||!pd) return; const d=dist(e.touches); if(Math.abs(d-pd)\u003e6){ zoom360(-(d-pd)*.35); pd=d; } },{passive:true});\n vw.addEventListener(\"touchend\",()=\u003e{ pd=0; },{passive:true}); })();\n$(\"#r360ctl\").addEventListener(\"click\",e=\u003e{ const b=e.target.closest(\"button\"); if(!b) return; e.stopPropagation(); if(b.dataset.z) zoom360(-(+b.dataset.z)*18); else move360(0,+b.dataset.h*15); });\n$(\"#r360ctl\").addEventListener(\"pointerdown\",e=\u003ee.stopPropagation());\n(()=\u003e{ const vw=$(\"#viewer\");\n vw.addEventListener(\"pointerdown\",e=\u003e{ if(S.view!==\"r360\"||!R||e.target!==R.renderer.domElement) return; L360.pts++; if(L360.pts\u003e1){ L360.drag=null; return; }\n L360.drag={x:e.clientX,y:e.clientY,yaw:L360.yaw,pitch:L360.pitch,moved:false}; try{ vw.setPointerCapture(e.pointerId); }catch(_){} });\n vw.addEventListener(\"pointermove\",e=\u003e{ const d=L360.drag; if(!d||S.view!==\"r360\") return; \n if(Math.hypot(e.clientX-d.x,e.clientY-d.y)\u003e4) d.moved=true; const f=.8*(R.camera.fov/90)*Math.PI/Math.max(300,vw.clientWidth);\n L360.yaw=d.yaw-(e.clientX-d.x)*f; L360.pitch=clamp(d.pitch-(e.clientY-d.y)*f,-1.1,1.1); apply360(); });\n let lastTap=null;\n const end=e=\u003e{ const d=L360.drag; if(d\u0026\u0026!d.moved\u0026\u0026e\u0026\u0026e.type===\"pointerup\"\u0026\u0026S.view===\"r360\"){ const now=Date.now();\n if(lastTap\u0026\u0026now-lastTap.t\u003c350\u0026\u0026Math.hypot(e.clientX-lastTap.x,e.clientY-lastTap.y)\u003c14){ walkTo(e); lastTap=null; } else lastTap={t:now,x:e.clientX,y:e.clientY}; }\n L360.pts=Math.max(0,L360.pts-1); if(!L360.pts) L360.drag=null; };\n vw.addEventListener(\"pointerup\",end); vw.addEventListener(\"pointercancel\",end);\n document.addEventListener(\"keydown\",e=\u003e{ if(S.view!==\"r360\"||/INPUT|SELECT|TEXTAREA/.test((e.target.tagName||\"\"))) return;\n const k=e.key; if(k===\"ArrowLeft\"){ L360.yaw+=.08; apply360(); } else if(k===\"ArrowRight\"){ L360.yaw-=.08; apply360(); } else if(k===\"ArrowUp\") move360(20,0); else if(k===\"ArrowDown\") move360(-20,0); else return; e.preventDefault(); }); })();\n$(\"#v360Render\").addEventListener(\"click\",()=\u003e{ goStep(5); const a=$(\"#aiBtn\"); if(a){ a.scrollIntoView({behavior:\"smooth\",block:\"center\"}); if(!a.disabled) runRender(); } });$(\"#vWall\").addEventListener(\"click\",()=\u003esetView(\"wall\"));\n$(\"#customIn\").addEventListener(\"change\",e=\u003e{const f=e.target.files\u0026\u0026e.target.files[0];if(!f)return;const done=img=\u003e{const c=document.createElement(\"canvas\"),sc=Math.min(1,2000/Math.max(img.width,img.height));c.width=Math.round(img.width*sc);c.height=Math.round(img.height*sc);c.getContext(\"2d\").drawImage(img,0,0,c.width,c.height);S.customImg=c;S.customThumb=c.toDataURL(\"image/jpeg\",.7);S.customId++;S.walls[S.sel].decor=\"Eigen ontwerp\";S.walls[S.sel].on=true;texCache.clear();changed();};\n const fail=()=\u003etoast(\"Deze afbeelding kan niet worden geopend. Kies een JPG of PNG.\");\n try{const url=URL.createObjectURL(f),im=new Image();im.onload=()=\u003edone(im);im.onerror=()=\u003e{const r=new FileReader();r.onload=()=\u003e{const i2=new Image();i2.onload=()=\u003edone(i2);i2.onerror=fail;i2.src=r.result;};r.onerror=fail;r.readAsDataURL(f);};im.src=url;}catch(_){fail();}});\n$(\"#copyBtn\").addEventListener(\"click\",()=\u003e{const t=$(\"#copyBox\").value,fb=()=\u003e{const ta=$(\"#copyBox\");ta.focus();ta.select();toast(\"Tekst geselecteerd, kopieer met Ctrl/Cmd+C\");};try{navigator.clipboard.writeText(t).then(()=\u003etoast(\"Overzicht gekopieerd\"),fb);}catch(_){fb();}});\n\n/* start: lege ruimte; de klant kiest zelf per wand en voor de vloer */\nsyncWalls();\ndefaultOpenings(); S.items=[]; S.selItem=-1; S.defaultItems=false; renderLib();\nbuildShapes(); init3D(); renderAll(); renderItemsUI(); setView(\"over\");\n})();\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n"; (function(){var f=document.getElementById("ruimteplanner");if(f&&!f.getAttribute("srcdoc")){f.srcdoc=window.HB_RP_HTML;}})();