vomebook commited on
Commit
fc4bb8e
·
1 Parent(s): 4996b35

Upload 3 files

Browse files
Files changed (3) hide show
  1. static/app.js +69 -1
  2. static/index.html +5 -5
  3. static/style.css +163 -4
static/app.js CHANGED
@@ -124,6 +124,7 @@ function cacheDom() {
124
  DOM.hamburgerBtn = $("#hamburger-btn");
125
  DOM.settingsBtn = $("#settings-btn");
126
  DOM.mobileToggleBtn = $("#mobile-toggle-btn");
 
127
  DOM.themeBtn = $("#theme-btn");
128
  DOM.leftSidebar = $("#left-sidebar");
129
  DOM.rightSidebar = $("#right-sidebar");
@@ -273,6 +274,9 @@ function applyTheme() {
273
  function applyMobileMode() {
274
  document.body.classList.toggle("mobile", STATE.isMobile);
275
  document.body.classList.toggle("force-desktop", !STATE.isMobile);
 
 
 
276
  if (STATE.isMobile) {
277
  STATE.leftSidebarOpen = false;
278
  STATE.rightSidebarOpen = false;
@@ -283,6 +287,51 @@ function applyMobileMode() {
283
  updateSidebarVisibility();
284
  }
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  async function ensureFulltextLoaded() {
287
  if (!STATE.fulltextManifest) {
288
  STATE.fulltextManifest = await API.fulltextManifest();
@@ -494,6 +543,7 @@ async function renderSourcesFilter() {
494
  renderCheckboxList(DOM.filterSourceList, STATE.sources.map((source) => ({ key: source.slug, label: source.name, count: source.count })), STATE.selectedSources, (values) => {
495
  STATE.selectedSources = values;
496
  STATE.page = 1;
 
497
  doSearch();
498
  });
499
  }
@@ -520,6 +570,7 @@ function renderFolderNodes(container, nodes, depth) {
520
  STATE.folderSelections = STATE.folderSelections.filter((path) => path !== node.path);
521
  }
522
  STATE.page = 1;
 
523
  doSearch();
524
  });
525
  container.appendChild(row);
@@ -581,6 +632,7 @@ function clearFilters() {
581
  DOM.filterMaxUnit.value = "MB";
582
  renderSourcesFilter();
583
  renderFolderTree();
 
584
  doSearch();
585
  }
586
 
@@ -596,6 +648,7 @@ async function randomDoc() {
596
  function updateMultiUi() {
597
  STATE.multiSelect = DOM.multiSelectToggle.checked;
598
  DOM.multiActionBar.style.display = STATE.multiSelect ? "" : "none";
 
599
  if (!STATE.multiSelect) STATE.selectedIds.clear();
600
  DOM.multiSelectedCount.textContent = STATE.selectedIds.size ? `已选 ${STATE.selectedIds.size} 项` : "";
601
  renderResults();
@@ -643,6 +696,7 @@ function attachEvents() {
643
  if (!item) return;
644
  DOM.searchInput.value = item.dataset.query;
645
  STATE.query = item.dataset.query;
 
646
  doSearch();
647
  });
648
  let timer = null;
@@ -652,27 +706,32 @@ function attachEvents() {
652
  STATE.query = DOM.searchInput.value.trim();
653
  STATE.page = 1;
654
  addHistory(STATE.query);
 
655
  doSearch();
656
  }, 250);
657
  });
658
  DOM.sortSelect.addEventListener("change", () => {
659
  STATE.page = 1;
 
660
  doSearch();
661
  });
662
  DOM.exactToggle.addEventListener("change", () => {
663
  STATE.exact = DOM.exactToggle.checked;
664
  STATE.page = 1;
 
665
  doSearch();
666
  });
667
  DOM.historyToggle.addEventListener("change", () => {
668
  STATE.historyEnabled = DOM.historyToggle.checked;
669
  if (!STATE.historyEnabled) saveHistory([]);
 
670
  });
671
  DOM.fulltextToggle.addEventListener("change", async () => {
672
  STATE.fulltext = DOM.fulltextToggle.checked;
673
  DOM.searchInput.placeholder = STATE.fulltext ? "全文搜索 TXT 正文..." : "搜索 TXT 文件或路径...";
674
  STATE.page = 1;
675
  if (STATE.fulltext) await ensureFulltextLoaded();
 
676
  doSearch();
677
  });
678
  DOM.clearFiltersBtn.addEventListener("click", clearFilters);
@@ -680,18 +739,22 @@ function attachEvents() {
680
  if (DOM.randomBookBtn) DOM.randomBookBtn.addEventListener("click", randomDoc);
681
  DOM.filterMinSize.addEventListener("input", () => {
682
  STATE.minSize = bytesFromInput(DOM.filterMinSize.value, DOM.filterMinUnit.value);
 
683
  doSearch();
684
  });
685
  DOM.filterMaxSize.addEventListener("input", () => {
686
  STATE.maxSize = bytesFromInput(DOM.filterMaxSize.value, DOM.filterMaxUnit.value);
 
687
  doSearch();
688
  });
689
  DOM.filterMinUnit.addEventListener("change", () => {
690
  STATE.minSize = bytesFromInput(DOM.filterMinSize.value, DOM.filterMinUnit.value);
 
691
  doSearch();
692
  });
693
  DOM.filterMaxUnit.addEventListener("change", () => {
694
  STATE.maxSize = bytesFromInput(DOM.filterMaxSize.value, DOM.filterMaxUnit.value);
 
695
  doSearch();
696
  });
697
  DOM.folderSelectAll.addEventListener("click", () => {
@@ -699,11 +762,13 @@ function attachEvents() {
699
  flattenNodes(STATE.folderTree, paths);
700
  STATE.folderSelections = paths;
701
  renderFolderTree();
 
702
  doSearch();
703
  });
704
  DOM.folderDeselectAll.addEventListener("click", () => {
705
  STATE.folderSelections = [];
706
  renderFolderTree();
 
707
  doSearch();
708
  });
709
  DOM.multiSelectToggle.addEventListener("change", updateMultiUi);
@@ -767,6 +832,7 @@ function attachEvents() {
767
  STATE.folderSelections = path ? [path] : [];
768
  }
769
  renderFolderTree();
 
770
  doSearch();
771
  }
772
  });
@@ -826,8 +892,9 @@ async function init() {
826
  if (savedMobile === "mobile") STATE.isMobile = true;
827
  else if (savedMobile === "desktop") STATE.isMobile = false;
828
  else STATE.isMobile = window.innerWidth <= 768;
829
- applyMobileMode();
830
  syncRoute();
 
 
831
  STATE.sources = await API.getSources();
832
  STATE.sourceMap = Object.fromEntries(STATE.sources.map((source) => [source.slug, source]));
833
  await renderSourcesFilter();
@@ -836,6 +903,7 @@ async function init() {
836
  updateFilterVisibility();
837
  attachEvents();
838
  updateSidebarVisibility();
 
839
  doSearch();
840
  }
841
 
 
124
  DOM.hamburgerBtn = $("#hamburger-btn");
125
  DOM.settingsBtn = $("#settings-btn");
126
  DOM.mobileToggleBtn = $("#mobile-toggle-btn");
127
+ DOM.mobileToggleIcon = $("#mobile-toggle-icon");
128
  DOM.themeBtn = $("#theme-btn");
129
  DOM.leftSidebar = $("#left-sidebar");
130
  DOM.rightSidebar = $("#right-sidebar");
 
274
  function applyMobileMode() {
275
  document.body.classList.toggle("mobile", STATE.isMobile);
276
  document.body.classList.toggle("force-desktop", !STATE.isMobile);
277
+ if (DOM.mobileToggleIcon) {
278
+ DOM.mobileToggleIcon.className = STATE.isMobile ? "ui-icon ui-icon-desktop" : "ui-icon ui-icon-phone";
279
+ }
280
  if (STATE.isMobile) {
281
  STATE.leftSidebarOpen = false;
282
  STATE.rightSidebarOpen = false;
 
287
  updateSidebarVisibility();
288
  }
289
 
290
+ function syncUrl(replace = true) {
291
+ const params = new URLSearchParams();
292
+ if (STATE.query) params.set("q", STATE.query);
293
+ if (STATE.selectedSources.length && !STATE.source) {
294
+ for (const slug of STATE.selectedSources) params.append("source", slug);
295
+ }
296
+ if (STATE.folderSelections.length && STATE.source) {
297
+ for (const path of STATE.folderSelections) params.append("folder", path);
298
+ }
299
+ if (STATE.minSize !== null) params.set("min_size", String(STATE.minSize));
300
+ if (STATE.maxSize !== null) params.set("max_size", String(STATE.maxSize));
301
+ if (DOM.sortSelect.value !== "relevance") params.set("sort", DOM.sortSelect.value);
302
+ if (STATE.exact) params.set("exact", "1");
303
+ if (STATE.fulltext) params.set("fulltext", "1");
304
+ if (!STATE.historyEnabled) params.set("history", "0");
305
+ if (STATE.isMobile) params.set("view", "mobile");
306
+ const basePath = STATE.source ? `/${encodeURIComponent(STATE.source)}` : "/";
307
+ const nextUrl = params.toString() ? `${basePath}?${params.toString()}` : basePath;
308
+ if (replace) history.replaceState(null, "", nextUrl);
309
+ else history.pushState(null, "", nextUrl);
310
+ }
311
+
312
+ function loadUrlState() {
313
+ const params = new URLSearchParams(window.location.search);
314
+ STATE.query = params.get("q") || "";
315
+ STATE.selectedSources = params.getAll("source");
316
+ STATE.folderSelections = params.getAll("folder");
317
+ STATE.minSize = params.get("min_size") ? parseInt(params.get("min_size"), 10) : null;
318
+ STATE.maxSize = params.get("max_size") ? parseInt(params.get("max_size"), 10) : null;
319
+ STATE.exact = params.get("exact") === "1";
320
+ STATE.fulltext = params.get("fulltext") === "1";
321
+ STATE.historyEnabled = params.get("history") !== "0";
322
+ const sort = params.get("sort") || "relevance";
323
+ const view = params.get("view");
324
+ if (view === "mobile") STATE.isMobile = true;
325
+ if (view === "desktop") STATE.isMobile = false;
326
+ DOM.searchInput.value = STATE.query;
327
+ DOM.exactToggle.checked = STATE.exact;
328
+ DOM.fulltextToggle.checked = STATE.fulltext;
329
+ DOM.historyToggle.checked = STATE.historyEnabled;
330
+ DOM.sortSelect.value = sort;
331
+ if (STATE.minSize !== null) DOM.filterMinSize.value = STATE.minSize;
332
+ if (STATE.maxSize !== null) DOM.filterMaxSize.value = STATE.maxSize;
333
+ }
334
+
335
  async function ensureFulltextLoaded() {
336
  if (!STATE.fulltextManifest) {
337
  STATE.fulltextManifest = await API.fulltextManifest();
 
543
  renderCheckboxList(DOM.filterSourceList, STATE.sources.map((source) => ({ key: source.slug, label: source.name, count: source.count })), STATE.selectedSources, (values) => {
544
  STATE.selectedSources = values;
545
  STATE.page = 1;
546
+ syncUrl();
547
  doSearch();
548
  });
549
  }
 
570
  STATE.folderSelections = STATE.folderSelections.filter((path) => path !== node.path);
571
  }
572
  STATE.page = 1;
573
+ syncUrl();
574
  doSearch();
575
  });
576
  container.appendChild(row);
 
632
  DOM.filterMaxUnit.value = "MB";
633
  renderSourcesFilter();
634
  renderFolderTree();
635
+ syncUrl();
636
  doSearch();
637
  }
638
 
 
648
  function updateMultiUi() {
649
  STATE.multiSelect = DOM.multiSelectToggle.checked;
650
  DOM.multiActionBar.style.display = STATE.multiSelect ? "" : "none";
651
+ document.body.classList.toggle("multiselect", STATE.multiSelect);
652
  if (!STATE.multiSelect) STATE.selectedIds.clear();
653
  DOM.multiSelectedCount.textContent = STATE.selectedIds.size ? `已选 ${STATE.selectedIds.size} 项` : "";
654
  renderResults();
 
696
  if (!item) return;
697
  DOM.searchInput.value = item.dataset.query;
698
  STATE.query = item.dataset.query;
699
+ syncUrl();
700
  doSearch();
701
  });
702
  let timer = null;
 
706
  STATE.query = DOM.searchInput.value.trim();
707
  STATE.page = 1;
708
  addHistory(STATE.query);
709
+ syncUrl();
710
  doSearch();
711
  }, 250);
712
  });
713
  DOM.sortSelect.addEventListener("change", () => {
714
  STATE.page = 1;
715
+ syncUrl();
716
  doSearch();
717
  });
718
  DOM.exactToggle.addEventListener("change", () => {
719
  STATE.exact = DOM.exactToggle.checked;
720
  STATE.page = 1;
721
+ syncUrl();
722
  doSearch();
723
  });
724
  DOM.historyToggle.addEventListener("change", () => {
725
  STATE.historyEnabled = DOM.historyToggle.checked;
726
  if (!STATE.historyEnabled) saveHistory([]);
727
+ syncUrl();
728
  });
729
  DOM.fulltextToggle.addEventListener("change", async () => {
730
  STATE.fulltext = DOM.fulltextToggle.checked;
731
  DOM.searchInput.placeholder = STATE.fulltext ? "全文搜索 TXT 正文..." : "搜索 TXT 文件或路径...";
732
  STATE.page = 1;
733
  if (STATE.fulltext) await ensureFulltextLoaded();
734
+ syncUrl();
735
  doSearch();
736
  });
737
  DOM.clearFiltersBtn.addEventListener("click", clearFilters);
 
739
  if (DOM.randomBookBtn) DOM.randomBookBtn.addEventListener("click", randomDoc);
740
  DOM.filterMinSize.addEventListener("input", () => {
741
  STATE.minSize = bytesFromInput(DOM.filterMinSize.value, DOM.filterMinUnit.value);
742
+ syncUrl();
743
  doSearch();
744
  });
745
  DOM.filterMaxSize.addEventListener("input", () => {
746
  STATE.maxSize = bytesFromInput(DOM.filterMaxSize.value, DOM.filterMaxUnit.value);
747
+ syncUrl();
748
  doSearch();
749
  });
750
  DOM.filterMinUnit.addEventListener("change", () => {
751
  STATE.minSize = bytesFromInput(DOM.filterMinSize.value, DOM.filterMinUnit.value);
752
+ syncUrl();
753
  doSearch();
754
  });
755
  DOM.filterMaxUnit.addEventListener("change", () => {
756
  STATE.maxSize = bytesFromInput(DOM.filterMaxSize.value, DOM.filterMaxUnit.value);
757
+ syncUrl();
758
  doSearch();
759
  });
760
  DOM.folderSelectAll.addEventListener("click", () => {
 
762
  flattenNodes(STATE.folderTree, paths);
763
  STATE.folderSelections = paths;
764
  renderFolderTree();
765
+ syncUrl();
766
  doSearch();
767
  });
768
  DOM.folderDeselectAll.addEventListener("click", () => {
769
  STATE.folderSelections = [];
770
  renderFolderTree();
771
+ syncUrl();
772
  doSearch();
773
  });
774
  DOM.multiSelectToggle.addEventListener("change", updateMultiUi);
 
832
  STATE.folderSelections = path ? [path] : [];
833
  }
834
  renderFolderTree();
835
+ syncUrl();
836
  doSearch();
837
  }
838
  });
 
892
  if (savedMobile === "mobile") STATE.isMobile = true;
893
  else if (savedMobile === "desktop") STATE.isMobile = false;
894
  else STATE.isMobile = window.innerWidth <= 768;
 
895
  syncRoute();
896
+ loadUrlState();
897
+ applyMobileMode();
898
  STATE.sources = await API.getSources();
899
  STATE.sourceMap = Object.fromEntries(STATE.sources.map((source) => [source.slug, source]));
900
  await renderSourcesFilter();
 
903
  updateFilterVisibility();
904
  attachEvents();
905
  updateSidebarVisibility();
906
+ syncUrl();
907
  doSearch();
908
  }
909
 
static/index.html CHANGED
@@ -14,23 +14,23 @@
14
  <body class="dark">
15
  <header id="header">
16
  <div class="header-left">
17
- <button id="hamburger-btn" class="icon-btn" aria-label="菜单"></button>
18
  <a id="header-logo" class="header-logo" href="https://huggingface.co/VOMEBOOK" title="VOMEBOOK">
19
  <span id="header-title">VOMEBOOK</span>
20
  </a>
21
  </div>
22
  <div class="header-center">
23
  <div class="search-box">
24
- <span class="search-icon"></span>
25
  <input type="search" id="search-input" class="search-input" placeholder="搜索 TXT 文件或路径..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
26
  <kbd class="search-kbd">/</kbd>
27
  <div id="search-history-dropdown" class="search-history-dropdown" style="display:none"></div>
28
  </div>
29
  </div>
30
  <div class="header-right">
31
- <button id="settings-btn" class="icon-btn" aria-label="搜索设置"></button>
32
- <button id="mobile-toggle-btn" class="icon-btn" aria-label="切换手机或电脑模式" title="手机/电脑模式切换"></button>
33
- <button id="theme-btn" class="icon-btn" aria-label="切换主题"></button>
34
  </div>
35
  </header>
36
 
 
14
  <body class="dark">
15
  <header id="header">
16
  <div class="header-left">
17
+ <button id="hamburger-btn" class="icon-btn" aria-label="菜单"><span class="ui-icon ui-icon-menu" aria-hidden="true"></span></button>
18
  <a id="header-logo" class="header-logo" href="https://huggingface.co/VOMEBOOK" title="VOMEBOOK">
19
  <span id="header-title">VOMEBOOK</span>
20
  </a>
21
  </div>
22
  <div class="header-center">
23
  <div class="search-box">
24
+ <span class="search-icon"><span class="ui-icon ui-icon-search" aria-hidden="true"></span></span>
25
  <input type="search" id="search-input" class="search-input" placeholder="搜索 TXT 文件或路径..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
26
  <kbd class="search-kbd">/</kbd>
27
  <div id="search-history-dropdown" class="search-history-dropdown" style="display:none"></div>
28
  </div>
29
  </div>
30
  <div class="header-right">
31
+ <button id="settings-btn" class="icon-btn" aria-label="搜索设置"><span class="ui-icon ui-icon-sliders" aria-hidden="true"></span></button>
32
+ <button id="mobile-toggle-btn" class="icon-btn" aria-label="切换手机或电脑模式" title="手机/电脑模式切换"><span id="mobile-toggle-icon" class="ui-icon ui-icon-devices" aria-hidden="true"></span></button>
33
+ <button id="theme-btn" class="icon-btn" aria-label="切换主题"><span class="ui-icon ui-icon-theme" aria-hidden="true"></span></button>
34
  </div>
35
  </header>
36
 
static/style.css CHANGED
@@ -238,6 +238,9 @@ ul, li {
238
  flex-shrink: 0;
239
  margin-left: 12px;
240
  color: var(--on-surface-variant);
 
 
 
241
  }
242
 
243
  .search-input {
@@ -358,6 +361,12 @@ ul, li {
358
  color: var(--on-surface);
359
  }
360
 
 
 
 
 
 
 
361
  .icon-btn-sm {
362
  width: 28px;
363
  height: 28px;
@@ -802,6 +811,154 @@ body.light .left-sidebar.expanded-wide .browser-item:nth-child(even) {
802
  border-left-width: 0.75px;
803
  }
804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
  /* ═══════════════════════════════════════════════════════════
806
  Status Bar
807
  ═══════════════════════════════════════════════════════════ */
@@ -1731,15 +1888,17 @@ body.mobile .multi-action-btn {
1731
  }
1732
 
1733
  .result-checkbox {
1734
- display: none;
1735
  flex-shrink: 0;
1736
- width: 16px;
1737
- height: 16px;
1738
  margin-right: 6px;
1739
  align-self: center;
1740
  accent-color: var(--primary);
 
1741
  }
1742
- body.multiselect .result-checkbox { display: block; }
 
1743
  body.multiselect .result-file-icon { display: none; }
1744
  .result-item.selected {
1745
  background: color-mix(in srgb, var(--primary) 15%, transparent) !important;
 
238
  flex-shrink: 0;
239
  margin-left: 12px;
240
  color: var(--on-surface-variant);
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
  }
245
 
246
  .search-input {
 
361
  color: var(--on-surface);
362
  }
363
 
364
+ .icon-btn .ui-icon,
365
+ .search-icon .ui-icon {
366
+ width: 18px;
367
+ height: 18px;
368
+ }
369
+
370
  .icon-btn-sm {
371
  width: 28px;
372
  height: 28px;
 
811
  border-left-width: 0.75px;
812
  }
813
 
814
+ .ui-icon-menu::before {
815
+ content: "";
816
+ position: absolute;
817
+ left: 2px;
818
+ top: 4px;
819
+ width: 12px;
820
+ height: 1.8px;
821
+ background: currentColor;
822
+ border-radius: 999px;
823
+ box-shadow: 0 4px 0 currentColor, 0 8px 0 currentColor;
824
+ }
825
+
826
+ .ui-icon-search::before {
827
+ content: "";
828
+ position: absolute;
829
+ left: 2px;
830
+ top: 2px;
831
+ width: 9px;
832
+ height: 9px;
833
+ border: 1.8px solid currentColor;
834
+ border-radius: 50%;
835
+ }
836
+
837
+ .ui-icon-search::after {
838
+ content: "";
839
+ position: absolute;
840
+ right: 2px;
841
+ bottom: 2px;
842
+ width: 6px;
843
+ height: 1.8px;
844
+ background: currentColor;
845
+ border-radius: 999px;
846
+ transform: rotate(45deg);
847
+ }
848
+
849
+ .ui-icon-sliders::before,
850
+ .ui-icon-sliders::after {
851
+ content: "";
852
+ position: absolute;
853
+ left: 2px;
854
+ width: 12px;
855
+ height: 1.8px;
856
+ background: currentColor;
857
+ border-radius: 999px;
858
+ }
859
+
860
+ .ui-icon-sliders::before {
861
+ top: 5px;
862
+ box-shadow: 0 6px 0 currentColor;
863
+ }
864
+
865
+ .ui-icon-sliders::after {
866
+ top: 5px;
867
+ left: 8px;
868
+ width: 4px;
869
+ height: 4px;
870
+ border: 1.8px solid currentColor;
871
+ border-radius: 50%;
872
+ background: var(--surface-container);
873
+ box-shadow: -6px 6px 0 -1px var(--surface-container), -6px 6px 0 0 currentColor;
874
+ }
875
+
876
+ .ui-icon-theme::before {
877
+ content: "";
878
+ position: absolute;
879
+ inset: 2px;
880
+ border-radius: 50%;
881
+ border: 1.8px solid currentColor;
882
+ }
883
+
884
+ .ui-icon-theme::after {
885
+ content: "";
886
+ position: absolute;
887
+ top: 2px;
888
+ right: 2px;
889
+ width: 7px;
890
+ height: 11px;
891
+ background: var(--surface-container);
892
+ border-radius: 999px;
893
+ }
894
+
895
+ .ui-icon-phone::before {
896
+ content: "";
897
+ position: absolute;
898
+ left: 4px;
899
+ top: 1px;
900
+ width: 8px;
901
+ height: 14px;
902
+ border: 1.8px solid currentColor;
903
+ border-radius: 3px;
904
+ }
905
+
906
+ .ui-icon-phone::after {
907
+ content: "";
908
+ position: absolute;
909
+ left: 7px;
910
+ bottom: 3px;
911
+ width: 2px;
912
+ height: 2px;
913
+ background: currentColor;
914
+ border-radius: 50%;
915
+ }
916
+
917
+ .ui-icon-desktop::before {
918
+ content: "";
919
+ position: absolute;
920
+ left: 1px;
921
+ top: 2px;
922
+ width: 14px;
923
+ height: 9px;
924
+ border: 1.8px solid currentColor;
925
+ border-radius: 3px;
926
+ }
927
+
928
+ .ui-icon-desktop::after {
929
+ content: "";
930
+ position: absolute;
931
+ left: 5px;
932
+ bottom: 1px;
933
+ width: 6px;
934
+ height: 1.8px;
935
+ background: currentColor;
936
+ border-radius: 999px;
937
+ box-shadow: 0 -3px 0 -0.3px var(--surface-container);
938
+ }
939
+
940
+ .ui-icon-devices::before {
941
+ content: "";
942
+ position: absolute;
943
+ left: 1px;
944
+ top: 3px;
945
+ width: 9px;
946
+ height: 7px;
947
+ border: 1.8px solid currentColor;
948
+ border-radius: 2px;
949
+ }
950
+
951
+ .ui-icon-devices::after {
952
+ content: "";
953
+ position: absolute;
954
+ right: 1px;
955
+ top: 1px;
956
+ width: 5px;
957
+ height: 10px;
958
+ border: 1.8px solid currentColor;
959
+ border-radius: 2px;
960
+ }
961
+
962
  /* ═══════════════════════════════════════════════════════════
963
  Status Bar
964
  ═══════════════════════════════════════════════════════════ */
 
1888
  }
1889
 
1890
  .result-checkbox {
1891
+ display: block;
1892
  flex-shrink: 0;
1893
+ width: 18px;
1894
+ height: 18px;
1895
  margin-right: 6px;
1896
  align-self: center;
1897
  accent-color: var(--primary);
1898
+ opacity: 0.85;
1899
  }
1900
+ .result-checkbox:not(:checked) { visibility: hidden; }
1901
+ body.multiselect .result-checkbox { visibility: visible; }
1902
  body.multiselect .result-file-icon { display: none; }
1903
  .result-item.selected {
1904
  background: color-mix(in srgb, var(--primary) 15%, transparent) !important;