v0.6.0: ruimtes (verfemmer: functie, netto m², vloerafwerking, FIBO-wandpanelen, eigen verfkleur, vals plafond, ruimtestaat op de PDF), inrichting (sanitair, keuken, kookeiland, kasten, bed, zithoek, boiler; slepen met snappen op het wandvlak, draaien, kosten), kozijnen met deurrichting, glasdeur en schuifdeuren (inbouw/opbouw, deuren open in de loopmodus) plus dagkanten, gevel stucwerk en wandstukken splitsen met eigen gevel, dak over de hele woning met overkapping, geschilderd plafond, model Amstel als nieuw ontwerp, kosten en prijzen uit kosten/intern.csv en kosten/leveranciers.csv (CostCsv); easter egg verwijderd; overbodige comments opgeschoond (alleen copyright, uitzonderingen en valkuilen blijven)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-11 00:34:27 +02:00
co-authored by Claude Opus 5.5
parent b5510d56c7
commit ceda0b554d
45 changed files with 4117 additions and 3700 deletions
+10 -27
View File
@@ -4,18 +4,7 @@
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
* Zie LICENTIE.md.
*/
// ============================================================
// core/ui-kit.js — gedeelde UI-bouwstenen (window.UI): iconen (Bootstrap
// Icons, css/vendor), invoervelden, schakelaars, secties, tabbladen,
// inklapbare secties, schuiven, specificatie-kaarten en de 3-staps
// weergave-cyclus (sneltoets T).
// Eén plek voor opmaak en woordgebruik, zodat alle panelen gelijk ogen.
//
// Woordgebruik labels: zelfstandig naamwoord met hoofdletter, geen dubbele
// punt, geen uitleg tussen haakjes (die gaat in een hint), eenheid rechts
// van het veld. Bijvoorbeeld "Afstand min." i.p.v. "Lampen min. h.o.h.".
// Plain script (geen ES-module).
// ============================================================
// Labels: zelfstandig naamwoord met hoofdletter, geen dubbele punt, uitleg in een hint, eenheid rechts.
window.UI = (() => {
const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
@@ -26,7 +15,6 @@ window.UI = (() => {
return `<div class="ui-field${o && o.disabled ? ' is-disabled' : ''}"><label for="${id}">${label}${hint(o && o.hint)}</label><div class="ui-control">${control}</div></div>`;
}
// Getal. o: { step, unit ('mm' standaard, '' = geen), hint, min, disabled }
function num(id, label, value, o) {
o = o || {};
const unit = o.unit === undefined ? 'mm' : o.unit;
@@ -35,8 +23,6 @@ window.UI = (() => {
`<span class="ui-unit">${unit}</span>`, o);
}
// Schuif met gekoppeld getal (voor maten met een logisch bereik, zoals
// hoogtes en hellingen). o: { min, max, step, unit ('mm' standaard), hint }.
// Het getal mag buiten het schuifbereik; het model klemt zelf.
function range(id, label, value, o) {
o = o || {};
@@ -46,7 +32,6 @@ window.UI = (() => {
`<div class="ui-control ui-range"><input type="range" id="${id}-r" ${lim} value="${value}"${o.disabled ? ' disabled' : ''}>` +
`<input type="number" id="${id}" step="${o.step || 10}" value="${value}"${o.disabled ? ' disabled' : ''}><span class="ui-unit">${unit}</span></div></div>`;
}
// fn(v) bij schuiven en typen; set(id, v) zet beide zonder event.
function onRange(id, fn, after) {
const n = document.getElementById(id), r = document.getElementById(id + '-r');
if (!n || !r) return;
@@ -64,13 +49,11 @@ window.UI = (() => {
if (r) r.value = v;
}
// Keuzelijst. options: { waarde: 'Label' }.
function sel(id, label, options, value, o) {
const opts = Object.entries(options).map(([k, v]) => `<option value="${k}"${k === value ? ' selected' : ''}>${v}</option>`).join('');
return field(id, label, `<select id="${id}">${opts}</select><span class="ui-unit"></span>`, o);
}
// Kleurkiezer (waarde '#rrggbb'); onColor reageert pas bij loslaten.
function color(id, label, value, o) {
return field(id, label, `<input type="color" id="${id}" value="${value}"><span class="ui-unit"></span>`, o);
}
@@ -79,7 +62,14 @@ window.UI = (() => {
if (el) el.addEventListener('change', (e) => { fn(e.target.value); after && after(); });
}
// Aan/uit-schakelaar.
function text(id, label, value, o) {
return field(id, label, `<input type="text" id="${id}" value="${esc(value || '')}"${o && o.placeholder ? ` placeholder="${esc(o.placeholder)}"` : ''}><span class="ui-unit"></span>`, o);
}
function onText(id, fn, after) {
const el = document.getElementById(id);
if (el) el.addEventListener('input', (e) => { fn(e.target.value); after && after(); });
}
function toggle(id, label, checked, o) {
return field(id, label,
`<label class="ui-switch"><input type="checkbox" id="${id}"${checked ? ' checked' : ''}><span></span></label><span class="ui-unit"></span>`, o);
@@ -94,8 +84,6 @@ window.UI = (() => {
`<button type="button" class="ui-btn${cls ? ' ' + cls : ''}" ${attrs || ''}>${iconName ? icon(iconName) : ''}<span>${label}</span></button>`;
const actions = (...btns) => `<div class="ui-actions">${btns.join('')}</div>`;
// Tabbladen. group = sleutel waaronder het actieve tabblad onthouden wordt
// (bijv. 'woning', 'unit'); tabs = [{ key, label, icon, html }].
const activeTabs = {};
function tabs(group, list) {
const active = list.some(t => t.key === activeTabs[group]) ? activeTabs[group] : list[0].key;
@@ -104,7 +92,6 @@ window.UI = (() => {
${list.map(t => `<div class="ui-tab-page${t.key === active ? ' active' : ''}" data-tab-page="${t.key}">${t.html}</div>`).join('')}
</div>`;
}
// Eenmalig per container: klik op een tab wisselt de pagina.
function wireTabs(container) {
if (container._uiTabsWired) return;
container._uiTabsWired = true;
@@ -118,14 +105,12 @@ window.UI = (() => {
});
}
// Specificaties: kaart met regels [label, waarde, eenheid].
function specCard(title, iconName, rows, total) {
const row = ([label, value, unit]) =>
`<div class="ui-spec-row"><span>${label}</span><span class="ui-spec-val">${value}${unit ? `<small>${unit}</small>` : ''}</span></div>`;
return `<div class="ui-spec-card"><h2>${icon(iconName)}${title}</h2>${rows.map(row).join('')}${total ? `<div class="ui-spec-total">${row(total)}</div>` : ''}</div>`;
}
// Invoer koppelen: onNum/onSel/onToggle(root, id, fn, after).
function onNum(id, fn, after, o) {
const el = document.getElementById(id);
if (!el) return;
@@ -143,8 +128,6 @@ window.UI = (() => {
if (el) el.addEventListener('change', (e) => { fn(e.target.checked); after && after(); });
}
// Weergave-cyclus 3D (sneltoets T): standaard -> 3D groot -> alleen de
// render (ook het info-/specificatiedeel eronder weg) -> standaard.
function cycle3D(el) {
el.classList.remove('fullscreen-2d');
if (!el.classList.contains('fullscreen-3d')) el.classList.add('fullscreen-3d');
@@ -156,5 +139,5 @@ window.UI = (() => {
el.classList.toggle('fullscreen-2d');
}
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle, cycle3D, toggle2D };
return { esc, icon, hint, num, range, onRange, setRange, sel, toggle, color, onColor, text, onText, section, info, warn, grid, button, actions, tabs, wireTabs, specCard, onNum, onSel, onToggle, cycle3D, toggle2D };
})();