// DEV_Design.jsx — table layout with real canvas
// hooks via React.useState / React.useEffect

const PALETTE_GROUPS = [
  { label:'Data Fields', items:[
    { type:'input',    label:'Text Input',  icon:'T',  color:'#2563eb' },
    { type:'number',   label:'Number',      icon:'#',  color:'#0891b2' },
    { type:'date',     label:'Date',        icon:'📅', color:'#059669' },
    { type:'time',     label:'Time',        icon:'🕐', color:'#059669' },
    { type:'select',   label:'Dropdown',    icon:'▾',  color:'#d97706' },
    { type:'checkbox', label:'Checkbox',    icon:'☑',  color:'#d97706' },
    { type:'file',     label:'File/Image',  icon:'📎', color:'#dc2626' },
  ]},
  { label:'Display', items:[
    { type:'text',    label:'Text/Label', icon:'ℹ',  color:'#64748b' },
    { type:'rating',  label:'Rating',     icon:'⭐', color:'#f59e0b' },
    { type:'divider', label:'Divider',    icon:'—',  color:'#94a3b8' },
    { type:'spacer',  label:'Spacer',     icon:'↕',  color:'#94a3b8' },
    { type:'tabs',    label:'Tabs',        icon:'⊟',  color:'#7c3aed' },
  ]},
  { label:'External', items:[
    { type:'map',      label:'Map',      icon:'🗺', color:'#059669' },
    { type:'weather',  label:'Weather',  icon:'⛅', color:'#0891b2' },
    { type:'portal',   label:'Portal',   icon:'⊞',  color:'#7c3aed' },
    { type:'currency', label:'Currency', icon:'$',  color:'#d97706', soon:true },
    { type:'s3',       label:'S3/R2',    icon:'☁',  color:'#dc2626', soon:true },
  ]},
];

const OBJ_ICONS = {
  input:    { icon:'T',  color:'#2563eb' },
  textarea: { icon:'¶',  color:'#7c3aed' },
  number:   { icon:'#',  color:'#0891b2' },
  date:     { icon:'📅', color:'#059669' },
  time:     { icon:'🕐', color:'#059669' },
  select:   { icon:'▾',  color:'#d97706' },
  checkbox: { icon:'☑',  color:'#d97706' },
  file:     { icon:'📎', color:'#dc2626' },
  text:     { icon:'ℹ',  color:'#64748b' },
  tabs:     { icon:'⊟',  color:'#7c3aed' },
  map:      { icon:'🗺', color:'#059669' },
  weather:  { icon:'⛅', color:'#0891b2' },
  portal:   { icon:'⊞',  color:'#7c3aed' },
  rating:   { icon:'⭐', color:'#f59e0b' },
};

function objIcon(type) { return OBJ_ICONS[type] || { icon:'?', color:'#94a3b8' }; }

// ─────────────────────────────────────────────
// DOConfirm — custom confirm dialog
// Replaces window.confirm() throughout DEV/APP
//
// Usage:
//   const [confirm, setConfirm] = React.useState(null);
//   setConfirm({ msg:'Delete row?', onOk: () => doDelete() });
//   {confirm && <DOConfirm {...confirm} onClose={() => setConfirm(null)} />}
// ─────────────────────────────────────────────
function DOConfirm({ msg, detail, okLabel='Yes', cancelLabel='Cancel',
                     okDanger=false, onOk, onClose }) {
  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);
  return (
    <>
      <div onClick={onClose} style={{
        position:'fixed', inset:0, zIndex:2000,
        background:'rgba(0,0,0,0.35)',
      }} />
      <div style={{
        position:'fixed', top:'50%', left:'50%',
        transform:'translate(-50%,-50%)',
        zIndex:2001, background:'#fff', borderRadius:10,
        boxShadow:'0 8px 40px rgba(0,0,0,0.18)',
        padding:'24px 28px', minWidth:320, maxWidth:440,
        fontFamily:'DM Sans, sans-serif',
      }}>
        <div style={{ fontSize:15, fontWeight:700, color:'#0f1923', marginBottom:8 }}>{msg}</div>
        {detail && <div style={{ fontSize:13, color:'#64748b', marginBottom:16, lineHeight:1.5 }}>{detail}</div>}
        <div style={{ display:'flex', justifyContent:'flex-end', gap:8, marginTop:detail?0:16 }}>
          <button onClick={onClose}
            style={{ padding:'7px 18px', background:'#fff', border:'1.5px solid #e2e8f0',
              borderRadius:6, fontSize:13, cursor:'pointer', fontFamily:'DM Sans, sans-serif',
              color:'#374151', fontWeight:500 }}>
            {cancelLabel}
          </button>
          <button onClick={() => { onOk(); onClose(); }}
            style={{ padding:'7px 18px',
              background: okDanger ? '#dc2626' : '#0f1923',
              border:'none', borderRadius:6, fontSize:13, cursor:'pointer',
              fontFamily:'DM Sans, sans-serif', color:'#fff', fontWeight:600 }}>
            {okLabel}
          </button>
        </div>
      </div>
    </>
  );
}

// ─────────────────────────────────────────────
// KB (Knowledge Base) — Info icon + sidebar
//
// Usage: <KBIcon kbKey="valuelists" />
//
// Fetches article from /v6/kb/:key on first open.
// Renders a slide-in sidebar with HTML content.
// Add anywhere in DEV or APP with one line.
// ─────────────────────────────────────────────
function KBIcon({ kbKey, size = 14 }) {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <button
        onClick={() => setOpen(true)}
        title="Learn more"
        style={{
          background: 'none', border: 'none', cursor: 'pointer',
          padding: '1px 3px', lineHeight: 1, display: 'inline-flex',
          alignItems: 'center', color: '#94a3b8', flexShrink: 0,
        }}
        onMouseEnter={e => e.currentTarget.style.color = '#2563eb'}
        onMouseLeave={e => e.currentTarget.style.color = '#94a3b8'}
      >
        <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
          stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <circle cx="12" cy="12" r="10"/>
          <line x1="12" y1="8" x2="12" y2="8" strokeWidth="2.5"/>
          <line x1="12" y1="12" x2="12" y2="16"/>
        </svg>
      </button>
      {open && <KBSidebar kbKey={kbKey} onClose={() => setOpen(false)} />}
    </>
  );
}

function KBSidebar({ kbKey, onClose }) {
  const [loading, setLoading] = React.useState(true);
  const [title,   setTitle]   = React.useState('');
  const [content, setContent] = React.useState('');
  const [error,   setError]   = React.useState(null);

  React.useEffect(() => {
    setLoading(true); setError(null);
    apiFetch(`${API_BASE}/v6/kb/${encodeURIComponent(kbKey)}`)
      .then(r => r.json())
      .then(data => {
        if (data.status !== 'ok') throw new Error(data.message);
        setTitle(data.title);
        setContent(data.content);
      })
      .catch(e => setError(e.message))
      .finally(() => setLoading(false));
  }, [kbKey]);

  // Close on Escape
  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  return (
    <>
      {/* Backdrop */}
      <div
        onClick={onClose}
        style={{
          position: 'fixed', inset: 0, zIndex: 1100,
          background: 'rgba(0,0,0,0.25)',
        }}
      />
      {/* Sidebar */}
      <div style={{
        position: 'fixed', top: 0, right: 0, bottom: 0,
        width: 360, zIndex: 1101,
        background: '#fff',
        borderLeft: '1px solid #e2e8f0',
        boxShadow: '-4px 0 24px rgba(0,0,0,0.12)',
        display: 'flex', flexDirection: 'column',
        fontFamily: 'DM Sans, sans-serif',
      }}>
        {/* Header */}
        <div style={{
          padding: '14px 16px', borderBottom: '1px solid #e2e8f0',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          background: '#0f1923', flexShrink: 0,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="#93c5fd" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="10"/>
              <line x1="12" y1="8" x2="12" y2="8" strokeWidth="2.5"/>
              <line x1="12" y1="12" x2="12" y2="16"/>
            </svg>
            <span style={{ color: '#fff', fontWeight: 700, fontSize: 14 }}>
              {loading ? 'Loading…' : title || 'Help'}
            </span>
          </div>
          <button onClick={onClose}
            style={{
              background: 'rgba(255,255,255,0.1)', border: 'none', color: '#fff',
              width: 28, height: 28, borderRadius: 5, cursor: 'pointer', fontSize: 16,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontFamily: 'inherit',
            }}>✕</button>
        </div>

        {/* Content */}
        <div style={{ flex: 1, overflowY: 'auto', padding: '20px 20px' }}>
          {loading && (
            <div style={{ color: '#94a3b8', fontSize: 13, textAlign: 'center', marginTop: 40 }}>
              Loading…
            </div>
          )}
          {error && (
            <div style={{ color: '#dc2626', fontSize: 12, padding: '10px 12px',
              background: '#fee2e2', borderRadius: 6 }}>
              ⚠ {error}
            </div>
          )}
          {!loading && !error && (
            <div
              className="kb-content"
              dangerouslySetInnerHTML={{ __html: content }}
              style={{ fontSize: 13, color: '#374151', lineHeight: 1.7 }}
            />
          )}
        </div>

        {/* Footer */}
        <div style={{
          padding: '10px 16px', borderTop: '1px solid #f1f5f9',
          fontSize: 10, color: '#94a3b8', flexShrink: 0,
        }}>
          Press Esc to close · Key: {kbKey}
        </div>
      </div>

      {/* Inline styles for kb-content HTML */}
      <style>{`
        .kb-content h2 { font-size:15px; font-weight:700; color:#0f1923; margin:0 0 10px; }
        .kb-content h3 { font-size:13px; font-weight:700; color:#1e293b; margin:16px 0 6px; }
        .kb-content p  { margin:0 0 10px; }
        .kb-content ul, .kb-content ol { padding-left:18px; margin:0 0 10px; }
        .kb-content li { margin-bottom:4px; }
        .kb-content code { background:#f1f5f9; padding:1px 5px; border-radius:3px;
          font-family:DM Mono,monospace; font-size:11px; color:#2563eb; }
        .kb-content strong { font-weight:700; color:#0f1923; }
        .kb-content a { color:#2563eb; }
        .kb-content hr { border:none; border-top:1px solid #e2e8f0; margin:14px 0; }
        .kb-content .tip { background:#eff6ff; border-left:3px solid #2563eb;
          padding:8px 12px; border-radius:0 6px 6px 0; margin:10px 0; font-size:12px; }
        .kb-content .warn { background:#fef9ec; border-left:3px solid #f59e0b;
          padding:8px 12px; border-radius:0 6px 6px 0; margin:10px 0; font-size:12px; }
      `}</style>
    </>
  );
}

function DEVDesign({ app, jwt }) {
  const [tables,       setTables]       = React.useState([]);
  const [activeTable,  setActiveTable]  = React.useState(null);
  const [activeView,   setActiveView]   = React.useState('home'); // home | list | detail
  const [activeLayout, setActiveLayout] = React.useState('1');
  const [fields,       setFields]       = React.useState([]);
  const [layoutMap,    setLayoutMap]    = React.useState({});
  const [tabs,         setTabs]         = React.useState(null);
  const [theme,        setTheme]        = React.useState(null);
  const [selectedObj,  setSelectedObj]  = React.useState(null);
  const [showTheme,    setShowTheme]    = React.useState(false);
  const [listviewCfg,  setListviewCfg]  = React.useState(null);
  const [collapsed,    setCollapsed]    = React.useState({});
  const [loading,      setLoading]      = React.useState(false);
  const [dirty,        setDirty]        = React.useState(false);
  const [dirtySection, setDirtySection] = React.useState(null); // 'home'|'theme'|'layout'
  const [saving,       setSaving]       = React.useState(false);
  const [saveMsg,      setSaveMsg]      = React.useState(null);
  const [placingType,  setPlacingType]  = React.useState(null); // object type being placed
  const [hoverCol,     setHoverCol]     = React.useState(null); // { rowNum, colNum }
  const [iconBrowser,  setIconBrowser]  = React.useState({ open:false, onSelect:null }); // icon browser sidebar
  const [confirm,      setConfirm]      = React.useState(null); // DOConfirm dialog state

  // ── Add Tabs Object ───────────────────────────────────────────────────
  // Called when dev clicks "Tabs" in palette.
  // Only allowed if no tabsObj exists yet for this layout.
  function addTabsObject() {
    const fLid = Object.keys(layoutMap)[0] || activeLayout;
    const existing = Object.values(layoutMap[fLid]?.rows?.['0']?.cols || {})
      .flatMap(c => c.objects || []).find(o => o.type === 'tabs');
    if (existing) {
      setConfirm({
        msg: 'Tabs already added',
        detail: 'This table already has a Tabs object. Click it in the canvas to edit.',
        okLabel: 'OK', cancelLabel: null,
        onOk: () => {},
      });
      return;
    }
    setConfirm({
      msg: `Add a Tabs object to ${activeTable?.name || 'this table'}?`,
      detail: 'A Tabs bar will appear above your rows. Use the Inspector to add, rename and style your tabs.',
      okLabel: 'Add Tabs',
      onOk: () => {
        const tempId = 'temp_' + Math.random().toString(36).slice(2).toUpperCase();
        const tabsObj = {
          _oid:    tempId,
          id:      tempId,
          group:   'object',
          type:    'tabs',
          layout:  activeLayout,
          table:   activeTable.tName,
          row:     '0',
          col:     '1',
          sort:    '1',
          style:   'underline',
          sticky:  false,
          status:  '1',
          tabDefs: [
            { id: '1', label: 'Tab 1' },
          ],
        };
        setLayoutMap(prev => {
          const next = JSON.parse(JSON.stringify(prev));
          if (!next[activeLayout]) next[activeLayout] = { rows: {} };
          // Row 0 is reserved for the Tabs object — never shown as a regular row
          const insertLid = Object.keys(next)[0] || activeLayout;
          if (!next[insertLid]) next[insertLid] = { rows: {} };
          next[insertLid].rows['0'] = {
            rowObj: null,
            cols: {
              '1': {
                colObj: {
                  _oid: 'temp_col0_' + Math.random().toString(36).slice(2),
                  group: 'layout', type: 'col',
                  layout: insertLid, table: activeTable.tName,
                  row: '0', col: '1', width: '12',
                },
                objects: [tabsObj],
              }
            }
          };
          return next;
        });
        setActiveLayout('1');
        setSelectedObj(tabsObj);
        setDirty(true); setDirtySection('layout');
        setPlacingType(null);
      },
    });
    setPlacingType(null);
  }

  React.useEffect(() => { loadTables(); }, [app.app_id]);
  React.useEffect(() => { if (activeTable) loadDesign(activeTable.tNum); }, [activeTable?.tNum]);

  async function loadTables() {
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/tables`);
      const data = await res.json();
      const sorted = (data.tables || []).sort((a,b) => a.name.localeCompare(b.name));
      setTables(sorted);
      if (sorted.length) setActiveTable({ tNum: sorted[0].tNum, tName: sorted[0].table, name: sorted[0].name });
    } catch(e) { console.error(e); }
  }

  async function loadDesign(tNum) {
    setLoading(true); setSelectedObj(null); setDirty(false);
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/design/${tNum}`);
      const data = await res.json();
      setFields(data.fields || []);
      setTabs(data.tabs);
      setTheme(data.theme);
      setListviewCfg(data.listview || null);

      setLayoutMap(data.layout || {});
      // Default to first tab from tabsObj (row 0) if present, else legacy tabs
      const allLayouts   = data.layout || {};
      const firstLid     = Object.keys(allLayouts)[0] || activeLayout;
      const row0obj      = Object.values(allLayouts[firstLid]?.rows?.['0']?.cols || {})
                             .flatMap(c => c.objects || [])
                             .find(o => o.type === 'tabs');
      const firstTabId   = row0obj?.tabDefs?.[0]?.id
                        || data.tabs?.tabs?.[0]?.id
                        || '1';
      // Preserve current tab after save — only reset if on initial load or tab no longer exists
      const validIds = (row0obj?.tabDefs || []).map(t => String(t.id));
      if (validIds.length === 0 || !validIds.includes(String(activeLayout))) {
        setActiveLayout(firstTabId);
      }
      // else: keep activeLayout so dev stays on the tab they were editing
    } catch(e) { console.error(e); }
    setLoading(false);
  }

  function toggleGroup(label) {
    setCollapsed(prev => ({ ...prev, [label]: !prev[label] }));
  }

  function markLayout() { setDirty(true); setDirtySection('layout'); }

  // ESC cancels placing mode
  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') setPlacingType(null); }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // ─────────────────────────────────────────────
  // PURE REACT DRAG/DROP
  // Replaces SortableJS — no DOM mutation conflicts
  //
  // dragState ref: tracks drag in progress without
  // causing re-renders on every mousemove.
  // dragOver state: minimal — only row/col/index
  // needed to render the drop indicator line.
  //
  // Supports:
  //   - Reorder within a column
  //   - Move between columns (any row)
  //   - Clean undo on Escape or mouseup outside canvas
  // ─────────────────────────────────────────────
  const dragState   = React.useRef(null); // { oid, fromRow, fromCol, fromIdx }
  const dragOverRef = React.useRef(null); // mirrors dragOver — always current in closures
  const [dragOver, setDragOver] = React.useState(null); // { row, col, idx } — drop indicator

  function handleDragStart(e, obj, rowNum, colNum) {
    e.preventDefault();
    const objs = layoutMap[activeLayout]?.rows[rowNum]?.cols[colNum]?.objects || [];
    const fromIdx = objs.findIndex(o => o._oid === obj._oid);
    dragState.current = { oid: obj._oid, fromRow: rowNum, fromCol: colNum, fromIdx };
    document.body.style.cursor = 'grabbing';

    function onMouseUp() {
      document.body.style.cursor = '';
      document.removeEventListener('mouseup', onMouseUp);
      // Read from ref — NOT stale closure variable
      if (dragState.current && dragOverRef.current) {
        commitDrop(dragState.current, dragOverRef.current);
      }
      dragState.current   = null;
      dragOverRef.current = null;
      setDragOver(null);
    }

    document.addEventListener('mouseup', onMouseUp);
  }

  function handleColMouseMove(e, rowNum, colNum) {
    if (!dragState.current) return;
    const colEl    = e.currentTarget;
    const children = Array.from(colEl.querySelectorAll('[data-objidx]'));
    let dropIdx    = children.length;

    for (let i = 0; i < children.length; i++) {
      const rect = children[i].getBoundingClientRect();
      if (e.clientY < rect.top + rect.height / 2) { dropIdx = i; break; }
    }

    const next = { row: rowNum, col: colNum, idx: dropIdx };
    dragOverRef.current = next;
    setDragOver(prev => {
      if (prev?.row === rowNum && prev?.col === colNum && prev?.idx === dropIdx) return prev;
      return next;
    });
  }

  function handleColMouseLeave() {
    if (!dragState.current) return;
    dragOverRef.current = null;
    setDragOver(null);
  }

  function commitDrop(ds, dov) {
    if (!ds || !dov) return;
    const { oid, fromRow, fromCol } = ds;
    const { row: toRow, col: toCol, idx: toIdx } = dov;

    setLayoutMap(prev => {
      const next = JSON.parse(JSON.stringify(prev));
      const lay  = prev[activeLayout] ? activeLayout : Object.keys(prev)[0];
      const fromObjs = next[lay]?.rows[fromRow]?.cols[fromCol]?.objects;
      const toObjs   = next[lay]?.rows[toRow]?.cols[toCol]?.objects;
      if (!fromObjs || !toObjs) return prev;

      const fromIdx = fromObjs.findIndex(o => o._oid === oid);
      if (fromIdx === -1) return prev;

      const [moved] = fromObjs.splice(fromIdx, 1);
      moved.row = toRow;
      moved.col = toCol;

      // Adjust insertion index if dropping in same col after removal
      let insertAt = toIdx;
      if (fromRow === toRow && fromCol === toCol && toIdx > fromIdx) {
        insertAt = toIdx - 1;
      }
      insertAt = Math.max(0, Math.min(insertAt, toObjs.length));
      toObjs.splice(insertAt, 0, moved);

      // Renumber sort for all cols
      Object.entries(next[lay].rows).forEach(([rn, rd]) => {
        Object.entries(rd.cols).forEach(([cn, cd]) => {
          (cd.objects || []).forEach((o, i) => {
            o.sort = String(i + 1);
            o.row  = rn;
            o.col  = cn;
          });
        });
      });

      return next;
    });

    setDirty(true);
    setDirtySection('layout');
  }

  function addObjectToCol(rowNum, colNum, e) {
    if (!placingType) return;
    if (placingType === 'tabs') return; // tabs never placed in col
    // Only place if clicking the col body background, not an existing object
    // e is optional — if provided check target
    // e is optional — target check removed, parent div handles click area

    // Parse type and field from placingType
    let objType = placingType;
    let fieldRef = null;
    let fieldName = null;

    if (placingType.startsWith('field:')) {
      // Placing a specific field from palette
      fieldRef = placingType.slice(6); // e.g. 'f1'
      const schemaField = fields.find(f => f.field === fieldRef);
      fieldName = schemaField?.name || null;
      objType   = schemaField?.fieldType || 'input';
    }

    const next = JSON.parse(JSON.stringify(layoutMap));
    const colObjs = next[activeLayout].rows[rowNum].cols[colNum].objects;
    const newSort = colObjs.length + 1;

    const tempId = Math.random().toString(36).slice(2).toUpperCase();
    const newObj = {
      _oid:        `temp_${tempId}`,
      // no 'app' attribute — not needed
      id:          `temp_${tempId}`,  // will be replaced with real UUID on save
      group:       'object',
      type:        objType,
      name:        fieldName || null,
      label:       fieldName || null,
      labelpos:    'top',
      labelalign:  'left',
      field:       fieldRef  || null,
      layout:      activeLayout,
      table:       activeTable.tName,
      row:         rowNum,
      col:         colNum,
      sort:        String(newSort),
      status:      '1',
    };

    colObjs.push(newObj);
    setLayoutMap(next);
    setDirty(true);
    setDirtySection('layout');
    setSelectedObj(newObj);  // open inspector immediately
    setPlacingType(null);    // exit placing mode
  }

  async function handleSave() {
    setSaving(true); setSaveMsg(null);
    try {
      if (dirtySection === 'theme' && theme) {
        const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/design/theme/save`, {
          method: 'POST',
          body: JSON.stringify({ theme }),
        });
        const data = await res.json();
        if (data.status !== 'ok') throw new Error(data.message);
      } else if (dirtySection === 'listview') {
        const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/design/${activeTable?.tNum}/listview/save`, {
          method: 'POST',
          body: JSON.stringify({ listview: listviewCfg }),
        });
        const data = await res.json();
        if (data.status !== 'ok') throw new Error(data.message);
      } else if (dirtySection === 'home') {
        const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/design/home/save`, {
          method: 'POST',
          body: JSON.stringify({ tables }),
        });
        const data = await res.json();
        if (data.status !== 'ok') throw new Error(data.message);
      } else {
        // Save layout objects
        const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/design/${activeTable?.tNum}/save`, {
          method: 'POST',
          body: JSON.stringify({ objects: collectObjects(), deletedIds:[], tabs }),
        });
        const data = await res.json();
        if (data.status !== 'ok') throw new Error(data.message);
        // Reload design after layout save — replaces temp_ IDs with real DB IDs
        // so subsequent saves don't re-insert already-saved objects
        await loadDesign(activeTable.tNum);
      }
      setDirty(false); setDirtySection(null);
      setSaveMsg('✓ Saved');
      setTimeout(() => setSaveMsg(null), 3000);
    } catch(err) {
      setSaveMsg('⚠ ' + err.message);
    }
    setSaving(false);
  }

  function collectObjects() {
    const all = [];
    Object.entries(layoutMap[activeLayout]?.rows || {}).forEach(([, rowData]) => {
      if (rowData.rowObj) all.push(rowData.rowObj);
      Object.entries(rowData.cols || {}).forEach(([, colData]) => {
        if (colData.colObj) all.push(colData.colObj);
        (colData.objects || []).forEach(o => all.push(o));
      });
    });
    return all;
  }

  function moveObj(rowNum, colNum, oid, dir) {
    const next = JSON.parse(JSON.stringify(layoutMap));
    const objs = next[activeLayout].rows[rowNum].cols[colNum].objects;
    const idx  = objs.findIndex(o => o._oid === oid);
    const swap = idx + dir;
    if (swap < 0 || swap >= objs.length) return;
    [objs[idx], objs[swap]] = [objs[swap], objs[idx]];
    setLayoutMap(next); setDirty(true);
  }

  function deleteObj(rowNum, colNum, oid) {
    const next = JSON.parse(JSON.stringify(layoutMap));
    next[activeLayout].rows[rowNum].cols[colNum].objects =
      next[activeLayout].rows[rowNum].cols[colNum].objects.filter(o => o._oid !== oid);
    setLayoutMap(next);
    if (selectedObj?._oid === oid) setSelectedObj(null);
    setDirty(true);
  }

  // tabList — read from layout '1' row 0 (tabs object is table-level, not per-layout)
  const firstLayoutId = Object.keys(layoutMap)[0] || '1';
  const tabsObjDef = Object.values(layoutMap[firstLayoutId]?.rows?.['0']?.cols || {})
    .flatMap(c => c.objects || [])
    .find(o => o.type === 'tabs');
  const tabList = (selectedObj?.type === 'tabs' ? selectedObj.tabDefs : null)
               || tabsObjDef?.tabDefs
               || tabs?.tabs
               || [{ id:'1', label:'Default' }];
  // Row 0 is reserved for tabsObj — exclude from regular row rendering (all layouts)
  const curRows  = Object.entries(layoutMap[activeLayout]?.rows || {})
    .filter(([rn]) => rn !== '0')
    .sort(([a],[b]) => Number(a) - Number(b));

  return (
    <div id="do-design-panel" style={{ border:'1px solid #e2e8f0', borderRadius:8, overflow:'hidden', marginTop:8, background:'#fff' }}>

      {/* Toolbar */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'8px 14px', borderBottom:'1px solid #e2e8f0', background:'#f8fafc', gap:8 }}>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          {/* Home button */}
          {/* Home Page button */}
          <button onClick={() => { setActiveView('home'); setSelectedObj(null); setShowTheme(false); }}
            style={{ padding:'5px 12px', border: (activeView==='home' && !showTheme) ? '2px solid #0f1923' : '1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontWeight:500, background:'#fff', color:'#0f1923', cursor:'pointer', fontFamily:'DM Sans, sans-serif', display:'flex', alignItems:'center', gap:5 }}>
            🏠 <span>Home Page</span>
          </button>

          {/* Table selector */}
          <select
            style={{ padding:'5px 10px', border: (['list','detail'].includes(activeView) && !showTheme) ? '2px solid #0f1923' : '1.5px solid #e2e8f0', borderRadius:6, fontSize:13, fontFamily:'DM Sans, sans-serif', background:'#fff', cursor:'pointer' }}
            value={activeView === 'home' || activeView === 'valuelists' || showTheme ? '' : (activeTable?.tNum || '')}
            onChange={e => {
              if (!e.target.value) return;
              const t = tables.find(t => String(t.tNum) === e.target.value);
              if (t) { setActiveTable({ tNum:t.tNum, tName:t.table, name:t.name }); setShowTheme(false); setActiveView('detail'); setSelectedObj(null); }
            }}>
            <option value="">🗃️ Tables</option>
            {tables.map(t => <option key={t.tNum} value={t.tNum}>{t.name}</option>)}
          </select>

          {/* List / Detail buttons */}
          {['list','detail'].includes(activeView) && (
            <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden' }}>
              {['list','detail'].map(v => (
                <button key={v}
                  style={{ padding:'5px 14px', border:'none', borderRight:'1px solid #e2e8f0', fontSize:12, fontWeight:500, cursor:'pointer', fontFamily:'DM Sans, sans-serif', background: activeView===v ? '#f1f5f9' : '#fff', color:'#0f1923', fontWeight: activeView===v ? 700 : 500 }}
                  onClick={() => { setActiveView(v); setShowTheme(false); }}>
                  {v === 'detail' ? 'Detail View' : 'List View'}
                </button>
              ))}
            </div>
          )}

          {/* Separator */}
          <div style={{ width:1, height:22, background:'#e2e8f0', margin:'0 2px' }} />

          {/* Theme button */}
          <button onClick={() => { setShowTheme(!showTheme); }}
            style={{ padding:'5px 12px', border: showTheme ? '2px solid #0f1923' : '1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontWeight:500, background:'#fff', color:'#0f1923', cursor:'pointer', fontFamily:'DM Sans, sans-serif', display:'flex', alignItems:'center', gap:5 }}>
            🎨 <span>Theme</span>
          </button>

          {/* Value Lists button */}
          <button onClick={() => { setActiveView('valuelists'); setShowTheme(false); }}
            style={{ padding:'5px 12px', border: (activeView==='valuelists' && !showTheme) ? '2px solid #0f1923' : '1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontWeight:500, background:'#fff', color:'#0f1923', cursor:'pointer', fontFamily:'DM Sans, sans-serif', display:'flex', alignItems:'center', gap:5 }}>
            🗒️ <span>Value Lists</span>
          </button>
        </div>

        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          {dirty && <span style={{ fontSize:12, color:'#f59e0b', fontWeight:500 }}>● Unsaved {dirtySection ? `(${dirtySection})` : ''}</span>}
          {saveMsg && <span style={{ fontSize:12, color:'#22c55e', fontWeight:500 }}>{saveMsg}</span>}
          {dirty && (
            <button onClick={handleSave} disabled={saving}
              style={{ padding:'5px 14px', background:'#0f1923', color:'#fff', border:'none', borderRadius:6, fontSize:12, fontWeight:600, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {saving ? 'Saving…' : 'Save'}
            </button>
          )}
          <button onClick={() => {
            const el = document.getElementById('do-design-panel');
            if (!el) return;
            const isExpanded = el.getAttribute('data-expanded') === '1';
            if (!isExpanded) {
              el.setAttribute('data-expanded','1');
              el.style.position = 'fixed';
              el.style.top      = '54px'; // below DataObjects nav bar
              el.style.left     = '0';
              el.style.right    = '0';
              el.style.bottom   = '0';
              el.style.zIndex   = '500';
              el.style.borderRadius = '0';
              el.style.marginTop = '0';
            } else {
              el.setAttribute('data-expanded','0');
              el.style.position = '';
              el.style.top = el.style.left = el.style.right = el.style.bottom = '';
              el.style.zIndex = '';
              el.style.borderRadius = '';
              el.style.marginTop = '';
            }
          }} style={{ padding:'5px 9px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:14, background:'#fff', cursor:'pointer' }} title="Expand">⛶</button>
        </div>
      </div>

      {/* Three columns via display:table — palette hidden for home/theme/valuelists */}
      <div style={{ display:'table', width:'100%', tableLayout:'fixed' }}>
        <div style={{ display:'table-row' }}>

          {/* LEFT — Palette (Detail only) */}
          <div style={{ display:'table-cell', width: (activeView==='detail' && !showTheme) ? 185 : 0, verticalAlign:'top', borderRight: (activeView==='detail' && !showTheme) ? '1px solid #e2e8f0' : 'none', background:'#fff', overflow:'hidden', padding: (activeView==='detail' && !showTheme) ? undefined : 0 }}>
            <div style={{ fontSize:11, fontWeight:700, color:'#64748b', letterSpacing:'0.07em', textTransform:'uppercase', padding:'10px 14px 6px', borderBottom:'1px solid #f1f5f9' }}>Objects</div>
            {PALETTE_GROUPS.map(group => (
              <div key={group.label}>
                <button onClick={() => toggleGroup(group.label)}
                  style={{ width:'100%', display:'flex', justifyContent:'space-between', alignItems:'center', padding:'7px 14px 4px', background:'none', border:'none', fontSize:11, fontWeight:600, color:'#94a3b8', cursor:'pointer', textTransform:'uppercase', letterSpacing:'0.05em', fontFamily:'DM Sans, sans-serif' }}>
                  <span>{group.label}</span><span style={{ fontSize:10 }}>{collapsed[group.label] ? '▶' : '▼'}</span>
                </button>
                {!collapsed[group.label] && (
                  <div style={{ padding:'0 8px 6px', display:'flex', flexDirection:'column', gap:2 }}>
                    {group.items.map(item => (
                      <button key={item.type}
                        onClick={() => {
                          if (item.soon) return;
                          if (item.type === 'tabs') { addTabsObject(); return; }
                          setPlacingType(placingType === item.type ? null : item.type);
                        }}
                        style={{ display:'flex', alignItems:'center', gap:8, padding:'5px 7px',
                          background: placingType === item.type ? '#eff6ff' : '#f8fafc',
                          border: placingType === item.type ? '1.5px solid #2563eb' : '1px solid #f1f5f9',
                          borderRadius:6, cursor: item.soon ? 'not-allowed' : 'pointer',
                          opacity: item.soon ? 0.4 : 1, fontFamily:'DM Sans, sans-serif' }}>
                        <span style={{ width:20, height:20, borderRadius:4, display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:700, flexShrink:0, background: item.color+'18', color: item.color }}>{item.icon}</span>
                        <span style={{ fontSize:12, color:'#374151', flex:1, textAlign:'left' }}>{item.label}</span>
                        {item.soon && <span style={{ fontSize:9, color:'#94a3b8' }}>soon</span>}
                      </button>
                    ))}
                  </div>
                )}
              </div>
            ))}
            {fields.length > 0 && (
              <div>
                <button onClick={() => toggleGroup('Fields')}
                  style={{ width:'100%', display:'flex', justifyContent:'space-between', alignItems:'center', padding:'7px 14px 4px', background:'none', border:'none', fontSize:11, fontWeight:600, color:'#94a3b8', cursor:'pointer', textTransform:'uppercase', letterSpacing:'0.05em', fontFamily:'DM Sans, sans-serif' }}>
                  <span>Fields ({fields.length})</span><span style={{ fontSize:10 }}>{collapsed['Fields'] ? '▶' : '▼'}</span>
                </button>
                {!collapsed['Fields'] && (
                  <div style={{ padding:'0 8px 8px', display:'flex', flexDirection:'column', gap:2 }}>
                    {fields.map(f => (
                      <button key={f.o_id}
                        onClick={() => setPlacingType(placingType === ('field:'+f.field) ? null : ('field:'+f.field))}
                        style={{ display:'flex', alignItems:'center', gap:6, padding:'4px 7px',
                          background: placingType===('field:'+f.field) ? '#eff6ff' : '#f8fafc',
                          border: placingType===('field:'+f.field) ? '1.5px solid #2563eb' : '1px solid #f1f5f9',
                          borderRadius:6, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                        <span style={{ fontFamily:'DM Mono, monospace', fontSize:10, color:'#94a3b8', width:24 }}>{f.field}</span>
                        <span style={{ fontSize:12, color:'#374151', flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', textAlign:'left' }}>{f.name}</span>
                      </button>
                    ))}
                  </div>
                )}
              </div>
            )}
          </div>

          {/* CENTER — Canvas */}
          <div style={{ display:'table-cell', verticalAlign:'top', background:'#f8fafc' }}>

            {/* Placing mode banner */}
            {placingType && placingType !== 'tabs' && (
              <div style={{ background:'#2563eb', color:'#fff', padding:'7px 16px', fontSize:12,
                fontWeight:500, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
                <span>✛ Click a column to place <strong>{placingType.startsWith('field:') ? placingType.slice(6) : placingType}</strong></span>
                <button onClick={() => setPlacingType(null)}
                  style={{ background:'rgba(255,255,255,0.2)', border:'none', color:'#fff',
                    padding:'3px 10px', borderRadius:4, fontSize:12, cursor:'pointer', fontFamily:'inherit' }}>
                  Cancel (ESC)
                </button>
              </div>
            )}

            {/* THEME VIEW — full canvas, no overlay */}
            {showTheme && (
              <ThemePanel theme={theme} onChange={t => { setTheme(t); setDirty(true); setDirtySection('theme'); }} />
            )}

            {/* HOME VIEW */}
            {!showTheme && activeView === 'home' && (
              <HomeCanvas
                tables={tables}
                selectedObj={selectedObj}
                onSelectTable={t => { setSelectedObj({ ...t, _isTable:true, _oid: t.o_id || ('table_'+t.tNum) }); }}
                onReorder={newTables => { setTables(newTables); setDirty(true); }}
              />
            )}

            {/* LIST VIEW */}
            {!showTheme && activeView === 'list' && (
              <ListViewPanel
                fields={fields}
                cfg={listviewCfg}
                table={activeTable}
                app={app}
                onChange={cfg => { setListviewCfg(cfg); setDirty(true); setDirtySection('listview'); }}
              />
            )}

            {/* VALUE LISTS VIEW */}
            {!showTheme && activeView === 'valuelists' && (
              <ValueListsPanel app={app} />
            )}

            {/* DETAIL VIEW */}
            {!showTheme && activeView === 'detail' && (
              <div>
                {/* ── TABS OBJECT — rendered above rows when present ── */}
                {tabsObjDef && (() => {
                  const tObj = tabsObjDef;
                  const tabDefs = tObj.tabDefs || [{ id:'1', label:'Tab 1' }];
                  const sel = selectedObj?._oid === tObj._oid;
                  const isUnderline = tObj.style !== 'tabs' && tObj.style !== 'pills';
                  const isPills     = tObj.style === 'pills';
                  const isTabs      = tObj.style === 'tabs';
                  return (
                    <div
                      onClick={() => setSelectedObj(tObj)}
                      style={{
                        margin:'12px 12px 0',
                        border: sel ? '1.5px solid #7c3aed' : '1px solid #e2e8f0',
                        borderRadius: isTabs ? '8px 8px 0 0' : 8,
                        background:'#fff', cursor:'pointer',
                        boxShadow: sel ? '0 0 0 2px #ede9fe' : 'none',
                      }}>
                      {/* Tab style preview */}
                      <div style={{
                        display:'flex', alignItems:'center', gap: isPills ? 6 : 0,
                        padding: isPills ? '8px 12px' : isUnderline ? '0 12px' : '8px 12px 0',
                        borderBottom: isUnderline ? '2px solid #e2e8f0' : 'none',
                        flexWrap:'wrap',
                      }}>
                        {tabDefs.map(t => {
                          const active = t.id === activeLayout;
                          if (isPills) return (
                            <button key={t.id} onClick={e => { e.stopPropagation(); setActiveLayout(t.id); }}
                              style={{ padding:'5px 14px', borderRadius:20, border:'none', fontSize:12,
                                fontWeight:600, cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                                background: active ? '#7c3aed' : '#f1f5f9',
                                color: active ? '#fff' : '#64748b' }}>
                              {t.label}
                            </button>
                          );
                          if (isTabs) return (
                            <button key={t.id} onClick={e => { e.stopPropagation(); setActiveLayout(t.id); }}
                              style={{ padding:'8px 18px', border:'1px solid #e2e8f0',
                                borderBottom: active ? '1px solid #fff' : '1px solid #e2e8f0',
                                borderRadius:'6px 6px 0 0', fontSize:12, fontWeight:600,
                                cursor:'pointer', fontFamily:'DM Sans, sans-serif', marginBottom:-1,
                                background: active ? '#fff' : '#f8fafc',
                                color: active ? '#0f1923' : '#64748b' }}>
                              {t.label}
                            </button>
                          );
                          // underline (default)
                          return (
                            <button key={t.id} onClick={e => { e.stopPropagation(); setActiveLayout(t.id); }}
                              style={{ padding:'10px 16px', border:'none',
                                borderBottom: active ? '2px solid #7c3aed' : '2px solid transparent',
                                background:'none', fontSize:13, fontWeight:500,
                                cursor:'pointer', fontFamily:'DM Sans, sans-serif', marginBottom:'-2px',
                                color: active ? '#7c3aed' : '#64748b' }}>
                              {t.label}
                            </button>
                          );
                        })}
                        <span style={{ marginLeft:'auto', paddingRight:6, fontSize:13, color:'#cbd5e1' }}>✏️</span>
                      </div>
                    </div>
                  );
                })()}

                {/* Rows */}
                <div style={{ padding:12, display:'flex', flexDirection:'column', gap:10 }}>
                  {loading && <div style={{ textAlign:'center', padding:40, color:'#94a3b8' }}>Loading…</div>}
                  {!loading && curRows.length === 0 && (
                    <div style={{ border:'2px dashed #e2e8f0', borderRadius:8, padding:40, textAlign:'center', color:'#94a3b8', fontSize:13 }}>
                      No layout yet — click + Add Row
                    </div>
                  )}
                  {curRows.map(([rowNum, rowData]) => {
                    const cols = Object.entries(rowData.cols || {}).sort(([a],[b]) => Number(a)-Number(b));
                    return (
                      <div key={rowNum} style={{ background:'#fff', border:'1px solid #e2e8f0', borderRadius:8, overflow:'hidden' }}>
                        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'6px 10px', background:'#f1f5f9', borderBottom:'1px solid #e2e8f0' }}>
                          <span style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.06em' }}>Row {rowNum}</span>
                          <div style={{ display:'flex', gap:6 }}>
                            <button onClick={() => {
                              const next = JSON.parse(JSON.stringify(layoutMap));
                              const rd = next[activeLayout].rows[rowNum];
                              const colNums = Object.keys(rd.cols).map(Number);
                              const newCol = String(Math.max(...colNums) + 1);
                              const total  = colNums.length + 1;
                              const base   = Math.floor(12 / total);
                              const rem    = 12 - base * total;
                              colNums.forEach((c,i) => { rd.cols[c].colObj.width = String(base + (i===0?rem:0)); });
                              rd.cols[newCol] = { colObj:{ _oid:`temp_${Math.random().toString(36).slice(2)}`, group:'layout', type:'col', layout:activeLayout, table:activeTable.tName, row:rowNum, col:newCol, width:String(base) }, objects:[] };
                              setLayoutMap(next); setDirty(true);
                            }} style={{ padding:'2px 8px', background:'#fff', border:'1px solid #e2e8f0', borderRadius:4, fontSize:11, cursor:'pointer', color:'#374151', fontFamily:'DM Sans, sans-serif' }}>+ Col</button>
                            <button onClick={() => {
                              setConfirm({
                                msg: 'Delete this row?',
                                detail: 'All objects in this row will be removed.',
                                okLabel: 'Delete', okDanger: true,
                                onOk: () => {
                                  const next = JSON.parse(JSON.stringify(layoutMap));
                                  delete next[activeLayout].rows[rowNum];
                                  setLayoutMap(next); setDirty(true);
                                }
                              });
                            }} style={{ padding:'2px 8px', background:'#fff', border:'1px solid #e2e8f0', borderRadius:4, fontSize:11, cursor:'pointer', color:'#dc2626', fontFamily:'DM Sans, sans-serif' }}>✕</button>
                          </div>
                        </div>
                        <div style={{ display:'table', width:'100%', tableLayout:'fixed' }}>
                          <div style={{ display:'table-row' }}>
                            {cols.map(([colNum, colData]) => (
                              <div key={colNum} style={{ display:'table-cell', verticalAlign:'top', borderRight: colData.colObj?.border !== false ? '1px solid #f1f5f9' : 'none', width:`${((Number(colData.colObj?.width||4))/12)*100}%` }}>
                                <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'4px 8px', background: selectedObj?._isCol && selectedObj?.row===rowNum && selectedObj?.col===colNum ? '#eff6ff' : '#fafafa', borderBottom:'1px solid #f1f5f9', border: selectedObj?._isCol && selectedObj?.row===rowNum && selectedObj?.col===colNum ? '1.5px solid #2563eb' : 'none' }}>
                                  <span style={{ fontSize:10, fontWeight:600, color:'#94a3b8', textTransform:'uppercase' }}>Col {colNum} <span style={{ fontWeight:400 }}>({colData.colObj?.width||4}/12)</span></span>
                                  <div style={{ display:'flex', gap:3 }}>
                                    <button onClick={e => { e.stopPropagation();
                                      const siblings = Object.entries(layoutMap[activeLayout].rows[rowNum].cols)
                                        .filter(([cn]) => cn !== colNum)
                                        .reduce((sum,[,cd]) => sum + Number(cd.colObj?.width||4), 0);
                                      const colObj = colData.colObj || {};
                                      setSelectedObj({
                                        ...colObj,
                                        _isCol: true,
                                        _oid: colObj._oid || colObj.id || `col_${rowNum}_${colNum}`,
                                        row: rowNum,
                                        col: colNum,
                                        width: colObj.width || '4',
                                        _siblingTotal: siblings,
                                        _layoutId: activeLayout,
                                      });
                                    }} style={{ background:'none', border:'none', fontSize:13, cursor:'pointer', color:'#64748b', padding:'1px 3px', fontFamily:'inherit' }} title="Edit column">✏️</button>

                                  </div>
                                </div>
                                <div
                                  data-row={rowNum} data-col={colNum}
                                  onClick={() => { if(placingType) addObjectToCol(rowNum, colNum); }}
                                  onMouseEnter={() => { placingType && setHoverCol({rowNum, colNum}); }}
                                  onMouseMove={e => { if(!placingType) handleColMouseMove(e, rowNum, colNum); }}
                                  onMouseLeave={() => { setHoverCol(null); handleColMouseLeave(); }}
                                  style={{ padding:6, display:'flex', flexDirection:'column', gap:5, minHeight:50,
                                    cursor: placingType ? 'crosshair' : 'default',
                                    background: (placingType && hoverCol?.rowNum===rowNum && hoverCol?.colNum===colNum) ? 'rgba(37,99,235,0.06)' :
                                                (dragOver?.row===rowNum && dragOver?.col===colNum) ? 'rgba(37,99,235,0.04)' : 'transparent',
                                    outline: (placingType && hoverCol?.rowNum===rowNum && hoverCol?.colNum===colNum) ? '2px dashed #2563eb' :
                                             (dragOver?.row===rowNum && dragOver?.col===colNum) ? '1px dashed #93c5fd' : 'none',
                                    outlineOffset:'-3px', borderRadius:4 }}>
                                  {(colData.objects||[]).map((obj, objIdx) => {
                                    const oi  = objIcon(obj.type);
                                    const f   = fields.find(f => f.field === obj.field);
                                    const sel = selectedObj?._oid === obj._oid;
                                    const isDragging = dragState.current?.oid === obj._oid;
                                    const showDropBefore = dragOver?.row === rowNum && dragOver?.col === colNum && dragOver?.idx === objIdx;
                                    const showDropAfter  = dragOver?.row === rowNum && dragOver?.col === colNum && dragOver?.idx === objIdx + 1 && objIdx === (colData.objects||[]).length - 1;
                                    return (
                                      <React.Fragment key={obj._oid}>
                                        {/* Drop indicator line — appears above this object */}
                                        {showDropBefore && (
                                          <div style={{ height:3, background:'#2563eb', borderRadius:2, margin:'0 2px', flexShrink:0 }} />
                                        )}
                                      <div data-objidx={objIdx} onClick={e => {
                                          if (placingType) {
                                            e.stopPropagation();
                                            addObjectToCol(rowNum, colNum);
                                          } else {
                                            e.stopPropagation();
                                            setSelectedObj(obj);
                                          }
                                        }}
                                        style={{ border: sel ? '1.5px solid #2563eb' : '1.5px solid #94a3b8', borderRadius:6, padding:'6px 7px', cursor: placingType ? 'crosshair' : 'default', background: isDragging ? '#f0f7ff' : sel ? '#eff6ff' : '#fff', opacity: isDragging ? 0.6 : 1, transition:'opacity 0.1s' }}>
                                        <div style={{ display:'flex', alignItems:'center', gap:5, marginBottom:3 }}>
                                          <span style={{ fontSize:11, color:'#374151', fontWeight:500, flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                                            {['divider','spacer','text'].includes(obj.type)
                                              ? <span style={{ color:'#94a3b8', fontStyle:'italic' }}>{obj.type}</span>
                                              : (f?.name || obj.label || obj.name || <span style={{ color:'#dc2626' }}>unset</span>)
                                            }
                                          </span>
                                          <span style={{ fontSize:9, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.04em', flexShrink:0 }}>{obj.type}</span>
                                          <span style={{ width:18, height:18, borderRadius:3, display:'flex', alignItems:'center', justifyContent:'center', fontSize:10, fontWeight:700, background:oi.color+'18', color:oi.color, flexShrink:0 }}>{oi.icon}</span>
                                          {/* Drag handle — mousedown starts drag */}
                                          <span
                                            className="drag-handle"
                                            onMouseDown={e => { e.stopPropagation(); handleDragStart(e, obj, rowNum, colNum); }}
                                            style={{ cursor:'grab', color:'#cbd5e1', fontSize:12, padding:'0 2px', flexShrink:0, userSelect:'none' }}
                                            title="Drag to reorder">⠿</span>
                                        </div>
                                        {(obj.type==='input'||obj.type==='number') && <div style={{ fontSize:10, color:'#64748b', fontFamily:'DM Mono, monospace', borderBottom:'1px solid #94a3b8', paddingBottom:2 }}>___________</div>}
                                        {obj.type==='date'     && <div style={{ fontSize:10, color:'#64748b', fontFamily:'DM Mono, monospace' }}>MM/DD/YYYY</div>}
                                        {obj.type==='time'     && <div style={{ fontSize:10, color:'#64748b', fontFamily:'DM Mono, monospace' }}>HH:MM</div>}
                                        {obj.type==='select'   && <div style={{ fontSize:10, color:'#475569', border:'1px solid #94a3b8', borderRadius:3, padding:'2px 5px', display:'flex', justifyContent:'space-between' }}><span>Select…</span><span>▾</span></div>}
                                        {obj.type==='checkbox' && <div style={{ display:'flex', alignItems:'center', gap:4 }}><div style={{ width:12, height:12, border:'1.5px solid #64748b', borderRadius:2 }}></div><span style={{ fontSize:10, color:'#94a3b8' }}>{f?.name||obj.name}</span></div>}
                                        {obj.type==='file'     && <div style={{ textAlign:'center', padding:'4px 0' }}><span style={{ fontSize:16 }}>📎</span></div>}
                                        {obj.type==='text' && (
                                          <div style={{
                                            fontSize: (obj.fontSize||12)+'px',
                                            fontWeight: obj.fontWeight||'400',
                                            textAlign: obj.textalign||'left',
                                            color: obj.color||'#374151',
                                            background: obj.bgcolor||'transparent',
                                            padding: obj.bgcolor ? '4px 6px' : '0',
                                            borderRadius: obj.bgcolor ? '3px' : '0',
                                            lineHeight: 1.4,
                                          }}>
                                            {obj.contentType === 'formula'
                                              ? (obj.formula || <span style={{color:'#94a3b8',fontStyle:'italic'}}>formula...</span>)
                                              : (obj.content || <span style={{color:'#94a3b8',fontStyle:'italic'}}>empty text</span>)
                                            }
                                          </div>
                                        )}
                                        {obj.type==='divider'  && <div style={{ borderTop: `${obj.height||1}px solid ${obj.color||'#e2e8f0'}`, margin:'3px 0' }} />}
                                        {obj.type==='spacer'   && <div style={{ height: (obj.height||20)+'px', background:'repeating-linear-gradient(45deg,#f8fafc,#f8fafc 3px,#fff 3px,#fff 8px)', borderRadius:3 }} />}
                                        {obj.type==='map'      && <div style={{ background:'#ecfdf5', borderRadius:3, padding:'4px 0', textAlign:'center', fontSize:14 }}>🗺</div>}
                                        {obj.type==='weather'  && <div style={{ background:'#eff6ff', borderRadius:3, padding:'4px 0', textAlign:'center', fontSize:14 }}>⛅</div>}
                                      </div>
                                        {/* Drop indicator line — appears after last object */}
                                        {showDropAfter && (
                                          <div style={{ height:3, background:'#2563eb', borderRadius:2, margin:'0 2px', flexShrink:0 }} />
                                        )}
                                      </React.Fragment>
                                    );
                                  })}
                                  {/* Empty state */}
                                  {(colData.objects||[]).length === 0 && (
                                    <div
                                      onClick={e => { e.stopPropagation(); if(placingType) addObjectToCol(rowNum, colNum); }}
                                      style={{ border: placingType ? '1px dashed #2563eb' : dragOver?.row===rowNum && dragOver?.col===colNum ? '2px dashed #2563eb' : '1px dashed #94a3b8',
                                        borderRadius:6, padding:'10px 4px', textAlign:'center',
                                        fontSize:10, color: placingType ? '#2563eb' : dragOver?.row===rowNum && dragOver?.col===colNum ? '#2563eb' : '#64748b',
                                        cursor: placingType ? 'crosshair' : 'default',
                                        background: dragOver?.row===rowNum && dragOver?.col===colNum ? 'rgba(37,99,235,0.06)' : 'transparent' }}>
                                      {placingType ? '✛ click to place' : dragOver?.row===rowNum && dragOver?.col===colNum ? 'drop here' : 'empty'}
                                    </div>
                                  )}
                                  {/* Drop zone — always visible when placing, even if col has objects */}
                                  {placingType && (colData.objects||[]).length > 0 && (
                                    <div
                                      onClick={e => { e.stopPropagation(); addObjectToCol(rowNum, colNum); }}
                                      style={{ border:'1px dashed #2563eb', borderRadius:6,
                                        padding:'6px 4px', textAlign:'center', fontSize:10,
                                        color:'#2563eb', cursor:'crosshair', marginTop:2 }}>
                                      ✛ add here
                                    </div>
                                  )}
                                </div>
                              </div>
                            ))}
                          </div>
                        </div>
                      </div>
                    );
                  })}
                  <button onClick={() => {
                    const next = JSON.parse(JSON.stringify(layoutMap));
                    if (!next[activeLayout]) next[activeLayout] = { rows:{} };
                    const rows = Object.keys(next[activeLayout].rows);
                    const newRow = String(rows.length ? Math.max(...rows.map(Number)) + 1 : 1);
                    next[activeLayout].rows[newRow] = {
                      rowObj: { _oid:`temp_${Math.random().toString(36).slice(2)}`, group:'layout', type:'row', layout:activeLayout, table:activeTable.tName, row:newRow, status:'1' },
                      cols: { '1': { colObj:{ _oid:`temp_${Math.random().toString(36).slice(2)}`, group:'layout', type:'col', layout:activeLayout, table:activeTable.tName, row:newRow, col:'1', width:'12' }, objects:[] } }
                    };
                    setLayoutMap(next); setDirty(true);
                  }} style={{ padding:10, background:'#fff', border:'2px dashed #cbd5e1', borderRadius:8, fontSize:13, fontWeight:500, color:'#64748b', cursor:'pointer', fontFamily:'DM Sans, sans-serif', width:'100%' }}>+ Add Row</button>
                </div>
              </div>
            )}

          </div>

          {/* RIGHT — Inspector */}
          {/* RIGHT — Inspector (hidden for theme) */}
          <div style={{ display:'table-cell', width: (showTheme || activeView==='valuelists' || activeView==='list') ? 0 : 220, verticalAlign:'top', borderLeft: (showTheme || activeView==='valuelists' || activeView==='list') ? 'none' : '1px solid #e2e8f0', background:'#fff', overflow:'hidden' }}>
            <Inspector
              selectedObj={selectedObj}
              fields={fields}
              tables={tables}
              app={app}
              onBrowseIcon={(onSelect) => setIconBrowser({
                open: true,
                onSelect: (val) => { onSelect(val); setIconBrowser(b => ({...b, open:false})); }
              })}
              onUpdateTable={(updated) => {
                setTables(prev => prev.map(t => t.tNum === updated.tNum ? { ...t, ...updated } : t));
                setSelectedObj(updated);
                setDirty(true); setDirtySection('home');
                // Also need to save back to objects table — mark as dirty with special flag
              }}
              onUpdate={(updated) => {
                setSelectedObj(updated);
                const next = JSON.parse(JSON.stringify(layoutMap));
                const lid = updated.layout || '1';
                const r    = updated.row;
                const c    = updated.col;
                if (updated.type === 'tabs') {
                  // Update tabs object — search all layouts for row 0
                  Object.values(next).forEach(layData => {
                    Object.values(layData?.rows?.['0']?.cols || {}).forEach(cd => {
                      const idx = (cd.objects || []).findIndex(o => o.type === 'tabs');
                      if (idx >= 0) cd.objects[idx] = { ...cd.objects[idx], ...updated };
                    });
                  });
                } else if (updated._isCol) {
                  // Update col — spread full updated object so all attributes (width, border, etc.) are saved
                  if (next[lid]?.rows[r]?.cols[c]) {
                    const { _isCol, _siblingTotal, _layoutId, _oid, ...colAttrs } = updated;
                    next[lid].rows[r].cols[c].colObj = { ...next[lid].rows[r].cols[c].colObj, ...colAttrs };
                  }
                } else if (next[lid]?.rows[r]?.cols[c]) {
                  const objs = next[lid].rows[r].cols[c].objects;
                  const idx  = objs.findIndex(o => o._oid === updated._oid);
                  if (idx >= 0) objs[idx] = updated;
                }
                setLayoutMap(next);
                setDirty(true);
              }}
              onDelete={(obj) => {
                if (obj.type === 'tabs') {
                  setConfirm({
                    msg: 'Remove Tabs object?',
                    detail: 'The tab bar will be removed. Your rows and content are not affected.',
                    okLabel: 'Remove', okDanger: true,
                    onOk: () => {
                      const next = JSON.parse(JSON.stringify(layoutMap));
                      Object.values(next).forEach(layData => {
                        if (layData?.rows?.['0']) delete layData.rows['0'];
                      });
                      setLayoutMap(next); setDirty(true); setSelectedObj(null);
                    }
                  });
                  return;
                }
                if (obj._isCol) {
                  // Delete column
                  const colKeys = Object.keys(layoutMap[obj._layoutId || activeLayout]?.rows[obj.row]?.cols || {});
                  if (colKeys.length <= 1) { alert('Row must have at least one column.'); return; }
                  const next = JSON.parse(JSON.stringify(layoutMap));
                  delete next[obj._layoutId || activeLayout].rows[obj.row].cols[obj.col];
                  setLayoutMap(next); setDirty(true); setSelectedObj(null);
                } else {
                  deleteObj(obj.row, obj.col, obj._oid);
                }
              }}
            />
          </div>

        </div>
      </div>
    {/* Icon Browser Sidebar */}
    {iconBrowser.open && (
      <IconBrowser
        onSelect={iconBrowser.onSelect}
        onClose={() => setIconBrowser(b => ({...b, open:false}))}
      />
    )}
    {/* Custom Confirm Dialog */}
    {confirm && (
      <DOConfirm
        msg={confirm.msg}
        detail={confirm.detail}
        okLabel={confirm.okLabel || 'Yes'}
        cancelLabel={confirm.cancelLabel !== undefined ? confirm.cancelLabel : 'Cancel'}
        okDanger={confirm.okDanger}
        onOk={confirm.onOk}
        onClose={() => setConfirm(null)}
      />
    )}

    </div>
  );
}

// ─────────────────────────────────────────────
// Inspector component
// ─────────────────────────────────────────────
// ─────────────────────────────────────────────
// ValueListPicker — dropdown of existing value lists
// Used in select/checkbox inspector
// ─────────────────────────────────────────────
function ValueListPicker({ appId, value, onChange }) {
  const [lists,   setLists]   = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    if (!appId) return;
    apiFetch(`${API_BASE}/v6/dev/apps/${appId}/valuelists`)
      .then(r => r.json())
      .then(d => { if (d.status === 'ok') setLists(d.lists); })
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [appId]);

  if (loading) return <div style={{ fontSize:12, color:'#94a3b8', marginBottom:10 }}>Loading lists…</div>;
  if (!lists.length) return (
    <div style={{ padding:'8px 10px', background:'#fef9ec', border:'1px solid #fde68a',
      borderRadius:6, fontSize:12, color:'#92400e', marginBottom:10 }}>
      No value lists found. Create one in the Value Lists tab first.
    </div>
  );

  return (
    <select value={value || ''} onChange={e => onChange(e.target.value)}
      style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6,
        fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923',
        background:'#fff', outline:'none', marginBottom:10 }}>
      <option value="">— select a value list —</option>
      {lists.map(vl => (
        <option key={vl.id} value={vl.name}>{vl.name} ({(vl.options||[]).length} options)</option>
      ))}
    </select>
  );
}

// ─────────────────────────────────────────────
// ValueListsPanel — two-column: list | editor
// ─────────────────────────────────────────────
function ValueListsPanel({ app }) {
  const [lists,      setLists]      = React.useState([]);
  const [loading,    setLoading]    = React.useState(true);
  const [selected,   setSelected]   = React.useState(null); // { id, name, options, desc }
  const [editing,    setEditing]    = React.useState(null); // local edit state
  const [saving,     setSaving]     = React.useState(false);
  const [msg,        setMsg]        = React.useState(null);
  const [newName,    setNewName]    = React.useState('');
  const [creating,   setCreating]   = React.useState(false);
  const [library,    setLibrary]    = React.useState({ public:[], private:[] });
  const [libLoading, setLibLoading] = React.useState(false);
  const [showSaveToLib, setShowSaveToLib] = React.useState(false);
  const [vlIconBrowser, setVlIconBrowser]  = React.useState(false);

  React.useEffect(() => { loadLists(); loadLibrary(); }, [app?.app_id]);

  async function loadLists() {
    if (!app?.app_id) return;
    setLoading(true);
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/apps/${app.app_id}/valuelists`);
      const data = await res.json();
      if (data.status === 'ok') setLists(data.lists);
    } catch(e) { console.error(e); }
    setLoading(false);
  }

  // Convert options array to editable text
  // Supports both plain strings and {label, value} objects
  function optionsToText(options) {
    if (!options?.length) return '';
    return options.map(o => {
      if (typeof o === 'string') return o;
      if (o.label !== undefined && o.value !== undefined) return `${o.label} | ${o.value}`;
      return JSON.stringify(o);
    }).join('\n');
  }

  // Convert editable text back to options array
  // Lines with " | " become {label, value} pairs, others stay as plain strings
  function textToOptions(text) {
    return text.split('\n')
      .map(s => s.trim())
      .filter(Boolean)
      .map(line => {
        const pipeIdx = line.indexOf(' | ');
        if (pipeIdx !== -1) {
          return { label: line.slice(0, pipeIdx).trim(), value: line.slice(pipeIdx + 3).trim() };
        }
        return line;
      });
  }

  async function loadLibrary() {
    if (!app?.app_id) return;
    setLibLoading(true);
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/library`);
      const data = await res.json();
      if (data.status === 'ok') {
        setLibrary({ public: data.public || [], private: data.private || [] });
      } else {
        console.error('Library load error:', data.message);
      }
    } catch(e) { console.error('Library fetch error:', e); }
    setLibLoading(false);
  }

  async function addFromLibrary(libItem) {
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/library/add-to-app`, {
        method: 'POST',
        body: JSON.stringify({ appId: app.app_id, libId: libItem.id }),
      });
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);
      setMsg('✓ Added to app');
      setTimeout(() => setMsg(null), 2500);
      await loadLists();
      selectList({ id: data.id, name: data.name, options: data.options, desc: '' });
    } catch(e) { setMsg('⚠ ' + e.message); }
  }

  async function saveToLibrary() {
    if (!editing?.id) return;
    setShowSaveToLib(false);
    try {
      const res  = await apiFetch(`${API_BASE}/v6/dev/library/save-to-private`, {
        method: 'POST',
        body: JSON.stringify({ appId: app.app_id, vlId: editing.id }),
      });
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);
      setMsg('✓ Saved to private library');
      setTimeout(() => setMsg(null), 2500);
      await loadLibrary();
    } catch(e) { setMsg('⚠ ' + e.message); }
  }

  function sortOptions() {
    if (!editing) return;
    const sorted = textToOptions(editing.optionsText || '');
    sorted.sort((a, b) => {
      const la = (typeof a === 'string' ? a : a.label).toLowerCase();
      const lb = (typeof b === 'string' ? b : b.label).toLowerCase();
      return la < lb ? -1 : la > lb ? 1 : 0;
    });
    setEditing(p => ({ ...p, optionsText: optionsToText(sorted) }));
  }

  function selectList(vl) {
    setSelected(vl);
    setEditing({ ...vl, icon: vl.icon || '', optionsText: optionsToText(vl.options) });
    setMsg(null);
  }

  async function saveList() {
    if (!editing) return;
    setSaving(true); setMsg(null);
    try {
      const options = textToOptions(editing.optionsText || '');
      const res  = await apiFetch(
        `${API_BASE}/v6/dev/apps/${app.app_id}/valuelists/${editing.id}`,
        { method:'POST', body: JSON.stringify({ name: editing.name, desc: editing.desc, icon: editing.icon || '', options }) }
      );
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);
      setMsg('✓ Saved');
      setTimeout(() => setMsg(null), 2500);
      await loadLists();
      setEditing(prev => ({ ...prev, options, optionsText: optionsToText(options) }));
    } catch(e) { setMsg('⚠ ' + e.message); }
    setSaving(false);
  }

  async function deleteList() {
    if (!editing) return;
    if (!confirm(`Delete "${editing.name}"? This cannot be undone.`)) return;
    setSaving(true);
    try {
      await apiFetch(
        `${API_BASE}/v6/dev/apps/${app.app_id}/valuelists/${editing.id}`,
        { method:'POST', body: JSON.stringify({ action:'delete' }) }
      );
      setSelected(null); setEditing(null);
      await loadLists();
    } catch(e) { setMsg('⚠ ' + e.message); }
    setSaving(false);
  }

  async function createList() {
    if (!newName.trim()) return;
    setSaving(true);
    try {
      const res  = await apiFetch(
        `${API_BASE}/v6/dev/apps/${app.app_id}/valuelists`,
        { method:'POST', body: JSON.stringify({ name: newName.trim(), options: [] }) }
      );
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);
      setNewName(''); setCreating(false);
      await loadLists();
      // Auto-select new list
      selectList({ id: data.id, name: data.name, options: [], desc: '' });
    } catch(e) { setMsg('⚠ ' + e.message); }
    setSaving(false);
  }

  const optionCount = (editing?.optionsText || '').split('\n').filter(s => s.trim()).length;

  return (
    <div style={{ display:'table', width:'100%', tableLayout:'fixed', height:'100%' }}>
      <div style={{ display:'table-row' }}>

        {/* LEFT — list of value lists */}
        <div style={{ display:'table-cell', width:220, verticalAlign:'top',
          borderRight:'1px solid #e2e8f0', background:'#fafafa' }}>

          {/* Header */}
          <div style={{ padding:'10px 12px', borderBottom:'1px solid #e2e8f0',
            display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <span style={{ fontSize:11, fontWeight:700, color:'#64748b',
              textTransform:'uppercase', letterSpacing:'0.06em' }}>
              Value Lists
            </span>
            <button onClick={() => setCreating(true)}
              style={{ background:'#0f1923', border:'none', color:'#fff',
                width:22, height:22, borderRadius:4, cursor:'pointer', fontSize:16,
                display:'flex', alignItems:'center', justifyContent:'center',
                fontFamily:'DM Sans, sans-serif', lineHeight:1 }}>+</button>
          </div>

          {/* New list input */}
          {creating && (
            <div style={{ padding:'8px 10px', borderBottom:'1px solid #f1f5f9',
              background:'#eff6ff' }}>
              <input autoFocus value={newName} onChange={e => setNewName(e.target.value)}
                onKeyDown={e => { if(e.key==='Enter') createList(); if(e.key==='Escape') { setCreating(false); setNewName(''); } }}
                placeholder="List name…"
                style={{ width:'100%', padding:'5px 8px', border:'1.5px solid #2563eb',
                  borderRadius:5, fontSize:12, fontFamily:'DM Sans, sans-serif',
                  outline:'none', marginBottom:6, boxSizing:'border-box' }} />
              <div style={{ display:'flex', gap:6 }}>
                <button onClick={createList}
                  style={{ flex:1, padding:'4px', background:'#0f1923', color:'#fff',
                    border:'none', borderRadius:4, fontSize:11, cursor:'pointer',
                    fontFamily:'DM Sans, sans-serif' }}>Create</button>
                <button onClick={() => { setCreating(false); setNewName(''); }}
                  style={{ padding:'4px 8px', background:'#fff', color:'#64748b',
                    border:'1px solid #e2e8f0', borderRadius:4, fontSize:11,
                    cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>Cancel</button>
              </div>
            </div>
          )}

          {/* List */}
          {loading ? (
            <div style={{ padding:20, textAlign:'center', color:'#94a3b8', fontSize:12 }}>Loading…</div>
          ) : lists.length === 0 ? (
            <div style={{ padding:20, textAlign:'center', color:'#94a3b8', fontSize:12 }}>
              No value lists yet.<br/>Click + to create one.
            </div>
          ) : (
            <div style={{ overflowY:'auto', maxHeight:600 }}>
              {lists.map(vl => (
                <div key={vl.id} onClick={() => selectList(vl)}
                  style={{ padding:'8px 12px', cursor:'pointer', borderBottom:'1px solid #f1f5f9',
                    background: selected?.id === vl.id ? '#eff6ff' : 'transparent',
                    borderLeft: selected?.id === vl.id ? '3px solid #2563eb' : '3px solid transparent' }}>
                  <div style={{ fontSize:12, fontWeight:600, color:'#1e293b',
                    overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
                    display:'flex', alignItems:'center', gap:5 }}>
                    {vl.icon && <span style={{ fontSize:14 }}>{vl.icon}</span>}
                    {vl.name}
                  </div>
                  <div style={{ fontSize:11, color:'#94a3b8', marginTop:1 }}>
                    {(vl.options || []).length} options
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* RIGHT — editor */}
        <div style={{ display:'table-cell', verticalAlign:'top', background:'#fff', padding:0 }}>
          {!editing ? (
            <div style={{ padding:60, textAlign:'center', color:'#94a3b8', fontSize:13 }}>
              Select a value list to edit,<br/>or click + to create one.
            </div>
          ) : (
            <div style={{ padding:16, maxWidth:600 }}>

              {/* Name */}
              <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8',
                textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>List Name</div>
              <input value={editing.name || ''} onChange={e => setEditing(p => ({...p, name: e.target.value}))}
                style={{ width:'100%', padding:'7px 10px', border:'1.5px solid #e2e8f0',
                  borderRadius:6, fontSize:13, fontFamily:'DM Sans, sans-serif',
                  outline:'none', marginBottom:12, boxSizing:'border-box' }} />

              {/* Icon */}
              <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8',
                textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4,
                display:'flex', alignItems:'center', justifyContent:'space-between' }}>
                Icon
                <button type="button"
                  onClick={() => setVlIconBrowser(true)}
                  style={{ background:'none', border:'1px solid #e2e8f0', borderRadius:5,
                    padding:'2px 7px', fontSize:11, color:'#2563eb', cursor:'pointer',
                    fontFamily:'DM Sans, sans-serif', fontWeight:600 }}>
                  🔍 Browse
                </button>
              </div>
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:12 }}>
                <input value={editing.icon || ''} onChange={e => setEditing(p => ({...p, icon: e.target.value}))}
                  placeholder="😀"
                  style={{ width:60, height:44, padding:'4px', border:'1.5px solid #e2e8f0',
                    borderRadius:7, fontSize:24, textAlign:'center',
                    fontFamily:'DM Sans, sans-serif', outline:'none' }} />
                {editing.icon && (
                  <button onClick={() => setEditing(p => ({...p, icon: ''}))}
                    style={{ background:'none', border:'none', color:'#94a3b8',
                      cursor:'pointer', fontSize:13 }}>✕</button>
                )}
              </div>

              {/* Description */}
              <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8',
                textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Description (optional)</div>
              <input value={editing.desc || ''} onChange={e => setEditing(p => ({...p, desc: e.target.value}))}
                placeholder="e.g. US State codes"
                style={{ width:'100%', padding:'7px 10px', border:'1.5px solid #e2e8f0',
                  borderRadius:6, fontSize:13, fontFamily:'DM Sans, sans-serif',
                  outline:'none', marginBottom:12, boxSizing:'border-box' }} />

              {/* Options */}
              <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4 }}>
                <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8',
                  textTransform:'uppercase', letterSpacing:'0.06em' }}>
                  Options — one per line
                </div>
                <span style={{ fontSize:11, color:'#94a3b8', fontFamily:'DM Mono, monospace' }}>
                  {optionCount} values
                </span>
              </div>
              <textarea
                value={editing.optionsText || ''}
                onChange={e => setEditing(p => ({...p, optionsText: e.target.value}))}
                placeholder={'Plain value\nLabel | StoredValue\nPolice Dept | PD'}
                style={{ width:'100%', height:280, padding:'8px 10px',
                  border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:13,
                  fontFamily:'DM Mono, monospace', outline:'none', resize:'vertical',
                  lineHeight:1.6, boxSizing:'border-box', marginBottom:4 }} />
              <div style={{ fontSize:10, color:'#94a3b8', marginBottom:12, lineHeight:1.5 }}>
                Plain text = simple value. Use <strong>Label | Value</strong> for name/value pairs.
              </div>

              {/* Actions */}
              <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
                <button onClick={saveList} disabled={saving}
                  style={{ flex:1, padding:'9px', background:'#0f1923', color:'#fff',
                    border:'none', borderRadius:6, fontSize:13, fontWeight:600,
                    cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                  {saving ? 'Saving…' : 'Save'}
                </button>
                <button onClick={sortOptions} title="Sort options A→Z"
                  style={{ padding:'9px 12px', background:'#fff', color:'#374151',
                    border:'1px solid #e2e8f0', borderRadius:6, fontSize:13,
                    cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                  A↓Z
                </button>
                <button onClick={() => setShowSaveToLib(true)} disabled={saving}
                  title="Save to private library"
                  style={{ padding:'9px 12px', background:'#fff', color:'#2563eb',
                    border:'1px solid #bfdbfe', borderRadius:6, fontSize:13,
                    cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                  📚 Save to Library
                </button>
                <button onClick={deleteList} disabled={saving}
                  style={{ padding:'9px 12px', background:'#fff', color:'#dc2626',
                    border:'1px solid #fecaca', borderRadius:6, fontSize:13,
                    cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                  Delete
                </button>
              </div>
              {msg && (
                <div style={{ marginTop:8, fontSize:12, fontWeight:500,
                  color: msg.startsWith('✓') ? '#22c55e' : '#dc2626' }}>{msg}</div>
              )}
              {showSaveToLib && (
                <div style={{ marginTop:10, padding:10, background:'#eff6ff',
                  border:'1px solid #bfdbfe', borderRadius:6, fontSize:12 }}>
                  <div style={{ marginBottom:8, color:'#1e40af', fontWeight:600 }}>Save "{editing.name}" to private library?</div>
                  <div style={{ display:'flex', gap:8 }}>
                    <button onClick={saveToLibrary}
                      style={{ flex:1, padding:'6px', background:'#2563eb', color:'#fff',
                        border:'none', borderRadius:5, fontSize:12, cursor:'pointer',
                        fontFamily:'DM Sans, sans-serif' }}>Confirm</button>
                    <button onClick={() => setShowSaveToLib(false)}
                      style={{ padding:'6px 12px', background:'#fff', color:'#64748b',
                        border:'1px solid #e2e8f0', borderRadius:5, fontSize:12,
                        cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>Cancel</button>
                  </div>
                </div>
              )}

              {/* Preview */}
              {optionCount > 0 && (
                <div style={{ marginTop:16, padding:12, background:'#f8fafc',
                  border:'1px solid #e2e8f0', borderRadius:8 }}>
                  <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8',
                    textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:8 }}>Preview</div>
                  <div style={{ display:'flex', flexWrap:'wrap', gap:5 }}>
                    {textToOptions(editing.optionsText || '').slice(0, 20).map((opt, i) => (
                      <span key={i} style={{ padding:'3px 10px', background:'#fff',
                        border:'1px solid #e2e8f0', borderRadius:20, fontSize:12,
                        color:'#374151' }}>
                        {typeof opt === 'string' ? opt : <>{opt.label} <span style={{ color:'#94a3b8' }}>| {opt.value}</span></>}
                      </span>
                    ))}
                    {optionCount > 20 && (
                      <span style={{ fontSize:12, color:'#94a3b8', padding:'3px 6px' }}>
                        +{optionCount - 20} more
                      </span>
                    )}
                  </div>
                </div>
              )}
            </div>
          )}
        </div>

        {/* RIGHT — Library */}
        <div style={{ display:'table-cell', width:240, verticalAlign:'top',
          borderLeft:'1px solid #e2e8f0', background:'#fafafa' }}>
          <div style={{ padding:'10px 12px', borderBottom:'1px solid #e2e8f0',
            display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <span style={{ fontSize:11, fontWeight:700, color:'#64748b',
              textTransform:'uppercase', letterSpacing:'0.06em' }}>📚 Library</span>
            <KBIcon kbKey="valuelists" />
          </div>
          <div style={{ overflowY:'auto', maxHeight:700 }}>
            {libLoading ? (
              <div style={{ padding:20, textAlign:'center', color:'#94a3b8', fontSize:12 }}>Loading…</div>
            ) : (<>
              {/* Public */}
              <div style={{ padding:'8px 12px 4px', fontSize:10, fontWeight:700, color:'#94a3b8',
                textTransform:'uppercase', letterSpacing:'0.06em', borderBottom:'1px solid #f1f5f9' }}>
                🌐 Public ({library.public.length})
              </div>
              {library.public.length === 0
                ? <div style={{ padding:'10px 12px', fontSize:12, color:'#94a3b8' }}>No public lists yet</div>
                : library.public.map(item => (
                  <LibraryItem key={item.id} item={item} onAdd={addFromLibrary} />
                ))
              }
              {/* Private */}
              <div style={{ padding:'8px 12px 4px', marginTop:4, fontSize:10, fontWeight:700,
                color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em',
                borderBottom:'1px solid #f1f5f9', borderTop:'1px solid #e2e8f0' }}>
                🔒 Private ({library.private.length})
              </div>
              {library.private.length === 0
                ? <div style={{ padding:'10px 12px', fontSize:12, color:'#94a3b8', lineHeight:1.5 }}>
                    No private lists yet.<br/>
                    <span style={{ fontSize:11 }}>Save a list to reuse across apps.</span>
                  </div>
                : library.private.map(item => (
                  <LibraryItem key={item.id} item={item} onAdd={addFromLibrary} />
                ))
              }
            </>)}
          </div>
        </div>

      </div>
    {vlIconBrowser && (
      <IconBrowser
        onSelect={val => { setEditing(p => ({...p, icon: val})); setVlIconBrowser(false); }}
        onClose={() => setVlIconBrowser(false)}
      />
    )}
    </div>
  );
}

function LibraryItem({ item, onAdd }) {
  const [hover, setHover] = React.useState(false);
  return (
    <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ padding:'7px 12px', borderBottom:'1px solid #f8fafc',
        background: hover ? '#f1f5f9' : 'transparent',
        display:'flex', alignItems:'center', justifyContent:'space-between', gap:6 }}>
      <div style={{ minWidth:0 }}>
        <div style={{ fontSize:12, fontWeight:600, color:'#1e293b',
          overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{item.name}</div>
        <div style={{ fontSize:10, color:'#94a3b8' }}>{(item.options||[]).length} options</div>
      </div>
      <button onClick={() => onAdd(item)} title="Add to this app"
        style={{ flexShrink:0, padding:'3px 8px', background:'#0f1923', color:'#fff',
          border:'none', borderRadius:4, fontSize:11, cursor:'pointer',
          fontFamily:'DM Sans, sans-serif' }}>+ Add</button>
    </div>
  );
}

// ─────────────────────────────────────────────
// IconBrowser — slide-in sidebar from right
// ─────────────────────────────────────────────
function IconBrowser({ onSelect, onClose }) {
  const [search,    setSearch]    = React.useState('');
  const [icons,     setIcons]     = React.useState({});  // grouped data
  const [loading,   setLoading]   = React.useState(true);
  const [error,     setError]     = React.useState(null);
  const [collapsed, setCollapsed] = React.useState({});  // cat collapse state
  const [copied,    setCopied]    = React.useState(null); // recently copied value
  const [filter,    setFilter]    = React.useState('emoji'); // active type tab

  // Load icons on mount
  React.useEffect(() => {
    async function load() {
      setLoading(true); setError(null);
      try {
        const res  = await apiFetch(`${API_BASE}/v6/dev/icons?type=${filter}${search ? '&q='+encodeURIComponent(search) : ''}`);
        const data = await res.json();
        if (data.status !== 'ok') throw new Error(data.message);
        setIcons(data.grouped || {});
      } catch(e) {
        setError(e.message);
      }
      setLoading(false);
    }
    load();
  }, [filter, search]);

  function handleCopy(value) {
    navigator.clipboard?.writeText(value).catch(() => {});
    setCopied(value);
    setTimeout(() => setCopied(null), 1500);
  }

  function toggleCat(key) {
    setCollapsed(prev => ({ ...prev, [key]: !prev[key] }));
  }

  // Flatten all icons for search results display
  const allIcons = [];
  Object.values(icons).forEach(cats =>
    Object.values(cats).forEach(subs =>
      Object.values(subs).forEach(arr =>
        arr.forEach(ic => allIcons.push(ic))
      )
    )
  );

  const isSearching = search.trim().length > 0;

  return (
    <div style={{
      position:'fixed', top:0, right:0, bottom:0,
      width:320, zIndex:1000,
      background:'#fff',
      borderLeft:'1px solid #e2e8f0',
      boxShadow:'-4px 0 24px rgba(0,0,0,0.12)',
      display:'flex', flexDirection:'column',
      fontFamily:'DM Sans, sans-serif',
    }}>

      {/* Header */}
      <div style={{ padding:'14px 16px', borderBottom:'1px solid #e2e8f0', display:'flex', alignItems:'center', justifyContent:'space-between', background:'#0f1923', flexShrink:0 }}>
        <div style={{ color:'#fff', fontWeight:700, fontSize:14, letterSpacing:'-0.01em' }}>
          😀 Icon Browser
        </div>
        <button onClick={onClose}
          style={{ background:'rgba(255,255,255,0.1)', border:'none', color:'#fff', width:28, height:28, borderRadius:5, cursor:'pointer', fontSize:16, display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'inherit' }}>
          ✕
        </button>
      </div>

      {/* Type tabs */}
      <div style={{ display:'flex', borderBottom:'1px solid #e2e8f0', flexShrink:0 }}>
        {['emoji','lucide','custom'].map(t => (
          <button key={t} onClick={() => setFilter(t)}
            style={{ flex:1, padding:'9px 6px', border:'none', background: filter===t ? '#f8fafc' : '#fff',
              borderBottom: filter===t ? '2px solid #0f1923' : '2px solid transparent',
              fontSize:12, fontWeight: filter===t ? 700 : 400,
              color: filter===t ? '#0f1923' : '#94a3b8',
              cursor:'pointer', fontFamily:'DM Sans, sans-serif', textTransform:'capitalize' }}>
            {t}
          </button>
        ))}
      </div>

      {/* Search */}
      <div style={{ padding:'10px 12px', borderBottom:'1px solid #f1f5f9', flexShrink:0 }}>
        <div style={{ position:'relative' }}>
          <span style={{ position:'absolute', left:9, top:'50%', transform:'translateY(-50%)', fontSize:13, color:'#94a3b8', pointerEvents:'none' }}>🔍</span>
          <input value={search} onChange={e => setSearch(e.target.value)}
            placeholder="Search icons..."
            style={{ width:'100%', padding:'7px 10px 7px 30px', border:'1.5px solid #e2e8f0', borderRadius:7, fontSize:13, fontFamily:'DM Sans, sans-serif', outline:'none', boxSizing:'border-box' }} />
          {search && (
            <button onClick={() => setSearch('')}
              style={{ position:'absolute', right:8, top:'50%', transform:'translateY(-50%)', background:'none', border:'none', color:'#94a3b8', cursor:'pointer', fontSize:14, padding:0 }}>✕</button>
          )}
        </div>
      </div>

      {/* Instruction */}
      <div style={{ padding:'6px 12px', background:'#f8fafc', borderBottom:'1px solid #f1f5f9', fontSize:11, color:'#94a3b8', flexShrink:0 }}>
        Click icon to select · 📋 to copy to clipboard
      </div>

      {/* Icon list */}
      <div style={{ flex:1, overflowY:'auto', padding:'8px 0' }}>

        {loading && (
          <div style={{ padding:40, textAlign:'center', color:'#94a3b8', fontSize:13 }}>Loading icons…</div>
        )}

        {error && (
          <div style={{ padding:20, color:'#dc2626', fontSize:12, textAlign:'center' }}>
            ⚠ {error}<br/>
            <span style={{ color:'#94a3b8', fontSize:11 }}>Add icons to DO.icons table to get started</span>
          </div>
        )}

        {!loading && !error && allIcons.length === 0 && (
          <div style={{ padding:32, textAlign:'center', color:'#94a3b8', fontSize:13 }}>
            {isSearching ? `No icons matching "${search}"` : 'No icons found in DO.icons'}
          </div>
        )}

        {/* Search results — flat grid */}
        {!loading && isSearching && allIcons.length > 0 && (
          <div style={{ padding:'8px 12px' }}>
            <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:8 }}>
              {allIcons.length} result{allIcons.length !== 1 ? 's' : ''}
            </div>
            <div style={{ display:'grid', gridTemplateColumns:'repeat(6, 1fr)', gap:4 }}>
              {allIcons.map(ic => (
                <IconCell key={ic.id} ic={ic} onSelect={onSelect} onCopy={handleCopy} copied={copied} />
              ))}
            </div>
          </div>
        )}

        {/* Grouped — collapsible by cat */}
        {!loading && !isSearching && !error && Object.entries(icons).map(([type, cats]) => (
          Object.entries(cats).map(([cat, subs]) => {
            const catKey   = `${type}_${cat}`;
            const isOpen   = collapsed[catKey] !== true; // default open
            const catIcons = Object.values(subs).flat();
            return (
              <div key={catKey}>
                {/* Category header */}
                <button onClick={() => toggleCat(catKey)}
                  style={{ width:'100%', padding:'8px 14px', background:'#f8fafc', border:'none', borderBottom:'1px solid #f1f5f9', borderTop:'1px solid #f1f5f9', display:'flex', alignItems:'center', justifyContent:'space-between', cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                  <span style={{ fontSize:12, fontWeight:700, color:'#374151', textTransform:'capitalize' }}>
                    {cat} <span style={{ color:'#94a3b8', fontWeight:400 }}>({catIcons.length})</span>
                  </span>
                  <span style={{ fontSize:11, color:'#94a3b8', transform: isOpen ? 'rotate(90deg)' : 'none', transition:'transform 0.15s', display:'inline-block' }}>›</span>
                </button>

                {isOpen && (
                  <div style={{ padding:'8px 12px' }}>
                    {/* Sub-categories */}
                    {Object.entries(subs).map(([sub, subIcons]) => (
                      <div key={sub}>
                        {sub && (
                          <div style={{ fontSize:10, fontWeight:600, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:5, marginTop:6 }}>{sub}</div>
                        )}
                        <div style={{ display:'grid', gridTemplateColumns:'repeat(6, 1fr)', gap:4, marginBottom:6 }}>
                          {subIcons.map(ic => (
                            <IconCell key={ic.id} ic={ic} onSelect={onSelect} onCopy={handleCopy} copied={copied} />
                          ))}
                        </div>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            );
          })
        ))}
      </div>
    </div>
  );
}

function IconCell({ ic, onSelect, onCopy, copied }) {
  const [hover, setHover] = React.useState(false);
  const isCopied = copied === ic.value;

  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      title={ic.name + (ic.desc ? ' — ' + ic.desc : '')}
      style={{ position:'relative' }}>
      {/* Main icon button — click to select */}
      <button
        onClick={() => onSelect && onSelect(ic.value)}
        style={{
          width:'100%', aspectRatio:'1', border: hover ? '1.5px solid #2563eb' : '1px solid #e2e8f0',
          borderRadius:7, background: hover ? '#eff6ff' : '#fff',
          fontSize:20, cursor:'pointer', display:'flex', alignItems:'center',
          justifyContent:'center', transition:'all 0.12s', padding:0,
        }}>
        {ic.value}
      </button>
      {/* Copy button — appears on hover */}
      {hover && (
        <button
          onClick={e => { e.stopPropagation(); onCopy(ic.value); }}
          title="Copy to clipboard"
          style={{
            position:'absolute', top:-4, right:-4, width:16, height:16,
            background: isCopied ? '#22c55e' : '#0f1923',
            border:'none', borderRadius:3, cursor:'pointer',
            color:'#fff', fontSize:8, display:'flex', alignItems:'center', justifyContent:'center',
            fontFamily:'DM Sans, sans-serif', zIndex:1,
          }}>
          {isCopied ? '✓' : '📋'}
        </button>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// ColorPicker — jscolor + hex input
// ─────────────────────────────────────────────
function ColorPicker({ value, onChange, label }) {
  const inputRef = React.useRef(null);
  const [hex, setHex] = React.useState(value || '');

  // Sync when value changes externally
  React.useEffect(() => {
    setHex(value || '');
    if (inputRef.current?.jscolor) {
      try { inputRef.current.jscolor.fromString(value || 'ffffffff'); } catch(e) {}
    }
  }, [value]);

  // Init jscolor after mount
  React.useEffect(() => {
    if (!inputRef.current || !window.jscolor) return;
    const jsc = new window.jscolor(inputRef.current, {
      position:      'left',
      format:        'hex',
      alphaChannel:  false,
      previewSize:   0,
      padding:       4,
      borderRadius:  6,
      shadow:        true,
      zIndex:        9999,
      onChange:      function() {
        const h = '#' + this.toHEXString().replace('#','');
        setHex(h);
        onChange(h);
      },
    });
    if (value) { try { jsc.fromString(value); } catch(e) {} }
    return () => { try { jsc.hide(); } catch(e) {} };
  }, []);

  function handleInput(e) {
    const v = e.target.value;
    setHex(v);
    if (/^#[0-9A-Fa-f]{6}$/.test(v)) {
      onChange(v);
      if (inputRef.current?.jscolor) {
        try { inputRef.current.jscolor.fromString(v); } catch(e) {}
      }
    }
  }

  function handleBlur() {
    let v = hex.trim();
    if (!v) { onChange(''); return; }
    if (!v.startsWith('#')) v = '#' + v;
    if (/^#[0-9A-Fa-f]{3}$/.test(v))
      v = '#' + v[1]+v[1]+v[2]+v[2]+v[3]+v[3];
    setHex(v);
    if (/^#[0-9A-Fa-f]{6}$/.test(v)) {
      onChange(v);
      if (inputRef.current?.jscolor) {
        try { inputRef.current.jscolor.fromString(v); } catch(e) {}
      }
    }
  }

  const isValid = /^#[0-9A-Fa-f]{6}$/.test(hex);

  return (
    <div style={{ marginBottom:12, width:'100%', boxSizing:'border-box' }}>
      {label && <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:5, marginTop:10 }}>{label}</div>}
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        {/* Color swatch — jscolor attaches here */}
        <div style={{ width:32, height:32, borderRadius:6, flexShrink:0, overflow:'hidden',
          background: isValid ? hex : '#e2e8f0', border:'1.5px solid #e2e8f0', cursor:'pointer',
          position:'relative' }}>
          <input ref={inputRef}
            readOnly
            style={{ opacity:0, position:'absolute', inset:0, width:'100%', height:'100%',
              cursor:'pointer', border:'none', padding:0, background:'transparent' }} />
        </div>
        {/* Hex input */}
        <input value={hex} onChange={handleInput} onBlur={handleBlur}
          placeholder="#000000"
          style={{ flex:1, minWidth:0, padding:'6px 8px',
            border: isValid || !hex ? '1.5px solid #e2e8f0' : '1.5px solid #fca5a5',
            borderRadius:6, fontSize:12, fontFamily:'DM Mono, monospace',
            color:'#0f1923', outline:'none', boxSizing:'border-box' }} />
        {hex && (
          <button onClick={() => { setHex(''); onChange(''); }}
            style={{ background:'none', border:'none', color:'#94a3b8',
              cursor:'pointer', fontSize:14, padding:'0 2px', fontFamily:'inherit' }}>✕</button>
        )}
      </div>
    </div>
  );
}

function Inspector({ selectedObj, fields, tables, app, onBrowseIcon, onUpdateTable, onUpdate, onDelete }) {
  const [local, setLocal] = React.useState(null);

  React.useEffect(() => {
    setLocal(selectedObj ? JSON.parse(JSON.stringify(selectedObj)) : null);
  }, [selectedObj?._oid, selectedObj?.tNum, selectedObj?._isTable]);

  if (!local) return (
    <div style={{ padding:14 }}>
      <div style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:12, paddingBottom:8, borderBottom:'1px solid #f1f5f9' }}>Inspector</div>
      <div style={{ fontSize:12, color:'#94a3b8', textAlign:'center', marginTop:40 }}>Click an object<br/>to edit</div>
    </div>
  );

  function set(key, val) { setLocal(prev => ({ ...prev, [key]: val })); }

  const isDataField = ['input','number','date','time','select','checkbox','file'].includes(local.type);
  const isDisplayObj = ['tabs','divider','spacer','text','rating'].includes(local.type);

  // Table object (from home page)
  if (local._isTable) {
    return (
      <div style={{ padding:14, overflowY:'auto', maxHeight:700 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:12, paddingBottom:8, borderBottom:'1px solid #f1f5f9' }}>
          <span style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.07em' }}>Inspector</span>
          <span style={{ fontSize:10, background:'#f0fdf4', color:'#059669', padding:'2px 7px', borderRadius:4, fontWeight:600, textTransform:'uppercase' }}>Table</span>
        </div>

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Table Name</div>
        <input value={local.name||''} onChange={e => set('name', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', outline:'none', marginBottom:10, boxSizing:'border-box' }} />

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Description</div>
        <textarea value={local.desc||''} onChange={e => set('desc', e.target.value)}
          style={{ width:'100%', height:60, padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', outline:'none', resize:'none', marginBottom:10, boxSizing:'border-box' }}
          maxLength={120} placeholder="Max 120 chars" />
        <div style={{ fontSize:10, color:'#94a3b8', marginBottom:10, textAlign:'right' }}>{(local.desc||'').length}/120</div>

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          Icon (Emoji)
          <button type="button"
            onClick={() => onBrowseIcon && onBrowseIcon((val) => { set('icon', val); })}
            style={{ background:'none', border:'1px solid #e2e8f0', borderRadius:5, padding:'2px 7px', fontSize:11, color:'#2563eb', cursor:'pointer', fontFamily:'DM Sans, sans-serif', fontWeight:600 }}>
            🔍 Browse
          </button>
        </div>
        <div style={{ marginBottom:10 }}>
          <div style={{ width:44, height:44, border:'1.5px solid #e2e8f0', borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', fontSize:24, background:'#f8fafc' }}>
            {local.icon || '?'}
          </div>
        </div>

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Show on Home</div>
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:true,l:'Visible'},{v:false,l:'Hidden'}].map(o => (
            <button key={String(o.v)} type="button" onClick={() => set('show', o.v)}
              style={{ flex:1, padding:'6px', border:'none', fontSize:12, fontWeight:500, cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                background: local.show===o.v || (o.v===true && local.show===undefined) ? '#0f1923' : '#fff',
                color: local.show===o.v || (o.v===true && local.show===undefined) ? '#fff' : '#64748b' }}>
              {o.l}
            </button>
          ))}
        </div>

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Sort Order</div>
        <input type="number" value={local.sort||''} onChange={e => set('sort', Number(e.target.value))}
          style={{ width:70, padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, outline:'none', marginBottom:10 }}
          placeholder="1" min="1" />

        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4 }}>Opens To</div>
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:16 }}>
          {[{v:'list',l:'List View'},{v:'detail',l:'Detail View'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('link', o.v)}
              style={{ flex:1, padding:'6px', border:'none', fontSize:12, fontWeight:500, cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                background: (local.link||'list')===o.v ? '#0f1923' : '#fff',
                color: (local.link||'list')===o.v ? '#fff' : '#64748b' }}>
              {o.l}
            </button>
          ))}
        </div>

        <button onClick={() => onUpdateTable && onUpdateTable(local)}
          style={{ width:'100%', padding:'8px', background:'#0f1923', color:'#fff', border:'none', borderRadius:6, fontSize:12, fontWeight:600, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
          Apply
        </button>
      </div>
    );
  }

  // Col object — show width editor
  if (local._isCol) {
    // Option A: calculate max allowed width for this col
    // We need sibling col widths — passed via _siblingTotal
    const siblingTotal = Number(local._siblingTotal || 0);
    const maxAllowed   = 12 - siblingTotal;

    return (
      <div style={{ padding:14 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4, paddingBottom:8, borderBottom:'1px solid #f1f5f9' }}>
          <span style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.07em' }}>Inspector</span>
          <span style={{ fontSize:10, background:'#f1f5f9', color:'#64748b', padding:'2px 7px', borderRadius:4, fontWeight:600, textTransform:'uppercase' }}>Column</span>
        </div>
        <div style={{ fontSize:12, color:'#374151', fontWeight:600, marginBottom:12 }}>
          Row {local.row} · Col {local.col}
        </div>
        <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:6 }}>Width (1–12)</div>
        <div style={{ display:'flex', gap:4, flexWrap:'wrap', marginBottom:4 }}>
          {[1,2,3,4,5,6].map(n => (
            <button key={n} onClick={() => n <= maxAllowed && set('width', String(n))}
              style={{ width:26, height:26, border: local.width===String(n) ? '2px solid #2563eb' : '1.5px solid #e2e8f0', borderRadius:5, background: local.width===String(n) ? '#eff6ff' : n > maxAllowed ? '#f8fafc' : '#fff', fontSize:12, fontWeight:600, color: local.width===String(n) ? '#2563eb' : n > maxAllowed ? '#cbd5e1' : '#374151', cursor: n > maxAllowed ? 'not-allowed' : 'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {n}
            </button>
          ))}
        </div>
        <div style={{ display:'flex', gap:4, flexWrap:'wrap', marginBottom:8 }}>
          {[7,8,9,10,11,12].map(n => (
            <button key={n} onClick={() => n <= maxAllowed && set('width', String(n))}
              style={{ width:26, height:26, border: local.width===String(n) ? '2px solid #2563eb' : '1.5px solid #e2e8f0', borderRadius:5, background: local.width===String(n) ? '#eff6ff' : n > maxAllowed ? '#f8fafc' : '#fff', fontSize:12, fontWeight:600, color: local.width===String(n) ? '#2563eb' : n > maxAllowed ? '#cbd5e1' : '#374151', cursor: n > maxAllowed ? 'not-allowed' : 'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {n}
            </button>
          ))}
        </div>
        <div style={{ fontSize:11, color:'#94a3b8', marginBottom:4 }}>
          {local.width}/12 = {Math.round((Number(local.width)/12)*100)}% width
        </div>
        <div style={{ fontSize:11, color: maxAllowed < 12 ? '#f59e0b' : '#94a3b8', marginBottom:14 }}>
          Max available: {maxAllowed}/12
        </div>
        <div style={{ height:1, background:'#f1f5f9', margin:'4px 0 12px' }} />
        <label style={{ display:'flex', alignItems:'center', gap:8, fontSize:12, color:'#374151', cursor:'pointer', marginBottom:14 }}>
          <input type="checkbox"
            checked={local.border !== false}
            onChange={e => set('border', e.target.checked)}
            style={{ width:14, height:14, cursor:'pointer' }} />
          Show divider between columns
        </label>
        <button onClick={() => onUpdate(local)}
          style={{ width:'100%', padding:'8px', background:'#0f1923', color:'#fff', border:'none', borderRadius:6, fontSize:12, fontWeight:600, cursor:'pointer', fontFamily:'DM Sans, sans-serif', marginBottom:8 }}>
          Apply
        </button>
        <button onClick={() => {
            if (!confirm('Delete this column and all its objects?')) return;
            onDelete && onDelete(local);
          }}
          style={{ width:'100%', padding:'8px', background:'#fff', color:'#dc2626', border:'1px solid #fecaca', borderRadius:6, fontSize:12, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
          Delete Column
        </button>
      </div>
    );
  }
  const lbl = (txt) => <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:4, marginTop:10 }}>{txt}</div>;
  const inp = (key, props={}) => (
    <input {...props} value={local[key] || ''} onChange={e => set(key, e.target.value)}
      style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', outline:'none', boxSizing:'border-box' }} />
  );
  const sel = (key, options) => (
    <select value={local[key] || ''} onChange={e => set(key, e.target.value)}
      style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none' }}>
      {options.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}
    </select>
  );

  return (
    <div style={{ padding:14, overflowY:'auto', maxHeight:700 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:12, paddingBottom:8, borderBottom:'1px solid #f1f5f9' }}>
        <span style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.07em' }}>Inspector</span>
        <span style={{ fontSize:10, background:'#eff6ff', color:'#2563eb', padding:'2px 7px', borderRadius:4, fontWeight:600, textTransform:'uppercase' }}>{local.type}</span>
      </div>

      {/* ── FIELD ASSIGNMENT (non-input data types) ── */}
      {isDataField && !['input','number','file','date','time','select','checkbox'].includes(local.type) && <>
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { set('label', local.label || f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>
        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}

      {local.type === 'input' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'e.g. first_name' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:6 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>
        {lbl('Height (rows)')}
        {inp('height', { type:'number', placeholder:'1', min:'1', max:'20' })}
        <div style={{ height:1, background:'#e2e8f0', margin:'14px 0 12px' }} />
        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}
      {local.type === 'number' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:6 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>

        {lbl('Format')}
        {sel('format', [{ v:'', l:'Plain' }, { v:'currency', l:'Currency' }, { v:'percent', l:'Percent (%)' }, { v:'decimal', l:'Decimal' }])}

        {local.format === 'currency' && <>
          {lbl('Currency Symbol')}
          {sel('currencySymbol', [{ v:'$', l:'$ Dollar' }, { v:'€', l:'€ Euro' }, { v:'£', l:'£ Pound' }, { v:'¥', l:'¥ Yen' }, { v:'₿', l:'₿ Bitcoin' }])}
        </>}

        {(local.format === 'decimal' || local.format === 'currency') && <>
          {lbl('Decimal Places')}
          {sel('decimals', [
            { v:'0', l:'0 — Integer' },
            { v:'1', l:'1 — .0' },
            { v:'2', l:'2 — .00' },
            { v:'3', l:'3 — .000' },
            { v:'4', l:'4 — .0000' },
            { v:'5', l:'5 — .00000' },
            { v:'6', l:'6 — .000000' },
          ])}
        </>}

        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8, marginBottom:6 }}>
          <div>
            {lbl('Min')}
            {inp('min', { type:'number', placeholder:'e.g. 0' })}
          </div>
          <div>
            {lbl('Max')}
            {inp('max', { type:'number', placeholder:'e.g. 150' })}
          </div>
        </div>

        {lbl('Align')}
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:'left',l:'← Left'},{v:'right',l:'→ Right'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('align', o.v)}
              style={{ flex:1, padding:'5px', border:'none', fontSize:11, fontWeight:500,
                background: (local.align||'right')===o.v ? '#0f1923' : '#fff',
                color: (local.align||'right')===o.v ? '#fff' : '#374151',
                cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {o.l}
            </button>
          ))}
        </div>

        <div style={{ height:1, background:'#e2e8f0', margin:'14px 0 12px' }} />

        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}
      {local.type === 'date' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:6 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>
        {lbl('Display Format')}
        {sel('dateformat', [
          { v:'MM/DD/YYYY', l:'MM/DD/YYYY — 01/15/2025' },
          { v:'DD/MM/YYYY', l:'DD/MM/YYYY — 15/01/2025' },
          { v:'YYYY-MM-DD', l:'YYYY-MM-DD — 2025-01-15' },
          { v:'MMM D YYYY', l:'MMM D YYYY — Jan 15 2025' },
          { v:'MMMM D YYYY', l:'MMMM D YYYY — January 15 2025' },
          { v:'D MMM YYYY', l:'D MMM YYYY — 15 Jan 2025' },
        ])}
        <div style={{ fontSize:10, color:'#94a3b8', marginTop:-6, marginBottom:10, lineHeight:1.4 }}>
          Format applies to display. Date picker stores YYYY-MM-DD.
        </div>
        {lbl('Align')}
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:'left',l:'← Left'},{v:'right',l:'→ Right'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('align', o.v)}
              style={{ flex:1, padding:'5px', border:'none', fontSize:11, fontWeight:500,
                background: (local.align||'left')===o.v ? '#0f1923' : '#fff',
                color: (local.align||'left')===o.v ? '#fff' : '#374151',
                cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {o.l}
            </button>
          ))}
        </div>
        <div style={{ height:1, background:'#e2e8f0', margin:'6px 0 12px' }} />
        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}
      {local.type === 'time' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:6 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>
        {lbl('Display Format')}
        {sel('timeformat', [
          { v:'12', l:'12 hour — 2:30 PM' },
          { v:'24', l:'24 hour — 14:30' },
        ])}
        <div style={{ fontSize:10, color:'#94a3b8', marginTop:-6, marginBottom:10, lineHeight:1.4 }}>
          Always stored as 24hr (14:30). Format controls display only.
        </div>
        {lbl('Align')}
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:'left',l:'← Left'},{v:'right',l:'→ Right'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('align', o.v)}
              style={{ flex:1, padding:'5px', border:'none', fontSize:11, fontWeight:500,
                background: (local.align||'left')===o.v ? '#0f1923' : '#fff',
                color: (local.align||'left')===o.v ? '#fff' : '#374151',
                cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {o.l}
            </button>
          ))}
        </div>
        <div style={{ height:1, background:'#e2e8f0', margin:'6px 0 12px' }} />
        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}
      {(local.type === 'select' || local.type === 'checkbox') && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>

        {lbl('Options Source')}
        {sel('source', [
          { v:'inline',    l:'Inline (type options below)' },
          { v:'valuelist', l:'Value List' },
          { v:'table',     l:'Table — Coming Soon' },
          { v:'query',     l:'Query — Coming Soon' },
        ])}

        {/* Inline — textarea */}
        {(local.source === 'inline' || !local.source) && <>
          {lbl('Options — one per line')}
          <textarea
            value={(local.options||[]).join('\n')}
            onChange={e => set('options', e.target.value.split('\n'))}
            style={{ width:'100%', height:160, padding:'6px 8px',
              border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12,
              fontFamily:'DM Mono, monospace', resize:'vertical', outline:'none',
              marginBottom:4, boxSizing:'border-box', lineHeight:1.6 }}
            placeholder={'Option A\nOption B\nOption C'} />
          <div style={{ fontSize:10, color:'#94a3b8', marginBottom:10 }}>
            {(local.options||[]).filter(s => s.trim()).length} options
          </div>
        </>}

        {/* Value List — dropdown of existing lists */}
        {local.source === 'valuelist' && <>
          {lbl('Value List')}
          <ValueListPicker appId={app?.app_id} value={local.valuelist} onChange={v => set('valuelist', v)} />
        </>}

        {/* Table — coming soon */}
        {local.source === 'table' && (
          <div style={{ padding:'10px 12px', background:'#f8fafc', border:'1px solid #e2e8f0',
            borderRadius:6, fontSize:12, color:'#94a3b8', marginBottom:10 }}>
            Table source coming soon
          </div>
        )}
        {local.source === 'query' && (
          <div style={{ padding:'10px 12px', background:'#f8fafc', border:'1px solid #e2e8f0',
            borderRadius:6, fontSize:12, color:'#94a3b8', marginBottom:10 }}>
            Query source coming soon
          </div>
        )}

        {lbl('Allow Multiple')}
        {sel('multi', [{ v:'no', l:'Single select' }, { v:'yes', l:'Multi select' }])}
        {lbl('Display')}
        {sel('display', [{ v:'vertical', l:'Vertical' }, { v:'horizontal', l:'Horizontal' }])}

        <div style={{ height:1, background:'#e2e8f0', margin:'10px 0 12px' }} />
        {lbl('Label')}
        {inp('label', { placeholder:'Display label' })}
        {lbl('Label Position')}
        {sel('labelpos', [{ v:'top', l:'Top' }, { v:'left', l:'Left (inline)' }, { v:'none', l:'Hidden' }])}
        {(local.labelpos !== 'none') && <>
          {lbl('Label Align')}
          {sel('labelalign', [{ v:'left', l:'Left' }, { v:'center', l:'Center' }, { v:'right', l:'Right' }])}
        </>}
      </>}

      {local.type === 'file' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'' })}
        {lbl('Field')}
        <select value={local.field || ''} onChange={e => {
            const f = fields.find(f => f.field === e.target.value);
            set('field', e.target.value);
            if (f) { if(!local.label) set('label', f.name); if(!local.name) set('name', f.name); }
          }}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="">— unassigned —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>

        {lbl('Display As')}
        {sel('display', [{ v:'image', l:'Single Image' }, { v:'gallery', l:'Gallery' }, { v:'file', l:'File List' }])}

        {local.display === 'image' && <>
          {lbl('Width')}
          {sel('width', [{ v:'25%', l:'25% of column' }, { v:'50%', l:'50% of column' }, { v:'75%', l:'75% of column' }, { v:'100%', l:'100% of column (default)' }])}
          {lbl('Max Height (px)')}
          {sel('maxHeight', [{ v:'100', l:'100px' }, { v:'200', l:'200px (default)' }, { v:'300', l:'300px' }, { v:'400', l:'400px' }, { v:'', l:'No limit' }])}
          {lbl('Align')}
          <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
            {[{v:'left',l:'←'},{v:'center',l:'↔'},{v:'right',l:'→'}].map(o => (
              <button key={o.v} type="button" onClick={() => set('align', o.v)}
                style={{ flex:1, padding:'5px', border:'none', fontSize:14,
                  background: (local.align||'left')===o.v ? '#0f1923' : '#fff',
                  color: (local.align||'left')===o.v ? '#fff' : '#374151',
                  cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                {o.l}
              </button>
            ))}
          </div>
        </>}

        {local.display === 'gallery' && <>
          {lbl('Thumb Size (px)')}
          <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:10, width:'100%', boxSizing:'border-box' }}>
            <input type="range" min="50" max="200" step="1"
              value={local.thumbSize || 100}
              onChange={e => set('thumbSize', Number(e.target.value))}
              style={{ flex:1, minWidth:0, cursor:'pointer' }} />
            <span style={{ fontSize:11, fontFamily:'DM Mono, monospace', color:'#374151', width:34, textAlign:'right', flexShrink:0 }}>{local.thumbSize || 100}px</span>
          </div>
          {lbl('Display Rows')}
          <select value={local.displayRows || ''} onChange={e => set('displayRows', e.target.value ? Number(e.target.value) : '')}
            style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:10 }}>
            <option value="1">1 row</option><option value="2">2 rows</option>
            <option value="3">3 rows (default)</option><option value="4">4 rows</option><option value="5">5 rows</option>
          </select>
        </>}

        {(local.display === 'file' || !local.display) && <>
          {lbl('Display Rows')}
          <select value={local.displayRows || ''} onChange={e => set('displayRows', e.target.value ? Number(e.target.value) : '')}
            style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', outline:'none', marginBottom:10 }}>
            <option value="3">3 rows (default)</option><option value="5">5 rows</option>
            <option value="8">8 rows</option><option value="10">10 rows</option><option value="">Show all</option>
          </select>
          {lbl('Show Columns')}
          <div style={{ display:'flex', flexDirection:'column', gap:6, marginBottom:10 }}>
            {[{ k:'showSize', l:'File Size' }, { k:'showDate', l:'Date Uploaded' }, { k:'showUser', l:'Uploaded By' }].map(col => (
              <label key={col.k} style={{ display:'flex', alignItems:'center', gap:8, fontSize:12, color:'#374151', cursor:'pointer' }}>
                <input type="checkbox" checked={local[col.k] !== false}
                  onChange={e => set(col.k, e.target.checked)}
                  style={{ width:14, height:14, cursor:'pointer' }} />
                {col.l}
              </label>
            ))}
          </div>
        </>}

        <div style={{ height:1, background:'#e2e8f0', margin:'14px 0 12px' }} />
        {lbl('Permissions')}
        <div style={{ display:'flex', flexDirection:'column', gap:6, marginBottom:10 }}>
          {[
            { k:'allowCamera',   l:'Allow Camera',   def:false },
            { k:'allowUpload',   l:'Allow Upload',   def:true  },
            { k:'allowDownload', l:'Allow Download', def:true  },
            { k:'allowDelete',   l:'Allow Delete',   def:true  },
          ].map(perm => {
            // Use explicit stored value, fall back to default
            const isChecked = local[perm.k] !== undefined ? Boolean(local[perm.k]) : perm.def;
            return (
              <label key={perm.k} style={{ display:'flex', alignItems:'center', gap:8, fontSize:12, color:'#374151', cursor:'pointer' }}>
                <input type="checkbox" checked={isChecked}
                  onChange={e => set(perm.k, e.target.checked)}
                  style={{ width:14, height:14, cursor:'pointer' }} />
                {perm.l}
              </label>
            );
          })}
        </div>
      </>}
      {local.type === 'text' && <>
        {lbl('Identifier (internal)')}
        {inp('name', { placeholder:'e.g. hotel_heading' })}

        {lbl('Content Type')}
        {sel('contentType', [{ v:'static', l:'Static Text' }, { v:'formula', l:'Formula' }])}

        {(!local.contentType || local.contentType === 'static') && <>
          {lbl('Content')}
          <textarea value={local.content||''} onChange={e => set('content', e.target.value)}
            style={{ width:'100%', height:70, padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', resize:'vertical', outline:'none', marginBottom:10 }}
            placeholder="Text to display..." />
        </>}

        {local.contentType === 'formula' && <>
          {lbl('Formula')}
          <div style={{ fontSize:10, color:'#94a3b8', marginBottom:4 }}>Use {'{f1}'} for field values</div>
          <textarea value={local.formula||''} onChange={e => set('formula', e.target.value)}
            style={{ width:'100%', height:55, padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Mono, monospace', resize:'vertical', outline:'none', marginBottom:4 }}
            placeholder="{f1} {f2}" />
          <div style={{ display:'flex', flexWrap:'wrap', gap:3, marginBottom:10 }}>
            {fields.map(f => (
              <button key={f.o_id} type="button"
                onClick={() => set('formula', (local.formula||'') + '{' + f.field + '}')}
                style={{ fontSize:10, padding:'2px 6px', background:'#f1f5f9', border:'1px solid #e2e8f0', borderRadius:4, cursor:'pointer', fontFamily:'DM Mono, monospace', color:'#374151' }}>
                {f.field}
              </button>
            ))}
          </div>
        </>}

        {lbl('Text Align')}
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:'left',l:'← Left'},{v:'center',l:'↔ Center'},{v:'right',l:'→ Right'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('textalign', o.v)}
              style={{ flex:1, padding:'5px 2px', border:'none', borderRight:'1px solid #e2e8f0', fontSize:11, fontWeight:500,
                background: (local.textalign||'left')===o.v ? '#0f1923' : '#fff',
                color: (local.textalign||'left')===o.v ? '#fff' : '#374151', cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {o.l}
            </button>
          ))}
        </div>

        {lbl('Font Size')}
        {sel('fontSize', [{ v:'10', l:'10 Small' }, { v:'12', l:'12 Normal' }, { v:'14', l:'14 Medium' }, { v:'18', l:'18 Large' }, { v:'24', l:'24 XL' }, { v:'32', l:'32 XXL' }])}
        {lbl('Font Weight')}
        {sel('fontWeight', [{ v:'300', l:'Light' }, { v:'400', l:'Normal' }, { v:'600', l:'Semi-bold' }, { v:'700', l:'Bold' }])}

        <ColorPicker label="Font Color" value={local.color||''} onChange={v => set('color', v)} />
        <ColorPicker label="Background" value={local.bgcolor||''} onChange={v => set('bgcolor', v)} />
      </>}
      {local.type === 'tabs' && <>
        {lbl('Tab Style')}
        <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6, overflow:'hidden', marginBottom:10 }}>
          {[{v:'underline',l:'Underline'},{v:'tabs',l:'Tabs'},{v:'pills',l:'Pills'}].map(o => (
            <button key={o.v} type="button" onClick={() => set('style', o.v)}
              style={{ flex:1, padding:'6px 4px', border:'none', fontSize:11, fontWeight:500,
                background:(local.style||'underline')===o.v ? '#7c3aed' : '#fff',
                color:(local.style||'underline')===o.v ? '#fff' : '#64748b',
                cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
              {o.l}
            </button>
          ))}
        </div>

        {lbl('Tabs')}
        <div style={{ display:'flex', flexDirection:'column', gap:4, marginBottom:8 }}>
          {(local.tabDefs||[{id:'1',label:'Tab 1'}]).map((t, i) => (
            <div key={t.id} style={{ display:'flex', alignItems:'center', gap:6 }}>
              <input
                value={t.label}
                maxLength={20}
                onChange={e => {
                  const defs = [...(local.tabDefs||[])];
                  defs[i] = { ...defs[i], label: e.target.value };
                  set('tabDefs', defs);
                }}
                style={{ width:0, flex:1, minWidth:0, padding:'5px 6px', border:'1.5px solid #e2e8f0',
                  borderRadius:5, fontSize:12, fontFamily:'DM Sans, sans-serif', outline:'none',
                  boxSizing:'border-box' }}
                placeholder={`Tab ${i+1}`}
              />
              {(local.tabDefs||[]).length > 1 && (
                <button type="button"
                  onClick={() => {
                    const label = t.label || `Tab ${i+1}`;
                    setConfirm({
                      msg: `Remove "${label}"?`,
                      detail: "Objects on this tab will remain but won't be visible until moved to another tab.",
                      okLabel: 'Remove', okDanger: true,
                      onOk: () => {
                        const defs = (local.tabDefs||[]).filter((_,idx) => idx !== i);
                        set('tabDefs', defs);
                      }
                    });
                  }}
                  style={{ background:'none', border:'none', color:'#94a3b8', cursor:'pointer',
                    fontSize:14, padding:'0 2px', fontFamily:'inherit', flexShrink:0 }}>✕</button>
              )}
            </div>
          ))}
        </div>
        <button type="button"
          onClick={() => {
            const defs = local.tabDefs || [{id:'1', label:'Tab 1'}];
            const maxId = Math.max(...defs.map(t => parseInt(t.id)||0));
            set('tabDefs', [...defs, { id: String(maxId+1), label: `Tab ${defs.length+1}` }]);
          }}
          style={{ width:'100%', padding:'6px', background:'#f8fafc', border:'1.5px dashed #e2e8f0',
            borderRadius:6, fontSize:12, color:'#64748b', cursor:'pointer',
            fontFamily:'DM Sans, sans-serif', marginBottom:10 }}>
          + Add Tab
        </button>

        <label style={{ display:'flex', alignItems:'center', gap:8, fontSize:12,
          color:'#374151', cursor:'pointer', marginBottom:14 }}>
          <input type="checkbox"
            checked={local.sticky === true}
            onChange={e => set('sticky', e.target.checked)}
            style={{ width:14, height:14, cursor:'pointer' }} />
          Sticky (stays visible while scrolling)
        </label>
      </>}
      {local.type === 'divider' && <>
        {lbl('Height (px)')}
        {inp('height', { type:'number', placeholder:'1' })}
        <ColorPicker label="Color" value={local.color||''} onChange={v => set('color', v)} />
      </>}
      {local.type === 'spacer' && <>
        {lbl('Height (px)')}
        {inp('height', { type:'number', placeholder:'20' })}
      </>}
      {local.type === 'map' && <>
        {lbl('Name (internal)')}{inp('name', { placeholder:'e.g. location_map' })}
        {lbl('Address Mode')}
        <div style={{ display:'flex', gap:0, marginBottom:10, borderRadius:6, overflow:'hidden', border:'1.5px solid #e2e8f0' }}>
          {['full','partial'].map(mode => (
            <button key={mode}
              onClick={() => set('addressMode', mode)}
              style={{ flex:1, padding:'6px 0', fontSize:12, fontFamily:'DM Sans, sans-serif',
                border:'none', cursor:'pointer', fontWeight:500,
                background: (local.addressMode||'full') === mode ? '#0f1923' : '#fff',
                color:      (local.addressMode||'full') === mode ? '#fff'    : '#64748b',
                transition:'all 0.15s' }}>
              {mode === 'full' ? '📍 Full' : '✂️ Partial'}
            </button>
          ))}
        </div>
        {(local.addressMode||'full') === 'full' && <>
          {lbl('Full Address Field')}
          <select value={local.linkedField||''} onChange={e => set('linkedField', e.target.value)}
            style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
            <option value="">— select field —</option>
            {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
          </select>
        </>}
        {(local.addressMode||'full') === 'partial' && <>
          {['Address','City','State','Zip','Country'].map(part => {
            const key = 'field' + part;
            return (
              <React.Fragment key={key}>
                {lbl(part + ' Field')}
                <select value={local[key]||''} onChange={e => set(key, e.target.value)}
                  style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
                  <option value="">— none —</option>
                  {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
                </select>
              </React.Fragment>
            );
          })}
        </>}
        {lbl('Map Type')}
        <select value={local.mapType||'roadmap'} onChange={e => set('mapType', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="roadmap">Roadmap</option>
          <option value="satellite">Satellite</option>
        </select>
        {lbl('Zoom Level')}
        <select value={local.zoom||'12'} onChange={e => set('zoom', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="0">0 — Entire World</option>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
          <option value="4">4 — Continents</option>
          <option value="5">5</option>
          <option value="6">6</option>
          <option value="7">7</option>
          <option value="8">8 — Countries</option>
          <option value="9">9</option>
          <option value="10">10 — States</option>
          <option value="11">11</option>
          <option value="12">12 — Cities</option>
          <option value="13">13</option>
          <option value="14">14</option>
          <option value="15">15 — Neighborhoods</option>
          <option value="16">16</option>
          <option value="17">17</option>
          <option value="18">18 — Streets</option>
          <option value="19">19</option>
          <option value="20">20</option>
          <option value="21">21</option>
          <option value="22">22 — Buildings</option>
        </select>
        {lbl('Map Height (px)')}{inp('mapHeight', { type:'number', placeholder:'300' })}
        <label style={{ display:'flex', alignItems:'center', gap:6, fontSize:12,
          color:'#374151', cursor:'pointer', marginBottom:8 }}>
          <input type="checkbox"
            checked={local.showAddress !== false}
            onChange={e => set('showAddress', e.target.checked)}
            style={{ width:14, height:14, cursor:'pointer' }} />
          Show address caption below map
        </label>
        {lbl('Label')}{inp('label', { placeholder:'e.g. Location' })}
        {lbl('Label Position')}
        <select value={local.labelpos||'top'} onChange={e => set('labelpos', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="top">Top</option>
          <option value="bottom">Bottom</option>
          <option value="none">None</option>
        </select>
        {lbl('Label Align')}
        <select value={local.labelalign||'left'} onChange={e => set('labelalign', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="left">Left</option>
          <option value="center">Center</option>
          <option value="right">Right</option>
        </select>
      </>}
      {local.type === 'weather' && <>
        {lbl('Linked Field')}
        <select value={local.linkedField||''} onChange={e => set('linkedField', e.target.value)}
          style={{ width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', outline:'none', marginBottom:10 }}>
          <option value="">— select field —</option>
          {fields.map(f => <option key={f.o_id} value={f.field}>{f.name} ({f.field})</option>)}
        </select>
        {lbl('Units')}{sel('units', [{ v:'imperial', l:'°F (Imperial)' }, { v:'metric', l:'°C (Metric)' }])}
      </>}
      {local.type === 'rating' && <RatingInspector local={local} set={set} fields={fields} onBrowseIcon={onBrowseIcon} lbl={lbl} inp={inp} sel={sel} />}

      {local.type === 'portal' && (
        <PortalInspector
          local={{ ...local, _appId: app?.app_id }}
          set={set}
          tables={tables}
          fields={fields}
          lbl={lbl}
          sel={sel}
          inp={inp}
        />
      )}

      {/* Actions */}
      <div style={{ display:'flex', gap:8, marginTop:16 }}>
        <button onClick={() => {
            // For file objects ensure all permission flags explicitly written
            if (local.type === 'file') {
              const withPerms = { ...local };
              if (withPerms.allowCamera   === undefined) withPerms.allowCamera   = false;
              if (withPerms.allowUpload   === undefined) withPerms.allowUpload   = true;
              if (withPerms.allowDownload === undefined) withPerms.allowDownload = true;
              if (withPerms.allowDelete   === undefined) withPerms.allowDelete   = true;
              return onUpdate(withPerms);
            }
            // For portal objects ensure all defaults explicitly written
            // so APP renderer never has to guess
            if (local.type === 'portal') {
              return onUpdate({
                ...local,
                displayAs:    local.displayAs    || 'rows',
                sortDir:      local.sortDir      || 'desc',
                allowAdd:     local.allowAdd     || 'yes',
                allowDelete:  local.allowDelete  || 'yes',
                deleteParent: local.deleteParent || 'no',
                portalRows:   local.portalRows   || 25,
                portalHeight: local.portalHeight || 0,
                displayFields: Array.isArray(local.displayFields) ? local.displayFields : [],
              });
            }
            onUpdate(local);
          }}
          style={{ flex:1, padding:'8px', background:'#0f1923', color:'#fff', border:'none', borderRadius:6, fontSize:12, fontWeight:600, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
          Apply
        </button>
        <button onClick={() => { if(confirm('Delete this object?')) onDelete(local); }}
          style={{ padding:'8px 10px', background:'#fff', color:'#dc2626', border:'1px solid #fecaca', borderRadius:6, fontSize:12, cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
          Del
        </button>
      </div>

      {/* Raw JSON toggle */}
      <details style={{ marginTop:12 }}>
        <summary style={{ fontSize:10, color:'#94a3b8', cursor:'pointer' }}>Raw JSON</summary>
        <pre style={{ fontSize:9, color:'#94a3b8', background:'#f8fafc', padding:8, borderRadius:6, marginTop:6, whiteSpace:'pre-wrap', wordBreak:'break-all' }}>{JSON.stringify(local, null, 2)}</pre>
      </details>
    </div>
  );
}

// ─────────────────────────────────────────────
// HomeCanvas — table card grid
// ─────────────────────────────────────────────
function HomeCanvas({ tables, selectedObj, onSelectTable, onReorder }) {
  const sorted = [...tables].sort((a,b) => (Number(a.sort)||99) - (Number(b.sort)||99));
  if (!tables?.length) return <div style={{ padding:40, textAlign:'center', color:'#94a3b8', fontSize:13 }}>No tables found. Add tables in the Database tab first.</div>;

  return (
    <div style={{ padding:16 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
        <div style={{ fontSize:12, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.06em' }}>
          Home Page · {sorted.filter(t => t.show !== false).length} visible tables
        </div>
        <div style={{ fontSize:11, color:'#94a3b8' }}>Click a card to configure in Inspector</div>
      </div>

      {/* Card grid — 4 per row on desktop */}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(180px, 1fr))', gap:12 }}>
        {sorted.map((t, idx) => {
          const hidden  = t.show === false;
          const sel     = selectedObj?._isTable && selectedObj?.tNum === t.tNum;
          return (
            <div key={t.tNum}
              onClick={() => { console.log('card click', t.tNum); onSelectTable(t); }}
              style={{ background: hidden ? '#f8fafc' : '#fff',
                       border: sel ? '2px solid #2563eb' : '1.5px solid #e2e8f0',
                       borderRadius:10, padding:'16px 14px', cursor:'pointer',
                       opacity: hidden ? 0.5 : 1, position:'relative',
                       minHeight:100 }}>
              {/* Hidden badge */}
              {hidden && (
                <div style={{ position:'absolute', top:8, right:8, fontSize:9, background:'#f1f5f9', color:'#94a3b8', padding:'1px 6px', borderRadius:4, fontWeight:600, textTransform:'uppercase' }}>hidden</div>
              )}
              {/* Sort handle */}
              <div style={{ position:'absolute', top:8, left:8, fontSize:10, color:'#cbd5e1', fontWeight:700 }}>#{idx+1}</div>

              {/* Icon */}
              <div style={{ fontSize:28, marginBottom:8, marginTop:4 }}>{t.icon || '⬡'}</div>

              {/* Name */}
              <div style={{ fontSize:14, fontWeight:700, color:'#0f1923', marginBottom:4, lineHeight:1.2 }}>{t.name}</div>

              {/* Desc — truncated to 120 chars */}
              <div style={{ fontSize:12, color:'#64748b', lineHeight:1.5 }}>
                {t.desc ? (t.desc.length > 120 ? t.desc.slice(0,120)+'…' : t.desc) : ''}
              </div>

              {/* Link type badge */}
              <div style={{ marginTop:8, fontSize:10, color: t.link==='detail' ? '#7c3aed' : '#2563eb',
                background: t.link==='detail' ? '#faf5ff' : '#eff6ff',
                display:'inline-block', padding:'2px 7px', borderRadius:4, fontWeight:600 }}>
                → {t.link === 'detail' ? 'Detail' : 'List'}
              </div>
            </div>
          );
        })}
      </div>

      {/* Mobile preview note */}
      <div style={{ marginTop:16, fontSize:11, color:'#94a3b8', background:'#f8fafc', borderRadius:6, padding:'8px 12px', border:'1px solid #f1f5f9' }}>
        📱 On mobile: cards display full width, one per row · 🖥 Desktop: 4 per row
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// ListViewPanel
// DEV UI for configuring the APP List View.
// Saves JSON matching the listview object schema:
// { group:"app", type:"listview", table, label,
//   fields:[{field,label,width}],
//   pagesize, sortfield, sortdir, status }
// ─────────────────────────────────────────────
const GOOGLE_FONTS = [
  { value:"'DM Sans', sans-serif",          label:'DM Sans (Sans)' },
  { value:"'Inter', sans-serif",            label:'Inter (Sans)' },
  { value:"'Open Sans', sans-serif",        label:'Open Sans (Sans)' },
  { value:"'Lato', sans-serif",             label:'Lato (Sans)' },
  { value:"'Poppins', sans-serif",          label:'Poppins (Sans)' },
  { value:"'Roboto', sans-serif",           label:'Roboto (Sans)' },
  { value:"'Merriweather', serif",          label:'Merriweather (Serif)' },
  { value:"'Playfair Display', serif",      label:'Playfair Display (Serif)' },
  { value:"'Lora', serif",                  label:'Lora (Serif)' },
  { value:"'Dancing Script', cursive",      label:'Dancing Script (Cursive)' },
  { value:"'Pacifico', cursive",            label:'Pacifico (Cursive)' },
  { value:"'DM Mono', monospace",           label:'DM Mono (Mono)' },
];
const FONT_SIZES    = ['10','12','13','14','15','16','18','20','24'];
const BORDER_RADII  = ['0','4','6','8','10','12','16'];
const PADDINGS      = [
  { value:'S',  label:'S — Compact' },
  { value:'M',  label:'M — Default' },
  { value:'L',  label:'L — Relaxed' },
  { value:'XL', label:'XL — Spacious' },
];


function ListViewPanel({ fields, cfg, table, app, onChange }) {
  const initSelected = () => {
    if (cfg?.fields?.length) return cfg.fields.map(f => ({ ...f }));
    return (fields || []).slice(0, 5).map(f => ({
      field: f.field,
      label: f.name || f.label || f.field,
      width: 2,
    }));
  };

  const [selected,      setSelected]      = React.useState(initSelected);
  const [pagesize,      setPagesize]      = React.useState(cfg?.pagesize  || 25);
  const [sortfield,     setSortfield]     = React.useState(cfg?.sortfield || 'r_auto');
  const [sortdir,       setSortdir]       = React.useState(cfg?.sortdir   || 'asc');
  const [dragIdx,       setDragIdx]       = React.useState(null);
  const [dragOver,      setDragOver]      = React.useState(null);
  const [previewRows,   setPreviewRows]   = React.useState([]);
  const [previewLoad,   setPreviewLoad]   = React.useState(false);
  const dragRef = React.useRef(null);

  // Available fields not yet in selected list
  const available = (fields || []).filter(f => !selected.find(s => s.field === f.field));

  const selectedFieldKey = selected.map(s => s.field).join(',');
  // Fetch real records for preview whenever selected fields or sort changes
  React.useEffect(() => {
    if (!table?.tName || !selected.length) { setPreviewRows([]); return; }
    setPreviewLoad(true);
    const fieldList = selected.map(s => s.field).join(',');
    const params = new URLSearchParams({
      app:    app?.app_id || (typeof APP_ID !== 'undefined' ? APP_ID : ''),
      table:  table.tName,
      fields: fieldList,
      limit:  8,
      offset: 0,
      sort:   sortfield,
      dir:    sortdir,
    });
    apiFetch(`${API_BASE}/v6/records?${params}`)
      .then(r => r.json())
      .then(data => { if (data.status === 'ok') setPreviewRows(data.records || []); })
      .catch(() => {})
      .finally(() => setPreviewLoad(false));
  }, [table?.tName, selectedFieldKey, sortfield, sortdir]);

  const emit = (sel, ps, sf, sd) => {
    onChange({
      group:     'app',
      type:      'listview',
      table:     table?.tName || '',
      label:     table?.name  || '',
      fields:    sel,
      pagesize:  ps,
      sortfield: sf,
      sortdir:   sd,
      status:    '1',
    });
  };

  const addField = (fieldVal) => {
    const f = (fields || []).find(f => f.field === fieldVal);
    if (!f) return;
    const next = [...selected, { field: f.field, label: f.name || f.label || f.field, width: 2 }];
    setSelected(next);
    emit(next, pagesize, sortfield, sortdir);
  };

  const removeField = (idx) => {
    const next = selected.filter((_, i) => i !== idx);
    setSelected(next);
    const sf = next.find(s => s.field === sortfield) ? sortfield : 'r_auto';
    setSortfield(sf);
    emit(next, pagesize, sf, sortdir);
  };

  const updateField = (idx, key, val) => {
    const next = selected.map((s, i) => i === idx ? { ...s, [key]: val } : s);
    setSelected(next);
    emit(next, pagesize, sortfield, sortdir);
  };

  const handleSortChange = (sf) => { setSortfield(sf); emit(selected, pagesize, sf, sortdir); };
  const handleDirChange  = (sd) => { setSortdir(sd);   emit(selected, pagesize, sortfield, sd); };
  const handlePageChange = (ps) => { setPagesize(ps);  emit(selected, ps, sortfield, sortdir); };

  // Drag to reorder
  const onDragStart = (idx) => { dragRef.current = idx; setDragIdx(idx); };
  const onDragEnter = (idx) => setDragOver(idx);
  const onDragEnd   = () => {
    if (dragRef.current !== null && dragOver !== null && dragRef.current !== dragOver) {
      const next = [...selected];
      const [moved] = next.splice(dragRef.current, 1);
      next.splice(dragOver, 0, moved);
      setSelected(next);
      emit(next, pagesize, sortfield, sortdir);
    }
    dragRef.current = null; setDragIdx(null); setDragOver(null);
  };

  const F  = { fontFamily:'DM Sans, sans-serif' };
  const iS = { padding:'5px 9px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff', color:'#0f1923', outline:'none' };
  const lS = { fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:3, display:'block' };

  return (
    <div style={{ ...F, fontSize:13 }}>

      {/* ── SETTINGS BAR ── */}
      <div style={{ display:'flex', alignItems:'flex-end', gap:16, padding:'12px 16px', borderBottom:'1px solid #e2e8f0', background:'#f8fafc', flexWrap:'wrap' }}>

        {/* Add Field */}
        <div>
          <label style={lS}>Add Field</label>
          <select style={iS} value="" onChange={e => { if (e.target.value) addField(e.target.value); }}>
            <option value="">— select field —</option>
            {available.map(f => (
              <option key={f.field} value={f.field}>{f.name || f.label || f.field} ({f.field})</option>
            ))}
          </select>
        </div>

        <div style={{ width:1, height:32, background:'#e2e8f0', alignSelf:'center' }} />

        {/* Rows per page */}
        <div>
          <label style={lS}>Rows / Page</label>
          <select style={iS} value={pagesize} onChange={e => handlePageChange(parseInt(e.target.value))}>
            {[10,25,50,100,250].map(n => <option key={n} value={n}>{n}</option>)}
          </select>
        </div>

        {/* Sort field */}
        <div>
          <label style={lS}>Default Sort</label>
          <select style={iS} value={sortfield} onChange={e => handleSortChange(e.target.value)}>
            <option value="r_auto">ID (default)</option>
            {selected.map(s => (
              <option key={s.field} value={s.field}>{s.label || s.field}</option>
            ))}
          </select>
        </div>

        {/* Sort direction */}
        <div>
          <label style={lS}>Direction</label>
          <div style={{ display:'flex', gap:4 }}>
            {[['asc','↑ A–Z'],['desc','↓ Z–A']].map(([val, lbl]) => (
              <button key={val} onClick={() => handleDirChange(val)}
                style={{ padding:'5px 10px', border: sortdir===val ? '2px solid #2563eb' : '1.5px solid #e2e8f0',
                  borderRadius:6, fontSize:11, fontWeight: sortdir===val ? 700 : 500,
                  background: sortdir===val ? '#eff6ff' : '#fff',
                  color: sortdir===val ? '#2563eb' : '#374151',
                  cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                {lbl}
              </button>
            ))}
          </div>
        </div>

      </div>

      {/* ── COLUMN EDITOR ── */}
      <div style={{ padding:'12px 16px' }}>

        {selected.length === 0 && (
          <div style={{ padding:'24px', textAlign:'center', border:'2px dashed #e2e8f0', borderRadius:8, color:'#94a3b8', fontSize:13 }}>
            Use "Add Field" above to add columns to the list view
          </div>
        )}

        {selected.length > 0 && (
          <>
            {/* Header */}
            <div style={{ display:'grid', gridTemplateColumns:'28px 160px 1fr 120px 36px', gap:8, padding:'0 6px 4px', alignItems:'center' }}>
              <div />
              <div style={lS}>Field</div>
              <div style={lS}>Column Label</div>
              <div style={{ ...lS, display:'flex', alignItems:'center', gap:4 }}>
                Relative Width
                <span title="Flex weight — width:2 gets twice as much space as width:1" style={{ cursor:'help', color:'#cbd5e1', fontSize:11 }}>?</span>
              </div>
              <div />
            </div>

            {selected.map((s, idx) => {
              const sf = (fields || []).find(f => f.field === s.field);
              const isDragging = dragIdx === idx;
              const isOver     = dragOver === idx;
              return (
                <div key={s.field}
                  draggable
                  onDragStart={() => onDragStart(idx)}
                  onDragEnter={() => onDragEnter(idx)}
                  onDragEnd={onDragEnd}
                  onDragOver={e => e.preventDefault()}
                  style={{
                    display:'grid', gridTemplateColumns:'28px 160px 1fr 120px 36px',
                    gap:8, alignItems:'center',
                    padding:'5px 6px', marginBottom:3,
                    background: isOver ? '#eff6ff' : isDragging ? '#f8fafc' : '#fff',
                    border: isOver ? '1.5px solid #2563eb' : '1.5px solid #e2e8f0',
                    borderRadius:6, opacity: isDragging ? 0.5 : 1,
                  }}>
                  <span style={{ cursor:'grab', color:'#cbd5e1', fontSize:15, textAlign:'center', userSelect:'none' }}>⠿</span>
                  <div style={{ overflow:'hidden' }}>
                    <span style={{ fontFamily:'DM Mono, monospace', fontSize:10, color:'#94a3b8', marginRight:5 }}>{s.field}</span>
                    <span style={{ fontSize:12, color:'#64748b', whiteSpace:'nowrap' }}>{sf?.name || s.field}</span>
                  </div>
                  <input style={{ ...iS, width:'100%', boxSizing:'border-box' }}
                    value={s.label}
                    onChange={e => updateField(idx, 'label', e.target.value)}
                    placeholder="Column label" />
                  <div style={{ display:'flex', gap:3 }}>
                    {[1,2,3,4].map(w => (
                      <button key={w} onClick={() => updateField(idx, 'width', w)}
                        style={{ flex:1, padding:'4px 0', border: s.width===w ? '2px solid #2563eb' : '1.5px solid #e2e8f0',
                          borderRadius:5, fontSize:11, fontWeight: s.width===w ? 700 : 400,
                          background: s.width===w ? '#eff6ff' : '#fff',
                          color: s.width===w ? '#2563eb' : '#374151',
                          cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
                        {w}
                      </button>
                    ))}
                  </div>
                  <button onClick={() => removeField(idx)}
                    style={{ background:'none', border:'none', color:'#f87171', cursor:'pointer', fontSize:16, lineHeight:1, padding:'2px' }}>
                    ✕
                  </button>
                </div>
              );
            })}
          </>
        )}
      </div>

      {/* ── LIVE PREVIEW ── */}
      {selected.length > 0 && (
        <div style={{ margin:'0 16px 16px', border:'1px solid #e2e8f0', borderRadius:8, overflow:'hidden' }}>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'7px 12px', background:'#f8fafc', borderBottom:'1px solid #e2e8f0' }}>
            <span style={{ fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase', letterSpacing:'0.06em' }}>
              Preview — {table?.name}
            </span>
            {previewLoad && <span style={{ fontSize:11, color:'#94a3b8' }}>Loading…</span>}
            {!previewLoad && <span style={{ fontSize:11, color:'#94a3b8' }}>{previewRows.length} sample record{previewRows.length !== 1 ? 's' : ''}</span>}
          </div>

          {/* Header row */}
          <div style={{ display:'flex', background:'#f1f5f9', borderBottom:'1px solid #e2e8f0' }}>
            {selected.map(s => (
              <div key={s.field} style={{ flex: s.width || 1, padding:'7px 12px', fontSize:11, fontWeight:700, color:'#374151', borderRight:'1px solid #e2e8f0', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', minWidth:0 }}>
                {s.label}
              </div>
            ))}
            <div style={{ width:32 }} />{/* spacer for nav arrow */}
          </div>

          {/* Data rows */}
          {previewRows.length === 0 && !previewLoad && (
            <div style={{ padding:'16px 12px', fontSize:12, color:'#94a3b8', textAlign:'center' }}>No records found</div>
          )}
          {previewRows.map((row, ri) => (
            <div key={ri} style={{ display:'flex', borderBottom:'1px solid #f1f5f9', background: ri%2===0 ? '#fff' : '#fafbfc' }}>
              {selected.map(s => (
                <div key={s.field} style={{ flex: s.width || 1, padding:'7px 12px', fontSize:12, color:'#374151', borderRight:'1px solid #f1f5f9', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', minWidth:0 }}>
                  {(row[s.field] !== null && row[s.field] !== undefined && row[s.field] !== '') ? String(row[s.field]) : <span style={{ color:'#cbd5e1' }}>—</span>}
                </div>
              ))}
              <div style={{ width:32, display:'flex', alignItems:'center', justifyContent:'center', color:'#cbd5e1', fontSize:12 }}>▶</div>
            </div>
          ))}
        </div>
      )}

    </div>
  );
}
  


// ─────────────────────────────────────────────
// ThemePanel
// ─────────────────────────────────────────────
function ThemePanel({ theme, onChange }) {
  if (!theme) return <div style={{ padding:40, textAlign:'center', color:'#94a3b8' }}>No theme data</div>;
  function set(key, val) { onChange({ ...theme, [key]: val }); }

  const colorRow = (label, key) => (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'8px 0', borderBottom:'1px solid #f8fafc' }}>
      <span style={{ fontSize:12, color:'#374151', fontWeight:500 }}>{label}</span>
      <div style={{ display:'flex', alignItems:'center', gap:8 }}>
        <input type="color" value={theme[key] || '#000000'} onChange={e => set(key, e.target.value)}
          style={{ width:32, height:32, border:'1.5px solid #e2e8f0', borderRadius:6, cursor:'pointer', padding:2 }} />
        <span style={{ fontSize:10, fontFamily:'DM Mono, monospace', color:'#64748b', width:60 }}>{theme[key]}</span>
      </div>
    </div>
  );

  const selectRow = (label, key, options) => (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'8px 0', borderBottom:'1px solid #f8fafc', gap:8 }}>
      <span style={{ fontSize:12, color:'#374151', fontWeight:500, flexShrink:0 }}>{label}</span>
      <select value={theme[key] || ''} onChange={e => set(key, e.target.value)}
        style={{ padding:'4px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', background:'#fff', maxWidth:180 }}>
        {(options || []).map(o => typeof o === 'string'
          ? <option key={o} value={o}>{o}</option>
          : <option key={o.value} value={o.value}>{o.label}</option>
        )}
      </select>
    </div>
  );

  const textRow = (label, key, placeholder='') => (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'8px 0', borderBottom:'1px solid #f8fafc', gap:8 }}>
      <span style={{ fontSize:12, color:'#374151', fontWeight:500, flexShrink:0 }}>{label}</span>
      <input value={theme[key] || ''} onChange={e => set(key, e.target.value)} placeholder={placeholder}
        style={{ padding:'4px 8px', border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif', color:'#0f1923', width:160, outline:'none' }} />
    </div>
  );

  return (
    <div style={{ padding:'16px 20px' }}>
      <div style={{ fontSize:13, fontWeight:700, color:'#0f1923', marginBottom:16, paddingBottom:10, borderBottom:'2px solid #f1f5f9' }}>
        🎨 Theme Settings
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:'0 40px' }}>
        <div>
          <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:8 }}>Colors</div>
          {colorRow('Nav Background', 'nav_bg')}
          {colorRow('Nav Text',       'nav_color')}
          {colorRow('Primary Color',  'primary')}
          {colorRow('Accent Color',   'accent')}
          {colorRow('Card Background','card_bg')}
          {colorRow('Page Background','page_bg')}
        </div>
        <div>
          <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:8 }}>Typography</div>
          {selectRow('Font Family',   'font',          GOOGLE_FONTS)}
          {selectRow('Font Size',     'font_size',     FONT_SIZES)}
          {selectRow('Border Radius', 'border_radius', BORDER_RADII)}
          {selectRow('Padding',       'density',       PADDINGS)}
          <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginTop:16, marginBottom:8 }}>Branding</div>
          {textRow('Logo Text', 'logo_text', 'MyApp')}
          {textRow('Logo URL',  'logo_url',  'https://...')}
          <div style={{ fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase', letterSpacing:'0.07em', marginTop:16, marginBottom:8 }}>Object Labels</div>
          {selectRow('Label Size', 'label_size', [
            { value:'9px',  label:'9px — Tiny'   },
            { value:'10px', label:'10px — Small (default)' },
            { value:'11px', label:'11px — Medium' },
            { value:'12px', label:'12px — Large'  },
            { value:'13px', label:'13px — X-Large'},
          ])}
          {colorRow('Label Color', 'label_color')}
        </div>
      </div>

      {/* Live preview */}
      <div style={{ marginTop:20, borderRadius:10, overflow:'hidden', border:'1px solid #e2e8f0' }}>
        <div style={{ background: theme.nav_bg||'#0f1923', padding:'10px 16px', display:'flex', alignItems:'center', gap:10 }}>
          <span style={{ color: theme.nav_color||'#fff', fontSize:13, fontWeight:700 }}>{theme.logo_text||'MyApp'}</span>
          <span style={{ color: theme.nav_color||'#fff', fontSize:12, opacity:0.6, marginLeft:'auto' }}>Nav Item</span>
        </div>
        <div style={{ background: theme.page_bg||'#f5f3ff', padding:16, display:'flex', gap:10 }}>
          <div style={{ background: theme.card_bg||'#fff', borderRadius: (theme.border_radius||8)+'px', padding:14, flex:1, border:'1px solid #e2e8f0', fontFamily: theme.font||'inherit', fontSize: (theme.font_size||14)+'px', color:'#374151' }}>
            <div style={{ fontWeight:700, marginBottom:6 }}>Sample Card</div>
            <div style={{ fontSize: theme.label_size||'10px', color: theme.label_color||'#64748b', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.07em', marginBottom:3 }}>FIELD LABEL</div>
            <div style={{ height:28, background:'#f8fafc', border:'1px solid #e2e8f0', borderRadius:4, marginBottom:8 }} />
            <button style={{ marginTop:2, padding:'6px 14px', background: theme.primary||'#7c3aed', color:'#fff', border:'none', borderRadius: (theme.border_radius||8)+'px', fontSize:12, cursor:'pointer' }}>Button</button>
          </div>
          <div style={{ background: theme.card_bg||'#fff', borderRadius: (theme.border_radius||8)+'px', padding:14, flex:1, border:'1px solid #e2e8f0', fontFamily: theme.font||'inherit', fontSize: (theme.font_size||14)+'px' }}>
            <div style={{ fontWeight:700, marginBottom:6, color:'#374151' }}>Another Card</div>
            <div style={{ color: theme.accent||'#f59e0b', fontSize:12, fontWeight:600 }}>Accent text</div>
          </div>
        </div>
      </div>
    </div>
  );
}

