The flowcharts use four kinds of blocks:
Arrows run from top to bottom. At a decision the flow splits, and each branch carries a label (Yes, No, or the chosen option).
The configurator steps follow each other in a fixed order. Every step filters or drives the next one.
While rendering the item list (step 3), a feasibility check runs for each item. The outcome decides whether the item is marked suitable, tilt fallback or not possible.
kamerhoog, hoogte_stof,kantelbaar, patroonThe cut height is the length of fabric cut per panel. Finished height, heading tape and bottom hem together determine the cut height. Pattern repeat (patroonhoogte) does not enter the calculation — the workshop handles repeat alignment at processing time.
The effective width depends on the pleat (return pleats need extra slack) and the number of parts (side hems only apply to floor-to-ceiling).
The way panels are counted depends on whether the item is floor-to-ceiling, and for floor-to-ceiling on whether the fabric needs to be tilted.
The fabric price uses the manual verkoopprijs_per_m1 field if it has been set; otherwise a 60% fallback based on the purchase price is used.
The weight-cord choice is a 3-state dropdown: Existing weight cord, Add weight cord or No. The article flag geschikt_voor_verzwaringskoord drives whether the option Add is available.
The header (Dutch: hoofdje) is the narrow strip of fabric above the pleat tape. It is a configurator choice in Step 6 — Pleat & split that documents the workshop finish — it does not affect the cut height. Allowed values: 0 or 2.5 mm. With Wave, Single return and Double return the header is technically always 0 — the configurator forces the value to 0 and disables the field.
The item field geschikte_plooi (Multi-Select Picklist) determines which pleat types are allowed for this fabric. A separate rule also applies: Wave pleat is only suitable for floor-to-ceiling fabrics — with panel fabric Wave is dropped automatically, even if it appears in geschikte_plooi. If the user picks an unsuitable pleat the configurator shows a warning — the calculation keeps running, but the user clearly sees that the recommendation is to deviate.
Where is this visible?
Quick reference: find an input field in the first column and see which other fields or calculations change as a result.
| Source field (input) | Step | Drives → target field or calculation |
|---|---|---|
| Curtain type | 1 | Filters the item list (only items with that gordijn_type). |
| FinishedWidth | 2 |
→ returnAllowance (300 cm threshold) → effectiveWidth → requiredFabricWidth → panelCount (roll fabric or tilted) → warning > 600 cm |
| FinishedHeight | 2 |
→ rawCutHeight → cutHeight → floor-to-ceiling feasibility check → totalMetreage for roll fabric / tilted |
| Item.kamerhoog | 3 | Selects scenario A (floor-to-ceiling) or B (roll fabric). Floor-to-ceiling adds the side-hem allowance. |
| Item.hoogte_stof | 3 | Upper limit for upright floor-to-ceiling. In the tilted scenario this is the panel width. |
| Item.breedte_stof | 3 | For roll fabric: roll width used for panelCount = ⌈fabricWidth/breedte_stof⌉. |
| Item.kantelbaar | 3 | Toggles the tilted scenario on/off. Forced to No when patterned. |
| Item.patroon (+ pattern height) | 3 | For Patterned: forces kantelbaar = No. The pattern repeat (patroonhoogte) is informational and does not enter the cut height — the workshop handles repeat alignment at processing time. |
| Item.verkoopprijs_per_m1 | 3 | Source manual → fabricPrice. Empty/0 → auto via purchase×3 / xx.95. |
| Pleat | 6 |
→ pleat factor (1.80–2.50) → returnAllowance (only for Single/Double return) → requiredFabricWidth → compared against item.geschikte_plooi → warning if not in list |
| Item.geschikte_plooi | 3 | Multi-Select list of allowed pleats (Wave / Double return / Single return / Double / Single). Empty = no restriction. Drives the warning in step 6 plus badge rendering in the item list and aside. |
| Item.geschikt_vouwgordijn | 3 | Picklist Yes / No / Yes (unlined). Informational — not used in metreage or price. |
| Number of parts | 6 | → side-hem allowance (Pair +48 / 1 part +24, floor-to-ceiling only). |
| Heading tape (8/10/12/15) | 6 | → rawCutHeight. |
| Lining | 7 | Lining ≠ None reveals colour + price field; multiplies with totalMetreage. |
| Weight cord (configurator) | 8 |
3 options: Existing, Add, No. → Existing/Add → bottom hem = 2 cm → No → bottom hem = 15 cm → in tilted floor-to-ceiling: only Existing costs 2 cm panel width (cord cut away). Add gives no loss. |
| Item.geschikt_voor_verzwaringskoord | 3 | Field label "Suitable with weight cord" (Yes/No). Drives whether the option Add weight cord is offered in the configurator. No → option hidden. |
| Item.eenheid | 3 | Price unit. For curtain fabric m¹; for accessories may be unit. Shown next to cost & sales price in the aside and in the price line of the breakdown. |
| Pull rod (12 SKUs) | 8 | Only visible with Hand operated. Material (Fiber transparent / White aluminium / Silver aluminium / Black aluminium) × length (92 / 122 / 152 cm). The customer always picks the length themselves; per option the configurator shows the live bottom (bottom = FinishedHeight − length) with ✓ within 140–160 cm or ⚠ outside. Recommendation under the field: the length closest to 150 cm bottom. Prices incl. VAT: 92 cm €19.95 / 122 cm €22.95 / 152 cm €24.95. Quantity = number of curtain parts. |
| Rail (8 SKUs, hand operated) | 8 | Family × colour. DS (white/black) and DS XL (white/anthracite/silver) for Wave + Return; KS (white/black/silver) for Single/Double pleat. Family dropdown is filtered by the chosen pleat. Header is set automatically: KS → 2.5 mm, DS / DS XL → 0 mm. Rod of 600 cm; rod count = ⌈FinishedWidth÷600⌉, minimum 1. Prices range from €40 (KS white) to €150 (DS XL anthracite/silver). With Electric: TBD order line. |
| Rail accessories (TBD) | 8 | Per curtain order line auto-added, article numbers to be filled: bracket (platform for ceiling/recess, wall bracket for wall) — 1 pc per 50 cm; end piece — 2 pcs; glider — 10 per metre of FinishedWidth; lead glider — 1 pc; wave runner — 2 pcs only for Wave pleat. |
| Electric operation — Lab21 wizard | 8 | Choosing Electric reveals a 6-question wizard (derived from the Forest Group decision tree with Forest → Lab21). Questions: 1) smart system present, 2) operate via existing system, 3) system type (Domotics / Smart Home), 3a) brand (Somfy / HUE / IKEA / Apple HomeKit / Homey / Fibaro / KAKU / SmartThings / Apple Siri / Luxaflex / Google·Amazon / Coulisse), 3b) domotics type (RS232/485 / volt-free / switching wires), 4) power point at motor, 5) operation (multi: remote / wall switch / app / voice / domotics), 6) feedback. Live advice below the wizard with The solution (Lab21 Shuttle motor + WiFi dongle / Wireless Connector / Diamond Sense AB / Wall switch / External RF Multi receiver / RJ-45 / AC Control Set) and extras. Third-party plugs (HomeKit / KAKU / Z-Wave / Somfy / HUE / IKEA / Powerview) are explicitly marked external, not via Lab21. All components appear as separate order lines with price to be filled. |
| Wave pleat rule | 6 | Wave pleat is only suitable for floor-to-ceiling fabrics. With panel fabric Wave is dropped automatically (warning + exclusion in dropdown). |
The page style-configurator-en.html is a separate tool that translates a customer's living style and colour preference into a structured JSON profile. That profile is then consumed by Kevin's external AI visualizer API to place furniture into a customer photo.
Step 2 still shows all four palettes, but always greys out the one that clashes stylistically. Non-matching palettes remain on screen and clickable so the user can see why the choice narrows — blocking instead of hiding prevents confusion.
| Style | Allowed palettes (order) | Greyed out |
|---|---|---|
| Industrial | Bold Contrast · Warm & Cosy · Natural | Cool & Fresh |
| Scandinavian | Cool & Fresh · Warm & Cosy · Natural | Bold Contrast |
| Modern / Minimalist | Cool & Fresh · Bold Contrast · Warm & Cosy | Natural |
| Rural / Classic | Warm & Cosy · Natural · Bold Contrast | Cool & Fresh |
For each window the customer selects one or more goals (privacy, blackout, acoustics, heat/UV, …). The function filterRaamOplossingen(goals) filters the matrix from the source spreadsheet and shows the Top 4 combinations that cover all goals, sorted by versatility.
// TypeScript equivalent for the AI pipeline
function filterRaamOplossingen(goals: string[]): RaamOplossing[] {
if (!goals.length) return [...RAAM_OPLOSSINGEN].sort(byCoverage);
return RAAM_OPLOSSINGEN
.filter(o => goals.every(g => o.goals.includes(g)))
.sort((a, b) => b.goals.length - a.goals.length);
}
The 15 combinations in the matrix cover every product in the source file: Inbetween, Drapery, Venetian blind, Duette (incl. transparent variant) and Roller blind — either solo or in combinations of 2 or 3 products.
For every chosen window-treatment combination a kapstalen_id is generated automatically in the form KAP-{style}-{palette}-{products}-{nr}. With this code the showroom salesperson knows immediately which physical samples to pull from the rack.
generateKapstalenId('Industrial', 'Warm_Earth', 'inb_over_jal', 1)
// → "KAP-IND-WE-INB-OG-JAL-001"
The primary keys are still style_id and color_palette_id; alongside those, project_scope, existing_furniture and per-window window_specs now travel with the payload — including the technical advanced fields and the Sample-rack ID that links the salesperson to the showroom.
{
"style_id": "Industrial",
"color_palette_id": "Warm_Earth",
"project_scope": ["flooring", "window_decor", "wall", "furniture"],
"existing_furniture": [
{ "type": "Sofa", "dimensions": { "l": 220, "w": 90, "h": 80 }, "image_url": "data:…" }
],
"window_specs": [
{
"name": "Window 1",
"dimensions": { "width": 1800, "height": 2400 },
"frame_material": "plastic",
"operable_type": "movable",
"mounting": "in_recess",
"function_goals": ["privacy", "blackout", "acoustics"],
"suggested_products": ["inbetween", "drapery", "duette"],
"kapstalen_id": "KAP-IND-WE-INB-OG-DUE-001",
"advanced": { "vol_muur": false, "draaikiep": true, "…": "…" }
}
],
"customer_photo_url": "data:image/jpeg;base64,…",
"ai_prompt_tags": ["exposed brick walls", "warm earth tones", "…"],
"meta": { "ai_suggested": false, "furniture_count": 1, "window_count": 1, "…": "…" }
}
ai_suggested: true in the output.state.projectScope is an array; in the JSON it travels through 1-to-1 as project_scope..tile-image block has an HTML comment <!-- IMAGE-PLACEHOLDER {ID} -->; replace it with <img src="…"> for showroom photos.floor_plan object (filename + mime_type + size_bytes + base64 data_url) so an advisor can do manual calculations on it (surface area, positions, material quantities).The page product-selector-en.html is a parallel path for customers who do not want style or colour advice but want to pick straight from a catalogue. The Style Engine and the Product Selector are cross-linked — the user can switch at any moment.
The cart (sticky side panel) runs validateCart() live after every product change and shows warnings + alternatives:
drill → drilling not allowed, suggest Duette/Roller blind with clamp mounting.Every notice carries a clickable alternative; one click adds the suggested product to the cart directly.
The schema follows the brief exactly, with arrays where combinations are possible (Inbetween + Duette). texture_map points to a static CDN URL that Kevin's diffusion pipeline loads in.
{
"mode": "free_selection",
"project_scope": ["flooring", "window_decor"],
"selected_products": {
"floor": {
"id": "PVC_OAK_HERR_001", "pattern": "herringbone", "material": "PVC",
"texture_map": "/textures/floor/PVC_OAK_HERR_001.jpg"
},
"window": [
{ "id": "DUE_BLK_003", "product_type": "duette",
"mount": "clamp_on_frame",
"texture_map": "/textures/window/DUE_BLK_003.jpg" },
{ "id": "INB_LIN_NAT_001", "product_type": "inbetween",
"mount": "wall_mount",
"texture_map": "/textures/window/INB_LIN_NAT_001.jpg" }
]
},
"window_context": { "frame_material": "plastic", "draaikiep": true, "mounting": "in_recess" },
"custom_furniture": [
{ "id": "user_sofa_1", "type": "Sofa", "dimensions": [220, 90, 80], "image_url": "data:…" }
],
"floor_plan": {
"filename": "floor_plan_living_room.pdf",
"mime_type": "application/pdf",
"size_bytes": 234567,
"data_url": "data:application/pdf;base64,…"
},
"room_photo": "data:image/jpeg;base64,…",
"validation_notices": [
{ "icon": "ℹ", "message": "Tilt & turn window: chosen products block tilting …" }
],
"meta": { "floor_count": 1, "window_count": 2, "furniture_count": 1, "…": "…" }
}
| Aspect | Style Engine | Free Choice |
|---|---|---|
| Entry point | Style + colour as anchor | Straight into the catalogue |
| JSON mode | (implicit) | "mode": "free_selection" |
| Product choice | AI suggests (Top 4 from matrix) | Customer picks from grid themselves |
| Output to AI | style_id + color_palette_id + tags | Product_ID + texture_map per item |
| Cross-link | "🛍️ Free choice" in launch bar | "Help me choose based on my style" in switch bar |
| Visual design | Identical — shared stylesheet style-engine.css | |
Both pages link style-engine.css for all visual basics: header, progress bar, buttons, .tile, .upload-zone, .toast, .final-note, .plan-upload, gradient backgrounds and mobile tweaks. The scope tiles in step 1 (Free Choice) and step 4 (Style Engine) now use the same .tile + bg-* + SVG-icon combination so the two paths share an identical look and feel.
Page-specific components (Style Engine: window cards, top4 grid, profile summary, advanced details; Free Choice: tabs, filter bar, product grid, cart panel, ctx card) stay inline on the page itself — that is intentional, they do not need to be shared and it keeps the CSS file small.
↑ Back to top