// RoomCanvas — Interactive floor plan boundary builder
// Phases: base → upper → openings → appliances → done

function RoomCanvas({ lengthIn, widthIn, onComplete, unit = 'in' }) {
  const SVG_W = 500;
  const PAD   = 56;
  const BASE_CAB_IN  = 24;
  const UPPER_CAB_IN = 12;

  const ASPECT   = (widthIn > 0 && lengthIn > 0) ? Math.min(Math.max(widthIn / lengthIn, 0.4), 1.1) : 0.7;
  const SVG_H    = Math.round(SVG_W * ASPECT);
  const roomPxW  = SVG_W - PAD * 2;
  const roomPxH  = SVG_H - PAD * 2;
  const scaleX   = lengthIn > 0 ? roomPxW / lengthIn : 1;
  const scaleY   = widthIn  > 0 ? roomPxH / widthIn  : 1;

  // ── State ─────────────────────────────────────────────────────────────────
  const nextId    = React.useRef(1);
  const canvasRef = React.useRef(null);
  React.useEffect(() => {
    if (canvasRef.current) {
      const y = canvasRef.current.getBoundingClientRect().top + window.scrollY - 80;
      window.scrollTo({ top: Math.max(0, y), behavior: 'smooth' });
    }
  }, []);
  const [phase,          setPhase]          = React.useState('base');
  const [selWalls,       setSelWalls]       = React.useState(new Set());
  const [trims,          setTrims]          = React.useState({});
  const [baseData,       setBaseData]       = React.useState(null);
  const [upperData,      setUpperData]      = React.useState(null);
  const [openings,       setOpenings]       = React.useState([]);   // confirmed
  const [pendingOpening, setPendingOpening] = React.useState(null); // being configured
  // addMode: null | 'choosing' | 'door' | 'window'
  const [addMode,          setAddMode]          = React.useState(null);
  const [appliances,       setAppliances]       = React.useState([]);
  const [pendingAppliance, setPendingAppliance] = React.useState(null);
  // appMode: null | 'fridge' | 'sink' | 'stove' | 'dishwasher'
  const [appMode,          setAppMode]          = React.useState(null);

  // ── Phase booleans ────────────────────────────────────────────────────────
  const phaseIsBase       = phase === 'base';
  const phaseIsUpper      = phase === 'upper';
  const phaseIsOpenings   = phase === 'openings';
  const phaseIsAppliances = phase === 'appliances';
  const phaseIsDone       = phase === 'done';


  // ── Cabinet phase config ──────────────────────────────────────────────────
  const CAB_IN      = (phaseIsBase || phaseIsAppliances) ? BASE_CAB_IN : UPPER_CAB_IN;
  const cabPxX      = CAB_IN * scaleX;
  const cabPxY      = CAB_IN * scaleY;
  const baseCabPxX  = BASE_CAB_IN * scaleX;
  const baseCabPxY  = BASE_CAB_IN * scaleY;
  const upperCabPxX = UPPER_CAB_IN * scaleX;
  const upperCabPxY = UPPER_CAB_IN * scaleY;
  const phaseAccent = phaseIsBase ? '#c0703a' : '#8c9a86';
  const hatchId     = phaseIsBase ? 'hatch-base' : 'hatch-upper';

  const ORDER = ['north', 'east', 'south', 'west'];

  const NW = { x: PAD + cabPxX,           y: PAD + cabPxY };
  const NE = { x: PAD + roomPxW - cabPxX, y: PAD + cabPxY };
  const SE = { x: PAD + roomPxW - cabPxX, y: PAD + roomPxH - cabPxY };
  const SW = { x: PAD + cabPxX,           y: PAD + roomPxH - cabPxY };

  // Cabinet-phase hit zones — large, forgiving, full-side coverage
  const hitNS = Math.min(Math.max(cabPxY * 2.5, 22), Math.max((roomPxH - 40) / 2, 22));
  const hitEW = Math.min(Math.max(cabPxX * 2.5, 22), Math.max((roomPxW - 40) / 2, 22));

  // Placement hit zones — narrow band, ~cab-depth + margin (≈2–3 ft visual strip at wall)
  const phNS = cabPxY + 20;   // pixels deep from N/S wall outer edge
  const phEW = cabPxX + 20;   // pixels deep from E/W wall outer edge
  const PLACE_HIT = {
    north: { x: PAD,                    y: PAD,                        w: roomPxW, h: phNS  },
    south: { x: PAD,                    y: PAD + roomPxH - phNS,       w: roomPxW, h: phNS  },
    east:  { x: PAD + roomPxW - phEW,  y: PAD,                        w: phEW,    h: roomPxH },
    west:  { x: PAD,                    y: PAD,                        w: phEW,    h: roomPxH },
  };

  const WALL = {
    north: { start: NW, end: NE, outerLine: [PAD, PAD, PAD + roomPxW, PAD],
      hit: { x: PAD, y: PAD, w: roomPxW, h: hitNS }, perp: { x: 0, y: -1 } },
    east:  { start: NE, end: SE, outerLine: [PAD + roomPxW, PAD, PAD + roomPxW, PAD + roomPxH],
      hit: { x: PAD + roomPxW - hitEW, y: PAD, w: hitEW, h: roomPxH }, perp: { x: 1, y: 0 } },
    south: { start: SE, end: SW, outerLine: [PAD, PAD + roomPxH, PAD + roomPxW, PAD + roomPxH],
      hit: { x: PAD, y: PAD + roomPxH - hitNS, w: roomPxW, h: hitNS }, perp: { x: 0, y: 1 } },
    west:  { start: SW, end: NW, outerLine: [PAD, PAD, PAD, PAD + roomPxH],
      hit: { x: PAD, y: PAD, w: hitEW, h: roomPxH }, perp: { x: -1, y: 0 } },
  };

  const wallLbl = {
    north: { x: PAD + roomPxW / 2, y: PAD + cabPxY + 15 },
    south: { x: PAD + roomPxW / 2, y: PAD + roomPxH - cabPxY - 15 },
    east:  { x: PAD + roomPxW - cabPxX - 15, y: PAD + roomPxH / 2 },
    west:  { x: PAD + cabPxX + 15,           y: PAD + roomPxH / 2 },
  };

  // ── Cabinet wall toggle ───────────────────────────────────────────────────
  const toggleWall = (id) => {
    setSelWalls(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
    setTrims({});
  };

  // ── Run detection ─────────────────────────────────────────────────────────
  const getRuns = () => {
    const sel = ORDER.filter(w => selWalls.has(w));
    if (!sel.length) return [];
    let startI = 0;
    for (let i = 0; i < sel.length; i++) {
      if (!selWalls.has(ORDER[(ORDER.indexOf(sel[i]) + 3) % 4])) { startI = i; break; }
    }
    const ordered = [...sel.slice(startI), ...sel.slice(0, startI)];
    const runs = []; let run = [ordered[0]];
    for (let i = 1; i < ordered.length; i++) {
      (ORDER.indexOf(ordered[i - 1]) + 1) % 4 === ORDER.indexOf(ordered[i])
        ? run.push(ordered[i]) : (runs.push(run), run = [ordered[i]]);
    }
    runs.push(run);
    return runs;
  };

  // ── Polyline helpers ──────────────────────────────────────────────────────
  const runPoints = (run) => {
    const projStart = {
      north: { x: PAD,           y: PAD + cabPxY },
      east:  { x: PAD + roomPxW - cabPxX, y: PAD },
      south: { x: PAD + roomPxW, y: PAD + roomPxH - cabPxY },
      west:  { x: PAD + cabPxX,  y: PAD + roomPxH },
    };
    const projEnd = {
      north: { x: PAD + roomPxW, y: PAD + cabPxY },
      east:  { x: PAD + roomPxW - cabPxX, y: PAD + roomPxH },
      south: { x: PAD,           y: PAD + roomPxH - cabPxY },
      west:  { x: PAD + cabPxX,  y: PAD },
    };
    const pts = [projStart[run[0]]];
    for (let i = 0; i < run.length - 1; i++) pts.push(WALL[run[i]].end);
    pts.push(projEnd[run[run.length - 1]]);
    return pts;
  };
  const dist    = (a, b) => Math.hypot(b.x - a.x, b.y - a.y);
  const polyLen = (pts) => { let l = 0; for (let i = 1; i < pts.length; i++) l += dist(pts[i-1], pts[i]); return l; };
  const pointAt = (pts, d) => {
    let rem = d;
    for (let i = 1; i < pts.length; i++) {
      const s = dist(pts[i-1], pts[i]);
      if (rem <= s) { const t = rem / s; return { x: pts[i-1].x + (pts[i].x - pts[i-1].x) * t, y: pts[i-1].y + (pts[i].y - pts[i-1].y) * t }; }
      rem -= s;
    }
    return pts[pts.length - 1];
  };
  const trimPoly = (pts, s, e) => {
    const total = polyLen(pts), from = s, to = total - e;
    if (from >= to - 0.5) return [];
    const result = [pointAt(pts, from)];
    let cum = 0;
    for (let i = 1; i < pts.length; i++) {
      const seg = dist(pts[i-1], pts[i]), sS = cum, sE = cum + seg;
      if (sE > from && sS < to) result.push(sE <= to ? pts[i] : pointAt(pts, to));
      cum = sE;
    }
    return result;
  };
  const realLen  = (pts) => { let l = 0; for (let i = 1; i < pts.length; i++) l += Math.abs(pts[i].x - pts[i-1].x) / scaleX + Math.abs(pts[i].y - pts[i-1].y) / scaleY; return l; };
  const pxToIn   = (px, axis) => axis === 'h' ? px / scaleX : px / scaleY;
  const runAxis  = (run) => { const ax = run.map(w => WALL[w].perp.x === 0 ? 'h' : 'v'); return ax.every(a => a === 'h') ? 'h' : ax.every(a => a === 'v') ? 'v' : 'mix'; };
  const fmtDim   = (inches) => {
    if (unit === 'mm') return `${Math.round(inches * 25.4)}mm`;
    if (inches <= 0.5) return '0"';
    const ft = Math.floor(inches / 12), i = Math.round(inches % 12);
    return ft > 0 ? `${ft}'-${i}"` : `${i}"`;
  };
  const ptStr    = (pts) => pts.map(p => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');

  const segWall = (p1, p2) => {
    if (Math.abs(p2.y - p1.y) < 2) return (p1.y + p2.y) / 2 < PAD + roomPxH / 2 ? 'north' : 'south';
    return (p1.x + p2.x) / 2 < PAD + roomPxW / 2 ? 'west' : 'east';
  };

  // ── Cabinet rectangle polygons ────────────────────────────────────────────
  const cabRects = (trimmedPts, cpxX, cpxY) => {
    const rects = [];
    for (let i = 1; i < trimmedPts.length; i++) {
      const p1 = trimmedPts[i-1], p2 = trimmedPts[i];
      if (dist(p1, p2) < 0.5) continue;
      const w = segWall(p1, p2);
      const { perp } = WALL[w];
      const dep = perp.x === 0 ? cpxY : cpxX;
      rects.push([p1, p2, { x: p2.x + perp.x * dep, y: p2.y + perp.y * dep }, { x: p1.x + perp.x * dep, y: p1.y + perp.y * dep }]);
    }
    return rects;
  };

  const cornerFills = (trimmedPts, cpxX, cpxY) => {
    const fills = [];
    const cnw = { x: PAD + cpxX,           y: PAD + cpxY };
    const cne = { x: PAD + roomPxW - cpxX, y: PAD + cpxY };
    const cse = { x: PAD + roomPxW - cpxX, y: PAD + roomPxH - cpxY };
    const csw = { x: PAD + cpxX,           y: PAD + roomPxH - cpxY };
    const corners = [
      { pt: cnw, x: PAD,                  y: PAD,                  w: cpxX, h: cpxY },
      { pt: cne, x: PAD + roomPxW - cpxX, y: PAD,                  w: cpxX, h: cpxY },
      { pt: cse, x: PAD + roomPxW - cpxX, y: PAD + roomPxH - cpxY, w: cpxX, h: cpxY },
      { pt: csw, x: PAD,                  y: PAD + roomPxH - cpxY, w: cpxX, h: cpxY },
    ];
    for (let i = 1; i < trimmedPts.length - 1; i++) {
      const p = trimmedPts[i];
      for (const c of corners) {
        if (Math.abs(p.x - c.pt.x) < 3 && Math.abs(p.y - c.pt.y) < 3) fills.push(c);
      }
    }
    return fills;
  };

  // ── CAD dimension annotation ──────────────────────────────────────────────
  const renderSegDim = (p1, p2, accent, faded, key, dimOffset, cpxX, cpxY, p1Jn, p2Jn) => {
    const w = segWall(p1, p2);
    const op = faded ? 0.45 : 1;
    const tick = (x, y) => <line x1={x-3.5} y1={y-3.5} x2={x+3.5} y2={y+3.5} stroke={accent} strokeWidth="1.7" />;
    if (w === 'north' || w === 'south') {
      const snapX = (x) => {
        if (Math.abs(x - (PAD + cpxX))           < 4) return PAD;
        if (Math.abs(x - (PAD + roomPxW - cpxX)) < 4) return PAD + roomPxW;
        return x;
      };
      const x1 = p1Jn ? snapX(p1.x) : p1.x;
      const x2 = p2Jn ? snapX(p2.x) : p2.x;
      const segLenIn = Math.abs(x2 - x1) / scaleX;
      if (segLenIn < 2) return null;
      const wallY = w === 'north' ? PAD : PAD + roomPxH;
      const dimY  = w === 'north' ? PAD - dimOffset : PAD + roomPxH + dimOffset;
      const textY = w === 'north' ? dimY - 8 : dimY + 12;
      return (
        <g key={key} opacity={op}>
          <line x1={x1} y1={wallY} x2={x1} y2={dimY} stroke={accent} strokeWidth="1.0" />
          <line x1={x2} y1={wallY} x2={x2} y2={dimY} stroke={accent} strokeWidth="1.0" />
          <line x1={x1} y1={dimY}  x2={x2} y2={dimY} stroke={accent} strokeWidth="1.2" />
          {tick(x1, dimY)}{tick(x2, dimY)}
          <text x={(x1+x2)/2} y={textY} textAnchor="middle"
            fontFamily="'IBM Plex Mono', monospace" fontSize="11" fill={accent}>{fmtDim(segLenIn)}</text>
        </g>
      );
    } else {
      const snapY = (y) => {
        if (Math.abs(y - (PAD + cpxY))           < 4) return PAD;
        if (Math.abs(y - (PAD + roomPxH - cpxY)) < 4) return PAD + roomPxH;
        return y;
      };
      const y1 = p1Jn ? snapY(p1.y) : p1.y;
      const y2 = p2Jn ? snapY(p2.y) : p2.y;
      const segLenIn = Math.abs(y2 - y1) / scaleY;
      if (segLenIn < 2) return null;
      const wallX = w === 'east' ? PAD + roomPxW : PAD;
      const dimX  = w === 'east' ? PAD + roomPxW + dimOffset : PAD - dimOffset;
      const midY  = (y1 + y2) / 2;
      const txtX  = w === 'east' ? dimX + 10 : dimX - 10;
      return (
        <g key={key} opacity={op}>
          <line x1={wallX} y1={y1} x2={dimX} y2={y1} stroke={accent} strokeWidth="1.0" />
          <line x1={wallX} y1={y2} x2={dimX} y2={y2} stroke={accent} strokeWidth="1.0" />
          <line x1={dimX}  y1={y1} x2={dimX} y2={y2} stroke={accent} strokeWidth="1.2" />
          {tick(dimX, y1)}{tick(dimX, y2)}
          <text x={txtX} y={midY} textAnchor="middle" dominantBaseline="middle"
            fontFamily="'IBM Plex Mono', monospace" fontSize="11" fill={accent}
            transform={`rotate(-90 ${txtX} ${midY})`}>{fmtDim(segLenIn)}</text>
        </g>
      );
    }
  };

  // ── Render boundary dataset ───────────────────────────────────────────────
  const renderBoundary = (dataArray, cpxX, cpxY, accent, hatch, faded, showDims, dimOffset) => {
    if (!dataArray?.length) return null;
    return dataArray.map((seg, si) => {
      if (!seg.pts?.length) return null;
      const rects   = cabRects(seg.pts, cpxX, cpxY);
      const fills   = cornerFills(seg.pts, cpxX, cpxY);
      const dashArr = faded ? '3 2' : undefined;
      const rectOp  = faded ? 0.45 : 1;
      return (
        <g key={si}>
          <g opacity={rectOp}>
            {rects.map((r, ri) => (
              <polygon key={ri} points={ptStr(r)} fill={`url(#${hatch})`} stroke={accent} strokeWidth="1" strokeDasharray={dashArr} />
            ))}
            {fills.map((c, ci) => (
              <rect key={ci} x={c.x} y={c.y} width={c.w} height={c.h} fill={`url(#${hatch})`} stroke={accent} strokeWidth="1" strokeDasharray={dashArr} />
            ))}
            <polyline points={ptStr(seg.pts)} fill="none" stroke={accent} strokeWidth={faded ? 1.2 : 2}
              strokeDasharray={dashArr} strokeLinecap="round" strokeLinejoin="round" />
          </g>
          {showDims && seg.pts.length >= 2 && seg.pts.slice(1).map((p2, ii) =>
            renderSegDim(seg.pts[ii], p2, accent, faded, `${si}_${ii}`, dimOffset, cpxX, cpxY,
              ii > 0, ii < seg.pts.length - 2)
          )}
        </g>
      );
    });
  };

  // ── Opening helpers ───────────────────────────────────────────────────────
  const wallLenIn  = (wall) => (wall === 'north' || wall === 'south') ? lengthIn : widthIn;
  const wallIsHorz = (wall) => wall === 'north' || wall === 'south';
  const wallScale  = (wall) => wallIsHorz(wall) ? scaleX : scaleY;

  // All openings including pending (for rendering)
  const allOpenings = [...openings, ...(pendingOpening ? [pendingOpening] : [])];

  // Opening clash detection — same wall, overlapping effective ranges (opening + clearance on each side)
  const openingClashIds = (() => {
    const ids = new Set();
    for (let i = 0; i < allOpenings.length; i++) {
      for (let j = i + 1; j < allOpenings.length; j++) {
        const a = allOpenings[i], b = allOpenings[j];
        if (a.wall !== b.wall) continue;
        const aS = a.positionIn - (a.clearanceIn||0), aE = a.positionIn + a.widthIn + (a.clearanceIn||0);
        const bS = b.positionIn - (b.clearanceIn||0), bE = b.positionIn + b.widthIn + (b.clearanceIn||0);
        if (aS < bE && aE > bS) { ids.add(a.id); ids.add(b.id); }
      }
    }
    return ids;
  })();
  const pendingOpeningClashes = pendingOpening ? openingClashIds.has(pendingOpening.id) : false;

  // Appliance config: width range, default, color
  const APP_CFG = {
    fridge:     { label: 'Fridge',     color: '#5c82b0', minW: 24, maxW: 48, defW: 36 },
    sink:       { label: 'Sink',       color: '#4a8b6f', minW: 24, maxW: 48, defW: 30 },
    stove:      { label: 'Stove',      color: '#b56b3a', minW: 30, maxW: 36, defW: 30 },
    dishwasher: { label: 'Dishwasher', color: '#8c7ab0', minW: 24, maxW: 30, defW: 24 },
  };
  const APP_TYPES = ['fridge', 'sink', 'stove', 'dishwasher'];
  const allAppliances = [...appliances, ...(pendingAppliance ? [pendingAppliance] : [])];

  // Placement-mode hit zone cues
  const placing = (phaseIsOpenings && (addMode === 'door' || addMode === 'window')) ||
                  (phaseIsAppliances && APP_TYPES.includes(appMode));
  const placeColor = placing
    ? phaseIsOpenings ? (addMode === 'door' ? '#c0703a' : '#5c82b0') : APP_CFG[appMode].color
    : null;

  // Clash detection — two appliances on the same wall with overlapping position ranges
  const clashingIds = (() => {
    const ids = new Set();
    for (let i = 0; i < allAppliances.length; i++) {
      for (let j = i + 1; j < allAppliances.length; j++) {
        const a = allAppliances[i], b = allAppliances[j];
        if (a.wall === b.wall && a.positionIn < b.positionIn + b.widthIn && a.positionIn + a.widthIn > b.positionIn) {
          ids.add(a.id); ids.add(b.id);
        }
      }
    }
    return ids;
  })();
  const pendingClashes = pendingAppliance ? clashingIds.has(pendingAppliance.id) : false;

  const addOpening = (wall) => {
    if (addMode !== 'door' && addMode !== 'window') return;
    const type  = addMode;
    const wLen  = wallLenIn(wall);
    const defW  = type === 'door' ? 30 : 36;
    const defWC = Math.min(defW, wLen);
    const defPos = Math.max(0, Math.min((wLen - defWC) / 2, wLen - defWC));
    setPendingOpening({ id: nextId.current++, type, wall, positionIn: defPos, widthIn: defWC, clearanceIn: 6 });
    setAddMode(null);
  };

  const confirmPendingOpening = () => {
    if (!pendingOpening) return;
    setOpenings(prev => [...prev, pendingOpening]);
    setPendingOpening(null);
  };

  const cancelPending = () => { setPendingOpening(null); setAddMode(null); };

  const updatePendingOpening = (key, rawVal) => {
    setPendingOpening(prev => {
      if (!prev) return null;
      const upd = { ...prev, [key]: +rawVal };
      const wLen = wallLenIn(upd.wall);
      const maxW = upd.type === 'door' ? 36 : 90;
      upd.widthIn     = Math.max(1, Math.min(upd.widthIn, Math.min(maxW, wLen)));
      upd.positionIn  = Math.max(0, Math.min(upd.positionIn, wLen - upd.widthIn));
      upd.clearanceIn = Math.max(0, upd.clearanceIn);
      return upd;
    });
  };

  const deleteOpening = (id) => setOpenings(prev => prev.filter(o => o.id !== id));

  // ── Appliance helpers ─────────────────────────────────────────────────────
  const addAppliance = (wall) => {
    if (!APP_TYPES.includes(appMode)) return;
    const type = appMode;
    const cfg  = APP_CFG[type];
    const wLen = wallLenIn(wall);
    const defW = Math.min(cfg.defW, wLen);
    const defPos = Math.max(0, (wLen - defW) / 2);
    const obj = { id: nextId.current++, type, wall, positionIn: defPos, widthIn: defW };
    if (type === 'stove')  obj.aboveStove = 'hood';
    if (type === 'fridge') obj.heightIn   = 72;
    setPendingAppliance(obj);
    setAppMode(null);
  };

  const confirmPendingAppliance = () => {
    if (!pendingAppliance) return;
    setAppliances(prev => [...prev, pendingAppliance]);
    setPendingAppliance(null);
  };

  const cancelPendingAppliance = () => { setPendingAppliance(null); setAppMode(null); };

  const updatePendingAppliance = (key, rawVal) => {
    setPendingAppliance(prev => {
      if (!prev) return null;
      if (key === 'aboveStove') return { ...prev, aboveStove: rawVal };
      const upd = { ...prev, [key]: +rawVal };
      const wLen = wallLenIn(upd.wall);
      const cfg  = APP_CFG[upd.type];
      upd.widthIn    = Math.max(cfg.minW, Math.min(upd.widthIn, Math.min(cfg.maxW, wLen)));
      upd.positionIn = Math.max(0, Math.min(upd.positionIn, wLen - upd.widthIn));
      return upd;
    });
  };

  const deleteAppliance = (id) => setAppliances(prev => prev.filter(a => a.id !== id));

  // ── Wall line renderer with opening gaps ──────────────────────────────────
  const renderWallLine = (wallId, stroke, sw) => {
    const [x1, y1, x2, y2] = WALL[wallId].outerLine;
    const wallOps = allOpenings.filter(o => o.wall === wallId);
    if (!wallOps.length) {
      return [<line key="full" x1={x1} y1={y1} x2={x2} y2={y2} stroke={stroke} strokeWidth={sw} />];
    }
    const horiz  = wallIsHorz(wallId);
    const sc     = wallScale(wallId);
    const axS    = horiz ? x1 : y1;
    const axE    = horiz ? x2 : y2;
    const gaps   = wallOps.map(o => ({ s: axS + o.positionIn * sc, e: axS + (o.positionIn + o.widthIn) * sc }))
                          .sort((a, b) => a.s - b.s);
    const segs = [];
    let cur = axS;
    for (const g of gaps) {
      if (cur < g.s - 0.5) segs.push([cur, g.s]);
      cur = Math.max(cur, g.e);
    }
    if (cur < axE - 0.5) segs.push([cur, axE]);
    if (!segs.length) return [];
    return segs.map(([a, b], i) =>
      horiz
        ? <line key={i} x1={a} y1={y1} x2={b} y2={y2} stroke={stroke} strokeWidth={sw} />
        : <line key={i} x1={x1} y1={a} x2={x2} y2={b} stroke={stroke} strokeWidth={sw} />
    );
  };

  // ── Opening cuts — erase cabinet hatch in blocked zones ───────────────────
  // cabType: 'base' (doors only) | 'upper' (doors + windows)
  const renderOpeningCuts = (cabType) => {
    const relevant = cabType === 'base'
      ? allOpenings.filter(o => o.type === 'door')
      : allOpenings;
    if (!relevant.length) return null;
    const depthX = cabType === 'base' ? baseCabPxX : upperCabPxX;
    const depthY = cabType === 'base' ? baseCabPxY : upperCabPxY;
    return relevant.map(o => {
      const sc    = wallScale(o.wall);
      const gapA  = PAD + o.positionIn * sc;
      const gapW  = o.widthIn * sc;
      const clrPx = o.clearanceIn * sc;
      let rx, ry, rw, rh;
      if (o.wall === 'north') {
        rx = gapA - clrPx; ry = PAD;          rw = gapW + 2*clrPx; rh = depthY + 2;
      } else if (o.wall === 'south') {
        rx = gapA - clrPx; ry = PAD + roomPxH - depthY - 2; rw = gapW + 2*clrPx; rh = depthY + 2;
      } else if (o.wall === 'east') {
        rx = PAD + roomPxW - depthX - 2; ry = gapA - clrPx; rw = depthX + 2; rh = gapW + 2*clrPx;
      } else {
        rx = PAD; ry = gapA - clrPx; rw = depthX + 2; rh = gapW + 2*clrPx;
      }
      return <rect key={o.id} x={rx} y={ry} width={Math.max(0,rw)} height={Math.max(0,rh)} fill="var(--as-paper)" />;
    });
  };

  // ── Opening symbols (door arc + window glazing + clearance tick lines) ──────
  // Sweep per wall: north=1, south=0, east=1, west=0
  // Hinge at positionIn/"left" end. Clearance shown as dashed boundary lines, not a blob.
  const renderOpeningSymbols = () => {
    if (!allOpenings.length) return null;
    return allOpenings.map(o => {
      const isPending = pendingOpening && o.id === pendingOpening.id;
      const horiz  = wallIsHorz(o.wall);
      const sc     = wallScale(o.wall);
      const gapA   = PAD + o.positionIn * sc;
      const gapW   = o.widthIn * sc;
      const gapB   = gapA + gapW;
      const clrPx  = o.clearanceIn * sc;
      const color  = o.type === 'door' ? '#c0703a' : '#5c82b0';
      const sweep  = (o.wall === 'north' || o.wall === 'east') ? 1 : 0;
      const typeIdx = openings.filter(x => x.type === o.type).indexOf(o) + 1 || '';
      const lbl    = o.type === 'door' ? `D${typeIdx}` : `W${typeIdx}`;

      let wallCoord, inward;
      if (o.wall === 'north')      { wallCoord = PAD;           inward =  1; }
      else if (o.wall === 'south') { wallCoord = PAD + roomPxH; inward = -1; }
      else if (o.wall === 'east')  { wallCoord = PAD + roomPxW; inward = -1; }
      else                         { wallCoord = PAD;           inward =  1; }

      // Clearance boundary lines: dashed, perpendicular to wall, extending to actual cabinet depth.
      // Doors use base-cab depth + ochre, windows use upper-cab depth + sage.
      const clrDepthPx = o.type === 'door'
        ? (horiz ? baseCabPxY : baseCabPxX)
        : (horiz ? upperCabPxY : upperCabPxX);
      const clrColor = o.type === 'door' ? '#c0703a' : '#8c9a86';
      let clrLines, symbol;
      // Label anchored to gap center, fixed offset inward — never moves with clearance
      let labelX, labelY;

      if (horiz) {
        clrLines = clrPx > 2 ? (
          <>
            <line x1={gapA - clrPx} y1={wallCoord} x2={gapA - clrPx} y2={wallCoord + inward * clrDepthPx}
              stroke={clrColor} strokeWidth="0.8" strokeDasharray="2.5 2" opacity="0.5" />
            <line x1={gapB + clrPx} y1={wallCoord} x2={gapB + clrPx} y2={wallCoord + inward * clrDepthPx}
              stroke={clrColor} strokeWidth="0.8" strokeDasharray="2.5 2" opacity="0.5" />
          </>
        ) : null;

        if (o.type === 'door') {
          const arcY = wallCoord + inward * gapW;
          symbol = (
            <>
              {/* Door panel — hinge at gapA, panel shown at 90° open (perpendicular to wall) */}
              <line x1={gapA} y1={wallCoord} x2={gapA} y2={arcY} stroke={color} strokeWidth={isPending ? 1.8 : 1.4} />
              {/* Swing arc — from closed end to open position */}
              <path d={`M ${gapB},${wallCoord} A ${gapW},${gapW} 0 0 ${sweep} ${gapA},${arcY}`}
                fill="none" stroke={color} strokeWidth={isPending ? 1 : 0.75} strokeDasharray="3 2" />
            </>
          );
        } else {
          symbol = (
            <>
              <line x1={gapA} y1={wallCoord - 1.5} x2={gapB} y2={wallCoord - 1.5} stroke={color} strokeWidth={1.2} />
              <line x1={gapA} y1={wallCoord + 1.5} x2={gapB} y2={wallCoord + 1.5} stroke={color} strokeWidth={1.2} />
            </>
          );
        }
        labelX = (gapA + gapB) / 2;
        labelY = wallCoord + inward * 11; // fixed inset, never moves
      } else {
        clrLines = clrPx > 2 ? (
          <>
            <line x1={wallCoord} y1={gapA - clrPx} x2={wallCoord + inward * clrDepthPx} y2={gapA - clrPx}
              stroke={clrColor} strokeWidth="0.8" strokeDasharray="2.5 2" opacity="0.5" />
            <line x1={wallCoord} y1={gapB + clrPx} x2={wallCoord + inward * clrDepthPx} y2={gapB + clrPx}
              stroke={clrColor} strokeWidth="0.8" strokeDasharray="2.5 2" opacity="0.5" />
          </>
        ) : null;

        if (o.type === 'door') {
          const arcX = wallCoord + inward * gapW;
          symbol = (
            <>
              {/* Door panel — hinge at gapA, panel shown at 90° open */}
              <line x1={wallCoord} y1={gapA} x2={arcX} y2={gapA} stroke={color} strokeWidth={isPending ? 1.8 : 1.4} />
              {/* Swing arc */}
              <path d={`M ${wallCoord},${gapB} A ${gapW},${gapW} 0 0 ${sweep} ${arcX},${gapA}`}
                fill="none" stroke={color} strokeWidth={isPending ? 1 : 0.75} strokeDasharray="3 2" />
            </>
          );
        } else {
          symbol = (
            <>
              <line x1={wallCoord - 1.5} y1={gapA} x2={wallCoord - 1.5} y2={gapB} stroke={color} strokeWidth={1.2} />
              <line x1={wallCoord + 1.5} y1={gapA} x2={wallCoord + 1.5} y2={gapB} stroke={color} strokeWidth={1.2} />
            </>
          );
        }
        labelX = wallCoord + inward * 11;
        labelY = (gapA + gapB) / 2;
      }

      const isOpeningClash = openingClashIds.has(o.id);
      let clx, cly, clw, clh;
      if (horiz) {
        clx = gapA - clrPx; clw = gapW + 2*clrPx;
        cly = inward > 0 ? wallCoord : wallCoord - clrDepthPx; clh = clrDepthPx;
      } else {
        cly = gapA - clrPx; clh = gapW + 2*clrPx;
        clx = inward > 0 ? wallCoord : wallCoord - clrDepthPx; clw = clrDepthPx;
      }

      return (
        <g key={o.id} opacity={isPending ? 1 : 0.88}>
          {clrLines}
          {symbol}
          {isOpeningClash && <rect x={clx} y={cly} width={Math.max(0,clw)} height={Math.max(0,clh)} fill="rgba(168,72,60,0.08)" stroke="var(--as-error)" strokeWidth="1.5" />}
          <text x={labelX} y={labelY} textAnchor="middle" dominantBaseline="middle"
            fontFamily="'IBM Plex Mono', monospace" fontSize="8.5" fill={color} fontWeight="600">
            {isPending ? (o.type === 'door' ? 'D' : 'W') : lbl}
          </text>
        </g>
      );
    });
  };

  // ── Wall dimension chain ────────────────────────────────────────────────
  // One continuous dim string per wall, anchored wall-corner to wall-corner.
  // Gap segments: neutral gray. Component segments: their own color.
  // Labels are dropped if the segment pixel width is too narrow to fit text.
  const renderWallChain = (wallId, comps, getColor, DL, DT) => {
    const horiz   = wallIsHorz(wallId);
    const sc      = wallScale(wallId);
    const wLen    = wallLenIn(wallId);
    const axS     = PAD;
    const axE     = PAD + (horiz ? roomPxW : roomPxH);
    const MIN_LBL = 18;
    const GAP_CLR = '#4e606f';

    const wallPx  = horiz
      ? (wallId === 'north' ? PAD : PAD + roomPxH)
      : (wallId === 'east'  ? PAD + roomPxW : PAD);
    const dir     = (wallId === 'north' || wallId === 'west') ? -1 : 1;
    const chainPx = wallPx + dir * DL;
    const effectiveDT = dir > 0 ? Math.max(DT, DL + 13) : DT;
    const textPx  = wallPx + dir * effectiveDT;

    // Build ordered segment list: gaps interleaved with components
    const sorted = [...comps].sort((a, b) => a.positionIn - b.positionIn);
    const segs = [];
    let cursor = 0;
    for (const c of sorted) {
      if (c.positionIn > cursor + 0.5)
        segs.push({ startIn: cursor, endIn: c.positionIn, isComp: false, comp: null });
      segs.push({ startIn: c.positionIn, endIn: c.positionIn + c.widthIn, isComp: true, comp: c });
      cursor = c.positionIn + c.widthIn;
    }
    if (cursor < wLen - 0.5)
      segs.push({ startIn: cursor, endIn: wLen, isComp: false, comp: null });

    // Pixel boundary points along the wall axis
    const bpts = [axS, ...segs.map(s => axS + s.endIn * sc)];
    const tk   = (bp) => horiz
      ? <line x1={bp} y1={chainPx - 3.5} x2={bp} y2={chainPx + 3.5} stroke="rgba(44,62,80,0.80)" strokeWidth="1.4" />
      : <line x1={chainPx - 3.5} y1={bp} x2={chainPx + 3.5} y2={bp} stroke="rgba(44,62,80,0.80)" strokeWidth="1.4" />;

    return (
      <g key={wallId}>
        {/* Backbone */}
        {horiz
          ? <line x1={axS} y1={chainPx} x2={axE} y2={chainPx} stroke="rgba(44,62,80,0.45)" strokeWidth="1.0" />
          : <line x1={chainPx} y1={axS} x2={chainPx} y2={axE} stroke="rgba(44,62,80,0.45)" strokeWidth="1.0" />
        }
        {/* Ticks at every segment boundary */}
        {bpts.map((bp, i) => <React.Fragment key={i}>{tk(bp)}</React.Fragment>)}
        {/* Segment labels — skipped if segment too narrow */}
        {segs.map((seg, i) => {
          const a   = axS + seg.startIn * sc;
          const b   = axS + seg.endIn   * sc;
          const mid = (a + b) / 2;
          if (b - a < MIN_LBL) return null;
          const lIn   = seg.endIn - seg.startIn;
          const color = seg.isComp ? getColor(seg.comp) : GAP_CLR;
          const fs    = seg.isComp ? '10.5' : '9.5';
          const op    = seg.isComp ? 1 : 0.88;
          return horiz
            ? <text key={i} x={mid} y={textPx} textAnchor="middle"
                fontFamily="'IBM Plex Mono', monospace" fontSize={fs} fill={color} opacity={op}>
                {fmtDim(lIn)}
              </text>
            : <text key={i} x={textPx} y={mid} textAnchor="middle" dominantBaseline="middle"
                fontFamily="'IBM Plex Mono', monospace" fontSize={fs} fill={color} opacity={op}
                transform={`rotate(-90,${textPx},${mid})`}>
                {fmtDim(lIn)}
              </text>;
        })}
      </g>
    );
  };

  // ── Opening dimension chains (one per wall, live during placement) ────────
  const renderOpeningDims = () => {
    if (!allOpenings.length) return null;
    const DL = 13, DT = 24;
    return ORDER.map(wallId => {
      const wallComps = allOpenings.filter(o => o.wall === wallId);
      if (!wallComps.length) return null;
      return renderWallChain(wallId, wallComps, (o) => o.type === 'door' ? '#c0703a' : '#5c82b0', DL, DT);
    });
  };

  // ── Appliance cuts — erase cabinet hatch in blocked zones ────────────────
  // base: fridge + stove  /  upper: stove (unless aboveStove === 'cabinets')
  const renderApplianceCuts = (cabType) => {
    const relevant = cabType === 'base'
      ? allAppliances.filter(a => a.type === 'fridge' || a.type === 'stove')
      : allAppliances.filter(a => (a.type === 'stove' && a.aboveStove !== 'cabinets') || a.type === 'fridge');
    if (!relevant.length) return null;
    const depthX = cabType === 'base' ? baseCabPxX : upperCabPxX;
    const depthY = cabType === 'base' ? baseCabPxY : upperCabPxY;
    return relevant.map(a => {
      const sc = wallScale(a.wall);
      const gapA = PAD + a.positionIn * sc;
      const gapW = a.widthIn * sc;
      let rx, ry, rw, rh;
      if (a.wall === 'north')      { rx = gapA; ry = PAD;                     rw = gapW; rh = depthY + 2; }
      else if (a.wall === 'south') { rx = gapA; ry = PAD + roomPxH - depthY - 2; rw = gapW; rh = depthY + 2; }
      else if (a.wall === 'east')  { rx = PAD + roomPxW - depthX - 2; ry = gapA; rw = depthX + 2; rh = gapW; }
      else                         { rx = PAD;                          ry = gapA; rw = depthX + 2; rh = gapW; }
      return <rect key={a.id + cabType} x={rx} y={ry} width={Math.max(0,rw)} height={Math.max(0,rh)} fill="var(--as-paper)" />;
    });
  };

  // ── Appliance floor-plan symbols ──────────────────────────────────────────
  const renderApplianceSymbols = () => {
    if (!allAppliances.length) return null;
    return allAppliances.map(a => {
      const isPending = pendingAppliance && a.id === pendingAppliance.id;
      const sc    = wallScale(a.wall);
      const horiz = wallIsHorz(a.wall);
      const gapA  = PAD + a.positionIn * sc;
      const gapW  = a.widthIn * sc;
      const cfg   = APP_CFG[a.type];

      // Body rect: appliance occupies the base cab depth band
      const depthPx  = horiz ? baseCabPxY : baseCabPxX;
      const uDepthPx = horiz ? upperCabPxY : upperCabPxX;
      let rx, ry, rw, rh;
      if (a.wall === 'north')      { rx = gapA; ry = PAD;                      rw = gapW; rh = depthPx; }
      else if (a.wall === 'south') { rx = gapA; ry = PAD + roomPxH - depthPx;  rw = gapW; rh = depthPx; }
      else if (a.wall === 'east')  { rx = PAD + roomPxW - depthPx; ry = gapA;  rw = depthPx; rh = gapW; }
      else                         { rx = PAD;                      ry = gapA;  rw = depthPx; rh = gapW; }
      const mx = rx + rw / 2, my = ry + rh / 2;
      const sw = isPending ? 1.5 : 1.2;

      let symbol;

      if (a.type === 'fridge') {
        // Fridge takes full height — both base and upper cleared, body spans base cab depth
        symbol = (
          <>
            <rect x={rx} y={ry} width={rw} height={rh}
              fill="rgba(92,130,176,0.10)" stroke={cfg.color} strokeWidth={sw} />
            {horiz
              ? <line x1={gapA+3} y1={ry} x2={gapA+3} y2={ry+rh*0.88} stroke={cfg.color} strokeWidth="0.8" />
              : <line x1={rx} y1={gapA+3} x2={rx+rw*0.88} y2={gapA+3} stroke={cfg.color} strokeWidth="0.8" />}
            <text x={mx} y={my} textAnchor="middle" dominantBaseline="middle"
              fontFamily="'IBM Plex Mono', monospace" fontSize="8" fill={cfg.color} fontWeight="600">REF</text>
          </>
        );

      } else if (a.type === 'sink') {
        // Faucet T-shape at the wall edge, pointing inward
        const fLen = Math.min(rw, rh) * 0.2;
        let faucet;
        if (a.wall === 'north') {
          faucet = <><line x1={mx} y1={ry} x2={mx} y2={ry+fLen} stroke={cfg.color} strokeWidth="1.3" />
            <line x1={mx-fLen*0.45} y1={ry+fLen*0.5} x2={mx+fLen*0.45} y2={ry+fLen*0.5} stroke={cfg.color} strokeWidth="1.3" /></>;
        } else if (a.wall === 'south') {
          faucet = <><line x1={mx} y1={ry+rh} x2={mx} y2={ry+rh-fLen} stroke={cfg.color} strokeWidth="1.3" />
            <line x1={mx-fLen*0.45} y1={ry+rh-fLen*0.5} x2={mx+fLen*0.45} y2={ry+rh-fLen*0.5} stroke={cfg.color} strokeWidth="1.3" /></>;
        } else if (a.wall === 'east') {
          faucet = <><line x1={rx+rw} y1={my} x2={rx+rw-fLen} y2={my} stroke={cfg.color} strokeWidth="1.3" />
            <line x1={rx+rw-fLen*0.5} y1={my-fLen*0.45} x2={rx+rw-fLen*0.5} y2={my+fLen*0.45} stroke={cfg.color} strokeWidth="1.3" /></>;
        } else {
          faucet = <><line x1={rx} y1={my} x2={rx+fLen} y2={my} stroke={cfg.color} strokeWidth="1.3" />
            <line x1={rx+fLen*0.5} y1={my-fLen*0.45} x2={rx+fLen*0.5} y2={my+fLen*0.45} stroke={cfg.color} strokeWidth="1.3" /></>;
        }
        symbol = (
          <>
            <rect x={rx} y={ry} width={rw} height={rh}
              fill="rgba(74,139,111,0.06)" stroke={cfg.color} strokeWidth={sw} />
            <rect x={rx+rw*0.08} y={ry+rh*0.08} width={rw*0.84} height={rh*0.84}
              rx="3" ry="3" fill="rgba(74,139,111,0.14)" stroke={cfg.color} strokeWidth="0.9" />
            <circle cx={mx} cy={my} r={1.8} fill={cfg.color} />
            {faucet}
          </>
        );

      } else if (a.type === 'stove') {
        const br = Math.min(rw, rh) * 0.11;
        const bp = [
          [rx + rw*0.3, ry + rh*0.3], [rx + rw*0.7, ry + rh*0.3],
          [rx + rw*0.3, ry + rh*0.7], [rx + rw*0.7, ry + rh*0.7],
        ];
        // Hood/microwave occupies the wall-side of the cabinet zone (upper cab band).
        // For south/east the body rect starts at the inner edge, so we anchor from the wall.
        const hoodY = a.wall === 'south' ? PAD + roomPxH - uDepthPx : ry;
        const hoodX = a.wall === 'east'  ? PAD + roomPxW - uDepthPx : rx;
        let aboveEl = null;
        if (a.aboveStove === 'hood') {
          const shrink = gapW * 0.08;
          const wallEdge_y = a.wall === 'north' ? hoodY : hoodY + uDepthPx;
          const innerEdge_y = a.wall === 'north' ? hoodY + uDepthPx : hoodY;
          const wallEdge_x = a.wall === 'west' ? hoodX : hoodX + uDepthPx;
          const innerEdge_x = a.wall === 'west' ? hoodX + uDepthPx : hoodX;
          aboveEl = horiz
            ? <polygon
                points={`${gapA},${wallEdge_y} ${gapA+gapW},${wallEdge_y} ${gapA+gapW-shrink},${innerEdge_y} ${gapA+shrink},${innerEdge_y}`}
                fill="rgba(181,107,58,0.10)" stroke={cfg.color} strokeWidth="0.8" strokeDasharray="2 2" />
            : <polygon
                points={`${wallEdge_x},${gapA} ${wallEdge_x},${gapA+gapW} ${innerEdge_x},${gapA+gapW-shrink} ${innerEdge_x},${gapA+shrink}`}
                fill="rgba(181,107,58,0.10)" stroke={cfg.color} strokeWidth="0.8" strokeDasharray="2 2" />;
        } else if (a.aboveStove === 'microwave') {
          aboveEl = horiz
            ? <rect x={gapA+gapW*0.05} y={hoodY} width={gapW*0.9} height={uDepthPx}
                fill="rgba(181,107,58,0.10)" stroke={cfg.color} strokeWidth="0.8" />
            : <rect x={hoodX} y={gapA+gapW*0.05} width={uDepthPx} height={gapW*0.9}
                fill="rgba(181,107,58,0.10)" stroke={cfg.color} strokeWidth="0.8" />;
        }
        symbol = (
          <>
            {aboveEl}
            <rect x={rx} y={ry} width={rw} height={rh}
              fill="rgba(181,107,58,0.08)" stroke={cfg.color} strokeWidth={sw} />
            {bp.map(([bx, by], i) =>
              <circle key={i} cx={bx} cy={by} r={Math.max(br, 2.5)} fill="none" stroke={cfg.color} strokeWidth="0.9" />)}
          </>
        );

      } else { // dishwasher
        symbol = (
          <>
            <rect x={rx} y={ry} width={rw} height={rh}
              fill="rgba(140,122,176,0.08)" stroke={cfg.color} strokeWidth={sw} />
            <text x={mx} y={my} textAnchor="middle" dominantBaseline="middle"
              fontFamily="'IBM Plex Mono', monospace" fontSize="8" fill={cfg.color} fontWeight="600">DW</text>
          </>
        );
      }

      const isClash = clashingIds.has(a.id);
      return (
        <g key={a.id} opacity={isPending ? 1 : 0.9}>
          {symbol}
          {isClash && <rect x={rx} y={ry} width={rw} height={rh}
            fill="rgba(168,72,60,0.10)" stroke="var(--as-error)" strokeWidth="1.5" />}
        </g>
      );
    });
  };

  // ── Active cabinet segments ───────────────────────────────────────────────
  const runs     = getRuns();
  const segments = runs.map(run => {
    const id      = run.join('+');
    const pts     = runPoints(run);
    const totalPx = polyLen(pts);
    const ax      = runAxis(run);
    const trim    = trims[id] || { s: 0, e: 0 };
    const trimmed = trimPoly(pts, trim.s, trim.e);
    const totalIn = realLen(pts);
    const sIn     = pxToIn(trim.s, ax === 'mix' ? 'h' : ax);
    const eIn     = pxToIn(trim.e, ax === 'mix' ? 'h' : ax);
    const activeIn = Math.max(0, totalIn - sIn - eIn);
    return { id, run, pts, totalPx, totalIn, ax, trim, trimmed, sIn, eIn, activeIn };
  });

  const setTrim = (id, key, val) =>
    setTrims(prev => ({ ...prev, [id]: { ...(prev[id] || { s: 0, e: 0 }), [key]: +val } }));

  // ── Appliance dimension chains (one per wall, live during placement) ──────
  const renderApplianceDims = () => {
    if (!allAppliances.length) return null;
    const DL = 13, DT = 24;
    return ORDER.map(wallId => {
      const wallComps = allAppliances.filter(a => a.wall === wallId);
      if (!wallComps.length) return null;
      return renderWallChain(wallId, wallComps, (a) => APP_CFG[a.type].color, DL, DT);
    });
  };

  // ── Phase transitions ─────────────────────────────────────────────────────
  const confirm = () => {
    const data = segments.map(s => ({ run: s.run, pts: s.trimmed, lengthIn: s.activeIn }));
    if (phaseIsBase) {
      setBaseData(data);
      setSelWalls(new Set()); setTrims({});
      setPhase('upper');
    } else {
      setUpperData(data);
      setPhase('openings');
    }
  };
  const skipUpper = () => { setUpperData([]); setPhase('openings'); };
  const confirmOpenings = () => { setPhase('appliances'); };
  const confirmAppliances = () => {
    onComplete?.(baseData, upperData || [], openings, appliances);
    setPhase('done');
  };

  // ── Step indicator ────────────────────────────────────────────────────────
  const stepState = (n) => {
    if (phaseIsDone) return 'done';
    if (n === 1) return 'done';
    if (n === 2) return (phaseIsUpper || phaseIsOpenings || phaseIsAppliances) ? 'done' : phaseIsBase ? 'active' : 'upcoming';
    if (n === 3) return (phaseIsOpenings || phaseIsAppliances) ? 'done' : phaseIsUpper ? 'active' : 'upcoming';
    if (n === 4) return phaseIsAppliances ? 'done' : phaseIsOpenings ? 'active' : 'upcoming';
    if (n === 5) return phaseIsAppliances ? 'active' : 'upcoming';
    return 'upcoming';
  };
  const STEPS = [
    { n: 1, label: 'Room' },
    { n: 2, label: 'Base Cabinets' },
    { n: 3, label: 'Upper Cabinets' },
    { n: 4, label: 'Openings' },
    { n: 5, label: 'Appliances' },
  ];

  // ── Render ────────────────────────────────────────────────────────────────
  return (
    <div ref={canvasRef}>

      {/* Sticky canvas header */}
      <div className="as-canvas-header">

      {/* Step indicator */}
      <div style={{ display: 'flex', alignItems: 'center', gap: '0', padding: '10px 0', borderBottom: '1px solid var(--border-hairline)' }}>
        {STEPS.map((step, idx) => {
          const st = stepState(step.n);
          const isDone   = st === 'done';
          const isActive = st === 'active';
          const isNext   = !isDone && !isActive && idx > 0 && stepState(STEPS[idx-1].n) !== 'upcoming';
          return (
            <React.Fragment key={step.n}>
              {idx > 0 && (
                <div style={{ flex: 1, height: '1px', background: isDone ? 'var(--as-success)' : 'var(--border-default)', minWidth: '16px' }} />
              )}
              <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '5px' }}>
                <div style={{ width: '22px', height: '22px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-mono)', fontSize: '9px', flexShrink: 0,
                  background: isDone ? 'var(--as-success)' : isActive ? 'var(--as-navy)' : 'var(--border-default)',
                  color: (isDone || isActive) ? '#fff' : 'var(--text-muted)',
                  border: isNext ? '1px dashed var(--text-muted)' : 'none',
                }}>
                  {isDone ? '✓' : step.n}
                </div>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: '8.5px', letterSpacing: '0.06em', fontWeight: isActive ? '500' : '400', whiteSpace: 'nowrap', textTransform: 'uppercase',
                  color: isDone ? 'var(--as-success)' : isActive ? 'var(--text-primary)' : 'var(--text-muted)',
                }}>
                  {step.label}
                </span>
              </div>
            </React.Fragment>
          );
        })}
      </div>

      {/* Phase tab */}
      {(phaseIsBase || phaseIsUpper) && (
        <div style={{ display: 'flex', gap: '1px', background: 'var(--border-default)', marginTop: '12px' }}>
          {[['base', 'BASE BOUNDARY'], ['upper', 'UPPER BOUNDARY']].map(([p, lbl]) => (
            <div key={p} style={{ flex: 1, padding: '8px 16px', textAlign: 'center',
              background: phase === p ? 'var(--as-navy)' : 'var(--surface-card)',
              fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.12em',
              color: phase === p ? 'var(--as-mist)' : 'var(--text-muted)',
            }}>{lbl}</div>
          ))}
        </div>
      )}
      {phaseIsOpenings && (
        <div style={{ display: 'flex', gap: '1px', background: 'var(--border-default)', marginTop: '12px' }}>
          <div style={{ flex: 1, padding: '8px 16px', textAlign: 'center', background: 'var(--as-navy)',
            fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.12em', color: 'var(--as-mist)',
          }}>DOORS & WINDOWS</div>
        </div>
      )}
      {phaseIsAppliances && (
        <div style={{ display: 'flex', gap: '1px', background: 'var(--border-default)', marginTop: '12px' }}>
          <div style={{ flex: 1, padding: '8px 16px', textAlign: 'center', background: 'var(--as-navy)',
            fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.12em', color: 'var(--as-mist)',
          }}>APPLIANCES</div>
        </div>
      )}

      {/* Instruction */}
      {(phaseIsBase || phaseIsUpper) && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-text)', fontSize: 'var(--text-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
          Click the walls where <strong style={{ color: 'var(--text-primary)', fontWeight: 'normal' }}>{phaseIsBase ? 'base' : 'upper'} cabinets</strong> will run.
          Use sliders to trim.{phaseIsUpper && ' Ochre = confirmed base.'}
        </p>
      )}
      {phaseIsOpenings && !pendingOpening && addMode === null && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-text)', fontSize: 'var(--text-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
          Add doors and windows — they define cabinet-free zones on each wall.
        </p>
      )}
      {phaseIsOpenings && (addMode === 'door' || addMode === 'window') && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.06em', color: addMode === 'door' ? '#c0703a' : '#5c82b0', lineHeight: 1.6 }}>
          ← click a wall to place the {addMode}
        </p>
      )}
      {phaseIsOpenings && pendingOpening && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-text)', fontSize: 'var(--text-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
          Adjust position and size, then confirm to lock it in.
        </p>
      )}
      {phaseIsAppliances && !pendingAppliance && !appMode && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-text)', fontSize: 'var(--text-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
          Place appliances against walls. Fridge clears full height (base + upper). Stove clears both layers.
        </p>
      )}
      {phaseIsAppliances && appMode && APP_TYPES.includes(appMode) && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.06em', color: APP_CFG[appMode].color, lineHeight: 1.6 }}>
          ← click a wall to place the {APP_CFG[appMode].label.toLowerCase()}
        </p>
      )}
      {phaseIsAppliances && pendingAppliance && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-text)', fontSize: 'var(--text-xs)', color: 'var(--text-muted)', lineHeight: 1.6 }}>
          Adjust position and size, then confirm.
        </p>
      )}
      {phaseIsDone && (
        <p style={{ margin: '8px 0 0', fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--as-success)', letterSpacing: '0.08em' }}>
          ✓ Layout confirmed — continue below.
        </p>
      )}

      </div>{/* /as-canvas-header */}

      {/* ─── SVG ─────────────────────────────────────────────────────────── */}
      <div style={{ border: '1px solid var(--border-default)', background: '#fff', touchAction: 'manipulation' }}>
        <svg viewBox={`0 0 ${SVG_W} ${SVG_H}`} style={{ display: 'block', width: '100%' }}>
          <defs>
            <pattern id="hatch-base"       patternUnits="userSpaceOnUse" width="7" height="7" patternTransform="rotate(45)">
              <line x1="0" y1="0" x2="0" y2="7" stroke="#c0703a" strokeWidth="0.8" opacity="0.55" />
            </pattern>
            <pattern id="hatch-upper"      patternUnits="userSpaceOnUse" width="7" height="7" patternTransform="rotate(45)">
              <line x1="0" y1="0" x2="0" y2="7" stroke="#8c9a86" strokeWidth="0.8" opacity="0.55" />
            </pattern>
            <pattern id="hatch-base-ghost" patternUnits="userSpaceOnUse" width="7" height="7" patternTransform="rotate(45)">
              <line x1="0" y1="0" x2="0" y2="7" stroke="#c0703a" strokeWidth="0.6" opacity="0.3" />
            </pattern>
          </defs>

          {/* 1 — Room fill */}
          <rect x={PAD} y={PAD} width={roomPxW} height={roomPxH} fill="var(--as-paper)" />

          {/* 2 — Cabinet depth guide (base/upper phases only) */}
          {(phaseIsBase || phaseIsUpper) && (
            <rect x={NW.x} y={NW.y} width={SE.x - NW.x} height={SE.y - NW.y}
              fill="none" stroke="var(--border-default)" strokeWidth="0.4" strokeDasharray="3 4" />
          )}

          {/* 3 — Confirmed BASE + opening/appliance cuts */}
          {(phaseIsUpper || phaseIsOpenings || phaseIsAppliances || phaseIsDone) && renderBoundary(baseData, baseCabPxX, baseCabPxY, '#c0703a',
            phaseIsUpper ? 'hatch-base-ghost' : 'hatch-base', phaseIsUpper, phaseIsUpper, 32)}
          {(phaseIsOpenings || phaseIsAppliances || phaseIsDone) && renderOpeningCuts('base')}
          {(phaseIsAppliances || phaseIsDone) && renderApplianceCuts('base')}

          {/* 4 — Confirmed UPPER + cuts */}
          {(phaseIsOpenings || phaseIsAppliances || phaseIsDone) && renderBoundary(upperData, upperCabPxX, upperCabPxY, '#8c9a86', 'hatch-upper', false, false, 14)}
          {(phaseIsOpenings || phaseIsAppliances || phaseIsDone) && renderOpeningCuts('upper')}
          {(phaseIsAppliances || phaseIsDone) && renderApplianceCuts('upper')}

          {/* 5 — Active cabinet segments (base / upper phases) */}
          {(phaseIsBase || phaseIsUpper) && segments.map(s => {
            if (s.trimmed.length < 2) return null;
            const rects = cabRects(s.trimmed, cabPxX, cabPxY);
            const fills = cornerFills(s.trimmed, cabPxX, cabPxY);
            return (
              <g key={s.id}>
                {rects.map((r, ri) => <polygon key={ri} points={ptStr(r)} fill={`url(#${hatchId})`} stroke={phaseAccent} strokeWidth="1" />)}
                {fills.map((c, ci) => <rect key={ci} x={c.x} y={c.y} width={c.w} height={c.h} fill={`url(#${hatchId})`} stroke={phaseAccent} strokeWidth="1" />)}
                <polyline points={ptStr(s.trimmed)} fill="none" stroke={phaseAccent} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
                {s.trimmed.slice(1).map((p2, ii) =>
                  renderSegDim(s.trimmed[ii], p2, phaseAccent, false, `${s.id}_${ii}`, 14, cabPxX, cabPxY,
                    ii > 0, ii < s.trimmed.length - 2)
                )}
              </g>
            );
          })}

          {/* 6 — Wall lines (drawn after cabs so wall covers cut rect edges) */}
          {ORDER.map(id => <g key={id}>{renderWallLine(id, '#2c3e50', 1.5)}</g>)}

          {/* 7 — Opening symbols always visible; dims only during openings phase (PDF carries all dims) */}
          {renderOpeningSymbols()}
          {phaseIsOpenings && renderOpeningDims()}

          {/* 7b — Appliance symbols + dims */}
          {(phaseIsAppliances || phaseIsDone) && renderApplianceSymbols()}
          {(phaseIsAppliances || phaseIsDone) && renderApplianceDims()}

          {/* 8 — Interactive wall hit zones */}
          {(phaseIsBase || phaseIsUpper || phaseIsOpenings || phaseIsAppliances) && ORDER.map(id => {
            const w   = WALL[id];
            const sel = selWalls.has(id);
            const lp  = wallLbl[id];
            const hit = placing ? PLACE_HIT[id] : w.hit;
            const wallClickable = phaseIsOpenings
              ? (addMode === 'door' || addMode === 'window')
              : phaseIsAppliances
                ? APP_TYPES.includes(appMode)
                : true;
            return (
              <g key={id} style={{ cursor: wallClickable ? 'pointer' : 'default' }}
                onClick={() => {
                  if (phaseIsOpenings && (addMode === 'door' || addMode === 'window')) addOpening(id);
                  else if (phaseIsAppliances && APP_TYPES.includes(appMode)) addAppliance(id);
                  else if (!phaseIsOpenings && !phaseIsAppliances) toggleWall(id);
                }}>
                <rect x={hit.x} y={hit.y} width={hit.w} height={hit.h}
                  fill={placing ? placeColor : sel ? (phaseIsBase ? '#c0703a' : '#8c9a86') : 'transparent'}
                  fillOpacity={placing ? 0.12 : sel ? 0.06 : 0}
                  stroke={placing ? placeColor : 'none'}
                  strokeOpacity={placing ? 0.40 : 0}
                  strokeWidth={placing ? "1" : "0"}
                  strokeDasharray={placing ? "5 3" : undefined}
                />
                <text x={lp.x} y={lp.y} textAnchor="middle" dominantBaseline="middle"
                  fontFamily="'IBM Plex Mono', monospace"
                  fontSize={placing ? "12" : "11"}
                  fill={placing ? placeColor : sel && !phaseIsOpenings ? phaseAccent : '#b0b8bf'}
                  fontWeight={placing ? '700' : sel && !phaseIsOpenings ? '600' : '400'}
                  opacity={placing ? 0.85 : 1}>
                  {id[0].toUpperCase()}
                </text>
              </g>
            );
          })}

          {/* Room label */}
          <text x={PAD + roomPxW / 2} y={PAD + roomPxH / 2 - 7} textAnchor="middle"
            fontFamily="'IBM Plex Mono', monospace" fontSize="10" fill="#b8bfc5">{fmtDim(lengthIn)}</text>
          <text x={PAD + roomPxW / 2} y={PAD + roomPxH / 2 + 8} textAnchor="middle"
            fontFamily="'IBM Plex Mono', monospace" fontSize="10" fill="#b8bfc5">{fmtDim(widthIn)}</text>
        </svg>
      </div>

      {/* ─── Cabinet trim sliders ─────────────────────────────────────────── */}
      {(phaseIsBase || phaseIsUpper) && segments.length > 0 && (
        <div style={{ marginTop: '12px', display: 'flex', flexDirection: 'column', gap: '10px' }}>
          {segments.map(s => (
            <div key={s.id} style={{ padding: '14px 16px', background: 'var(--surface-card)', border: '1px solid var(--border-default)' }}>
              <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.10em', color: phaseIsBase ? 'var(--as-ochre-ink)' : '#566150', marginBottom: '10px' }}>
                {s.run.map(w => w[0].toUpperCase()).join('+')} wall{s.run.length > 1 ? 's' : ''} — {fmtDim(s.activeIn)} active
              </div>
              <div className="as-slider-grid">
                {[['s', 'Trim start', s.sIn], ['e', 'Trim end', s.eIn]].map(([key, lbl, valIn]) => (
                  <div key={key}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '5px' }}>
                      <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)' }}>{lbl}</span>
                      <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-primary)' }}>{fmtDim(valIn)}</span>
                    </div>
                    <input type="range" min="0" max={s.totalPx * 0.5} step="0.5"
                      value={s.trim[key]}
                      onChange={e => setTrim(s.id, key, e.target.value)}
                      style={{ width: '100%', accentColor: phaseAccent }} />
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      )}

      {/* ─── Openings controls ───────────────────────────────────────────── */}
      {phaseIsOpenings && (
        <div style={{ marginTop: '12px' }}>

          {/* Pending opening — Apply/Cancel pinned to top of panel */}
          {pendingOpening && (() => {
            const color  = pendingOpening.type === 'door' ? '#c0703a' : '#5c82b0';
            const wLenIn = wallLenIn(pendingOpening.wall);
            return (
              <div style={{ padding: '16px', background: 'var(--surface-card)', border: `1px solid ${color}`, marginBottom: '8px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' }}>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.10em', color }}>
                    {pendingOpening.type} — {pendingOpening.wall} wall
                  </div>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button onClick={confirmPendingOpening} style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', background: color, color: '#fff', border: 'none', padding: '5px 12px', cursor: 'pointer' }}>Apply</button>
                    <button onClick={cancelPending} style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', background: 'transparent', border: '1px solid var(--border-default)', color: 'var(--text-muted)', padding: '5px 10px', cursor: 'pointer' }}>Cancel</button>
                  </div>
                </div>
                {pendingOpeningClashes && (
                  <div style={{ padding: '8px 12px', background: 'rgba(168,72,60,0.08)', border: '1px solid var(--as-error)', fontFamily: 'var(--font-mono)', fontSize: '9.5px', color: 'var(--as-error)', letterSpacing: '0.04em', marginBottom: '14px' }}>
                    ⚠ Overlaps another opening on this wall — adjust position or width.
                  </div>
                )}
                <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
                  {[
                    { key: 'positionIn',  label: 'Position from corner', min: 0,  max: Math.max(0, wLenIn - pendingOpening.widthIn), val: pendingOpening.positionIn },
                    { key: 'widthIn',     label: 'Zone width',           min: 12, max: Math.min(pendingOpening.type === 'door' ? 36 : 90, wLenIn), val: pendingOpening.widthIn },
                    { key: 'clearanceIn', label: 'Clearance each side',  min: 0,  max: 24,                                           val: pendingOpening.clearanceIn },
                  ].map(({ key, label, min, max, val }) => (
                    <div key={key}>
                      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '5px' }}>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)' }}>{label}</span>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-primary)' }}>{fmtDim(val)}</span>
                      </div>
                      <input type="range" min={min} max={max} step={0.5} value={val}
                        onChange={e => updatePendingOpening(key, +e.target.value)}
                        style={{ width: '100%', accentColor: color }} />
                    </div>
                  ))}
                </div>
              </div>
            );
          })()}

          {/* Door / Window selector — always visible when no pending */}
          {!pendingOpening && (
            <div style={{ display: 'flex', gap: '8px', marginBottom: openings.length > 0 ? '8px' : '0' }}>
              {[['door', '#c0703a', 'Door', 'base + upper cabs'], ['window', '#5c82b0', 'Window', 'upper cabs only']].map(([type, color, lbl, sub]) => (
                <button key={type} onClick={() => setAddMode(addMode === type ? null : type)} style={{
                  flex: 1, padding: '12px 16px', cursor: 'pointer', textAlign: 'left',
                  background: addMode === type ? color : 'transparent',
                  border: `1px solid ${color}`,
                  fontFamily: 'var(--font-mono)', fontSize: '10px',
                }}>
                  <div style={{ color: addMode === type ? '#fff' : color, fontWeight: '600', marginBottom: '3px' }}>{lbl}</div>
                  <div style={{ color: addMode === type ? 'rgba(255,255,255,0.70)' : 'var(--text-muted)', fontSize: '9px' }}>{sub}</div>
                </button>
              ))}
            </div>
          )}

          {/* Confirmed openings list */}
          {openings.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '4px', marginTop: pendingOpening ? '8px' : '0' }}>
              {openings.map((o) => {
                const color   = o.type === 'door' ? '#c0703a' : '#5c82b0';
                const typeIdx = openings.filter(x => x.type === o.type).indexOf(o) + 1;
                return (
                  <div key={o.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 14px', background: 'var(--surface-card)', border: '1px solid var(--border-default)' }}>
                    <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color }}>
                      {o.type === 'door' ? 'D' : 'W'}{typeIdx} &nbsp;·&nbsp; {o.wall} &nbsp;·&nbsp; {fmtDim(o.widthIn)}
                    </span>
                    <button onClick={() => deleteOpening(o.id)} style={{ background: 'transparent', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: '11px', padding: '0 4px', lineHeight: 1 }}>×</button>
                  </div>
                );
              })}
            </div>
          )}

        </div>
      )}

      {/* ─── Appliance controls ──────────────────────────────────────────── */}
      {phaseIsAppliances && (
        <div style={{ marginTop: '12px' }}>

          {/* Active panel first — closest to canvas */}

          {/* Pending appliance sliders — Apply/Cancel pinned to top */}
          {pendingAppliance && (() => {
            const pa  = pendingAppliance;
            const cfg = APP_CFG[pa.type];
            const wLenIn = wallLenIn(pa.wall);
            return (
              <div style={{ padding: '16px', background: 'var(--surface-card)', border: `1px solid ${cfg.color}`, marginBottom: '8px' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '14px' }}>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.10em', color: cfg.color }}>
                    {cfg.label} — {pa.wall} wall
                  </div>
                  <div style={{ display: 'flex', gap: '6px' }}>
                    <button onClick={confirmPendingAppliance} style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', background: cfg.color, color: '#fff', border: 'none', padding: '5px 12px', cursor: 'pointer' }}>Apply</button>
                    <button onClick={cancelPendingAppliance} style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', background: 'transparent', border: '1px solid var(--border-default)', color: 'var(--text-muted)', padding: '5px 10px', cursor: 'pointer' }}>Cancel</button>
                  </div>
                </div>
                {pendingClashes && (
                  <div style={{ padding: '8px 12px', background: 'rgba(168,72,60,0.08)', border: '1px solid var(--as-error)', fontFamily: 'var(--font-mono)', fontSize: '9.5px', color: 'var(--as-error)', letterSpacing: '0.04em' }}>
                    ⚠ Overlaps another appliance on this wall — adjust position or width.
                  </div>
                )}
                <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
                  {[
                    { key: 'positionIn', label: 'Position from corner', min: 0, max: Math.max(0, wLenIn - pa.widthIn), val: pa.positionIn },
                    { key: 'widthIn',    label: 'Width',                min: cfg.minW, max: Math.min(cfg.maxW, wLenIn), val: pa.widthIn },
                    ...(pa.type === 'fridge' ? [{ key: 'heightIn', label: 'Height', min: 60, max: 90, val: pa.heightIn ?? 72 }] : []),
                  ].map(({ key, label, min, max, val }) => (
                    <div key={key}>
                      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '5px' }}>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)' }}>{label}</span>
                        <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-primary)' }}>{fmtDim(val)}</span>
                      </div>
                      <input type="range" min={min} max={max} step={0.5} value={val}
                        onChange={e => updatePendingAppliance(key, e.target.value)}
                        style={{ width: '100%', accentColor: cfg.color }} />
                    </div>
                  ))}
                  {pa.type === 'stove' && (
                    <div>
                      <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)', marginBottom: '8px' }}>Above stove</div>
                      <div style={{ display: 'flex', gap: '6px' }}>
                        {[['hood', 'Range Hood'], ['microwave', 'Microwave'], ['cabinets', 'Cabinets']].map(([val, lbl]) => (
                          <button key={val} onClick={() => updatePendingAppliance('aboveStove', val)} style={{
                            flex: 1, padding: '8px 6px', cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: '9px', letterSpacing: '0.05em',
                            background: pa.aboveStove === val ? cfg.color : 'transparent',
                            color: pa.aboveStove === val ? '#fff' : cfg.color,
                            border: `1px solid ${cfg.color}`,
                          }}>{lbl}</button>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              </div>
            );
          })()}

          {/* Type chooser buttons */}
          {!pendingAppliance && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px', marginBottom: appliances.length > 0 ? '8px' : '0' }}>
              {APP_TYPES.map(type => {
                const active = appMode === type;
                return (
                  <button key={type} onClick={() => setAppMode(active ? null : type)} style={{
                    padding: '12px 14px', cursor: 'pointer', textAlign: 'left',
                    background: active ? APP_CFG[type].color : 'transparent',
                    border: `1px solid ${APP_CFG[type].color}`,
                    fontFamily: 'var(--font-mono)', fontSize: '10px',
                  }}>
                    <div style={{ color: active ? '#fff' : APP_CFG[type].color, fontWeight: '600', marginBottom: '2px' }}>{APP_CFG[type].label}</div>
                    <div style={{ color: active ? 'rgba(255,255,255,0.70)' : 'var(--text-muted)', fontSize: '9px' }}>
                      {type === 'fridge'     ? 'clears base + upper · full height' :
                       type === 'sink'       ? 'no cab blocking · draws basin' :
                       type === 'stove'      ? 'blocks base + upper · hood/micro option' :
                                              'no cab blocking · 24–30"'}
                    </div>
                  </button>
                );
              })}
            </div>
          )}

          {/* Confirmed appliances list — below active panel */}
          {appliances.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '4px', marginTop: pendingAppliance || appMode ? '8px' : '0' }}>
              {appliances.map(a => (
                <div key={a.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 14px', background: 'var(--surface-card)', border: '1px solid var(--border-default)' }}>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: APP_CFG[a.type].color }}>
                    {APP_CFG[a.type].label} &nbsp;·&nbsp; {a.wall} &nbsp;·&nbsp; {fmtDim(a.widthIn)}
                    {a.type === 'stove' && a.aboveStove ? ` · ${a.aboveStove}` : ''}
                  </span>
                  <button onClick={() => deleteAppliance(a.id)} style={{ background: 'transparent', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: '11px', padding: '0 4px', lineHeight: 1 }}>×</button>
                </div>
              ))}
            </div>
          )}

        </div>
      )}

      {/* ─── Action buttons ───────────────────────────────────────────────── */}
      <div style={{ marginTop: '18px', display: 'flex', gap: '12px', alignItems: 'center', flexWrap: 'wrap' }}>

        {phaseIsBase && (
          selWalls.size > 0
            ? <button onClick={confirm} style={{ fontFamily: 'var(--font-text)', fontSize: 'var(--text-sm)', letterSpacing: '0.04em', background: 'var(--as-navy)', color: 'var(--as-mist)', border: 'none', padding: '11px 26px', cursor: 'pointer' }}>
                Confirm Base Boundary →
              </button>
            : <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)' }}>Select at least one wall to continue.</span>
        )}

        {phaseIsUpper && (
          <>
            {selWalls.size > 0
              ? <button onClick={confirm} style={{ fontFamily: 'var(--font-text)', fontSize: 'var(--text-sm)', letterSpacing: '0.04em', background: 'var(--as-navy)', color: 'var(--as-mist)', border: 'none', padding: '11px 26px', cursor: 'pointer' }}>
                  Confirm Upper Boundary →
                </button>
              : <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--text-muted)' }}>Select upper cabinet walls, or skip.</span>
            }
            <button onClick={skipUpper} style={{ fontFamily: 'var(--font-text)', fontSize: 'var(--text-sm)', letterSpacing: '0.04em', background: 'transparent', color: 'var(--text-primary)', border: '1px solid var(--border-strong)', padding: '11px 20px', cursor: 'pointer' }}>
              No upper cabinets
            </button>
          </>
        )}

        {phaseIsAppliances && APP_TYPES.includes(appMode) && !pendingAppliance && (
          <button onClick={() => setAppMode(null)} style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', background: 'transparent', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', padding: '0' }}>
            Cancel
          </button>
        )}

        {phaseIsAppliances && !pendingAppliance && !appMode && (
          <button onClick={confirmAppliances} style={{ fontFamily: 'var(--font-text)', fontSize: 'var(--text-sm)', letterSpacing: '0.04em', background: 'var(--as-navy)', color: 'var(--as-mist)', border: 'none', padding: '11px 26px', cursor: 'pointer' }}>
            Submit Drawing →
          </button>
        )}

        {phaseIsOpenings && !pendingOpening && (
          <button onClick={confirmOpenings} style={{ fontFamily: 'var(--font-text)', fontSize: 'var(--text-sm)', letterSpacing: '0.04em', background: 'var(--as-navy)', color: 'var(--as-mist)', border: 'none', padding: '11px 26px', cursor: 'pointer' }}>
            Proceed to Appliances →
          </button>
        )}

      </div>
    </div>
  );
}

window.RoomCanvas = RoomCanvas;
