233 lines
10 KiB
JavaScript
233 lines
10 KiB
JavaScript
/*!
|
||
* Tectum
|
||
* Copyright © 2026 Machiel Heinen.
|
||
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||
* Zie LICENTIE.md.
|
||
*/
|
||
|
||
// PDF van de light-versie: voorblad "Uw woningontwerp" (beeld, kerncijfers, keuzes, indicatieve prijs tenzij
|
||
// d.price === false, contact en QR-code naar de modelpagina), de aanzichten en plattegrond (HousePdf, klantvariant) en een pagina over de werkwijze.
|
||
window.LightPdf = (() => {
|
||
const COMPANY = {
|
||
naam: 'Van Emous Mantelzorgwoningen',
|
||
adres: ['Keizerswoert 19', '3881 LE Putten'],
|
||
telefoon: '0341 - 275 147',
|
||
email: 'info@vanemousmantelzorgwoningen.nl',
|
||
web: 'https://www.vanemousmantelzorgwoningen.nl',
|
||
open: 'Ma t/m vr 08:00 - 17:00 uur, za en zo op afspraak'
|
||
};
|
||
// Modellen met een eigen pagina op de site; andere verwijzen naar het overzicht.
|
||
const SITE_MODELS = ['amstel', 'ijssel', 'maas', 'reest', 'rijn', 'waal', 'wetering', 'eem'];
|
||
const modelUrl = (key) => SITE_MODELS.includes(key) ? `${COMPANY.web}/modellen/model-${key}/` : `${COMPANY.web}/modellen/`;
|
||
|
||
// Werkwijze, naar de site (werkwijze/stappenplan).
|
||
const STEPS = [
|
||
['Mag u plaatsen?', 'Wij toetsen vrijblijvend uw perceel aan de regels van uw gemeente en geven een eerste indicatie van de mogelijkheden. Voor zekerheid helpen wij u graag bij het overleg met de gemeente.'],
|
||
['Uw ontwerp uitwerken', 'U start vanuit een van onze standaardmodellen of vanuit uw eigen ontwerp. Onze specialisten werken uw wensen uit tot een productietekening. Omdat wij in onze eigen fabriek bouwen, is vrijwel elk ontwerp technisch haalbaar.'],
|
||
['Wij gaan bouwen', 'Zodra het ontwerp definitief is en de omgevingsvergunning er is, start de productie in onze fabriek. Tijdens openingstijden bent u welkom om de voortgang te komen bekijken.'],
|
||
['Transport en plaatsing', 'De afgebouwde modules worden naar uw perceel gebracht, op de fundering geplaatst en aangesloten op de nutsvoorzieningen. Na de laatste afwerking ontvangt u de sleutel.'],
|
||
['Oplevering en service', 'Samen lopen we de woning na, zodat alles klopt met het ontwerp en uw wensen. Ook na de oplevering blijven wij beschikbaar voor service en onderhoud.']
|
||
];
|
||
|
||
const MAATWERK = ['Dit is pas het begin: alles kan op maat',
|
||
'Dit ontwerp is gemaakt met een vereenvoudigde versie van onze configurator. Met onze eigen ontwerpsoftware werken wij uw woning ' +
|
||
'samen met u tot in detail uit: een keuken op maat, een andere indeling met extra binnenwanden, of ramen en deuren op een andere ' +
|
||
'plek en in een ander formaat. U bent van harte welkom voor een vrijblijvend adviesgesprek, waarin we samen uw wensen en ' +
|
||
'mogelijkheden doornemen. Ook staat onze fabriek in Putten open voor bezoek: kom kijken hoe onze woningen gebouwd worden en ervaar ' +
|
||
'de kwaliteit met eigen ogen. Maak gerust een afspraak; wij denken graag met u mee.'];
|
||
|
||
const ORANGE = [185, 89, 19], INK = [33, 33, 33], MUTED = [91, 102, 115], SOFT = [249, 246, 240], LINE = [226, 231, 238];
|
||
const PW = 420, PH = 297, M = 20;
|
||
const nl = (n, d = 0) => n.toLocaleString('nl-NL', { minimumFractionDigits: d, maximumFractionDigits: d });
|
||
const meters = (mm) => `${nl(mm / 1000, 2)} m`;
|
||
|
||
// QR-code als vlakken (scherp bij elke vergroting), met witte rand.
|
||
function drawQr(doc, text, x, y, size) {
|
||
const qr = qrcode(0, 'M');
|
||
qr.addData(text);
|
||
qr.make();
|
||
const n = qr.getModuleCount(), quiet = 2, s = size / (n + 2 * quiet);
|
||
doc.setFillColor(255, 255, 255);
|
||
doc.rect(x, y, size, size, 'F');
|
||
doc.setFillColor(0, 0, 0);
|
||
for (let r = 0; r < n; r++) {
|
||
for (let c = 0; c < n; c++) {
|
||
if (!qr.isDark(r, c)) continue;
|
||
let e = c;
|
||
while (e + 1 < n && qr.isDark(r, e + 1)) e++;
|
||
doc.rect(x + (quiet + c) * s, y + (quiet + r) * s, (e - c + 1) * s + 0.02, s + 0.02, 'F');
|
||
c = e;
|
||
}
|
||
}
|
||
}
|
||
|
||
function logo(doc, x, y, h) {
|
||
const L = window.TECTUM_LOGO;
|
||
if (L) doc.addImage(L.png, 'PNG', x, y, h * L.aspect, h, 'tectum-logo', 'FAST');
|
||
else { doc.setFont('helvetica', 'bold'); doc.setFontSize(14); doc.setTextColor(...INK); doc.text(COMPANY.naam, x, y + h * 0.7); }
|
||
}
|
||
|
||
// Contactblok met QR-code; y = bovenkant.
|
||
function contactBand(doc, y, url, label) {
|
||
doc.setDrawColor(...LINE);
|
||
doc.setLineWidth(0.3);
|
||
doc.line(M, y, PW - M, y);
|
||
doc.setTextColor(...INK);
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(12);
|
||
doc.text(COMPANY.naam, M, y + 9);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10);
|
||
doc.setTextColor(...MUTED);
|
||
const col = (x, lines) => lines.forEach((t, i) => doc.text(t, x, y + 16 + i * 5.2));
|
||
col(M, COMPANY.adres.concat([COMPANY.open]));
|
||
col(M + 95, [`Telefoon ${COMPANY.telefoon}`, COMPANY.email, COMPANY.web.replace(/^https:\/\//, '')]);
|
||
const q = 34;
|
||
drawQr(doc, url, PW - M - q, y + 4, q);
|
||
doc.setFontSize(9);
|
||
doc.setTextColor(...INK);
|
||
doc.text(doc.splitTextToSize(label, 70), PW - M - q - 4, y + 12, { align: 'right' });
|
||
doc.setTextColor(...MUTED);
|
||
doc.setFontSize(8);
|
||
doc.text(url.replace(/^https:\/\//, ''), PW - M - q - 4, y + 30, { align: 'right' });
|
||
}
|
||
|
||
function keyValues(doc, x, y, w, rows, title) {
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(12);
|
||
doc.setTextColor(...INK);
|
||
doc.text(title, x, y);
|
||
let yy = y + 8;
|
||
rows.forEach(([k, v]) => {
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(k, x, yy);
|
||
doc.setTextColor(...INK);
|
||
const lines = doc.splitTextToSize(String(v), w - 42);
|
||
doc.text(lines, x + 42, yy);
|
||
yy += 5.4 * lines.length + 0.8;
|
||
});
|
||
return yy;
|
||
}
|
||
|
||
function cover(doc, d) {
|
||
doc.setFillColor(...SOFT);
|
||
doc.rect(0, 0, PW, PH, 'F');
|
||
logo(doc, M, 18, 14);
|
||
doc.setTextColor(...INK);
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(34);
|
||
doc.text('Uw woningontwerp', M, 62);
|
||
doc.setTextColor(...ORANGE);
|
||
doc.setFontSize(20);
|
||
doc.text(d.label, M, 74);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(`Samengesteld op ${new Date().toLocaleDateString('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' })}`, M, 82);
|
||
|
||
const i = d.info;
|
||
let y = keyValues(doc, M, 98, 150, [
|
||
['Buitenmaten', `${meters(i.lengthX)} × ${meters(i.lengthZ)}`], ['Bouwhoogte', meters(i.height)],
|
||
['Vloeroppervlak', `${nl(i.brutoM2, 1)} m² bruto, ${nl(i.leefbaarM2, 1)} m² leefbaar`], ['Inhoud', `${nl(i.inhoudM3, 0)} m³`],
|
||
['Modules', String(i.units)]
|
||
], 'Kerncijfers');
|
||
y = keyValues(doc, M, y + 6, 150, d.choices, 'Uw keuzes');
|
||
|
||
// Prijs: rustig, als de rest van de pagina; alleen het bedrag groter en vet.
|
||
const py = Math.max(y + 4, 196), price = d.price !== false;
|
||
if (price) {
|
||
doc.setDrawColor(...LINE);
|
||
doc.setLineWidth(0.3);
|
||
doc.line(M, py, M + 150, py);
|
||
doc.setTextColor(...MUTED);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10);
|
||
doc.text('Indicatieve prijs', M, py + 9);
|
||
doc.setTextColor(...INK);
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(20);
|
||
doc.text(CostModel.eur(Math.round(i.price / 100) * 100), M, py + 19);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(9);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text('incl. btw, onder voorbehoud', M, py + 25);
|
||
}
|
||
|
||
// Beeld van de woning
|
||
const bx = 190, by = 18, bw = PW - M - bx, bh = 206;
|
||
if (d.image) {
|
||
const ar = d.image.width / d.image.height, w = Math.min(bw, bh * ar), h = w / ar;
|
||
doc.addImage(d.image.url, 'JPEG', bx + (bw - w) / 2, by + (bh - h) / 2, w, h);
|
||
}
|
||
contactBand(doc, 240, d.url, `Bekijk ${d.label} online`);
|
||
doc.setFontSize(7);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(`Dit ontwerp${price ? ' en de prijs zijn' : ' is'} indicatief; hieraan kunnen geen rechten worden ontleend.`, M, PH - 6);
|
||
}
|
||
|
||
function process(doc, d) {
|
||
doc.setFillColor(...SOFT);
|
||
doc.rect(0, 0, PW, PH, 'F');
|
||
logo(doc, M, 18, 14);
|
||
doc.setTextColor(...INK);
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(28);
|
||
doc.text('Hoe verloopt het traject?', M, 62);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(12);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(doc.splitTextToSize('Van het eerste idee tot de sleuteloverdracht: wij begeleiden u in duidelijke stappen naar een mantelzorgwoning die bij u past. Dit ontwerp is de start van stap 2.', 260), M, 74);
|
||
|
||
const gap = 8, w = (PW - 2 * M - gap * (STEPS.length - 1)) / STEPS.length, y = 92, h = 98;
|
||
STEPS.forEach(([title, text], k) => {
|
||
const x = M + k * (w + gap);
|
||
doc.setFillColor(255, 255, 255);
|
||
doc.setDrawColor(...LINE);
|
||
doc.roundedRect(x, y, w, h, 3, 3, 'FD');
|
||
doc.setFillColor(...ORANGE);
|
||
doc.circle(x + 12, y + 14, 6.5, 'F');
|
||
doc.setTextColor(255, 255, 255);
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(13);
|
||
doc.text(String(k + 1), x + 12, y + 14, { align: 'center', baseline: 'middle' });
|
||
doc.setTextColor(...INK);
|
||
doc.setFontSize(13);
|
||
doc.text(doc.splitTextToSize(title, w - 12), x + 6, y + 32);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(doc.splitTextToSize(text, w - 12), x + 6, y + 44, { lineHeightFactor: 1.4 });
|
||
});
|
||
// Maatwerk: dit ontwerp is een begin, de eigen software kan veel verder.
|
||
const by = 195, bh = 40;
|
||
doc.setFillColor(255, 255, 255);
|
||
doc.setDrawColor(...LINE);
|
||
doc.roundedRect(M, by, PW - 2 * M, bh, 3, 3, 'FD');
|
||
doc.setFillColor(...ORANGE);
|
||
doc.rect(M, by + 3, 2.5, bh - 6, 'F');
|
||
doc.setFont('helvetica', 'bold');
|
||
doc.setFontSize(13);
|
||
doc.setTextColor(...INK);
|
||
doc.text(MAATWERK[0], M + 9, by + 10);
|
||
doc.setFont('helvetica', 'normal');
|
||
doc.setFontSize(10.5);
|
||
doc.setTextColor(...MUTED);
|
||
doc.text(doc.splitTextToSize(MAATWERK[1], PW - 2 * M - 18), M + 9, by + 17, { lineHeightFactor: 1.35 });
|
||
contactBand(doc, 240, d.url, `Bekijk ${d.label} online`);
|
||
}
|
||
|
||
// d: { st, label, info, choices: [[label, tekst]], url, image: { url, width, height } | null }
|
||
function generate(d) {
|
||
const doc = PdfSheet.createDoc();
|
||
cover(doc, d);
|
||
HousePdf.generate(d.st, { customer: true, doc, pageOffset: 1, pageTotal: 4 });
|
||
doc.addPage('a3', 'landscape');
|
||
process(doc, d);
|
||
return doc;
|
||
}
|
||
|
||
return { COMPANY, modelUrl, generate };
|
||
})();
|