// cards-panel.jsx — Full-page CardsScreen, MyMind-style masonry grid.
// Exports window.CardsScreen.

const { useState: _qp_us, useEffect: _qp_ue, useRef: _qp_ur } = React;

const CARD_TYPES = [
  { type: "note", label: "Note", color: "var(--fg-2)" },
  { type: "image", label: "Image", color: "var(--c-pattern)" },
  { type: "quote", label: "Quote", color: "var(--c-perspective)" },
  { type: "link", label: "Link", color: "var(--c-function)" },
  { type: "video", label: "Video", color: "var(--c-essence)" },
];

const CARD_LEVEL_COLOR = {
  consist: "var(--c-consist)",
  function: "var(--c-function)",
  perspective: "var(--c-perspective)",
  pattern: "var(--c-pattern)",
  essence: "var(--c-essence)",
};

const CardIcons = {
  Note: (s = 14) => (
    <svg width={s} height={s} viewBox="0 0 32 32" fill="none">
      <path d="M17.3333 26.6667L26.6666 17.3334" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      <path d="M17.3333 26.6667V18.6667C17.3333 18.3131 17.4737 17.9739 17.7238 17.7239C17.9738 17.4739 18.313 17.3334 18.6666 17.3334H26.6666V8.00004C26.6666 7.2928 26.3856 6.61452 25.8855 6.11442C25.3854 5.61433 24.7072 5.33337 23.9999 5.33337H7.99992C7.29267 5.33337 6.6144 5.61433 6.1143 6.11442C5.6142 6.61452 5.33325 7.2928 5.33325 8.00004V24C5.33325 24.7073 5.6142 25.3856 6.1143 25.8857C6.6144 26.3858 7.29267 26.6667 7.99992 26.6667H17.3333Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  ),
  Image: (s = 14) => (
    <svg width={s} height={s} viewBox="0 0 32 32" fill="none">
      <path d="M20 10.6666H20.0133" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      <path d="M4 8C4 6.93913 4.42143 5.92172 5.17157 5.17157C5.92172 4.42143 6.93913 4 8 4H24C25.0609 4 26.0783 4.42143 26.8284 5.17157C27.5786 5.92172 28 6.93913 28 8V24C28 25.0609 27.5786 26.0783 26.8284 26.8284C26.0783 27.5786 25.0609 28 24 28H8C6.93913 28 5.92172 27.5786 5.17157 26.8284C4.42143 26.0783 4 25.0609 4 24V8Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      <path d="M4 21.3333L10.6667 14.6667C11.904 13.476 13.4293 13.476 14.6667 14.6667L21.3333 21.3333" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
      <path d="M18.6667 18.6666L20.0001 17.3333C21.2374 16.1426 22.7627 16.1426 24.0001 17.3333L28.0001 21.3333" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  ),
  Quote: (s = 14) => (
    <svg width={s} height={s} viewBox="0 0 32 32" fill="none">
      <path d="M12 13C12 12.7348 12.1054 12.4804 12.2929 12.2929C12.4804 12.1054 12.7348 12 13 12H21C21.2652 12 21.5196 12.1054 21.7071 12.2929C21.8946 12.4804 22 12.7348 22 13C22 13.2652 21.8946 13.5196 21.7071 13.7071C21.5196 13.8946 21.2652 14 21 14H13C12.7348 14 12.4804 13.8946 12.2929 13.7071C12.1054 13.5196 12 13.2652 12 13ZM13 18H21C21.2652 18 21.5196 17.8946 21.7071 17.7071C21.8946 17.5196 22 17.2652 22 17C22 16.7348 21.8946 16.4804 21.7071 16.2929C21.5196 16.1054 21.2652 16 21 16H13C12.7348 16 12.4804 16.1054 12.2929 16.2929C12.1054 16.4804 12 16.7348 12 17C12 17.2652 12.1054 17.5196 12.2929 17.7071C12.4804 17.8946 12.7348 18 13 18ZM29 24C29 25.0609 28.5786 26.0783 27.8284 26.8284C27.0783 27.5786 26.0609 28 25 28H11C9.93913 28 8.92172 27.5786 8.17157 26.8284C7.42143 26.0783 7 25.0609 7 24V8C7 7.46957 6.78929 6.96086 6.41421 6.58579C6.03914 6.21071 5.53043 6 5 6C4.46957 6 3.96086 6.21071 3.58579 6.58579C3.21071 6.96086 3 7.46957 3 8C3 8.7175 3.60375 9.2025 3.61 9.2075C3.7755 9.33486 3.89698 9.51084 3.95738 9.71075C4.01779 9.91066 4.01409 10.1245 3.94681 10.3222C3.87953 10.5199 3.75205 10.6915 3.58224 10.8131C3.41244 10.9347 3.20883 11 3 11C2.78375 11.0004 2.57337 10.9297 2.40125 10.7987C2.25625 10.6925 1 9.70125 1 8C1 6.93913 1.42143 5.92172 2.17157 5.17157C2.92172 4.42143 3.93913 4 5 4H22C23.0609 4 24.0783 4.42143 24.8284 5.17157C25.5786 5.92172 26 6.93913 26 8V21H27C27.2164 21 27.4269 21.0702 27.6 21.2C27.75 21.3075 29 22.2988 29 24ZM12.0325 21.685C12.1008 21.4833 12.2312 21.3084 12.405 21.1854C12.5788 21.0623 12.7871 20.9974 13 21H24V8C24 7.46957 23.7893 6.96086 23.4142 6.58579C23.0391 6.21071 22.5304 6 22 6H8.46125C8.81492 6.60713 9.00086 7.29737 9 8V24C9 24.5304 9.21071 25.0391 9.58579 25.4142C9.96086 25.7893 10.4696 26 11 26C11.5304 26 12.0391 25.7893 12.4142 25.4142C12.7893 25.0391 13 24.5304 13 24C13 23.2825 12.3963 22.7975 12.39 22.7925C12.2196 22.6706 12.0928 22.4972 12.0285 22.2977C11.9641 22.0983 11.9655 21.8835 12.0325 21.685ZM27 24C26.9875 23.6295 26.8445 23.2753 26.5963 23H14.8462C14.9468 23.3238 14.9978 23.661 14.9975 24C14.9985 24.7023 14.8134 25.3924 14.4613 26H25C25.5304 26 26.0391 25.7893 26.4142 25.4142C26.7893 25.0391 27 24.5304 27 24Z" fill="currentColor" />
    </svg>
  ),
  Link: (s = 14) => (
    <svg width={s} height={s} viewBox="0 0 32 32" fill="none">
      <path d="M17.1925 23.2951C17.3799 23.4826 17.4851 23.7368 17.4851 24.0019C17.4851 24.267 17.3799 24.5213 17.1925 24.7088L15.95 25.9588C14.636 27.272 12.8542 28.0094 10.9965 28.0088C9.13884 28.0082 7.3575 27.2697 6.04434 25.9557C4.73119 24.6417 3.9938 22.8599 3.99439 21.0022C3.99497 19.1446 4.73349 17.3632 6.04747 16.0501L9.06247 13.0351C10.3246 11.7719 12.0217 11.0389 13.8065 10.9859C15.5913 10.9329 17.3289 11.564 18.6637 12.7501C18.7622 12.8374 18.8425 12.9433 18.9001 13.0616C18.9577 13.18 18.9914 13.3086 18.9993 13.44C19.0072 13.5713 18.9891 13.703 18.9461 13.8274C18.9031 13.9518 18.836 14.0666 18.7487 14.1651C18.6614 14.2635 18.5555 14.3439 18.4371 14.4014C18.3188 14.459 18.1902 14.4927 18.0588 14.5006C17.9274 14.5085 17.7958 14.4904 17.6714 14.4475C17.5469 14.4045 17.4322 14.3374 17.3337 14.2501C16.3804 13.4034 15.1396 12.953 13.8651 12.9909C12.5907 13.0288 11.3789 13.5521 10.4775 14.4538L7.46247 17.4651C6.5246 18.4029 5.99771 19.675 5.99771 21.0013C5.99771 22.3277 6.5246 23.5997 7.46247 24.5376C8.40034 25.4754 9.67237 26.0023 10.9987 26.0023C12.3251 26.0023 13.5971 25.4754 14.535 24.5376L15.7775 23.2951C15.8703 23.2021 15.9806 23.1283 16.102 23.078C16.2234 23.0277 16.3536 23.0018 16.485 23.0018C16.6164 23.0018 16.7465 23.0277 16.8679 23.078C16.9893 23.1283 17.0996 23.2021 17.1925 23.2951ZM25.9525 6.04506C24.6383 4.73383 22.8577 3.99744 21.0012 3.99744C19.1448 3.99744 17.3641 4.73383 16.05 6.04506L14.8075 7.28881C14.62 7.47645 14.5147 7.73088 14.5149 7.99612C14.515 8.26137 14.6205 8.51571 14.8081 8.70318C14.9957 8.89066 15.2502 8.99591 15.5154 8.99579C15.7807 8.99568 16.035 8.8902 16.2225 8.70256L17.465 7.46006C18.4028 6.52218 19.6749 5.99529 21.0012 5.99529C22.3276 5.99529 23.5996 6.52218 24.5375 7.46006C25.4753 8.39793 26.0022 9.66995 26.0022 10.9963C26.0022 12.3227 25.4753 13.5947 24.5375 14.5326L21.5225 17.5501C20.6206 18.4513 19.4085 18.9739 18.134 19.0111C16.8596 19.0483 15.6191 18.5972 14.6662 17.7501C14.5677 17.6627 14.453 17.5957 14.3286 17.5527C14.2042 17.5097 14.0725 17.4916 13.9411 17.4995C13.6758 17.5154 13.4276 17.6361 13.2512 17.8351C13.1639 17.9335 13.0968 18.0483 13.0538 18.1727C13.0108 18.2971 12.9928 18.4288 13.0007 18.5602C13.0166 18.8255 13.1373 19.0737 13.3362 19.2501C14.6707 20.4364 16.4081 21.0679 18.1929 21.0154C19.9778 20.9629 21.675 20.2303 22.9375 18.9676L25.9525 15.9526C27.2645 14.6383 28.0013 12.8571 28.0013 11.0001C28.0013 9.143 27.2645 7.36183 25.9525 6.04756V6.04506Z" fill="currentColor" />
    </svg>
  ),
  Video: (s = 14) => (
    <svg width={s} height={s} viewBox="0 0 32 32" fill="none">
      <path d="M31.4713 9.125C31.3118 9.03953 31.1321 8.99892 30.9514 9.0075C30.7707 9.01609 30.5956 9.07354 30.445 9.17375L26 12.1313V9C26 8.46957 25.7893 7.96086 25.4142 7.58579C25.0391 7.21071 24.5304 7 24 7H4C3.46957 7 2.96086 7.21071 2.58579 7.58579C2.21071 7.96086 2 8.46957 2 9V23C2 23.5304 2.21071 24.0391 2.58579 24.4142C2.96086 24.7893 3.46957 25 4 25H24C24.5304 25 25.0391 24.7893 25.4142 24.4142C25.7893 24.0391 26 23.5304 26 23V19.875L30.445 22.8388C30.6101 22.946 30.8032 23.002 31 23C31.2652 23 31.5196 22.8946 31.7071 22.7071C31.8946 22.5196 32 22.2652 32 22V10C31.9987 9.82007 31.949 9.64382 31.8559 9.48982C31.7628 9.33582 31.63 9.20979 31.4713 9.125ZM24 23H4V9H24V23ZM30 20.1313L26 17.465V14.535L30 11.875V20.1313Z" fill="currentColor" />
    </svg>
  ),
  Upload: (s = 15) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
      <polyline points="17 8 12 3 7 8" />
      <line x1="12" y1="3" x2="12" y2="15" />
    </svg>
  ),
  Edit: (s = 12) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
      <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
      <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
    </svg>
  ),
  Trash: (s = 12) => (
    <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
      <path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12" />
    </svg>
  ),
};

function cardIcon(type, s) {
  const k = type.charAt(0).toUpperCase() + type.slice(1);
  return CardIcons[k]?.(s) || null;
}

// Shared field styles used in both Add and Edit forms
function fieldStyle() {
  return {
    width: "100%", display: "block", boxSizing: "border-box",
    background: "var(--bg-elev)", border: "none",
    borderRadius: 10, padding: "10px 14px",
    boxShadow: "0 1px 2px rgba(0,0,0,.04), 0 4px 20px -6px rgba(0,0,0,.08)",
    fontSize: 14, color: "var(--fg)", fontFamily: "var(--font-ui)",
    outline: "none", resize: "vertical",
    transition: "box-shadow 160ms",
  };
}
const LBL = {
  display: "block", marginBottom: 6,
  fontSize: 9.5, fontFamily: "var(--font-mono)", letterSpacing: ".12em",
  textTransform: "uppercase", color: "var(--fg-3)",
};

// ── CardInlineEdit — edits an existing card in-place ────────────

function CardInlineEdit({ card, onSave, onCancel }) {
  const isBase64 = (card.content || "").startsWith("data:");
  const [content, setContent] = _qp_us(card.content || "");
  const [source, setSource] = _qp_us(card.source || "");
  const [caption, setCaption] = _qp_us(card.caption || "");
  const [imageMode, setImageMode] = _qp_us(isBase64 ? "upload" : "url");
  const fileRef = _qp_ur(null);
  const type = card.type;

  function handleFileUpload(e) {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (evt) => setContent(evt.target.result);
    reader.readAsDataURL(file);
  }

  function toEmbedUrl(url) {
    const m = url.match(/(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/)|youtu\.be\/)([A-Za-z0-9_-]{11})/);
    return m ? `https://www.youtube.com/embed/${m[1]}` : url;
  }

  function canSave() {
    if (type === "image") return !!(content || caption);
    return !!content.trim();
  }

  function handleSubmit() {
    if (!canSave()) return;
    const finalContent = type === "video" ? toEmbedUrl(content.trim()) : content.trim();
    onSave({ content: finalContent, source: source.trim(), caption: caption.trim() });
  }

  function handleKey(e) {
    if (e.key === "Escape") onCancel();
    if ((e.metaKey || e.ctrlKey) && e.key === "Enter") handleSubmit();
  }

  const fs = fieldStyle();

  return (
    <div style={{ padding: "16px 16px 14px" }} onKeyDown={handleKey}>
      <div style={{ display: "flex", flexDirection: "column", gap: 9 }}>

        {type === "note" && (
          <textarea value={content} onChange={e => setContent(e.target.value)}
            rows={5} style={{ ...fs, fontSize: 15, lineHeight: 1.65 }} autoFocus />
        )}

        {type === "image" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <div style={{ display: "flex", gap: 5 }}>
              {["url", "upload"].map(m => (
                <button key={m} className="btn tiny"
                  style={{
                    fontSize: 10.5,
                    background: imageMode === m ? "var(--bg-sunk)" : "transparent",
                    border: `1px solid ${imageMode === m ? "var(--border)" : "var(--border-soft)"}`,
                    color: imageMode === m ? "var(--fg)" : "var(--fg-3)",
                  }}
                  onClick={() => setImageMode(m)}>
                  {m === "url" ? "URL" : "↑ Upload"}
                </button>
              ))}
            </div>
            {imageMode === "url"
              ? <input value={content} onChange={e => setContent(e.target.value)}
                placeholder="https://…" style={fs} autoFocus />
              : <div>
                <input ref={fileRef} type="file" accept="image/*"
                  onChange={handleFileUpload} style={{ display: "none" }} />
                <button className="btn ghost"
                  style={{ width: "100%", justifyContent: "center", padding: "16px 0", borderStyle: "dashed", gap: 7, fontSize: 12 }}
                  onClick={() => fileRef.current?.click()}>
                  {CardIcons.Upload(13)}
                  {content ? "click to replace image" : "choose image file"}
                </button>
                {content && <img src={content} alt="" style={{ width: "100%", borderRadius: 8, marginTop: 7, maxHeight: 130, objectFit: "cover", display: "block" }} />}
              </div>
            }
            <div>
              <label style={LBL}>Caption</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional…" style={fs} />
            </div>
          </div>
        )}

        {type === "quote" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <textarea value={content} onChange={e => setContent(e.target.value)}
              rows={4} placeholder="The exact words…"
              style={{ ...fs, fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: 16 }} autoFocus />
            <div>
              <label style={LBL}>Source</label>
              <input value={source} onChange={e => setSource(e.target.value)}
                placeholder="Author, book, URL…" style={fs} />
            </div>
          </div>
        )}

        {type === "link" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <div>
              <label style={LBL}>URL</label>
              <input value={content} onChange={e => setContent(e.target.value)}
                placeholder="https://…" style={fs} autoFocus />
            </div>
            <div>
              <label style={LBL}>Label</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional display text…" style={fs} />
            </div>
          </div>
        )}

        {type === "video" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <div>
              <label style={LBL}>Video URL</label>
              <input value={content} onChange={e => setContent(e.target.value)}
                placeholder="YouTube URL or embed URL…" style={fs} autoFocus />
            </div>
            <div>
              <label style={LBL}>Caption</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional…" style={fs} />
            </div>
          </div>
        )}

        <div style={{ display: "flex", gap: 6, justifyContent: "flex-end", paddingTop: 2 }}>
          <span style={{ fontSize: 10, color: "var(--fg-3)", fontFamily: "var(--font-mono)", alignSelf: "center", marginRight: "auto", opacity: 0.7 }}>
            ⌘↵ save
          </span>
          <button className="btn ghost tiny" onClick={onCancel}>Cancel</button>
          <button className="btn tiny" onClick={handleSubmit} disabled={!canSave()}
            style={{
              background: canSave() ? "var(--fg)" : "var(--bg-sunk)",
              color: canSave() ? "var(--bg)" : "var(--fg-3)",
              borderColor: canSave() ? "var(--fg)" : "var(--border-soft)",
              cursor: canSave() ? "pointer" : "not-allowed",
            }}>
            Save
          </button>
        </div>
      </div>
    </div>
  );
}

// ── CardModal — popup overlay for a single card ────────────────

function CardModal({ card, onClose, onUpdate, onDelete, onOpenConcept, concepts, onMove }) {
  var _favState = _qp_us(function () {
    try {
      var stored = localStorage.getItem('senes-card-favorites');
      return stored ? JSON.parse(stored).indexOf(card.id) !== -1 : false;
    } catch (e) { return false; }
  });
  var isFavorite = _favState[0], setIsFavorite = _favState[1];
  function toggleFavorite(e) {
    e.stopPropagation();
    setIsFavorite(function (prev) {
      var next = !prev;
      try {
        var stored = localStorage.getItem('senes-card-favorites');
        var arr = stored ? JSON.parse(stored) : [];
        if (next) { if (arr.indexOf(card.id) === -1) arr.push(card.id); }
        else { arr = arr.filter(function (id) { return id !== card.id; }); }
        localStorage.setItem('senes-card-favorites', JSON.stringify(arr));
      } catch (ex) {}
      return next;
    });
  }
  var linkMetaState = _qp_us(null);
  var linkMeta = linkMetaState[0], setLinkMeta = linkMetaState[1];
  var draftCaptionState = _qp_us(card.caption || '');
  var draftCaption = draftCaptionState[0], setDraftCaption = draftCaptionState[1];
  var draftSourceState = _qp_us(card.source || '');
  var draftSource = draftSourceState[0], setDraftSource = draftSourceState[1];
  var draftContentState = _qp_us(card.content || '');
  var draftContent = draftContentState[0], setDraftContent = draftContentState[1];
  var movingState = _qp_us(false);
  var moving = movingState[0], setMoving = movingState[1];
  var moveConceptQueryState = _qp_us('');
  var moveConceptQuery = moveConceptQueryState[0], setMoveConceptQuery = moveConceptQueryState[1];
  var moveConceptIdState = _qp_us('');
  var moveConceptId = moveConceptIdState[0], setMoveConceptId = moveConceptIdState[1];
  var moveParentTypeState = _qp_us('');
  var moveParentType = moveParentTypeState[0], setMoveParentType = moveParentTypeState[1];
  var moveParentIdState = _qp_us('');
  var moveParentId = moveParentIdState[0], setMoveParentId = moveParentIdState[1];
  var moveShowDropState = _qp_us(false);
  var moveShowDrop = moveShowDropState[0], setMoveShowDrop = moveShowDropState[1];

  _qp_ue(function () {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    document.addEventListener('keydown', onKey);
    return function () { document.removeEventListener('keydown', onKey); };
  }, [onClose]);

  _qp_ue(function () {
    if (card.type !== 'link' || !card.content) return;
    fetch('https://api.microlink.io?url=' + encodeURIComponent(card.content))
      .then(function (r) { return r.json(); })
      .then(function (data) { if (data.status === 'success') setLinkMeta(data.data); })
      .catch(function () { });
  }, [card.content]);

  _qp_ue(function () {
    if (!moveConceptId || !moveParentType) return;
    var c = concepts && concepts.find(function (x) { return x.id === moveConceptId; });
    if (!c) return;
    var items = [];
    if (moveParentType === 'consist') items = [{ id: c.id + '_consist', label: c.consist || '(empty)' }];
    else if (moveParentType === 'function') items = [{ id: c.id + '_function', label: c.function || '(empty)' }];
    else if (moveParentType === 'perspective') items = (c.perspectives || []).map(function (p) { return { id: p.id, label: p.title || p.body || p.id }; });
    else if (moveParentType === 'pattern') items = (c.patterns || []).map(function (p) { return { id: p.id, label: p.title || p.body || p.id }; });
    else if (moveParentType === 'essence') items = (c.essences || []).map(function (e) { return { id: e.id, label: e.body || e.id }; });
    setMoveParentId(items.length === 1 ? items[0].id : '');
  }, [moveConceptId, moveParentType]);

  var levelColor = CARD_LEVEL_COLOR[card.parent_type] || 'var(--fg-3)';

  function saveCaption() {
    if (draftCaption !== (card.caption || '')) onUpdate(card.id, { caption: draftCaption });
  }
  function saveSource() {
    if (draftSource !== (card.source || '')) onUpdate(card.id, { source: draftSource });
  }
  function saveContent() {
    if (draftContent !== (card.content || '')) onUpdate(card.id, { content: draftContent });
  }

  function getMoveParentItems() {
    if (!concepts) return [];
    var c = concepts.find(function (x) { return x.id === moveConceptId; });
    if (!c || !moveParentType) return [];
    if (moveParentType === 'consist') return [{ id: c.id + '_consist', label: c.consist || '(empty)' }];
    if (moveParentType === 'function') return [{ id: c.id + '_function', label: c.function || '(empty)' }];
    if (moveParentType === 'perspective') return (c.perspectives || []).map(function (p) { return { id: p.id, label: p.title || p.body || p.id }; });
    if (moveParentType === 'pattern') return (c.patterns || []).map(function (p) { return { id: p.id, label: p.title || p.body || p.id }; });
    if (moveParentType === 'essence') return (c.essences || []).map(function (e) { return { id: e.id, label: e.body || e.id }; });
    return [];
  }
  function canMoveSubmit() {
    return !!(moveConceptId && moveParentType && moveParentId);
  }
  function handleMoveSubmit() {
    if (!canMoveSubmit() || !onMove) return;
    var realParentId = (moveParentType === 'consist' || moveParentType === 'function') ? moveConceptId : moveParentId;
    onMove(card.id, { concept_id: moveConceptId, parent_type: moveParentType, parent_id: realParentId });
    onClose();
  }

  function renderMoveForm() {
    var suggestions = moveConceptQuery.trim()
      ? (concepts || []).filter(function (c) { return c.title.toLowerCase().includes(moveConceptQuery.toLowerCase()); }).slice(0, 6)
      : [];
    var items = getMoveParentItems();
    var MLBL = { fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--fg-3)', marginBottom: 8, display: 'block' };
    var LEVELS = ['consist', 'function', 'perspective', 'pattern', 'essence'];
    return (
      <div style={{ width: '100%', maxWidth: 460, background: 'var(--bg-elev)', borderRadius: 18, padding: 28, display: 'flex', flexDirection: 'column', gap: 20, boxShadow: '0 24px 80px rgba(0,0,0,0.18)' }}>
        <div style={{ fontFamily: 'Nunito, var(--font-ui)', fontSize: 20, color: 'var(--fg)', fontWeight: 600 }}>Move to&hellip;</div>

        <div style={{ position: 'relative' }}>
          <label style={MLBL}>Concept</label>
          <input
            value={moveConceptQuery}
            onChange={function (e) { setMoveConceptQuery(e.target.value); setMoveConceptId(''); setMoveShowDrop(true); }}
            onFocus={function () { setMoveShowDrop(true); }}
            onBlur={function () { setTimeout(function () { setMoveShowDrop(false); }, 150); }}
            placeholder='Search concept…'
            autoFocus
            style={{ width: '100%', background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '9px 13px', fontSize: 14, fontFamily: 'Nunito, var(--font-ui)', outline: 'none', color: 'var(--fg)', boxSizing: 'border-box' }}
          />
          {moveShowDrop && suggestions.length > 0 && (
            <div style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden', zIndex: 10, boxShadow: '0 8px 24px rgba(0,0,0,.1)' }}>
              {suggestions.map(function (c) {
                return (
                  <div key={c.id}
                    onMouseDown={function () { setMoveConceptId(c.id); setMoveConceptQuery(c.title); setMoveShowDrop(false); setMoveParentType(''); setMoveParentId(''); }}
                    style={{ padding: '8px 13px', cursor: 'pointer', fontSize: 13 }}
                    onMouseEnter={function (e) { e.currentTarget.style.background = 'var(--bg-sunk)'; }}
                    onMouseLeave={function (e) { e.currentTarget.style.background = 'transparent'; }}>
                    <div style={{ fontWeight: 500, color: 'var(--fg)', fontFamily: 'Nunito, var(--font-ui)' }}>{c.title}</div>
                    {c.category && <div style={{ fontSize: 10, color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em' }}>{c.category}</div>}
                  </div>
                );
              })}
            </div>
          )}
        </div>

        {moveConceptId && (
          <div>
            <label style={MLBL}>Level</label>
            <div style={{ display: 'flex', gap: 28, justifyContent: 'center' }}>
              {LEVELS.map(function (lvl) {
                var active = moveParentType === lvl;
                var color = CARD_LEVEL_COLOR[lvl] || 'var(--fg-3)';
                var _isNatM = !!document.querySelector('.app-shell[data-nature]');
                return (
                  <button key={lvl} type='button' title={lvl}
                    onClick={function () { setMoveParentType(lvl); setMoveParentId(''); }}
                    style={{
                      width: 26, height: 26, borderRadius: '50%', padding: 0, cursor: 'pointer', flexShrink: 0,
                      background: _isNatM ? (active ? 'rgba(255,255,255,0.2)' : 'transparent') : (active ? color : 'var(--bg-elev)'),
                      border: _isNatM ? '1.5px solid rgba(255,255,255,0.6)' : ('2.5px solid ' + color),
                      transition: 'all 140ms',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}>
                    {_isNatM && <window.LevelShape level={lvl} size={12} color={active ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.4)'} />}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        {moveConceptId && moveParentType && items.length > 1 && (
          <div>
            <label style={MLBL}>{moveParentType}</label>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: 160, overflowY: 'auto' }}>
              {items.map(function (item) {
                var active = moveParentId === item.id;
                var color = CARD_LEVEL_COLOR[moveParentType] || 'var(--fg-3)';
                return (
                  <button key={item.id} type='button'
                    onClick={function () { setMoveParentId(item.id); }}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 8,
                      textAlign: 'left', padding: '6px 10px', borderRadius: 8, cursor: 'pointer', border: 'none',
                      background: active ? 'var(--bg-sunk)' : 'transparent',
                      fontSize: 13, fontFamily: 'Nunito, var(--font-ui)',
                      color: active ? 'var(--fg)' : 'var(--fg-3)',
                      transition: 'all 120ms',
                    }}>
                    <span style={{ width: 5, height: 5, borderRadius: 999, background: active ? color : 'var(--border)', flexShrink: 0, transition: 'background 120ms' }} />
                    {item.label}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        <div style={{ display: 'flex', gap: 16, alignItems: 'center', justifyContent: 'center', paddingTop: 4 }}>
          <button onClick={function () { setMoving(false); }}
            style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
            title='Cancel'>
            {window.Icons.Close(22)}
          </button>
          <button
            disabled={!canMoveSubmit()}
            onClick={handleMoveSubmit}
            style={{ background: 'none', border: 'none', cursor: canMoveSubmit() ? 'pointer' : 'not-allowed', color: canMoveSubmit() ? 'var(--fg)' : 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: canMoveSubmit() ? 1 : 0.4 }}
            title='Move here'>
            {window.Icons.Mark(22)}
          </button>
        </div>
      </div>
    );
  }

  function renderLeft() {
    if (card.type === 'image' && card.content) {
      return (
        <img src={card.content} alt={card.caption || ''}
          style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 6 }} />
      );
    }
    if (card.type === 'video' && card.content) {
      return (
        <div style={{ width: '100%', position: 'relative', paddingBottom: '56.25%', borderRadius: 10, overflow: 'hidden' }}>
          <iframe src={card.content} title={card.caption || 'video'}
            allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
            allowFullScreen
            style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', border: 0 }} />
        </div>
      );
    }
    if (card.type === 'quote') {
      return (
        <div style={{ padding: '52px 56px', maxWidth: 520, textAlign: 'center' }}>
          <p style={{ margin: '0 0 22px', fontFamily: 'var(--font-ui)', fontSize: 26, lineHeight: 1.55, color: 'var(--fg)' }}>
            {card.content}
          </p>
          {card.source && (
            <p style={{ margin: 0, fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--fg-3)', letterSpacing: '.04em' }}>
              — {card.source} —
            </p>
          )}
        </div>
      );
    }
    if (card.type === 'note') {
      return (
        <div style={{ padding: '52px 56px', maxWidth: 520 }}>
          <p style={{ margin: 0, fontSize: 20, lineHeight: 1.72, color: 'var(--fg)', whiteSpace: 'pre-wrap' }}>
            {card.content}
          </p>
        </div>
      );
    }
    if (card.type === 'link') {
      if (linkMeta && linkMeta.image && linkMeta.image.url) {
        return (
          <img src={linkMeta.image.url} alt=''
            style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 6 }} />
        );
      }
      return (
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14, color: 'var(--fg-3)' }}>
          {CardIcons.Link(40)}
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, wordBreak: 'break-all', textAlign: 'center', maxWidth: 300 }}>
            {(function () { try { return new URL(card.content).hostname; } catch (ex) { return card.content; } }())}
          </span>
        </div>
      );
    }
    return null;
  }

  var _nature = !!document.querySelector('.app-shell[data-nature]');
  var LBL = { fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.14em', textTransform: 'uppercase', color: 'var(--fg-3)', marginBottom: 6 };
  var FIELD_WRAP = { marginBottom: 18 };
  var FIELD_BASE = { display: 'block', width: '100%', margin: 0, padding: '9px 12px', boxSizing: 'border-box', background: _nature ? 'rgba(255,255,255,0.1)' : 'var(--bg-sunk)', backdropFilter: _nature ? 'blur(12px)' : undefined, WebkitBackdropFilter: _nature ? 'blur(12px)' : undefined, border: 'none', borderRadius: 8, outline: 'none', fontFamily: 'inherit', cursor: 'text', color: 'var(--fg)', lineHeight: 1.55 };

  var _modalNode = (
    <div onClick={onClose} className='card-modal-backdrop' style={{
      position: 'fixed', inset: 0, zIndex: 300,
      background: _nature ? 'transparent' : 'rgba(0,0,0,0.45)',
      backdropFilter: _nature ? undefined : 'blur(16px)',
      WebkitBackdropFilter: _nature ? undefined : 'blur(16px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 32,
    }}>
      <div onClick={function (e) { e.stopPropagation(); }} className='fade-up' style={{
        display: 'flex',
        width: '100%', maxWidth: _nature ? 'calc(100vw - 48px)' : 960,
        height: _nature ? 'calc(100vh - 48px)' : '80vh', maxHeight: _nature ? 'none' : 680,
        borderRadius: _nature ? 14 : 18, overflow: 'hidden',
        background: _nature
          ? 'var(--glass-bg)'
          : isFavorite
            ? 'radial-gradient(ellipse at 12% 48%, hsla(190, 90%, 78%, 0.80) 0%, transparent 46%), radial-gradient(ellipse at 68% 42%, hsla(318, 62%, 86%, 0.72) 0%, transparent 46%), radial-gradient(ellipse at 28% 82%, hsla(165, 72%, 82%, 0.65) 0%, transparent 42%), radial-gradient(ellipse at 88% 68%, hsla(270, 68%, 83%, 0.72) 0%, transparent 44%), radial-gradient(ellipse at 55% 62%, hsla(225, 55%, 85%, 0.55) 0%, transparent 40%), hsl(248, 45%, 88%)'
            : 'var(--bg-sunk)',
        backdropFilter: _nature ? 'blur(22px) saturate(1.2)' : undefined,
        WebkitBackdropFilter: _nature ? 'blur(22px) saturate(1.2)' : undefined,
        boxShadow: _nature ? 'none' : '0 24px 80px rgba(0,0,0,0.3)',
      }}>

        {/* Left: main content or move form */}
        <div style={{
          flex: '1 1 60%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          overflow: moving ? 'auto' : 'hidden',
          padding: moving ? '28px 32px' : 16,
        }}>
          {moving ? renderMoveForm() : renderLeft()}
        </div>

        {/* Right: form card floating inside the main card */}
        <div style={{
          flex: '0 0 280px', minWidth: 0,
          padding: 10,
          display: 'flex',
        }}>
          <div style={{
            flex: 1,
            background: _nature ? 'var(--glass-bg)' : 'var(--bg-elev)',
            backdropFilter: _nature ? 'blur(12px)' : undefined,
            WebkitBackdropFilter: _nature ? 'blur(12px)' : undefined,
            borderRadius: 12,
            display: 'flex', flexDirection: 'column',
            overflow: 'hidden',
            boxShadow: _nature ? 'none' : '0 2px 16px rgba(0,0,0,0.12)',
          }}>
            <div style={{ flex: 1, overflowY: 'auto', padding: '24px 20px' }}>

              <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 26, color: 'var(--fg-3)' }}>
                <window.LevelDot level={card.parent_type} size={7} />
                {(function () { var c = concepts && concepts.find(function (x) { return x.id === card.concept_id; }); return c ? <span style={{ fontFamily: 'var(--font-mono)', fontSize: 9.5, letterSpacing: '.14em', textTransform: 'uppercase' }}>{c.title}</span> : null; })()}
                <button onClick={toggleFavorite} title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
                  style={{ marginLeft: 'auto', background: 'none', border: 'none', cursor: 'pointer', padding: 2, display: 'flex', color: isFavorite ? 'var(--c-perspective)' : 'var(--fg-3)', transition: 'color 150ms' }}>
                  <svg width='15' height='15' viewBox='0 0 32 32' fill={isFavorite ? 'currentColor' : 'none'} stroke='currentColor' strokeWidth='1.8'>
                    <path d='M28.69 14.13L20.76 11.25L17.88 3.31C17.73 2.93 17.48 2.6 17.14 2.37C16.81 2.13 16.41 2.01 16 2.01C15.59 2.01 15.19 2.13 14.86 2.37C14.52 2.6 14.27 2.93 14.13 3.31L11.25 11.24L3.31 14.13C2.93 14.27 2.6 14.52 2.37 14.86C2.13 15.19 2.01 15.59 2.01 16C2.01 16.41 2.13 16.81 2.37 17.14C2.6 17.48 2.93 17.73 3.31 17.88L11.24 20.75L14.13 28.69C14.27 29.07 14.52 29.4 14.86 29.63C15.19 29.87 15.59 29.99 16 29.99C16.41 29.99 16.81 29.87 17.14 29.63C17.48 29.4 17.73 29.07 17.88 28.69L20.76 20.76L28.69 17.88C29.07 17.73 29.4 17.48 29.63 17.14C29.87 16.81 29.99 16.41 29.99 16C29.99 15.59 29.87 15.19 29.63 14.86C29.4 14.52 29.07 14.27 28.69 14.13Z'/>
                  </svg>
                </button>
              </div>

              {/* Content — editable main body */}
              <div style={FIELD_WRAP}>
                <div style={LBL}>{card.type === 'link' || card.type === 'image' || card.type === 'video' ? 'URL' : 'Content'}</div>
                {(card.type === 'note' || card.type === 'quote') ? (
                  <textarea
                    value={draftContent}
                    onChange={function (e) { setDraftContent(e.target.value); }}
                    onBlur={saveContent}
                    placeholder={'Add ' + card.type + '…'}
                    rows={5}
                    style={{ ...FIELD_BASE, fontSize: 14, resize: 'none' }}
                  />
                ) : (
                  <input
                    type='text'
                    value={draftContent}
                    onChange={function (e) { setDraftContent(e.target.value); }}
                    onBlur={saveContent}
                    placeholder='https://…'
                    style={{ ...FIELD_BASE, fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--fg-2)' }}
                  />
                )}
              </div>

              {(card.type === 'image' || card.type === 'video' || card.type === 'link') && (
                <div style={FIELD_WRAP}>
                  <div style={LBL}>Caption</div>
                  <textarea
                    value={draftCaption}
                    onChange={function (e) { setDraftCaption(e.target.value); }}
                    onBlur={saveCaption}
                    placeholder='Add caption…'
                    rows={3}
                    style={{ ...FIELD_BASE, fontSize: 14, resize: 'none' }}
                  />
                </div>
              )}

              {card.type === 'quote' && (
                <div style={FIELD_WRAP}>
                  <div style={LBL}>Source</div>
                  <input
                    type='text'
                    value={draftSource}
                    onChange={function (e) { setDraftSource(e.target.value); }}
                    onBlur={saveSource}
                    placeholder='Add source…'
                    style={{ ...FIELD_BASE, fontSize: 12.5, color: 'var(--fg-2)', fontFamily: 'var(--font-mono)' }}
                  />
                </div>
              )}

              {card.type === 'link' && draftContent && (
                <div style={{ paddingLeft: 2, marginTop: -4, marginBottom: 10 }}>
                  <a href={draftContent} target='_blank' rel='noopener noreferrer'
                    onClick={function (e) { e.stopPropagation(); }}
                    style={{ fontSize: 10.5, color: !!document.querySelector('.app-shell[data-nature]') ? 'rgba(255,255,255,0.7)' : 'var(--c-function)', fontFamily: 'var(--font-mono)', textDecoration: 'none', opacity: 0.8 }}>
                    open →
                  </a>
                </div>
              )}

              {card.type === 'link' && linkMeta && linkMeta.title && (
                <div style={FIELD_WRAP}>
                  <div style={LBL}>Title</div>
                  <p style={{ margin: 0, fontSize: 14, color: 'var(--fg)', lineHeight: 1.4, fontWeight: 500 }}>{linkMeta.title}</p>
                </div>
              )}

              {card.type === 'link' && linkMeta && linkMeta.description && (
                <div style={FIELD_WRAP}>
                  <div style={LBL}>Description</div>
                  <p style={{ margin: 0, fontSize: 13, color: 'var(--fg-2)', lineHeight: 1.55 }}>{linkMeta.description}</p>
                </div>
              )}

            </div>

            <div style={{ padding: '8px 10px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 2 }}>
              {onOpenConcept && (
                <button className='btn ghost tiny' title='Open concept' onClick={function () { onOpenConcept(card.concept_id, 'edit', card.parent_type); onClose(); }}
                  style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: 'transparent', color: 'var(--fg-3)' }}>
                  {window.Icons.Word(24)}
                </button>
              )}
              {concepts && concepts.length > 0 && onMove && (
                <button className='btn ghost tiny' title='Move to…'
                  onClick={function () { setMoving(function (v) { return !v; }); }}
                  style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: 'transparent', color: moving ? 'var(--fg)' : 'var(--fg-3)' }}>
                  {window.Icons.Move(24)}
                </button>
              )}
              <button className='btn ghost tiny' title='Delete' onClick={function () { onDelete(card.id); onClose(); }} style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: 'transparent', color: 'var(--fg-3)' }}>
                {window.Icons.Trash(24)}
              </button>
              <button className='btn ghost tiny' title='Close' onClick={onClose} style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: 'transparent', color: 'var(--fg-3)' }}>
                {window.Icons.Close(24)}
              </button>
            </div>
          </div>
        </div>

      </div>
    </div>
  );
  return ReactDOM.createPortal(_modalNode, document.body);
}

function VideoCard({ embedUrl, thumb, caption }) {
  var playingState = _qp_us(false);
  var playing = playingState[0], setPlaying = playingState[1];
  var hovState = _qp_us(false);
  var hov = hovState[0], setHov = hovState[1];
  return (
    <div style={{ position: 'relative', paddingBottom: '56.25%', background: '#000' }}
      onMouseEnter={function () { setHov(true); }}
      onMouseLeave={function () { setHov(false); }}>
      {playing ? (
        <iframe src={embedUrl + '?autoplay=1'} title={caption || 'video'}
          allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
          allowFullScreen
          style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', border: 0 }} />
      ) : (
        <>
          {thumb
            ? <img src={thumb} alt='' style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
            : <div style={{ position: 'absolute', inset: 0, background: 'var(--app-bg, var(--bg))' }} />
          }
          <div style={{
            position: 'absolute', bottom: 0, left: 0, right: 0, height: '60%',
            background: 'linear-gradient(to top, rgba(0,0,0,0.72) 0%, transparent 100%)',
            opacity: hov ? 1 : 0,
            transition: 'opacity 200ms ease',
            pointerEvents: 'none',
          }} />
          <div style={{
            position: 'absolute', inset: 0,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <div onClick={function (e) { e.stopPropagation(); setPlaying(true); }}
              style={{
                cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
                width: 48, height: 48, borderRadius: 999,
                background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(4px)',
                transition: 'transform 150ms ease, background 150ms ease',
              }}
              onMouseEnter={function (e) { e.currentTarget.style.transform = 'scale(1.1)'; e.currentTarget.style.background = 'rgba(0,0,0,0.65)'; }}
              onMouseLeave={function (e) { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.background = 'rgba(0,0,0,0.45)'; }}>
              <svg width='18' height='18' viewBox='0 0 24 24' fill='rgba(255,255,255,0.9)' style={{ marginLeft: 2 }}>
                <polygon points='5,3 19,12 5,21' />
              </svg>
            </div>
          </div>
        </>
      )}
    </div>
  );
}


// ── CardCard ─────────────────────────────────────────────────────

function CardCard({ card, onDelete, onUpdate, isDragging, onDragStart, onDragOver, onDragEnd, animDelay, conceptInfo, onOpen, isFavorite, onToggleFavorite }) {
  const [editing, setEditing] = _qp_us(false);
  const [flipped, setFlipped] = _qp_us(false);
  const [linkMeta, setLinkMeta] = _qp_us(null);
  const [confirmDelete, setConfirmDelete] = _qp_us(false);
  const levelColor = CARD_LEVEL_COLOR[card.parent_type] || 'var(--fg-3)';


  _qp_ue(function () {
    if (card.type !== 'link' || !card.content) return;
    var url = card.content;
    fetch('https://api.microlink.io?url=' + encodeURIComponent(url))
      .then(function (r) { return r.json(); })
      .then(function (data) { if (data.status === 'success') setLinkMeta(data.data); })
      .catch(function () { });
  }, [card.content]);

  function handleSave(changes) {
    onUpdate(card.id, changes);
    setEditing(false);
  }

  function handleContextMenu(e) {
    if (editing) return;
    e.preventDefault();
    setFlipped(v => !v);
    setConfirmDelete(false);
  }

  return (
    <div
      className={'card-hub-flip-scene fade-up' + (isFavorite ? ' is-favorite' : '')}
      style={{ animationDelay: animDelay ? animDelay + 'ms' : '0ms' }}
      onContextMenu={handleContextMenu}
    >
      <div className={'card-hub-flipper' + (flipped ? ' is-flipped' : '')}>

        {/* ── Front ── */}
        <div
          className={`card-hub-front qk-card${editing ? " qk-editing" : ""}${isDragging ? " qk-dragging" : ""}`}
          style={{ cursor: onOpen && !editing ? "pointer" : "default", pointerEvents: flipped ? 'none' : 'auto' }}
          onClick={() => !editing && onOpen && onOpen(card)}
          draggable={!editing && !flipped && !!onDragStart}
          onDragStart={(e) => {
            e.dataTransfer.effectAllowed = "move";
            e.dataTransfer.setData("text/plain", card.id);
            onDragStart && onDragStart();
          }}
          onDragOver={(e) => {
            e.preventDefault();
            e.stopPropagation();
            const rect = e.currentTarget.getBoundingClientRect();
            const isTopHalf = e.clientY < rect.top + rect.height / 2;
            onDragOver && onDragOver(card.id, isTopHalf);
          }}
          onDragEnd={() => { onDragEnd && onDragEnd(); }}
        >

          {/* ── Edit mode ── */}
          {editing && (
            <CardInlineEdit
              card={card}
              onSave={handleSave}
              onCancel={() => setEditing(false)}
            />
          )}

          {/* ── View: NOTE ── */}
          {!editing && card.type === "note" && (
            <div className="qk-card-body" style={{ padding: "20px 20px 20px" }}>
              <p style={{ margin: 0, fontSize: 14.5, lineHeight: 1.72, color: "var(--fg)", whiteSpace: "pre-wrap" }}>
                {card.content}
              </p>
            </div>
          )}

          {/* ── View: IMAGE ── */}
          {!editing && card.type === "image" && (
            <div>
              {card.content
                ? <img src={card.content} alt={card.caption || ""}
                  style={{ width: "100%", display: "block", maxHeight: 420, objectFit: "cover" }} />
                : <window.StripePlaceholder label="image" h={180} />
              }
            </div>
          )}

          {/* ── View: QUOTE ── */}
          {!editing && card.type === "quote" && (
            <div className="qk-card-body" style={{ padding: "22px 20px 22px", textAlign: "center" }}>
              <p style={{ margin: "0 0 16px", fontFamily: "var(--font-ui)", fontSize: 17.5, lineHeight: 1.65, color: "var(--fg)" }}>
                {card.content}
              </p>
              {card.source && (
                <span style={{ fontSize: 11.5, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>
                  — {card.source} —
                </span>
              )}
            </div>
          )}

          {/* ── View: LINK ── */}
          {!editing && card.type === 'link' && (
            <div>
              {linkMeta && linkMeta.image && linkMeta.image.url && (
                <img src={linkMeta.image.url} alt=""
                  style={{ width: '100%', display: 'block', maxHeight: 180, objectFit: 'cover' }} />
              )}
              <div style={{ padding: '14px 18px 34px' }}>
                <div style={{ display: 'flex', alignItems: 'flex-start', gap: 9 }}>
                  <img
                    src={'https://www.google.com/s2/favicons?domain=' + (function () { try { return new URL(card.content).hostname; } catch (ex) { return ''; } }()) + '&sz=32'}
                    alt="" onError={function (e) { e.target.style.display = 'none'; }}
                    style={{ width: 15, height: 15, borderRadius: 3, flexShrink: 0, marginTop: 3, objectFit: 'contain' }}
                  />
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <a href={card.content} target="_blank" rel="noopener noreferrer"
                      onClick={function (e) { e.stopPropagation(); }}
                      style={{ display: 'block', fontSize: 14, fontWeight: 500, color: 'var(--fg)', textDecoration: 'none', lineHeight: 1.35, marginBottom: 3, wordBreak: 'break-word' }}>
                      {card.caption || (linkMeta && linkMeta.title) || card.content}
                    </a>
                  </div>
                </div>
              </div>
            </div>
          )}

          {/* ── View: VIDEO ── */}
          {!editing && card.type === 'video' && (function () {
            var embedUrl = card.content || '';
            var vidId = '';
            var m = embedUrl.match(/\/embed\/([A-Za-z0-9_-]{11})/);
            if (m) vidId = m[1];
            var thumb = vidId ? 'https://img.youtube.com/vi/' + vidId + '/hqdefault.jpg' : '';
            return (
              <VideoCard embedUrl={embedUrl} thumb={thumb} caption={card.caption} />
            );
          })()}

          {/* ── Hover overlay (image only) ── */}
          {!editing && card.type === "image" && (
            <div className="qk-overlay-image">
              {card.caption && <div style={{ fontSize: 20, lineHeight: 1.4, color: "rgba(255,255,255,0.9)", marginBottom: 5, textAlign: "center" }}>{card.caption}</div>}
              <div style={{ display: 'flex', alignItems: 'center', gap: 5, color: 'rgba(255,255,255,0.6)' }}>
                <window.LevelDot level={card.parent_type} size={6} />
                {conceptInfo && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '.1em', textTransform: 'uppercase' }}>{conceptInfo}</span>}
              </div>
            </div>
          )}

          {/* ── Info (non-image cards) ── */}
          {!editing && card.type !== "image" && (
            <div className="qk-info">
              <div style={{ display: 'flex', alignItems: 'center', gap: 5, color: 'var(--fg-3)' }}>
                <window.LevelDot level={card.parent_type} size={6} />
                {conceptInfo && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '.1em', textTransform: 'uppercase' }}>{conceptInfo}</span>}
              </div>
            </div>
          )}
        </div>

        {/* ── Back ── */}
        <div
          className='card-hub-back'
          onClick={() => { setFlipped(false); setConfirmDelete(false); }}
          style={{ pointerEvents: flipped ? 'auto' : 'none', cursor: 'default' }}
        >
          <div className={'holo-fill' + (isFavorite ? ' active' : '')} />

          {confirmDelete ? (
            <div style={{ position: 'relative', zIndex: 1, flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 20 }}
              onClick={e => e.stopPropagation()}>
              <p style={{ margin: 0, fontFamily: 'Nunito, var(--font-ui)', fontSize: 15, color: 'var(--fg)', textAlign: 'center', lineHeight: 1.45 }}>
                Delete this card?
              </p>
              <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
                <button onClick={() => setConfirmDelete(false)}
                  style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
                  title='Cancel'>
                  {window.Icons.Close(22)}
                </button>
                <button onClick={() => onDelete && onDelete(card.id)}
                  style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
                  title='Delete'>
                  {window.Icons.Mark(22)}
                </button>
              </div>
            </div>
          ) : (
            <div style={{ position: 'relative', zIndex: 1, flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', color: isFavorite ? 'rgba(0,0,0,0.65)' : undefined }}
              onClick={e => e.stopPropagation()}>

              {/* Top-left: type icon */}
              <span style={{ position: 'absolute', top: 0, left: 0, color: isFavorite ? 'rgba(0,0,0,0.4)' : 'var(--fg-3)', display: 'flex' }}>{cardIcon(card.type, 24)}</span>

              {/* Center: Favorite + Delete */}
              <div style={{ display: 'flex', gap: 4 }}>
                {onToggleFavorite && (
                  <button
                    onClick={() => onToggleFavorite()}
                    className={'btn ghost' + (isFavorite ? ' fav-btn active' : '')}
                    style={{ width: 52, height: 52, borderRadius: 999, justifyContent: 'center', padding: 0, color: isFavorite ? 'rgba(0,0,0,0.65)' : undefined, background: isFavorite ? 'rgba(255,255,255,0.22)' : 'transparent' }}
                    title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}>
                    <svg width='24' height='24' viewBox='0 0 32 32' fill='none'><defs><linearGradient id='star-holo' x1='0' y1='0' x2='1' y2='1'><stop offset='0%' stopColor='hsla(190,90%,68%,1)' /><stop offset='40%' stopColor='hsla(318,62%,76%,1)' /><stop offset='80%' stopColor='hsla(165,72%,72%,1)' /><stop offset='100%' stopColor='hsla(270,68%,73%,1)' /></linearGradient></defs><path d='M28.6876 14.125L20.7588 11.25L17.8751 3.3125C17.733 2.92974 17.4772 2.59964 17.142 2.36653C16.8068 2.13342 16.4083 2.00847 16.0001 2.00847C15.5918 2.00847 15.1933 2.13342 14.8581 2.36653C14.523 2.59964 14.2671 2.92974 14.1251 3.3125L11.2501 11.2412L3.31257 14.125C2.92982 14.2671 2.59971 14.5229 2.3666 14.8581C2.13349 15.1932 2.00854 15.5917 2.00854 16C2.00854 16.4083 2.13349 16.8068 2.3666 17.1419C2.59971 17.4771 2.92982 17.7329 3.31257 17.875L11.2413 20.75L14.1251 28.6875C14.2671 29.0703 14.523 29.4004 14.8581 29.6335C15.1933 29.8666 15.5918 29.9915 16.0001 29.9915C16.4083 29.9915 16.8068 29.8666 17.142 29.6335C17.4772 29.4004 17.733 29.0703 17.8751 28.6875L20.7588 20.7575L28.6876 17.875C29.0703 17.7329 29.4004 17.4771 29.6335 17.1419C29.8667 16.8068 29.9916 16.4083 29.9916 16C29.9916 15.5917 29.8667 15.1932 29.6335 14.8581C29.4004 14.5229 29.0703 14.2671 28.6876 14.125Z' fill={isFavorite ? 'url(#star-holo)' : 'none'} stroke={isFavorite ? 'none' : 'var(--fg-3)'} strokeWidth='1.8' /></svg>
                  </button>
                )}
                {onDelete && (
                  <button
                    onClick={() => setConfirmDelete(true)}
                    className='btn ghost back-btn'
                    style={{ width: 52, height: 52, borderRadius: '50%', justifyContent: 'center', padding: 0, color: isFavorite ? 'rgba(0,0,0,0.65)' : undefined }}
                    title='Delete card'>
                    <svg width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='1.8' strokeLinecap='round'>
                      <path d='M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12' />
                    </svg>
                  </button>
                )}
              </div>

            </div>
          )}
        </div>

      </div>
    </div>
  );
}

// ── AddCardForm ──────────────────────────────────────────────────

function AddCardForm({ type, onSubmit, onCancel }) {
  const [content, setContent] = _qp_us("");
  const [source, setSource] = _qp_us("");
  const [caption, setCaption] = _qp_us("");
  const [imageMode, setImageMode] = _qp_us("url");
  const fileRef = _qp_ur(null);

  const info = CARD_TYPES.find(t => t.type === type);

  function handleFileUpload(e) {
    const file = e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (evt) => setContent(evt.target.result);
    reader.readAsDataURL(file);
  }

  function toEmbedUrl(url) {
    const m = url.match(/(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/)|youtu\.be\/)([A-Za-z0-9_-]{11})/);
    return m ? `https://www.youtube.com/embed/${m[1]}` : url;
  }

  function canSubmit() {
    if (type === "image") return !!(content || caption);
    return !!content.trim();
  }

  function handleSubmit() {
    if (!canSubmit()) return;
    const finalContent = type === "video" ? toEmbedUrl(content.trim()) : content.trim();
    onSubmit({ type, content: finalContent, source: source.trim(), caption: caption.trim() });
  }

  function handleKey(e) {
    if (e.key === "Escape") onCancel();
    if ((e.metaKey || e.ctrlKey) && e.key === "Enter") handleSubmit();
  }

  const fs = fieldStyle();

  return (
    <div className="surface fade-up"
      style={{ padding: "20px 22px", marginBottom: 24, borderRadius: 16 }}
      onKeyDown={handleKey}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 16 }}>
        <span style={{ color: info?.color }}>{cardIcon(type, 24)}</span>
        <span style={{ fontSize: 11, fontFamily: "var(--font-mono)", letterSpacing: ".12em", textTransform: "uppercase", color: "var(--fg-3)", flex: 1 }}>
          Add {type}
        </span>
        <span style={{ fontSize: 10, color: "var(--fg-3)", fontFamily: "var(--font-mono)", opacity: 0.7 }}>⌘↵ save</span>
      </div>

      <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
        {type === "note" && (
          <textarea value={content} onChange={e => setContent(e.target.value)}
            placeholder="Write your note…" rows={5}
            style={{ ...fs, fontSize: 15, lineHeight: 1.65 }} autoFocus />
        )}

        {type === "image" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ display: "flex", gap: 6 }}>
              {["url", "upload"].map(m => (
                <button key={m} className="btn tiny"
                  style={{
                    fontSize: 11,
                    background: imageMode === m ? "var(--bg-sunk)" : "transparent",
                    border: `1px solid ${imageMode === m ? "var(--border)" : "var(--border-soft)"}`,
                    color: imageMode === m ? "var(--fg)" : "var(--fg-3)",
                  }}
                  onClick={() => setImageMode(m)}>
                  {m === "url" ? "URL" : "↑ Upload"}
                </button>
              ))}
            </div>
            {imageMode === "url"
              ? <input value={content} onChange={e => setContent(e.target.value)}
                placeholder="https://…" style={fs} autoFocus />
              : <div>
                <input ref={fileRef} type="file" accept="image/*"
                  onChange={handleFileUpload} style={{ display: "none" }} />
                <button className="btn ghost"
                  style={{ width: "100%", justifyContent: "center", padding: "24px 0", borderStyle: "dashed", gap: 8 }}
                  onClick={() => fileRef.current?.click()}>
                  {CardIcons.Upload(14)}
                  {content ? "image loaded — click to replace" : "choose image file"}
                </button>
                {content && <img src={content} alt="" style={{ width: "100%", borderRadius: 8, marginTop: 8, maxHeight: 140, objectFit: "cover", display: "block" }} />}
              </div>
            }
            <div>
              <label style={LBL}>Caption</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional…" style={fs} />
            </div>
          </div>
        )}

        {type === "quote" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <textarea value={content} onChange={e => setContent(e.target.value)}
              placeholder="The exact words…" rows={4}
              style={{ ...fs, fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: 16 }} autoFocus />
            <div>
              <label style={LBL}>Source</label>
              <input value={source} onChange={e => setSource(e.target.value)}
                placeholder="Author, book, URL…" style={fs} />
            </div>
          </div>
        )}

        {type === "link" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <input value={content} onChange={e => setContent(e.target.value)}
              placeholder="https://…" style={fs} autoFocus />
            <div>
              <label style={LBL}>Label</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional display text…" style={fs} />
            </div>
          </div>
        )}

        {type === "video" && (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <input value={content} onChange={e => setContent(e.target.value)}
              placeholder="YouTube URL or embed URL…" style={fs} autoFocus />
            <span style={{ fontSize: 11, color: "var(--fg-3)", fontFamily: "var(--font-mono)", marginTop: -4 }}>
              YouTube links auto-convert to embeds.
            </span>
            <div>
              <label style={LBL}>Caption</label>
              <input value={caption} onChange={e => setCaption(e.target.value)}
                placeholder="Optional…" style={fs} />
            </div>
          </div>
        )}

        <div style={{ display: "flex", gap: 16, justifyContent: "center", alignItems: "center", paddingTop: 2 }}>
          <button onClick={onCancel}
            style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", padding: 6, borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center" }}
            title="Cancel">
            {window.Icons.Close(22)}
          </button>
          <button onClick={handleSubmit} disabled={!canSubmit()}
            style={{ background: "none", border: "none", cursor: canSubmit() ? "pointer" : "not-allowed", color: canSubmit() ? "var(--fg)" : "var(--fg-3)", padding: 6, borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", opacity: canSubmit() ? 1 : 0.4 }}
            title="Add card">
            {window.Icons.Mark(22)}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── CardsScreen — full page, masonry ────────────────────────────

function CardsScreen({ conceptId, parentType, parentId, label, conceptTitle, onBack, onOpenConcept, concepts }) {
  const [cards, setCards] = _qp_us([]);
  const [adding, setAdding] = _qp_us(null);
  const [draggedId, setDraggedId] = _qp_us(null);
  const [dropBeforeId, setDropBeforeId] = _qp_us(null);
  const [selectedCardId, setSelectedCardId] = _qp_us(null);
  const selectedCard = selectedCardId ? cards.find(q => q.id === selectedCardId) : null;

  _qp_ue(() => {
    if (window.cardsService) {
      window.cardsService.getCards(parentType, parentId).then(rows => {
        setCards(rows.slice().sort((a, b) => {
          if (a.sort_order != null && b.sort_order != null) return a.sort_order - b.sort_order;
          if (a.sort_order != null) return -1;
          if (b.sort_order != null) return 1;
          return a.created_at < b.created_at ? -1 : 1;
        }));
      });
    }
    setAdding(null);
  }, [parentType, parentId]);

  async function handleAdd(card) {
    const now = new Date().toISOString();
    const newCard = {
      id: "qk" + Date.now() + Math.random().toString(36).slice(2, 6),
      parent_type: parentType,
      parent_id: parentId,
      concept_id: conceptId,
      sort_order: (cards.length + 1) * 1000,
      created_at: now,
      updated_at: now,
      ...card,
    };
    await window.cardsService.addCard(newCard);
    setCards(qs => [...qs, newCard]);
    setAdding(null);
  }

  async function handleUpdate(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(qs => qs.map(q => q.id === id
      ? { ...q, ...changes, updated_at: new Date().toISOString() }
      : q
    ));
  }

  async function handleDelete(id) {
    await window.cardsService.deleteCard(id);
    setCards(qs => qs.filter(q => q.id !== id));
  }
  async function handleMove(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(qs => qs.filter(q => q.id !== id));
    setSelectedCardId(null);
  }

  function handleDragStart(id) {
    setDraggedId(id);
    setDropBeforeId(null);
  }

  function handleDragOver(targetId, isTopHalf) {
    if (targetId === draggedId) return;
    if (isTopHalf) {
      setDropBeforeId(targetId);
    } else {
      const idx = cards.findIndex(q => q.id === targetId);
      const next = cards[idx + 1];
      setDropBeforeId(next ? next.id : null);
    }
  }

  function handleDragEnd() {
    setDraggedId(null);
    setDropBeforeId(null);
  }

  async function handleDrop() {
    if (!draggedId) { handleDragEnd(); return; }
    const dragged = cards.find(q => q.id === draggedId);
    if (!dragged) { handleDragEnd(); return; }

    const rest = cards.filter(q => q.id !== draggedId);
    let newOrder;
    if (dropBeforeId === null) {
      newOrder = [...rest, dragged];
    } else {
      const insertIdx = rest.findIndex(q => q.id === dropBeforeId);
      newOrder = insertIdx === -1
        ? [...rest, dragged]
        : [...rest.slice(0, insertIdx), dragged, ...rest.slice(insertIdx)];
    }

    const updated = newOrder.map((q, i) => ({ ...q, sort_order: (i + 1) * 1000 }));
    setCards(updated);
    for (const q of updated) {
      await window.cardsService.updateCard(q.id, { sort_order: q.sort_order });
    }
    setDraggedId(null);
    setDropBeforeId(null);
  }

  const levelColor = CARD_LEVEL_COLOR[parentType] || "var(--fg-2)";

  return (
    <div className="page" data-screen-label="Cards" style={{ maxWidth: 1080 }}>

      {/* Toolbar */}
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 40 }}>
        <button className="btn ghost tiny" onClick={onBack} style={{ gap: 6 }}>
          {window.Icons.ArrowL(13)} {conceptTitle || "Concept"}
        </button>
        <div style={{ flex: 1 }} />
        {cards.length > 0 && (
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, letterSpacing: ".08em", color: "var(--fg-3)" }}>
            {cards.length} card{cards.length !== 1 ? "s" : ""}
          </span>
        )}
      </div>

      {/* Hero */}
      <div style={{ marginBottom: 36 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
          <window.LevelDot level={parentType} size={8} />
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".16em", textTransform: "uppercase", color: "var(--fg-3)" }}>
            {parentType}
          </span>
        </div>
        {label && (
          <h1 className="concept-title" style={{ fontSize: 48, margin: 0, lineHeight: 1.05 }}>
            {label}
          </h1>
        )}
      </div>

      {/* Add type buttons */}
      <div style={{ display: "flex", gap: 14, marginBottom: 28 }}>
        {CARD_TYPES.map(qt => {
          const active = adding === qt.type;
          return (
            <button key={qt.type}
              onClick={() => setAdding(active ? null : qt.type)}
              style={{
                flex: 1,
                display: "flex", flexDirection: "column", alignItems: "center", gap: 7,
                padding: "14px 8px 12px",
                borderRadius: 12,
                background: active ? "var(--bg-sunk)" : "var(--bg-elev)",
                boxShadow: active ? "none" : "0 1px 2px rgba(0,0,0,.04), 0 4px 20px -6px rgba(0,0,0,.08)",
                color: active ? "var(--fg)" : "var(--fg-3)",
                cursor: "pointer",
                transition: "background 150ms, box-shadow 150ms",
              }}>
              <span style={{ display: "flex" }}>{cardIcon(qt.type, 24)}</span>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase" }}>{qt.label}</span>
            </button>
          );
        })}
      </div>

      {/* Add form */}
      {adding && (
        <AddCardForm type={adding} onSubmit={handleAdd} onCancel={() => setAdding(null)} />
      )}

      {/* Empty state */}
      {cards.length === 0 && !adding && (
        <div style={{
          padding: "100px 0", textAlign: "center",
          display: "flex", flexDirection: "column", alignItems: "center", gap: 14,
          color: "var(--fg-3)",
        }}>
          <svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="0.9" style={{ opacity: 0.15 }}>
            <circle cx="12" cy="5.5" r="3.5" />
            <circle cx="5" cy="18" r="3.5" />
            <circle cx="19" cy="18" r="3.5" />
          </svg>
          <p style={{ margin: 0, fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: 17 }}>
            Nothing here yet.
          </p>
          <p style={{ margin: 0, fontSize: 13 }}>
            Attach a note, image, quote, link, or video to this {parentType}.
          </p>
        </div>
      )}

      {/* Masonry grid */}
      {cards.length > 0 && (() => {
        const items = [];
        cards.forEach((q, i) => {
          if (draggedId && dropBeforeId === q.id && draggedId !== q.id) {
            items.push(<div key="__drop_ph__" className="qk-drop-placeholder" />);
          }
          items.push(
            <CardCard
              key={q.id}
              card={q}
              isDragging={draggedId === q.id}
              onDragStart={() => handleDragStart(q.id)}
              onDragOver={handleDragOver}
              onDragEnd={handleDragEnd}
              onDelete={handleDelete}
              onUpdate={handleUpdate}
              animDelay={draggedId ? 0 : i * 35}
              onOpen={(q) => setSelectedCardId(q.id)}
            />
          );
        });
        if (draggedId && dropBeforeId === null) {
          items.push(<div key="__drop_ph__" className="qk-drop-placeholder" />);
        }
        return (
          <div className="qk-grid"
            onDragOver={(e) => e.preventDefault()}
            onDrop={(e) => { e.preventDefault(); handleDrop(); }}>
            {items}
          </div>
        );
      })()}

      <div style={{ height: 80 }} />

      {selectedCard && (
        <CardModal
          card={selectedCard}
          onClose={() => setSelectedCardId(null)}
          onUpdate={handleUpdate}
          onDelete={(id) => { handleDelete(id); setSelectedCardId(null); }}
          onOpenConcept={onOpenConcept}
          concepts={concepts}
          onMove={handleMove}
        />
      )}

    </div>
  );
}


// ── AddHubCardModal — pick concept + level + type then add ──────
function AddHubCardModal({ concepts, onClose, onAdd, initialConceptId, initialParentType, initialParentId, inline }) {
  const [conceptQuery, setConceptQuery] = _qp_us('');
  const [conceptId, setConceptId] = _qp_us(initialConceptId || '');
  const [parentType, setParentType] = _qp_us(initialParentType || '');
  const [parentId, setParentId] = _qp_us(initialParentId || '');
  const [cardType, setCardType] = _qp_us('note');
  const [content, setContent] = _qp_us('');
  const [caption, setCaption] = _qp_us('');
  const [source, setSource] = _qp_us('');
  const [showDrop, setShowDrop] = _qp_us(false);
  const imgUploadRef = _qp_ur(null);

  function handleImgUpload(e) {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = ev => setContent(ev.target.result);
    reader.readAsDataURL(file);
  }

  const selectedConcept = concepts.find(c => c.id === conceptId) || null;

  const suggestions = conceptQuery.trim()
    ? concepts.filter(c => c.title.toLowerCase().includes(conceptQuery.toLowerCase())).slice(0, 6)
    : [];

  function parentItems() {
    if (!selectedConcept || !parentType) return [];
    if (parentType === 'consist') return [{ id: selectedConcept.id + '_consist', label: selectedConcept.consist || '(empty)' }];
    if (parentType === 'function') return [{ id: selectedConcept.id + '_function', label: selectedConcept.function || '(empty)' }];
    if (parentType === 'perspective') return (selectedConcept.perspectives || []).map(p => ({ id: p.id, label: p.title || p.body || p.id }));
    if (parentType === 'pattern') return (selectedConcept.patterns || []).map(p => ({ id: p.id, label: p.title || p.body || p.id }));
    if (parentType === 'essence') return (selectedConcept.essences || []).map(e => ({ id: e.id, label: e.body || e.id }));
    return [];
  }

  _qp_ue(() => {
    if (initialParentId && parentId === initialParentId) return;
    const items = parentItems();
    setParentId(items.length === 1 ? items[0].id : '');
  }, [conceptId, parentType]);

  _qp_ue(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  function getRealParentId() {
    if (parentType === 'consist') return selectedConcept.id;
    if (parentType === 'function') return selectedConcept.id;
    return parentId;
  }

  function canSubmit() {
    if (!conceptId || !parentType || !parentId) return false;
    if (cardType === 'image') return !!(content.trim() || caption.trim());
    return !!content.trim();
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!canSubmit()) return;
    var finalContent = cardType === 'video'
      ? (function () {
        var m = content.match(/(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/)|youtu\.be\/)([A-Za-z0-9_-]{11})/);
        return m ? 'https://www.youtube.com/embed/' + m[1] : content.trim();
      })()
      : content.trim();
    onAdd({
      parent_type: parentType,
      parent_id: getRealParentId(),
      concept_id: conceptId,
      type: cardType,
      content: finalContent,
      caption: caption.trim(),
      source: source.trim(),
    });
    onClose();
  }

  const LBL_S = { fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--fg-3)', marginBottom: 7, display: 'block' };
  const LEVEL_COLORS = { consist: 'var(--c-consist)', function: 'var(--c-function)', perspective: 'var(--c-perspective)', pattern: 'var(--c-pattern)', essence: 'var(--c-essence)' };
  const items = parentItems();

  const formContent = (
    <>

      <div style={{ fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 22, color: 'var(--fg)' }}>Add a card</div>

      {/* Concept */}
      {!inline && (
        <div style={{ position: 'relative' }}>
          <label style={LBL_S}>Concept</label>
          <input
            value={conceptQuery}
            onChange={e => { setConceptQuery(e.target.value); setConceptId(''); setShowDrop(true); }}
            onFocus={() => setShowDrop(true)}
            onBlur={() => setTimeout(() => setShowDrop(false), 150)}
            placeholder="Search concept…"
            autoFocus
            style={{ width: '100%', background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '10px 14px', fontSize: 15, fontFamily: 'var(--font-serif)', outline: 'none', color: 'var(--fg)' }}
          />
          {showDrop && suggestions.length > 0 && (
            <div style={{ position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 12, overflow: 'hidden', zIndex: 10, boxShadow: '0 8px 24px rgba(0,0,0,.1)' }}>
              {suggestions.map(c => (
                <div key={c.id} onMouseDown={() => { setConceptId(c.id); setConceptQuery(c.title); setShowDrop(false); setParentType(''); }}
                  style={{ padding: '9px 14px', cursor: 'pointer', fontSize: 14, display: 'flex', flexDirection: 'column', gap: 2 }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-sunk)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >
                  <span style={{ fontWeight: 500 }}>{c.title}</span>
                  {c.category && <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--fg-3)', letterSpacing: '.06em' }}>{c.category}</span>}
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* Level */}
      {conceptId && (
        <div>
          <label style={LBL_S}>Level</label>
          <div style={{ display: 'flex', gap: 30, justifyContent: 'center' }}>
            {['consist', 'function', 'perspective', 'pattern', 'essence'].map(t => {
              const active = parentType === t;
              const _isNat = !!document.querySelector('.app-shell[data-nature]');
              return (
                <button key={t} type="button" title={t} onClick={() => setParentType(t)} style={{
                  width: 26, height: 26, borderRadius: '50%', padding: 0, cursor: 'pointer', flexShrink: 0,
                  background: _isNat ? (active ? 'rgba(255,255,255,0.2)' : 'transparent') : (active ? LEVEL_COLORS[t] : 'var(--bg-elev)'),
                  border: _isNat ? '1.5px solid rgba(255,255,255,0.6)' : ('2.5px solid ' + LEVEL_COLORS[t]),
                  transition: 'all 140ms',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  {_isNat && <window.LevelShape level={t} size={12} color={active ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.4)'} />}
                </button>
              );
            })}
          </div>
        </div>
      )}

      {/* Sub-item picker (if multiple) */}
      {conceptId && parentType && items.length > 1 && (
        <div>
          <label style={LBL_S}>{parentType}</label>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 140, overflowY: 'auto' }}>
            {items.map(item => {
              const _isNat2 = !!document.querySelector('.app-shell[data-nature]');
              return (
                <button key={item.id} type="button" onClick={() => setParentId(item.id)} style={{
                  textAlign: 'left', padding: '8px 12px', borderRadius: 8,
                  border: parentId === item.id
                    ? (_isNat2 ? '1.5px solid rgba(255,255,255,0.7)' : ('1.5px solid ' + LEVEL_COLORS[parentType]))
                    : (_isNat2 ? '1px solid rgba(255,255,255,0.2)' : '1px solid var(--border-soft)'),
                  background: parentId === item.id
                    ? (_isNat2 ? 'rgba(255,255,255,0.15)' : ('color-mix(in oklab, ' + LEVEL_COLORS[parentType] + ' 10%, var(--bg-elev))'))
                    : 'transparent',
                  fontSize: 13, fontFamily: 'var(--font-ui)', color: 'var(--fg)',
                  cursor: 'pointer', transition: 'all 140ms',
                }}>{item.label}</button>
              );
            })}
          </div>
        </div>
      )}

      {/* Card type */}
      {conceptId && parentType && parentId && (
        <div>
          <label style={LBL_S}>Card type</label>
          <div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>
            {CARD_TYPES.map(qt => {
              const active = cardType === qt.type;
              return (
                <button key={qt.type} type="button" title={qt.label} onClick={() => setCardType(qt.type)} style={{
                  width: 40, height: 40, borderRadius: '50%', border: 0, padding: 0,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  background: active ? 'var(--bg-sunk)' : 'transparent',
                  boxShadow: 'none',
                  color: active ? 'var(--fg)' : 'var(--fg-3)',
                  cursor: 'pointer', transition: 'all 140ms',
                }}>
                  {cardIcon(qt.type, 24)}
                </button>
              );
            })}
          </div>
        </div>
      )}

      {/* Content */}
      {conceptId && parentType && parentId && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {(cardType === 'note' || cardType === 'quote') && (
            <textarea value={content} onChange={e => setContent(e.target.value)} placeholder={cardType === 'quote' ? 'The exact words…' : 'Write your note…'} rows={4}
              style={{ width: '100%', background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '10px 14px', fontSize: 14, resize: 'none', outline: 'none', color: 'var(--fg)', fontFamily: 'var(--font-ui)' }}
            />
          )}
          {(cardType === 'image' || cardType === 'link' || cardType === 'video') && (
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              <input value={content} onChange={e => setContent(e.target.value)} placeholder="https://…"
                style={{ flex: 1, background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '10px 14px', fontSize: 13, outline: 'none', color: 'var(--fg)', fontFamily: 'var(--font-mono)' }}
              />
              {cardType === 'image' && (
                <>
                  <input ref={imgUploadRef} type="file" accept="image/*" style={{ display: 'none' }} onChange={handleImgUpload} />
                  <button type="button" onClick={() => imgUploadRef.current && imgUploadRef.current.click()}
                    style={{ flexShrink: 0, background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'var(--fg-3)' }}
                    title="Upload image">
                    {window.Icons.Image(20)}
                  </button>
                </>
              )}
            </div>
          )}
          {cardType === 'quote' && (
            <input value={source} onChange={e => setSource(e.target.value)} placeholder="Source…"
              style={{ width: '100%', background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '10px 14px', fontSize: 13, outline: 'none', color: 'var(--fg)' }}
            />
          )}
          {(cardType === 'image' || cardType === 'video' || cardType === 'link') && (
            <input value={caption} onChange={e => setCaption(e.target.value)} placeholder="Caption (optional)…"
              style={{ width: '100%', background: 'var(--bg-sunk)', border: 'none', borderRadius: 10, padding: '10px 14px', fontSize: 13, outline: 'none', color: 'var(--fg)' }}
            />
          )}
        </div>
      )}

      {/* Actions */}
      <div style={{ display: 'flex', gap: 16, justifyContent: 'center', alignItems: 'center', paddingTop: 4 }}>
        <button type="button" onClick={onClose}
          style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
          title="Cancel">
          {window.Icons.Close(22)}
        </button>
        <button type="submit" disabled={!canSubmit()}
          style={{ background: 'none', border: 'none', cursor: canSubmit() ? 'pointer' : 'not-allowed', color: canSubmit() ? 'var(--fg)' : 'var(--fg-3)', padding: 6, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: canSubmit() ? 1 : 0.4 }}
          title="Add card">
          {window.Icons.Mark(22)}
        </button>
      </div>
    </>
  );

  if (inline) return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      {formContent}
    </form>
  );

  const _nature = !!document.querySelector('.app-shell[data-nature]');
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 300, background: _nature ? 'transparent' : 'rgba(0,0,0,0.4)', backdropFilter: _nature ? undefined : 'blur(18px)', WebkitBackdropFilter: _nature ? undefined : 'blur(18px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 32 }}>
      <form onClick={e => e.stopPropagation()} onSubmit={handleSubmit} className="fade-up" style={{ width: '100%', maxWidth: 520, background: _nature ? 'var(--glass-bg)' : 'var(--bg-elev)', backdropFilter: _nature ? 'blur(22px) saturate(1.2)' : undefined, WebkitBackdropFilter: _nature ? 'blur(22px) saturate(1.2)' : undefined, borderRadius: 18, padding: 28, display: 'flex', flexDirection: 'column', gap: 20, boxShadow: _nature ? 'none' : '0 24px 80px rgba(0,0,0,0.25)' }}>
        {formContent}
      </form>
    </div>
  );
}

// ── Cards3DUniverse — spherical 3D card browser ──────────────────

function Cards3DUniverse({ cards, conceptMap, onOpenCard }) {
  const containerRef = _qp_ur(null);
  const itemEls = _qp_ur([]);
  const rotX = _qp_ur(0.22);
  const rotY = _qp_ur(0);
  const velX = _qp_ur(0);
  const velY = _qp_ur(0);
  const isDragging = _qp_ur(false);
  const lastMouse = _qp_ur({ x: 0, y: 0 });
  const rafRef = _qp_ur(null);

  const RADIUS = 340;
  const FOCAL = 860;
  const W = 148, H = 108;

  const positions = (function () {
    const n = cards.length;
    if (n === 0) return [];
    const pts = [];
    const golden = Math.PI * (3 - Math.sqrt(5));
    for (let i = 0; i < n; i++) {
      const y = n === 1 ? 0 : 1 - (i / (n - 1)) * 2;
      const r = Math.sqrt(Math.max(0, 1 - y * y));
      const theta = golden * i;
      pts.push([Math.cos(theta) * r, y, Math.sin(theta) * r]);
    }
    return pts;
  })();

  _qp_ue(function () {
    const container = containerRef.current;
    if (!container) return;
    itemEls.current = [];

    function project(x, y, z) {
      const w = container.offsetWidth, h = container.offsetHeight;
      const cx = w / 2, cy = h / 2;
      const rx = rotX.current, ry = rotY.current;
      const cRy = Math.cos(ry), sRy = Math.sin(ry);
      const cRx = Math.cos(rx), sRx = Math.sin(rx);
      const x1 = x * cRy + z * sRy;
      const z1 = -x * sRy + z * cRy;
      const y2 = y * cRx - z1 * sRx;
      const z2 = y * sRx + z1 * cRx;
      const sc = FOCAL / (FOCAL + z2 + RADIUS * 0.4);
      return { px: cx + x1 * sc, py: cy + y2 * sc, scale: sc, z: z2 };
    }

    function tick() {
      if (!isDragging.current) {
        velX.current *= 0.94;
        velY.current *= 0.94;
        rotX.current += velX.current;
        rotY.current += velY.current + 0.0022;
      }
      const projs = positions.map(function (pos, i) {
        const p = project(pos[0] * RADIUS, pos[1] * RADIUS, pos[2] * RADIUS);
        return { i: i, px: p.px, py: p.py, scale: p.scale, z: p.z };
      });
      projs.sort(function (a, b) { return a.z - b.z; });
      projs.forEach(function (p, order) {
        const el = itemEls.current[p.i];
        if (!el) return;
        const opacity = Math.max(0.1, Math.min(1, (p.scale - 0.3) * 2.0 + 0.15));
        el.style.transform = 'translate(' + (p.px - W / 2) + 'px,' + (p.py - H / 2) + 'px) scale(' + p.scale + ')';
        el.style.opacity = opacity;
        el.style.zIndex = order;
        el.style.pointerEvents = p.scale > 0.52 ? 'auto' : 'none';
      });
      rafRef.current = requestAnimationFrame(tick);
    }
    tick();

    function onDown(e) {
      isDragging.current = true;
      lastMouse.current = { x: e.clientX, y: e.clientY };
      velX.current = 0; velY.current = 0;
      container.style.cursor = 'grabbing';
    }
    function onMove(e) {
      if (!isDragging.current) return;
      const dx = e.clientX - lastMouse.current.x;
      const dy = e.clientY - lastMouse.current.y;
      lastMouse.current = { x: e.clientX, y: e.clientY };
      velY.current = dx * 0.005;
      velX.current = dy * 0.005;
      rotY.current += dx * 0.005;
      rotX.current += dy * 0.005;
    }
    function onUp() {
      isDragging.current = false;
      container.style.cursor = 'grab';
    }
    function onTouchStart(e) {
      isDragging.current = true;
      lastMouse.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
      velX.current = 0; velY.current = 0;
    }
    function onTouchMove(e) {
      if (!isDragging.current) return;
      e.preventDefault();
      const dx = e.touches[0].clientX - lastMouse.current.x;
      const dy = e.touches[0].clientY - lastMouse.current.y;
      lastMouse.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
      velY.current = dx * 0.005;
      velX.current = dy * 0.005;
      rotY.current += dx * 0.005;
      rotX.current += dy * 0.005;
    }

    container.addEventListener('mousedown', onDown);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    container.addEventListener('touchstart', onTouchStart, { passive: true });
    container.addEventListener('touchmove', onTouchMove, { passive: false });
    container.addEventListener('touchend', onUp);
    return function () {
      cancelAnimationFrame(rafRef.current);
      container.removeEventListener('mousedown', onDown);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      container.removeEventListener('touchstart', onTouchStart);
      container.removeEventListener('touchmove', onTouchMove);
      container.removeEventListener('touchend', onUp);
    };
  }, [cards.length]);

  return (
    <div ref={containerRef} style={{
      position: 'absolute', inset: 0,
      cursor: 'grab', overflow: 'hidden', userSelect: 'none',
      background: 'var(--bg)',
    }}>
      {cards.map(function (card, i) {
        const lvlColor = CARD_LEVEL_COLOR[card.parent_type] || 'var(--fg-3)';
        let thumb = null;
        if (card.type === 'image' && card.content) {
          thumb = <img src={card.content} alt='' style={{ width: '100%', height: '100%', objectFit: 'cover' }} />;
        } else if (card.type === 'note') {
          thumb = <div style={{ padding: '9px 11px', fontSize: 10.5, lineHeight: 1.52, overflow: 'hidden', color: 'var(--fg)' }}>{card.content}</div>;
        } else if (card.type === 'quote') {
          thumb = <div style={{ padding: '9px 11px', fontSize: 10, fontStyle: 'italic', lineHeight: 1.5, overflow: 'hidden', color: 'var(--fg)' }}>&#8220;{card.content}&#8221;</div>;
        } else if (card.type === 'link') {
          thumb = <div style={{ padding: '9px 11px', fontSize: 10, color: !!document.querySelector('.app-shell[data-nature]') ? 'rgba(255,255,255,0.7)' : 'var(--c-function)', overflow: 'hidden', wordBreak: 'break-all' }}>{card.caption || card.content}</div>;
        } else if (card.type === 'video') {
          thumb = (
            <div style={{ width: '100%', height: '100%', background: '#0a0a0f', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <svg width='26' height='26' viewBox='0 0 24 24' fill='rgba(255,255,255,0.7)'><polygon points='5,3 19,12 5,21' /></svg>
            </div>
          );
        }
        return (
          <div
            key={card.id}
            ref={function (el) { itemEls.current[i] = el; }}
            onClick={function () { onOpenCard && onOpenCard(card); }}
            style={{
              position: 'absolute', left: 0, top: 0,
              width: W, height: H,
              borderRadius: 11,
              background: 'var(--bg-elev)',
              border: '1px solid var(--border-soft)',
              borderBottom: '3px solid ' + lvlColor,
              overflow: 'hidden',
              cursor: 'pointer',
              boxShadow: '0 2px 12px rgba(0,0,0,0.14)',
              willChange: 'transform, opacity',
              transformOrigin: 'center center',
            }}
          >
            {thumb}
          </div>
        );
      })}
    </div>
  );
}

// ── CardsHubScreen — full-screen interactive card feed ─────────

function _QhSlider({ label, value, min, max, step, onChange }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>
        <span>{label}</span><span>{value}</span>
      </div>
      <input type="range" min={min} max={max} step={step} value={value}
        onChange={e => onChange(Number(e.target.value))}
        style={{ width: "100%", cursor: "pointer" }} />
    </div>
  );
}

function CardsHubScreen({ concepts, initialConceptId, onOpenConcept, onOpenSolar, onOpenGraph, onOpenGroup, onBack }) {
  const _qs = (() => { try { return JSON.parse(localStorage.getItem('disene_qhub_settings')) || {}; } catch { return {}; } })();

  const [allCards, setAllCards] = _qp_us([]);
  const [conceptFilter, setConceptFilter] = _qp_us(() => {
    if (!initialConceptId) return null;
    const c = concepts.find(c => c.id === initialConceptId);
    return c ? c.title : null;
  });
  const [query, setQuery] = _qp_us(() => {
    if (!initialConceptId) return '';
    const c = concepts.find(c => c.id === initialConceptId);
    return c ? c.title : '';
  });
  const [dropOpen, setDropOpen] = _qp_us(false);
  const searchInputRef = _qp_ur(null);
  const [dropRect, setDropRect] = _qp_us(null);

  function openDrop(el) {
    const target = el || searchInputRef.current;
    if (target) {
      const r = target.getBoundingClientRect();
      setDropRect({ top: r.bottom + 8, left: r.left });
    }
    setDropOpen(true);
  }
  const [catFilter, setCatFilter] = _qp_us(null);
  const [typeFilter, setTypeFilter] = _qp_us(null);
  const [cols, setCols] = _qp_us(_qs.cols ?? 3);
  const [showSettings, setShowSettings] = _qp_us(false);
  const [selectedCardId, setSelectedCardId] = _qp_us(null);
  const selectedCard = selectedCardId ? allCards.find(q => q.id === selectedCardId) : null;
  const [showAddModal, setShowAddModal] = _qp_us(false);
  const [cardFavorites, setCardFavorites] = _qp_us(() => {
    try {
      const stored = localStorage.getItem('senes-card-favorites');
      return stored ? new Set(JSON.parse(stored)) : new Set();
    } catch { return new Set(); }
  });
  const [favFilter, setFavFilter] = _qp_us(false);
  const [show3D, setShow3D] = _qp_us(false);
  const [scrolled, setScrolled] = _qp_us(false);
  const scrollRef = _qp_ur(null);

  _qp_ue(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => setScrolled(el.scrollTop > 32);
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => el.removeEventListener('scroll', onScroll);
  }, []);

  function toggleCardFavorite(id) {
    setCardFavorites(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      try { localStorage.setItem('senes-card-favorites', JSON.stringify([...next])); } catch { }
      return next;
    });
  }

  _qp_ue(() => {
    localStorage.setItem('disene_qhub_settings', JSON.stringify({ cols }));
  }, [cols]);

  _qp_ue(() => {
    if (!window.cardsService) return;
    window.cardsService.getAllCards().then(rows => {
      const s = rows.slice();
      for (let i = s.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [s[i], s[j]] = [s[j], s[i]];
      }
      setAllCards(s);
    });
  }, []);

  const conceptMap = {};
  concepts.forEach(c => { conceptMap[c.id] = c; });

  const allCategories = [...new Set(concepts.map(c => c.category).filter(Boolean))].sort();
  const q = query.trim().toLowerCase();
  const conceptSuggestions = q ? concepts.filter(c => c.title.toLowerCase().includes(q)).slice(0, 7) : [];
  const catSuggestions = q ? allCategories.filter(cat => cat.toLowerCase().includes(q)).slice(0, 7) : [];

  const activeConceptId = conceptFilter ? (concepts.find(c => c.title === conceptFilter) || {}).id : null;
  const selectedConcept = activeConceptId ? concepts.find(c => c.id === activeConceptId) : null;

  const filtered = allCards.filter(qk => {
    const c = conceptMap[qk.concept_id];
    if (conceptFilter && (!c || c.title !== conceptFilter)) return false;
    if (catFilter && (!c || c.category !== catFilter)) return false;
    if (typeFilter && qk.type !== typeFilter) return false;
    if (favFilter && !cardFavorites.has(qk.id)) return false;
    return true;
  });

  const hasFilter = !!(conceptFilter || catFilter || q);
  const showDrop = dropOpen && q.length > 0;

  async function handleUpdate(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setAllCards(qs => qs.map(qk => qk.id === id ? { ...qk, ...changes, updated_at: new Date().toISOString() } : qk));
  }
  async function handleMove(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setAllCards(qs => qs.map(qk => qk.id === id ? { ...qk, ...changes, updated_at: new Date().toISOString() } : qk));
  }
  async function handleDelete(id) {
    await window.cardsService.deleteCard(id);
    setAllCards(qs => qs.filter(qk => qk.id !== id));
  }

  async function handleHubAdd(cardData) {
    const now = new Date().toISOString();
    const newCard = {
      id: 'qk' + Date.now() + Math.random().toString(36).slice(2, 6),
      sort_order: (allCards.length + 1) * 1000,
      created_at: now, updated_at: now,
      ...cardData,
    };
    await window.cardsService.addCard(newCard);
    setAllCards(qs => [newCard, ...qs]);
  }

  function pickConcept(c) { setConceptFilter(c.title); setQuery(c.title); setDropOpen(false); }
  function pickCategory(cat) { setCatFilter(cat); setQuery(cat); setDropOpen(false); }
  function clearFilters() { setConceptFilter(null); setCatFilter(null); setQuery(''); }

  const navDim = { border: 0, background: 'transparent', color: 'var(--fg-3)', opacity: selectedConcept ? 1 : 0.35 };
  const navActive = { border: 0, background: 'radial-gradient(ellipse at 12% 48%, hsla(190, 90%, 78%, 0.80) 0%, transparent 46%), radial-gradient(ellipse at 68% 42%, hsla(318, 62%, 86%, 0.72) 0%, transparent 46%), radial-gradient(ellipse at 28% 82%, hsla(165, 72%, 82%, 0.65) 0%, transparent 42%), radial-gradient(ellipse at 88% 68%, hsla(270, 68%, 83%, 0.72) 0%, transparent 44%), radial-gradient(ellipse at 55% 62%, hsla(225, 55%, 85%, 0.55) 0%, transparent 40%), hsl(248, 45%, 88%)', boxShadow: '0 1px 3px rgba(0,0,0,.15)', color: 'rgba(0,0,0,0.8)' };

  return (
    <div style={{ position: 'relative', height: '100vh', overflow: 'hidden', background: 'var(--app-bg, var(--bg))' }}>

      {/* Floating toolbar */}
      <div className={!scrolled ? 'hub-toolbar' : 'hub-toolbar-scrolled'} style={{
        position: 'fixed',
        top: scrolled ? 8 : 0,
        left: scrolled ? '50%' : 92,
        right: scrolled ? 'auto' : 8,
        transform: scrolled ? 'translateX(-50%)' : undefined,
        height: scrolled ? 62 : 76,
        borderRadius: scrolled ? 9999 : 0,
        background: scrolled ? 'color-mix(in oklab, var(--bg-elev) 92%, transparent)' : 'transparent',
        backdropFilter: 'blur(18px)',
        WebkitBackdropFilter: 'blur(18px)',
        boxShadow: scrolled ? '0 2px 8px rgba(0,0,0,.06), 0 8px 28px -8px rgba(0,0,0,.1)' : 'none',
        border: '1px solid ' + (scrolled ? 'var(--border-soft)' : 'transparent'),
        display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 30, padding: '0 24px',
        transition: 'top 300ms ease, height 300ms ease, border-radius 300ms ease, background 300ms ease, box-shadow 300ms ease, border-color 300ms ease',
      }}>
        {!scrolled ? (
          <>
            {/* Fullwidth: search and filters at original 25vw / 75vw positions */}
            <div className='hub-tb-search' style={{ position: 'absolute', left: 'calc(25vw - 92px)', top: '50%', transform: 'translate(-50%, -50%)', display: 'flex', alignItems: 'center', gap: 7 }}>
              <input
                ref={searchInputRef}
                value={query}
                onChange={e => { setQuery(e.target.value); setConceptFilter(null); setCatFilter(null); openDrop(e.target); }}
                onFocus={e => { if (q) openDrop(e.target); }}
                onBlur={() => setTimeout(() => setDropOpen(false), 160)}
                onKeyDown={e => { if (e.key === 'Escape') { clearFilters(); setDropOpen(false); } }}
                placeholder="Search…"
                className="cards-hub-search"
                size={Math.max((query || 'search…').length, 7)}
                style={{ background: 'transparent', border: 'none', outline: 'none', fontSize: 38, fontFamily: 'var(--font-serif)', fontStyle: 'italic', color: 'var(--fg)', padding: '0px 0' }}
              />
              {hasFilter && (
                <button onClick={clearFilters} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: '0 2px', lineHeight: 1, opacity: 0.6, display: 'flex' }}>
                  {window.Icons.Close(16)}
                </button>
              )}
            </div>
            <div className='hub-tb-filters' style={{ position: 'absolute', left: 'calc(75vw - 92px)', top: '50%', transform: 'translate(-50%, -50%)', display: 'flex', alignItems: 'center', gap: 8 }}>
              <button onClick={() => setFavFilter(v => !v)} title="Favorites" style={{ border: 0, background: 'none', boxShadow: 'none', cursor: 'pointer', borderRadius: 6, lineHeight: 1, padding: '0 6px', color: 'var(--fg-3)' }}><svg width='24' height='24' viewBox='0 0 32 32' fill='none'><defs><linearGradient id='star-holo-cards' x1='0' y1='0' x2='1' y2='1'><stop offset='0%' stopColor='hsla(190,90%,68%,1)' /><stop offset='40%' stopColor='hsla(318,62%,76%,1)' /><stop offset='80%' stopColor='hsla(165,72%,72%,1)' /><stop offset='100%' stopColor='hsla(270,68%,73%,1)' /></linearGradient></defs><path d='M28.6876 14.125L20.7588 11.25L17.8751 3.3125C17.733 2.92974 17.4772 2.59964 17.142 2.36653C16.8068 2.13342 16.4083 2.00847 16.0001 2.00847C15.5918 2.00847 15.1933 2.13342 14.8581 2.36653C14.523 2.59964 14.2671 2.92974 14.1251 3.3125L11.2501 11.2412L3.31257 14.125C2.92982 14.2671 2.59971 14.5229 2.3666 14.8581C2.13349 15.1932 2.00854 15.5917 2.00854 16C2.00854 16.4083 2.13349 16.8068 2.3666 17.1419C2.59971 17.4771 2.92982 17.7329 3.31257 17.875L11.2413 20.75L14.1251 28.6875C14.2671 29.0703 14.523 29.4004 14.8581 29.6335C15.1933 29.8666 15.5918 29.9915 16.0001 29.9915C16.4083 29.9915 16.8068 29.8666 17.142 29.6335C17.4772 29.4004 17.733 29.0703 17.8751 28.6875L20.7588 20.7575L28.6876 17.875C29.0703 17.7329 29.4004 17.4771 29.6335 17.1419C29.8667 16.8068 29.9916 16.4083 29.9916 16C29.9916 15.5917 29.8667 15.1932 29.6335 14.8581C29.4004 14.5229 29.0703 14.2671 28.6876 14.125Z' fill={favFilter ? 'url(#star-holo-cards)' : 'none'} stroke={favFilter ? 'none' : 'var(--fg-3)'} strokeWidth='1.8' /></svg></button>
              {CARD_TYPES.map(qt => {
                const active = typeFilter === qt.type;
                return (
                  <button key={qt.type || 'all'} onClick={() => setTypeFilter(active ? null : qt.type)} className="btn tiny"
                    style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: active ? 'var(--bg-elev)' : 'transparent', boxShadow: 'none', color: active ? 'var(--fg)' : 'var(--fg-3)' }}>
                    {qt.type ? <span style={{ display: 'flex', color: 'inherit' }}>{cardIcon(qt.type, 24)}</span> : <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', letterSpacing: '.04em' }}>All</span>}
                  </button>
                );
              })}
            </div>
          </>
        ) : (
          /* Pill: search + filters grouped and centered */
          <div style={{ display: 'flex', alignItems: 'center', gap: 48 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
              <input
                value={query}
                onChange={e => { setQuery(e.target.value); setConceptFilter(null); setCatFilter(null); openDrop(e.target); }}
                onFocus={e => { if (q) openDrop(e.target); }}
                onBlur={() => setTimeout(() => setDropOpen(false), 160)}
                onKeyDown={e => { if (e.key === 'Escape') { clearFilters(); setDropOpen(false); } }}
                placeholder="Search…"
                className="cards-hub-search"
                size={Math.max((query || 'search…').length, 7)}
                style={{ background: 'transparent', border: 'none', outline: 'none', fontSize: 38, fontFamily: 'var(--font-serif)', fontStyle: 'italic', color: 'var(--fg)', padding: '0px 0' }}
              />
              {hasFilter && (
                <button onClick={clearFilters} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--fg-3)', padding: '0 2px', lineHeight: 1, opacity: 0.6, display: 'flex' }}>
                  {window.Icons.Close(16)}
                </button>
              )}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <button onClick={() => setFavFilter(v => !v)} title="Favorites" style={{ border: 0, background: 'none', boxShadow: 'none', cursor: 'pointer', borderRadius: 6, lineHeight: 1, padding: '0 6px', color: 'var(--fg-3)' }}><svg width='24' height='24' viewBox='0 0 32 32' fill='none'><defs><linearGradient id='star-holo-cards2' x1='0' y1='0' x2='1' y2='1'><stop offset='0%' stopColor='hsla(190,90%,68%,1)' /><stop offset='40%' stopColor='hsla(318,62%,76%,1)' /><stop offset='80%' stopColor='hsla(165,72%,72%,1)' /><stop offset='100%' stopColor='hsla(270,68%,73%,1)' /></linearGradient></defs><path d='M28.6876 14.125L20.7588 11.25L17.8751 3.3125C17.733 2.92974 17.4772 2.59964 17.142 2.36653C16.8068 2.13342 16.4083 2.00847 16.0001 2.00847C15.5918 2.00847 15.1933 2.13342 14.8581 2.36653C14.523 2.59964 14.2671 2.92974 14.1251 3.3125L11.2501 11.2412L3.31257 14.125C2.92982 14.2671 2.59971 14.5229 2.3666 14.8581C2.13349 15.1932 2.00854 15.5917 2.00854 16C2.00854 16.4083 2.13349 16.8068 2.3666 17.1419C2.59971 17.4771 2.92982 17.7329 3.31257 17.875L11.2413 20.75L14.1251 28.6875C14.2671 29.0703 14.523 29.4004 14.8581 29.6335C15.1933 29.8666 15.5918 29.9915 16.0001 29.9915C16.4083 29.9915 16.8068 29.8666 17.142 29.6335C17.4772 29.4004 17.733 29.0703 17.8751 28.6875L20.7588 20.7575L28.6876 17.875C29.0703 17.7329 29.4004 17.4771 29.6335 17.1419C29.8667 16.8068 29.9916 16.4083 29.9916 16C29.9916 15.5917 29.8667 15.1932 29.6335 14.8581C29.4004 14.5229 29.0703 14.2671 28.6876 14.125Z' fill={favFilter ? 'url(#star-holo-cards2)' : 'none'} stroke={favFilter ? 'none' : 'var(--fg-3)'} strokeWidth='1.8' /></svg></button>
              {CARD_TYPES.map(qt => {
                const active = typeFilter === qt.type;
                return (
                  <button key={qt.type || 'all'} onClick={() => setTypeFilter(active ? null : qt.type)} className="btn tiny"
                    style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: active ? 'var(--bg-elev)' : 'transparent', boxShadow: 'none', color: active ? 'var(--fg)' : 'var(--fg-3)' }}>
                    {qt.type ? <span style={{ display: 'flex', color: 'inherit' }}>{cardIcon(qt.type, 24)}</span> : <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', letterSpacing: '.04em' }}>All</span>}
                  </button>
                );
              })}
            </div>
          </div>
        )}
      </div>

      {/* Mode-pill — fixed outside backdrop-filter container */}
      <div className="view-mode-sw mode-pill">
        <button className="btn tiny" style={{ ...navDim, width: 36, height: 36, padding: 0, justifyContent: 'center', opacity: 1 }} onClick={() => selectedConcept ? onOpenConcept && onOpenConcept(selectedConcept.id, 'edit') : onBack && onBack()}>{window.Icons.Word(24)}</button>
        <button className="btn tiny" data-active="true" style={{ ...navActive, width: 36, height: 36, padding: 0, justifyContent: 'center' }}>{window.Icons.Cards(24)}</button>
        <button className="btn tiny" style={{ ...navDim, width: 36, height: 36, padding: 0, justifyContent: 'center', opacity: 1 }} onClick={() => onOpenSolar && (selectedConcept ? onOpenSolar(selectedConcept.id) : onOpenSolar(null))}>{window.Icons.Planet(24)}</button>
        <button className="btn tiny" style={{ ...navDim, width: 36, height: 36, padding: 0, justifyContent: 'center', opacity: 1 }} onClick={() => onOpenGraph && onOpenGraph(selectedConcept ? selectedConcept.id : null)}>{window.Icons.Graph(24)}</button>
      </div>

      {/* Scrollable grid */}
      <div ref={scrollRef} className="no-scrollbar hub-scroll-area" style={{ position: 'absolute', top: 0, left: 98, right: 0, bottom: 0, overflowY: 'auto', paddingTop: 74 }}>
        {allCards.length === 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', gap: 12, color: 'var(--fg-3)' }}>
            <p style={{ margin: 0, fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 17 }}>No cards yet.</p>
            <p style={{ margin: 0, fontSize: 13 }}>Open a concept and attach notes, images, quotes, links or videos to its levels.</p>
          </div>
        )}
        {allCards.length > 0 && filtered.length === 0 && (
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: 'var(--fg-3)' }}>
            <p style={{ margin: 0, fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 17 }}>No cards match this filter.</p>
          </div>
        )}
        <div className='hub-card-grid' style={{ columnCount: cols, columnGap: 10, padding: '10px' }}>
          {/* Add card tile */}
          <div onClick={() => setShowAddModal(true)} style={{
            breakInside: 'avoid', display: 'inline-block', width: '100%', marginBottom: 10,
            minHeight: 160, border: '2px dashed var(--border)', borderRadius: 16,
            background: 'transparent', cursor: 'pointer', padding: 20,
            transition: 'border-color 200ms', boxSizing: 'border-box',
          }}
            onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--fg-3)'}
            onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}
          >
            <div style={{ fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 24, color: 'var(--fg-3)' }}>Add new card…</div>
          </div>

          {filtered.map((qk, i) => {
            const c = conceptMap[qk.concept_id];
            const conceptInfo = c ? c.title : null;
            return (
              <CardCard
                key={qk.id}
                card={qk}
                conceptInfo={conceptInfo}
                onDelete={handleDelete}
                onUpdate={handleUpdate}
                animDelay={i * 15}
                onOpen={(qk) => setSelectedCardId(qk.id)}
                isFavorite={cardFavorites.has(qk.id)}
                onToggleFavorite={() => toggleCardFavorite(qk.id)}
              />
            );
          })}
        </div>
      </div>

      {/* 3D Universe overlay */}
      {show3D && (
        <div style={{ position: 'absolute', top: 0, left: 98, right: 0, bottom: 0, zIndex: 10 }}>
          <Cards3DUniverse
            cards={filtered.length > 0 ? filtered : allCards}
            conceptMap={conceptMap}
            onOpenCard={function (card) { setSelectedCardId(card.id); }}
          />
        </div>
      )}

      {/* Settings button */}
      <button className="btn icon hub-settings-btn" onClick={() => setShowSettings(s => !s)} title="Grid settings"
        style={{
          position: 'absolute', bottom: 14, right: 14, zIndex: 20,
          borderRadius: 8, padding: 7, width: 32, height: 32, justifyContent: 'center',
          background: showSettings ? 'var(--bg-sunk)' : 'var(--bg-elev)',
          border: 'none', boxShadow: showSettings ? '0 1px 2px rgba(0,0,0,.04)' : '0 1px 2px rgba(0,0,0,.06), 0 4px 16px -4px rgba(0,0,0,.1)'
        }}>
        {window.Icons.Sliders(24)}
      </button>

      {showSettings && (
        <div style={{
          position: 'absolute', bottom: 54, right: 14, zIndex: 20,
          background: 'var(--bg-elev)',
          borderRadius: 14, padding: '14px 16px',
          boxShadow: '0 4px 24px rgba(0,0,0,.12), 0 1px 4px rgba(0,0,0,.06)',
          minWidth: 180, display: 'flex', flexDirection: 'column', gap: 12,
        }}>
          <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--fg-3)', paddingBottom: 2 }}>Display</div>
          <_QhSlider label="Columns" value={cols} min={1} max={6} step={1} onChange={setCols} />
        </div>
      )}

      {selectedCard && (
        <CardModal
          card={selectedCard}
          onClose={() => setSelectedCardId(null)}
          onUpdate={handleUpdate}
          onDelete={(id) => { handleDelete(id); setSelectedCardId(null); }}
          onOpenConcept={onOpenConcept}
          concepts={concepts}
          onMove={handleMove}
        />
      )}

      {showAddModal && (
        <AddHubCardModal
          concepts={concepts}
          onClose={() => setShowAddModal(false)}
          onAdd={handleHubAdd}
        />
      )}

      {/* Two-column search dropdown — rendered outside toolbar to escape mask-image clipping */}
      {showDrop && dropRect && (
        <div style={{
          position: 'fixed',
          top: dropRect.top,
          left: dropRect.left,
          zIndex: 500,
          background: 'var(--bg-elev)',
          borderRadius: 12, boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
          overflow: 'hidden', display: 'grid', gridTemplateColumns: '1fr 1fr',
          minWidth: 400,
        }}>
          <div style={{ padding: '8px 0' }}>
            <div style={{ padding: '4px 14px 7px', fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.14em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>Concepts</div>
            {conceptSuggestions.length === 0
              ? <div style={{ padding: '5px 14px', fontSize: 12, color: 'var(--fg-3)', fontStyle: 'italic' }}>no matches</div>
              : conceptSuggestions.map(c => (
                <div key={c.id} onMouseDown={() => pickConcept(c)}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-sunk)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  style={{ padding: '7px 14px', cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 1 }}>
                  <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--fg)' }}>{c.title}</span>
                  {c.category && <span style={{ fontSize: 10, color: 'var(--fg-3)', fontFamily: 'var(--font-mono)', letterSpacing: '.04em' }}>{c.category}</span>}
                </div>
              ))
            }
          </div>
          <div style={{ padding: '8px 0' }}>
            <div style={{ padding: '4px 14px 7px', fontSize: 9, fontFamily: 'var(--font-mono)', letterSpacing: '.14em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>Categories</div>
            {catSuggestions.length === 0
              ? <div style={{ padding: '5px 14px', fontSize: 12, color: 'var(--fg-3)', fontStyle: 'italic' }}>no matches</div>
              : catSuggestions.map(cat => (
                <div key={cat} onMouseDown={() => pickCategory(cat)}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-sunk)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                  style={{ padding: '7px 14px', cursor: 'pointer' }}>
                  <span style={{ fontSize: 13, color: 'var(--fg)' }}>{cat}</span>
                </div>
              ))
            }
          </div>
        </div>
      )}
    </div>
  );
}

// ── CardsSidePanel — compact right-rail panel for solar/graph views ─

function CardsSidePanel({ conceptId, parentType, parentId, label, onBack, concepts }) {
  const [cards, setCards] = _qp_us([]);
  const [adding, setAdding] = _qp_us(null);
  const [selectedCardId, setSelectedCardId] = _qp_us(null);
  const selectedCard = selectedCardId ? cards.find(q => q.id === selectedCardId) : null;

  _qp_ue(() => {
    if (window.cardsService) {
      window.cardsService.getCards(parentType, parentId).then(rows => {
        setCards(rows.slice().sort((a, b) => {
          if (a.sort_order != null && b.sort_order != null) return a.sort_order - b.sort_order;
          if (a.sort_order != null) return -1;
          if (b.sort_order != null) return 1;
          return a.created_at < b.created_at ? -1 : 1;
        }));
      });
    }
    setAdding(null);
  }, [parentType, parentId]);

  async function handleAdd(card) {
    const now = new Date().toISOString();
    const newCard = {
      id: "qk" + Date.now() + Math.random().toString(36).slice(2, 6),
      parent_type: parentType,
      parent_id: parentId,
      concept_id: conceptId,
      sort_order: (cards.length + 1) * 1000,
      created_at: now,
      updated_at: now,
      ...card,
    };
    await window.cardsService.addCard(newCard);
    setCards(qs => [...qs, newCard]);
    setAdding(null);
  }

  async function handleUpdate(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(qs => qs.map(q => q.id === id
      ? { ...q, ...changes, updated_at: new Date().toISOString() }
      : q
    ));
  }

  async function handleDelete(id) {
    await window.cardsService.deleteCard(id);
    setCards(qs => qs.filter(q => q.id !== id));
  }
  async function handleMove(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(qs => qs.filter(q => q.id !== id));
    setSelectedCardId(null);
  }

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" }}>

      {/* Header */}
      <div style={{ padding: "20px 20px 14px", flexShrink: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }}>
          {onBack && (
            <button onClick={onBack} className="btn ghost tiny" style={{ padding: "2px 6px", marginRight: 2 }}>
              {window.Icons.ArrowL(11)}
            </button>
          )}
          <window.LevelDot level={parentType} size={6} />
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 9.5, letterSpacing: ".14em", textTransform: "uppercase", color: "var(--fg-3)" }}>
            {parentType}
          </span>
          {cards.length > 0 && (
            <span style={{ marginLeft: "auto", fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--fg-3)" }}>
              {cards.length}
            </span>
          )}
        </div>
        <div style={{ fontSize: 22, fontFamily: "var(--font-serif)", fontStyle: "italic", color: "var(--fg)", lineHeight: 1.35, marginBottom: 14, marginTop: 28 }}>
          {label || parentType}
        </div>
        <div style={{ display: "flex", gap: 6, marginTop: 4 }}>
          {CARD_TYPES.map(qt => {
            const active = adding === qt.type;
            return (
              <button key={qt.type}
                onClick={() => setAdding(active ? null : qt.type)}
                style={{
                  flex: 1,
                  display: "flex", flexDirection: "column", alignItems: "center", gap: 6,
                  padding: "12px 6px 10px",
                  borderRadius: 12,
                  background: active ? "var(--bg-sunk)" : "var(--bg-elev)",
                  boxShadow: active ? "none" : "0 1px 2px rgba(0,0,0,.04), 0 4px 20px -6px rgba(0,0,0,.08)",
                  color: active ? "var(--fg)" : "var(--fg-3)",
                  cursor: "pointer",
                  transition: "background 150ms, box-shadow 150ms",
                }}>
                <span style={{ display: "flex" }}>{cardIcon(qt.type, 24)}</span>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: ".08em", textTransform: "uppercase" }}>{qt.label}</span>
              </button>
            );
          })}
        </div>
      </div>

      {/* Scrollable content */}
      <div style={{ flex: 1, overflowY: "auto", padding: "14px 14px 40px" }}>
        {adding && (
          <div style={{ marginBottom: 14 }}>
            <AddCardForm type={adding} onSubmit={handleAdd} onCancel={() => setAdding(null)} />
          </div>
        )}
        {cards.length === 0 && !adding && (
          <div style={{ padding: "48px 0", textAlign: "center", color: "var(--fg-3)" }}>
            <p style={{ margin: 0, fontFamily: "var(--font-serif)", fontStyle: "italic", fontSize: 15 }}>
              Nothing here yet.
            </p>
            <p style={{ margin: "8px 0 0", fontSize: 12, lineHeight: 1.5 }}>
              Attach a note, image, quote,<br />link, or video.
            </p>
          </div>
        )}
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {cards.map((q, i) => (
            <CardCard
              key={q.id}
              card={q}
              onDelete={handleDelete}
              onUpdate={handleUpdate}
              animDelay={i * 30}
              onOpen={(q) => setSelectedCardId(q.id)}
            />
          ))}
        </div>
      </div>

      {selectedCard && (
        <CardModal
          card={selectedCard}
          onClose={() => setSelectedCardId(null)}
          onUpdate={handleUpdate}
          onDelete={(id) => { handleDelete(id); setSelectedCardId(null); }}
          concepts={concepts}
          onMove={handleMove}
        />
      )}
    </div>
  );
}

// ── ConceptCardsPanel — right-rail panel showing all concept cards ──

function ConceptCardsPanel({ conceptId, concept, activeLevel, onClearLevel, hideBreadcrumb, panelMode, externalLevelFilter }) {
  const [cards, setCards] = _qp_us([]);
  const [cols] = _qp_us(2);
  const [showAdd, setShowAdd] = _qp_us(false);
  const [levelFilter, setLevelFilter] = _qp_us(null);
  const [selectedCardId, setSelectedCardId] = _qp_us(null);
  const selectedCard = selectedCardId ? cards.find(q => q.id === selectedCardId) : null;
  const [cardFavorites, setCardFavorites] = _qp_us(() => {
    try {
      const stored = localStorage.getItem('senes-card-favorites');
      return stored ? new Set(JSON.parse(stored)) : new Set();
    } catch { return new Set(); }
  });



  function toggleCardFavorite(id) {
    setCardFavorites(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      try { localStorage.setItem('senes-card-favorites', JSON.stringify([...next])); } catch { }
      return next;
    });
  }

  _qp_ue(() => {
    if (!window.cardsService) return;
    setShowAdd(false);
    setLevelFilter(null);
    if (activeLevel) {
      window.cardsService.getCards(activeLevel.type, activeLevel.id).then(rows => {
        setCards(rows.slice().sort((a, b) => a.created_at < b.created_at ? -1 : 1));
      });
    } else {
      window.cardsService.getCardsByConceptId(conceptId).then(rows => {
        setCards(rows.slice().sort((a, b) => a.created_at < b.created_at ? -1 : 1));
      });
    }
  }, [conceptId, activeLevel]);

  const activeFilter = externalLevelFilter !== undefined ? externalLevelFilter : levelFilter;
  const displayCards = activeFilter && !activeLevel
    ? cards.filter(c => c.parent_type === activeFilter)
    : cards;

  async function handleAdd(card) {
    const now = new Date().toISOString();
    const newCard = {
      id: 'qk' + Date.now() + Math.random().toString(36).slice(2, 6),
      sort_order: (cards.length + 1) * 1000,
      created_at: now, updated_at: now,
      ...card,
    };
    await window.cardsService.addCard(newCard);
    setCards(prev => [...prev, newCard]);
    setShowAdd(false);
  }

  async function handleUpdate(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(prev => prev.map(q => q.id === id ? { ...q, ...changes } : q));
  }

  async function handleDelete(id) {
    await window.cardsService.deleteCard(id);
    setCards(prev => prev.filter(q => q.id !== id));
  }

  async function handleMove(id, changes) {
    await window.cardsService.updateCard(id, changes);
    setCards(prev => prev.filter(q => q.id !== id));
    setSelectedCardId(null);
  }

  return (
    <div style={{
      ...(panelMode ? { height: '100%' } : { position: 'sticky', top: 82, height: 'calc(100vh - 72px)' }),
      display: 'flex', flexDirection: 'column', overflow: 'hidden',
    }}>

      {/* Level breadcrumb */}
      {activeLevel && !hideBreadcrumb && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, minHeight: 32 }}>
          <button className="btn ghost tiny" onClick={onClearLevel} style={{ padding: '3px 8px', fontSize: 11 }}>← All</button>
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>{activeLevel.type}</span>
          <span style={{ fontSize: 13, color: 'var(--fg-2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{activeLevel.title}</span>
        </div>
      )}

      {/* Scrollable cards */}
      <div className="no-scrollbar" style={{ flex: 1, overflowY: 'auto' }}>

        {showAdd ? (
          <div style={{
            borderRadius: 14, padding: '16px', marginBottom: 10,
            background: 'var(--bg-elev)',
            boxShadow: '0 1px 2px rgba(0,0,0,.04), 0 4px 20px -6px rgba(0,0,0,.08)',
          }}>
            <AddHubCardModal
              concepts={[concept]}
              initialConceptId={conceptId}
              inline={true}
              onClose={() => setShowAdd(false)}
              onAdd={card => { handleAdd(card); setShowAdd(false); }}
            />
          </div>
        ) : (
          <div
            onClick={() => setShowAdd(true)}
            onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--fg-3)'}
            onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}
            style={{
              borderRadius: 14, padding: '18px 16px', marginBottom: 10,
              border: '2px dashed var(--border)',
              cursor: 'pointer', color: 'var(--fg-3)',
              transition: 'border-color 180ms',
              display: 'flex', alignItems: 'center',
            }}>
            <span style={{ fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 20 }}>Add a card…</span>
          </div>
        )}

        {displayCards.length === 0 && !showAdd && (
          <div style={{ padding: '32px 0', textAlign: 'center', color: 'var(--fg-3)' }}>
            <p style={{ margin: 0, fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontSize: 15 }}>Nothing here yet.</p>
          </div>
        )}
        <div style={{ columnCount: cols, columnGap: 8 }}>
          {displayCards.map((q, i) => (
            <CardCard key={q.id} card={q}
              onDelete={handleDelete}
              onUpdate={handleUpdate}
              animDelay={i * 20}
              onOpen={q => setSelectedCardId(q.id)}
              isFavorite={cardFavorites.has(q.id)}
              onToggleFavorite={() => toggleCardFavorite(q.id)}
            />
          ))}
        </div>
      </div>

      {selectedCard && (
        <CardModal
          card={selectedCard}
          onClose={() => setSelectedCardId(null)}
          onUpdate={handleUpdate}
          onDelete={id => { handleDelete(id); setSelectedCardId(null); }}
          onMove={handleMove}
        />
      )}
    </div>
  );
}

window.CardsScreen = CardsScreen;
window.CardsHubScreen = CardsHubScreen;
window.CardsSidePanel = CardsSidePanel;
window.ConceptCardsPanel = ConceptCardsPanel;
window.CardCard = CardCard;
window.CardModal = CardModal;
window.AddHubCardModal = AddHubCardModal;
