@metron-labs/quote-react 0.1.60 → 0.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
+ import { offersOption } from "../../lib/estimators/fixedProducts";
4
5
  const PROFILES = [
5
6
  { id: "ogee", label: "Ogee", blurb: "Traditional curved profile" },
6
7
  { id: "torus", label: "Torus", blurb: "Soft rounded detail" },
@@ -46,5 +47,5 @@ export default function ArchitraveEstimator({ onBack, onReview, submitLabel = "A
46
47
  },
47
48
  ];
48
49
  const openingRow = (label, opening, onChange, onRemove) => (_jsxs("div", { className: "architrave-calculator__opening-row", children: [_jsx("span", { children: label }), _jsx("input", { "aria-label": `${label} quantity`, type: "number", min: "0", step: "1", value: opening.quantity, onChange: (event) => onChange("quantity", event.target.value) }), _jsx("input", { "aria-label": `${label} width`, type: "number", min: "0", step: "0.01", value: opening.width, onChange: (event) => onChange("width", event.target.value) }), _jsx("input", { "aria-label": `${label} height`, type: "number", min: "0", step: "0.01", value: opening.height, onChange: (event) => onChange("height", event.target.value) }), onRemove ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: onRemove, children: "Remove" }) : null] }));
49
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Architrave Calculator" }), _jsx("p", { children: "Estimate architrave lengths and board quantities for door openings." })] }), _jsxs("div", { className: "truss-estimator__card architrave-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Measure your openings" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each door or window opening. Mitred corners are allowed for, and board lengths are worked out from the net run." }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Door openings" }), _jsx("p", { className: "architrave-calculator__hint", children: "Each opening is calculated as two side pieces plus one head piece." }), _jsxs("div", { className: "architrave-calculator__unit-row", children: [_jsx("span", { children: "Units" }), _jsxs("div", { className: "brick-calculator__unit-toggle", role: "group", "aria-label": "Measurement units", children: [_jsx("button", { type: "button", className: unit === "m" ? "is-selected" : "", onClick: () => setUnit("m"), children: "Metres" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Feet" })] })] }), _jsxs("div", { className: "architrave-calculator__dimensions-header", children: [_jsx("span", { children: "Opening" }), _jsx("span", { children: "Qty" }), _jsxs("span", { children: ["Width (", unit, ")"] }), _jsxs("span", { children: ["Height (", unit, ")"] }), _jsx("span", {})] }), openingRow("Single doors", singleDoor, (key, value) => updateOpening(setSingleDoor, key, value)), openingRow("Double doors", doubleDoor, (key, value) => updateOpening(setDoubleDoor, key, value)), alternativeDoors.map((opening, index) => openingRow(`Alternative door ${index + 1}`, opening, (key, value) => setAlternativeDoors((items) => items.map((item, itemIndex) => itemIndex === index ? { ...item, [key]: value } : item)), () => setAlternativeDoors((items) => items.filter((_, itemIndex) => itemIndex !== index)))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setAlternativeDoors((items) => [...items, emptyOpening()]), children: "+ Add alternative-sized door" })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Standard profile" }), _jsx("p", { className: "architrave-calculator__hint", children: "Choose the profile you plan to use. Length calculations are the same for each profile." }), _jsx("div", { className: "architrave-calculator__profiles", children: PROFILES.map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${profile === option.id ? " is-selected" : ""}`, onClick: () => setProfile(option.id), children: [_jsx(ProfileDiagram, { profile: option.id }), _jsx("div", { className: "truss-estimator__option-title", children: option.label }), _jsx("div", { className: "truss-estimator__option-copy", children: option.blurb })] }, option.id))) })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Board and waste" }), _jsx("div", { className: "architrave-calculator__options", children: _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Board length (m)" }), _jsx("input", { value: "4.40 m", disabled: true })] }) }), _jsx("p", { className: "architrave-calculator__hint", children: "Waste allows for mitred corners, cuts and damaged lengths." })] }), _jsxs("div", { className: "architrave-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "architrave-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Architrave needed" }), _jsxs("strong", { children: [baseLengthM.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Board lengths" }), _jsx("strong", { children: boardCount })] })] })] }), _jsxs("div", { className: "architrave-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), _jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", onClick: () => onReview?.(requirements), disabled: baseLengthM <= 0, children: submitLabel })] })] })] }) }));
50
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Architrave Calculator" }), _jsx("p", { children: "Estimate architrave lengths and board quantities for door openings." })] }), _jsxs("div", { className: "truss-estimator__card architrave-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Measure your openings" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each door or window opening. Mitred corners are allowed for, and board lengths are worked out from the net run." }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Door openings" }), _jsx("p", { className: "architrave-calculator__hint", children: "Each opening is calculated as two side pieces plus one head piece." }), _jsxs("div", { className: "architrave-calculator__unit-row", children: [_jsx("span", { children: "Units" }), _jsxs("div", { className: "brick-calculator__unit-toggle", role: "group", "aria-label": "Measurement units", children: [_jsx("button", { type: "button", className: unit === "m" ? "is-selected" : "", onClick: () => setUnit("m"), children: "Metres" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Feet" })] })] }), _jsxs("div", { className: "architrave-calculator__dimensions-header", children: [_jsx("span", { children: "Opening" }), _jsx("span", { children: "Qty" }), _jsxs("span", { children: ["Width (", unit, ")"] }), _jsxs("span", { children: ["Height (", unit, ")"] }), _jsx("span", {})] }), openingRow("Single doors", singleDoor, (key, value) => updateOpening(setSingleDoor, key, value)), openingRow("Double doors", doubleDoor, (key, value) => updateOpening(setDoubleDoor, key, value)), alternativeDoors.map((opening, index) => openingRow(`Alternative door ${index + 1}`, opening, (key, value) => setAlternativeDoors((items) => items.map((item, itemIndex) => itemIndex === index ? { ...item, [key]: value } : item)), () => setAlternativeDoors((items) => items.filter((_, itemIndex) => itemIndex !== index)))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setAlternativeDoors((items) => [...items, emptyOpening()]), children: "+ Add alternative-sized door" })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Standard profile" }), _jsx("p", { className: "architrave-calculator__hint", children: "Choose the profile you plan to use. Length calculations are the same for each profile." }), _jsx("div", { className: "architrave-calculator__profiles", children: PROFILES.filter((option) => offersOption("architrave", `architrave-${option.id}`)).map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${profile === option.id ? " is-selected" : ""}`, onClick: () => setProfile(option.id), children: [_jsx(ProfileDiagram, { profile: option.id }), _jsx("div", { className: "truss-estimator__option-title", children: option.label }), _jsx("div", { className: "truss-estimator__option-copy", children: option.blurb })] }, option.id))) })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Board and waste" }), _jsx("div", { className: "architrave-calculator__options", children: _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Board length (m)" }), _jsx("input", { value: "4.40 m", disabled: true })] }) }), _jsx("p", { className: "architrave-calculator__hint", children: "Waste allows for mitred corners, cuts and damaged lengths." })] }), _jsxs("div", { className: "architrave-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "architrave-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Architrave needed" }), _jsxs("strong", { children: [baseLengthM.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Board lengths" }), _jsx("strong", { children: boardCount })] })] })] }), _jsxs("div", { className: "architrave-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), _jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", onClick: () => onReview?.(requirements), disabled: baseLengthM <= 0, children: submitLabel })] })] })] }) }));
50
51
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useRef, useState } from "react";
4
4
  import { assetUrl } from "../../lib/assets";
5
+ import { offersOption } from "../../lib/estimators/fixedProducts";
5
6
  import ForwardArrow from "../ForwardArrow";
6
7
  const CANVAS_WIDTH_M = 12;
7
8
  const CANVAS_HEIGHT_M = 8;
@@ -272,7 +273,7 @@ export default function DeckingEstimator({ onBack, onReview, submitLabel = "Add
272
273
  else
273
274
  setStepIndex((value) => value + 1);
274
275
  };
275
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Decking Calculator" }), _jsx("p", { children: "Estimate timber boards, joists and screws for your deck." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your timber decking" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Both options are fixed, orderable pressure-treated timber boards." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb }), _jsxs("div", { className: "truss-estimator__option-copy", children: [range.boardLengthM, "m boards"] })] }, id))) })] })) : null, currentStep === "grid" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Tell us your deck size" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Start with a quick rectangle estimate, or draw an irregular deck for a more precise result." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "draw", drawMode: "outline" })), children: "Draw your space" })] }), state.deckSizeMode === "quick" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Length (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Width (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickWidthM, onChange: (event) => setState((value) => ({ ...value, quickWidthM: event.target.value })) })] })] }), _jsx("div", { className: "truss-estimator__grid-hint", children: "For L-shaped decks, cut-outs, or sides against a wall, use Draw your space." }), hasQuickDimensions ? (_jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [(quickLengthM * quickWidthM).toFixed(2), " m\u00B2 deck"] }), _jsxs("div", { children: [(2 * (quickLengthM + quickWidthM)).toFixed(2), " m perimeter"] })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Draw your deck" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Click to add corners, then close the outline. Drag any handle to refine it." }), _jsxs("div", { className: "deck-outline__toolbar", children: [state.isOutlineClosed ? (_jsxs("div", { className: "deck-outline__summary", children: [_jsxs("div", { children: [_jsx("span", { children: "Deck area" }), _jsxs("strong", { children: [drawingArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Perimeter" }), _jsxs("strong", { children: [drawingPerimeter.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Wall edges" }), _jsx("strong", { children: state.wallSegments.size > 0 ? state.wallSegments.size : "None" })] }), _jsxs("div", { children: [_jsx("span", { children: "Visible edge" }), _jsxs("strong", { children: [visibleDrawingEdgeLength.toFixed(2), " m"] })] })] })) : null, _jsxs("div", { className: "deck-outline__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, points: value.points.slice(0, -1) })), disabled: state.points.length === 0 || state.isOutlineClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
276
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Decking Calculator" }), _jsx("p", { children: "Estimate timber boards, joists and screws for your deck." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your timber decking" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Both options are fixed, orderable pressure-treated timber boards." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).filter(([id]) => offersOption("decking", `boards-${id}`)).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb }), _jsxs("div", { className: "truss-estimator__option-copy", children: [range.boardLengthM, "m boards"] })] }, id))) })] })) : null, currentStep === "grid" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Tell us your deck size" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Start with a quick rectangle estimate, or draw an irregular deck for a more precise result." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "draw", drawMode: "outline" })), children: "Draw your space" })] }), state.deckSizeMode === "quick" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Length (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Width (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickWidthM, onChange: (event) => setState((value) => ({ ...value, quickWidthM: event.target.value })) })] })] }), _jsx("div", { className: "truss-estimator__grid-hint", children: "For L-shaped decks, cut-outs, or sides against a wall, use Draw your space." }), hasQuickDimensions ? (_jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [(quickLengthM * quickWidthM).toFixed(2), " m\u00B2 deck"] }), _jsxs("div", { children: [(2 * (quickLengthM + quickWidthM)).toFixed(2), " m perimeter"] })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Draw your deck" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Click to add corners, then close the outline. Drag any handle to refine it." }), _jsxs("div", { className: "deck-outline__toolbar", children: [state.isOutlineClosed ? (_jsxs("div", { className: "deck-outline__summary", children: [_jsxs("div", { children: [_jsx("span", { children: "Deck area" }), _jsxs("strong", { children: [drawingArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Perimeter" }), _jsxs("strong", { children: [drawingPerimeter.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Wall edges" }), _jsx("strong", { children: state.wallSegments.size > 0 ? state.wallSegments.size : "None" })] }), _jsxs("div", { children: [_jsx("span", { children: "Visible edge" }), _jsxs("strong", { children: [visibleDrawingEdgeLength.toFixed(2), " m"] })] })] })) : null, _jsxs("div", { className: "deck-outline__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, points: value.points.slice(0, -1) })), disabled: state.points.length === 0 || state.isOutlineClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
276
277
  setDrawCursorPoint(null);
277
278
  setState((value) => ({ ...value, points: [], isOutlineClosed: false, wallSegments: new Set(), drawMode: "outline", drawError: "" }));
278
279
  }, disabled: state.points.length === 0, children: "Clear" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useRef, useState } from "react";
4
4
  import { assetUrl } from "../../lib/assets";
5
+ import { offersOption } from "../../lib/estimators/fixedProducts";
5
6
  import ForwardArrow from "../ForwardArrow";
6
7
  const GRID_SIZE = 12;
7
8
  const CELL_PX = 30;
@@ -31,6 +32,11 @@ const CONFIG = {
31
32
  },
32
33
  };
33
34
  const STEPS = ["range", "height", "gate", "posts"];
35
+ // Fences 1.2m and lower use 6ft posts; taller ones 8ft.
36
+ const postLengthFor = (height) => (Number(height) <= 1.2 ? "6" : "8");
37
+ // Only the panels and posts the merchant stocks are offered (see offersOption).
38
+ const heightOffered = (range, height) => offersOption("fencing", `panels-${range ?? "boundary"}-${height}`);
39
+ const mountingOffered = (mounting, height) => !height || offersOption("fencing", mounting === "concrete" ? `concrete-intermediate-${postLengthFor(height)}` : `timber-post-${postLengthFor(height)}`);
34
40
  const INITIAL_STATE = {
35
41
  range: null,
36
42
  colour: null,
@@ -199,7 +205,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
199
205
  };
200
206
  const results = computeResults();
201
207
  const gateLabel = state.gateType && state.gateType !== "none" ? CONFIG.gate[state.gateType].label : null;
202
- const postLength = Number(results.height) <= 1.2 ? "6" : "8";
208
+ const postLength = postLengthFor(results.height);
203
209
  const requirements = results
204
210
  ? [
205
211
  { key: `panels-${state.range ?? "boundary"}-${results.height}`, label: `${results.range.label} panels (${results.height}m)`, qty: results.panelsCount, unit: "each", query: "fence panel", category: "fencing" },
@@ -419,7 +425,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
419
425
  setStepIndex((value) => value + 1);
420
426
  };
421
427
  const perimeterEdges = getPerimeterEdges(state.grid);
422
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Fencing Calculator" }), _jsx("p", { children: "Estimate panels, posts and cost for your fence line in a few steps." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your fence panels" }), _jsx("p", { className: "truss-estimator__step-sub", children: "The calculator uses fixed, orderable timber panels from the catalogue." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb })] }, id))) })] })) : null, currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Measure your fence line" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Use a quick total, or draw the boundary to mark existing edges that do not need new fencing." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "draw" })), children: "Draw your boundary" })] }), state.boundaryMode === "quick" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact fence-boundary__quick-field", children: [_jsx("span", { children: "Total fence length (m)" }), _jsx("input", { type: "number", min: "0", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "fence-boundary__toolbar", children: _jsxs("div", { className: "fence-boundary__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, boundaryPoints: value.boundaryPoints.slice(0, -1), boundaryClosed: false, excludedBoundaryEdges: new Set() })), disabled: state.boundaryPoints.length === 0 || state.boundaryClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
428
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Fencing Calculator" }), _jsx("p", { children: "Estimate panels, posts and cost for your fence line in a few steps." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your fence panels" }), _jsx("p", { className: "truss-estimator__step-sub", children: "The calculator uses fixed, orderable timber panels from the catalogue." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id, height: value.height && heightOffered(id, value.height) ? value.height : null })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb })] }, id))) })] })) : null, currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Measure your fence line" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Use a quick total, or draw the boundary to mark existing edges that do not need new fencing." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "draw" })), children: "Draw your boundary" })] }), state.boundaryMode === "quick" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact fence-boundary__quick-field", children: [_jsx("span", { children: "Total fence length (m)" }), _jsx("input", { type: "number", min: "0", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "fence-boundary__toolbar", children: _jsxs("div", { className: "fence-boundary__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, boundaryPoints: value.boundaryPoints.slice(0, -1), boundaryClosed: false, excludedBoundaryEdges: new Set() })), disabled: state.boundaryPoints.length === 0 || state.boundaryClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
423
429
  setBoundaryPan({ x: 0, y: 0 });
424
430
  setBoundaryCursorPoint(null);
425
431
  setState((value) => ({ ...value, boundaryPoints: [], boundaryClosed: false, excludedBoundaryEdges: new Set() }));
@@ -487,7 +493,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
487
493
  next.add(edge.key);
488
494
  return { ...value, excludedEdges: next };
489
495
  }) }, edge.key));
490
- }) })) : null] }) }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__grid-hint", children: "Click an edge on the shape above to toggle it." }), _jsxs("div", { className: "truss-estimator__legend", children: [_jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "var(--accent)", strokeWidth: "4", strokeLinecap: "round" }) }), "Needs new fencing"] }), _jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "#9aa3b5", strokeWidth: "4", strokeLinecap: "round", strokeDasharray: "3 3" }) }), "Existing boundary"] })] })] })) : (_jsx("div", { className: "truss-estimator__grid-hint", children: "Once you have drawn the plot, switch to boundary mode to exclude any sides that do not need a new fence." })), _jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [state.grid.size, " m\u00B2 plot"] }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [results.totalEdgeM, " m fence run"] }), _jsxs("div", { children: [results.cornerPostCount, " corner/end posts"] })] })) : null] })] })) : null, currentStep === "height" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Fence height" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Select the nearest available panel height." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--pitch", children: CONFIG.heights.map((height) => (_jsx("button", { type: "button", className: `truss-estimator__visual-card${state.height === height ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, height })), children: _jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--tall", src: assetUrl(`/fencing/height/${height}.webp`), alt: "" }) }, height))) }), _jsx("div", { className: "truss-estimator__section-heading", children: "Gravel board?" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
496
+ }) })) : null] }) }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__grid-hint", children: "Click an edge on the shape above to toggle it." }), _jsxs("div", { className: "truss-estimator__legend", children: [_jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "var(--accent)", strokeWidth: "4", strokeLinecap: "round" }) }), "Needs new fencing"] }), _jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "#9aa3b5", strokeWidth: "4", strokeLinecap: "round", strokeDasharray: "3 3" }) }), "Existing boundary"] })] })] })) : (_jsx("div", { className: "truss-estimator__grid-hint", children: "Once you have drawn the plot, switch to boundary mode to exclude any sides that do not need a new fence." })), _jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [state.grid.size, " m\u00B2 plot"] }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [results.totalEdgeM, " m fence run"] }), _jsxs("div", { children: [results.cornerPostCount, " corner/end posts"] })] })) : null] })] })) : null, currentStep === "height" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Fence height" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Select the nearest available panel height." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--pitch", children: CONFIG.heights.filter((height) => heightOffered(state.range, height)).map((height) => (_jsx("button", { type: "button", className: `truss-estimator__visual-card${state.height === height ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, height, mountingType: value.mountingType && mountingOffered(value.mountingType, height) ? value.mountingType : null })), children: _jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--tall", src: assetUrl(`/fencing/height/${height}.webp`), alt: "" }) }, height))) }), _jsx("div", { className: "truss-estimator__section-heading", children: "Gravel board?" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
491
497
  { id: "no", label: "No gravel board", img: "/fencing/no-gravel-board.webp" },
492
498
  { id: "yes", label: "Add gravel board", img: "/fencing/gravel-board.webp" },
493
499
  ].map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__visual-card truss-estimator__visual-card--wide${state.gravelBoard === option.id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, gravelBoard: option.id })), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) })] })) : null, currentStep === "gate" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Do you need a gate?" }), _jsx("p", { className: "truss-estimator__step-sub", children: "A gate opening replaces part of the panel run and adds a gate kit with heavy-duty posts." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
@@ -497,5 +503,5 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
497
503
  ...value,
498
504
  gateType: option.id,
499
505
  gateWidthM: option.id === "none" ? "" : String(CONFIG.gate[option.id].widthDefault),
500
- })), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) }), state.gateType && state.gateType !== "none" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact", children: [_jsx("span", { children: "Gate width (m)" }), _jsx("input", { type: "text", inputMode: "decimal", value: state.gateWidthM, onChange: (event) => setState((value) => ({ ...value, gateWidthM: event.target.value })) })] })) : null] })) : null, currentStep === "posts" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Posts and fixing type" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Choose how the posts will be set." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.mounting).map(([id, mounting]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.mountingType === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, mountingType: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: mounting.label }), _jsx("div", { className: "truss-estimator__option-copy", children: mounting.blurb })] }, id))) })] })) : null] }), _jsxs("div", { className: "truss-estimator__nav", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", justifyContent: "center", marginTop: 0 }, onClick: handleBack, children: "Back" }), _jsxs("button", { type: "button", className: "cta-btn", style: { flex: "0 1 70%", marginTop: 0 }, onClick: handleNext, disabled: !canContinue(), children: [_jsx("span", { children: nextLabel }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })] })] }) }));
506
+ })), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) }), state.gateType && state.gateType !== "none" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact", children: [_jsx("span", { children: "Gate width (m)" }), _jsx("input", { type: "text", inputMode: "decimal", value: state.gateWidthM, onChange: (event) => setState((value) => ({ ...value, gateWidthM: event.target.value })) })] })) : null] })) : null, currentStep === "posts" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Posts and fixing type" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Choose how the posts will be set." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.mounting).filter(([id]) => mountingOffered(id, state.height)).map(([id, mounting]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.mountingType === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, mountingType: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: mounting.label }), _jsx("div", { className: "truss-estimator__option-copy", children: mounting.blurb })] }, id))) })] })) : null] }), _jsxs("div", { className: "truss-estimator__nav", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", justifyContent: "center", marginTop: 0 }, onClick: handleBack, children: "Back" }), _jsxs("button", { type: "button", className: "cta-btn", style: { flex: "0 1 70%", marginTop: 0 }, onClick: handleNext, disabled: !canContinue(), children: [_jsx("span", { children: nextLabel }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })] })] }) }));
501
507
  }