// FloorCanvas — UI: toolbar, left tool rail, right library + properties

const TOOLS = [
  { id: 'select', label: 'Select', key: 'V', icon: <g><path d="M5 3 L5 23 L11 17 L14 23 L17 22 L14 16 L21 16 Z" /></g> },
  { id: 'wall',   label: 'Wall',   key: 'W', icon: <g><line x1="3" y1="20" x2="23" y2="6" /><circle cx="3" cy="20" r="2" /><circle cx="23" cy="6" r="2" /></g> },
  { id: 'room',   label: 'Room',   key: 'R', icon: <g><rect x="4" y="5" width="18" height="16" /></g> },
  { id: 'door',   label: 'Door',   key: 'D', icon: <g><line x1="4" y1="22" x2="22" y2="22" /><line x1="4" y1="22" x2="4" y2="6" /><path d="M4 6 A 16 16 0 0 1 20 22" fill="none" strokeDasharray="2 2" /></g> },
  { id: 'window', label: 'Window', key: 'N', icon: <g><line x1="3" y1="13" x2="23" y2="13" /><line x1="3" y1="10" x2="23" y2="10" /><line x1="3" y1="16" x2="23" y2="16" /></g> },
  { id: 'furniture', label: 'Fixture', key: 'F', icon: <g><rect x="3" y="9" width="20" height="11" /><rect x="3" y="5" width="20" height="4" /></g> },
  { id: 'label',  label: 'Label',  key: 'T', icon: <g><line x1="4" y1="6" x2="22" y2="6" /><line x1="13" y1="6" x2="13" y2="22" /></g> },
  { id: 'dimension', label: 'Dim', key: 'M', icon: <g><line x1="3" y1="13" x2="23" y2="13" /><line x1="3" y1="9" x2="3" y2="17" /><line x1="23" y1="9" x2="23" y2="17" /></g> },
  { id: 'autodim', label: 'Auto', key: 'A', icon: <g><line x1="3" y1="6" x2="23" y2="6" /><line x1="3" y1="20" x2="23" y2="20" /><line x1="13" y1="6" x2="13" y2="20" /><polygon points="13,6 11,10 15,10" /><polygon points="13,20 11,16 15,16" /></g> },
  { id: 'measure', label: 'Tape', key: 'P', icon: <g><rect x="3" y="9" width="20" height="8" /><line x1="7" y1="9" x2="7" y2="13" /><line x1="11" y1="9" x2="11" y2="13" /><line x1="15" y1="9" x2="15" y2="13" /><line x1="19" y1="9" x2="19" y2="13" /></g> },
  { id: 'floor', label: 'Floor', key: 'G', icon: <g><rect x="4" y="4" width="18" height="18" /><line x1="4" y1="10" x2="22" y2="10" /><line x1="4" y1="16" x2="22" y2="16" /><line x1="10" y1="4" x2="10" y2="22" /><line x1="16" y1="4" x2="16" y2="22" /></g> },
];

const FLOOR_FILLS = [
  { value: 'tile', label: 'Tile (diag)' },
  { value: 'tile-sq', label: 'Tile (sq)' },
  { value: 'wood', label: 'Wood plank' },
  { value: 'wood-herringbone', label: 'Herringbone' },
  { value: 'water', label: 'Pool water' },
  { value: 'grass', label: 'Grass' },
  { value: 'pavers', label: 'Pavers' },
  { value: 'carpet', label: 'Carpet' },
  { value: 'concrete', label: 'Concrete' },
];

function LeftRail({ tool, setTool, setPlacingKind }) {
  return (
    <div className="left">
      {TOOLS.map((t) => (
        <button
          key={t.id}
          className="tool"
          data-active={tool === t.id}
          onClick={() => {
            setTool(t.id);
            if (t.id !== 'furniture' && t.id !== 'floor') setPlacingKind(null);
            if (t.id === 'floor') setPlacingKind((k) => k && FLOOR_FILLS.find((f) => f.value === k) ? k : 'tile');
          }}
          title={`${t.label} (${t.key})`}
        >
          <svg viewBox="0 0 26 26">{t.icon}</svg>
          <span>{t.label}</span>
          <kbd>{t.key}</kbd>
        </button>
      ))}
    </div>
  );
}

function FloorFillPicker({ placingKind, setPlacingKind }) {
  return (
    <div style={{ padding: '10px 14px', borderBottom: '1.5px solid var(--ink)', background: 'var(--paper)' }}>
      <div className="t-cap" style={{ marginBottom: 8 }}>floor pattern</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}>
        {FLOOR_FILLS.map((f) => (
          <button
            key={f.value}
            onClick={() => setPlacingKind(f.value)}
            style={{
              padding: 0, height: 44, border: '1.5px solid var(--ink)',
              background: placingKind === f.value ? 'var(--ink)' : 'var(--paper)',
              color: placingKind === f.value ? 'var(--paper)' : 'var(--ink)',
              cursor: 'pointer', position: 'relative', overflow: 'hidden',
              fontFamily: 'var(--font-narrow)', fontWeight: 700, fontSize: 9,
              textTransform: 'uppercase', letterSpacing: '0.08em',
            }}
            title={f.label}
          >
            <svg viewBox="0 0 40 28" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}>
              <rect width={40} height={28} fill={`url(#fp-${f.value})`} />
            </svg>
            <span style={{ position: 'relative', display: 'inline-block', background: 'rgba(241,235,225,0.85)', color: 'var(--ink)', padding: '1px 4px' }}>{f.label}</span>
          </button>
        ))}
      </div>
      <svg width={0} height={0} style={{ position: 'absolute' }}>
        <defs>
          <FloorPatternsScaled />
        </defs>
      </svg>
    </div>
  );
}

// Tiny version of patterns for the picker (scaled down).
function FloorPatternsScaled() {
  return (
    <>
      <pattern id="fp-tile" patternUnits="userSpaceOnUse" width={4} height={4} patternTransform="rotate(45)">
        <rect width={4} height={4} fill="#ead9b8" />
        <rect x={0.2} y={0.2} width={3.6} height={3.6} fill="#f0dfbe" stroke="#c8a872" strokeWidth={0.2} />
      </pattern>
      <pattern id="fp-tile-sq" patternUnits="userSpaceOnUse" width={4} height={4}>
        <rect width={4} height={4} fill="#f0dfbe" stroke="#c8a872" strokeWidth={0.2} />
      </pattern>
      <pattern id="fp-wood" patternUnits="userSpaceOnUse" width={8} height={3}>
        <rect width={8} height={3} fill="#c69968" />
        <line x1={0} y1={0} x2={8} y2={0} stroke="#7a5a35" strokeWidth={0.2} />
        <line x1={4} y1={0} x2={4} y2={3} stroke="#7a5a35" strokeWidth={0.15} />
      </pattern>
      <pattern id="fp-wood-herringbone" patternUnits="userSpaceOnUse" width={5} height={5} patternTransform="rotate(45)">
        <rect width={5} height={5} fill="#c69968" />
        <rect x={0} y={0} width={2.5} height={5} fill="#b88858" stroke="#7a5a35" strokeWidth={0.2} />
        <rect x={2.5} y={0} width={2.5} height={5} fill="#d6a878" stroke="#7a5a35" strokeWidth={0.2} />
      </pattern>
      <pattern id="fp-water" patternUnits="userSpaceOnUse" width={6} height={6}>
        <rect width={6} height={6} fill="#5fb3e3" />
        <path d="M 0 2 Q 1.5 1 3 2 T 6 2" fill="none" stroke="#9ed1ef" strokeWidth={0.3} />
        <path d="M 0 4.5 Q 1.5 3.5 3 4.5 T 6 4.5" fill="none" stroke="#9ed1ef" strokeWidth={0.3} />
      </pattern>
      <pattern id="fp-grass" patternUnits="userSpaceOnUse" width={3} height={3}>
        <rect width={3} height={3} fill="#6b9a4a" />
        <circle cx={0.8} cy={0.8} r={0.3} fill="#7eb058" />
        <circle cx={2.2} cy={1.8} r={0.25} fill="#5a8a3a" />
      </pattern>
      <pattern id="fp-pavers" patternUnits="userSpaceOnUse" width={5} height={5} patternTransform="rotate(45)">
        <rect width={5} height={5} fill="#d4c098" stroke="#8a7050" strokeWidth={0.25} />
      </pattern>
      <pattern id="fp-carpet" patternUnits="userSpaceOnUse" width={2} height={2}>
        <rect width={2} height={2} fill="#d4c4a8" />
        <circle cx={0.5} cy={0.5} r={0.1} fill="#a89070" />
        <circle cx={1.5} cy={1.3} r={0.1} fill="#a89070" />
      </pattern>
      <pattern id="fp-concrete" patternUnits="userSpaceOnUse" width={6} height={6}>
        <rect width={6} height={6} fill="#bcbcb4" />
        <circle cx={1} cy={1} r={0.08} fill="#8c8c80" />
        <circle cx={4.5} cy={3} r={0.1} fill="#8c8c80" />
        <circle cx={3} cy={4.8} r={0.08} fill="#8c8c80" />
      </pattern>
    </>
  );
}

function TopBar({ projectName, setProjectName, undo, redo, canUndo, canRedo, clearAll, exportSVG, saveJSON, loadJSON, units, setUnits, gridSize, setGridSize, vizStyle, setVizStyle, activeFloorName, presentation, setPresentation, shapes }) {
  const [exportOpen, setExportOpen] = React.useState(false);
  const STYLES = [
    { value: 'bauhaus',    label: 'Bauhaus' },
    { value: 'realistic',  label: 'Realistic color' },
    { value: 'blueprint',  label: 'Blueprint' },
    { value: 'sketch',     label: 'Hand sketch' },
    { value: 'mono',       label: 'Technical mono' },
    { value: 'watercolor', label: 'Watercolor' },
    { value: 'midcentury', label: 'Midcentury' },
  ];
  return (
    <div className="topbar">
      <div className="logo"><div className="logo-mark"><i /></div></div>
      <div className="title-block">
        <input
          value={projectName}
          onChange={(e) => setProjectName(e.target.value)}
          style={{ font: 'inherit', fontFamily: 'var(--font-display)', fontSize: 18, textTransform: 'uppercase', background: 'transparent', border: 'none', color: 'var(--ink)', width: 220, padding: 0, outline: 'none' }}
        />
        <div>
          <div className="sub">floorcanvas · drafting</div>
        </div>
      </div>
      <div className="top-actions">
        <div className="top-group">
          <button className="tb-btn icon" disabled={!canUndo} onClick={undo} title="Undo">↶</button>
          <button className="tb-btn icon" disabled={!canRedo} onClick={redo} title="Redo">↷</button>
        </div>
        <div className="top-group">
          <span className="t-cap" style={{ marginRight: 6 }}>style</span>
          <select
            value={vizStyle || 'bauhaus'}
            onChange={(e) => setVizStyle && setVizStyle(e.target.value)}
            style={{
              height: 36, padding: '0 8px', border: '1.5px solid var(--ink)', background: 'var(--paper)',
              color: 'var(--ink)', fontFamily: 'var(--font-narrow)', fontWeight: 700, textTransform: 'uppercase',
              letterSpacing: '0.12em', fontSize: 11, cursor: 'pointer', borderRadius: 0, minWidth: 140,
            }}
          >
            {STYLES.map((s) => <option key={s.value} value={s.value}>{s.label}</option>)}
          </select>
        </div>
        <div className="top-group">
          <span className="t-cap" style={{ marginRight: 6 }}>grid</span>
          {[0.5, 1, 2].map((g) => (
            <button key={g} className="tb-btn" data-active={gridSize === g} onClick={() => setGridSize(g)}
              style={gridSize === g ? { background: 'var(--ink)', color: 'var(--paper)' } : {}}>
              {g}'
            </button>
          ))}
        </div>
        <div className="top-group">
          <span className="t-cap" style={{ marginRight: 6 }}>units</span>
          <button className="tb-btn" style={units === 'ft' ? { background: 'var(--ink)', color: 'var(--paper)' } : {}} onClick={() => setUnits('ft')}>ft</button>
          <button className="tb-btn" style={units === 'm' ? { background: 'var(--ink)', color: 'var(--paper)' } : {}} onClick={() => setUnits('m')}>m</button>
        </div>
        <div className="top-group">
          <button className="tb-btn" onClick={clearAll}>clear</button>
          {window.RenderButton && <window.RenderButton shapes={shapes || []} projectName={projectName} activeFloorName={activeFloorName} />}
          <button className="tb-btn" onClick={() => setPresentation(!presentation)} style={presentation ? { background: 'var(--yellow)', color: 'var(--ink)' } : {}}>present</button>
          <button className="tb-btn primary" onClick={() => setExportOpen(true)}>export ▾</button>
          {exportOpen && (
            <window.ExportMenu
              onClose={() => setExportOpen(false)}
              projectName={projectName}
              activeFloorName={activeFloorName || 'plan'}
              exportSVG={exportSVG}
              saveJSON={saveJSON}
              loadJSON={loadJSON}
              presentation={presentation}
              setPresentation={setPresentation}
            />
          )}
        </div>
      </div>
    </div>
  );
}

function LibraryPanel({ placingKind, setPlacingKind, setTool, selected, updateSelected, deleteSelected, tool }) {
  const [open, setOpen] = React.useState({ Bedroom: true, Living: true, Dining: false, Kitchen: false, Bath: false, Outdoor: false, Landscaping: true, Office: false, Laundry: false, Garage: false, Stairs: false, Elevations: false });

  return (
    <div className="right">
      <div className="panel-head">
        <div className="ttl">{tool === 'floor' ? 'Floor patterns' : 'Library'}</div>
        <div className="ct">{tool === 'floor' ? `${FLOOR_FILLS.length} fills` : `${window.FURNITURE.length} items`}</div>
      </div>

      {tool === 'floor' && (
        <FloorFillPicker placingKind={placingKind} setPlacingKind={setPlacingKind} />
      )}

      {selected && selected.type === 'floor' && (
        <div className="props">
          <div className="t-cap" style={{ marginBottom: 8 }}>selected · floor</div>
          <div className="props-row"><label>pattern</label>
            <select value={selected.fill} onChange={(e) => updateSelected({ fill: e.target.value })}
              style={{ fontFamily: 'var(--font-mono)', fontSize: 12, padding: '4px 6px', border: '1.5px solid var(--ink)', background: 'var(--paper)' }}>
              {FLOOR_FILLS.map((f) => <option key={f.value} value={f.value}>{f.label}</option>)}
            </select>
          </div>
          <div className="props-row"><label>x</label><input type="number" step={0.5} value={selected.x.toFixed(2)} onChange={(e) => updateSelected({ x: +e.target.value })} /></div>
          <div className="props-row"><label>y</label><input type="number" step={0.5} value={selected.y.toFixed(2)} onChange={(e) => updateSelected({ y: +e.target.value })} /></div>
          <div className="props-row"><label>width</label><input type="number" step={0.5} value={selected.w.toFixed(2)} onChange={(e) => updateSelected({ w: +e.target.value })} /></div>
          <div className="props-row"><label>height</label><input type="number" step={0.5} value={selected.h.toFixed(2)} onChange={(e) => updateSelected({ h: +e.target.value })} /></div>
          <div className="props-row"><button className="delete" onClick={deleteSelected}>delete</button></div>
        </div>
      )}

      {selected && selected.type !== 'floor' && (
        <div className="props">
          <div className="t-cap" style={{ marginBottom: 8 }}>selected · {selected.type}{selected.kind ? ` / ${selected.kind}` : ''}</div>
          {(selected.type === 'furniture' || selected.type === 'door' || selected.type === 'window') && (
            <>
              <div className="props-row">
                <label>x</label><input type="number" step={0.5} value={selected.x.toFixed(2)} onChange={(e) => updateSelected({ x: +e.target.value })} />
              </div>
              <div className="props-row">
                <label>y</label><input type="number" step={0.5} value={selected.y.toFixed(2)} onChange={(e) => updateSelected({ y: +e.target.value })} />
              </div>
              <div className="props-row">
                <label>rotate</label><input type="number" step={15} value={selected.rotation || 0} onChange={(e) => updateSelected({ rotation: +e.target.value })} />
              </div>
            </>
          )}
          {selected.type === 'furniture' && (
            <>
              <div className="props-row">
                <label>width</label><input type="number" step={0.5} value={(selected.w || 0).toFixed(2)} onChange={(e) => updateSelected({ w: +e.target.value })} />
              </div>
              <div className="props-row">
                <label>height</label><input type="number" step={0.5} value={(selected.h || 0).toFixed(2)} onChange={(e) => updateSelected({ h: +e.target.value })} />
              </div>
            </>
          )}
          {selected.type === 'wall' && (
            <>
              <div className="props-row"><label>x1</label><input type="number" step={0.5} value={selected.a.x.toFixed(2)} onChange={(e) => updateSelected({ a: { ...selected.a, x: +e.target.value } })} /></div>
              <div className="props-row"><label>y1</label><input type="number" step={0.5} value={selected.a.y.toFixed(2)} onChange={(e) => updateSelected({ a: { ...selected.a, y: +e.target.value } })} /></div>
              <div className="props-row"><label>x2</label><input type="number" step={0.5} value={selected.b.x.toFixed(2)} onChange={(e) => updateSelected({ b: { ...selected.b, x: +e.target.value } })} /></div>
              <div className="props-row"><label>y2</label><input type="number" step={0.5} value={selected.b.y.toFixed(2)} onChange={(e) => updateSelected({ b: { ...selected.b, y: +e.target.value } })} /></div>
              <div className="props-row"><label>thickness</label><input type="number" step={0.1} value={(selected.thickness || 0.5).toFixed(2)} onChange={(e) => updateSelected({ thickness: +e.target.value })} /></div>
            </>
          )}
          {selected.type === 'label' && (
            <>
              <div className="props-row"><label>name</label><input type="text" value={selected.text} onChange={(e) => updateSelected({ text: e.target.value })} /></div>
              <div className="props-row"><label>dim</label><input type="text" value={selected.dim || ''} placeholder="12'-0&quot; X 12'-0&quot;" onChange={(e) => updateSelected({ dim: e.target.value })} /></div>
              <div className="props-row"><label>size</label><input type="number" step={0.1} value={selected.size || 0.7} onChange={(e) => updateSelected({ size: +e.target.value })} /></div>
            </>
          )}
          <div className="props-row">
            <button className="delete" onClick={deleteSelected}>delete</button>
          </div>
        </div>
      )}

      <div className="panel-body">
        {window.CATEGORIES.map((cat) => {
          const items = window.FURNITURE.filter((f) => f.category === cat);
          return (
            <div className="cat" key={cat}>
              <button className="cat-head" onClick={() => setOpen((o) => ({ ...o, [cat]: !o[cat] }))}>
                <span>{cat}</span>
                <span className="ct" style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--ink-5)' }}>{items.length}</span>
                <span className="chev">{open[cat] ? '−' : '+'}</span>
              </button>
              {open[cat] && (
                <div className="cat-grid">
                  {items.map((f) => (
                    <button
                      key={f.kind}
                      className="lib-item"
                      data-active={placingKind === f.kind}
                      onClick={() => { setPlacingKind(f.kind); setTool('furniture'); }}
                      title={`${f.label} — ${f.w}'×${f.h}'`}
                    >
                      <window.FurniturePreview kind={f.kind} />
                      <span className="lbl">{f.label}</span>
                    </button>
                  ))}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function FloorTabs({ floors, activeId, setActiveFloor, addFloor, renameFloor, deleteFloor, duplicateFloor, moveFloor, showGhost, ghostMode, setGhost, setGhostMode }) {
  const [editingId, setEditingId] = React.useState(null);
  const [menuId, setMenuId] = React.useState(null);
  React.useEffect(() => {
    if (!menuId) return;
    const close = () => setMenuId(null);
    setTimeout(() => window.addEventListener('click', close, { once: true }), 0);
    return () => window.removeEventListener('click', close);
  }, [menuId]);
  return (
    <div className="floor-tabs">
      <div className="ft-label">FLOOR</div>
      <div className="ft-row">
        {floors.map((f, i) => (
          <div key={f.id} className="ft-tab" data-active={f.id === activeId} onClick={() => setActiveFloor(f.id)} onDoubleClick={() => setEditingId(f.id)}>
            <div className="ft-level">{f.level >= 0 ? '+' : ''}{f.level}</div>
            {editingId === f.id ? (
              <input
                autoFocus
                defaultValue={f.name}
                onClick={(e) => e.stopPropagation()}
                onBlur={(e) => { renameFloor(f.id, e.target.value || f.name); setEditingId(null); }}
                onKeyDown={(e) => { if (e.key === 'Enter') { renameFloor(f.id, e.target.value || f.name); setEditingId(null); } if (e.key === 'Escape') setEditingId(null); }}
              />
            ) : (
              <div className="ft-name">{f.name}</div>
            )}
            <button className="ft-menu" onClick={(e) => { e.stopPropagation(); setMenuId(menuId === f.id ? null : f.id); }} title="More">⋯</button>
            {menuId === f.id && (
              <div className="ft-pop" onClick={(e) => e.stopPropagation()}>
                <button onClick={() => { setEditingId(f.id); setMenuId(null); }}>Rename</button>
                <button onClick={() => { duplicateFloor(f.id); setMenuId(null); }}>Duplicate</button>
                <button onClick={() => { moveFloor(f.id, -1); setMenuId(null); }} disabled={i === 0}>Move ↓</button>
                <button onClick={() => { moveFloor(f.id, 1); setMenuId(null); }} disabled={i === floors.length - 1}>Move ↑</button>
                <button className="ft-pop-del" onClick={() => { if (confirm(`Delete ${f.name}?`)) deleteFloor(f.id); setMenuId(null); }} disabled={floors.length <= 1}>Delete</button>
              </div>
            )}
          </div>
        ))}
        <button className="ft-add" onClick={addFloor} title="Add floor">+</button>
      </div>
      <div className="ft-spacer" />
      <button
        className="ft-ghost-btn"
        data-active={showGhost}
        onClick={() => setGhost(!showGhost)}
        title="Toggle ghost layer (other floor)"
      >
        <svg viewBox="0 0 24 24" width="14" height="14"><rect x="4" y="4" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeDasharray="3 2" /></svg>
        ghost · {ghostMode}
      </button>
      <select className="ft-ghost-mode" value={ghostMode} onChange={(e) => setGhostMode(e.target.value)} title="Ghost mode">
        <option value="below">below</option>
        <option value="above">above</option>
        <option value="all">all others</option>
        <option value="none">none</option>
      </select>
    </div>
  );
}

function StatusBar({ shapes, tool, gridSize, units, floorName, floorCount }) {
  const walls = shapes.filter((s) => s.type === 'wall').length;
  const furn = shapes.filter((s) => s.type === 'furniture').length;
  const openings = shapes.filter((s) => s.type === 'door' || s.type === 'window').length;
  // Compute total wall length
  let total = 0;
  shapes.forEach((s) => { if (s.type === 'wall') total += Math.hypot(s.b.x - s.a.x, s.b.y - s.a.y); });
  const totalDisplay = units === 'm' ? (total * 0.3048).toFixed(1) + ' m' : total.toFixed(1) + " ft";
  return (
    <div className="status">
      <span className="dot yellow" /> <span>floorcanvas v1 · phase 0</span>
      <span className="sep">·</span>
      <span>tool <strong style={{ color: 'var(--ink)' }}>{tool}</strong></span>
      <span className="sep">·</span>
      <span>grid {gridSize}'</span>
      <span className="sep">·</span>
      <span>floor <strong style={{ color: 'var(--ink)' }}>{floorName}</strong> · {floorCount}</span>
      <div className="right-side">
        <span>walls <strong style={{ color: 'var(--ink)' }}>{walls}</strong></span>
        <span>openings <strong style={{ color: 'var(--ink)' }}>{openings}</strong></span>
        <span>fixtures <strong style={{ color: 'var(--ink)' }}>{furn}</strong></span>
        <span>perimeter <strong style={{ color: 'var(--ink)' }}>{totalDisplay}</strong></span>
        <span>scroll: zoom · space+drag: pan</span>
      </div>
    </div>
  );
}

// Compact, horizontally-laid-out props bar pinned to the top of the
// drawing area when a shape is selected. Shows just the placement +
// size fields for the shape's type, plus a delete shortcut. The full
// editor (text, dim, fill, etc.) still lives in the right Library panel.
function SelectionBar({ selected, updateSelected, deleteSelected }) {
  if (!selected) return null;
  // Each field renders as a vertical stack: label · ^ · input · v.
  // The ^/v buttons nudge the value by `step` so touch users don't have
  // to type. Hold = continuous nudge via repeating timer.
  const num = (label, val, step, onChange) => {
    const cur = Number(val ?? 0);
    const bump = (dir) => onChange(+(cur + dir * step).toFixed(3));
    const start = (dir) => {
      bump(dir);
      let t = setTimeout(function tick() {
        bump(dir);
        t = setTimeout(tick, 80);
      }, 350);
      const stop = () => { clearTimeout(t); window.removeEventListener('pointerup', stop); window.removeEventListener('pointercancel', stop); };
      window.addEventListener('pointerup', stop);
      window.addEventListener('pointercancel', stop);
    };
    return (
      <div className="sb-field" key={label}>
        <label>{label}</label>
        <button className="sb-step" aria-label={`${label} up`} onPointerDown={() => start(+1)}>^</button>
        <input type="number" step={step} value={cur.toFixed(2)}
          onChange={(e) => onChange(+e.target.value)} />
        <button className="sb-step" aria-label={`${label} down`} onPointerDown={() => start(-1)}>v</button>
      </div>
    );
  };
  const fields = [];
  const tagLabel = selected.kind ? `${selected.type} · ${selected.kind}` : selected.type;
  if (selected.a && selected.b) {
    fields.push(
      num('x1', selected.a.x, 0.5, (v) => updateSelected({ a: { ...selected.a, x: v } })),
      num('y1', selected.a.y, 0.5, (v) => updateSelected({ a: { ...selected.a, y: v } })),
      num('x2', selected.b.x, 0.5, (v) => updateSelected({ b: { ...selected.b, x: v } })),
      num('y2', selected.b.y, 0.5, (v) => updateSelected({ b: { ...selected.b, y: v } })),
    );
    if (selected.type === 'wall') {
      fields.push(num('t', selected.thickness, 0.1, (v) => updateSelected({ thickness: v })));
    }
  } else {
    if (selected.x != null) fields.push(num('x', selected.x, 0.5, (v) => updateSelected({ x: v })));
    if (selected.y != null) fields.push(num('y', selected.y, 0.5, (v) => updateSelected({ y: v })));
    if (selected.w != null) fields.push(num('w', selected.w, 0.5, (v) => updateSelected({ w: v })));
    if (selected.h != null) fields.push(num('h', selected.h, 0.5, (v) => updateSelected({ h: v })));
    if (selected.width != null) fields.push(num('w', selected.width, 0.5, (v) => updateSelected({ width: v })));
    if (selected.size != null) fields.push(num('size', selected.size, 0.1, (v) => updateSelected({ size: v })));
    if (selected.rotation != null) fields.push(num('rot', selected.rotation, 15, (v) => updateSelected({ rotation: v })));
  }
  return (
    <div className="sel-bar" role="toolbar" aria-label="Selection properties">
      <span className="sb-tag">{tagLabel}</span>
      {fields}
      <button className="sb-del" onClick={deleteSelected} title="Delete (⌫)">del</button>
    </div>
  );
}

Object.assign(window, { LeftRail, TopBar, LibraryPanel, StatusBar, FloorTabs, TOOLS, SelectionBar });
