/* ===================================================================
   KabelFlux v5 — Project home, Connectors, Settings, Empty
   =================================================================== */

/* ---------- Project home (NEW screen) ----------
   Bento-style overview answering: "what is the state of this harness?"
   ============================================================ */
const ProjectHome = ({ project, onGoto, wires=[], connectors=[], nodes=[], bundles=[], realPid=null, onExport }) => {
  const wireCount = wires.length;
  const openWires = wires.filter(w => (w.notes || '').toLowerCase().includes('open')).length;
  const lastEdit = '2 min ago by L. Kohli';

  const RECENT_EVENTS = [
    { t: '2 min',  who: 'L. Kohli',  kind: 'edit',   what: 'changed gauge on cavity 7 (22 → 20 AWG)' },
    { t: '14 min', who: 'L. Kohli',  kind: 'add',    what: 'added wire cavity 18 · J2:6 → J3:6 · BK' },
    { t: '1 h',    who: 'R. Bedi',   kind: 'review', what: 'marked rev B for engineering review' },
    { t: '3 h',    who: 'L. Kohli',  kind: 'edit',   what: 'updated J5 type to D-Sub DB-9' },
    { t: '5 h',    who: 'system',    kind: 'backup', what: 'pre-deploy snapshot taken (224 KB)' },
    { t: 'Yesterday', who: 'R. Bedi',kind: 'note',   what: 'added revision note: "Shield drain wire to chassis ring per IPC-WHMA-A-620"' },
  ];

  return (
    <div className="kf-view kf-home">
      <div className="kf-home-hero">
        <div>
          <div className="kf-home-eyebrow">
            <Chip tone="info" size="sm" icon="folder">{project.code}</Chip>
            <Chip tone={project.status === 'released' ? 'success' : project.status === 'review' ? 'warning' : 'info'} size="sm" dot>
              {project.status === 'released' ? 'Released' : project.status === 'review' ? 'In review' : project.status === 'draft' ? 'Draft' : 'Active'}
            </Chip>
            <span className="kf-home-rev num">Rev {project.revision}</span>
          </div>
          <h1 className="kf-home-title">{project.name}</h1>
          <div className="kf-home-meta">
            Last edit {lastEdit} · Owned by {project.owner}
          </div>
        </div>
        <div className="kf-home-hero-actions">
          <Btn kind="ghost" icon="history">Revision history</Btn>
          <Btn kind="outline" icon="share">Share</Btn>
          <Btn kind="primary" icon="download" onClick={() => onExport && onExport('prod')}>Production drawing</Btn>
        </div>
      </div>

      <div className="kf-home-grid">
        {/* Big harness preview */}
        <div className="card kf-home-preview">
          <div className="card-head">
            <h3>Layout preview</h3>
            <div className="row">
              <Chip size="sm" icon="diagram" active>Schematic</Chip>
              <Btn size="sm" kind="ghost" iconRight="chevR" onClick={() => onGoto('layout')}>Open Layout</Btn>
            </div>
          </div>
          <div className="kf-home-preview-svg">
            <MiniHarness wires={wires} connectors={connectors} nodes={nodes} bundles={bundles}/>
          </div>
        </div>

        {/* Health card */}
        <div className="card">
          <div className="card-head"><h3>Harness health</h3><Chip size="sm" tone={openWires > 0 ? 'warning' : 'success'} dot>{openWires > 0 ? `${openWires} open` : 'OK'}</Chip></div>
          <div className="card-body kf-health">
            <HealthRow label="Continuity"   ok done={wireCount - openWires} total={wireCount}/>
            <HealthRow label="Shield wired" ok done={wires.filter(w=>w.shield).length} total={wires.filter(w=>w.shield).length}/>
            <HealthRow label="Pin coverage" ok done={wires.length} total={connectors.reduce((a,c)=>a+c.pins,0)}/>
            <HealthRow label="AS50881 lint" ok done={36} total={38} warn={2}/>
            <HealthRow label="Title block"  ok done={1} total={1}/>
          </div>
        </div>

        {/* Quick stats */}
        <div className="card">
          <div className="card-head"><h3>At a glance</h3></div>
          <div className="card-body">
            <div className="kf-quick-stats">
              <QStat icon="wires"     label="Wires"      v={wireCount}/>
              <QStat icon="connector" label="Connectors" v={connectors.length}/>
              <QStat icon="nodes"     label="Nodes"      v={nodes.length}/>
              <QStat icon="bundle"    label="Bundles"    v={bundles.length}/>
              <QStat icon="ruler"     label="Length"     v={`${(wires.reduce((a,w)=>a+w.length,0)/1000).toFixed(2)} m`}/>
              <QStat icon="bolt"      label="Shielded"   v={wires.filter(w=>w.shield).length}/>
            </div>
          </div>
        </div>

        {/* Activity feed */}
        <div className="card kf-home-activity">
          <div className="card-head">
            <h3>Recent activity</h3>
            <Btn size="sm" kind="ghost">View all</Btn>
          </div>
          <div className="kf-activity">
            {RECENT_EVENTS.map((e, i) => (
              <div key={i} className="kf-activity-row">
                <div className="kf-activity-time num">{e.t}</div>
                <div className={`kf-activity-dot kf-activity-dot-${e.kind}`}/>
                <div className="kf-activity-text">
                  <b>{e.who}</b> {e.what}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Exports row */}
        <div className="card kf-home-exports">
          <div className="card-head"><h3>Recent exports</h3><Btn size="sm" kind="ghost" iconRight="chevR">All formats</Btn></div>
          <div className="kf-exports-row">
            <ExportCard icon="file" label="Production Drawing" sub="A3 · 4 sheets · 1.2 MB" time="2 min ago" tone="primary"/>
            <ExportCard icon="flask" label="Continuity Plan" sub="QA · 1 sheet · 84 KB" time="1 h ago"/>
            <ExportCard icon="bom" label="Wire Table" sub="XLSX · 18 rows · 12 KB" time="3 h ago"/>
            <ExportCard icon="ruler" label="Formboard DXF" sub="4 layers · AutoCAD" time="5 h ago"/>
          </div>
        </div>
      </div>
    </div>
  );
};

/* Mini-layout for the Project Home preview card.
   - Reads real connectors/wires/nodes/bundles from props.
   - Computes a tight viewBox around the data + padding, lets the SVG
     scale to fit the card.
   - Empty-state placeholder when the project has no connectors yet. */
const MiniHarness = ({ wires=[], connectors=[], nodes=[], bundles=[] }) => {
  // Empty-state: no connectors → show a gentle "open the Layout view"
  // placeholder instead of a misleading generic harness diagram.
  if (connectors.length === 0 && nodes.length === 0) {
    return (
      <svg viewBox="0 0 600 320" preserveAspectRatio="xMidYMid meet"
           style={{ width: '100%', height: '100%' }} aria-hidden="true">
        <defs>
          <pattern id="miniGridEmpty" width="20" height="20" patternUnits="userSpaceOnUse">
            <circle cx="1" cy="1" r="1" fill="var(--grid-dot)"/>
          </pattern>
        </defs>
        <rect width="600" height="320" fill="url(#miniGridEmpty)"/>
        <text x="300" y="155" textAnchor="middle" fontSize="14"
              fill="var(--t-3)" fontFamily="var(--f-sans)">No layout yet</text>
        <text x="300" y="175" textAnchor="middle" fontSize="11"
              fill="var(--t-4)" fontFamily="var(--f-sans)">Add connectors and wires to see them here</text>
      </svg>
    );
  }

  // Compute bounding box from real connector + node positions, then pad.
  const points = [
    ...connectors.map(c => ({ x: c.x || 0, y: c.y || 0 })),
    ...nodes.map(n => ({ x: n.x || 0, y: n.y || 0 })),
  ];
  const xs = points.map(p => p.x);
  const ys = points.map(p => p.y);
  const PAD = 60;
  const minX = Math.min.apply(null, xs) - PAD;
  const minY = Math.min.apply(null, ys) - PAD;
  const maxX = Math.max.apply(null, xs) + PAD;
  const maxY = Math.max.apply(null, ys) + PAD;
  const vbW = Math.max(maxX - minX, 200);
  const vbH = Math.max(maxY - minY, 200);

  // Lookup table: wire endpoint names → {x,y}. Matches LayoutView's
  // `connectors.find(...) || nodes.find(...)` semantics so a wire that
  // terminates at a node still draws.
  const byName = {};
  connectors.forEach(c => { byName[c.id] = c; });
  nodes.forEach(n => { byName[n.id] = n; });

  const wireD = (w) => {
    const from = byName[w.fromConn];
    const to   = byName[w.toConn];
    if (!from || !to) return null;
    // Simple straight line for the preview — full LayoutView has bezier
    // + 90° formboard modes but this thumbnail doesn't need them.
    return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
  };

  const COLOR_HEX = (typeof window !== 'undefined' && window.COLOR_HEX) || {};

  return (
    <svg viewBox={`${minX} ${minY} ${vbW} ${vbH}`}
         preserveAspectRatio="xMidYMid meet"
         style={{ width: '100%', height: '100%' }}
         role="img" aria-label="Live preview of this harness layout">
      <defs>
        <pattern id="miniGrid" width="24" height="24" patternUnits="userSpaceOnUse">
          <circle cx="1" cy="1" r="1" fill="var(--grid-dot)"/>
        </pattern>
      </defs>
      <rect x={minX} y={minY} width={vbW} height={vbH} fill="url(#miniGrid)"/>

      {/* Bundles — soft tinted bands underneath the wires */}
      {bundles.map((b, i) => (
        b && b.from && b.to ? (
          <path key={`b-${i}`}
                d={`M ${b.from.x} ${b.from.y} L ${b.to.x} ${b.to.y}`}
                stroke="rgba(74,158,255,.16)"
                strokeWidth={Math.min(28, 6 + ((b.count || 1) * 2))}
                strokeLinecap="round" fill="none"/>
        ) : null
      ))}

      {/* Wires — coloured by w.color, falls back to neutral grey */}
      {wires.map(w => {
        const d = wireD(w);
        if (!d) return null;
        const hex = COLOR_HEX[w.color] || '#888';
        return (
          <path key={w.id} d={d} stroke={hex} strokeWidth="1.8"
                fill="none" strokeLinecap="round" opacity={0.95}/>
        );
      })}

      {/* Nodes — small amber dots so they read as "junctions" */}
      {nodes.map(n => (
        <g key={n.id}>
          <circle cx={n.x} cy={n.y} r="6" fill="var(--warning)"
                  stroke="var(--bg-1)" strokeWidth="2"/>
          <text x={n.x} y={n.y - 14} textAnchor="middle" fontSize="10"
                fill="var(--t-3)" fontFamily="var(--f-mono)">{n.label || n.id}</text>
        </g>
      ))}

      {/* Connectors — outer ring + perimeter pin dots + label.
          Pin count caps at 32 in the preview to keep the SVG light. */}
      {connectors.map(c => {
        const pins = Math.min(c.pins || 0, 32);
        const r = 22;
        const pinR = r - 7;
        return (
          <g key={c.id}>
            <circle cx={c.x} cy={c.y} r={r} fill="var(--bg-3)"
                    stroke="var(--line-strong)" strokeWidth="1.6"/>
            <circle cx={c.x} cy={c.y} r={r - 5} fill="none"
                    stroke="var(--line)" strokeWidth="0.8"/>
            {Array.from({ length: pins }).map((_, i) => {
              const a = i / Math.max(pins, 1) * Math.PI * 2 - Math.PI / 2;
              return (
                <circle key={i}
                        cx={c.x + Math.cos(a) * pinR}
                        cy={c.y + Math.sin(a) * pinR}
                        r="1.6" fill="var(--t-3)"/>
              );
            })}
            <text x={c.x} y={c.y - r - 6} textAnchor="middle"
                  fontSize="12" fill="var(--t-1)" fontWeight="700"
                  fontFamily="var(--f-mono)">{c.id}</text>
          </g>
        );
      })}
    </svg>
  );
};

const HealthRow = ({ label, done, total, warn=0 }) => {
  const pct = total > 0 ? (done / total) * 100 : 100;
  const ok = warn === 0 && done >= total;
  return (
    <div className="kf-health-row">
      <div className="kf-health-label">{label}</div>
      <div className="kf-health-bar">
        <div className="kf-health-fill" style={{ width: `${pct}%`, background: ok ? 'var(--success)' : 'var(--warning)' }}/>
      </div>
      <div className="kf-health-val num">
        {done}/{total}
        {warn > 0 && <span className="kf-health-warn"> · {warn} warn</span>}
      </div>
    </div>
  );
};

const QStat = ({ icon, label, v }) => (
  <div className="kf-qstat">
    <div className="kf-qstat-icon"><Icon name={icon} size={16} stroke="var(--t-3)"/></div>
    <div className="kf-qstat-val num">{v}</div>
    <div className="kf-qstat-label">{label}</div>
  </div>
);

const ExportCard = ({ icon, label, sub, time, tone }) => (
  <button className={`kf-export-card ${tone ? 'kf-export-card-'+tone : ''}`}>
    <div className="kf-export-icon"><Icon name={icon} size={18}/></div>
    <div className="kf-export-text">
      <div className="kf-export-label">{label}</div>
      <div className="kf-export-sub">{sub}</div>
    </div>
    <div className="kf-export-time num">{time}</div>
  </button>
);

/* ---------- Connectors view ---------- */
const ConnectorsView = ({ connectors=[], wires=[], loading=false, realPid=null, onRefresh }) => {
  const toast = useToast();
  const [editing, setEditing] = useState(null);    // connector being edited, or 'new'
  const [confirm, setConfirm] = useState(null);
  /* Click-to-expand: on narrow viewports only one card body is visible
     at a time. Initialise to the first connector so the user lands on
     a non-empty view. Toggling closes the current card; clicking a
     different card switches to it. */
  const [expandedId, setExpandedId] = React.useState(null);
  React.useEffect(() => {
    if (expandedId == null && connectors.length) setExpandedId(connectors[0].id);
  }, [connectors, expandedId]);
  const toggleExpand = (id) => setExpandedId(prev => prev === id ? null : id);

  const openNew = () => setEditing({
    id: '', name: '', type: '', description: '',
    pins: 8, gender: 'M', x: 100, y: 100,
  });
  const openEdit = (c) => setEditing({
    id: c.id, name: c.id, type: c.type || '',
    description: c.label && c.label.includes(' — ') ? c.label.split(' — ')[1] : '',
    pins: c.pins || 0, gender: c.gender || 'M',
    x: c.x || 100, y: c.y || 100,
    _raw: c._raw,
  });
  const save = async (draft) => {
    try {
      if (draft._raw && draft._raw.id) {
        await kfxUpdateConnector(draft._raw.id, {
          id: draft.name, name: draft.name, type: draft.type, description: draft.description,
          pins: draft.pins, gender: draft.gender, x: draft.x, y: draft.y,
        });
        toast.ok('Connector updated', draft.name);
      } else {
        if (!realPid) throw new Error('No project loaded');
        await kfxCreateConnector(realPid, {
          id: draft.name, name: draft.name, type: draft.type, description: draft.description,
          pins: draft.pins, gender: draft.gender, x: draft.x, y: draft.y,
        });
        toast.ok('Connector added', draft.name);
      }
      setEditing(null);
      if (onRefresh) await onRefresh();
    } catch (e) {
      toast.err('Could not save connector', (e && e.message) || 'Server error');
    }
  };
  const del = (c) => {
    setConfirm({
      title: `Delete connector ${c.id}?`,
      body: <>This removes <b>{c.id}</b> from the harness. Any wires terminating on it will become orphaned.</>,
      onConfirm: async () => {
        if (!c._raw || !c._raw.id) { toast.err('No backend id for this connector'); return; }
        try {
          await kfxDeleteConnector(c._raw.id);
          toast.ok(`Deleted ${c.id}`);
          if (onRefresh) await onRefresh();
        } catch (e) {
          toast.err('Could not delete connector', (e && e.message) || 'Server error');
        }
      }
    });
  };

  if (loading && connectors.length === 0) {
    return (
      <div className="kf-view">
        <ViewHeader title="Connectors"/>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10, color: 'var(--t-3)' }}>
          <Spinner size={16}/> <span>Loading connectors…</span>
        </div>
      </div>
    );
  }
  if (connectors.length === 0) {
    return (
      <div className="kf-view">
        <ViewHeader title="Connectors" count={0} actions={
          <>
            <Btn kind="ghost" icon="library" size="sm">Symbol library</Btn>
            <Btn kind="primary" icon="plus" onClick={openNew}>New connector</Btn>
          </>
        }/>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center' }}>
          <EmptyState icon="connector"
            title="No connectors yet"
            hint="Add the first connector — pick from the symbol library or define a custom shell."
            action={<Btn kind="primary" icon="plus" onClick={openNew}>Add the first connector</Btn>}/>
        </div>
        {editing && <ConnectorEditModal draft={editing} onChange={setEditing} onCancel={() => setEditing(null)} onSave={save}/>}
      </div>
    );
  }
  return (
    <div className="kf-view">
      <ViewHeader title="Connectors" count={connectors.length} actions={
        <>
          <Btn kind="ghost" icon="library" size="sm">Symbol library</Btn>
          <Btn kind="primary" icon="plus" onClick={openNew}>New connector</Btn>
        </>
      }/>
      <div className="kf-conn-grid">
        {connectors.map(c => {
          const wired = wires.filter(w => w.fromConn === c.id || w.toConn === c.id).length;
          const pct = c.pins > 0 ? (wired / c.pins) * 100 : 0;
          const desc = c.label && c.label.includes(' — ') ? c.label.split(' — ')[1] : (c.type || '');
          const isExpanded = expandedId === c.id;
          return (
            <div key={c.id}
                 className={`card kf-conn-card ${isExpanded ? 'is-expanded' : 'is-collapsed'}`}
                 data-expanded={isExpanded}>
              {/* Header is a button-ish row: clicking the chip+type area
                  toggles expansion; edit/trash stay separately clickable. */}
              <div className="card-head kf-conn-card-head">
                <button type="button"
                        className="kf-conn-card-toggle"
                        onClick={() => toggleExpand(c.id)}
                        aria-expanded={isExpanded}
                        aria-controls={`conn-body-${c.id}`}
                        aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${c.id}`}>
                  <Chip size="sm" tone="info" icon="connector">{c.id}</Chip>
                  <span className="kf-conn-card-type">{c.type}</span>
                  <Icon name={isExpanded ? 'chevD' : 'chevR'} size={12} stroke="var(--t-3)"/>
                </button>
                <div className="row">
                  <IconBtn icon="edit"  label={`Edit ${c.id}`}  onClick={(e) => { e && e.stopPropagation && e.stopPropagation(); openEdit(c); }}/>
                  <IconBtn icon="trash" label={`Delete ${c.id}`} danger onClick={(e) => { e && e.stopPropagation && e.stopPropagation(); del(c); }}/>
                </div>
              </div>
              <div id={`conn-body-${c.id}`} className="kf-conn-card-body">
                <div className="kf-conn-card-row1">
                  <svg viewBox="0 0 120 120" width="100" height="100" aria-hidden="true" style={{flexShrink:0}}>
                    <circle cx="60" cy="60" r="40" fill="var(--bg-3)" stroke="var(--line-strong)" strokeWidth="1.5"/>
                    <circle cx="60" cy="60" r="34" fill="none" stroke="var(--line)" strokeWidth=".8"/>
                    {Array.from({ length: c.pins }).map((_, i) => {
                      const a = i / c.pins * Math.PI * 2 - Math.PI / 2;
                      return c.gender === 'M'
                        ? <circle key={i} cx={60 + Math.cos(a) * 26} cy={60 + Math.sin(a) * 26} r="2.5" fill="var(--t-2)"/>
                        : <circle key={i} cx={60 + Math.cos(a) * 26} cy={60 + Math.sin(a) * 26} r="2.5" fill="none" stroke="var(--t-2)" strokeWidth="1.2"/>;
                    })}
                  </svg>
                  <div className="kf-conn-meta">
                    <div className="kf-conn-meta-row"><span>Gender</span><b>{c.gender === 'M' ? 'Male' : c.gender === 'F' ? 'Female' : '—'}</b></div>
                    <div className="kf-conn-meta-row"><span>Pins</span><b className="num">{c.pins}</b></div>
                    <div className="kf-conn-meta-row"><span>Wired</span><b className="num">{wired}/{c.pins}</b></div>
                  </div>
                </div>
                <div className="kf-conn-progress">
                  <div className="kf-conn-progress-fill" style={{ width: `${pct}%`, background: pct === 100 ? 'var(--success)' : 'var(--info)' }}/>
                </div>
                <div className="kf-conn-desc">{desc}</div>
              </div>
            </div>
          );
        })}
        <button className="card kf-conn-add" aria-label="Add new connector" onClick={openNew}>
          <Icon name="plus" size={28} stroke="var(--t-3)"/>
          <span>Add connector</span>
        </button>
      </div>

      {editing && <ConnectorEditModal draft={editing} onChange={setEditing} onCancel={() => setEditing(null)} onSave={save}/>}
      {confirm && <ConfirmModal {...confirm} danger confirmLabel="Delete connector" onClose={() => setConfirm(null)}/>}
    </div>
  );
};

/* Simple connector edit/create modal — name + type + pins + gender. */
const ConnectorEditModal = ({ draft, onChange, onCancel, onSave }) => {
  const isNew = !(draft._raw && draft._raw.id);
  const setF = (k, v) => onChange({ ...draft, [k]: v });
  const canSubmit = draft.name && draft.name.trim().length > 0 && draft.pins > 0;
  return (
    <Modal title={isNew ? 'New connector' : `Edit ${draft.id || draft.name}`} onClose={onCancel} size="sm" footer={
      <>
        <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
        <AsyncBtn kind="primary" icon={isNew ? 'plus' : 'check'} disabled={!canSubmit} busyLabel="Saving…"
                  onClick={async () => { if (canSubmit) await onSave(draft); }}>
          {isNew ? 'Create connector' : 'Save changes'}
        </AsyncBtn>
      </>
    }>
      <form className="kf-form" onSubmit={e => e.preventDefault()}>
        <div className="kf-form-row">
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nc-name">Reference (e.g. J1)</label>
            <input id="nc-name" autoFocus className="kf-input num" value={draft.name}
                   onChange={e => setF('name', e.target.value)} placeholder="J1"/>
          </div>
          <div className="kf-field">
            <label htmlFor="nc-pins">Pins</label>
            <input id="nc-pins" type="number" min="1" className="kf-input num" value={draft.pins}
                   onChange={e => setF('pins', Math.max(1, +e.target.value || 1))}/>
          </div>
        </div>
        <div className="kf-form-row">
          <div className="kf-field">
            <label htmlFor="nc-type">Type</label>
            <input id="nc-type" className="kf-input" value={draft.type}
                   onChange={e => setF('type', e.target.value)} placeholder="e.g. D38999 · MS3470 · DB9"/>
          </div>
          <div className="kf-field">
            <label htmlFor="nc-gender">Gender</label>
            <select id="nc-gender" className="kf-input" value={draft.gender}
                    onChange={e => setF('gender', e.target.value)}>
              <option value="M">Male</option>
              <option value="F">Female</option>
            </select>
          </div>
        </div>
        <div className="kf-form-row">
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nc-desc">Description</label>
            <input id="nc-desc" className="kf-input" value={draft.description}
                   onChange={e => setF('description', e.target.value)}/>
          </div>
        </div>
      </form>
    </Modal>
  );
};

/* ---------- Nodes view ---------- */
const NodesView = ({ nodes=[], wires=[], loading=false, realPid=null, onRefresh }) => {
  const toast = useToast();
  const [editing, setEditing] = useState(null);
  const [confirm, setConfirm] = useState(null);

  const openNew = () => setEditing({
    id: '', label: '', type: 'splice', value: '',
    part_number: '', description: '', length: '', length_unit: 'mm',
    x: 300, y: 200,
    /* Wires that should route through this node. Mirrors backend
       wires.node_ref — we toggle each wire's node_ref to/from the
       node label on save. */
    wireIds: [],
  });
  const openEdit = (n) => setEditing({
    id: n.id, label: n.label || n.id || '', type: n.type || 'splice',
    value: n.value || '',
    part_number: (n._raw && n._raw.part_number) || '',
    description: (n._raw && n._raw.description) || '',
    length:      (n._raw && n._raw.length)      || '',
    length_unit: 'mm',
    x: n.x || 300, y: n.y || 200,
    /* Seed wireIds from the wires whose backend node_ref equals this
       node's label. The user can then check more or uncheck some. */
    wireIds: wires
      .filter(w => (w._raw && w._raw.node_ref) === (n.label || n.id))
      .map(w => w._raw && w._raw.id)
      .filter(Boolean),
    _raw: n._raw,
  });
  const save = async (draft) => {
    try {
      let savedNodeLabel = draft.label;
      if (draft._raw && draft._raw.id) {
        await kfxUpdateNode(draft._raw.id, draft);
        toast.ok('Node updated', draft.label);
      } else {
        if (!realPid) throw new Error('No project loaded');
        await kfxCreateNode(realPid, draft);
        toast.ok('Node added', draft.label);
      }
      /* Sync wire-tags: any wire currently pointing at this node must
         either stay (still checked) or be cleared (unchecked); any newly-
         checked wire gets node_ref set to the node's label. Best-effort:
         a single failed wire write toasts the error but doesn't block the
         rest. */
      const wantIds = new Set(draft.wireIds || []);
      const wasIds  = new Set(
        wires.filter(w => (w._raw && w._raw.node_ref) === savedNodeLabel)
             .map(w => w._raw && w._raw.id)
             .filter(Boolean)
      );
      const toAdd    = [...wantIds].filter(id => !wasIds.has(id));
      const toRemove = [...wasIds].filter(id => !wantIds.has(id));
      for (const id of toAdd) {
        try { await kfxUpdateWire(id, { node_ref: savedNodeLabel }); }
        catch (e) { toast.err(`Could not tag wire #${id}`, (e && e.message) || ''); }
      }
      for (const id of toRemove) {
        try { await kfxUpdateWire(id, { node_ref: '' }); }
        catch (e) { toast.err(`Could not untag wire #${id}`, (e && e.message) || ''); }
      }
      setEditing(null);
      if (onRefresh) await onRefresh();
    } catch (e) {
      toast.err('Could not save node', (e && e.message) || 'Server error');
    }
  };
  const del = (n) => {
    setConfirm({
      title: `Delete node ${n.label || n.id}?`,
      body: <>This removes <b>{n.label || n.id}</b> from the harness.</>,
      onConfirm: async () => {
        if (!n._raw || !n._raw.id) { toast.err('No backend id for this node'); return; }
        try {
          await kfxDeleteNode(n._raw.id);
          toast.ok(`Deleted ${n.label || n.id}`);
          if (onRefresh) await onRefresh();
        } catch (e) {
          toast.err('Could not delete node', (e && e.message) || 'Server error');
        }
      },
    });
  };

  if (loading && nodes.length === 0) {
    return (
      <div className="kf-view">
        <ViewHeader title="Nodes"/>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10, color: 'var(--t-3)' }}>
          <Spinner size={16}/> <span>Loading nodes…</span>
        </div>
      </div>
    );
  }
  if (nodes.length === 0) {
    return (
      <div className="kf-view">
        <ViewHeader title="Nodes" count={0} actions={
          <Btn kind="primary" icon="plus" onClick={openNew}>Add a node</Btn>
        }/>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center' }}>
          <EmptyState icon="nodes"
            title="No nodes yet"
            hint="Splices, fuses, grounds, jumpers — every non-connector junction in the harness."
            action={<Btn kind="primary" icon="plus" onClick={openNew}>Add the first node</Btn>}/>
        </div>
        {editing && <NodeEditModal draft={editing} wires={wires} onChange={setEditing} onCancel={() => setEditing(null)} onSave={save}/>}
      </div>
    );
  }
  return (
    <div className="kf-view">
      <ViewHeader title="Nodes" count={nodes.length} actions={
        <Btn kind="primary" icon="plus" onClick={openNew}>Add a node</Btn>
      }/>
      <div className="kf-table-wrap">
        <table className="kf-table">
          <thead>
            <tr><th className="kf-th-sticky">Ref</th><th>Type</th><th>Value</th><th className="num">X</th><th className="num">Y</th><th aria-label="actions"/></tr>
          </thead>
          <tbody>
            {nodes.map(n => (
              <tr key={n.id}>
                <td className="kf-td-sticky mono"><b>{n.label || n.id}</b></td>
                <td>{n.type}</td>
                <td className="mono">{n.value || '—'}</td>
                <td className="mono num">{n.x}</td>
                <td className="mono num">{n.y}</td>
                <td>
                  <div className="kf-row-actions">
                    <IconBtn icon="edit"  label={`Edit ${n.label || n.id}`}  onClick={() => openEdit(n)}/>
                    <IconBtn icon="trash" label={`Delete ${n.label || n.id}`} danger onClick={() => del(n)}/>
                  </div>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {editing && <NodeEditModal draft={editing} onChange={setEditing} onCancel={() => setEditing(null)} onSave={save}/>}
      {confirm && <ConfirmModal {...confirm} danger confirmLabel="Delete node" onClose={() => setConfirm(null)}/>}
    </div>
  );
};

/* Node-type icon for the preview tile — matches the on-canvas glyph
   so the user gets immediate feedback when they change type. */
const NODE_TYPE_META = {
  splice: { label: 'Wire junction / fan-out point',     glyph: '⊕' },
  fuse:   { label: 'Inline fuse',                       glyph: '⊟' },
  ground: { label: 'Chassis ground',                    glyph: '⏚' },
  jumper: { label: 'Jumper / strap',                    glyph: '⇄' },
};

const NodeEditModal = ({ draft, wires=[], onChange, onCancel, onSave }) => {
  const isNew = !(draft._raw && draft._raw.id);
  const setF = (k, v) => onChange({ ...draft, [k]: v });
  const canSubmit = draft.label && draft.label.trim().length > 0;
  const wireIds = draft.wireIds || [];
  const toggleWire = (id) => {
    const set = new Set(wireIds);
    if (set.has(id)) set.delete(id); else set.add(id);
    setF('wireIds', [...set]);
  };
  const selectAll = () => setF('wireIds', wires.map(w => w._raw && w._raw.id).filter(Boolean));
  const deselectAll = () => setF('wireIds', []);
  const typeMeta = NODE_TYPE_META[draft.type] || NODE_TYPE_META.splice;
  return (
    <Modal title="Add Node" onClose={onCancel} size="md" footer={
      <>
        <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
        <AsyncBtn kind="primary" icon={isNew ? 'plus' : 'check'} disabled={!canSubmit} busyLabel="Saving…"
                  onClick={async () => { if (canSubmit) await onSave(draft); }}>
          {isNew ? 'Save Node' : 'Save changes'}
        </AsyncBtn>
      </>
    }>
      <form className="kf-form kf-node-form" onSubmit={e => e.preventDefault()}>
        {/* Row 1 — Node type + Label */}
        <div className="kf-form-row">
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nn-type">NODE TYPE</label>
            <select id="nn-type" className="kf-input" value={draft.type}
                    onChange={e => setF('type', e.target.value)}>
              <option value="splice">Splice — Wire junction / fan-out point</option>
              <option value="fuse">Fuse — Inline fuse</option>
              <option value="ground">Ground — Chassis ground</option>
              <option value="jumper">Jumper — Jumper / strap</option>
            </select>
          </div>
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nn-label">LABEL / ID</label>
            <input id="nn-label" autoFocus className="kf-input" value={draft.label}
                   onChange={e => setF('label', e.target.value)} placeholder="SP1, F1, R10, GND1…"/>
          </div>
        </div>

        {/* Row 2 — Value + Part number */}
        <div className="kf-form-row">
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nn-val">VALUE <span className="kf-field-hint">(resistance, capacitance, amp rating…)</span></label>
            <input id="nn-val" className="kf-input" value={draft.value}
                   onChange={e => setF('value', e.target.value)}
                   placeholder="e.g. 5A, 10kΩ, 100nF"/>
          </div>
          <div className="kf-field" style={{ flex: 1 }}>
            <label htmlFor="nn-pn">PART NUMBER</label>
            <input id="nn-pn" className="kf-input num" value={draft.part_number || ''}
                   onChange={e => setF('part_number', e.target.value)}
                   placeholder="e.g. LT1-5A"/>
          </div>
        </div>

        {/* Row 3 — Description */}
        <div className="kf-field">
          <label htmlFor="nn-desc">DESCRIPTION</label>
          <input id="nn-desc" className="kf-input" value={draft.description || ''}
                 onChange={e => setF('description', e.target.value)}
                 placeholder="e.g. Main harness inline fuse"/>
        </div>

        {/* Row 4 — Length + unit */}
        <div className="kf-field">
          <label htmlFor="nn-len">LENGTH <span className="kf-field-hint">(distance from reference point, for formboard placement)</span></label>
          <div className="kf-form-row" style={{ gap: 8 }}>
            <input id="nn-len" type="text" className="kf-input num" value={draft.length || ''}
                   onChange={e => setF('length', e.target.value)} placeholder="e.g. 250" style={{ flex: 1 }}/>
            <select className="kf-input" value={draft.length_unit || 'mm'}
                    onChange={e => setF('length_unit', e.target.value)} style={{ width: 80 }}>
              <option value="mm">mm</option>
              <option value="in">in</option>
            </select>
          </div>
        </div>

        {/* Row 5 — Wire tags (multi-select) */}
        <div className="kf-field">
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <label style={{ flex: 1 }}>
              WIRE TAGS <span className="kf-field-hint">(select wires that route through this node)</span>
            </label>
            <button type="button" className="kf-link" onClick={selectAll}>Select All</button>
            <button type="button" className="kf-link" onClick={deselectAll}>Deselect All</button>
          </div>
          <div className="kf-node-wires">
            {wires.length === 0 && (
              <div className="kf-node-wires-empty">No wires yet — add wires first, then come back here to tag them.</div>
            )}
            {wires.map(w => {
              const id = w._raw && w._raw.id;
              if (!id) return null;
              const on = wireIds.indexOf(id) !== -1;
              const tag = (
                (w.fromConn || '?') + ' · ' + (w.fromPin || '?') +
                ' → ' + (w.toConn || '?') + ' · ' + (w.toPin || '?') +
                ' · ' + (w.net || '—') +
                ' · ' + (w.color || '') +
                ' · ' + (w.gauge || '')
              );
              return (
                <label key={id} className={`kf-node-wire ${on ? 'on' : ''}`}>
                  <input type="checkbox" checked={on} onChange={() => toggleWire(id)}/>
                  <span className="kf-node-wire-tag">{tag}</span>
                  {w.color && (
                    <span className="kf-node-wire-swatch"
                          style={{ background: (typeof COLOR_HEX !== 'undefined' && COLOR_HEX[w.color]) || '#888' }}/>
                  )}
                </label>
              );
            })}
          </div>
          {wires.length > 0 && (
            <div className="kf-node-wires-count">✓ {wireIds.length} of {wires.length} wires tagged to this node</div>
          )}
        </div>

        {/* Preview tile */}
        <div className="kf-node-preview-wrap">
          <div className="kf-node-preview">
            <div className="kf-node-preview-label">preview</div>
            <div className="kf-node-preview-glyph">{typeMeta.glyph}</div>
            <div className="kf-node-preview-sub">{typeMeta.label}</div>
          </div>
        </div>

        {/* Row — X / Y (kept but tucked at the end since the modal puts
            label/type/value first the way the user expects) */}
        <div className="kf-form-row">
          <div className="kf-field">
            <label htmlFor="nn-x">X</label>
            <input id="nn-x" type="number" className="kf-input num" value={draft.x}
                   onChange={e => setF('x', +e.target.value || 0)}/>
          </div>
          <div className="kf-field">
            <label htmlFor="nn-y">Y</label>
            <input id="nn-y" type="number" className="kf-input num" value={draft.y}
                   onChange={e => setF('y', +e.target.value || 0)}/>
          </div>
        </div>
      </form>
    </Modal>
  );
};

/* ---------- Settings (with proper IA — L3 fix) ---------- */
const SettingsView = ({ section='general' }) => {
  const [tab, setTab] = useState(section);
  return (
    <div className="kf-view kf-settings">
      <ViewHeader title="Settings" hint="Personal preferences. For backups, users and company use the Admin menu." actions={null}/>
      <div className="kf-settings-body">
        <nav className="kf-settings-nav" aria-label="Settings sections">
          {[
            { k: 'general',     l: 'General',           icon: 'settings' },
            /* Appearance tab dropped — theme is fixed to daylight, density
               no UI, no other appearance knobs remain. */
            { k: 'shortcuts',   l: 'Keyboard shortcuts',icon: 'keyboard' },
            { k: 'integrations',l: 'Integrations',      icon: 'share' },
            { k: 'about',       l: 'About KabelFlux',   icon: 'info' },
          ].map(s => (
            <button key={s.k} className={`kf-settings-nav-item ${tab === s.k ? 'on' : ''}`} onClick={() => setTab(s.k)}>
              <Icon name={s.icon} size={14}/> {s.l}
            </button>
          ))}
        </nav>
        <div className="kf-settings-panel">
          {tab === 'general' && <GeneralSettings/>}
          {tab === 'shortcuts' && <ShortcutsSettings/>}
          {tab === 'integrations' && <IntegrationsSettings/>}
          {tab === 'about' && <AboutSettings/>}
        </div>
      </div>
    </div>
  );
};

const GeneralSettings = () => (
  <>
    <div className="card">
      <div className="card-head"><h3>Profile</h3></div>
      <div className="card-body kf-form">
        <div className="kf-form-row">
          <div className="kf-field"><label htmlFor="s-name">Full name</label><input id="s-name" className="kf-input" defaultValue="Laila Kohli"/></div>
          <div className="kf-field"><label htmlFor="s-title">Job title</label><input id="s-title" className="kf-input" defaultValue="Senior Harness Designer"/></div>
        </div>
        <div className="kf-form-row">
          <div className="kf-field"><label htmlFor="s-email">Email</label><input id="s-email" className="kf-input" type="email" defaultValue="laila@ikran.aero"/></div>
          <div className="kf-field"><label htmlFor="s-unit">Units</label>
            <select id="s-unit" className="kf-input" defaultValue="mm">
              <option value="mm">Millimetres (mm)</option>
              <option value="in">Inches (in)</option>
            </select>
          </div>
        </div>
        <div className="kf-form-row">
          <div className="kf-checkfield"><input id="s-autosave" type="checkbox" defaultChecked/><label htmlFor="s-autosave">Auto-save edits (every 5 s)</label></div>
          <div className="kf-checkfield"><input id="s-warn" type="checkbox" defaultChecked/><label htmlFor="s-warn">Warn before destructive actions</label></div>
        </div>
      </div>
    </div>

    <div className="card">
      <div className="card-head">
        <h3>Localization</h3>
        <span className="kf-card-sub">Used for BOM pricing, exports and timestamps.</span>
      </div>
      <div className="card-body kf-form">
        <div className="kf-form-row">
          <div className="kf-field">
            <label htmlFor="s-currency">Currency</label>
            <select id="s-currency" className="kf-input" defaultValue="USD">
              <optgroup label="Common">
                <option value="USD">USD — US Dollar ($)</option>
                <option value="EUR">EUR — Euro (€)</option>
                <option value="GBP">GBP — British Pound (£)</option>
                <option value="JPY">JPY — Japanese Yen (¥)</option>
                <option value="CHF">CHF — Swiss Franc (Fr)</option>
              </optgroup>
              <optgroup label="Asia">
                <option value="INR">INR — Indian Rupee (₹)</option>
                <option value="CNY">CNY — Chinese Yuan (¥)</option>
                <option value="SGD">SGD — Singapore Dollar (S$)</option>
                <option value="AED">AED — UAE Dirham (د.إ)</option>
              </optgroup>
              <optgroup label="Americas">
                <option value="CAD">CAD — Canadian Dollar (C$)</option>
                <option value="MXN">MXN — Mexican Peso (Mex$)</option>
                <option value="BRL">BRL — Brazilian Real (R$)</option>
              </optgroup>
              <optgroup label="Oceania">
                <option value="AUD">AUD — Australian Dollar (A$)</option>
                <option value="NZD">NZD — New Zealand Dollar (NZ$)</option>
              </optgroup>
            </select>
            <div className="kf-field-hint">Applied to BOM totals, supplier costs and quote exports.</div>
          </div>
          <div className="kf-field">
            <label htmlFor="s-num-format">Number format</label>
            <select id="s-num-format" className="kf-input" defaultValue="1,234.56">
              <option value="1,234.56">1,234.56 (US / UK)</option>
              <option value="1.234,56">1.234,56 (EU)</option>
              <option value="1 234,56">1 234,56 (FR / SE)</option>
              <option value="1'234.56">1'234.56 (CH)</option>
            </select>
            <div className="kf-field-hint">Thousands separator and decimal mark.</div>
          </div>
        </div>
        <div className="kf-form-row">
          <div className="kf-field">
            <label htmlFor="s-locale">Language / locale</label>
            <select id="s-locale" className="kf-input" defaultValue="en-US">
              <option value="en-US">English (United States)</option>
              <option value="en-GB">English (United Kingdom)</option>
              <option value="de-DE">Deutsch (Deutschland)</option>
              <option value="fr-FR">Français (France)</option>
              <option value="es-ES">Español (España)</option>
              <option value="ja-JP">日本語 (日本)</option>
              <option value="zh-CN">中文 (简体)</option>
            </select>
          </div>
          <div className="kf-field">
            <label htmlFor="s-tz">Timezone</label>
            <select id="s-tz" className="kf-input" defaultValue="Europe/London">
              <option value="UTC">UTC — Coordinated Universal Time</option>
              <option value="America/Los_Angeles">PST/PDT — Los Angeles</option>
              <option value="America/New_York">EST/EDT — New York</option>
              <option value="Europe/London">GMT/BST — London</option>
              <option value="Europe/Berlin">CET/CEST — Berlin</option>
              <option value="Asia/Kolkata">IST — Kolkata</option>
              <option value="Asia/Tokyo">JST — Tokyo</option>
              <option value="Australia/Sydney">AEST/AEDT — Sydney</option>
            </select>
          </div>
        </div>
        <div className="kf-form-row">
          <div className="kf-field">
            <label htmlFor="s-date">Date format</label>
            <select id="s-date" className="kf-input" defaultValue="iso">
              <option value="iso">2026-05-23 (ISO 8601)</option>
              <option value="us">May 23, 2026</option>
              <option value="eu">23 May 2026</option>
              <option value="slash">23/05/2026</option>
            </select>
          </div>
          <div className="kf-field">
            <label htmlFor="s-time">Time format</label>
            <select id="s-time" className="kf-input" defaultValue="24">
              <option value="24">24-hour (14:30)</option>
              <option value="12">12-hour (2:30 PM)</option>
            </select>
          </div>
        </div>
      </div>
    </div>
  </>
);

const ShortcutsSettings = () => (
  <div className="card">
    <div className="card-head"><h3>Keyboard shortcuts</h3><Chip size="sm">Editable</Chip></div>
    <div className="card-body">
      <div className="kf-sc-list">
        {[
          ['Command palette',          ['⌘','K']],
          ['New wire',                 ['N']],
          ['Toggle Wire mode',         ['W']],
          ['Search current view',      ['/']],
          ['Switch project',           ['⌘','P']],
          ['Export production drawing',['⌘','⇧','E']],
          ['Toggle sidebar',           ['⌘','\\']],
          ['Layout tab',               ['G','1']],
          ['Wires tab',                ['G','2']],
          ['BOM tab',                  ['G','3']],
          ['Undo / redo',              ['⌘','Z']],
        ].map(([label, keys]) => (
          <div key={label} className="kf-sc-row">
            <span className="kf-sc-label">{label}</span>
            <span className="kf-sc-keys">{keys.map((k,i) => <Kbd key={i}>{k}</Kbd>)}</span>
          </div>
        ))}
      </div>
    </div>
  </div>
);

const IntegrationsSettings = () => (
  <div className="card">
    <div className="card-head"><h3>Connected systems</h3></div>
    <div className="card-body">
      <div className="kf-integrations">
        {[
          { name:'Komax / Schleuniger',   status:'connected', sub:'KX-V machine · last sync 2 h ago' },
          { name:'AutoCAD / Fusion 360',  status:'connected', sub:'DXF export ready' },
          { name:'Jira (engineering)',    status:'disconnect',sub:'Connect to link revisions to issues' },
          { name:'PLM (Teamcenter)',      status:'disconnect',sub:'Map title block to part numbers' },
        ].map(it => (
          <div key={it.name} className="kf-int-row">
            <div className="kf-int-info">
              <div className="kf-int-name">{it.name}</div>
              <div className="kf-int-sub">{it.sub}</div>
            </div>
            {it.status === 'connected'
              ? <Chip tone="success" dot>Connected</Chip>
              : <Btn size="sm" kind="outline">Connect</Btn>}
          </div>
        ))}
      </div>
    </div>
  </div>
);

const AboutSettings = () => (
  <div className="card">
    <div className="card-head"><h3>About</h3></div>
    <div className="card-body" style={{ fontSize: 13, color: 'var(--t-2)', lineHeight: 1.6 }}>
      <b style={{ color:'var(--t-1)' }}>KabelFlux e-Harness Suite</b> · v5.0 UX refresh<br/>
      A wiring-harness design and documentation tool for aerospace engineers.<br/><br/>
      Compliant outputs: AS50881 conventions, IPC-WHMA-A-620, IEC 60757 colour codes, Komax / Schleuniger wire-prep CSV, AutoCAD DXF.
    </div>
  </div>
);

/* ---------- Project Settings (per-project, sidebar entry) ----------
   Per-project metadata, defaults, access, and danger zone. Everything
   personal / workspace-wide (theme, locale, currency, profile) lives in
   the global Settings view instead. */
const ProjectSettingsView = ({ project }) => {
  const toast = useToast();
  const [section, setSection] = useState('overview');

  // Team state — keeps which member is the Owner so we can enforce
  // "only one Owner at a time". Picking "Transfer ownership…" on any
  // non-Owner row moves the Owner role to that member and the previous
  // Owner falls back to Designer.
  const [team, setTeam] = useState([
    { id:'lk', name:'Laila Kohli',   email:'laila@ikran.aero', role:'Owner',    last:'now'         },
    { id:'rb', name:'Reza Bedi',     email:'reza@ikran.aero',  role:'Approver', last:'12 min ago'  },
    { id:'so', name:'Sam Okonkwo',   email:'sam.o@ikran.aero', role:'Reviewer', last:'2 h ago'     },
    { id:'nl', name:'Noemi Lazar',   email:'noemi@ikran.aero', role:'Designer', last:'2 d ago'     },
    { id:'ap', name:'Aiden Park',    email:'aiden@ikran.aero', role:'Viewer',   last:'yesterday'   },
  ]);
  const [pendingTransfer, setPendingTransfer] = useState(null); // {id, name}
  const setMemberRole = (id, role) => {
    if (role === '__transfer__') {
      const target = team.find(m => m.id === id);
      setPendingTransfer({ id, name: target.name });
      return;
    }
    setTeam(t => t.map(m => m.id === id ? { ...m, role } : m));
  };
  const confirmTransfer = () => {
    setTeam(t => t.map(m => {
      if (m.id === pendingTransfer.id) return { ...m, role: 'Owner' };
      if (m.role === 'Owner')          return { ...m, role: 'Designer' };
      return m;
    }));
    toast.ok('Ownership transferred', `${pendingTransfer.name} is now the Owner`);
    setPendingTransfer(null);
  };
  const sections = [
    { k:'overview',  l:'Overview',         icon:'folder' },
    { k:'titleblock',l:'Title block',      icon:'title' },
    { k:'team',      l:'Team & access',    icon:'user' },
    { k:'defaults',  l:'Harness defaults', icon:'wires' },
    { k:'compliance',l:'Compliance',       icon:'check' },
    { k:'exports',   l:'Exports & sync',   icon:'share' },
    { k:'danger',    l:'Danger zone',      icon:'warn' },
  ];

  return (
    <div className="kf-view kf-settings">
      <ViewHeader title="Project Settings"
                  hint={<span>Settings for <b>{project.name}</b> (<span className="num">{project.code}</span>). For personal preferences, currency or theme, open Settings from your avatar menu.</span>}
                  actions={null}/>
      <div className="kf-settings-body">
        <nav className="kf-settings-nav" aria-label="Project settings sections">
          {sections.map(s => (
            <button key={s.k} className={`kf-settings-nav-item ${section === s.k ? 'on' : ''}`} onClick={() => setSection(s.k)}>
              <Icon name={s.icon} size={14}/> {s.l}
            </button>
          ))}
        </nav>
        <div className="kf-settings-panel">
          {section === 'overview' && (
            <div className="card">
              <div className="card-head"><h3>Project metadata</h3></div>
              <div className="card-body kf-form">
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="p-name">Project name</label><input id="p-name" className="kf-input" defaultValue={project.name}/></div>
                  <div className="kf-field"><label htmlFor="p-code">Part number</label><input id="p-code" className="kf-input num" defaultValue={project.code}/></div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="p-rev">Revision</label><input id="p-rev" className="kf-input num" defaultValue={project.revision}/></div>
                  <div className="kf-field">
                    <label htmlFor="p-status">Status</label>
                    <select id="p-status" className="kf-input" defaultValue={project.status}>
                      <option value="draft">Draft</option>
                      <option value="active">Active</option>
                      <option value="review">In review</option>
                      <option value="released">Released</option>
                      <option value="archived">Archived</option>
                    </select>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label htmlFor="p-desc">Description</label>
                    <textarea id="p-desc" className="kf-input" rows="3" defaultValue="Aft tail-boom electrical harness for the Mira-S platform. Routes flight-control, power, and sensor returns through frame stations 14 → 27."/>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label htmlFor="p-tags">Tags</label>
                    <input id="p-tags" className="kf-input" defaultValue="aerospace, flight-critical, mira-s, tail-boom"/>
                    <div className="kf-field-hint">Comma-separated. Used for filtering on the Projects page.</div>
                  </div>
                </div>
              </div>
            </div>
          )}

          {section === 'titleblock' && (
            <div className="card">
              <div className="card-head">
                <h3>Title block</h3>
                <span className="kf-card-sub">Document header printed on every sheet of the production drawing.</span>
              </div>
              <div className="card-body kf-form">
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="tb-project">Project name</label><input id="tb-project" className="kf-input" defaultValue={project.name}/></div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="tb-title">Document name</label><input id="tb-title" className="kf-input" defaultValue={`${project.name} — Production drawing`}/></div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="tb-doc">Document number</label><input id="tb-doc" className="kf-input num" defaultValue={`DWG-${project.code}-001`}/></div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="tb-part">Part number</label><input id="tb-part" className="kf-input num" defaultValue={project.code}/></div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field"><label htmlFor="tb-rev">Version (revision)</label><input id="tb-rev" className="kf-input num" defaultValue={project.revision}/></div>
                </div>
              </div>
            </div>
          )}

          {section === 'team' && (
            <div className="card">
              <div className="card-head">
                <h3>Project team</h3>
                <span className="kf-card-sub">Assign a role for every member. Only Admins can change roles; the role gates what each member can do — designers edit, reviewers can comment + sign off, approvers must approve before release, viewers are read-only.</span>
                <Btn size="sm" kind="outline" icon="plus">Add member</Btn>
              </div>
              <div className="card-body">
                {/* Role legend — shown FIRST so admins know what each role
                    can do before they assign one. Compact pill row that
                    doesn't require scrolling. */}
                <div className="kf-team-legend">
                  <Tip side="bottom" label="Full control. Cannot be removed."><Chip size="sm" tone="success">Owner</Chip></Tip>
                  <Tip side="bottom" label="Must sign off before a release."><Chip size="sm" tone="warning">Approver</Chip></Tip>
                  <Tip side="bottom" label="Comment, propose changes, no edits."><Chip size="sm" tone="violet">Reviewer</Chip></Tip>
                  <Tip side="bottom" label="Create and edit wires, connectors, BOM."><Chip size="sm" tone="info">Designer</Chip></Tip>
                  <Tip side="bottom" label="Read-only. See drawings, no edits."><Chip size="sm">Viewer</Chip></Tip>
                  <span className="kf-team-legend-hint">Hover a role for details.</span>
                </div>

                <table className="kf-admin-table kf-team-table">
                  <thead>
                    <tr>
                      <th>Member</th>
                      <th>Project role</th>
                      <th>Status</th>
                      <th>Last active</th>
                      <th aria-label="Actions"/>
                    </tr>
                  </thead>
                  <tbody>
                    {team.map(m => {
                      const isOwner = m.role === 'Owner';
                      const tone = m.role === 'Owner' ? 'success' : m.role === 'Approver' ? 'warning' : m.role === 'Reviewer' ? 'violet' : m.role === 'Designer' ? 'info' : undefined;
                      return (
                        <tr key={m.id}>
                          <td>
                            <div className="kf-admin-who">
                              <div className="kf-audit-avatar">{m.name.split(' ').map(s => s[0]).join('').slice(0,2)}</div>
                              <div>
                                <div className="kf-audit-who-name">{m.name}</div>
                                <div className="kf-audit-who-role">{m.email}</div>
                              </div>
                            </div>
                          </td>
                          <td>
                            {isOwner ? (
                              <Chip size="sm" tone={tone}>{m.role}</Chip>
                            ) : (
                              <select className="kf-input kf-input-sm" value={m.role}
                                      onChange={e => setMemberRole(m.id, e.target.value)}>
                                <option value="Approver">Approver</option>
                                <option value="Reviewer">Reviewer</option>
                                <option value="Designer">Designer</option>
                                <option value="Viewer">Viewer</option>
                                <option disabled>──────────</option>
                                <option value="__transfer__">Transfer ownership to {m.name.split(' ')[0]}…</option>
                              </select>
                            )}
                          </td>
                          <td><Chip size="sm" dot tone="success">active</Chip></td>
                          <td className="kf-admin-muted">{m.last}</td>
                          <td className="kf-admin-actions">
                            {isOwner ? (
                              <IconBtn icon="info" label="Owner cannot be removed. Transfer ownership first." disabled/>
                            ) : (
                              <IconBtn icon="trash" label={`Remove ${m.name}`} danger
                                       onClick={() => setTeam(t => t.filter(x => x.id !== m.id))}/>
                            )}
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
            </div>
          )}

          {section === 'defaults' && (
            <>
              <div className="card">
                <div className="card-head"><h3>Harness defaults</h3><span className="kf-card-sub">Defaults for new wires and connectors in this project.</span></div>
                <div className="card-body kf-form">
                  <div className="kf-form-row">
                    <div className="kf-field">
                      <label htmlFor="p-units">Project units</label>
                      <select id="p-units" className="kf-input" defaultValue="mm">
                        <option value="mm">Millimetres (mm)</option>
                        <option value="in">Inches (in)</option>
                      </select>
                      <div className="kf-field-hint">Overrides your personal units for this project only.</div>
                    </div>
                    <div className="kf-field">
                      <label htmlFor="p-spec">Wire spec</label>
                      <select id="p-spec" className="kf-input" defaultValue="m22759">
                        <option value="m22759">MIL-DTL-22759 (PTFE, aerospace)</option>
                        <option value="m81044">MIL-DTL-81044 (XL-ETFE)</option>
                        <option value="m81381">MIL-DTL-81381 (polyimide)</option>
                        <option value="iso6722">ISO 6722 (automotive)</option>
                      </select>
                    </div>
                  </div>
                  <div className="kf-form-row">
                    <div className="kf-field">
                      <label htmlFor="p-gauge">Default gauge</label>
                      <select id="p-gauge" className="kf-input" defaultValue="22">
                        {['28','26','24','22','20','18','16','14','12','10'].map(g => <option key={g} value={g}>{g} AWG</option>)}
                      </select>
                    </div>
                    <div className="kf-field">
                      <label htmlFor="p-color">Default colour code</label>
                      <select id="p-color" className="kf-input" defaultValue="iec">
                        <option value="iec">IEC 60757</option>
                        <option value="mil">MIL-STD-681</option>
                        <option value="custom">Custom palette</option>
                      </select>
                    </div>
                  </div>
                  <div className="kf-form-row">
                    <div className="kf-field">
                      <label htmlFor="p-bend">Min bend radius factor</label>
                      <input id="p-bend" className="kf-input num" defaultValue="10" type="number" min="1" step="0.5"/>
                      <div className="kf-field-hint">Multiple of conductor diameter. AS50881 calls for ≥10× on flexed bundles.</div>
                    </div>
                    <div className="kf-field">
                      <label htmlFor="p-temp">Operating temperature</label>
                      <select id="p-temp" className="kf-input" defaultValue="-55-200">
                        <option value="-40-105">−40 °C → +105 °C</option>
                        <option value="-55-150">−55 °C → +150 °C</option>
                        <option value="-55-200">−55 °C → +200 °C</option>
                        <option value="-65-260">−65 °C → +260 °C</option>
                      </select>
                    </div>
                  </div>
                </div>
              </div>
            </>
          )}

          {section === 'compliance' && (
            <div className="card">
              <div className="card-head"><h3>Compliance & standards</h3></div>
              <div className="card-body kf-form">
                {[
                  { id:'as50881',   l:'AS50881',        desc:'Aerospace wiring practice', on:true  },
                  { id:'ipc620',    l:'IPC/WHMA-A-620', desc:'Cable & wire harness assemblies', on:true },
                  { id:'iec60757',  l:'IEC 60757',      desc:'Colour-coding designation', on:true },
                  { id:'mils8081',  l:'MIL-STD-681',    desc:'US-military colour code (alt)', on:false },
                  { id:'rohs',      l:'RoHS-3',         desc:'Restriction of hazardous substances', on:true },
                  { id:'reach',     l:'REACH SVHC',     desc:'EU substance compliance', on:true },
                  { id:'itar',      l:'ITAR-controlled', desc:'Restricted to US persons in workspace', on:false },
                ].map(c => (
                  <div key={c.id} className="kf-compliance-row">
                    <div className="kf-checkfield">
                      <input id={`c-${c.id}`} type="checkbox" defaultChecked={c.on}/>
                      <label htmlFor={`c-${c.id}`}>
                        <span className="kf-compliance-l">{c.l}</span>
                        <span className="kf-compliance-d">{c.desc}</span>
                      </label>
                    </div>
                    {c.on && <Chip size="sm" tone="success" dot>Enforced</Chip>}
                  </div>
                ))}
              </div>
            </div>
          )}

          {section === 'exports' && (
            <>
              <div className="card">
                <div className="card-head"><h3>Default export targets</h3></div>
                <div className="card-body kf-form">
                  <div className="kf-form-row">
                    <div className="kf-field">
                      <label htmlFor="p-pdf">Production drawing PDF</label>
                      <select id="p-pdf" className="kf-input" defaultValue="a3">
                        <option value="a3">A3 landscape · 14 sheets</option>
                        <option value="a2">A2 landscape · 8 sheets</option>
                        <option value="tabloid">Tabloid (11×17)</option>
                      </select>
                    </div>
                    <div className="kf-field">
                      <label htmlFor="p-cut">Wire-prep CSV format</label>
                      <select id="p-cut" className="kf-input" defaultValue="komax">
                        <option value="komax">Komax (TopWin .crimp)</option>
                        <option value="schleuniger">Schleuniger CAYMAN</option>
                        <option value="generic">Generic CSV</option>
                      </select>
                    </div>
                  </div>
                </div>
              </div>
              <div className="card">
                <div className="card-head"><h3>Sync</h3></div>
                <div className="card-body">
                  <div className="kf-checkfield"><input id="p-sync-pdm" type="checkbox" defaultChecked/><label htmlFor="p-sync-pdm">Push releases to Teamcenter PDM</label></div>
                  <div className="kf-checkfield"><input id="p-sync-git" type="checkbox" defaultChecked/><label htmlFor="p-sync-git">Mirror to git (kbf://ikran/{project.code.toLowerCase()})</label></div>
                  <div className="kf-checkfield"><input id="p-sync-slack" type="checkbox"/><label htmlFor="p-sync-slack">Post release notes to #harness-releases</label></div>
                </div>
              </div>
            </>
          )}

          {section === 'danger' && (
            <div className="card kf-danger-zone">
              <div className="card-head"><h3>Danger zone</h3></div>
              <div className="card-body">
                <div className="kf-danger-row">
                  <div>
                    <div className="kf-danger-title">Archive project</div>
                    <div className="kf-danger-hint">Project becomes read-only and is hidden from the default project list. Restorable any time.</div>
                  </div>
                  <Btn kind="outline">Archive</Btn>
                </div>
                <div className="kf-danger-row">
                  <div>
                    <div className="kf-danger-title">Transfer ownership</div>
                    <div className="kf-danger-hint">Hand off the project to another workspace member. You'll keep editor access.</div>
                  </div>
                  <Btn kind="outline">Transfer…</Btn>
                </div>
                <div className="kf-danger-row">
                  <div>
                    <div className="kf-danger-title kf-danger-strong">Delete project</div>
                    <div className="kf-danger-hint">Permanently removes wires, connectors, drawings and the project itself. Backups remain for 90 days.</div>
                  </div>
                  <Btn kind="danger" onClick={() => toast.warn('Delete requires Admin confirmation')}>Delete…</Btn>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
      {pendingTransfer && (
        <ConfirmModal
          title="Transfer ownership?"
          confirmLabel={`Yes, make ${pendingTransfer.name} the Owner`}
          danger
          onClose={() => setPendingTransfer(null)}
          onConfirm={confirmTransfer}
          body={
            <div style={{ fontSize: 13, color:'var(--t-2)', lineHeight: 1.55 }}>
              You are about to transfer ownership of <b style={{ color:'var(--t-1)' }}>{project.name}</b> to <b style={{ color:'var(--t-1)' }}>{pendingTransfer.name}</b>.
              <ul style={{ marginTop: 10, paddingLeft: 20, color:'var(--t-3)' }}>
                <li>Only one member can be the Owner at a time.</li>
                <li>The current Owner will be re-assigned the <b>Designer</b> role and can be changed afterwards.</li>
                <li>This action is logged in the Audit log.</li>
              </ul>
            </div>
          }/>
      )}
    </div>
  );
};

/* ---------- Symbol Library (workspace-level) ----------
   Shared catalog of connector symbols available across every project.
   Categories along the top, search + filters in a toolbar, then a grid
   of symbol cards each showing a tiny preview, family, pin count, and
   usage stats. Clicking a card would open a detail drawer (placeholder). */
const SYMBOL_FAMILIES = [
  { k:'all',         l:'All',           n:48 },
  { k:'circular',    l:'Circular MS',   n:11 },
  { k:'deutsch',     l:'Deutsch DT',    n: 7 },
  { k:'ampseal',     l:'AmpSeal',       n: 5 },
  { k:'weatherpack', l:'Weatherpack',   n: 4 },
  { k:'dsub',        l:'D-Sub',         n: 6 },
  { k:'molex',       l:'Molex MX150',   n: 5 },
  { k:'jst',         l:'JST',           n: 4 },
  { k:'custom',      l:'Custom',        n: 6 },
];
const SYMBOL_ROWS = [
  { id:'sym01', family:'circular',    name:'MS3470 · 6-pin',   pins: 6, gender:'plug',  shell:'A',  used:14, updated:'2026-04-22', tags:['mil-c-26482'] },
  { id:'sym02', family:'circular',    name:'MS3476 · 19-pin',  pins:19, gender:'recep', shell:'C',  used:23, updated:'2026-04-22', tags:['mil-c-26482'] },
  { id:'sym03', family:'circular',    name:'D38999 · 8-pin',   pins: 8, gender:'plug',  shell:'11', used:31, updated:'2026-05-04', tags:['series III'] },
  { id:'sym04', family:'circular',    name:'D38999 · 24-pin',  pins:24, gender:'recep', shell:'17', used:48, updated:'2026-05-12', tags:['series III'] },
  { id:'sym05', family:'deutsch',     name:'DT04-2P',          pins: 2, gender:'plug',  shell:'—',  used:62, updated:'2026-03-15', tags:[] },
  { id:'sym06', family:'deutsch',     name:'DT04-4P',          pins: 4, gender:'plug',  shell:'—',  used:54, updated:'2026-03-15', tags:[] },
  { id:'sym07', family:'deutsch',     name:'DT06-6S',          pins: 6, gender:'recep', shell:'—',  used:33, updated:'2026-03-15', tags:[] },
  { id:'sym08', family:'ampseal',     name:'AS-23',            pins:23, gender:'plug',  shell:'—',  used:18, updated:'2026-02-08', tags:[] },
  { id:'sym09', family:'ampseal',     name:'AS-35',            pins:35, gender:'plug',  shell:'—',  used:12, updated:'2026-02-08', tags:[] },
  { id:'sym10', family:'weatherpack', name:'WP-2',             pins: 2, gender:'plug',  shell:'—',  used:41, updated:'2026-01-29', tags:[] },
  { id:'sym11', family:'weatherpack', name:'WP-4',             pins: 4, gender:'recep', shell:'—',  used:29, updated:'2026-01-29', tags:[] },
  { id:'sym12', family:'dsub',        name:'DB9',              pins: 9, gender:'plug',  shell:'—',  used:22, updated:'2026-05-01', tags:['serial'] },
  { id:'sym13', family:'dsub',        name:'DB25',             pins:25, gender:'recep', shell:'—',  used:17, updated:'2026-05-01', tags:['parallel'] },
  { id:'sym14', family:'molex',       name:'MX150 · 8-pos',    pins: 8, gender:'plug',  shell:'—',  used:24, updated:'2026-04-11', tags:['sealed'] },
  { id:'sym15', family:'jst',         name:'JST PH · 4-pin',   pins: 4, gender:'plug',  shell:'—',  used:38, updated:'2026-03-30', tags:[] },
  { id:'sym16', family:'jst',         name:'JST XH · 6-pin',   pins: 6, gender:'plug',  shell:'—',  used:14, updated:'2026-03-30', tags:[] },
  { id:'sym17', family:'custom',      name:'Mira-S splice ring', pins: 4, gender:'—',  shell:'—',  used: 6, updated:'2026-05-21', tags:['internal'] },
  { id:'sym18', family:'custom',      name:'Cherokee bulkhead', pins:32, gender:'recep', shell:'—', used: 3, updated:'2026-05-09', tags:['internal'] },
];

const SymbolGlyph = ({ row }) => {
  // Parametric mode — when the row carries explicit dimensions (rows, cols,
  // pitch, shell W/H, shellShape), draw the outline + pin grid at scale.
  if (row.dims && row.dims.rows > 0 && row.dims.cols > 0) {
    const { rows, cols, pitch = 6, shellW, shellH, shellShape = 'rect', corner = 4 } = row.dims;
    const gridW = (cols - 1) * pitch;
    const gridH = (rows - 1) * pitch;
    const margin = pitch * 1.6;
    const w = shellW || Math.max(gridW + margin * 2, pitch * 2);
    const h = shellH || Math.max(gridH + margin * 2, pitch * 2);
    const vbPad = 8;
    const vbW = w + vbPad * 2;
    const vbH = h + vbPad * 2;

    // ---- shell-shape primitives ----
    // Each returns an array of [outerEl, innerEl] for the outline + the
    // thin inset stroke. Drawn at the origin (0,0); width/height come in.
    const outline = (() => {
      const outerProps = { fill:'var(--bg-3)', stroke:'var(--line-strong)', strokeWidth:1.5 };
      const innerProps = { fill:'none', stroke:'var(--line)', strokeWidth:1 };
      const inset = 4;
      switch (shellShape) {
        case 'circle': {
          const r = w / 2;
          return (<>
            <circle cx="0" cy="0" r={r} {...outerProps}/>
            <circle cx="0" cy="0" r={r - inset} {...innerProps}/>
          </>);
        }
        case 'hex': {
          // Pointy-top hex sized so width = w, height = h
          const hx = w / 2, hy = h / 2;
          const xQ = hx * 0.5;
          const outer = `${-hx + xQ},${-hy} ${hx - xQ},${-hy} ${hx},0 ${hx - xQ},${hy} ${-hx + xQ},${hy} ${-hx},0`;
          const sx = (hx - inset) * 1, sy = (hy - inset) * 1;
          const sxQ = sx * 0.5;
          const inner = `${-sx + sxQ},${-sy} ${sx - sxQ},${-sy} ${sx},0 ${sx - sxQ},${sy} ${-sx + sxQ},${sy} ${-sx},0`;
          return (<>
            <polygon points={outer} {...outerProps}/>
            <polygon points={inner} {...innerProps}/>
          </>);
        }
        case 'diamond': {
          const outer = `0,${-h / 2} ${w / 2},0 0,${h / 2} ${-w / 2},0`;
          const sx = w / 2 - inset, sy = h / 2 - inset;
          const inner = `0,${-sy} ${sx},0 0,${sy} ${-sx},0`;
          return (<>
            <polygon points={outer} {...outerProps}/>
            <polygon points={inner} {...innerProps}/>
          </>);
        }
        case 'triangle': {
          const outer = `0,${-h / 2} ${w / 2},${h / 2} ${-w / 2},${h / 2}`;
          const inner = `0,${-h / 2 + inset * 1.5} ${w / 2 - inset},${h / 2 - inset} ${-w / 2 + inset},${h / 2 - inset}`;
          return (<>
            <polygon points={outer} {...outerProps}/>
            <polygon points={inner} {...innerProps}/>
          </>);
        }
        case 'dshape': {
          // Classic D-Sub trapezoid — wider at top, narrower at bottom.
          const tx = w * 0.5, bx = w * 0.42;
          const outer = `${-tx},${-h / 2} ${tx},${-h / 2} ${bx},${h / 2} ${-bx},${h / 2}`;
          const stx = tx - inset, sbx = bx - inset;
          const inner = `${-stx},${-h / 2 + inset} ${stx},${-h / 2 + inset} ${sbx},${h / 2 - inset} ${-sbx},${h / 2 - inset}`;
          return (<>
            <polygon points={outer} {...outerProps}/>
            <polygon points={inner} {...innerProps}/>
          </>);
        }
        case 'arrow': {
          // Right-facing arrow pentagon.
          const ax = w * 0.42, tip = w * 0.5;
          const outer = `${-tip},${-h * 0.35} ${ax},${-h * 0.35} ${ax},${-h * 0.5} ${tip},0 ${ax},${h * 0.5} ${ax},${h * 0.35} ${-tip},${h * 0.35}`;
          return (<>
            <polygon points={outer} {...outerProps}/>
          </>);
        }
        case 'cross': {
          // Plus-sign cross.
          const a = w * 0.18, b = w * 0.5;
          const outer = `${-a},${-b} ${a},${-b} ${a},${-a} ${b},${-a} ${b},${a} ${a},${a} ${a},${b} ${-a},${b} ${-a},${a} ${-b},${a} ${-b},${-a} ${-a},${-a}`;
          return (<>
            <polygon points={outer} {...outerProps}/>
          </>);
        }
        case 'rect':
        default: {
          return (<>
            <rect x={-w / 2} y={-h / 2} width={w} height={h} rx={corner} {...outerProps}/>
            <rect x={-w / 2 + 3} y={-h / 2 + 3} width={w - 6} height={h - 6} rx={Math.max(0, corner - 2)} {...innerProps} strokeWidth=".8"/>
          </>);
        }
      }
    })();

    return (
      <svg viewBox={`${-vbW / 2} ${-vbH / 2} ${vbW} ${vbH}`} className="kf-symcard-glyph" aria-hidden="true">
        {outline}
        {Array.from({ length: rows * cols }).map((_, i) => {
          const r = Math.floor(i / cols);
          const c = i % cols;
          const x = -gridW / 2 + c * pitch;
          const y = -gridH / 2 + r * pitch;
          return <circle key={i} cx={x} cy={y} r={Math.max(1, pitch * .28)} fill="var(--accent)" opacity=".88"/>;
        })}
      </svg>
    );
  }

  // Original auto-layout glyph — used by library cards without explicit dims.
  const cap = Math.min(row.pins, 12);
  const cols = cap <= 4 ? cap : Math.ceil(Math.sqrt(cap));
  const rows = Math.ceil(cap / cols);
  const cw = 16, gap = 4;
  const w = cols * cw + (cols - 1) * gap;
  const h = rows * cw + (rows - 1) * gap;
  const isRound = row.family === 'circular' || row.family === 'deutsch';
  return (
    <svg viewBox="0 0 96 64" className="kf-symcard-glyph" aria-hidden="true">
      {isRound ? (
        <>
          <circle cx="48" cy="32" r="26" fill="var(--bg-3)" stroke="var(--line-strong)" strokeWidth="1.5"/>
          <circle cx="48" cy="32" r="20" fill="none" stroke="var(--line)" strokeWidth="1"/>
        </>
      ) : (
        <>
          <rect x="20" y="10" width="56" height="44" rx="3" fill="var(--bg-3)" stroke="var(--line-strong)" strokeWidth="1.5"/>
          <rect x="24" y="14" width="48" height="36" rx="2" fill="none" stroke="var(--line)" strokeWidth=".8"/>
        </>
      )}
      <g transform={`translate(${48 - w / 2}, ${32 - h / 2})`}>
        {Array.from({ length: cap }).map((_, i) => {
          const r = Math.floor(i / cols);
          const c = i % cols;
          return <circle key={i} cx={c * (cw + gap) + cw / 2 - 6} cy={r * (cw + gap) + cw / 2 - 6} r="2.4" fill="var(--accent)" opacity=".85"/>;
        })}
      </g>
      {row.pins > cap && <text x="86" y="60" fontSize="8" fill="var(--t-3)" fontFamily="var(--f-mono)" textAnchor="end">+{row.pins - cap}</text>}
    </svg>
  );
};

const LibraryView = () => {
  const [family, setFamily] = useState('all');
  const [q, setQ] = useState('');
  const [gender, setGender] = useState('all');
  const [view, setView] = useState('grid');
  const [createOpen, setCreateOpen] = useState(false);
  const [importOpen, setImportOpen] = useState(false);
  const [symbols, setSymbols] = useState([]);
  const [loading, setLoading] = useState(true);
  const toast = useToast();

  /* Hydrate from /api/connlib on mount. Backend seeds the 180 built-in
     parts; user-added customs come back in the same list. */
  React.useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const rows = await window.kfxLoadConnectorLibrary();
        if (!alive) return;
        setSymbols((rows || []).map(window.kfxMapLibraryRow));
      } catch (e) {
        if (alive) toast.err && toast.err('Could not load symbol library', (e && e.message) || '');
      } finally {
        if (alive) setLoading(false);
      }
    })();
    return () => { alive = false; };
  }, []);

  /* Families are derived from the loaded data — one per manufacturer —
     so the existing kf-library-fam tab strip becomes manufacturer-driven
     instead of hardcoded SYMBOL_FAMILIES. */
  const families = useMemo(() => {
    const counts = {};
    for (const s of symbols) counts[s.family] = (counts[s.family] || 0) + 1;
    const dynamic = Object.keys(counts)
      .sort()
      .map(k => ({
        k,
        /* Display label: pull the original (cased) manufacturer string from
           any row in this family. */
        l: (symbols.find(s => s.family === k) || {}).manufacturer || k,
        n: counts[k],
      }));
    return [{ k: 'all', l: 'All', n: symbols.length }, ...dynamic];
  }, [symbols]);

  const filtered = useMemo(() => {
    let list = symbols;
    if (family !== 'all') list = list.filter(s => s.family === family);
    if (gender !== 'all') list = list.filter(s => s.gender === gender);
    const needle = q.trim().toLowerCase();
    if (needle) list = list.filter(s =>
      (s.name + ' ' + s.manufacturer + ' ' + s.series + ' ' + s.description + ' ' + s.tags.join(' '))
        .toLowerCase().includes(needle)
    );
    return list;
  }, [family, gender, q, symbols]);

  const builtinCount = symbols.filter(s => s.isBuiltin).length;
  const customCount  = symbols.length - builtinCount;

  return (
    <div className="kf-view kf-library">
      <div className="kf-library-head">
        <div>
          <div className="kf-library-eyebrow">Workspace · shared across all projects</div>
          <h1 className="kf-library-title">Symbol Library</h1>
          <div className="kf-library-sub">
            {loading ? 'Loading…' : <>
              <span className="num">{builtinCount}</span> built-in · <span className="num">{customCount}</span> custom · grouped by manufacturer.
            </>}
          </div>
        </div>
        <div className="kf-library-head-actions">
          <Btn kind="outline" icon="import_" onClick={() => setImportOpen(true)}>Import symbol</Btn>
          <Btn kind="primary"  icon="plus"   onClick={() => setCreateOpen(true)}>New symbol</Btn>
        </div>
      </div>

      <div className="kf-library-families">
        {families.map(f => (
          <button key={f.k} className={`kf-library-fam ${family === f.k ? 'on' : ''}`}
                  onClick={() => setFamily(f.k)}>
            {f.l}
            <span className="kf-library-fam-n">{f.n}</span>
          </button>
        ))}
      </div>

      <div className="kf-library-toolbar">
        <div className="kf-projects-search" style={{ flex: 1 }}>
          <Icon name="search" size={13} stroke="var(--t-3)"/>
          <input value={q} onChange={e => setQ(e.target.value)}
                 placeholder="Search by name, tag or part number…"
                 aria-label="Search symbols"/>
        </div>
        <div className="kf-library-genders">
          {['all','plug','recep'].map(g => (
            <button key={g} className={`kf-library-fam ${gender === g ? 'on' : ''}`} onClick={() => setGender(g)}>
              {g === 'all' ? 'Both' : g === 'plug' ? 'Plug' : 'Receptacle'}
            </button>
          ))}
        </div>
        <div className="kf-library-views">
          <button className={`kf-iconbtn ${view === 'grid' ? 'on' : ''}`} onClick={() => setView('grid')} aria-label="Grid view"><Icon name="grid" size={14}/></button>
          <button className={`kf-iconbtn ${view === 'list' ? 'on' : ''}`} onClick={() => setView('list')} aria-label="List view"><Icon name="filter" size={14}/></button>
        </div>
      </div>

      {filtered.length === 0 && (
        <EmptyState icon="search" title="No symbols match" hint="Try a different family or clear the search."/>
      )}

      {view === 'grid' && (
        <div className="kf-symcard-grid">
          {filtered.map(s => (
            <div key={s.id} className="kf-symcard">
              <div className="kf-symcard-preview">
                <SymbolGlyph row={s}/>
              </div>
              <div className="kf-symcard-body">
                <div className="kf-symcard-name">{s.name}</div>
                {s.description && <div className="kf-symcard-desc">{s.description}</div>}
                <div className="kf-symcard-meta">
                  <span className="num">{s.pins}p</span>
                  {s.gender !== '—' && <Chip size="sm">{s.gender === 'plug' ? 'Male' : s.gender === 'recep' ? 'Female' : s.gender}</Chip>}
                  {s.termination && <Chip size="sm">{s.termination}</Chip>}
                  {!s.isBuiltin && <Chip size="sm" tone="warning">custom</Chip>}
                </div>
                <div className="kf-symcard-foot">
                  <span><span className="num">{s.used}</span> uses</span>
                  <span>{s.updated}</span>
                </div>
              </div>
            </div>
          ))}
        </div>
      )}

      {view === 'list' && (
        <div className="kf-library-list">
          <table className="kf-admin-table">
            <thead><tr>
              <th>Symbol</th><th>Family</th><th>Pins</th><th>Gender</th><th>Shell</th><th>Used in</th><th>Updated</th>
            </tr></thead>
            <tbody>
              {filtered.map(s => (
                <tr key={s.id}>
                  <td>
                    <div className="kf-admin-who" style={{ gap: 14 }}>
                      <div style={{ width: 56, height: 36, background:'var(--bg-2)', border:'1px solid var(--line)', borderRadius: 'var(--r-2)', display:'flex', alignItems:'center', justifyContent:'center' }}>
                        <SymbolGlyph row={s}/>
                      </div>
                      <div>
                        <div className="kf-audit-who-name">{s.name}</div>
                        <div className="kf-audit-who-role">{s.tags.join(' · ') || '—'}</div>
                      </div>
                    </div>
                  </td>
                  <td>{s.manufacturer || s.family}</td>
                  <td className="num">{s.pins}</td>
                  <td>{s.gender !== '—' && <Chip size="sm">{s.gender}</Chip>}</td>
                  <td className="num">{s.shell}</td>
                  <td className="num">{s.used}</td>
                  <td className="kf-admin-muted">{s.updated}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {createOpen && (
        <SymbolCreator
          onClose={() => setCreateOpen(false)}
          onCreate={(sym) => {
            setSymbols(s => [{ ...sym, id: `sym${Date.now()}`, used: 0, updated: new Date().toISOString().slice(0,10) }, ...s]);
            toast.ok('Symbol added', sym.name);
            setCreateOpen(false);
          }}/>
      )}
      {importOpen && (
        <SymbolImporter
          onClose={() => setImportOpen(false)}
          onImport={(rows) => {
            setSymbols(s => [...rows, ...s]);
            toast.ok(`Imported ${rows.length} symbol${rows.length === 1 ? '' : 's'}`);
            setImportOpen(false);
          }}/>
      )}
    </div>
  );
};

/* ===================================================================
   Catalog of standard connectors — the 90% case.
   Real-world harness designers spend most of their time picking from a
   vendor catalog (Deutsch, TE Connectivity, MIL-C, JST, Molex, etc.).
   Free-form SVG authoring is for the rare custom bulkhead or splice ring.
   =================================================================== */
const CATALOG = [
  // Aerospace / MIL-C
  { id:'c01', mfr:'Amphenol',  pn:'MS3470L8-33P',     family:'circular',    name:'MS3470 · 8-pin',       pins: 8, gender:'plug',  shell:'A',  series:'MIL-C-26482', sealed:true,  contact:'crimp',  awg:'20-24', tags:['mil-c-26482','flight-critical'] },
  { id:'c02', mfr:'Amphenol',  pn:'MS3476W19-32S',    family:'circular',    name:'MS3476 · 19-pin',      pins:19, gender:'recep', shell:'C',  series:'MIL-C-26482', sealed:true,  contact:'crimp',  awg:'20-24', tags:['mil-c-26482'] },
  { id:'c03', mfr:'TE',        pn:'D38999/26WB35PN',  family:'circular',    name:'D38999 · 13-pin',      pins:13, gender:'plug',  shell:'11', series:'D38999 III',  sealed:true,  contact:'crimp',  awg:'22-26', tags:['series III','flight-critical'] },
  { id:'c04', mfr:'TE',        pn:'D38999/26WC35SN',  family:'circular',    name:'D38999 · 24-pin',      pins:24, gender:'recep', shell:'17', series:'D38999 III',  sealed:true,  contact:'crimp',  awg:'22-26', tags:['series III','flight-critical'] },
  // Automotive / off-highway
  { id:'c05', mfr:'Deutsch',   pn:'DT04-2P',          family:'deutsch',     name:'DT04-2P',              pins: 2, gender:'plug',  shell:'—',  series:'DT',          sealed:true,  contact:'crimp',  awg:'14-18', tags:['automotive','ip67'] },
  { id:'c06', mfr:'Deutsch',   pn:'DT04-4P',          family:'deutsch',     name:'DT04-4P',              pins: 4, gender:'plug',  shell:'—',  series:'DT',          sealed:true,  contact:'crimp',  awg:'14-18', tags:['automotive','ip67'] },
  { id:'c07', mfr:'Deutsch',   pn:'DT06-6S',          family:'deutsch',     name:'DT06-6S',              pins: 6, gender:'recep', shell:'—',  series:'DT',          sealed:true,  contact:'crimp',  awg:'14-18', tags:['automotive'] },
  { id:'c08', mfr:'Deutsch',   pn:'DTM04-12PA',       family:'deutsch',     name:'DTM04-12PA',           pins:12, gender:'plug',  shell:'—',  series:'DTM',         sealed:true,  contact:'crimp',  awg:'18-20', tags:['automotive','ecu'] },
  // Sealed automotive
  { id:'c09', mfr:'TE',        pn:'1-770966-0',       family:'ampseal',     name:'AmpSeal 23-pin',       pins:23, gender:'plug',  shell:'—',  series:'AmpSeal',     sealed:true,  contact:'crimp',  awg:'16-22', tags:['automotive','ecu'] },
  { id:'c10', mfr:'TE',        pn:'1-776164-1',       family:'ampseal',     name:'AmpSeal 35-pin',       pins:35, gender:'plug',  shell:'—',  series:'AmpSeal',     sealed:true,  contact:'crimp',  awg:'16-22', tags:['automotive'] },
  { id:'c11', mfr:'Delphi',    pn:'12015193',         family:'weatherpack', name:'Weatherpack 2-pin',    pins: 2, gender:'plug',  shell:'—',  series:'WP',          sealed:true,  contact:'crimp',  awg:'14-20', tags:['automotive'] },
  { id:'c12', mfr:'Delphi',    pn:'12010974',         family:'weatherpack', name:'Weatherpack 4-pin',    pins: 4, gender:'recep', shell:'—',  series:'WP',          sealed:true,  contact:'crimp',  awg:'14-20', tags:['automotive'] },
  // D-Sub serial
  { id:'c13', mfr:'Amphenol',  pn:'L17DEFRA9PA309',   family:'dsub',        name:'DB9',                  pins: 9, gender:'plug',  shell:'—',  series:'D-Sub',       sealed:false, contact:'solder', awg:'24-28', tags:['serial','rs-232'] },
  { id:'c14', mfr:'Amphenol',  pn:'L17DBFRA25SA309',  family:'dsub',        name:'DB25',                 pins:25, gender:'recep', shell:'—',  series:'D-Sub',       sealed:false, contact:'solder', awg:'24-28', tags:['parallel'] },
  // Molex sealed
  { id:'c15', mfr:'Molex',     pn:'33472-1206',       family:'molex',       name:'MX150 · 8-pos',        pins: 8, gender:'plug',  shell:'—',  series:'MX150',       sealed:true,  contact:'crimp',  awg:'16-22', tags:['automotive','sealed'] },
  { id:'c16', mfr:'Molex',     pn:'33482-1201',       family:'molex',       name:'MX150 · 12-pos',       pins:12, gender:'recep', shell:'—',  series:'MX150',       sealed:true,  contact:'crimp',  awg:'16-22', tags:['automotive'] },
  // Tiny board-level
  { id:'c17', mfr:'JST',       pn:'PHR-4',            family:'jst',         name:'JST PH · 4-pin',       pins: 4, gender:'plug',  shell:'—',  series:'PH',          sealed:false, contact:'crimp',  awg:'24-32', tags:['board','2.0mm'] },
  { id:'c18', mfr:'JST',       pn:'XHP-6',            family:'jst',         name:'JST XH · 6-pin',       pins: 6, gender:'plug',  shell:'—',  series:'XH',          sealed:false, contact:'crimp',  awg:'22-26', tags:['board','2.5mm'] },
];

/* ----- Symbol creation widget -----
   Two paths:
     · From catalog (default, 90% case) — search a vendor catalog, click
       to add. All metadata (pin count, gender, contact, AWG range) comes
       in automatically.
     · Build custom — for proprietary connectors. Family + pin layout
       (rows×cols or linear/circular) + pinout table. Shape primitives
       only — no raw SVG/XY editing in the front door.
*/
const SymbolCreator = ({ onClose, onCreate }) => {
  const [path, setPath] = useState('catalog');
  return (
    <div className="kf-symmodal-bg fade-in" onClick={onClose} role="dialog" aria-modal="true" aria-label="Add connector">
      <div className="kf-symmodal kf-symmodal-wide" onClick={e => e.stopPropagation()}>
        <div className="kf-symmodal-head">
          <div>
            <h2>Add a connector to the library</h2>
            <p>Pick from <b>1,800+ standard parts</b> in the catalog, or build a custom symbol for proprietary connectors.</p>
          </div>
          <IconBtn icon="x" label="Close" onClick={onClose}/>
        </div>

        <div className="kf-sym-tabs" role="tablist" aria-label="Creation mode">
          <button role="tab" aria-selected={path === 'catalog'} className={`kf-sym-tab ${path === 'catalog' ? 'on' : ''}`} onClick={() => setPath('catalog')}>
            <Icon name="library" size={14}/> From catalog
            <Chip size="sm">{CATALOG.length * 100}+</Chip>
          </button>
          <button role="tab" aria-selected={path === 'build'} className={`kf-sym-tab ${path === 'build' ? 'on' : ''}`} onClick={() => setPath('build')}>
            <Icon name="tune" size={14}/> Build custom
          </button>
          <button role="tab" aria-selected={path === 'image'} className={`kf-sym-tab ${path === 'image' ? 'on' : ''}`} onClick={() => setPath('image')}>
            <Icon name="image" size={14}/> From image
            <Chip size="sm" tone="info">beta</Chip>
          </button>
        </div>

        {path === 'catalog' && <CatalogPicker onPick={onCreate}/>}
        {path === 'build'   && <CustomBuilder onCreate={onCreate} onCancel={onClose}/>}
        {path === 'image'   && <ImageBuilder onCreate={onCreate} onCancel={onClose}/>}
      </div>
    </div>
  );
};

/* ----- Path A: Catalog ----- */
const CatalogPicker = ({ onPick }) => {
  const [q, setQ] = useState('');
  const [fam, setFam] = useState('all');
  const [sealed, setSealed] = useState('any');
  const families = useMemo(() => {
    const out = {};
    CATALOG.forEach(c => { out[c.family] = (out[c.family] || 0) + 1; });
    return out;
  }, []);
  const filtered = useMemo(() => {
    let list = CATALOG;
    if (fam !== 'all') list = list.filter(c => c.family === fam);
    if (sealed === 'sealed')   list = list.filter(c => c.sealed);
    if (sealed === 'unsealed') list = list.filter(c => !c.sealed);
    const needle = q.trim().toLowerCase();
    if (needle) list = list.filter(c => (c.pn + ' ' + c.name + ' ' + c.mfr + ' ' + c.tags.join(' ')).toLowerCase().includes(needle));
    return list;
  }, [q, fam, sealed]);

  return (
    <div className="kf-symmodal-body kf-cat-pick">
      <div className="kf-cat-filters">
        <div className="kf-projects-search" style={{ flex: 1 }}>
          <Icon name="search" size={14} stroke="var(--t-3)"/>
          <input value={q} onChange={e => setQ(e.target.value)}
                 placeholder="Search by part number, manufacturer, or tag (e.g. ‘DT04’, ‘D38999’, ‘sealed automotive’)…"
                 autoFocus aria-label="Search catalog"/>
          {q && <button className="kf-projects-clear" onClick={() => setQ('')} aria-label="Clear"><Icon name="x" size={12}/></button>}
        </div>
        <div className="kf-seg">
          {[['any','Any'],['sealed','Sealed'],['unsealed','Unsealed']].map(([k, l]) => (
            <button key={k} className={sealed === k ? 'on' : ''} onClick={() => setSealed(k)}>{l}</button>
          ))}
        </div>
      </div>

      <div className="kf-cat-layout">
        {/* Family rail */}
        <nav className="kf-cat-rail" aria-label="Connector family">
          <button className={`kf-cat-rail-item ${fam === 'all' ? 'on' : ''}`} onClick={() => setFam('all')}>
            <span>All families</span><span className="num">{CATALOG.length}</span>
          </button>
          {SYMBOL_FAMILIES.filter(f => f.k !== 'all' && families[f.k]).map(f => (
            <button key={f.k} className={`kf-cat-rail-item ${fam === f.k ? 'on' : ''}`} onClick={() => setFam(f.k)}>
              <span>{f.l}</span><span className="num">{families[f.k]}</span>
            </button>
          ))}
        </nav>

        {/* Results */}
        <div className="kf-cat-results">
          {filtered.length === 0 ? (
            <EmptyState compact icon="search" title="No matching parts"
                        hint="Try a different part number or family."/>
          ) : (
            filtered.map(c => (
              <button key={c.id} className="kf-cat-card"
                      onClick={() => onPick({ ...c, used: 0, updated: new Date().toISOString().slice(0,10) })}>
                <div className="kf-cat-card-glyph">
                  <SymbolGlyph row={c}/>
                </div>
                <div className="kf-cat-card-main">
                  <div className="kf-cat-card-pn num">{c.pn}</div>
                  <div className="kf-cat-card-name">{c.name}</div>
                  <div className="kf-cat-card-meta">
                    <span>{c.mfr}</span>
                    <span>·</span>
                    <span><span className="num">{c.pins}</span>p · {c.gender}</span>
                    {c.sealed && <><span>·</span><Chip size="sm" tone="success">sealed</Chip></>}
                  </div>
                </div>
                <div className="kf-cat-card-cta">
                  <Btn size="sm" kind="ghost" icon="plus">Add</Btn>
                </div>
              </button>
            ))
          )}
        </div>
      </div>
    </div>
  );
};

/* ----- Path B: Build custom ----- */
const CustomBuilder = ({ onCreate, onCancel }) => {
  const [family, setFamily] = useState('circular');
  const [name, setName]     = useState('');
  const [mfr, setMfr]       = useState('');
  const [pn, setPn]         = useState('');
  const [gender, setGender] = useState('plug');
  const [shell, setShell]   = useState('');
  const [contact, setContact] = useState('crimp');
  const [awg, setAwg]       = useState('22-24');
  const [sealed, setSealed] = useState(true);

  // Parametric layout — every dimension is a NUMBER, not a drawing op.
  // The preview redraws to-scale from these so the user can dial in the
  // actual connector geometry without dragging vectors.
  const [dims, setDims] = useState({
    rows: 2, cols: 4,
    pitch: 5.08,                 // pin center-to-center, in mm (0.2" default)
    shellShape: 'rect',          // 'rect' | 'circle' — outline
    shellW: 0,                   // 0 = auto-size from grid + margin
    shellH: 0,
    corner: 4,
    autoShell: true,             // recompute shell W/H from grid each tick
  });
  const setDim = (k, v) => setDims(d => ({ ...d, [k]: v }));
  const pinCount = dims.rows * dims.cols;

  const [showAdvanced, setShowAdvanced] = useState(false);

  // Pinout rows — one per pin. Auto-grow/shrink to pin count.
  const [pins, setPins] = useState(() => Array.from({ length: 8 }, (_, i) => ({
    label: String(i + 1), signal: '', awg: '', notes: '',
  })));
  useEffect(() => {
    setPins(prev => {
      const n = pinCount;
      if (prev.length === n) return prev;
      if (n > prev.length) {
        return [...prev, ...Array.from({ length: n - prev.length }, (_, i) => ({
          label: String(prev.length + i + 1), signal: '', awg: '', notes: '',
        }))];
      }
      return prev.slice(0, n);
    });
  }, [pinCount]);
  const updatePin = (i, key, val) => setPins(p => p.map((pin, j) => j === i ? { ...pin, [key]: val } : pin));
  const renumber = () => setPins(p => p.map((pin, i) => ({ ...pin, label: String(i + 1) })));
  const alphabetize = () => setPins(p => p.map((pin, i) => ({ ...pin, label: String.fromCharCode(65 + i) })));

  // Auto-compute shell when in auto mode.
  const effectiveDims = useMemo(() => {
    if (!dims.autoShell) return dims;
    const margin = dims.pitch * 1.6;
    const gridW = (dims.cols - 1) * dims.pitch;
    const gridH = (dims.rows - 1) * dims.pitch;
    // Shapes that need a bounding-square to keep pins inside.
    const enclosing = ['circle','hex','diamond','triangle','cross','arrow'];
    if (enclosing.includes(dims.shellShape)) {
      const diam = Math.hypot(gridW, gridH) + margin * 2;
      // Triangle needs extra vertical headroom to fit pins inside the
      // tapered top; arrow needs horizontal headroom for its tip.
      const w = dims.shellShape === 'arrow'    ? diam * 1.4 : diam;
      const h = dims.shellShape === 'triangle' ? diam * 1.4 : diam;
      return { ...dims, shellW: w, shellH: h };
    }
    // Rectangular + dshape: snug fit.
    return {
      ...dims,
      shellW: gridW + margin * 2 + (dims.shellShape === 'dshape' ? margin : 0),
      shellH: gridH + margin * 2,
    };
  }, [dims]);

  const canSubmit = name.trim() && pinCount > 0;
  const preview = {
    family, name: name || pn || 'New connector',
    pins: pinCount, gender, shell,
    dims: effectiveDims,
    tags: [],
  };

  return (
    <>
      <div className="kf-symmodal-body kf-build">
        <div className="kf-build-grid">
          {/* LEFT — identity & shape */}
          <div className="kf-build-col">
            <div className="kf-build-section">
              <div className="kf-build-section-head">Identity</div>
              <div className="kf-form">
                <div className="kf-form-row">
                  <div className="kf-field" style={{ flex: 2 }}>
                    <label>Connector name <span className="kf-req">*</span></label>
                    <input className="kf-input" placeholder="e.g. Mira-S aft bulkhead" value={name} onChange={e => setName(e.target.value)} autoFocus/>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Manufacturer</label>
                    <input className="kf-input" placeholder="In-house, TE, Deutsch…" value={mfr} onChange={e => setMfr(e.target.value)}/>
                  </div>
                  <div className="kf-field">
                    <label>Part number</label>
                    <input className="kf-input num" placeholder="—" value={pn} onChange={e => setPn(e.target.value)}/>
                  </div>
                </div>
              </div>
            </div>

            <div className="kf-build-section">
              <div className="kf-build-section-head">Outline & pin grid (parametric)</div>
              <div className="kf-form">
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Family</label>
                    <select className="kf-input" value={family} onChange={e => setFamily(e.target.value)}>
                      {SYMBOL_FAMILIES.filter(f => f.k !== 'all').map(f => <option key={f.k} value={f.k}>{f.l}</option>)}
                    </select>
                  </div>
                  <div className="kf-field">
                    <label>Shell shape</label>
                    <div className="kf-shape-grid">
                      {[
                        { k:'rect',     l:'Rect' },
                        { k:'circle',   l:'Round' },
                        { k:'hex',      l:'Hex' },
                        { k:'diamond',  l:'Diamond' },
                        { k:'triangle', l:'Tri' },
                        { k:'dshape',   l:'D-Sub' },
                        { k:'arrow',    l:'Arrow' },
                        { k:'cross',    l:'Cross' },
                      ].map(s => (
                        <button key={s.k}
                                className={`kf-shape-tile ${dims.shellShape === s.k ? 'on' : ''}`}
                                onClick={() => setDim('shellShape', s.k)}
                                title={s.l}>
                          <SymbolGlyph row={{ dims: { rows: 2, cols: 2, pitch: 6, shellShape: s.k, corner: 3, autoShell: true, shellW: 24, shellH: 24 } }}/>
                          <span>{s.l}</span>
                        </button>
                      ))}
                    </div>
                  </div>
                </div>

                {/* Pin grid — rows × cols. Pin count is computed from these. */}
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Pin grid</label>
                    <div className="kf-dim-grid">
                      <div className="kf-dim-cell">
                        <input className="kf-input num" type="number" min="1" max="32" value={dims.rows}
                               onChange={e => setDim('rows', Math.max(1, +e.target.value || 1))}/>
                        <span className="kf-dim-l">rows</span>
                      </div>
                      <span className="kf-dim-x">×</span>
                      <div className="kf-dim-cell">
                        <input className="kf-input num" type="number" min="1" max="32" value={dims.cols}
                               onChange={e => setDim('cols', Math.max(1, +e.target.value || 1))}/>
                        <span className="kf-dim-l">cols</span>
                      </div>
                      <div className="kf-dim-cell kf-dim-cell-total">
                        <span className="kf-dim-total num">{pinCount}</span>
                        <span className="kf-dim-l">pins</span>
                      </div>
                    </div>
                  </div>
                  <div className="kf-field">
                    <label>Pin pitch (mm)</label>
                    <input className="kf-input num" type="number" step="0.01" min="0.5" max="20"
                           value={dims.pitch}
                           onChange={e => setDim('pitch', Math.max(.5, +e.target.value || .5))}/>
                    <div className="kf-field-hint">Center-to-center spacing. 2.54 (0.1″), 5.08 (0.2″) are common.</div>
                  </div>
                </div>

                {/* Outline dimensions */}
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Shell width (mm)</label>
                    <input className="kf-input num" type="number" step="0.1" disabled={dims.autoShell}
                           value={dims.autoShell ? effectiveDims.shellW.toFixed(1) : dims.shellW}
                           onChange={e => setDim('shellW', +e.target.value || 0)}/>
                  </div>
                  <div className="kf-field">
                    <label>Shell height (mm)</label>
                    <input className="kf-input num" type="number" step="0.1" disabled={dims.autoShell}
                           value={dims.autoShell ? effectiveDims.shellH.toFixed(1) : dims.shellH}
                           onChange={e => setDim('shellH', +e.target.value || 0)}/>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Corner radius (mm)</label>
                    <input className="kf-input num" type="number" step="0.5" min="0" max="20"
                           value={dims.corner}
                           onChange={e => setDim('corner', Math.max(0, +e.target.value || 0))}/>
                  </div>
                  <div className="kf-checkfield" style={{ alignSelf: 'flex-end' }}>
                    <input id="b-auto-shell" type="checkbox" checked={dims.autoShell}
                           onChange={e => setDim('autoShell', e.target.checked)}/>
                    <label htmlFor="b-auto-shell">Auto-size shell from pin grid</label>
                  </div>
                </div>

                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Gender</label>
                    <div className="kf-seg">
                      {[['plug','Plug'],['recep','Receptacle'],['—','N/A']].map(([k, l]) => (
                        <button key={k} className={gender === k ? 'on' : ''} onClick={() => setGender(k)}>{l}</button>
                      ))}
                    </div>
                  </div>
                  <div className="kf-field">
                    <label>Shell size designator</label>
                    <input className="kf-input" placeholder="e.g. 11, A, —" value={shell} onChange={e => setShell(e.target.value)}/>
                  </div>
                </div>
              </div>
            </div>

            <div className="kf-build-section">
              <div className="kf-build-section-head">Termination</div>
              <div className="kf-form">
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Contact type</label>
                    <div className="kf-seg">
                      {[['crimp','Crimp'],['solder','Solder'],['idc','IDC'],['screw','Screw']].map(([k, l]) => (
                        <button key={k} className={contact === k ? 'on' : ''} onClick={() => setContact(k)}>{l}</button>
                      ))}
                    </div>
                  </div>
                  <div className="kf-field">
                    <label>Wire gauge range</label>
                    <select className="kf-input" value={awg} onChange={e => setAwg(e.target.value)}>
                      <option>28-32</option><option>24-28</option><option>22-26</option>
                      <option>22-24</option><option>20-24</option><option>18-22</option>
                      <option>16-22</option><option>16-20</option><option>14-20</option><option>14-18</option><option>12-16</option>
                    </select>
                  </div>
                </div>
                <div className="kf-checkfield">
                  <input id="b-sealed" type="checkbox" checked={sealed} onChange={e => setSealed(e.target.checked)}/>
                  <label htmlFor="b-sealed">Environmentally sealed (IP67+)</label>
                </div>
              </div>
            </div>

            {/* Power-user escape hatch — hidden by default, opt-in. */}
            <button className="kf-build-advanced-toggle" onClick={() => setShowAdvanced(s => !s)}>
              <Icon name={showAdvanced ? 'chevD' : 'chevR'} size={12}/>
              Advanced shape (SVG / outline override)
            </button>
            {showAdvanced && (
              <div className="kf-build-advanced">
                <div className="kf-field">
                  <label>Custom outline (SVG path)</label>
                  <textarea className="kf-input num" rows="2" placeholder="M -20,-12 L 20,-12 L 20,12 L -20,12 Z" defaultValue=""/>
                  <div className="kf-field-hint">Optional — only if your connector has a non-standard body shape. Skip unless you really need it.</div>
                </div>
              </div>
            )}
          </div>

          {/* RIGHT — pinout table + live preview */}
          <div className="kf-build-col">
            <div className="kf-build-section">
              <div className="kf-build-section-head">
                Pinout
                <div className="kf-build-section-actions">
                  <Btn size="sm" kind="ghost" onClick={renumber}>1, 2, 3…</Btn>
                  <Btn size="sm" kind="ghost" onClick={alphabetize}>A, B, C…</Btn>
                </div>
              </div>
              <div className="kf-pinout-table">
                <div className="kf-pinout-head">
                  <div>Pin</div>
                  <div>Signal</div>
                  <div>AWG</div>
                  <div>Notes</div>
                </div>
                <div className="kf-pinout-rows">
                  {pins.map((p, i) => (
                    <div key={i} className="kf-pinout-row">
                      <input className="kf-input kf-input-sm num" value={p.label} onChange={e => updatePin(i, 'label', e.target.value)}/>
                      <input className="kf-input kf-input-sm" placeholder={i === 0 ? 'CAN_H' : i === 1 ? 'CAN_L' : i === 2 ? 'GND' : i === 3 ? '+12V' : `Pin ${p.label}`} value={p.signal} onChange={e => updatePin(i, 'signal', e.target.value)}/>
                      <input className="kf-input kf-input-sm" placeholder="22" value={p.awg} onChange={e => updatePin(i, 'awg', e.target.value)}/>
                      <input className="kf-input kf-input-sm" placeholder="—" value={p.notes} onChange={e => updatePin(i, 'notes', e.target.value)}/>
                    </div>
                  ))}
                </div>
              </div>
              <div className="kf-field-hint" style={{ marginTop: 10 }}>
                Signal names + AWG export to the wire-prep CSV. Empty is fine — fill them in later as the harness develops.
              </div>
            </div>

            <div className="kf-build-section">
              <div className="kf-build-section-head">Live preview</div>
              <div className="kf-symcard kf-symcard-preview-card">
                <div className="kf-symcard-preview"><SymbolGlyph row={preview}/></div>
                <div className="kf-symcard-body">
                  <div className="kf-symcard-name">{preview.name}</div>
                  <div className="kf-symcard-meta">
                    <span className="num">{preview.pins}p</span>
                    {gender !== '—' && <Chip size="sm">{gender}</Chip>}
                    {sealed && <Chip size="sm" tone="success">sealed</Chip>}
                  </div>
                  <div className="kf-symcard-foot">
                    <span>{SYMBOL_FAMILIES.find(f => f.k === family)?.l}</span>
                    <span>{contact} · {awg} AWG</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div className="kf-symmodal-foot">
        <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
        <div className="spacer"/>
        <Btn kind="primary" icon="check" disabled={!canSubmit} onClick={() => onCreate({
          family, name: name.trim(), mfr, pn, pins: pinCount, gender, shell, contact, awg, sealed,
          dims: effectiveDims, pinout: pins,
          tags: [contact, sealed ? 'sealed' : 'unsealed'].filter(Boolean),
        })}>
          Add to library
        </Btn>
      </div>
    </>
  );
};

/* ----- Symbol importer -----
   Lets the user paste a CSV / DXF reference or pick a file to import
   multiple symbols at once. Demo: shows three "matched" rows the user
   can confirm. */
/* ----- Path C: From image -----
   Drop a JPG/PNG of a connector face. The user marks pin centers by
   clicking on the preview, picks the outline shape, and the symbol is
   reconstructed parametrically. Real auto-detect would need OpenCV in
   the backend; here we provide the manual mark-up workflow that drives
   the same parametric model used by Build custom. */
const ImageBuilder = ({ onCreate, onCancel }) => {
  const toast = useToast();
  const [imgUrl, setImgUrl] = useState(null);
  const [imgName, setImgName] = useState('');
  const [pins, setPins] = useState([]);       // {x,y in 0..1 normalised}
  const [shape, setShape] = useState('circle');
  const [scaleMm, setScaleMm] = useState(30); // assumed real width of the image
  const [name, setName]   = useState('');
  const [mfr, setMfr]     = useState('');
  const [pn, setPn]       = useState('');
  const [gender, setGender] = useState('plug');
  const [opacity, setOpacity] = useState(0.55);
  const imgRef = useRef(null);

  const handleFile = (file) => {
    if (!file) return;
    if (!file.type.startsWith('image/')) { toast.warn('Only JPG / PNG files'); return; }
    const url = URL.createObjectURL(file);
    setImgUrl(url);
    setImgName(file.name);
    setPins([]);
    toast.ok('Image loaded', file.name);
  };
  const onDrop = (e) => { e.preventDefault(); handleFile(e.dataTransfer.files?.[0]); };
  const onPick = (e) => handleFile(e.target.files?.[0]);

  const addPin = (e) => {
    if (!imgRef.current) return;
    const r = imgRef.current.getBoundingClientRect();
    const x = (e.clientX - r.left) / r.width;
    const y = (e.clientY - r.top)  / r.height;
    setPins(p => [...p, { x, y, label: String(p.length + 1) }]);
  };
  const removePin = (i, e) => { e.stopPropagation(); setPins(p => p.filter((_, j) => j !== i)); };

  // Build a parametric dims object from the marked pins so the resulting
  // symbol renders using the same engine as Build custom. We use a 1×N
  // fake grid and store the actual normalised positions in pins[] for
  // the preview card. (For the saved symbol, we approximate with the
  // bounding box dims so it shows up properly in the library.)
  const computed = useMemo(() => {
    if (pins.length === 0) return null;
    const xs = pins.map(p => p.x);
    const ys = pins.map(p => p.y);
    const minX = Math.min(...xs), maxX = Math.max(...xs);
    const minY = Math.min(...ys), maxY = Math.max(...ys);
    const wMm = scaleMm;                          // image full width in mm
    const hMm = wMm * (maxY > minY ? 1 : 1);      // assume square aspect for now
    return {
      bbox: { minX, maxX, minY, maxY },
      bboxMm: {
        w: (maxX - minX) * wMm,
        h: (maxY - minY) * wMm,
      },
      shellMm: shape === 'circle' ? wMm : wMm,
    };
  }, [pins, scaleMm, shape]);

  const canSubmit = imgUrl && name.trim() && pins.length > 0;

  return (
    <>
      <div className="kf-symmodal-body kf-img-build">
        <div className="kf-img-grid">
          {/* LEFT — image + pin marking */}
          <div className="kf-img-pane">
            {!imgUrl ? (
              <div className="kf-img-drop"
                   onDragOver={e => e.preventDefault()}
                   onDrop={onDrop}>
                <Icon name="image" size={28} stroke="var(--t-3)"/>
                <div className="kf-img-drop-title">Drop a connector photo here</div>
                <div className="kf-img-drop-hint">JPG, PNG, or WebP. Front-on view of the pin face works best.</div>
                <label className="kf-btn kf-btn-primary kf-btn-md" style={{ cursor: 'pointer', marginTop: 12 }}>
                  <Icon name="import_" size={12}/>
                  <span className="kf-btn-label">Browse files</span>
                  <input type="file" accept="image/*" style={{ display: 'none' }} onChange={onPick}/>
                </label>
              </div>
            ) : (
              <>
                <div className="kf-img-canvas-wrap">
                  <div className="kf-img-canvas" onClick={addPin}>
                    <img ref={imgRef} src={imgUrl} alt={imgName} style={{ opacity }} draggable={false}/>
                    {/* Outline guide */}
                    {pins.length > 1 && (
                      <svg className="kf-img-outline" viewBox="0 0 100 100" preserveAspectRatio="none">
                        {(() => {
                          if (shape === 'rect' || shape === 'dshape') {
                            const xs = pins.map(p => p.x * 100), ys = pins.map(p => p.y * 100);
                            const pad = 8;
                            const x = Math.min(...xs) - pad, y = Math.min(...ys) - pad;
                            const w = Math.max(...xs) - Math.min(...xs) + pad * 2;
                            const h = Math.max(...ys) - Math.min(...ys) + pad * 2;
                            return <rect x={x} y={y} width={w} height={h} rx="3" fill="none" stroke="var(--accent)" strokeWidth=".8" strokeDasharray="2 2"/>;
                          }
                          if (shape === 'circle') {
                            const cx = pins.reduce((a, p) => a + p.x, 0) / pins.length * 100;
                            const cy = pins.reduce((a, p) => a + p.y, 0) / pins.length * 100;
                            const r = Math.max(...pins.map(p => Math.hypot(p.x * 100 - cx, p.y * 100 - cy))) + 10;
                            return <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--accent)" strokeWidth=".8" strokeDasharray="2 2"/>;
                          }
                          return null;
                        })()}
                      </svg>
                    )}
                    {/* Pin markers */}
                    {pins.map((p, i) => (
                      <div key={i} className="kf-img-pin" style={{ left: `${p.x * 100}%`, top: `${p.y * 100}%` }}>
                        <span className="kf-img-pin-dot"/>
                        <span className="kf-img-pin-label">{p.label}</span>
                        <button className="kf-img-pin-rm" onClick={e => removePin(i, e)} aria-label={`Remove pin ${p.label}`}>×</button>
                      </div>
                    ))}
                  </div>
                </div>
                <div className="kf-img-canvas-bar">
                  <span className="kf-admin-muted" style={{ fontSize: 11 }}>
                    <span className="num">{pins.length}</span> pins · click anywhere to add · click × to remove
                  </span>
                  <div className="spacer"/>
                  <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: 'var(--t-3)' }}>
                    Image opacity
                    <input type="range" min="0.2" max="1" step="0.05" value={opacity} onChange={e => setOpacity(+e.target.value)} style={{ width: 80 }}/>
                  </label>
                  <Btn size="sm" kind="ghost" onClick={() => setPins([])}>Reset pins</Btn>
                  <Btn size="sm" kind="ghost" onClick={() => { setImgUrl(null); setPins([]); }}>Change image</Btn>
                </div>
              </>
            )}
          </div>

          {/* RIGHT — config + preview */}
          <div className="kf-build-col">
            <div className="kf-build-section">
              <div className="kf-build-section-head">Identity</div>
              <div className="kf-form">
                <div className="kf-form-row">
                  <div className="kf-field" style={{ flex: 2 }}>
                    <label>Connector name <span className="kf-req">*</span></label>
                    <input className="kf-input" placeholder="e.g. Custom bulkhead" value={name} onChange={e => setName(e.target.value)}/>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Manufacturer</label>
                    <input className="kf-input" placeholder="In-house" value={mfr} onChange={e => setMfr(e.target.value)}/>
                  </div>
                  <div className="kf-field">
                    <label>Part number</label>
                    <input className="kf-input num" placeholder="—" value={pn} onChange={e => setPn(e.target.value)}/>
                  </div>
                </div>
              </div>
            </div>

            <div className="kf-build-section">
              <div className="kf-build-section-head">Outline & scale</div>
              <div className="kf-form">
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Detected outline</label>
                    <div className="kf-shape-grid kf-shape-grid-compact">
                      {[
                        { k:'rect',    l:'Rect' },
                        { k:'circle',  l:'Round' },
                        { k:'hex',     l:'Hex' },
                        { k:'dshape',  l:'D-Sub' },
                      ].map(s => (
                        <button key={s.k}
                                className={`kf-shape-tile ${shape === s.k ? 'on' : ''}`}
                                onClick={() => setShape(s.k)}>
                          <SymbolGlyph row={{ dims: { rows: 2, cols: 2, pitch: 6, shellShape: s.k, corner: 3, shellW: 24, shellH: 24 } }}/>
                          <span>{s.l}</span>
                        </button>
                      ))}
                    </div>
                  </div>
                </div>
                <div className="kf-form-row">
                  <div className="kf-field">
                    <label>Image width (mm)</label>
                    <input className="kf-input num" type="number" min="1" step="0.5"
                           value={scaleMm} onChange={e => setScaleMm(+e.target.value || 1)}/>
                    <div className="kf-field-hint">
                      Real-world width of the connector face shown in the photo. Used to set scale.
                      {computed && (
                        <> · Bounding box: <span className="num">{computed.bboxMm.w.toFixed(1)} × {computed.bboxMm.h.toFixed(1)}</span> mm</>
                      )}
                    </div>
                  </div>
                  <div className="kf-field">
                    <label>Gender</label>
                    <div className="kf-seg">
                      {[['plug','Plug'],['recep','Recep'],['—','N/A']].map(([k, l]) => (
                        <button key={k} className={gender === k ? 'on' : ''} onClick={() => setGender(k)}>{l}</button>
                      ))}
                    </div>
                  </div>
                </div>
              </div>
            </div>

            <div className="kf-build-section">
              <div className="kf-build-section-head">Pins detected</div>
              {pins.length === 0 ? (
                <div className="kf-img-empty">
                  <Icon name="info" size={14} stroke="var(--t-3)"/>
                  Click on the photo to mark pin centers. The order you click sets the pin numbers (1, 2, 3…).
                </div>
              ) : (
                <div className="kf-img-pin-list">
                  {pins.map((p, i) => (
                    <div key={i} className="kf-img-pin-row">
                      <span className="kf-img-pin-dot kf-img-pin-dot-row"/>
                      <span className="kf-img-pin-row-label num">{p.label}</span>
                      <span className="kf-admin-muted num" style={{ fontSize: 11 }}>
                        ({(p.x * 100).toFixed(0)}%, {(p.y * 100).toFixed(0)}%)
                      </span>
                      <div className="spacer"/>
                      <button className="kf-img-pin-row-rm" onClick={() => setPins(pp => pp.filter((_, j) => j !== i))} aria-label={`Delete pin ${p.label}`}>×</button>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
      <div className="kf-symmodal-foot">
        <Btn kind="ghost" onClick={onCancel}>Cancel</Btn>
        <div className="spacer"/>
        <Btn kind="primary" icon="check" disabled={!canSubmit} onClick={() => {
          // Approximate a 1-row grid for now; pin positions are stored
          // for a future "free-form" renderer. The symbol still shows up
          // in the library with the chosen shape and pin count.
          onCreate({
            family: shape === 'circle' ? 'circular' : shape === 'dshape' ? 'dsub' : 'custom',
            name: name.trim(), mfr, pn, pins: pins.length, gender, shell: '—',
            dims: { rows: 1, cols: Math.max(1, pins.length), pitch: scaleMm / Math.max(1, pins.length),
                    shellShape: shape, shellW: scaleMm, shellH: scaleMm, corner: 4, autoShell: false },
            sourceImage: imgUrl, sourcePins: pins,
            tags: ['from-image', 'custom'],
          });
        }}>
          Save as new symbol
        </Btn>
      </div>
    </>
  );
};

const SymbolImporter = ({ onClose, onImport }) => {
  const [source, setSource] = useState('csv');
  const demoMatches = [
    { id:'i1', family:'deutsch',  name:'DT06-12S',  pins:12, gender:'recep', shell:'—', tags:['imported'], pick:true },
    { id:'i2', family:'circular', name:'MS3475 · 14-pin', pins:14, gender:'plug', shell:'B', tags:['imported'], pick:true },
    { id:'i3', family:'molex',    name:'MX150 · 12-pos', pins:12, gender:'plug', shell:'—', tags:['imported'], pick:false },
  ];
  const [matches, setMatches] = useState(demoMatches);
  const toggle = (id) => setMatches(m => m.map(r => r.id === id ? { ...r, pick: !r.pick } : r));
  const picked = matches.filter(m => m.pick);

  return (
    <div className="kf-symmodal-bg fade-in" onClick={onClose}>
      <div className="kf-symmodal" onClick={e => e.stopPropagation()}>
        <div className="kf-symmodal-head">
          <div>
            <h2>Import symbols</h2>
            <p>Bring in symbols from a CSV file, KiCad library, or vendor catalogue.</p>
          </div>
          <IconBtn icon="x" label="Close" onClick={onClose}/>
        </div>
        <div className="kf-symmodal-body">
          <div className="kf-seg" style={{ marginBottom: 14 }}>
            <button className={source === 'csv' ? 'on' : ''}    onClick={() => setSource('csv')}>CSV</button>
            <button className={source === 'kicad' ? 'on' : ''}  onClick={() => setSource('kicad')}>KiCad .lib</button>
            <button className={source === 'vendor' ? 'on' : ''} onClick={() => setSource('vendor')}>Vendor catalogue</button>
          </div>

          <div className="kf-ws-logo-drop" style={{ marginBottom: 14 }}>
            <Icon name="import_" size={20} stroke="var(--t-3)"/>
            <div>
              <div style={{ fontWeight: 600, color:'var(--t-1)' }}>Drop a {source.toUpperCase()} file here</div>
              <div className="kf-admin-muted">or paste {source === 'csv' ? 'rows like: family,name,pins,gender,shell' : source === 'kicad' ? 'a KiCad symbol library' : 'a Deutsch/AMP/TE part number'}</div>
            </div>
            <Btn kind="outline" size="sm">Browse files</Btn>
          </div>

          <div className="kf-sym-import-matches">
            <div className="kf-sym-import-head">Matched symbols · {picked.length}/{matches.length} selected</div>
            {matches.map(m => (
              <label key={m.id} className={`kf-sym-import-row ${m.pick ? 'on' : ''}`}>
                <input type="checkbox" checked={m.pick} onChange={() => toggle(m.id)}/>
                <div className="kf-sym-import-glyph"><SymbolGlyph row={m}/></div>
                <div style={{ flex: 1 }}>
                  <div className="kf-audit-who-name">{m.name}</div>
                  <div className="kf-audit-who-role">{SYMBOL_FAMILIES.find(f => f.k === m.family)?.l} · {m.pins} pins · {m.gender}</div>
                </div>
                <Chip size="sm">imported</Chip>
              </label>
            ))}
          </div>
        </div>
        <div className="kf-symmodal-foot">
          <Btn kind="ghost" onClick={onClose}>Cancel</Btn>
          <div className="spacer"/>
          <Btn kind="primary" icon="check" disabled={picked.length === 0}
               onClick={() => onImport(picked.map((p, i) => ({ ...p, id: `imp${Date.now()}-${i}`, used: 0, updated: new Date().toISOString().slice(0,10) })))}>
            Add {picked.length} to library
          </Btn>
        </div>
      </div>
    </div>
  );
};

/* ===================================================================
   NOTES & REVISIONS VIEW
   Two stacked sections:
   - Design notes — general / electrical / production groups. Each note
     has a number printed on the drawing, a category, and an optional
     zone reference (where on the drawing the callout sits).
   - Revision history — printed in the title-block revision triangle.
     ECO ref, zone, description, approver, date.
   =================================================================== */
const DEFAULT_NOTES = [
  { id:'n1',  num:'1',  cat:'general',    body:'All dimensions in millimetres. Tolerances per ISO 2768-mK unless otherwise specified.', zone:'A1', author:'L. Kohli',  date:'2026-05-12' },
  { id:'n2',  num:'2',  cat:'general',    body:'Bend radii ≥ 10× conductor diameter on all flexed bundles per AS50881.',                  zone:'A2', author:'L. Kohli',  date:'2026-05-12' },
  { id:'n3',  num:'3',  cat:'general',    body:'Bundle wrap: spiral wrap 50% overlap, 25 mm pitch.',                                       zone:'B1', author:'R. Bedi',   date:'2026-05-14' },
  { id:'n4',  num:'4',  cat:'electrical', body:'Insulate all shields with heat-shrink HS-200 at termination points.',                      zone:'C3', author:'L. Kohli',  date:'2026-05-15' },
  { id:'n5',  num:'5',  cat:'electrical', body:'24 AWG twisted pairs: 4 turns per inch minimum. Differential signals: route together.',    zone:'D2', author:'L. Kohli',  date:'2026-05-15' },
  { id:'n6',  num:'6',  cat:'electrical', body:'Shield drain wires terminate to chassis at J1 only. Do NOT terminate at J3.2.',           zone:'D4', author:'R. Bedi',   date:'2026-05-18' },
  { id:'n7',  num:'7',  cat:'production', body:'Apply Kapton tape to all backshell-to-jacket transitions before strain relief.',           zone:'E1', author:'L. Kohli',  date:'2026-05-20' },
  { id:'n8',  num:'8',  cat:'production', body:'Crimp validation: pull-test 1 in 25 per MIL-PRF-22520/7. Log results in QA-187.',          zone:'E2', author:'S. Okonkwo',date:'2026-05-21' },
];
const DEFAULT_REVS = [
  { id:'r1', rev:'-', date:'2026-04-08', eco:'ECO-0184', zone:'—',  desc:'Initial release.',                                                  by:'L. Kohli', approved:'M. Adler', status:'released' },
  { id:'r2', rev:'A', date:'2026-04-22', eco:'ECO-0207', zone:'D3', desc:'Re-route W-014, W-015 to clear fuel-line clamp at FS22.',           by:'L. Kohli', approved:'R. Bedi',  status:'released' },
  { id:'r3', rev:'B', date:'2026-05-12', eco:'ECO-0231', zone:'C3', desc:'Add shield drain on differential pair to fix CAN bus error rate.',  by:'L. Kohli', approved:'R. Bedi',  status:'released' },
  { id:'r4', rev:'B', date:'2026-05-12', eco:'ECO-0231', zone:'A2', desc:'Update bend-radius note to AS50881 §3.5.2.',                        by:'L. Kohli', approved:'R. Bedi',  status:'released' },
  { id:'r5', rev:'C', date:'2026-05-22', eco:'ECO-0258', zone:'E1', desc:'Switch backshell from EN3155-008 to -012 at J3.2 (vendor change).', by:'L. Kohli', approved:'—',         status:'pending'  },
];
const CAT_META = {
  general:    { label:'General',    tone:'info',    color:'var(--info)'    },
  electrical: { label:'Electrical', tone:'violet',  color:'var(--violet)'  },
  production: { label:'Production', tone:'warning', color:'var(--warning)' },
};

const NotesView = ({ project }) => {
  const toast = useToast();
  const [tab, setTab] = useState('notes');
  const [notes, setNotes] = useState(DEFAULT_NOTES);
  const [revs,  setRevs]  = useState(DEFAULT_REVS);
  const [catFilter, setCatFilter] = useState('all');
  const [q, setQ] = useState('');
  const [editing, setEditing] = useState(null); // {mode, item} | null
  const filtered = useMemo(() => {
    let list = notes;
    if (catFilter !== 'all') list = list.filter(n => n.cat === catFilter);
    const needle = q.trim().toLowerCase();
    if (needle) list = list.filter(n => (n.body + ' ' + n.zone).toLowerCase().includes(needle));
    return list;
  }, [notes, catFilter, q]);
  const grouped = useMemo(() => {
    const out = { general:[], electrical:[], production:[] };
    filtered.forEach(n => out[n.cat].push(n));
    return out;
  }, [filtered]);
  const counts = useMemo(() => ({
    all:        notes.length,
    general:    notes.filter(n => n.cat === 'general').length,
    electrical: notes.filter(n => n.cat === 'electrical').length,
    production: notes.filter(n => n.cat === 'production').length,
  }), [notes]);

  const removeNote = (id) => { setNotes(ns => ns.filter(n => n.id !== id)); toast.warn('Note removed'); };
  const saveNote = (next) => {
    setNotes(ns => {
      if (next.id) return ns.map(n => n.id === next.id ? next : n);
      const nextNum = String(Math.max(...ns.map(n => +n.num || 0)) + 1);
      return [...ns, { ...next, id: `n${Date.now()}`, num: nextNum, author:'L. Kohli', date: new Date().toISOString().slice(0,10) }];
    });
    setEditing(null);
    toast.ok(next.id ? 'Note updated' : `Note ${next.num || ''} added`);
  };

  return (
    <div className="kf-view kf-notes">
      <ViewHeader title="Notes & Revisions"
                  hint={<span>Drawing notes and revision history for <b>{project.name}</b>. Notes are numbered and printed on the drawing; revisions populate the title-block revision triangle.</span>}
                  actions={
                    <>
                      {tab === 'notes' && <Btn kind="primary" icon="plus" onClick={() => setEditing({ mode:'create', item: { cat:'general', body:'', zone:'' } })}>Add note</Btn>}
                      {tab === 'revs'  && <Btn kind="primary" icon="plus">New revision</Btn>}
                    </>
                  }/>

      <div className="kf-notes-tabs" role="tablist" aria-label="Notes view">
        <button role="tab" aria-selected={tab === 'notes'} className={`kf-notes-tab ${tab === 'notes' ? 'on' : ''}`} onClick={() => setTab('notes')}>
          Notes <span className="kf-notes-tab-n">{notes.length}</span>
        </button>
        <button role="tab" aria-selected={tab === 'revs'} className={`kf-notes-tab ${tab === 'revs' ? 'on' : ''}`} onClick={() => setTab('revs')}>
          Revisions <span className="kf-notes-tab-n">{revs.length}</span>
        </button>
      </div>

      {tab === 'notes' && (
        <>
          <div className="kf-notes-toolbar">
            <div className="kf-projects-search" style={{ flex:1, maxWidth: 420 }}>
              <Icon name="search" size={13} stroke="var(--t-3)"/>
              <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search notes or zone…" aria-label="Search notes"/>
            </div>
            <div className="kf-notes-chips" role="tablist" aria-label="Filter by category">
              {[
                { k:'all',        l:'All',         tone:null },
                { k:'general',    l:'General',     tone:'info'    },
                { k:'electrical', l:'Electrical',  tone:'violet'  },
                { k:'production', l:'Production',  tone:'warning' },
              ].map(c => (
                <button key={c.k} role="tab" aria-selected={catFilter === c.k}
                        className={`kf-notes-chip ${catFilter === c.k ? 'on' : ''}`}
                        onClick={() => setCatFilter(c.k)}>
                  {c.tone && <span className={`kf-notes-chip-dot kf-notes-chip-dot-${c.tone}`}/>}
                  {c.l}
                  <span className="kf-projects-chip-count">{counts[c.k]}</span>
                </button>
              ))}
            </div>
          </div>

          {filtered.length === 0 ? (
            <EmptyState icon="search" title="No notes match"
                        hint="Try a different category or search term."
                        action={<Btn kind="outline" onClick={() => { setQ(''); setCatFilter('all'); }}>Clear filters</Btn>}/>
          ) : (
            <div className="kf-notes-groups">
              {['general','electrical','production'].map(cat => {
                if (!grouped[cat].length) return null;
                const m = CAT_META[cat];
                return (
                  <div key={cat} className="kf-notes-group">
                    <div className="kf-notes-group-head" style={{ '--cat-color': m.color }}>
                      <span className={`kf-notes-chip-dot kf-notes-chip-dot-${m.tone}`}/>
                      <h3>{m.label} notes</h3>
                      <span className="kf-admin-muted">{grouped[cat].length} entries</span>
                    </div>
                    <div className="kf-notes-list">
                      {grouped[cat].map(n => (
                        <div key={n.id} className="kf-notes-row">
                          <div className="kf-notes-num num">{n.num}</div>
                          <div className="kf-notes-body">
                            <div className="kf-notes-text">{n.body}</div>
                            <div className="kf-notes-meta">
                              <span>Zone <span className="num">{n.zone}</span></span>
                              <span>·</span>
                              <span>{n.author}</span>
                              <span>·</span>
                              <span className="num">{n.date}</span>
                            </div>
                          </div>
                          <div className="kf-notes-actions">
                            <IconBtn icon="settings" label={`Edit note ${n.num}`} onClick={() => setEditing({ mode:'edit', item: n })}/>
                            <IconBtn icon="trash" label={`Delete note ${n.num}`} danger onClick={() => removeNote(n.id)}/>
                          </div>
                        </div>
                      ))}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </>
      )}

      {tab === 'revs' && (
        <div className="kf-notes-revtable">
          <table className="kf-admin-table">
            <thead>
              <tr>
                <th style={{ width: 50 }}>Rev</th>
                <th style={{ width: 110 }}>Date</th>
                <th style={{ width: 120 }}>ECO ref</th>
                <th style={{ width: 70 }}>Zone</th>
                <th>Description</th>
                <th style={{ width: 130 }}>Drawn by</th>
                <th style={{ width: 130 }}>Approved</th>
                <th style={{ width: 110 }}>Status</th>
                <th aria-label="Actions" style={{ width: 60 }}/>
              </tr>
            </thead>
            <tbody>
              {revs.map(r => (
                <tr key={r.id}>
                  <td><div className="kf-rev-triangle">{r.rev}</div></td>
                  <td className="num kf-admin-muted">{r.date}</td>
                  <td className="num">{r.eco}</td>
                  <td className="num">{r.zone}</td>
                  <td>{r.desc}</td>
                  <td>{r.by}</td>
                  <td className={r.approved === '—' ? 'kf-admin-muted' : ''}>{r.approved}</td>
                  <td>
                    <Chip size="sm" dot tone={r.status === 'released' ? 'success' : r.status === 'pending' ? 'warning' : 'danger'}>
                      {r.status}
                    </Chip>
                  </td>
                  <td className="kf-admin-actions">
                    <IconBtn icon="settings" label={`Edit revision ${r.rev}`}/>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          <div className="kf-notes-revfoot">
            <Icon name="info" size={12} stroke="var(--t-3)"/>
            Revisions in <Chip size="sm" tone="warning" dot>pending</Chip> are saved as drafts but won't print on the drawing until they're approved.
          </div>
        </div>
      )}

      {editing && (
        <NoteEditor item={editing.item} mode={editing.mode}
                    onClose={() => setEditing(null)}
                    onSave={saveNote}/>
      )}
    </div>
  );
};

const NoteEditor = ({ item, mode, onClose, onSave }) => {
  const [cat, setCat]   = useState(item.cat);
  const [body, setBody] = useState(item.body);
  const [zone, setZone] = useState(item.zone || '');
  const canSave = body.trim().length > 0;
  return (
    <div className="kf-symmodal-bg fade-in" onClick={onClose} role="dialog" aria-modal="true" aria-label={mode === 'edit' ? 'Edit note' : 'Add note'}>
      <div className="kf-symmodal" onClick={e => e.stopPropagation()}>
        <div className="kf-symmodal-head">
          <div>
            <h2>{mode === 'edit' ? `Edit note ${item.num}` : 'Add note'}</h2>
            <p>{mode === 'edit' ? 'Updating the note will renumber other notes only if its category changes.' : 'Notes are auto-numbered within their category and printed on the drawing.'}</p>
          </div>
          <IconBtn icon="x" label="Close" onClick={onClose}/>
        </div>
        <div className="kf-symmodal-body">
          <div className="kf-form">
            <div className="kf-form-row">
              <div className="kf-field">
                <label>Category</label>
                <div className="kf-seg">
                  {['general','electrical','production'].map(c => (
                    <button key={c} className={cat === c ? 'on' : ''} onClick={() => setCat(c)}>
                      <span className={`kf-notes-chip-dot kf-notes-chip-dot-${CAT_META[c].tone}`}/>{CAT_META[c].label}
                    </button>
                  ))}
                </div>
              </div>
            </div>
            <div className="kf-form-row">
              <div className="kf-field">
                <label>Note text <span className="kf-req">*</span></label>
                <textarea className="kf-input" rows="4" value={body} onChange={e => setBody(e.target.value)}
                          placeholder="e.g. All dimensions in mm. Tolerances per ISO 2768-mK." autoFocus/>
                <div className="kf-field-hint">Printed verbatim on the drawing. Use complete sentences and reference standards where applicable.</div>
              </div>
            </div>
            <div className="kf-form-row">
              <div className="kf-field">
                <label>Zone reference</label>
                <input className="kf-input num" value={zone} onChange={e => setZone(e.target.value)}
                       placeholder="e.g. A1, C3, E2"/>
                <div className="kf-field-hint">Drawing grid coordinate where the callout sits. Optional.</div>
              </div>
            </div>
          </div>
        </div>
        <div className="kf-symmodal-foot">
          <Btn kind="ghost" onClick={onClose}>Cancel</Btn>
          <div className="spacer"/>
          <Btn kind="primary" icon="check" disabled={!canSave}
               onClick={() => onSave({ ...item, cat, body: body.trim(), zone: zone.trim() })}>
            {mode === 'edit' ? 'Save changes' : 'Add note'}
          </Btn>
        </div>
      </div>
    </div>
  );
};

/* ---------- Welcome / no-project (Empty) ---------- */
const WelcomeView = ({ onPickProject, onImportCsv }) => (
  <div className="kf-welcome">
    <div className="kf-welcome-inner">
      <div className="kf-welcome-logo">
        <KabelFluxMark size={56}/>
      </div>
      <h1 className="kf-welcome-title">KabelFlux</h1>
      <div className="kf-welcome-sub">e-Harness Suite · v5</div>
      <EmptyState
        icon="folder"
        title="Open a harness to get started"
        hint="Pick a project from the sidebar, create a new one, or import an existing CSV / Komax wire list."
        action={<Btn kind="primary" icon="plus" onClick={onPickProject}>Browse projects</Btn>}
        secondary={<Btn kind="outline" icon="import_" onClick={onImportCsv}>Import CSV</Btn>}/>
      <div className="kf-welcome-shortcuts">
        <span>Press <Kbd>⌘</Kbd> <Kbd>K</Kbd> any time to search across projects, wires and connectors.</span>
      </div>
    </div>
  </div>
);

/* ---------- Projects browser (full page) ----------
   Replaces the old in-sidebar list. Filter + sort + status chips along the
   top, then a responsive card grid. Pinned projects always render first. */
const ProjectsView = ({ projects, currentId, onOpen, onCreateNew, onImportCsv }) => {
  const [q, setQ] = useState('');
  const [statusFilter, setStatusFilter] = useState('all');
  const [sort, setSort] = useState('recent');

  const counts = useMemo(() => ({
    all:      projects.length,
    active:   projects.filter(p => p.status === 'active').length,
    review:   projects.filter(p => p.status === 'review').length,
    released: projects.filter(p => p.status === 'released').length,
    draft:    projects.filter(p => p.status === 'draft').length,
  }), [projects]);

  const filtered = useMemo(() => {
    let list = projects.slice();
    const needle = q.trim().toLowerCase();
    if (needle) list = list.filter(p => (p.name + ' ' + p.code + ' ' + p.owner).toLowerCase().includes(needle));
    if (statusFilter !== 'all') list = list.filter(p => p.status === statusFilter);
    // sort
    if (sort === 'name')   list.sort((a, b) => a.name.localeCompare(b.name));
    if (sort === 'wires')  list.sort((a, b) => b.wires - a.wires);
    if (sort === 'recent') {/* preserve incoming order */}
    // pinned first
    list.sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0));
    return list;
  }, [projects, q, statusFilter, sort]);

  return (
    <div className="kf-projects-view">
      <div className="kf-projects-head">
        <div>
          <div className="kf-projects-eyebrow">Workspace</div>
          <h1 className="kf-projects-title">Projects</h1>
          <div className="kf-projects-sub">
            {filtered.length} of {projects.length} harnesses · Last sync 4s ago
          </div>
        </div>
        <div className="kf-projects-head-actions">
          <Btn kind="outline" icon="import_" onClick={onImportCsv}>Import CSV</Btn>
          <Btn kind="primary"  icon="plus" onClick={onCreateNew}>New project</Btn>
        </div>
      </div>

      <div className="kf-projects-toolbar">
        <div className="kf-projects-search">
          <Icon name="search" size={14} stroke="var(--t-3)"/>
          <input value={q} onChange={e => setQ(e.target.value)}
                 placeholder="Search by name, part number, owner…"
                 aria-label="Search projects"/>
          {q && <button className="kf-projects-clear" onClick={() => setQ('')} aria-label="Clear search"><Icon name="x" size={12}/></button>}
        </div>
        <div className="kf-projects-chips" role="tablist" aria-label="Filter by status">
          {[
            { k: 'all',      l: 'All' },
            { k: 'active',   l: 'Active' },
            { k: 'review',   l: 'In review' },
            { k: 'released', l: 'Released' },
            { k: 'draft',    l: 'Draft' },
          ].map(s => (
            <button key={s.k} role="tab" aria-selected={statusFilter === s.k}
                    className={`kf-projects-chip ${statusFilter === s.k ? 'on' : ''}`}
                    onClick={() => setStatusFilter(s.k)}>
              {s.k !== 'all' && <span className={`kf-status-dot kf-status-${s.k}`}/>}
              {s.l}
              <span className="kf-projects-chip-count">{counts[s.k]}</span>
            </button>
          ))}
        </div>
        <div className="kf-projects-sort">
          <label htmlFor="kf-projects-sort">Sort</label>
          <select id="kf-projects-sort" value={sort} onChange={e => setSort(e.target.value)}>
            <option value="recent">Recently modified</option>
            <option value="name">Name (A–Z)</option>
            <option value="wires">Wire count</option>
          </select>
        </div>
      </div>

      {filtered.length === 0 ? (
        <EmptyState icon="search" title="No matching projects"
                    hint="Try a different name, part number, or status filter."/>
      ) : (
        <div className="kf-projects-grid">
          {filtered.map(p => (
            <button key={p.id}
                    className={`kf-pcard ${p.id === currentId ? 'on' : ''}`}
                    onClick={() => onOpen(p.id)}>
              <div className="kf-pcard-head">
                <span className={`kf-pcard-status kf-status-dot kf-status-${p.status}`} aria-hidden="true"/>
                <span className="kf-pcard-status-label">{p.status}</span>
                <span className="kf-pcard-rev">Rev {p.revision}</span>
                {p.pinned && <Icon name="star" size={12} stroke="var(--warning)" fill="var(--warning)"/>}
              </div>
              <div className="kf-pcard-name">{p.name}</div>
              <div className="kf-pcard-code">{p.code}</div>
              <div className="kf-pcard-stats">
                <div className="kf-pcard-stat"><div className="kf-pcard-stat-n">{p.wires}</div><div className="kf-pcard-stat-l">Wires</div></div>
                <div className="kf-pcard-stat"><div className="kf-pcard-stat-n">{p.conns}</div><div className="kf-pcard-stat-l">Connectors</div></div>
                <div className="kf-pcard-stat"><div className="kf-pcard-stat-n">{p.nodes}</div><div className="kf-pcard-stat-l">Nodes</div></div>
              </div>
              <div className="kf-pcard-foot">
                <span>{p.owner}</span>
                <span>{p.modified}</span>
              </div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
};

const KabelFluxMark = ({ size = 32 }) => (
  <span
    className="kf-mark"
    role="img"
    aria-label="KabelFlux logo"
    style={{ width: size, height: size }}
  />
);

const STYLE = `
/* — KabelFlux logo mark —
   Uses the alpha channel of the PNG as a mask so the visible color is
   driven by --accent (which retunes per theme). Drop the mask and switch
   to background-image to show the original blue artwork. */
.kf-mark {
  display: inline-block;
  background-color: var(--accent);
  -webkit-mask: url("assets/kableflux-mark.png") center/contain no-repeat;
          mask: url("assets/kableflux-mark.png") center/contain no-repeat;
  flex-shrink: 0;
  transition: background-color .18s var(--ease);
}

/* — Notes & Revisions — */
.kf-notes { padding: 0; display: flex; flex-direction: column; overflow: hidden; }
.kf-notes .kf-view-head { padding: 22px 24px 14px; }
.kf-notes-tabs {
  display: flex; gap: 4px; padding: 0 24px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 16px;
}
.kf-notes-tab {
  padding: 10px 4px; margin-right: 18px;
  background: transparent; border: none;
  border-bottom: 2px solid transparent;
  color: var(--t-3);
  font-family: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.kf-notes-tab:hover { color: var(--t-1); }
.kf-notes-tab.on { color: var(--t-1); border-bottom-color: var(--accent); }
.kf-notes-tab-n { font-family: var(--f-mono); font-size: 10.5px; padding: 1px 7px; background: var(--bg-3); color: var(--t-3); border-radius: 999px; }
.kf-notes-tab.on .kf-notes-tab-n { background: var(--accent-soft); color: var(--accent); }

.kf-notes-toolbar { display: flex; align-items: center; gap: 14px; padding: 0 24px 14px; flex-wrap: wrap; }
.kf-notes-chips { display: flex; gap: 4px; flex-wrap: wrap; }
.kf-notes-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 10px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--t-2);
  font-family: inherit; font-size: 12px; font-weight: 500;
  border-radius: var(--r-2); cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.kf-notes-chip:hover:not(.on) { background: var(--bg-3); color: var(--t-1); }
.kf-notes-chip.on { background: var(--bg-2); border-color: var(--line-strong); color: var(--t-1); }
.kf-notes-chip-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; }
.kf-notes-chip-dot-info    { background: var(--info); }
.kf-notes-chip-dot-violet  { background: var(--violet); }
.kf-notes-chip-dot-warning { background: var(--warning); }

.kf-notes-groups { padding: 0 24px 28px; overflow-y: auto; }
.kf-notes-group  { margin-bottom: 26px; }
.kf-notes-group-head {
  display: flex; align-items: center; gap: 10px;
  padding-bottom: 10px;
  border-bottom: 1px dashed var(--line);
  margin-bottom: 10px;
}
.kf-notes-group-head h3 { font-family: var(--f-display); font-size: 15px; font-weight: 600; color: var(--t-1); margin: 0; }
.kf-notes-group-head .kf-admin-muted { margin-left: auto; font-family: var(--f-mono); font-size: 11px; }
.kf-notes-list { display: flex; flex-direction: column; gap: 8px; }
.kf-notes-row {
  display: grid; grid-template-columns: 36px 1fr auto;
  gap: 14px; align-items: flex-start;
  padding: 12px 14px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.kf-notes-row:hover { background: var(--bg-3); border-color: var(--line-strong); }
.kf-notes-num {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 50%;
  font-family: var(--f-mono); font-size: 11.5px; font-weight: 700;
  color: var(--t-1);
}
.kf-notes-text { font-size: 13px; color: var(--t-1); line-height: 1.55; }
.kf-notes-meta {
  display: flex; gap: 6px;
  margin-top: 6px;
  font-size: 11px; color: var(--t-3);
}
.kf-notes-meta .num { font-family: var(--f-mono); color: var(--t-2); font-weight: 500; }
.kf-notes-actions { display: flex; gap: 2px; opacity: 0; transition: opacity .12s var(--ease); }
.kf-notes-row:hover .kf-notes-actions { opacity: 1; }

/* Revisions table */
.kf-notes-revtable { padding: 0 24px 28px; overflow-y: auto; }
.kf-rev-triangle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 22px;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--f-mono); font-size: 11px; font-weight: 700;
  clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
  padding-top: 6px;
}
.kf-notes-revfoot {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--t-3);
  padding: 14px 24px 0;
}

@media (max-width: 720px) {
  .kf-notes-row { grid-template-columns: 28px 1fr; }
  .kf-notes-actions { grid-column: 2; opacity: 1; }
}

/* — Symbol creator v2 (catalog-first + custom build) — */
.kf-symmodal-wide { width: 1080px; max-height: 92vh; }
.kf-sym-tabs {
  display: flex; gap: 4px;
  padding: 8px 18px 0;
  border-bottom: 1px solid var(--line);
}
.kf-sym-tab {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px;
  background: transparent;
  border: none; border-bottom: 2px solid transparent;
  color: var(--t-3);
  font-family: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer;
  margin-bottom: -1px;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.kf-sym-tab:hover { color: var(--t-1); }
.kf-sym-tab.on    { color: var(--t-1); border-bottom-color: var(--accent); }
.kf-sym-tab svg   { stroke: currentColor; }

/* CATALOG MODE */
.kf-cat-pick { padding: 0; display: flex; flex-direction: column; overflow: hidden; flex: 1; }
.kf-cat-filters { display: flex; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--line); align-items: center; }
.kf-cat-layout  { display: grid; grid-template-columns: 200px 1fr; flex: 1; overflow: hidden; }
.kf-cat-rail {
  border-right: 1px solid var(--line);
  padding: 8px 6px;
  display: flex; flex-direction: column; gap: 1px;
  overflow-y: auto;
  background: var(--bg-1);
}
.kf-cat-rail-item {
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 8px 10px;
  background: transparent; border: none;
  border-radius: var(--r-2);
  color: var(--t-2);
  font-family: inherit; font-size: 12.5px; font-weight: 500;
  cursor: pointer; text-align: left;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.kf-cat-rail-item:hover { background: var(--bg-3); color: var(--t-1); }
.kf-cat-rail-item.on { background: var(--accent-soft); color: var(--accent); }
.kf-cat-rail-item .num { font-family: var(--f-mono); font-size: 10.5px; color: var(--t-3); }
.kf-cat-rail-item.on .num { color: var(--accent); }

.kf-cat-results { padding: 12px 14px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.kf-cat-card {
  display: grid; grid-template-columns: 60px 1fr auto;
  gap: 14px; align-items: center;
  padding: 10px 14px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  cursor: pointer; text-align: left;
  font-family: inherit;
  transition: background .12s var(--ease), border-color .12s var(--ease), transform .1s var(--ease);
}
.kf-cat-card:hover { background: var(--bg-3); border-color: var(--accent); transform: translateX(2px); }
.kf-cat-card-glyph {
  width: 56px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.kf-cat-card-glyph svg { width: 50px; height: 32px; }
.kf-cat-card-pn   { font-family: var(--f-mono); font-size: 12px; color: var(--accent); font-weight: 600; }
.kf-cat-card-name { font-size: 13px; font-weight: 600; color: var(--t-1); margin: 2px 0; }
.kf-cat-card-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--t-3); }
.kf-cat-card-meta .num { font-family: var(--f-mono); color: var(--t-2); font-weight: 500; }

/* CUSTOM BUILD MODE */
.kf-build { padding: 0; overflow: hidden; flex: 1; }
.kf-build-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
}
.kf-build-col {
  padding: 16px 18px;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 18px;
}
.kf-build-col + .kf-build-col { border-left: 1px solid var(--line); background: var(--bg-1); }
.kf-build-section {}
.kf-build-section-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-family: var(--f-mono); font-size: 10.5px; font-weight: 600;
  color: var(--t-3); text-transform: uppercase; letter-spacing: .08em;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px dashed var(--line);
}
.kf-build-section-actions { display: flex; gap: 4px; }
.kf-build-advanced-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px;
  background: transparent; border: 1px dashed var(--line);
  border-radius: var(--r-2);
  color: var(--t-3); font-family: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.kf-build-advanced-toggle:hover { color: var(--t-1); border-color: var(--line-strong); }
.kf-build-advanced { margin-top: 10px; }

/* Shape picker grid — replaces the binary Rect/Round segment.
   Each tile shows a small live-rendered preview of that shell shape. */
.kf-shape-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.kf-shape-grid-compact { grid-template-columns: repeat(4, 1fr); }
.kf-shape-tile {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 6px 6px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  cursor: pointer;
  font-family: inherit; font-size: 11px; font-weight: 500;
  color: var(--t-3);
  transition: background .12s var(--ease), border-color .12s var(--ease), color .12s var(--ease);
}
.kf-shape-tile:hover { background: var(--bg-3); border-color: var(--line-strong); color: var(--t-1); }
.kf-shape-tile.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.kf-shape-tile svg { width: 36px; height: 28px; pointer-events: none; }

/* From-image builder */
.kf-img-build { padding: 0; overflow: hidden; flex: 1; }
.kf-img-grid {
  display: grid; grid-template-columns: 1fr 380px;
  gap: 0; height: 100%; overflow: hidden;
}
.kf-img-pane {
  display: flex; flex-direction: column;
  padding: 14px 16px;
  background: var(--bg-1);
  border-right: 1px solid var(--line);
  overflow: hidden;
}
.kf-img-drop {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  padding: 32px;
  background: var(--bg-2);
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-3);
  text-align: center;
}
.kf-img-drop-title { font-size: 15px; font-weight: 600; color: var(--t-1); margin-top: 6px; }
.kf-img-drop-hint  { font-size: 12px; color: var(--t-3); }
.kf-img-canvas-wrap { flex: 1; display: flex; align-items: center; justify-content: center; overflow: hidden; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-3); }
.kf-img-canvas {
  position: relative;
  display: inline-block;
  max-width: 100%; max-height: 100%;
  cursor: crosshair;
}
.kf-img-canvas img { display: block; max-width: 100%; max-height: 100%; transition: opacity .15s var(--ease); user-select: none; }
.kf-img-outline { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.kf-img-pin {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 4px;
  pointer-events: none;
}
.kf-img-pin-dot { display: block; width: 12px; height: 12px; border-radius: 50%; background: var(--accent); border: 2px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,.4); flex-shrink: 0; }
.kf-img-pin-dot-row { width: 10px; height: 10px; }
.kf-img-pin-label {
  font-family: var(--f-mono); font-size: 10px; font-weight: 700; color: #fff;
  background: var(--accent);
  padding: 1px 5px; border-radius: 4px;
  box-shadow: 0 1px 2px rgba(0,0,0,.4);
}
.kf-img-pin-rm {
  pointer-events: auto;
  width: 16px; height: 16px;
  display: flex; align-items: center; justify-content: center;
  background: var(--danger); color: #fff;
  border: 1.5px solid #fff;
  border-radius: 50%;
  font-size: 11px; font-weight: 700; line-height: 1;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0,0,0,.4);
  margin-left: 2px;
}
.kf-img-canvas-bar {
  display: flex; align-items: center; gap: 10px;
  padding-top: 10px;
}
.kf-img-empty {
  display: flex; align-items: center; gap: 8px;
  padding: 12px;
  background: var(--bg-1); border: 1px dashed var(--line);
  border-radius: var(--r-2);
  font-size: 12px; color: var(--t-3); line-height: 1.4;
}
.kf-img-pin-list { display: flex; flex-direction: column; gap: 4px; max-height: 280px; overflow-y: auto; }
.kf-img-pin-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px;
  background: var(--bg-1); border: 1px solid var(--line-soft);
  border-radius: var(--r-2);
}
.kf-img-pin-row-label { font-family: var(--f-mono); font-size: 11px; font-weight: 700; color: var(--t-1); }
.kf-img-pin-row-rm {
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none;
  color: var(--t-3); font-size: 16px; font-weight: 600; line-height: 1;
  cursor: pointer; border-radius: 4px;
}
.kf-img-pin-row-rm:hover { background: var(--bg-3); color: var(--danger); }

@media (max-width: 720px) {
  .kf-img-grid { grid-template-columns: 1fr; }
  .kf-img-pane { border-right: none; border-bottom: 1px solid var(--line); }
}

/* Parametric dimension grid */
.kf-dim-grid {
  display: flex; align-items: center; gap: 8px;
}
.kf-dim-cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.kf-dim-cell .kf-input { width: 64px; text-align: center; }
.kf-dim-cell-total {
  margin-left: 12px;
  padding: 0 10px;
  border-left: 1px dashed var(--line);
}
.kf-dim-total { font-family: var(--f-mono); font-size: 18px; font-weight: 700; color: var(--t-1); }
.kf-dim-x  { font-family: var(--f-mono); font-size: 14px; color: var(--t-3); padding-bottom: 16px; }
.kf-dim-l  { font-size: 10px; color: var(--t-3); text-transform: uppercase; letter-spacing: .06em; font-family: var(--f-mono); }

/* Pinout table */
.kf-pinout-table {
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
  background: var(--bg-2);
}
.kf-pinout-head, .kf-pinout-row {
  display: grid;
  grid-template-columns: 60px 1.4fr 60px 1fr;
  gap: 4px;
  align-items: center;
}
.kf-pinout-head {
  padding: 8px 8px;
  font-family: var(--f-mono); font-size: 10px; font-weight: 600;
  color: var(--t-3); text-transform: uppercase; letter-spacing: .06em;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
}
.kf-pinout-rows { max-height: 320px; overflow-y: auto; }
.kf-pinout-row {
  padding: 4px 8px;
  border-bottom: 1px solid var(--line-soft);
}
.kf-pinout-row:last-child { border-bottom: none; }
.kf-pinout-row .kf-input-sm { height: 28px; padding: 0 8px; font-size: 12px; }

@media (max-width: 720px) {
  .kf-symmodal-wide { width: 100vw; max-height: 100vh; }
  .kf-cat-layout { grid-template-columns: 1fr; }
  .kf-cat-rail { border-right: none; border-bottom: 1px solid var(--line); flex-direction: row; overflow-x: auto; }
  .kf-build-grid { grid-template-columns: 1fr; }
  .kf-build-col + .kf-build-col { border-left: none; border-top: 1px solid var(--line); }
}

/* — Symbol Library (workspace-level) — */
.kf-symmodal-bg {
  position: fixed; inset: 0; z-index: 800;
  background: rgba(7,9,15,.55); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.kf-symmodal {
  background: var(--bg-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-4);
  box-shadow: 0 24px 80px -20px rgba(0,0,0,.55);
  width: 560px; max-width: 100%; max-height: 90vh;
  display: flex; flex-direction: column;
}
.kf-symmodal-wide { width: 920px; }
.kf-symmodal-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 18px 22px 12px; gap: 18px;
  border-bottom: 1px solid var(--line);
}
.kf-symmodal-head h2 { font-family: var(--f-display); font-size: 18px; font-weight: 600; color: var(--t-1); margin: 0 0 4px; letter-spacing: -.01em; }
.kf-symmodal-head p  { font-size: 12.5px; color: var(--t-3); margin: 0; line-height: 1.5; }
.kf-symmodal-body    { padding: 18px 22px; overflow-y: auto; flex: 1; }
.kf-symmodal-foot    { display: flex; align-items: center; padding: 12px 22px; border-top: 1px solid var(--line); gap: 8px; }

/* Symbol creator — split layout: stepper+form on the left, live preview right */
.kf-sym-create { display: grid; grid-template-columns: 1fr 280px; gap: 22px; padding: 0; }
.kf-sym-create-form    { display: flex; flex-direction: column; gap: 14px; }
.kf-sym-create-preview {
  padding-left: 22px; border-left: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 12px;
}
.kf-sym-preview-label  { font-family: var(--f-mono); font-size: 10.5px; font-weight: 600; color: var(--t-3); text-transform: uppercase; letter-spacing: .08em; }
.kf-symcard-preview-card { background: var(--bg-1); }
.kf-sym-preview-tags { display: flex; flex-wrap: wrap; gap: 4px; }

/* Stepper at the top of the creator */
.kf-sym-steps { display: flex; gap: 6px; border-bottom: 1px solid var(--line); padding-bottom: 12px; margin-bottom: 4px; }
.kf-sym-step {
  flex: 1;
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--t-3);
  font-family: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.kf-sym-step .num { display: inline-flex; width: 20px; height: 20px; align-items: center; justify-content: center; background: var(--bg-3); border-radius: 50%; font-size: 10.5px; font-family: var(--f-mono); font-weight: 700; color: var(--t-2); }
.kf-sym-step:hover { color: var(--t-1); }
.kf-sym-step.on { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.kf-sym-step.on .num { background: var(--accent); color: #fff; }

/* Family picker grid */
.kf-sym-grid-pick { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 8px; }
.kf-sym-fam-card {
  padding: 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  cursor: pointer; text-align: center;
  font-family: inherit;
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.kf-sym-fam-card:hover { background: var(--bg-3); border-color: var(--line-strong); }
.kf-sym-fam-card.on { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.kf-sym-fam-card-glyph { height: 56px; display: flex; align-items: center; justify-content: center; }
.kf-sym-fam-card-glyph svg { width: 80px; height: 50px; }
.kf-sym-fam-card-name { font-size: 12px; font-weight: 600; color: var(--t-1); margin-top: 4px; }
.kf-sym-fam-card-count { font-size: 10.5px; color: var(--t-3); margin-top: 2px; }

/* Pin stepper */
.kf-pin-stepper { display: flex; gap: 4px; align-items: center; }
.kf-pin-stepper button {
  width: 32px; height: 36px;
  background: var(--bg-3); border: 1px solid var(--line); border-radius: var(--r-2);
  color: var(--t-2); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.kf-pin-stepper button:hover { background: var(--bg-4); color: var(--t-1); }
.kf-pin-stepper input { text-align: center; flex: 1; max-width: 80px; }

/* Segmented control */
.kf-seg {
  display: inline-flex;
  padding: 3px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  gap: 2px;
}
.kf-seg button {
  padding: 6px 12px;
  background: transparent; border: none;
  color: var(--t-2);
  font-family: inherit; font-size: 12px; font-weight: 500;
  border-radius: 4px; cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.kf-seg button:hover { color: var(--t-1); }
.kf-seg button.on { background: var(--bg-3); color: var(--t-1); }

.kf-req { color: var(--danger); }

/* Importer matched rows */
.kf-sym-import-head { font-family: var(--f-mono); font-size: 11px; font-weight: 600; color: var(--t-3); text-transform: uppercase; letter-spacing: .06em; margin-bottom: 8px; }
.kf-sym-import-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  margin-bottom: 6px;
  cursor: pointer;
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.kf-sym-import-row:hover { background: var(--bg-3); }
.kf-sym-import-row.on { border-color: var(--accent); background: var(--accent-soft); }
.kf-sym-import-glyph { width: 56px; height: 36px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-2); display: flex; align-items: center; justify-content: center; }
.kf-sym-import-glyph svg { width: 48px; height: 30px; }

@media (max-width: 720px) {
  .kf-sym-create { grid-template-columns: 1fr; }
  .kf-sym-create-preview { border-left: none; border-top: 1px solid var(--line); padding-left: 0; padding-top: 18px; }
}

/* — Symbol Library (workspace-level) — */.kf-library { padding: 28px 32px 48px; overflow-y: auto; }
.kf-library-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 22px; flex-wrap: wrap; }
.kf-library-eyebrow { font-family: var(--f-mono); font-size: 11px; font-weight: 600; color: var(--t-3); text-transform: uppercase; letter-spacing: .08em; margin-bottom: 8px; }
.kf-library-title { font-family: var(--f-display); font-size: 32px; font-weight: 700; letter-spacing: -.6px; color: var(--t-1); margin: 0 0 6px; }
.kf-library-sub { font-size: 13px; color: var(--t-3); max-width: 560px; line-height: 1.5; }
.kf-library-head-actions { display: flex; gap: 8px; flex-shrink: 0; }

.kf-library-families {
  display: flex; gap: 4px; flex-wrap: wrap;
  padding: 6px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  margin-bottom: 12px;
}
.kf-library-fam {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--t-2);
  font-family: inherit; font-size: 12px; font-weight: 500;
  border-radius: var(--r-2); cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.kf-library-fam:hover:not(.on) { background: var(--bg-3); color: var(--t-1); }
.kf-library-fam.on { background: var(--bg-1); border-color: var(--line-strong); color: var(--t-1); }
.kf-library-fam-n {
  font-family: var(--f-mono); font-size: 10.5px; font-weight: 500;
  color: var(--t-3);
  padding: 1px 6px;
  background: var(--bg-2); border: 1px solid var(--line-soft);
  border-radius: 999px;
}
.kf-library-fam.on .kf-library-fam-n { background: var(--bg-3); color: var(--t-2); }

.kf-library-toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 18px; flex-wrap: wrap; }
.kf-library-genders { display: flex; gap: 4px; padding: 4px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-3); }
.kf-library-views { display: flex; gap: 4px; padding: 4px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-3); }
.kf-library-views .kf-iconbtn.on { background: var(--bg-1); border: 1px solid var(--line-strong); color: var(--accent); }

.kf-symcard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}
.kf-symcard {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
  transition: background .14s var(--ease), border-color .14s var(--ease), transform .14s var(--ease);
  cursor: grab;
}
.kf-symcard:hover { background: var(--bg-3); border-color: var(--line-strong); transform: translateY(-1px); }
.kf-symcard-preview {
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  padding: 24px;
  display: flex; align-items: center; justify-content: center;
}
.kf-symcard-glyph { width: 96px; height: 64px; }
.kf-symcard-body  { padding: 14px 16px; }
.kf-symcard-name  { font-size: 13.5px; font-weight: 600; color: var(--t-1); margin-bottom: 6px; }
.kf-symcard-meta  { display: flex; gap: 8px; align-items: center; font-size: 11.5px; color: var(--t-3); margin-bottom: 8px; }
.kf-symcard-meta .num { font-family: var(--f-mono); font-weight: 600; color: var(--t-2); }
.kf-symcard-foot  { display: flex; justify-content: space-between; font-size: 11px; color: var(--t-3); padding-top: 8px; border-top: 1px dashed var(--line); }
.kf-symcard-foot .num { font-family: var(--f-mono); color: var(--t-1); font-weight: 600; }

.kf-library-list { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-3); overflow: hidden; }

@media (max-width: 720px) {
  .kf-library { padding: 20px 18px; }
  .kf-library-title { font-size: 26px; }
}

/* — Home — */
.kf-home { padding: 22px 24px; overflow-y: auto; }

/* — Projects browser (full-page) — */
.kf-projects-view { padding: 28px 32px 48px; overflow-y: auto; max-width: 1400px; }
.kf-projects-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 24px; flex-wrap: wrap; }
.kf-projects-eyebrow { font-family: var(--f-mono); font-size: 11px; font-weight: 600; color: var(--t-3); text-transform: uppercase; letter-spacing: .08em; margin-bottom: 8px; }
.kf-projects-title { font-family: var(--f-display); font-size: 32px; font-weight: 700; letter-spacing: -.6px; color: var(--t-1); margin: 0 0 4px; }
.kf-projects-sub { font-size: 13px; color: var(--t-3); }
.kf-projects-head-actions { display: flex; gap: 8px; flex-shrink: 0; }

.kf-projects-toolbar {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 12px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.kf-projects-search {
  display: flex; align-items: center; gap: 8px;
  flex: 1; min-width: 240px;
  height: 34px; padding: 0 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  transition: border-color .12s var(--ease);
}
.kf-projects-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.kf-projects-search input {
  flex: 1; background: transparent; border: none; outline: none;
  color: var(--t-1); font-family: inherit; font-size: 13px;
}
.kf-projects-search input::placeholder { color: var(--t-4); }
.kf-projects-clear {
  background: transparent; border: none; cursor: pointer;
  color: var(--t-3); padding: 2px; border-radius: 4px;
  display: flex; align-items: center;
}
.kf-projects-clear:hover { color: var(--t-1); background: var(--bg-3); }

.kf-projects-chips { display: flex; gap: 4px; flex-wrap: wrap; }
.kf-projects-chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 12px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--t-2);
  font-family: inherit; font-size: 12px; font-weight: 500;
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.kf-projects-chip:hover:not(.on) { background: var(--bg-3); color: var(--t-1); }
.kf-projects-chip.on { background: var(--bg-1); border-color: var(--line-strong); color: var(--t-1); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.kf-projects-chip-count {
  font-family: var(--f-mono); font-size: 10.5px; font-weight: 500;
  color: var(--t-3);
  padding: 1px 6px;
  background: var(--bg-2); border-radius: 999px;
  border: 1px solid var(--line-soft);
}
.kf-projects-chip.on .kf-projects-chip-count { color: var(--t-2); }

.kf-projects-sort { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--t-3); }
.kf-projects-sort label { font-family: var(--f-mono); text-transform: uppercase; letter-spacing: .06em; font-size: 10.5px; }
.kf-projects-sort select {
  height: 30px; padding: 0 10px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--t-1);
  font-family: inherit; font-size: 12px;
  cursor: pointer;
}

.kf-projects-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px;
}
.kf-pcard {
  display: flex; flex-direction: column; gap: 0;
  padding: 18px;
  text-align: left;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  color: var(--t-1); font-family: inherit;
  cursor: pointer;
  transition: background .14s var(--ease), border-color .14s var(--ease), transform .14s var(--ease);
}
.kf-pcard:hover { background: var(--bg-3); border-color: var(--line-strong); transform: translateY(-1px); }
.kf-pcard.on { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.kf-pcard-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 11px; color: var(--t-3); text-transform: uppercase; letter-spacing: .05em; font-family: var(--f-mono); }
.kf-pcard-status-label { color: var(--t-2); font-weight: 600; }
.kf-pcard-rev { margin-left: auto; color: var(--t-3); }
.kf-pcard-name { font-size: 16px; font-weight: 600; line-height: 1.25; color: var(--t-1); margin-bottom: 4px; }
.kf-pcard-code { font-family: var(--f-mono); font-size: 11.5px; color: var(--t-3); margin-bottom: 16px; }
.kf-pcard-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 12px 0; border-top: 1px dashed var(--line); border-bottom: 1px dashed var(--line); }
.kf-pcard-stat-n { font-family: var(--f-mono); font-size: 18px; font-weight: 600; color: var(--t-1); }
.kf-pcard-stat-l { font-size: 10.5px; color: var(--t-3); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }
.kf-pcard-foot { display: flex; justify-content: space-between; margin-top: 12px; font-size: 11.5px; color: var(--t-3); }

@media (max-width: 720px) {
  .kf-projects-view { padding: 20px 18px; }
  .kf-projects-title { font-size: 26px; }
}
.kf-home-hero { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 22px; flex-wrap: wrap; }
.kf-home-hero > div:first-child { min-width: 0; flex: 1; }
.kf-home-eyebrow { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.kf-home-rev { font-size: 11px; color: var(--t-3); font-family: var(--f-mono); }
.kf-home-title { font-family: var(--f-display); font-size: 26px; font-weight: 700; letter-spacing: -.5px; margin-bottom: 4px; max-width: 640px; line-height: 1.15; }
.kf-home-meta { font-size: 12.5px; color: var(--t-3); }
.kf-home-hero-actions { display: flex; gap: 8px; flex-shrink: 0; }

.kf-home-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  grid-template-rows: auto auto auto;
  grid-template-areas:
    "preview health"
    "preview stats"
    "activity exports";
  gap: 14px;
}
.kf-home-preview { grid-area: preview; display: flex; flex-direction: column; }
.kf-home-preview-svg { flex: 1; min-height: 320px; background: radial-gradient(circle, var(--grid-dot) 1px, transparent 1px) 0 0/24px 24px, var(--bg-1); border-top: 1px solid var(--line); }
.kf-home-activity { grid-area: activity; }
.kf-home-exports  { grid-area: exports; }
@media (max-width: 1100px) {
  .kf-home-grid { grid-template-columns: 1fr; grid-template-areas: "preview" "health" "stats" "activity" "exports"; }
}

.kf-health { display: flex; flex-direction: column; gap: 10px; }
.kf-health-row { display: grid; grid-template-columns: 110px 1fr 80px; gap: 10px; align-items: center; font-size: 12px; }
.kf-health-label { color: var(--t-2); }
.kf-health-bar { height: 6px; background: var(--bg-3); border-radius: 3px; overflow: hidden; }
.kf-health-fill { height: 100%; border-radius: 3px; transition: width .3s var(--ease); }
.kf-health-val { color: var(--t-1); text-align: right; font-weight: 600; }
.kf-health-warn { color: var(--warning); font-weight: 500; }

.kf-quick-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.kf-qstat { background: var(--bg-1); border: 1px solid var(--line); border-radius: var(--r-2); padding: 12px; display: flex; flex-direction: column; gap: 4px; }
.kf-qstat-icon { color: var(--t-3); }
.kf-qstat-val { font-family: var(--f-display); font-size: 20px; font-weight: 700; color: var(--t-1); }
.kf-qstat-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .8px; color: var(--t-3); font-weight: 700; }

.kf-activity { padding: 12px 4px; }
.kf-activity-row {
  display: grid;
  grid-template-columns: 64px 8px 1fr;
  gap: 12px; align-items: center;
  padding: 7px 14px;
  font-size: 12px;
}
.kf-activity-row:hover { background: var(--bg-3); }
.kf-activity-time { color: var(--t-3); font-size: 11px; }
.kf-activity-dot { width: 8px; height: 8px; border-radius: 50%; }
.kf-activity-dot-edit   { background: var(--info); }
.kf-activity-dot-add    { background: var(--success); }
.kf-activity-dot-review { background: var(--warning); }
.kf-activity-dot-backup { background: var(--violet); }
.kf-activity-dot-note   { background: var(--t-3); }
.kf-activity-text { color: var(--t-2); }
.kf-activity-text b { color: var(--t-1); font-weight: 600; }

.kf-exports-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; padding: 12px; }
@media (max-width: 1100px) { .kf-exports-row { grid-template-columns: repeat(2, 1fr); } }
.kf-export-card {
  display: flex; align-items: center; gap: 10px;
  padding: 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  text-align: left; cursor: pointer; font-family: inherit;
  transition: all .15s var(--ease);
}
.kf-export-card:hover { background: var(--bg-3); border-color: var(--line-strong); }
.kf-export-card-primary { border-color: rgba(74,158,255,.4); background: var(--info-soft); }
.kf-export-card-primary:hover { background: rgba(74,158,255,.2); }
.kf-export-icon { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; background: var(--bg-3); border-radius: 6px; color: var(--info); }
.kf-export-card-primary .kf-export-icon { background: var(--info); color: #fff; }
.kf-export-text { flex: 1; min-width: 0; }
.kf-export-label { font-size: 12px; font-weight: 600; color: var(--t-1); }
.kf-export-sub { font-size: 10.5px; color: var(--t-3); margin-top: 2px; }
.kf-export-time { font-size: 10px; color: var(--t-3); font-family: var(--f-mono); }

/* — Connectors — */
/* Connectors — responsive grid.
   - auto-fill + minmax adapts column count to container width
   - container queries on each card adjust internal layout when a card
     becomes narrow (e.g. 1-column phone or split-pane). Falls back to
     viewport @media breakpoints for older engines that don't support
     container queries. */
.kf-conn-grid {
  flex: 1; overflow-y: auto;
  padding: clamp(10px, 2vw, 20px);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: clamp(8px, 1.4vw, 14px);
  align-content: start;
}
.kf-conn-card {
  display: flex; flex-direction: column;
  container-type: inline-size;
  container-name: connCard;
  min-width: 0;            /* prevent grid blow-out from long descriptions */
  transition: box-shadow .15s var(--ease);
}
.kf-conn-card.is-expanded {
  box-shadow: 0 4px 16px rgba(0,0,0,.06), 0 0 0 1px var(--info-soft);
  border-color: var(--info);
}
.kf-conn-card .card-head {
  flex-wrap: wrap;
  gap: 6px 8px;
  padding: 10px 12px;
  min-height: 44px;
}
.kf-conn-card-head {
  /* header row: toggle button takes available space, edit/trash sit at end */
  display: flex; align-items: center; justify-content: space-between;
  gap: 6px;
}
.kf-conn-card-toggle {
  display: flex; align-items: center; gap: 8px;
  background: transparent; border: 0;
  padding: 4px 4px;
  margin: -4px -4px -4px 0;
  font: inherit; color: var(--t-1);
  cursor: pointer;
  flex: 1 1 auto; min-width: 0;
  text-align: left;
  border-radius: var(--r-2);
  transition: background .12s var(--ease);
}
.kf-conn-card-toggle:hover { background: var(--bg-3); }
.kf-conn-card-toggle:focus-visible { outline: 2px solid var(--info); outline-offset: 1px; }
.kf-conn-card-toggle > .kf-conn-card-type { flex: 1 1 auto; min-width: 0; }
.kf-conn-card-toggle > svg:last-of-type {
  flex-shrink: 0;
  transition: transform .15s var(--ease);
}
.kf-conn-card-type {
  font-size: 11px; color: var(--t-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0; flex: 1 1 auto;
}
.kf-conn-card-body {
  padding: 14px; display: flex; flex-direction: column;
  gap: 12px; align-items: stretch;
}
.kf-conn-card-row1 {
  display: flex; gap: 14px; align-items: center;
  width: 100%; min-width: 0;
}
.kf-conn-card-row1 > svg { flex-shrink: 0; }
.kf-conn-meta {
  display: grid; grid-template-columns: auto 1fr;
  row-gap: 4px; column-gap: 12px;
  flex: 1; min-width: 0;
  font-size: 12px;
}
.kf-conn-meta-row {
  display: contents;        /* let label + value flow into the grid cells */
  font-size: 12px; color: var(--t-3);
}
.kf-conn-meta-row span { color: var(--t-3); white-space: nowrap; }
.kf-conn-meta-row b { color: var(--t-1); font-weight: 600; text-align: right; }
.kf-conn-progress {
  width: 100%; height: 4px;
  background: var(--bg-3); border-radius: 2px; overflow: hidden;
}
.kf-conn-progress-fill {
  height: 100%; border-radius: 2px;
  transition: width .3s var(--ease);
}
.kf-conn-desc {
  width: 100%; font-size: 11.5px; color: var(--t-3); text-align: center;
  /* Clamp to 2 lines so cards stay the same height in the grid. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.35;
  min-height: 1.35em;
}
.kf-conn-add {
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 8px;
  background: transparent;
  border: 1.5px dashed var(--line-strong);
  cursor: pointer;
  color: var(--t-3); font-size: 12px; font-weight: 600;
  font-family: inherit;
  min-height: 200px;
  transition: all .15s var(--ease);
}
.kf-conn-add:hover { border-color: var(--info); color: var(--info); background: var(--info-soft); }

/* ── Expand / collapse behaviour ───────────────────────────────────────
   - On wide viewports (≥ 1100px) every card body is shown regardless
     of the React expandedId state — there's plenty of room to scan
     them all at once.
   - On narrower viewports only the card whose id matches expandedId
     keeps its body. Collapsed cards shrink to header height so the
     grid stays dense and the user can scroll a long list quickly.
   - The chevron rotates 90° between collapsed (▶) and expanded (▼)
     for a touch of motion feedback. */
.kf-conn-card.is-collapsed .kf-conn-card-body { display: none; }
@media (min-width: 1100px) {
  /* show all bodies regardless of state */
  .kf-conn-card.is-collapsed .kf-conn-card-body { display: flex; }
}

/* Container queries: when a card is narrower than 240px (typical when
   the grid is in 1-column mode on phones, or when the side panel is
   open), stack the pin diagram above the meta and shrink the SVG. */
@container connCard (max-width: 240px) {
  .kf-conn-card-body { padding: 12px; gap: 10px; }
  .kf-conn-card-row1 { flex-direction: column; gap: 10px; align-items: center; }
  .kf-conn-card-row1 > svg { width: 76px; height: 76px; }
  .kf-conn-meta {
    grid-template-columns: 1fr auto;
    width: 100%;
  }
}
/* At very narrow card widths (e.g. 1-column on 320px-wide screens),
   hide the pin diagram entirely — the meta carries the same info and
   the diagram becomes too small to read anyway. */
@container connCard (max-width: 180px) {
  .kf-conn-card-row1 > svg { display: none; }
  .kf-conn-card .card-head { padding: 8px 10px; }
  .kf-conn-add { min-height: 140px; font-size: 11.5px; }
}

/* Fallback for browsers without container-query support — same idea
   but driven by the viewport. Below 720px the main content area is
   usually a single column anyway. */
@supports not (container-type: inline-size) {
  @media (max-width: 720px) {
    .kf-conn-card-row1 { flex-direction: column; }
    .kf-conn-card-row1 > svg { width: 76px; height: 76px; }
    .kf-conn-meta { grid-template-columns: 1fr auto; width: 100%; }
  }
}

/* View-header polish for narrow viewports — keep the count badge with
   the title, let the action buttons wrap onto a new row. */
.kf-view-head {
  padding: clamp(10px, 1.8vw, 18px) clamp(12px, 2vw, 22px) clamp(8px, 1.4vw, 14px);
}
@media (max-width: 540px) {
  .kf-view-head-r .kf-btn-label { display: none; }   /* icon-only on phones */
  .kf-view-head-r .kf-btn { padding-left: 10px; padding-right: 10px; }
}

/* — Settings — */
.kf-settings-body { flex: 1; display: grid; grid-template-columns: 188px 1fr; overflow: hidden; }
.kf-settings-nav { padding: 14px 8px; border-right: 1px solid var(--line); background: var(--bg-2); display: flex; flex-direction: column; gap: 2px; }
.kf-settings-nav-item {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px;
  background: transparent; border: none; cursor: pointer;
  color: var(--t-2); text-align: left;
  border-radius: var(--r-2);
  font-family: inherit; font-size: 12.5px; font-weight: 500;
  transition: all .12s var(--ease);
}
.kf-settings-nav-item:hover { background: var(--bg-3); color: var(--t-1); }
.kf-settings-nav-item.on { background: var(--info-soft); color: var(--info); font-weight: 600; }
/* Settings forms — horizontal label/input rows.
   Project Settings and Workspace Settings cards reuse the .kf-form pattern.
   In the original (used by Wires, etc.) the label sits ABOVE the field;
   inside the settings panels that wastes vertical space and causes the two-
   column form-row to overflow at narrow widths. Inside .kf-settings we flip
   to a horizontal row: label on the left (160px), control on the right
   (flex), hint underneath. The row stacks back to vertical at < 540px. */
.kf-settings .kf-form { gap: 4px; }
.kf-settings .kf-form-row {
  display: flex; flex-direction: column;
  gap: 4px;
  padding: 12px 0;
  border-bottom: 1px dashed var(--line);
}
.kf-settings .kf-form-row:last-child { border-bottom: none; }
.kf-settings .kf-field {
  display: grid;
  grid-template-columns: 180px 1fr;
  align-items: center;
  gap: 16px;
  min-width: 0;
}
.kf-settings .kf-field > label {
  margin: 0;
  font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--t-2);
}
.kf-settings .kf-field > .kf-input,
.kf-settings .kf-field > select.kf-input,
.kf-settings .kf-field > textarea.kf-input {
  grid-column: 2;
  width: 100%;
  max-width: 480px;
}
.kf-settings .kf-field > .kf-field-hint {
  grid-column: 2;
  margin-top: 0;
}
/* Stack at narrow widths so labels don't crush against the input. */
@media (max-width: 720px) {
  .kf-settings .kf-field { grid-template-columns: 1fr; gap: 4px; }
  .kf-settings .kf-field > .kf-input { max-width: none; }
}
.kf-settings-panel { padding: 18px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }

/* Settings view header tweaks — give the description more breathing room
   and bump its size; the short title leaves plenty of space underneath. */
.kf-settings .kf-view-head { flex-direction: column; align-items: flex-start; gap: 6px; padding: 22px 24px 18px; }
.kf-settings .kf-view-head-l h1 { font-size: 24px; }
.kf-settings .kf-view-hint { font-size: 14px; line-height: 1.55; max-width: 760px; color: var(--t-2); }
.kf-settings .kf-view-hint b { color: var(--t-1); font-weight: 600; }
.kf-settings .kf-view-head-r { margin-left: 0; }

/* Project Settings — collaborator, compliance, danger rows. */
.kf-collab-row {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
}
.kf-collab-row:last-child { border-bottom: none; }

/* Project team table — full-width with role dropdown column. */
.kf-team-table { width: 100%; }
.kf-team-table td .kf-input-sm { max-width: 160px; }
.kf-team-legend {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
  padding: 10px 12px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.kf-team-legend-hint {
  margin-left: auto;
  font-size: 11px; color: var(--t-3); font-style: italic;
}

.kf-compliance-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
}
.kf-compliance-row:last-child { border-bottom: none; }
.kf-compliance-row .kf-checkfield { align-items: flex-start; gap: 10px; flex: 1; }
.kf-compliance-row .kf-checkfield input { margin-top: 3px; }
.kf-compliance-row label { display: flex; flex-direction: column; gap: 2px; cursor: pointer; }
.kf-compliance-l { font-size: 12.5px; font-weight: 600; color: var(--t-1); font-family: var(--f-mono); }
.kf-compliance-d { font-size: 11.5px; color: var(--t-3); }

.kf-danger-zone { border-color: rgba(239,78,78,.22); }
.kf-danger-zone .card-head h3 { color: var(--danger); }
.kf-danger-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 0;
  border-bottom: 1px dashed var(--line);
}
.kf-danger-row:last-child { border-bottom: none; }
.kf-danger-title { font-size: 13px; font-weight: 600; color: var(--t-1); margin-bottom: 4px; }
.kf-danger-title.kf-danger-strong { color: var(--danger); }
.kf-danger-hint { font-size: 11.5px; color: var(--t-3); max-width: 520px; line-height: 1.45; }

.kf-swatch-row { display: flex; gap: 10px; flex-wrap: wrap; }
.kf-swatch { display: flex; flex-direction: column; gap: 6px; padding: 8px; background: var(--bg-1); border: 1.5px solid var(--line); border-radius: var(--r-3); cursor: pointer; font: inherit; color: var(--t-2); font-size: 11.5px; font-weight: 600; }
.kf-swatch.on { border-color: var(--info); color: var(--info); }
.kf-swatch-preview { width: 80px; height: 50px; border-radius: 4px; border: 1px solid var(--line); }

/* — NodeEditModal — */
.kf-node-form .kf-form-row { gap: 12px; }
.kf-field-hint { font-weight: 400; color: var(--t-3); font-size: 11px; }
.kf-link { background: transparent; border: 0; color: var(--info); font: inherit; font-size: 11.5px; cursor: pointer; padding: 0; }
.kf-link:hover { text-decoration: underline; }

.kf-node-wires {
  max-height: 200px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--r-2);
  background: var(--bg-1); padding: 4px;
  display: flex; flex-direction: column; gap: 2px;
}
.kf-node-wires-empty { padding: 16px; text-align: center; color: var(--t-3); font-size: 12px; }
.kf-node-wire {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px;
  border-radius: var(--r-1);
  font-size: 11.5px; font-family: var(--f-mono); color: var(--t-2);
  cursor: pointer;
}
.kf-node-wire:hover { background: var(--bg-3); }
.kf-node-wire.on { background: var(--info-soft); color: var(--t-1); }
.kf-node-wire input[type="checkbox"] { margin: 0; flex-shrink: 0; }
.kf-node-wire-tag { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kf-node-wire-swatch { width: 10px; height: 10px; border-radius: 2px; border: 1px solid var(--line); flex-shrink: 0; }
.kf-node-wires-count { font-size: 11.5px; color: var(--success); margin-top: 4px; }

.kf-node-preview-wrap { display: flex; justify-content: center; margin: 12px 0; }
.kf-node-preview {
  width: 200px; padding: 16px;
  background: var(--bg-1); border: 1px solid var(--line); border-radius: var(--r-3);
  text-align: center;
}
.kf-node-preview-label { font-size: 10px; font-weight: 700; color: var(--t-3); letter-spacing: 1px; text-transform: uppercase; margin-bottom: 8px; }
.kf-node-preview-glyph { font-size: 32px; color: var(--warning); margin: 4px 0; }
.kf-node-preview-sub { font-size: 10.5px; color: var(--t-3); }

.kf-radio-row { display: inline-flex; gap: 2px; padding: 3px; background: var(--bg-1); border: 1px solid var(--line); border-radius: var(--r-2); }
.kf-radio { padding: 6px 16px; background: transparent; border: none; cursor: pointer; color: var(--t-3); font: inherit; font-size: 12px; font-weight: 600; border-radius: var(--r-1); }
.kf-radio.on { background: var(--info); color: #fff; }

.kf-sc-list { display: flex; flex-direction: column; gap: 1px; }
.kf-sc-row { display: flex; justify-content: space-between; align-items: center; padding: 9px 4px; border-bottom: 1px solid var(--line); font-size: 12.5px; color: var(--t-2); }
.kf-sc-row:last-child { border-bottom: none; }
.kf-sc-keys { display: flex; gap: 4px; }

.kf-integrations { display: flex; flex-direction: column; gap: 1px; }
.kf-int-row { display: flex; align-items: center; justify-content: space-between; padding: 12px 4px; border-bottom: 1px solid var(--line); }
.kf-int-row:last-child { border-bottom: none; }
.kf-int-name { font-size: 13px; font-weight: 600; color: var(--t-1); }
.kf-int-sub { font-size: 11px; color: var(--t-3); margin-top: 2px; }

/* — Welcome — */
.kf-welcome { display: flex; align-items: center; justify-content: center; height: 100%; padding: 40px; }
.kf-welcome-inner { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; max-width: 560px; }
.kf-welcome-logo { margin-bottom: 4px; }
.kf-welcome-title { font-family: var(--f-display); font-size: 34px; font-weight: 800; letter-spacing: -1px; }
.kf-welcome-sub { font-size: 12px; color: var(--t-3); text-transform: uppercase; letter-spacing: 2px; margin-bottom: 16px; }
.kf-welcome-shortcuts { font-size: 12px; color: var(--t-3); margin-top: 8px; }
.kf-welcome-shortcuts > span { display: inline-flex; align-items: center; gap: 6px; }
`;
document.head.appendChild(Object.assign(document.createElement('style'), { textContent: STYLE }));

Object.assign(window, { ProjectHome, ConnectorsView, SettingsView, ProjectSettingsView, NotesView, WelcomeView, ProjectsView, LibraryView, KabelFluxMark });
