minecodex 2.0.11 → 2.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +12 -0
  2. package/apps/dashboard/app.js +37 -4
  3. package/apps/dashboard/assets/data-transfer.js +129 -0
  4. package/apps/dashboard/assets/download.svg +15 -0
  5. package/apps/dashboard/assets/upload.svg +15 -0
  6. package/apps/dashboard/index.html +10 -0
  7. package/apps/dashboard/styles.css +52 -1
  8. package/features/file-navigation/assets/book-open.svg +16 -0
  9. package/features/file-navigation/codex-feature.json +27 -0
  10. package/features/file-navigation/src/adapters/codex/modules.mjs +67 -0
  11. package/features/file-navigation/src/adapters/codex/navigation.mjs +84 -0
  12. package/features/file-navigation/src/domain/navigation.mjs +12 -0
  13. package/features/file-navigation/src/entry.mjs +34 -0
  14. package/features/file-navigation/src/frontend/navigation.mjs +140 -0
  15. package/features/file-navigation/src/injection-source.mjs +9 -0
  16. package/features/file-navigation/src/navigation.css +16 -0
  17. package/features/file-navigation/vendor/lucide-LICENSE +43 -0
  18. package/features/markdown-reading/assets/book-open.svg +16 -0
  19. package/features/markdown-reading/codex-feature.json +27 -0
  20. package/features/markdown-reading/src/adapters/codex/links.mjs +280 -0
  21. package/features/markdown-reading/src/adapters/codex/lists.mjs +168 -0
  22. package/features/markdown-reading/src/adapters/codex/markdown-native.mjs +28 -0
  23. package/features/markdown-reading/src/adapters/codex/preview.mjs +38 -0
  24. package/features/markdown-reading/src/adapters/codex/providers.mjs +18 -0
  25. package/features/markdown-reading/src/adapters/codex/reading-runtime.mjs +152 -0
  26. package/features/markdown-reading/src/adapters/codex/rich-text.mjs +561 -0
  27. package/features/markdown-reading/src/adapters/codex/table-actions.mjs +270 -0
  28. package/features/markdown-reading/src/adapters/codex/tables.mjs +493 -0
  29. package/features/markdown-reading/src/adapters/codex/toc-native.mjs +131 -0
  30. package/features/markdown-reading/src/adapters/codex/toc.mjs +334 -0
  31. package/features/markdown-reading/src/domain/footnotes.mjs +56 -0
  32. package/features/markdown-reading/src/injection-source.mjs +43 -0
  33. package/features/markdown-reading/src/markdown-reading.mjs +82 -0
  34. package/features/markdown-reading/src/reading.css +346 -0
  35. package/features/markdown-reading/vendor/lucide-LICENSE +43 -0
  36. package/features/model-slider/codex-feature.json +1 -0
  37. package/features/model-slider/src/adapters/codex/controller.mjs +7 -5
  38. package/features/model-slider/src/backend/favorites.mjs +54 -0
  39. package/features/model-slider/src/domain/model-rules.mjs +6 -1
  40. package/features/model-slider/src/frontend/install.mjs +41 -18
  41. package/features/notes/assets/prompt-previews.json +26 -0
  42. package/features/notes/codex-feature.json +2 -1
  43. package/features/notes/src/backend/transfer.mjs +97 -0
  44. package/features/notes/src/frontend/entry.mjs +380 -10
  45. package/features/notes/src/frontend/icons.json +4 -1
  46. package/features/notes/src/frontend/styles.css +63 -0
  47. package/features/session-status/src/domain/session-stats.mjs +22 -8
  48. package/features/session-status/src/session-status.mjs +2 -2
  49. package/features/sites/src/backend/sites-service.mjs +20 -5
  50. package/features/sites/src/backend/transfer.mjs +43 -0
  51. package/package.json +11 -3
  52. package/packages/cli/src/config.mjs +4 -2
  53. package/packages/cli/src/control-server.mjs +23 -4
  54. package/packages/cli/src/data-transfer/service.mjs +42 -0
  55. package/packages/cli/src/runtime-manager.mjs +3 -0
  56. package/packages/runtime-host/src/codex-injection.mjs +1 -0
  57. package/packages/runtime-host/src/codex-runtime.mjs +11 -0
  58. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  59. package/packages/ui/src/native/theme.mjs +22 -1
  60. package/packages/ui/src/native/tokens.mjs +4 -0
@@ -25,6 +25,7 @@ function appearanceLabel(key) {
25
25
  }
26
26
  function errorMessage(error) {
27
27
  const messages = {
28
+ "Open a chat before using a prompt.": "请先打开一个对话,再使用提示词。",
28
29
  "Category was not found": "未找到该分类",
29
30
  "Prompt was not found": "未找到该提示词",
30
31
  "Choose one emoji or leave it empty": "请选择一个 Emoji,或留空以继承分类图标",
@@ -74,6 +75,8 @@ function position() {
74
75
  config.ui.composerMenu(panel, addButton);
75
76
  }
76
77
  function close(restoreFocus = false) {
78
+ clearQuickPreview(true);
79
+ quickTabsObserver?.disconnect();
77
80
  generation++;
78
81
  panel?.remove();
79
82
  panel = null;
@@ -82,6 +85,108 @@ function close(restoreFocus = false) {
82
85
  button?.setAttribute("data-state", "closed");
83
86
  if (restoreFocus) button?.focus({ preventScroll: true });
84
87
  }
88
+ let quickCategory = "all";
89
+ let quickTabsObserver = null;
90
+ let quickPreview = null;
91
+ let quickPreviewTimer = null;
92
+ let quickExitTimer = null;
93
+ function clearQuickPreview(immediate = false, shift = 0) {
94
+ clearTimeout(quickPreviewTimer);
95
+ clearTimeout(quickExitTimer);
96
+ const current = quickPreview;
97
+ if (!current) return;
98
+ current.anchor.removeAttribute("data-preview");
99
+ if (immediate || previewReducedMotion()) {
100
+ current.node.remove();
101
+ quickPreview = null;
102
+ return;
103
+ }
104
+ current.node.style.pointerEvents = "none";
105
+ tweenPreview(current.node, { opacity:0, transform:`translateY(${Math.max(-10, Math.min(10, shift))}px) scale(.98)` }, 150);
106
+ quickExitTimer = setTimeout(() => {
107
+ current.node.remove();
108
+ if (quickPreview === current) quickPreview = null;
109
+ }, 150);
110
+ }
111
+ function showQuickPreview(item, anchor) {
112
+ clearQuickPreview(true);
113
+ const preview = cardPreview(item);
114
+ if (!preview) return;
115
+ quickPreviewTimer = setTimeout(() => {
116
+ if (!panel || !anchor.isConnected) return;
117
+ const node = element("div", "qp-image-preview");
118
+ config.ui.surface(node, "popover");
119
+ config.ui.mount(node, "floating");
120
+ const image = element("img");
121
+ image.alt = text(`Full image: ${item.summary}`, `完整图片:${item.summary}`);
122
+ const place = () => {
123
+ if (quickPreview?.node !== node || !image.naturalWidth) return;
124
+ const r = anchor.getBoundingClientRect(), p = panel.getBoundingClientRect();
125
+ const right = innerWidth - p.right - 24, left = p.left - 24;
126
+ const available = Math.max(right, left);
127
+ if (available < 96) { clearQuickPreview(true); return; }
128
+ const scale = Math.min(280 / image.naturalWidth, available / image.naturalWidth, 360 / image.naturalHeight, (innerHeight - 24) / image.naturalHeight);
129
+ const width = image.naturalWidth * scale, height = image.naturalHeight * scale;
130
+ node.style.width = `${width}px`;
131
+ node.style.height = `${height}px`;
132
+ node.style.left = `${right >= width ? p.right + 12 : p.left - width - 12}px`;
133
+ node.style.top = `${Math.max(12, Math.min(r.top + (r.height - height) / 2, innerHeight - height - 12))}px`;
134
+ node.style.visibility = "visible";
135
+ if (!previewReducedMotion()) node.animate([{opacity:0,transform:"translateX(-4px) scale(.98)"},{opacity:1,transform:"none"}],{duration:180,easing:"cubic-bezier(.22,1,.36,1)"});
136
+ };
137
+ node.append(image);
138
+ quickPreview = {node, anchor};
139
+ anchor.dataset.preview = "true";
140
+ node.addEventListener("pointerenter", () => clearTimeout(quickPreviewTimer));
141
+ node.addEventListener("pointerleave", () => clearQuickPreview());
142
+ image.onload = place;
143
+ image.src = preview.data;
144
+ if (image.complete) place();
145
+ }, 160);
146
+ }
147
+ function quickCategoryTabs() {
148
+ const bar = element("div", "qp-tabs-bar");
149
+ const tabs = element("div", "qp-tabs");
150
+ tabs.setAttribute("role", "tablist");
151
+ tabs.setAttribute("aria-label", text("Prompt categories", "提示词分类"));
152
+ const choices = [{id:"all",name:text("All","全部")}, ...categories.filter(c => orderedItems(c.id).length), ...(orderedItems(null).length ? [{id:"other",name:text("Other","其他")}] : [])];
153
+ if (!choices.some(c => c.id === quickCategory)) quickCategory = "all";
154
+ for (const category of choices) {
155
+ const tab = control(category.name, () => { quickCategory = category.id; renderList(); panel.querySelector('[aria-selected="true"]')?.focus({preventScroll:true}); }, "qp-tab");
156
+ tab.setAttribute("role", "tab");
157
+ tab.setAttribute("aria-selected", String(category.id === quickCategory));
158
+ tab.setAttribute("aria-controls", "mcx-quick-results");
159
+ tabs.append(tab);
160
+ }
161
+ const arrows = element("div", "qp-tab-arrows");
162
+ const move = direction => tabs.scrollBy({left:direction * Math.max(100,tabs.clientWidth - 90), behavior:previewReducedMotion() ? "instant" : "smooth"});
163
+ const previous = control(text("Previous categories","前面的分类"), () => move(-1), "qp-tab-arrow qp-tab-previous", "chevron");
164
+ const next = control(text("More categories","后面的分类"), () => move(1), "qp-tab-arrow qp-tab-next", "chevron");
165
+ arrows.append(previous,next);
166
+ bar.append(tabs,arrows);
167
+ const update = () => {
168
+ const naturalWidth = [...tabs.children].reduce((width, tab) => width + tab.offsetWidth + 2, -2);
169
+ const overflow = naturalWidth > tabs.clientWidth + 1;
170
+ previous.hidden = !overflow || tabs.scrollLeft <= 1;
171
+ next.hidden = !overflow || tabs.scrollLeft + tabs.clientWidth >= tabs.scrollWidth - 1;
172
+ arrows.hidden = !overflow;
173
+ bar.dataset.overflow = String(overflow);
174
+ tabs.style.setProperty("--qp-arrow-space", `${overflow ? 68 : 0}px`);
175
+ };
176
+ tabs.addEventListener("scroll", update, {passive:true});
177
+ tabs.addEventListener("keydown", event => {
178
+ if (!["ArrowLeft","ArrowRight"].includes(event.key)) return;
179
+ event.preventDefault();
180
+ const buttons = [...tabs.children], i = buttons.indexOf(document.activeElement);
181
+ const target = buttons[Math.max(0,Math.min(buttons.length-1,i+(event.key === "ArrowRight" ? 1 : -1)))];
182
+ target?.focus();
183
+ });
184
+ quickTabsObserver = new ResizeObserver(update);
185
+ quickTabsObserver.observe(tabs);
186
+ requestAnimationFrame(() => { if (!bar.isConnected) return; update(); const selected=tabs.querySelector('[aria-selected="true"]'); if(selected) tabs.scrollLeft = Math.max(0,selected.offsetLeft-tabs.offsetLeft-tabs.clientWidth+selected.offsetWidth+68); update(); });
187
+ return bar;
188
+ }
189
+
85
190
  // 输入框 Popover:只浏览与插入
86
191
  async function loadList() {
87
192
  const current = ++generation;
@@ -143,6 +248,8 @@ function orderedItems(categoryId) {
143
248
  );
144
249
  }
145
250
  function renderList() {
251
+ clearQuickPreview(true);
252
+ quickTabsObserver?.disconnect();
146
253
  panel.replaceChildren();
147
254
  const list = element("div", "qp-scroll");
148
255
  if (!items.length)
@@ -153,15 +260,19 @@ function renderList() {
153
260
  .filter((item) => item.useCount > 0)
154
261
  .sort((a, b) => b.calledAt - a.calledAt || a.id.localeCompare(b.id))
155
262
  .slice(0, 3);
263
+ panel.append(quickCategoryTabs());
264
+ list.id = "mcx-quick-results";
265
+ let lastScroll = 0;
266
+ list.addEventListener("scroll", () => { const delta = lastScroll - list.scrollTop; lastScroll = list.scrollTop; clearQuickPreview(false, delta); }, {passive:true});
156
267
  const sections = [
157
- { name: text("Recently used", "最近使用"), prompts: recent },
268
+ { id:"recent", name: text("Recently used", "最近使用"), prompts: recent },
158
269
  ...categories.map((category) => ({
159
- name: category.name,
270
+ id: category.id, name: category.name,
160
271
  prompts: orderedItems(category.id),
161
272
  })),
162
- { name: text("Other", "其他"), prompts: orderedItems(null) },
273
+ { id:"other", name: text("Other", "其他"), prompts: orderedItems(null) },
163
274
  ];
164
- for (const section of sections.filter((section) => section.prompts.length)) {
275
+ for (const section of sections.filter((section) => section.prompts.length && (quickCategory === "all" || section.id === quickCategory))) {
165
276
  list.append(element("div", "qp-header", section.name));
166
277
  for (const item of section.prompts) {
167
278
  const row = element("div", "qp-row");
@@ -192,10 +303,15 @@ function renderList() {
192
303
  insert.append(
193
304
  element("span", "qp-preview", item.body.replace(/\s+/g, " ")),
194
305
  );
306
+ insert.addEventListener("pointerenter", () => showQuickPreview(item, insert));
307
+ insert.addEventListener("focus", () => showQuickPreview(item, insert));
308
+ insert.addEventListener("pointerleave", () => { clearTimeout(quickPreviewTimer); quickPreviewTimer = setTimeout(() => clearQuickPreview(), 100); });
309
+ insert.addEventListener("blur", () => clearQuickPreview());
195
310
  row.append(insert);
196
311
  list.append(row);
197
312
  }
198
313
  }
314
+ if (!list.children.length) list.append(element("div", "qp-empty", text("No prompts yet", "还没有提示词")));
199
315
  panel.append(list);
200
316
  position();
201
317
  }
@@ -292,6 +408,9 @@ let page = null;
292
408
  let pageGeneration = 0;
293
409
  let activeCategory = "all";
294
410
  let pageSort = "manual";
411
+ let pageView = "cards";
412
+ try { pageView = localStorage.getItem("minecodex.prompts.view") === "list" ? "list" : "cards"; } catch {}
413
+ let cardResizeObserver = null;
295
414
  let searchText = "";
296
415
  let editor = null;
297
416
  let pageFocus = null;
@@ -329,6 +448,8 @@ function restoreNavigationSelection() {
329
448
  }
330
449
  function closePage(restoreFocus = false, restoreNavigation = true) {
331
450
  dragState?.cancel();
451
+ closeImagePreview();
452
+ cardResizeObserver?.disconnect();
332
453
  pageGeneration++;
333
454
  closePagePopup();
334
455
  closeEditor(false);
@@ -381,7 +502,7 @@ function openPage() {
381
502
  syncNavigationSelection();
382
503
  positionPage();
383
504
  page.append(element("div", "pm-empty", text("Loading…", "加载中…")));
384
- void loadPage();
505
+ return loadPage();
385
506
  }
386
507
  let pagePopup = null;
387
508
  let popupTrigger = null;
@@ -541,6 +662,7 @@ function renderPage() {
541
662
  actions.append(
542
663
  sortButton,
543
664
  pageIcon(text("Edit categories", "编辑分类"), "settings", manageCategories),
665
+ viewSwitcher(),
544
666
  searchField,
545
667
  pageControl(text("New prompt", "新建提示词"), () => editPrompt(), true),
546
668
  );
@@ -594,7 +716,10 @@ function renderPage() {
594
716
  renderRows(list);
595
717
  }
596
718
  function renderRows(list) {
719
+ closeImagePreview();
720
+ cardResizeObserver?.disconnect();
597
721
  list.replaceChildren();
722
+ list.dataset.view = pageView;
598
723
  const query = searchText.trim().toLocaleLowerCase();
599
724
  const groups = [...categories, { id: null, name: text("Other", "其他") }];
600
725
  let shown = items.filter(
@@ -617,21 +742,28 @@ function renderRows(list) {
617
742
  if (pageSort === "recent")
618
743
  shown.sort((a, b) => (b.calledAt ?? 0) - (a.calledAt ?? 0));
619
744
  const sections =
620
- pageSort === "manual"
745
+ (pageSort === "manual" || pageView === "cards")
621
746
  ? groups
622
747
  .map((c) => ({
623
748
  name: c.name,
624
- items: orderedItems(c.id).filter((p) => shown.includes(p)),
749
+ items: pageSort === "manual"
750
+ ? orderedItems(c.id).filter((p) => shown.includes(p))
751
+ : shown.filter((p) => p.categoryId === c.id),
625
752
  }))
626
753
  .filter((c) => c.items.length)
627
754
  : [{ items: shown }];
628
755
  for (const section of sections) {
629
756
  if (section.name && activeCategory === "all")
630
757
  list.append(element("h2", "", section.name));
758
+ if (pageView === "cards") {
759
+ list.append(renderCardSection(section));
760
+ continue;
761
+ }
631
762
  for (const item of section.items) {
632
763
  const row = element("div", "pm-row");
633
764
  row.dataset.promptId = item.id;
634
- const info = control(item.summary, () => editPrompt(item), "pm-info");
765
+ const use = control(text(`Use ${item.summary}`, `使用 ${item.summary}`), () => void usePagePrompt(item, use), "pm-list-use");
766
+ const info = element("div", "pm-info");
635
767
  info.replaceChildren(
636
768
  element("div", "pm-title", item.summary),
637
769
  element("div", "pm-preview", item.body.replace(/\s+/g, " ")),
@@ -650,7 +782,8 @@ function renderRows(list) {
650
782
  : text(`${item.useCount} ${item.useCount === 1 ? "call" : "calls"}`, `${item.useCount} 次调用`),
651
783
  ),
652
784
  );
653
- row.append(categoryIcon(item), info, stats);
785
+ use.replaceChildren(categoryIcon(item), info, stats);
786
+ row.append(use, pageIcon(text("Edit prompt", "编辑提示词"), "square-pen", () => editPrompt(item)));
654
787
  if (pageSort === "manual" && !query)
655
788
  row.append(
656
789
  orderControls(item, orderedItems(item.categoryId), false, row),
@@ -659,6 +792,241 @@ function renderRows(list) {
659
792
  }
660
793
  }
661
794
  }
795
+ function viewSwitcher() {
796
+ const group = element("div", "pm-view-switch");
797
+ group.setAttribute("role", "group");
798
+ group.setAttribute("aria-label", text("View", "视图"));
799
+ for (const [value, label] of [["cards", text("Cards", "卡片")], ["list", text("List", "列表")]]) {
800
+ const button = control(label, () => {
801
+ pageView = value;
802
+ try { localStorage.setItem("minecodex.prompts.view", value); } catch {}
803
+ for (const child of group.children) child.setAttribute("aria-pressed", String(child === button));
804
+ renderRows(page.querySelector(".pm-list"));
805
+ }, "pm-view-option", value === "cards" ? "layout-grid" : "list");
806
+ button.setAttribute("aria-pressed", String(pageView === value));
807
+ group.append(button);
808
+ }
809
+ return group;
810
+ }
811
+ function cardPreview(item) {
812
+ // 当前只有分类语义,占位图不写入用户提示词,也不冒充生成结果。
813
+ const category = categories.find((c) => c.id === item.categoryId);
814
+ if (!/^(生图|图像生成|图片生成|image generation|images?)$/i.test(category?.name?.trim() ?? "")) return null;
815
+ const previews = config.resources.previews ?? [];
816
+ const index = orderedItems(item.categoryId).findIndex((p) => p.id === item.id);
817
+ return previews[index % previews.length] ?? null;
818
+ }
819
+ async function usePagePrompt(item, trigger) {
820
+ trigger.disabled = true;
821
+ closePage();
822
+ try {
823
+ // Page 归还原路由后等待输入框挂载;不自动发送消息。
824
+ let editable = null;
825
+ for (let attempt = 0; attempt < 40 && !signal.aborted; attempt++) {
826
+ editable = config.codex.composer(null, { purpose: "insertion" })?.editable;
827
+ if (editable) break;
828
+ await new Promise((resolve) => setTimeout(resolve, 50));
829
+ }
830
+ if (signal.aborted) return;
831
+ if (!editable) throw new Error("Open a chat before using a prompt.");
832
+ window.__codexPersonalRuntime.insertText(item.body, editable);
833
+ } catch (error) {
834
+ await openPage();
835
+ pageError(error);
836
+ return;
837
+ }
838
+ try {
839
+ await api(`/api/prompts/${encodeURIComponent(item.id)}/use`, {
840
+ method: "POST", body: { action: "insert", success: true },
841
+ });
842
+ } catch (error) {
843
+ // 正文已插入,计数失败不能再次插入。
844
+ console.warn("[Prompts] Usage could not be saved", error);
845
+ }
846
+ }
847
+ let imagePreview = null;
848
+ let imagePreviewAnchor = null;
849
+ let imagePreviewTimer = null;
850
+ let imagePreviewSpotlight = null;
851
+ let imagePreviewExitTimer = null;
852
+ const previewReducedMotion = () => matchMedia("(prefers-reduced-motion: reduce)").matches;
853
+ function tweenPreview(node, target, duration) {
854
+ if (!node) return;
855
+ const computed = getComputedStyle(node);
856
+ const from = { opacity:computed.opacity, transform:computed.transform };
857
+ node.getAnimations().forEach((animation) => animation.cancel());
858
+ node.animate([from, target], { duration, easing:"cubic-bezier(.22,1,.36,1)", fill:"forwards" });
859
+ }
860
+ function retainImagePreview() {
861
+ clearTimeout(imagePreviewTimer);
862
+ if (imagePreviewExitTimer === null) return;
863
+ clearTimeout(imagePreviewExitTimer);
864
+ imagePreviewExitTimer = null;
865
+ tweenPreview(imagePreview, { opacity:1, transform:"none" }, 180);
866
+ tweenPreview(imagePreviewSpotlight?.overlay, { opacity:1, transform:"none" }, 180);
867
+ }
868
+ function dismissImagePreview() {
869
+ clearTimeout(imagePreviewTimer);
870
+ if (!imagePreview || previewReducedMotion()) { closeImagePreview(); return; }
871
+ if (imagePreviewExitTimer !== null) return;
872
+ tweenPreview(imagePreview, { opacity:0, transform:"scale(.97)" }, 160);
873
+ tweenPreview(imagePreviewSpotlight?.overlay, { opacity:0, transform:"none" }, 180);
874
+ imagePreviewExitTimer = setTimeout(closeImagePreview, 180);
875
+ }
876
+ function closeImagePreview() {
877
+ clearTimeout(imagePreviewExitTimer);
878
+ imagePreviewExitTimer = null;
879
+ clearTimeout(imagePreviewTimer);
880
+ imagePreviewAnchor?.removeAttribute("aria-describedby");
881
+ imagePreview?.remove();
882
+ if (imagePreviewSpotlight) {
883
+ const { card, placeholder, layer, overlay, cssText } = imagePreviewSpotlight;
884
+ card.style.cssText = cssText;
885
+ placeholder.replaceWith(card);
886
+ layer.remove();
887
+ overlay.remove();
888
+ imagePreviewSpotlight = null;
889
+ }
890
+ imagePreview = null;
891
+ imagePreviewAnchor = null;
892
+ }
893
+ function deferImagePreviewClose() {
894
+ if (document.activeElement === imagePreviewAnchor) return;
895
+ clearTimeout(imagePreviewTimer);
896
+ imagePreviewTimer = setTimeout(dismissImagePreview, 100);
897
+ }
898
+ function showImagePreview(anchor, preview, title) {
899
+ if (imagePreviewAnchor === anchor && imagePreview) {
900
+ retainImagePreview();
901
+ return;
902
+ }
903
+ closeImagePreview();
904
+ imagePreviewAnchor = anchor;
905
+ imagePreviewTimer = setTimeout(() => {
906
+ if (!anchor.isConnected || !page || signal.aborted) return;
907
+ const panel = element("div", "pm-image-preview");
908
+ panel.id = "mcx-prompt-image-preview";
909
+ panel.setAttribute("role", "tooltip");
910
+ config.ui.surface(panel, "popover");
911
+ const image = element("img");
912
+ image.alt = text(`Full image: ${title}`, `完整图片:${title}`);
913
+ const position = () => {
914
+ if (imagePreview !== panel || !image.naturalWidth) return;
915
+ const r = anchor.getBoundingClientRect();
916
+ const scale = Math.min(520 / image.naturalWidth, (innerHeight - 40) / image.naturalHeight, (innerWidth - 40) / image.naturalWidth, 640 / image.naturalHeight);
917
+ const width = image.naturalWidth * scale;
918
+ const height = image.naturalHeight * scale;
919
+ panel.style.width = `${width}px`;
920
+ panel.style.height = `${height}px`;
921
+ const right = r.right + 12;
922
+ const left = r.left - width - 12;
923
+ panel.style.left = `${Math.max(14, Math.min(right + width <= innerWidth - 14 ? right : left >= 14 ? left : r.left + (r.width - width) / 2, innerWidth - width - 14))}px`;
924
+ panel.style.top = `${Math.max(14, Math.min(r.top + (r.height - height) / 2, innerHeight - height - 14))}px`;
925
+ panel.style.transformOrigin = `${Math.max(0, Math.min(width, r.left + r.width / 2 - parseFloat(panel.style.left)))}px ${Math.max(0, Math.min(height, r.top + r.height / 2 - parseFloat(panel.style.top)))}px`;
926
+ panel.style.visibility = "visible";
927
+ if (!panel.dataset.entered && !previewReducedMotion()) {
928
+ panel.animate([{ opacity:0, transform:"scale(.96)" }, { opacity:1, transform:"none" }], { duration:240, easing:"cubic-bezier(.22,1,.36,1)", fill:"forwards" });
929
+ }
930
+ panel.dataset.entered = "true";
931
+ };
932
+ image.addEventListener("load", position, { once: true });
933
+ image.src = preview.data;
934
+ panel.append(image);
935
+ panel.addEventListener("pointerenter", retainImagePreview);
936
+ panel.addEventListener("pointerleave", deferImagePreviewClose);
937
+ imagePreview = panel;
938
+ // 原卡片移入浮层,避开书架的 mask / overflow;占位保持页面不跳动。
939
+ const card = anchor.closest(".pm-card");
940
+ const rect = card.getBoundingClientRect();
941
+ const placeholder = element("div");
942
+ placeholder.style.width = `${rect.width}px`;
943
+ placeholder.style.height = `${rect.height}px`;
944
+ const overlay = element("div", "pm-preview-overlay");
945
+ config.ui.overlay(overlay);
946
+ config.ui.layer(overlay, "floating");
947
+ overlay.addEventListener("pointerdown", dismissImagePreview);
948
+ const layer = element("div", "pm-preview-spotlight");
949
+ layer.setAttribute(MARKER, "page");
950
+ config.ui.layer(layer, "floating");
951
+ const cssText = card.style.cssText;
952
+ imagePreviewSpotlight = { card, placeholder, layer, overlay, cssText };
953
+ card.replaceWith(placeholder);
954
+ Object.assign(card.style, { position:"absolute", left:`${rect.left}px`, top:`${rect.top}px`, width:`${rect.width}px`, height:`${rect.height}px`, pointerEvents:"auto" });
955
+ layer.append(card);
956
+ document.body.append(overlay, layer);
957
+ if (!previewReducedMotion()) overlay.animate([{ opacity:0 }, { opacity:1 }], { duration:220, easing:"cubic-bezier(.22,1,.36,1)", fill:"forwards" });
958
+ anchor.setAttribute("aria-describedby", panel.id);
959
+ document.body.append(panel);
960
+ position();
961
+ }, 200);
962
+ }
963
+ window.addEventListener("scroll", closeImagePreview, { capture:true, signal });
964
+ window.addEventListener("resize", closeImagePreview, { signal });
965
+ window.addEventListener("blur", closeImagePreview, { signal });
966
+ window.addEventListener("keydown", (event) => {
967
+ if (event.key === "Escape" && imagePreviewAnchor) {
968
+ event.preventDefault();
969
+ event.stopImmediatePropagation();
970
+ dismissImagePreview();
971
+ }
972
+ }, { capture:true, signal });
973
+ signal.addEventListener("abort", closeImagePreview, { once:true });
974
+ function renderCardSection(section) {
975
+ const shelf = element("div", activeCategory === "all" ? "pm-card-shelf" : "pm-card-grid");
976
+ if (activeCategory === "all") {
977
+ shelf.tabIndex = 0;
978
+ shelf.setAttribute("role", "region");
979
+ shelf.setAttribute("aria-label", section.name);
980
+ const updateEdges = () => {
981
+ shelf.dataset.left = String(shelf.scrollLeft > 3);
982
+ shelf.dataset.right = String(shelf.scrollLeft + shelf.clientWidth < shelf.scrollWidth - 3);
983
+ };
984
+ shelf.addEventListener("scroll", updateEdges, { passive: true });
985
+ cardResizeObserver ??= new ResizeObserver((entries) => {
986
+ for (const { target } of entries) target.dispatchEvent(new Event("scroll"));
987
+ });
988
+ cardResizeObserver.observe(shelf);
989
+ requestAnimationFrame(updateEdges);
990
+ }
991
+ for (const item of section.items) {
992
+ const preview = cardPreview(item);
993
+ const card = element("article", `pm-card${preview ? " pm-card-image" : ""}`);
994
+ card.dataset.promptId = item.id;
995
+ card.addEventListener("pointerenter", () => {
996
+ if (imagePreviewSpotlight?.card === card) retainImagePreview();
997
+ });
998
+ card.addEventListener("pointerleave", () => {
999
+ if (imagePreviewSpotlight?.card === card) deferImagePreviewClose();
1000
+ });
1001
+ if (preview) {
1002
+ const cover = control(text("Edit prompt", "编辑提示词"), () => editPrompt(item), "pm-card-cover");
1003
+ const image = element("img");
1004
+ image.src = preview.data;
1005
+ image.alt = "";
1006
+ image.loading = "lazy";
1007
+ cover.replaceChildren(image);
1008
+ cover.addEventListener("pointerenter", (event) => {
1009
+ if (event.pointerType !== "touch") showImagePreview(cover, preview, item.summary);
1010
+ });
1011
+ cover.addEventListener("pointerleave", (event) => {
1012
+ if (!card.contains(event.relatedTarget)) deferImagePreviewClose();
1013
+ });
1014
+ cover.addEventListener("focus", () => showImagePreview(cover, preview, item.summary));
1015
+ cover.addEventListener("blur", deferImagePreviewClose);
1016
+ cover.addEventListener("click", closeImagePreview);
1017
+ card.append(cover);
1018
+ }
1019
+ const content = control(item.summary, () => editPrompt(item), "pm-card-content");
1020
+ content.replaceChildren(categoryIcon(item), element("h3", "pm-card-title", item.summary), element("p", "pm-card-body", item.body));
1021
+ const footer = element("div", "pm-card-footer");
1022
+ const use = pageControl(text("Use", "使用"), () => void usePagePrompt(item, use));
1023
+ use.setAttribute("aria-label", text(`Use ${item.summary}`, `使用 ${item.summary}`));
1024
+ footer.append(element("span", "pm-count", text(`${item.useCount} ${item.useCount === 1 ? "call" : "calls"}`, `${item.useCount} 次调用`)), use);
1025
+ card.append(content, footer);
1026
+ shelf.append(card);
1027
+ }
1028
+ return shelf;
1029
+ }
662
1030
  function emptyState(searching) {
663
1031
  const empty = element("div", "pm-empty-state");
664
1032
  const art = element("span", "pm-empty-art", searching ? "🔎" : "📖");
@@ -876,6 +1244,7 @@ function editorShell(title, back = null) {
876
1244
  return editor;
877
1245
  }
878
1246
  function editPrompt(item = null) {
1247
+ closeImagePreview();
879
1248
  const target = editorShell(
880
1249
  item ? text("Edit prompt", "编辑提示词") : text("New prompt", "新建提示词"),
881
1250
  );
@@ -1345,6 +1714,7 @@ document.addEventListener(
1345
1714
  if (
1346
1715
  panel &&
1347
1716
  !panel.contains(event.target) &&
1717
+ !quickPreview?.node.contains(event.target) &&
1348
1718
  !button?.contains(event.target)
1349
1719
  )
1350
1720
  close();
@@ -1373,7 +1743,7 @@ document.addEventListener(
1373
1743
  },
1374
1744
  { signal, capture: true },
1375
1745
  );
1376
- window.addEventListener("resize", position, { signal });
1746
+ window.addEventListener("resize", () => { clearQuickPreview(true); position(); }, { signal });
1377
1747
  window.addEventListener("scroll", position, { signal, capture: true });
1378
1748
  sync();
1379
1749
 
@@ -9,5 +9,8 @@
9
9
  "settings": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6.33374 8.97461C7.54786 8.97484 8.56163 9.83236 8.80347 10.9746H13.3337C13.6235 10.9749 13.8591 11.2102 13.8591 11.5C13.8591 11.7898 13.6235 12.0251 13.3337 12.0254H8.80347C8.56163 13.1676 7.54786 14.0252 6.33374 14.0254C5.11907 14.0254 4.10455 13.1673 3.86304 12.0244L2.66675 12.0254C2.37691 12.0253 2.14138 11.7899 2.14136 11.5C2.14136 11.2102 2.37693 10.9748 2.66675 10.9746H3.86304C4.10491 9.83221 5.11941 8.97461 6.33374 8.97461ZM6.33374 10.0254C5.51912 10.0254 4.85913 10.6854 4.85913 11.5C4.85913 12.3146 5.51912 12.9746 6.33374 12.9746C7.14814 12.9743 7.80835 12.3145 7.80835 11.5C7.80835 10.6855 7.14814 10.0257 6.33374 10.0254Z\" fill=\"currentColor\"/> <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9.66675 1.97461C10.881 1.97461 11.8935 2.83236 12.1355 3.97461H13.3328C13.6227 3.97459 13.8581 4.21005 13.8582 4.5C13.8581 4.78993 13.6227 5.02537 13.3328 5.02539H12.1365C11.8946 6.16774 10.881 7.02539 9.66675 7.02539C8.45245 7.02539 7.43893 6.16774 7.19702 5.02539H2.66675C2.3768 5.02539 2.14136 4.78995 2.14136 4.5C2.14136 4.21005 2.3768 3.97461 2.66675 3.97461H7.19702C7.43893 2.83226 8.45245 1.97461 9.66675 1.97461ZM9.66675 3.02539C8.85213 3.02539 8.19214 3.68538 8.19214 4.5C8.19214 5.31462 8.85213 5.97461 9.66675 5.97461C10.4814 5.97461 11.1414 5.31462 11.1414 4.5C11.1414 3.68538 10.4814 3.02539 9.66675 3.02539Z\" fill=\"currentColor\"/></svg>",
10
10
  "drag": "<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M4 5h16\" />\n <path d=\"M4 12h16\" />\n <path d=\"M4 19h16\" />\n</svg>\n",
11
11
  "prompt-nav": "<svg\n class=\"lucide lucide-book-open\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"-1.5 -1.5 27 27\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M12 7v14 M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" />\n</svg>",
12
- "prompt-nav-selected": "<svg width=\"20\" height=\"20\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M240,56V200a8,8,0,0,1-8,8H160a24,24,0,0,0-24,23.94,7.9,7.9,0,0,1-5.12,7.55A8,8,0,0,1,120,232a24,24,0,0,0-24-24H24a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H88a32,32,0,0,1,32,32v87.73a8.17,8.17,0,0,0,7.47,8.25,8,8,0,0,0,8.53-8V80a32,32,0,0,1,32-32h64A8,8,0,0,1,240,56Z\"/></svg>"
12
+ "prompt-nav-selected": "<svg width=\"20\" height=\"20\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M240,56V200a8,8,0,0,1-8,8H160a24,24,0,0,0-24,23.94,7.9,7.9,0,0,1-5.12,7.55A8,8,0,0,1,120,232a24,24,0,0,0-24-24H24a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H88a32,32,0,0,1,32,32v87.73a8.17,8.17,0,0,0,7.47,8.25,8,8,0,0,0,8.53-8V80a32,32,0,0,1,32-32h64A8,8,0,0,1,240,56Z\"/></svg>",
13
+ "layout-grid": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <rect width=\"7\" height=\"7\" x=\"3\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"3\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"14\" y=\"14\" rx=\"1\" />\n <rect width=\"7\" height=\"7\" x=\"3\" y=\"14\" rx=\"1\" />\n</svg>\n",
14
+ "list": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M3 5h.01\" />\n <path d=\"M3 12h.01\" />\n <path d=\"M3 19h.01\" />\n <path d=\"M8 5h13\" />\n <path d=\"M8 12h13\" />\n <path d=\"M8 19h13\" />\n</svg>\n",
15
+ "square-pen": "<!-- Lucide, ISC license; see assets/brand/references/lucide-LICENSE.txt -->\n<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" />\n <path d=\"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z\" />\n</svg>\n"
13
16
  }
@@ -108,3 +108,66 @@
108
108
  :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-grid { display:grid; grid-template-columns:repeat(8,minmax(0,1fr)); gap:4px; max-height:264px; overflow:auto; }
109
109
  :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-grid .pm-emoji-choice { width:32px; height:32px; min-height:32px; padding:0; display:flex; justify-content:center; font-size:22px; border-radius:var(--mcx-radius-lg); }
110
110
  :is([data-minecodex-quick-prompts=page],[data-minecodex-quick-prompts=modal]) .pm-emoji-choice[aria-pressed=true] { background:var(--mcx-color-background-primary-ghost-hover); }
111
+
112
+ /* 全页面卡片:All 横向书架,单分类自适应网格。 */
113
+ [data-minecodex-quick-prompts=page] {
114
+ --pm-card-width:292px;
115
+ --pm-card-height:344px;
116
+ --pm-image-card-height:416px;
117
+ --pm-card-gap:16px;
118
+ }
119
+ [data-minecodex-quick-prompts=page] .pm-view-switch { display:flex; flex:none; gap:2px; padding:3px; border-radius:var(--mcx-radius-full); background:var(--mcx-color-background-primary-ghost-hover); }
120
+ [data-minecodex-quick-prompts=page] .pm-view-option { display:flex; align-items:center; justify-content:center; width:32px; height:28px; padding:0; border-radius:inherit; color:var(--mcx-color-text-secondary); white-space:nowrap; }
121
+ [data-minecodex-quick-prompts=page] .pm-view-option[aria-pressed=true] { background:var(--mcx-color-surface); color:var(--mcx-color-text-primary); box-shadow:0 1px 4px #00000012; }
122
+ [data-minecodex-quick-prompts=page] .pm-list[data-view=cards] { gap:0; }
123
+ [data-minecodex-quick-prompts=page] .pm-list[data-view=cards]>h2 { margin:24px 0 14px; }
124
+ [data-minecodex-quick-prompts=page] .pm-card-shelf { display:grid; grid-auto-flow:column; grid-auto-columns:min(var(--pm-card-width), 86%); gap:var(--pm-card-gap); overflow-x:auto; overscroll-behavior-x:contain; scroll-snap-type:x proximity; scroll-padding-inline:2px; padding:4px 2px 18px; margin:0 -2px 8px; scrollbar-width:thin; --pm-fade-left:#000; --pm-fade-right:#000; mask-image:linear-gradient(to right,var(--pm-fade-left),#000 32px,#000 calc(100% - 48px),var(--pm-fade-right)); }
125
+ [data-minecodex-quick-prompts=page] .pm-card-shelf[data-left=true] { --pm-fade-left:transparent; }
126
+ [data-minecodex-quick-prompts=page] .pm-card-shelf[data-right=true] { --pm-fade-right:transparent; }
127
+ [data-minecodex-quick-prompts=page] .pm-card-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(min(100%,260px),1fr)); gap:var(--pm-card-gap); padding:4px 2px 18px; }
128
+ [data-minecodex-quick-prompts=page] .pm-card { display:flex; flex-direction:column; height:var(--pm-card-height); min-width:0; border:1px solid var(--mcx-color-border-subtle,var(--mcx-color-border)); border-radius:var(--mcx-radius-2xl); background:var(--mcx-color-surface); box-shadow:0 4px 16px -6px #00000012; scroll-snap-align:start; overflow:hidden; transition:box-shadow 160ms ease,border-color 160ms ease; }
129
+ [data-minecodex-quick-prompts=page] .pm-card:hover, [data-minecodex-quick-prompts=page] .pm-card:focus-within { border-color:var(--mcx-color-border-primary-outline); box-shadow:0 6px 22px -6px #0000001a; }
130
+ [data-minecodex-quick-prompts=page] .pm-card-content { display:flex; flex-direction:column; align-items:flex-start; text-align:left; padding:22px 22px 0; min-height:0; flex:1; overflow:hidden; }
131
+ [data-minecodex-quick-prompts=page] .pm-card-title { margin:14px 0 8px; font-size:var(--mcx-text-base); font-weight:var(--font-weight-medium,500); line-height:1.4; width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:none; }
132
+ [data-minecodex-quick-prompts=page] .pm-card-body { margin:0; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:8; overflow:hidden; color:var(--mcx-color-text-tertiary); font-size:var(--mcx-text-sm); line-height:1.65; white-space:pre-line; overflow-wrap:anywhere; }
133
+ [data-minecodex-quick-prompts=page] .pm-card-footer { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:16px 22px 20px; flex:none; }
134
+ [data-minecodex-quick-prompts=page] .pm-card-footer .pm-command { min-height:30px; height:30px; padding:4px 14px; }
135
+ [data-minecodex-quick-prompts=page] .pm-card-image { height:var(--pm-image-card-height); }
136
+ [data-minecodex-quick-prompts=page] .pm-card-cover { position:relative; width:100%; height:160px; flex:none; overflow:hidden; background:var(--mcx-color-background-primary-ghost-hover); padding:0; }
137
+ [data-minecodex-quick-prompts=page] .pm-card-cover img { width:100%; height:100%; object-fit:cover; object-position:center; }
138
+ [data-minecodex-quick-prompts=page] .pm-card-image .pm-card-content { padding-top:18px; }
139
+ [data-minecodex-quick-prompts=page] .pm-card-image .pm-card-body { -webkit-line-clamp:4; }
140
+ @media (max-width:900px) {
141
+ [data-minecodex-quick-prompts=page] .pm-heading { align-items:flex-start; flex-wrap:wrap; }
142
+ [data-minecodex-quick-prompts=page] .pm-header-actions { flex-wrap:wrap; max-width:100%; }
143
+ }
144
+ @media (prefers-reduced-motion:reduce) {
145
+ [data-minecodex-quick-prompts=page] .pm-card { transition:none; }
146
+ }
147
+ [data-minecodex-quick-prompts=page] .pm-card-footer .pm-command { box-shadow:inset 0 0 0 1px var(--mcx-color-border-primary-outline); }
148
+ [data-minecodex-quick-prompts=page] .pm-card-content:focus-visible, [data-minecodex-quick-prompts=page] .pm-card-cover:focus-visible { outline-offset:-3px; }
149
+
150
+ [data-minecodex-quick-prompts=page] .pm-view-option svg { width:16px; height:16px; }
151
+ [data-minecodex-quick-prompts=page] .pm-list-use { display:flex; align-items:center; gap:12px; flex:1; min-width:0; align-self:stretch; text-align:left; }
152
+
153
+ .pm-image-preview { position:fixed; visibility:hidden; padding:0; box-sizing:border-box; border-radius:var(--mcx-radius-2xl); overflow:hidden; }
154
+ .pm-image-preview img { display:block; width:100%; height:100%; object-fit:contain; border-radius:inherit; }
155
+
156
+ .pm-preview-overlay { position:fixed; inset:0; }
157
+ [data-minecodex-quick-prompts=page].pm-preview-spotlight { position:fixed; inset:0; overflow:visible; pointer-events:none; }
158
+
159
+ /* Quick Prompts:固定分类栏,箭头覆盖渐隐尾端,列表保持原生单行密度。 */
160
+ [data-minecodex-quick-prompts=panel] .qp-tabs-bar { position:relative; flex:none; padding:4px 4px 8px; border-bottom:1px solid var(--mcx-color-border); margin-bottom:4px; }
161
+ [data-minecodex-quick-prompts=panel] .qp-tabs { display:flex; gap:2px; overflow-x:auto; scrollbar-width:none; padding-right:var(--qp-arrow-space,0px); }
162
+ [data-minecodex-quick-prompts=panel] .qp-tabs::-webkit-scrollbar { display:none; }
163
+ [data-minecodex-quick-prompts=panel] .qp-tabs-bar[data-overflow=true] .qp-tabs { mask-image:linear-gradient(to right,#000 calc(100% - 90px),transparent calc(100% - 60px)); }
164
+ [data-minecodex-quick-prompts=panel] .qp-tab { flex:none; white-space:nowrap; padding:6px 12px; border-radius:var(--mcx-radius-full); color:var(--mcx-color-text-secondary); }
165
+ [data-minecodex-quick-prompts=panel] .qp-tab[aria-selected=true], [data-minecodex-quick-prompts=panel] .qp-tab:hover, [data-minecodex-quick-prompts=panel] .qp-tab-arrow:hover, [data-minecodex-quick-prompts=panel] .qp-select[data-preview=true] { background:var(--mcx-color-background-primary-ghost-hover); color:var(--mcx-color-text-primary); }
166
+ [data-minecodex-quick-prompts=panel] .qp-tab-arrows { position:absolute; right:4px; top:4px; display:flex; gap:2px; }
167
+ [data-minecodex-quick-prompts=panel] .qp-tab-arrow { display:flex; align-items:center; justify-content:center; width:28px; height:30px; border-radius:var(--mcx-radius-full); }
168
+ [data-minecodex-quick-prompts=panel] .qp-tab-previous svg { transform:rotate(90deg); }
169
+ [data-minecodex-quick-prompts=panel] .qp-tab-next svg { transform:rotate(-90deg); }
170
+ [data-minecodex-quick-prompts=panel] [hidden] { display:none!important; }
171
+ [data-minecodex-quick-prompts=panel] .qp-title { max-width:55%; flex-shrink:0; }
172
+ .qp-image-preview { position:fixed; visibility:hidden; padding:0; overflow:hidden; border-radius:var(--mcx-radius-2xl); transform-origin:left center; }
173
+ .qp-image-preview img { display:block; width:100%; height:100%; object-fit:contain; }