/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ // Labels: zelfstandig naamwoord met hoofdletter, geen dubbele punt, uitleg in een hint, eenheid rechts. window.UI = (() => { const esc = (s) => String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); const icon = (name, cls) => ``; const hint = (text) => text ? `${icon('info-circle')}` : ''; function field(id, label, control, o) { return `
${control}
`; } function num(id, label, value, o) { o = o || {}; const unit = o.unit === undefined ? 'mm' : o.unit; return field(id, label, `` + `${unit}`, o); } // Het getal mag buiten het schuifbereik; het model klemt zelf. function range(id, label, value, o) { o = o || {}; const unit = o.unit === undefined ? 'mm' : o.unit; const lim = `min="${o.min}" max="${o.max}" step="${o.step || 10}"`; return `
` + `
` + `${unit}
`; } function onRange(id, fn, after) { const n = document.getElementById(id), r = document.getElementById(id + '-r'); if (!n || !r) return; r.addEventListener('input', () => { n.value = r.value; fn(parseFloat(r.value)); after && after(); }); n.addEventListener('input', () => { const v = parseFloat(n.value); if (!isFinite(v)) return; r.value = v; fn(v); after && after(); }); } function setRange(id, v) { const n = document.getElementById(id), r = document.getElementById(id + '-r'); if (n && document.activeElement !== n) n.value = v; if (r) r.value = v; } function sel(id, label, options, value, o) { const opts = Object.entries(options).map(([k, v]) => ``).join(''); return field(id, label, ``, o); } function color(id, label, value, o) { return field(id, label, ``, o); } function onColor(id, fn, after) { const el = document.getElementById(id); if (el) el.addEventListener('change', (e) => { fn(e.target.value); after && after(); }); } function text(id, label, value, o) { return field(id, label, ``, 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, ``, o); } const section = (title, iconName, body) => `

${iconName ? icon(iconName) : ''}${title}

${body}
`; const info = (text) => `
${icon('info-circle')}${text}
`; const warn = (text) => `
${icon('exclamation-triangle')}${text}
`; const grid = (...cols) => `
${cols.map(c => `
${c}
`).join('')}
`; const button = (label, iconName, attrs, cls) => ``; const actions = (...btns) => `
${btns.join('')}
`; const activeTabs = {}; function tabs(group, list) { const active = list.some(t => t.key === activeTabs[group]) ? activeTabs[group] : list[0].key; return `
${list.map(t => ``).join('')}
${list.map(t => `
${t.html}
`).join('')}
`; } function wireTabs(container) { if (container._uiTabsWired) return; container._uiTabsWired = true; container.addEventListener('click', (e) => { const btn = e.target.closest('.ui-tab'); if (!btn) return; const wrap = btn.closest('.ui-tabs'); activeTabs[wrap.dataset.tabGroup] = btn.dataset.tab; wrap.querySelectorAll('.ui-tab').forEach(b => b.classList.toggle('active', b === btn)); wrap.querySelectorAll('.ui-tab-page').forEach(p => p.classList.toggle('active', p.dataset.tabPage === btn.dataset.tab)); }); } function specCard(title, iconName, rows, total) { const row = ([label, value, unit]) => `
${label}${value}${unit ? `${unit}` : ''}
`; return `

${icon(iconName)}${title}

${rows.map(row).join('')}${total ? `
${row(total)}
` : ''}
`; } function onNum(id, fn, after, o) { const el = document.getElementById(id); if (!el) return; el.addEventListener('input', (e) => { const v = parseFloat(e.target.value); if (isFinite(v) && (!(o && o.positive) || v > 0)) { fn(v); after && after(); } }); } function onSel(id, fn, after) { const el = document.getElementById(id); if (el) el.addEventListener('input', (e) => { fn(e.target.value); after && after(); }); } function onToggle(id, fn, after) { const el = document.getElementById(id); if (el) el.addEventListener('change', (e) => { fn(e.target.checked); after && after(); }); } function cycle3D(el) { el.classList.remove('fullscreen-2d'); if (!el.classList.contains('fullscreen-3d')) el.classList.add('fullscreen-3d'); else if (!el.classList.contains('render-only')) el.classList.add('render-only'); else el.classList.remove('fullscreen-3d', 'render-only'); } function toggle2D(el) { el.classList.remove('fullscreen-3d', 'render-only'); el.classList.toggle('fullscreen-2d'); } 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 }; })();