// spaces.jsx — main bookmark library app
const { useState: useStateS, useEffect: useEffectS, useRef: useRefS, useMemo: useMemoS, useCallback: useCallbackS } = React;

const __SPACES_CSS = `
  /* ─────────────  Layout shell  ───────────── */
  .sp { display: grid; grid-template-rows: auto 1fr; height: 100vh; }

  /* ─────────────  Top bar  ───────────── */
  .sp-top {
    display: grid;
    grid-template-columns: 232px 1fr auto;
    align-items: center;
    gap: 16px;
    padding: 10px 16px 10px 18px;
    border-bottom: 1px solid var(--hairline);
    background: color-mix(in oklab, var(--surface) 92%, var(--bg));
    z-index: 10;
  }
  .sp-top-left {
    display: flex; align-items: center; gap: 8px;
  }
  .sp-side-toggle {
    width: 32px; height: 32px;
    border-radius: var(--r-sm);
    display: grid; place-items: center;
    background: transparent; border: 0; color: var(--ink-2);
  }
  .sp-side-toggle:hover { background: var(--hover); color: var(--ink); }

  .sp-search-wrap {
    max-width: 560px; margin: 0 auto;
    width: 100%;
    position: relative;
  }
  .sp-search-input {
    width: 100%;
    height: 36px;
    padding: 0 80px 0 38px;
    border: 1px solid var(--hairline);
    border-radius: var(--r-sm);
    background: var(--surface);
    color: var(--ink);
    font-size: 14px;
    transition: all 120ms ease;
  }
  .sp-search-input:hover { border-color: var(--border); background: var(--surface); }
  .sp-search-input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
  }
  .sp-search-icon {
    position: absolute; left: 12px; top: 50%;
    transform: translateY(-50%);
    color: var(--muted); pointer-events: none;
  }
  .sp-search-kbd {
    position: absolute; right: 8px; top: 50%;
    transform: translateY(-50%);
    display: flex; gap: 4px;
    pointer-events: none;
  }
  .sp-search-kbd kbd {
    font-family: var(--font-mono); font-size: 10px;
    padding: 2px 6px; border-radius: 4px;
    background: var(--surface-2);
    color: var(--muted);
    border: 1px solid var(--hairline);
    font-weight: 500;
  }
  .sp-search-clear {
    position: absolute; right: 8px; top: 50%;
    transform: translateY(-50%);
    width: 22px; height: 22px;
    display: grid; place-items: center;
    border-radius: 4px;
    background: transparent; border: 0; color: var(--muted);
  }
  .sp-search-clear:hover { background: var(--hover); color: var(--ink); }

  .sp-top-right {
    display: flex; align-items: center; gap: 4px;
  }
  .sp-view-toggle {
    display: flex;
    border: 1px solid var(--hairline);
    border-radius: var(--r-sm);
    padding: 2px;
    margin-right: 8px;
  }
  .sp-view-toggle button {
    width: 28px; height: 28px;
    background: transparent; border: 0;
    border-radius: 4px;
    color: var(--muted);
    display: grid; place-items: center;
  }
  .sp-view-toggle button:hover { color: var(--ink); }
  .sp-view-toggle button.active {
    background: var(--surface-2);
    color: var(--ink);
  }

  .sp-avatar {
    width: 32px; height: 32px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-ink);
    display: grid; place-items: center;
    font-size: 12px; font-weight: 600;
    border: 0;
    cursor: pointer;
    position: relative;
  }
  .sp-avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }

  .sp-menu {
    position: absolute; top: calc(100% + 6px); right: 0;
    min-width: 220px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-lg);
    padding: 4px;
    z-index: 100;
    animation: sp-menu-in 140ms ease;
  }
  @keyframes sp-menu-in { from { opacity: 0; transform: translateY(-4px); } }
  .sp-menu-h {
    padding: 10px 12px 8px;
    border-bottom: 1px solid var(--hairline);
    margin-bottom: 4px;
  }
  .sp-menu-h .name { font-weight: 600; font-size: 13.5px; }
  .sp-menu-h .email { font-size: 12px; color: var(--muted); margin-top: 2px; }
  .sp-menu-item {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 8px 12px;
    background: transparent; border: 0;
    border-radius: var(--r-xs);
    color: var(--ink);
    font-size: 13.5px;
    text-align: left;
  }
  .sp-menu-item:hover { background: var(--hover); }
  .sp-menu-item.danger { color: var(--danger); }
  .sp-menu-sep { height: 1px; background: var(--hairline); margin: 4px 0; }

  /* ─────────────  Body  ───────────── */
  .sp-body { display: grid; grid-template-columns: 232px 1fr; min-height: 0; }
  .sp-body.collapsed { grid-template-columns: 0 1fr; }

  /* ─────────────  Sidebar  ───────────── */
  .sp-side {
    border-right: 1px solid var(--hairline);
    background: color-mix(in oklab, var(--surface) 60%, var(--bg));
    overflow-y: auto;
    overflow-x: hidden;
    min-width: 0;
    transition: max-width 200ms ease;
  }
  .sp-body.collapsed .sp-side { display: none; }

  .sp-side-section { padding: 16px 8px 4px; }
  .sp-side-section + .sp-side-section { padding-top: 8px; }

  .sp-side-h {
    display: flex; align-items: center; justify-content: space-between;
    padding: 4px 10px 8px;
  }
  .sp-side-h-label {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 500;
  }
  .sp-side-h-add {
    width: 20px; height: 20px;
    display: grid; place-items: center;
    background: transparent; border: 0;
    border-radius: 4px;
    color: var(--muted);
  }
  .sp-side-h-add:hover { background: var(--hover); color: var(--ink); }

  .sp-quick {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 10px;
    border-radius: var(--r-xs);
    color: var(--ink-2);
    font-size: 13.5px;
    cursor: pointer;
    background: transparent; border: 0;
    width: 100%; text-align: left;
  }
  .sp-quick:hover { background: var(--hover); color: var(--ink); }
  .sp-quick.active {
    background: var(--selected);
    color: var(--accent);
    font-weight: 500;
  }
  [data-theme="dark"] .sp-quick.active { color: color-mix(in oklab, var(--accent) 60%, white); }
  .sp-quick-count {
    margin-left: auto;
    font-family: var(--font-mono); font-size: 11px;
    color: var(--muted);
    font-weight: 400;
  }
  .sp-quick.active .sp-quick-count { color: var(--accent); opacity: 0.8; }

  /* Tree */
  .sp-tree { display: flex; flex-direction: column; }
  .sp-node {
    display: flex; align-items: center; gap: 4px;
    padding: 6px 4px 6px 0;
    border-radius: var(--r-xs);
    color: var(--ink-2);
    font-size: 13.5px;
    cursor: pointer;
    position: relative;
  }
  .sp-node:hover { background: var(--hover); color: var(--ink); }
  .sp-node.active { background: var(--selected); color: var(--accent); font-weight: 500; }
  [data-theme="dark"] .sp-node.active { color: color-mix(in oklab, var(--accent) 60%, white); }
  .sp-node-caret {
    width: 20px; height: 20px;
    display: grid; place-items: center;
    color: var(--muted);
    transition: transform 160ms ease;
    flex-shrink: 0;
  }
  .sp-node-caret.open { transform: rotate(90deg); }
  .sp-node-caret.empty { visibility: hidden; }
  .sp-node-icon { display: grid; place-items: center; color: var(--muted); flex-shrink: 0; width: 18px; }
  .sp-node.active .sp-node-icon { color: var(--accent); }
  .sp-node-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .sp-node-actions {
    display: none;
    gap: 2px;
    margin-right: 2px;
  }
  .sp-node:hover .sp-node-actions { display: flex; }
  .sp-node-act {
    width: 22px; height: 22px;
    display: grid; place-items: center;
    background: transparent; border: 0;
    border-radius: 4px;
    color: var(--muted);
  }
  .sp-node-act:hover { background: var(--bg); color: var(--ink); }
  .sp-node-act.danger:hover { color: var(--danger); }

  .sp-tags-wrap {
    display: flex; flex-wrap: wrap; gap: 5px;
    padding: 4px 10px 12px;
  }

  .sp-side-clear {
    margin: 4px 8px 8px;
    padding: 8px 10px;
    width: calc(100% - 16px);
    text-align: left;
    background: var(--accent-soft);
    color: var(--accent);
    border: 1px dashed var(--accent-line);
    border-radius: var(--r-sm);
    font-size: 12px;
    display: flex; align-items: center; gap: 8px;
    cursor: pointer;
  }
  .sp-side-clear:hover { background: color-mix(in oklab, var(--accent) 18%, transparent); }

  .sp-side-empty {
    padding: 12px;
    color: var(--muted);
    font-size: 12px;
    font-style: italic;
    text-align: center;
  }

  /* ─────────────  Content area  ───────────── */
  .sp-content {
    display: flex; flex-direction: column;
    min-width: 0; min-height: 0;
    overflow: hidden;
  }

  .sp-c-hd {
    display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
    padding: 28px 32px 16px;
    border-bottom: 1px solid var(--hairline);
  }
  .sp-c-hd-left { min-width: 0; }
  .sp-c-eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-bottom: 6px;
    display: inline-flex; align-items: center; gap: 6px;
  }
  .sp-c-title {
    font-size: 28px;
    font-weight: 400;
    letter-spacing: -0.015em;
    margin: 0;
    line-height: 1.1;
    display: flex; align-items: center; gap: 12px;
    flex-wrap: wrap;
  }
  .sp-c-title .accent {
    font-family: var(--font-serif); font-style: italic;
    color: var(--accent); letter-spacing: -0.005em;
  }
  .sp-c-count-row { margin-top: 8px; }
  .sp-c-count {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    padding: 3px 8px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 999px;
    letter-spacing: 0.05em;
    display: inline-block;
    white-space: nowrap;
  }
  .sp-c-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

  /* Bulk toolbar */
  .sp-bulk {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px 32px;
    background: var(--accent-soft);
    border-bottom: 1px solid var(--accent-line);
    animation: sp-bulk-in 200ms ease;
  }
  @keyframes sp-bulk-in {
    from { opacity: 0; transform: translateY(-4px); }
  }
  .sp-bulk-label {
    font-size: 13px; font-weight: 500;
    color: var(--accent);
  }
  [data-theme="dark"] .sp-bulk-label { color: color-mix(in oklab, var(--accent) 60%, white); }
  .sp-bulk-actions { display: flex; gap: 6px; }

  /* ─────────────  List view  ───────────── */
  .sp-list-wrap {
    flex: 1;
    overflow-y: auto;
    padding: 8px 16px 16px;
  }
  .sp-list-head {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) minmax(0, 200px) 80px 48px;
    gap: 14px;
    align-items: center;
    padding: 8px 16px;
    border-bottom: 1px solid var(--hairline);
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--muted);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    position: sticky;
    top: 0;
    background: var(--bg);
    z-index: 1;
  }
  .sp-list-row {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) minmax(0, 200px) 80px 48px;
    gap: 14px;
    align-items: center;
    padding: var(--pad-y) 16px;
    border-bottom: 1px solid var(--hairline);
    min-height: var(--row-h);
    transition: background 120ms ease;
    cursor: pointer;
    position: relative;
  }
  .sp-list-row:hover { background: var(--hover); }
  .sp-list-row.selected { background: var(--selected); }
  .sp-list-row.selected:hover { background: color-mix(in oklab, var(--selected) 90%, var(--ink)); }

  .sp-check {
    width: 18px; height: 18px;
    border-radius: 4px;
    border: 1.5px solid var(--border);
    background: var(--surface);
    display: grid; place-items: center;
    color: white;
    transition: all 120ms ease;
    cursor: pointer;
    flex-shrink: 0;
  }
  .sp-check:hover { border-color: var(--ink-2); }
  .sp-check.checked {
    background: var(--accent);
    border-color: var(--accent);
  }
  .sp-check svg { opacity: 0; }
  .sp-check.checked svg { opacity: 1; }

  .sp-row-title-cell {
    display: flex; align-items: center; gap: 12px;
    min-width: 0;
  }
  .sp-row-fav {
    width: 28px; height: 28px;
    border-radius: 6px;
    background: var(--surface-2);
    display: grid; place-items: center;
    flex-shrink: 0;
    overflow: hidden;
    color: var(--accent);
  }
  .sp-row-fav img { width: 16px; height: 16px; }
  .sp-row-fav-fallback {
    font-family: var(--font-mono);
    font-size: 12px; font-weight: 600;
    color: var(--accent);
  }
  .sp-row-title {
    font-size: 14px;
    font-weight: 500;
    color: var(--ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .sp-row-host {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .sp-row-title-stack { min-width: 0; display: flex; flex-direction: column; gap: 1px; }

  .sp-row-tags { display: flex; flex-wrap: wrap; gap: 4px; overflow: hidden; max-height: 24px; }
  .sp-row-tag {
    font-family: var(--font-mono);
    font-size: 10.5px;
    padding: 2px 7px;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--ink-2);
    border: 1px solid var(--hairline);
    white-space: nowrap;
  }

  .sp-row-date {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--muted);
  }

  .sp-row-action {
    display: none;
    width: 28px; height: 28px;
    background: transparent; border: 0;
    border-radius: 6px;
    color: var(--muted);
    align-items: center; justify-content: center;
  }
  .sp-list-row:hover .sp-row-action { display: inline-flex; }
  .sp-row-action:hover { background: var(--surface-2); color: var(--danger); }

  /* ─────────────  Grid view  ───────────── */
  .sp-grid {
    flex: 1;
    overflow-y: auto;
    padding: 24px 32px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
    align-content: start;
  }
  .sp-card {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    overflow: hidden;
    transition: all 160ms ease;
    cursor: pointer;
    position: relative;
    display: flex; flex-direction: column;
    min-height: 240px;
  }
  .sp-card:hover {
    border-color: var(--ink-2);
    transform: translateY(-2px);
    box-shadow: var(--shadow-md);
  }
  .sp-card.selected {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
  }
  .sp-card-thumb {
    height: 90px;
    flex-shrink: 0;
    background:
      linear-gradient(135deg,
        color-mix(in oklab, var(--accent) 10%, var(--surface-2)),
        var(--surface-2));
    border-bottom: 1px solid var(--hairline);
    position: relative;
    display: grid; place-items: center;
    overflow: hidden;
  }
  .sp-card-thumb::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
      repeating-linear-gradient(45deg,
        transparent 0,
        transparent 12px,
        color-mix(in oklab, var(--accent) 5%, transparent) 12px,
        color-mix(in oklab, var(--accent) 5%, transparent) 14px);
  }
  .sp-card-fav {
    width: 42px; height: 42px;
    border-radius: 10px;
    background: var(--surface);
    display: grid; place-items: center;
    border: 1px solid var(--hairline);
    box-shadow: var(--shadow-sm);
    position: relative; z-index: 1;
  }
  .sp-card-fav img { width: 26px; height: 26px; }
  .sp-card-fav-fallback {
    font-family: var(--font-mono);
    font-size: 16px; font-weight: 600;
    color: var(--accent);
  }
  .sp-card-body {
    padding: 14px 16px 12px;
    flex: 1 1 auto;
    display: flex; flex-direction: column; gap: 8px;
    min-height: 0;
  }
  .sp-card-foot { flex-shrink: 0; }
  .sp-card-title {
    font-size: 14.5px;
    font-weight: 500;
    color: var(--ink);
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .sp-card-host {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .sp-card-tags {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin-top: auto;
  }
  .sp-card-foot {
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 16px;
    border-top: 1px solid var(--hairline);
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
  }
  .sp-card-check {
    position: absolute; top: 10px; left: 10px;
    z-index: 2;
  }

  /* ─────────────  Empty / loading ─────────── */
  .sp-empty {
    flex: 1;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 96px 32px;
    text-align: center;
    color: var(--muted);
  }
  .sp-empty-art {
    width: 96px; height: 96px;
    border-radius: 50%;
    background: var(--surface-2);
    color: var(--accent);
    display: grid; place-items: center;
    margin-bottom: 24px;
  }
  .sp-empty-h {
    font-size: 22px;
    font-weight: 400;
    color: var(--ink);
    margin: 0 0 8px;
    letter-spacing: -0.01em;
  }
  .sp-empty-h .accent { font-family: var(--font-serif); font-style: italic; color: var(--accent); }
  .sp-empty-p {
    margin: 0 0 24px;
    max-width: 36ch;
    font-size: 14px;
  }

  /* ─────────────  Pagination ─────────── */
  .sp-pager {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 32px;
    border-top: 1px solid var(--hairline);
    background: var(--bg);
    font-size: 13px;
    color: var(--muted);
  }
  .sp-pager-nums {
    display: flex; align-items: center; gap: 4px;
    font-family: var(--font-mono);
    font-size: 12px;
  }
  .sp-pager-btn {
    width: 28px; height: 28px;
    display: grid; place-items: center;
    background: transparent; border: 1px solid transparent;
    border-radius: var(--r-xs);
    color: var(--ink-2);
  }
  .sp-pager-btn:hover { background: var(--hover); color: var(--ink); }
  .sp-pager-btn.active {
    background: var(--accent);
    color: var(--accent-ink);
    border-color: var(--accent);
  }
  .sp-pager-btn:disabled { opacity: 0.3; cursor: not-allowed; }

  /* ─────────────  Import modal rows ─────────── */
  .sp-import-row {
    display: grid;
    grid-template-columns: 44px 1fr auto;
    gap: 14px;
    align-items: center;
    width: 100%;
    padding: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    color: var(--ink);
    cursor: pointer;
    transition: all 140ms ease;
    text-align: left;
  }
  .sp-import-row:hover {
    border-color: var(--ink-2);
    background: var(--surface-2);
    transform: translateY(-1px);
  }
  .sp-import-logo {
    width: 44px; height: 44px;
    border-radius: var(--r-sm);
    background: var(--surface-2);
    border: 1px solid var(--hairline);
    display: grid; place-items: center;
    overflow: hidden;
  }
  .sp-import-text { min-width: 0; }
  .sp-import-name {
    font-size: 14.5px;
    font-weight: 600;
    letter-spacing: -0.005em;
  }
  .sp-import-hint {
    font-size: 12px;
    color: var(--muted);
    margin-top: 2px;
    line-height: 1.3;
  }
  .sp-import-meta {
    display: flex; align-items: center; gap: 8px;
    color: var(--muted);
  }
  .sp-import-format {
    font-family: var(--font-mono);
    font-size: 10.5px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--hairline);
    letter-spacing: 0.04em;
    color: var(--ink-2);
    white-space: nowrap;
  }

  /* Mobile */
  .sp-mobile-backdrop {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 20;
    display: none;
    animation: fade-in 160ms ease;
  }
  @media (max-width: 880px) {
    .sp-list-head,
    .sp-list-row {
      grid-template-columns: 28px minmax(0, 1fr) 80px 48px;
      gap: 12px;
    }
    .sp-col-tags { display: none !important; }
  }
  @media (max-width: 760px) {
    .sp-top { grid-template-columns: auto 1fr auto; }
    .sp-top-left .brand-name { display: none; }
    .sp-body { grid-template-columns: 1fr; }
    .sp-side {
      position: fixed; top: 0; left: 0; bottom: 0;
      width: 280px; max-width: 86vw;
      transform: translateX(-100%);
      transition: transform 200ms ease;
      z-index: 30;
      background: var(--surface);
    }
    .sp-body.mobile-open .sp-side { transform: translateX(0); }
    .sp-body.mobile-open .sp-mobile-backdrop { display: block; }
    .sp-c-hd { padding: 20px; flex-direction: column; align-items: stretch; }
    .sp-c-hd .sp-c-actions { justify-content: stretch; }
    .sp-list-head { display: none; }
    .sp-list-row {
      grid-template-columns: 28px minmax(0, 1fr) 48px;
      gap: 12px;
    }
    .sp-row-date, .sp-col-date { display: none; }
    .sp-bulk { padding: 10px 20px; }
    .sp-list-wrap { padding: 4px 8px 8px; }
    .sp-grid { padding: 16px; }
  }
`;

// ─────────────────────────────────────────────
// Sample data lives on window.
// ─────────────────────────────────────────────

function makeFlat(folders, depth = 0, list = []) {
  for (const f of folders) {
    list.push({ id: f.id, title: f.title, depth });
    if (f.children) makeFlat(f.children, depth + 1, list);
  }
  return list;
}

// ── Folder tree node ───────────────────────────────────────────────
function FolderNode({ node, depth, expanded, selected, onSelect, onToggle, onAdd, onDelete }) {
  const isOpen = expanded.has(node.id);
  const isActive = selected === node.id;
  const hasKids = node.children && node.children.length > 0;
  return (
    <>
      <div className={`sp-node ${isActive ? "active" : ""}`}
           style={{ paddingLeft: 4 + depth * 16 }}
           onClick={() => onSelect(node.id)}>
        <span className={`sp-node-caret ${isOpen ? "open" : ""} ${hasKids ? "" : "empty"}`}
              onClick={(e) => { e.stopPropagation(); if (hasKids) onToggle(node.id); }}>
          <I.Chevron size={12} />
        </span>
        <span className="sp-node-icon">
          {isOpen && hasKids ? <I.FolderOpen size={15} /> : <I.Folder size={15} />}
        </span>
        <span className="sp-node-title">{node.title}</span>
        <span className="sp-node-actions">
          <button className="sp-node-act" title="New subfolder"
                  onClick={(e) => { e.stopPropagation(); onAdd(node.id); }}>
            <I.Plus size={12} />
          </button>
          <button className="sp-node-act danger" title="Delete"
                  onClick={(e) => { e.stopPropagation(); onDelete(node); }}>
            <I.Trash size={12} />
          </button>
        </span>
      </div>
      {isOpen && hasKids && node.children.map((c) => (
        <FolderNode key={c.id} node={c} depth={depth + 1}
                    expanded={expanded} selected={selected}
                    onSelect={onSelect} onToggle={onToggle}
                    onAdd={onAdd} onDelete={onDelete} />
      ))}
    </>
  );
}

// ── Custom checkbox ───────────────────────────────────────────────
function Check({ checked, onChange, indeterminate }) {
  return (
    <button className={`sp-check ${checked || indeterminate ? "checked" : ""}`}
            onClick={(e) => { e.stopPropagation(); onChange?.(!checked); }}
            role="checkbox" aria-checked={checked}>
      {indeterminate
        ? <svg width="10" height="10" viewBox="0 0 10 10"><rect x="2" y="4.5" width="6" height="1.5" fill="white"/></svg>
        : <I.Check size={11} stroke={3} />}
    </button>
  );
}

// ── Custom select (folder picker) ─────────────────────────────────
function FolderSelect({ value, onChange, folders, includeRoot = true }) {
  const flat = useMemoS(() => {
    const list = [];
    if (includeRoot) list.push({ id: "__root__", title: "— Root (no parent) —", depth: 0 });
    const walk = (nodes, d) => {
      for (const n of nodes) {
        list.push({ id: n.id, title: n.title, depth: d });
        if (n.children) walk(n.children, d + 1);
      }
    };
    walk(folders, 0);
    return list;
  }, [folders, includeRoot]);
  return (
    <select className="input" value={value} onChange={(e) => onChange(e.target.value)}
            style={{ height: 40, paddingLeft: 12 }}>
      {flat.map((o) => (
        <option key={o.id} value={o.id}>
          {"\u00A0\u00A0".repeat(o.depth)}{o.title}
        </option>
      ))}
    </select>
  );
}

// ── Tag picker ────────────────────────────────────────────────────
function TagPicker({ value, onChange, allTags }) {
  const [draft, setDraft] = useStateS("");
  const add = (t) => {
    const v = (t || draft).trim();
    if (!v) return;
    if (!value.includes(v)) onChange([...value, v]);
    setDraft("");
  };
  const remove = (t) => onChange(value.filter((x) => x !== t));
  return (
    <div style={{
      border: "1px solid var(--border)", borderRadius: "var(--r-sm)",
      background: "var(--surface)", padding: 8, minHeight: 40,
      display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center",
    }}>
      {value.map((t) => (
        <span key={t} className="chip" style={{ height: 24 }}>
          {t}
          <button onClick={() => remove(t)}
                  style={{ background: "transparent", border: 0, padding: 0, marginLeft: 4, color: "inherit", cursor: "pointer" }}>
            <I.Close size={11} />
          </button>
        </span>
      ))}
      <input value={draft}
             onChange={(e) => setDraft(e.target.value)}
             onKeyDown={(e) => {
               if (e.key === "Enter" || e.key === ",") { e.preventDefault(); add(); }
               if (e.key === "Backspace" && !draft && value.length) remove(value[value.length-1]);
             }}
             placeholder={value.length ? "" : "Type a tag and press Enter"}
             style={{ flex: 1, minWidth: 120, border: 0, background: "transparent", outline: "none", padding: "4px 6px", color: "var(--ink)", fontSize: 13 }} />
      {allTags?.length > 0 && (
        <div style={{ width: "100%", display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6, paddingTop: 6, borderTop: "1px dashed var(--hairline)" }}>
          {allTags.filter((t) => !value.includes(t.name)).slice(0, 8).map((t) => (
            <button key={t.name} className="chip" style={{ height: 22 }}
                    onClick={() => add(t.name)}>
              + {t.name}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// SpacesPage
// ─────────────────────────────────────────────
function SpacesPage({ navigate, user, onLogout }) {
  const [folders, setFolders] = useStateS([]);
  const [bookmarks, setBookmarks] = useStateS([]);
  const [tags, setTags] = useStateS([]);
  const [favorites, setFavorites] = useStateS(() => {
    try {
      const key = user?.id ? `bm_fav_${user.id}` : 'bm_fav';
      const raw = localStorage.getItem(key);
      return raw ? new Set(JSON.parse(raw)) : new Set();
    } catch { return new Set(); }
  });
  const [loading, setLoading] = useStateS(true);
  const [dataError, setDataError] = useStateS(null);

  const [selectedFolder, setSelectedFolder] = useStateS(null); // null | folderId
  const [selectedTag, setSelectedTag] = useStateS(null);
  const [scope, setScope] = useStateS("all"); // all | favorites | recent | folder | tag
  const [search, setSearch] = useStateS("");
  const [expanded, setExpanded] = useStateS(new Set(["f-design", "f-dev"]));
  const [selectedIds, setSelectedIds] = useStateS(new Set());
  const [view, setView] = useStateS("list"); // list | grid
  const [sidebarOpen, setSidebarOpen] = useStateS(false); // mobile
  const [sidebarCollapsed, setSidebarCollapsed] = useStateS(false); // desktop
  const [menuOpen, setMenuOpen] = useStateS(false);
  const [page, setPage] = useStateS(1);
  const pageSize = 12;

  // modals
  const [addBmOpen, setAddBmOpen] = useStateS(false);
  const [addFolderOpen, setAddFolderOpen] = useStateS(false);
  const [moveOpen, setMoveOpen] = useStateS(false);
  const [tagOpen, setTagOpen] = useStateS(false);
  const [exportOpen, setExportOpen] = useStateS(false);
  const [importOpen, setImportOpen] = useStateS(false);
  const [confirmDel, setConfirmDel] = useStateS(null); // { kind: bookmark|folder|bulk, ids/id, label }

  const [toastNode, toast] = useToast();
  const menuRef = useRefS(null);
  const searchRef = useRefS(null);

  // Close menu on outside
  useEffectS(() => {
    if (!menuOpen) return;
    const onClick = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false); };
    document.addEventListener("mousedown", onClick);
    return () => document.removeEventListener("mousedown", onClick);
  }, [menuOpen]);

  // Cmd/Ctrl+K -> focus search
  useEffectS(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        searchRef.current?.focus();
      }
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, []);

  // Persist favorites to localStorage
  useEffectS(() => {
    try {
      const key = user?.id ? `bm_fav_${user.id}` : 'bm_fav';
      localStorage.setItem(key, JSON.stringify([...favorites]));
    } catch {}
  }, [favorites]);

  const loadData = async () => {
    setLoading(true);
    setDataError(null);
    try {
      const [tree, tagsList] = await Promise.all([api.getTree(), api.getTags()]);
      setFolders(api.treeToFolders(tree));
      setBookmarks(api.treeToBookmarks(tree));
      setTags(tagsList.map(t => ({ name: t.name, count: t.count })));
    } catch (err) {
      setDataError(err.message || 'Failed to load data');
    } finally {
      setLoading(false);
    }
  };

  useEffectS(() => { loadData(); }, []);

  // Folder tree helpers
  const flatFolders = useMemoS(() => makeFlat(folders), [folders]);
  const folderById = useMemoS(() => {
    const m = new Map();
    const walk = (n) => { for (const f of n) { m.set(f.id, f); if (f.children) walk(f.children); } };
    walk(folders);
    return m;
  }, [folders]);

  const toggleExpanded = (id) => {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const selectFolder = (id) => {
    setSelectedFolder(id);
    setSelectedTag(null);
    setScope("folder");
    setSelectedIds(new Set());
    setPage(1);
    setSidebarOpen(false);
  };
  const selectTag = (name) => {
    if (selectedTag === name) {
      setSelectedTag(null); setScope("all");
    } else {
      setSelectedTag(name); setSelectedFolder(null); setScope("tag");
    }
    setSelectedIds(new Set());
    setPage(1);
    setSidebarOpen(false);
  };
  const selectScope = (s) => {
    setScope(s);
    setSelectedFolder(null);
    setSelectedTag(null);
    setSelectedIds(new Set());
    setPage(1);
    setSidebarOpen(false);
  };

  const clearAll = () => {
    setScope("all"); setSelectedFolder(null); setSelectedTag(null); setSearch("");
    setSelectedIds(new Set()); setPage(1);
  };

  // Filter bookmarks
  const filtered = useMemoS(() => {
    let list = bookmarks;
    if (scope === "favorites") list = list.filter((b) => favorites.has(b.id));
    else if (scope === "recent") {
      const sorted = [...list].sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt));
      list = sorted.slice(0, 10);
    } else if (scope === "folder" && selectedFolder) {
      // include nested
      const collect = (id, acc = new Set([id])) => {
        const f = folderById.get(id);
        if (f?.children) for (const c of f.children) { acc.add(c.id); collect(c.id, acc); }
        return acc;
      };
      const ids = collect(selectedFolder);
      list = list.filter((b) => ids.has(b.folderId));
    } else if (scope === "tag" && selectedTag) {
      list = list.filter((b) => b.tags?.includes(selectedTag));
    }
    if (search.trim()) {
      const q = search.trim().toLowerCase();
      list = list.filter((b) =>
        b.title.toLowerCase().includes(q) ||
        b.url.toLowerCase().includes(q) ||
        b.tags?.some((t) => t.toLowerCase().includes(q))
      );
    }
    return list;
  }, [bookmarks, scope, selectedFolder, selectedTag, search, favorites, folderById]);

  const total = filtered.length;
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
  const pageStart = (page - 1) * pageSize;
  const pageRows = filtered.slice(pageStart, pageStart + pageSize);
  useEffectS(() => { if (page > pageCount) setPage(1); }, [pageCount, page]);

  // CRUD — all mutations hit the API then update local state
  const addBookmark = async ({ title, url, tags: ts, folderId }) => {
    try {
      const data = await api.createBookmark({ title, url, parentId: folderId || null });
      if (data.duplicate) { toast("Already saved"); return; }
      if (ts?.length) {
        await api.addTagsToBookmark(data.id, ts);
      }
      const nb = {
        id: data.id, title: data.title, url: data.url || '',
        tags: ts || [], folderId: data.parentId || null,
        updatedAt: data.updatedAt || new Date().toISOString(), notes: data.notes || null,
      };
      setBookmarks((bs) => [nb, ...bs]);
      if (ts?.length) {
        try { const tl = await api.getTags(); setTags(tl.map(t => ({ name: t.name, count: t.count }))); } catch {}
      }
      toast("Bookmark added");
    } catch (err) {
      toast(err.message || "Failed to add");
    }
  };
  const updateBookmark = async (id, patch) => {
    try {
      await api.updateBookmark(id, patch);
      setBookmarks((bs) => bs.map((b) => b.id === id ? { ...b, ...patch, updatedAt: new Date().toISOString() } : b));
    } catch (err) {
      toast(err.message || "Failed to update");
    }
  };
  const deleteBookmark = async (id) => {
    try {
      await api.deleteBookmark(id);
      setBookmarks((bs) => bs.filter((b) => b.id !== id));
      setSelectedIds((s) => { const n = new Set(s); n.delete(id); return n; });
      toast("Deleted");
    } catch (err) {
      toast(err.message || "Failed to delete");
    }
  };
  const deleteBookmarks = async (ids) => {
    try {
      await Promise.all([...ids].map((id) => api.deleteBookmark(id)));
      setBookmarks((bs) => bs.filter((b) => !ids.has(b.id)));
      setSelectedIds(new Set());
      toast(`Deleted ${ids.size} bookmark${ids.size === 1 ? "" : "s"}`);
    } catch (err) {
      toast(err.message || "Failed to delete");
    }
  };
  const moveBookmarks = async (ids, folderId) => {
    const parentId = folderId === "__root__" ? null : folderId;
    try {
      await Promise.all([...ids].map((id) => api.updateBookmark(id, { parentId })));
      setBookmarks((bs) => bs.map((b) => ids.has(b.id) ? { ...b, folderId: parentId, updatedAt: new Date().toISOString() } : b));
      setSelectedIds(new Set());
      toast(`Moved ${ids.size} bookmark${ids.size === 1 ? "" : "s"}`);
    } catch (err) {
      toast(err.message || "Failed to move");
    }
  };
  const tagBookmarks = async (ids, newTags) => {
    try {
      await Promise.all([...ids].map((id) => {
        const bm = bookmarks.find((b) => b.id === id);
        const cur = bm?.tags || [];
        return api.syncBookmarkTags(id, cur, [...new Set([...cur, ...newTags])]);
      }));
      setBookmarks((bs) => bs.map((b) => {
        if (!ids.has(b.id)) return b;
        const merged = Array.from(new Set([...(b.tags || []), ...newTags]));
        return { ...b, tags: merged, updatedAt: new Date().toISOString() };
      }));
      try { const tl = await api.getTags(); setTags(tl.map(t => ({ name: t.name, count: t.count }))); } catch {}
      setSelectedIds(new Set());
      toast(`Tagged ${ids.size} bookmark${ids.size === 1 ? "" : "s"}`);
    } catch (err) {
      toast(err.message || "Failed to tag");
    }
  };
  const addFolder = async (title, parentId) => {
    try {
      const data = await api.createBookmark({ title, isFolder: true, parentId: parentId || null });
      const newNode = { id: data.id, title: data.title, children: [] };
      if (!parentId) {
        setFolders((fs) => [...fs, newNode]);
      } else {
        const add = (nodes) => nodes.map((n) =>
          n.id === parentId
            ? { ...n, children: [...(n.children || []), newNode] }
            : { ...n, children: n.children ? add(n.children) : [] }
        );
        setFolders((fs) => add(fs));
        setExpanded((prev) => new Set([...prev, parentId]));
      }
      toast("Folder created");
    } catch (err) {
      toast(err.message || "Failed to create folder");
    }
  };
  const deleteFolder = async (id) => {
    try {
      await api.deleteBookmark(id, true);
      if (selectedFolder === id) selectScope("all");
      const drop = (nodes) => nodes.filter((n) => n.id !== id).map((n) => ({ ...n, children: drop(n.children || []) }));
      setFolders((fs) => drop(fs));
      // Quietly refresh to remove cascade-deleted bookmarks
      try {
        const tree = await api.getTree();
        setFolders(api.treeToFolders(tree));
        setBookmarks(api.treeToBookmarks(tree));
      } catch {}
      toast("Folder deleted");
    } catch (err) {
      toast(err.message || "Failed to delete folder");
    }
  };
  const toggleFavorite = (id) => {
    setFavorites((s) => { const n = new Set(s); if (n.has(id)) n.delete(id); else n.add(id); return n; });
  };

  // Selection
  const toggleSelect = (id) => {
    setSelectedIds((s) => { const n = new Set(s); if (n.has(id)) n.delete(id); else n.add(id); return n; });
  };
  const allSelected = pageRows.length > 0 && pageRows.every((b) => selectedIds.has(b.id));
  const someSelected = !allSelected && pageRows.some((b) => selectedIds.has(b.id));
  const toggleSelectAll = () => {
    setSelectedIds((s) => {
      if (allSelected) {
        const n = new Set(s);
        for (const b of pageRows) n.delete(b.id);
        return n;
      } else {
        const n = new Set(s);
        for (const b of pageRows) n.add(b.id);
        return n;
      }
    });
  };

  const titleEl = (() => {
    if (scope === "folder" && selectedFolder) {
      const f = folderById.get(selectedFolder);
      return <><I.Folder size={20} /> {f?.title || "Folder"}</>;
    }
    if (scope === "tag" && selectedTag) return <><I.Tag size={18} /> <span className="mono" style={{ fontSize: 24 }}>#{selectedTag}</span></>;
    if (scope === "favorites") return <><I.Star size={20} /> <span>Favorites</span></>;
    if (scope === "recent") return <><I.Clock size={20} /> <span>Recent</span></>;
    return <><span>All</span> <span className="accent">bookmarks</span></>;
  })();

  const eyebrowEl = (() => {
    if (search) return `Searching for "${search}"`;
    if (scope === "tag") return "Tag";
    if (scope === "folder") return "Folder";
    if (scope === "favorites") return "Collection";
    if (scope === "recent") return "Recently added";
    return "Library";
  })();

  return (
    <div className="sp" data-density="regular">
      <style>{__SPACES_CSS}</style>
      {toastNode}

      {/* ── Top bar ── */}
      <header className="sp-top">
        <div className="sp-top-left">
          <button className="sp-side-toggle" onClick={() => {
            if (window.innerWidth <= 760) setSidebarOpen((o) => !o);
            else setSidebarCollapsed((c) => !c);
          }} title="Toggle sidebar">
            <I.Menu size={18} />
          </button>
          <a href="/" onClick={(e) => { e.preventDefault(); navigate("/"); }}>
            <Brand />
          </a>
        </div>

        <div className="sp-search-wrap">
          <span className="sp-search-icon"><I.Search size={15} /></span>
          <input ref={searchRef}
                 className="sp-search-input"
                 placeholder="Search bookmarks, URLs, tags…"
                 value={search}
                 onChange={(e) => { setSearch(e.target.value); setPage(1); }} />
          {search ? (
            <button className="sp-search-clear" onClick={() => setSearch("")} title="Clear">
              <I.Close size={13} />
            </button>
          ) : (
            <div className="sp-search-kbd"><kbd>⌘</kbd><kbd>K</kbd></div>
          )}
        </div>

        <div className="sp-top-right">
          <div className="sp-view-toggle">
            <button className={view === "list" ? "active" : ""} title="List view" onClick={() => setView("list")}>
              <I.List size={14} />
            </button>
            <button className={view === "grid" ? "active" : ""} title="Grid view" onClick={() => setView("grid")}>
              <I.Grid size={14} />
            </button>
          </div>
          <div ref={menuRef} style={{ position: "relative" }}>
            <button className="sp-avatar" onClick={() => setMenuOpen((o) => !o)} title="Account">
              {user?.picture ? <img src={user.picture} alt="" /> : (user?.name || user?.email || "?").charAt(0).toUpperCase()}
            </button>
            {menuOpen && (
              <div className="sp-menu" role="menu">
                <div className="sp-menu-h">
                  <div className="name">{user?.name || "Guest"}</div>
                  <div className="email">{user?.email}</div>
                </div>
                <button className="sp-menu-item" onClick={() => { setMenuOpen(false); navigate("/account"); }}>
                  <I.Settings size={14} /> Account settings
                </button>
                <button className="sp-menu-item" onClick={() => { setMenuOpen(false); navigate("/privacy"); }}>
                  <I.Lock size={14} /> Privacy
                </button>
                <div className="sp-menu-sep" />
                <button className="sp-menu-item danger" onClick={() => { setMenuOpen(false); onLogout(); }}>
                  <I.Logout size={14} /> Sign out
                </button>
              </div>
            )}
          </div>
        </div>
      </header>

      {/* ── Body ── */}
      <div className={`sp-body ${sidebarOpen ? "mobile-open" : ""} ${sidebarCollapsed ? "collapsed" : ""}`}>
        <div className="sp-mobile-backdrop" onClick={() => setSidebarOpen(false)} />

        {/* ── Sidebar ── */}
        <aside className="sp-side">
          {(scope !== "all" || search) && (
            <button className="sp-side-clear" onClick={clearAll}>
              <I.Close size={12} />
              Clear filters
            </button>
          )}

          <div className="sp-side-section">
            <div className="sp-side-h"><span className="sp-side-h-label">Library</span></div>
            <button className={`sp-quick ${scope === "all" ? "active" : ""}`} onClick={() => selectScope("all")}>
              <I.Folder size={15} /> All bookmarks
              <span className="sp-quick-count">{bookmarks.length}</span>
            </button>
            <button className={`sp-quick ${scope === "favorites" ? "active" : ""}`} onClick={() => selectScope("favorites")}>
              <I.Star size={15} /> Favorites
              <span className="sp-quick-count">{favorites.size}</span>
            </button>
            <button className={`sp-quick ${scope === "recent" ? "active" : ""}`} onClick={() => selectScope("recent")}>
              <I.Clock size={15} /> Recent
              <span className="sp-quick-count">10</span>
            </button>
          </div>

          <div className="sp-side-section">
            <div className="sp-side-h">
              <span className="sp-side-h-label">Folders</span>
              <button className="sp-side-h-add" title="New folder"
                      onClick={() => setAddFolderOpen({ parentId: null })}>
                <I.Plus size={12} />
              </button>
            </div>
            {folders.length === 0 ? (
              <div className="sp-side-empty">No folders yet</div>
            ) : (
              <div className="sp-tree">
                {folders.map((n) => (
                  <FolderNode key={n.id} node={n} depth={0}
                              expanded={expanded}
                              selected={scope === "folder" ? selectedFolder : null}
                              onSelect={selectFolder}
                              onToggle={toggleExpanded}
                              onAdd={(parentId) => setAddFolderOpen({ parentId })}
                              onDelete={(node) => setConfirmDel({ kind: "folder", id: node.id, label: node.title })} />
                ))}
              </div>
            )}
          </div>

          <div className="sp-side-section">
            <div className="sp-side-h"><span className="sp-side-h-label">Tags</span></div>
            <div className="sp-tags-wrap">
              {tags.length === 0 ? (
                <div className="sp-side-empty" style={{ width: "100%" }}>No tags yet</div>
              ) : tags.map((t) => (
                <span key={t.name} className={`chip ${selectedTag === t.name ? "active" : ""}`}
                      onClick={() => selectTag(t.name)}>
                  {t.name}<span className="chip-count">{t.count}</span>
                </span>
              ))}
            </div>
          </div>
        </aside>

        {/* ── Content ── */}
        <main className="sp-content">
          <div className="sp-c-hd">
            <div className="sp-c-hd-left">
              <div className="sp-c-eyebrow">{eyebrowEl}</div>
              <h1 className="sp-c-title">{titleEl}</h1>
              <div className="sp-c-count-row">
                <span className="sp-c-count">{total} item{total === 1 ? "" : "s"}</span>
              </div>
            </div>
            <div className="sp-c-actions">
              <button className="btn btn-ghost btn-sm" onClick={() => setImportOpen(true)}>
                <I.Import size={14} /> <span className="hide-sm">Import</span>
              </button>
              <button className="btn btn-ghost btn-sm" onClick={() => setExportOpen(true)}>
                <I.Export size={14} /> <span className="hide-sm">Export</span>
              </button>
              <button className="btn btn-accent btn-sm" onClick={() => setAddBmOpen(true)}>
                <I.Plus size={14} /> Add bookmark
              </button>
            </div>
          </div>

          {/* Bulk toolbar */}
          {selectedIds.size > 0 && (
            <div className="sp-bulk">
              <span className="sp-bulk-label">
                {selectedIds.size} selected
              </span>
              <div className="sp-bulk-actions">
                <button className="btn btn-ghost btn-sm" onClick={() => setMoveOpen(true)}>
                  <I.Move size={13} /> Move
                </button>
                <button className="btn btn-ghost btn-sm" onClick={() => setTagOpen(true)}>
                  <I.Tag size={13} /> Add tags
                </button>
                <button className="btn btn-ghost btn-sm" style={{ color: "var(--danger)" }}
                        onClick={() => setConfirmDel({ kind: "bulk", ids: new Set(selectedIds), label: `${selectedIds.size} bookmark${selectedIds.size === 1 ? "" : "s"}` })}>
                  <I.Trash size={13} /> Delete
                </button>
                <button className="btn btn-ghost btn-sm" onClick={() => setSelectedIds(new Set())}>
                  Clear
                </button>
              </div>
            </div>
          )}

          {/* Loading / error / empty / content */}
          {loading ? (
            <div className="sp-empty">
              <div className="sp-empty-art"><I.Clock size={32} /></div>
              <p className="sp-empty-p">Loading your library…</p>
            </div>
          ) : dataError ? (
            <div className="sp-empty">
              <div className="sp-empty-art" style={{ background: "color-mix(in oklab, var(--danger) 15%, transparent)", color: "var(--danger)" }}><I.Close size={32} /></div>
              <h2 className="sp-empty-h">Failed to <span className="accent">load.</span></h2>
              <p className="sp-empty-p">{dataError}</p>
              <button className="btn btn-outline" onClick={loadData}>Try again</button>
            </div>
          ) : total === 0 ? (
            <div className="sp-empty">
              <div className="sp-empty-art"><I.Folder size={32} /></div>
              <h2 className="sp-empty-h">{search ? "Nothing matches" : "Nothing here yet"} <span className="accent">{search ? "your search." : "—"}</span></h2>
              <p className="sp-empty-p">
                {search
                  ? "Try a different word, or clear the search to see everything."
                  : "Save your first bookmark and it'll appear right here."}
              </p>
              {search ? (
                <button className="btn btn-outline" onClick={() => setSearch("")}>Clear search</button>
              ) : (
                <button className="btn btn-accent" onClick={() => setAddBmOpen(true)}>
                  <I.Plus size={14} /> Add a bookmark
                </button>
              )}
            </div>
          ) : view === "list" ? (
            <div className="sp-list-wrap">
              <div className="sp-list-head">
                <Check checked={allSelected} indeterminate={someSelected} onChange={toggleSelectAll} />
                <span>Title</span>
                <span className="sp-col-tags">Tags</span>
                <span className="sp-col-date">Updated</span>
                <span></span>
              </div>
              {pageRows.map((b) => {
                const isFav = favorites.has(b.id);
                const sel = selectedIds.has(b.id);
                const host = hostOf(b.url);
                return (
                  <div key={b.id} className={`sp-list-row ${sel ? "selected" : ""}`}
                       onClick={() => window.open(b.url, "_blank")}>
                    <Check checked={sel} onChange={() => toggleSelect(b.id)} />
                    <div className="sp-row-title-cell">
                      <div className="sp-row-fav">
                        <span className="sp-row-fav-fallback">{(b.title[0] || "?").toUpperCase()}</span>
                      </div>
                      <div className="sp-row-title-stack">
                        <div className="sp-row-title">
                          {isFav && <span style={{ color: "var(--accent)", marginRight: 6 }}>★</span>}
                          {b.title}
                        </div>
                        <div className="sp-row-host">{host}</div>
                      </div>
                    </div>
                    <div className="sp-row-tags sp-col-tags">
                      {(b.tags || []).slice(0, 3).map((t) => (
                        <span key={t} className="sp-row-tag" onClick={(e) => { e.stopPropagation(); selectTag(t); }}>{t}</span>
                      ))}
                      {(b.tags || []).length > 3 && <span className="sp-row-tag">+{b.tags.length - 3}</span>}
                    </div>
                    <span className="sp-row-date sp-col-date">{relTime(b.updatedAt)}</span>
                    <div style={{ display: "flex", gap: 2, justifyContent: "flex-end" }}>
                      <button className="sp-row-action" title={isFav ? "Unfavorite" : "Favorite"}
                              onClick={(e) => { e.stopPropagation(); toggleFavorite(b.id); }}
                              style={{ color: isFav ? "var(--accent)" : undefined }}>
                        <I.Star size={14} />
                      </button>
                      <button className="sp-row-action" title="Delete"
                              onClick={(e) => { e.stopPropagation(); setConfirmDel({ kind: "bookmark", id: b.id, label: b.title }); }}>
                        <I.Trash size={14} />
                      </button>
                    </div>
                  </div>
                );
              })}
            </div>
          ) : (
            <div className="sp-grid">
              {pageRows.map((b) => {
                const sel = selectedIds.has(b.id);
                const isFav = favorites.has(b.id);
                return (
                  <div key={b.id} className={`sp-card ${sel ? "selected" : ""}`}
                       onClick={() => window.open(b.url, "_blank")}>
                    <div className="sp-card-check" onClick={(e) => e.stopPropagation()}>
                      <Check checked={sel} onChange={() => toggleSelect(b.id)} />
                    </div>
                    <div className="sp-card-thumb">
                      <div className="sp-card-fav">
                        <span className="sp-card-fav-fallback">{(b.title[0] || "?").toUpperCase()}</span>
                      </div>
                    </div>
                    <div className="sp-card-body">
                      <div className="sp-card-title">
                        {isFav && <span style={{ color: "var(--accent)", marginRight: 4 }}>★</span>}
                        {b.title}
                      </div>
                      <div className="sp-card-host">{hostOf(b.url)}</div>
                      <div className="sp-card-tags">
                        {(b.tags || []).slice(0, 3).map((t) => (
                          <span key={t} className="sp-row-tag" onClick={(e) => { e.stopPropagation(); selectTag(t); }}>{t}</span>
                        ))}
                      </div>
                    </div>
                    <div className="sp-card-foot">
                      <span>{relTime(b.updatedAt)}</span>
                      <div style={{ display: "flex", gap: 4 }}>
                        <button className="btn btn-ghost btn-sm btn-icon" style={{ height: 24, width: 24, color: isFav ? "var(--accent)" : "var(--muted)" }}
                                onClick={(e) => { e.stopPropagation(); toggleFavorite(b.id); }}>
                          <I.Star size={13} />
                        </button>
                        <button className="btn btn-ghost btn-sm btn-icon" style={{ height: 24, width: 24, color: "var(--muted)" }}
                                onClick={(e) => { e.stopPropagation(); window.open(b.url, "_blank"); }}>
                          <I.External size={12} />
                        </button>
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>
          )}

          {total > 0 && (
            <div className="sp-pager">
              <span>Showing <span className="mono">{pageStart + 1}–{Math.min(pageStart + pageSize, total)}</span> of <span className="mono">{total}</span></span>
              <div className="sp-pager-nums">
                <button className="sp-pager-btn" disabled={page === 1} onClick={() => setPage((p) => p - 1)}>
                  <I.ArrowL size={12} />
                </button>
                {Array.from({ length: pageCount }, (_, i) => i + 1).slice(0, 5).map((p) => (
                  <button key={p} className={`sp-pager-btn ${p === page ? "active" : ""}`} onClick={() => setPage(p)}>
                    {p}
                  </button>
                ))}
                <button className="sp-pager-btn" disabled={page === pageCount} onClick={() => setPage((p) => p + 1)}>
                  <I.ArrowR size={12} />
                </button>
              </div>
            </div>
          )}
        </main>
      </div>

      {/* ── Modals ── */}
      <AddBookmarkModal open={addBmOpen} onClose={() => setAddBmOpen(false)}
                        onSubmit={(v) => { addBookmark({ ...v, folderId: scope === "folder" ? selectedFolder : null }); setAddBmOpen(false); }}
                        allTags={tags} />
      <AddFolderModal open={!!addFolderOpen}
                      defaultParent={addFolderOpen?.parentId || null}
                      folders={folders}
                      onClose={() => setAddFolderOpen(false)}
                      onSubmit={({ title, parentId }) => { addFolder(title, parentId === "__root__" ? null : parentId); setAddFolderOpen(false); }} />
      <MoveModal open={moveOpen} onClose={() => setMoveOpen(false)} folders={folders}
                 count={selectedIds.size}
                 onSubmit={(parentId) => { moveBookmarks(selectedIds, parentId === "__root__" ? null : parentId); setMoveOpen(false); }} />
      <TagModal open={tagOpen} onClose={() => setTagOpen(false)} allTags={tags}
                count={selectedIds.size}
                onSubmit={(tagList) => { tagBookmarks(selectedIds, tagList); setTagOpen(false); }} />
      <ExportModal open={exportOpen} onClose={() => setExportOpen(false)}
                   onExport={async (fmt) => {
                     setExportOpen(false);
                     try { await api.triggerExport(fmt); toast(`Exported as ${fmt.toUpperCase()}`); }
                     catch (err) { toast(err.message || 'Export failed'); }
                   }} />
      <ImportModal open={importOpen} onClose={() => setImportOpen(false)}
                   onImport={async (fmt, file) => {
                     setImportOpen(false);
                     try {
                       const result = await api.importFile(fmt, file);
                       toast(`Imported ${result.imported} bookmark${result.imported === 1 ? '' : 's'}`);
                       await loadData();
                     } catch (err) { toast(err.message || 'Import failed'); }
                   }} />
      <ConfirmModal data={confirmDel} onClose={() => setConfirmDel(null)}
                    onConfirm={() => {
                      if (confirmDel.kind === "bookmark") deleteBookmark(confirmDel.id);
                      else if (confirmDel.kind === "folder") deleteFolder(confirmDel.id);
                      else if (confirmDel.kind === "bulk") deleteBookmarks(confirmDel.ids);
                      setConfirmDel(null);
                    }} />
    </div>
  );
}

// ──── Modals ────

function AddBookmarkModal({ open, onClose, onSubmit, allTags }) {
  const [title, setTitle] = useStateS("");
  const [url, setUrl] = useStateS("");
  const [tags, setTags] = useStateS([]);
  useEffectS(() => { if (open) { setTitle(""); setUrl(""); setTags([]); } }, [open]);
  return (
    <Modal open={open} onClose={onClose} title="Add bookmark"
           subtitle="Save a link to your library"
           footer={
             <>
               <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
               <button className="btn btn-accent" onClick={() => { if (!title.trim()) return; onSubmit({ title, url, tags }); }}>
                 Add bookmark
               </button>
             </>
           }>
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        <div>
          <label className="field-label">Title</label>
          <input className="input" placeholder="Bookmark title" value={title} onChange={(e) => setTitle(e.target.value)} autoFocus />
        </div>
        <div>
          <label className="field-label">URL</label>
          <input className="input" placeholder="https://example.com" value={url} onChange={(e) => setUrl(e.target.value)} />
        </div>
        <div>
          <label className="field-label">Tags</label>
          <TagPicker value={tags} onChange={setTags} allTags={allTags} />
        </div>
      </div>
    </Modal>
  );
}

function AddFolderModal({ open, onClose, onSubmit, defaultParent, folders }) {
  const [title, setTitle] = useStateS("");
  const [parentId, setParentId] = useStateS("__root__");
  useEffectS(() => { if (open) { setTitle(""); setParentId(defaultParent || "__root__"); } }, [open, defaultParent]);
  return (
    <Modal open={open} onClose={onClose} title="New folder"
           subtitle="Group bookmarks together"
           footer={
             <>
               <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
               <button className="btn btn-accent" onClick={() => { if (!title.trim()) return; onSubmit({ title, parentId }); }}>
                 Create folder
               </button>
             </>
           }>
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        <div>
          <label className="field-label">Folder name</label>
          <input className="input" placeholder="e.g. Side projects" value={title} onChange={(e) => setTitle(e.target.value)} autoFocus />
        </div>
        <div>
          <label className="field-label">Parent</label>
          <FolderSelect value={parentId} onChange={setParentId} folders={folders} />
        </div>
      </div>
    </Modal>
  );
}

function MoveModal({ open, onClose, onSubmit, folders, count }) {
  const [parentId, setParentId] = useStateS("__root__");
  useEffectS(() => { if (open) setParentId("__root__"); }, [open]);
  return (
    <Modal open={open} onClose={onClose} title="Move bookmarks"
           subtitle={`Move ${count} bookmark${count === 1 ? "" : "s"} to another folder`}
           footer={
             <>
               <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
               <button className="btn btn-accent" onClick={() => onSubmit(parentId)}>Move</button>
             </>
           }>
      <label className="field-label">Destination</label>
      <FolderSelect value={parentId} onChange={setParentId} folders={folders} />
    </Modal>
  );
}

function TagModal({ open, onClose, onSubmit, count, allTags }) {
  const [tags, setTags] = useStateS([]);
  useEffectS(() => { if (open) setTags([]); }, [open]);
  return (
    <Modal open={open} onClose={onClose} title="Add tags"
           subtitle={`Add tags to ${count} bookmark${count === 1 ? "" : "s"}`}
           footer={
             <>
               <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
               <button className="btn btn-accent" onClick={() => { if (tags.length) onSubmit(tags); }}>
                 Add tags
               </button>
             </>
           }>
      <label className="field-label">Tags</label>
      <TagPicker value={tags} onChange={setTags} allTags={allTags} />
    </Modal>
  );
}

function ExportModal({ open, onClose, onExport }) {
  return (
    <Modal open={open} onClose={onClose} title="Export library"
           subtitle="Take your bookmarks anywhere">
      <div style={{ display: "flex", gap: 12 }}>
        <button className="btn btn-outline btn-lg" style={{ flex: 1, flexDirection: "column", height: 96 }}
                onClick={() => onExport("html")}>
          <div style={{ fontSize: 20 }}><I.Globe size={20} /></div>
          <div style={{ fontWeight: 600 }}>HTML</div>
          <div style={{ fontSize: 11, color: "var(--muted)" }}>Standard format</div>
        </button>
        <button className="btn btn-outline btn-lg" style={{ flex: 1, flexDirection: "column", height: 96 }}
                onClick={() => onExport("json")}>
          <div style={{ fontSize: 20, fontFamily: "var(--font-mono)" }}>{`{ }`}</div>
          <div style={{ fontWeight: 600 }}>JSON</div>
          <div style={{ fontSize: 11, color: "var(--muted)" }}>Structured data</div>
        </button>
      </div>
    </Modal>
  );
}

// ── Safari logo (only used here) ──
function SafariLogo({ size = 26 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <linearGradient id="sf-ring" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#E8EEF4"/>
          <stop offset="100%" stopColor="#B6C2CC"/>
        </linearGradient>
        <radialGradient id="sf-face" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#7DD3FC"/>
          <stop offset="100%" stopColor="#1D6FB8"/>
        </radialGradient>
      </defs>
      <circle cx="24" cy="24" r="22" fill="url(#sf-ring)"/>
      <circle cx="24" cy="24" r="18.5" fill="url(#sf-face)"/>
      <path d="M24 8v3M24 37v3M8 24h3M37 24h3" stroke="white" strokeWidth="1.5" strokeLinecap="round"/>
      <path d="m30.8 17.2-9.2 4.6-4.4 9 9.2-4.6 4.4-9Z" fill="#fff"/>
      <path d="m30.8 17.2-9.2 4.6 4.8 4.4 4.4-9Z" fill="#EF4444"/>
      <circle cx="24" cy="24" r="1.6" fill="#fff"/>
    </svg>
  );
}

const IMPORT_SOURCES = [
  { id: "chrome",  name: "Chrome",   logo: BrowserLogo.Chrome,  hint: "Bookmarks → Export bookmarks (Cmd-Shift-O)", file: "HTML" },
  { id: "firefox", name: "Firefox",  logo: BrowserLogo.Firefox, hint: "Library → Import and Backup → Export", file: "HTML / JSON" },
  { id: "edge",    name: "Edge",     logo: BrowserLogo.Edge,    hint: "Favorites → ··· → Export favorites", file: "HTML" },
  { id: "safari",  name: "Safari",   logo: SafariLogo,          hint: "File → Export → Bookmarks", file: "HTML" },
];

function ImportModal({ open, onClose, onImport }) {
  const [picking, setPicking] = useStateS(null); // source id while file picker is open
  const fileRef = useRefS(null);

  const startPick = (source) => {
    setPicking(source);
    // Programmatically trigger file picker on next tick so React has set state
    setTimeout(() => fileRef.current?.click(), 0);
  };

  const onFile = (e) => {
    const file = e.target.files?.[0];
    e.target.value = ""; // reset so same file can be re-picked
    if (!file || !picking) return;
    const fmt = file.name.endsWith('.json') ? 'json' : 'html';
    onImport(fmt, file);
    setPicking(null);
  };

  return (
    <Modal open={open} onClose={onClose} title="Import bookmarks"
           subtitle="Bring your existing library in from another browser. We'll preserve folders."
           width={520}>
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {IMPORT_SOURCES.map((s) => {
          const Logo = s.logo;
          return (
            <button key={s.id} className="sp-import-row"
                    onClick={() => startPick(s)}>
              <div className="sp-import-logo"><Logo size={26} /></div>
              <div className="sp-import-text">
                <div className="sp-import-name">{s.name}</div>
                <div className="sp-import-hint">{s.hint}</div>
              </div>
              <div className="sp-import-meta">
                <span className="sp-import-format">{s.file}</span>
                <I.ArrowR size={14} />
              </div>
            </button>
          );
        })}
      </div>

      <div style={{
        marginTop: 16, padding: "12px 14px",
        border: "1px dashed var(--hairline)",
        borderRadius: "var(--r-sm)",
        background: "color-mix(in oklab, var(--surface-2) 60%, transparent)",
        fontSize: 12, color: "var(--muted)",
        display: "flex", alignItems: "center", gap: 10,
      }}>
        <I.Import size={14} />
        <div style={{ flex: 1 }}>
          Have a file already? Drag an <span className="mono">.html</span> or <span className="mono">.json</span> onto this window, or
          {" "}<button className="lg-link" style={{ background: "transparent", border: 0, padding: 0, color: "var(--accent)", cursor: "pointer", textDecoration: "underline", textUnderlineOffset: 3 }}
                  onClick={() => startPick({ id: "file", name: "file", file: "HTML / JSON" })}>browse</button>.
        </div>
      </div>

      <input ref={fileRef} type="file" accept=".html,.json,application/json,text/html"
             style={{ display: "none" }} onChange={onFile} />
    </Modal>
  );
}


function ConfirmModal({ data, onClose, onConfirm }) {
  if (!data) return null;
  return (
    <Modal open={!!data} onClose={onClose}
           title={data.kind === "folder" ? "Delete folder?" : "Delete bookmark" + (data.kind === "bulk" ? "s?" : "?")}
           subtitle={data.kind === "folder"
             ? `Delete "${data.label}" and everything inside it. This can't be undone.`
             : `Delete ${data.label}. This can't be undone.`}
           footer={
             <>
               <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
               <button className="btn btn-accent" style={{ background: "var(--danger)", borderColor: "var(--danger)" }} onClick={onConfirm}>
                 Delete
               </button>
             </>
           }>
      <div style={{ padding: 16, background: "var(--surface-2)", borderRadius: "var(--r-sm)", fontSize: 13, color: "var(--ink-2)" }}>
        {data.kind === "folder" && <><I.Folder size={14} style={{ verticalAlign: "middle", marginRight: 6 }} /> {data.label}</>}
        {data.kind === "bookmark" && <>📖 {data.label}</>}
        {data.kind === "bulk" && <>This will delete <strong>{data.label}</strong>.</>}
      </div>
    </Modal>
  );
}

window.SpacesPage = SpacesPage;
