@linxin666/dsh-client-ui-skin-center 0.4.2 → 0.4.4

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.
package/lib/client.js CHANGED
@@ -7,7 +7,7 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  //#region \0dsh-css:src/client/skin-center.module.css.mjs
10
- const css = "body[data-dsh-skin-center] .TJMolG_sectionList{margin:0;padding:0;list-style:none}body[data-dsh-skin-center] .TJMolG_pluginCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}body[data-dsh-skin-center] .TJMolG_pluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_cardHeaderStatic{align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}body[data-dsh-skin-center] .TJMolG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_pluginName{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:15px;font-weight:600;line-height:1.4;display:flex}body[data-dsh-skin-center] .TJMolG_cardDescription{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_cardBody{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}body[data-dsh-skin-center] .TJMolG_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .TJMolG_intro{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .TJMolG_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .TJMolG_themeButton{font:inherit;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .TJMolG_themeButton:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .TJMolG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;padding:12px;transition:border-color .16s;display:flex}body[data-dsh-skin-center] .TJMolG_card:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .TJMolG_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .TJMolG_cardTagline{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.45}body[data-dsh-skin-center] .TJMolG_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .TJMolG_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .TJMolG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_button{font:inherit;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .TJMolG_button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_buttonPrimary{border-color:var(--dsw-alias-button-info-fill);background:var(--dsw-alias-button-info-fill);color:var(--dsw-alias-bg-layer-3)}body[data-dsh-skin-center] .TJMolG_buttonPrimary:hover:not(:disabled),body[data-dsh-skin-center] .TJMolG_buttonPrimary:active:not(:disabled){opacity:.88}body[data-dsh-skin-center] .TJMolG_buttonDanger{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-state-danger-primary,#d93025);background:0 0}body[data-dsh-skin-center] .TJMolG_buttonDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-tertiary,#d9302514)}body[data-dsh-skin-center] .TJMolG_buttonDangerConfirm{border-color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-primary,#d93025);color:#fff}body[data-dsh-skin-center] .TJMolG_toolbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;margin:4px 0 2px;display:flex}body[data-dsh-skin-center] .TJMolG_verifySummary{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 12px;font-size:12px;line-height:1.4}body[data-dsh-skin-center] .TJMolG_verifySummarySuccess{color:var(--dsw-alias-state-success-primary,#0f6b3a);border-color:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_verifySummaryWarning{color:var(--dsw-alias-state-warning-primary,#b06000);border-color:var(--dsw-alias-state-warning-tertiary,#feebd3)}body[data-dsh-skin-center] .TJMolG_badgeSuccess{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_badgeWarning{color:var(--dsw-alias-state-warning-primary,#b06000);background:var(--dsw-alias-state-warning-tertiary,#feebd3)}body[data-dsh-skin-center] .TJMolG_badgeDanger{color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-tertiary,#d930251f)}body[data-dsh-skin-center] .TJMolG_integrityNote{color:var(--dsw-alias-state-warning-primary,#b06000);font-size:11px;line-height:1.4}body[data-dsh-skin-center] .TJMolG_buttonGhost{opacity:.5;background:0 0;border-color:#0000}body[data-dsh-skin-center] .TJMolG_button:disabled{opacity:.5;cursor:default}body[data-dsh-skin-center] .TJMolG_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .TJMolG_enableRow{flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_enableLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}body[data-dsh-skin-center] .TJMolG_enableHint{min-width:100%;color:var(--dsw-alias-label-secondary,#6b7280);flex:1;margin:0;font-size:12px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_switch{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-3,#e2e8f0);cursor:pointer;border-radius:999px;flex:none;align-items:center;width:40px;height:22px;padding:2px;transition:background .12s,border-color .12s;display:inline-flex;position:relative}body[data-dsh-skin-center] .TJMolG_switchOn{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-brand-primary,#2b7cd9)}body[data-dsh-skin-center] .TJMolG_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_switchThumb{background:var(--dsw-alias-label-primary-foreground,#fff);width:18px;height:18px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;transition:transform .12s;display:block;transform:translate(0)}body[data-dsh-skin-center] .TJMolG_switchOn .TJMolG_switchThumb{transform:translate(18px)}body[data-dsh-skin-center] .TJMolG_offNote{color:var(--dsw-alias-label-secondary,#6b7280);margin:0;font-size:12.5px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_backgroundLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}body[data-dsh-skin-center] .TJMolG_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .TJMolG_backgroundRange{background:var(--dsw-alias-label-tertiary,#9aa4b5);background:color-mix(in srgb, var(--dsw-alias-label-tertiary,#9aa4b5) 45%, transparent);width:100%;height:4px;box-shadow:0 0 0 1px var(--dsw-alias-border-l3,#cbd5e1);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;margin:0}body[data-dsh-skin-center] .TJMolG_backgroundRange::-webkit-slider-runnable-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-moz-range-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%;margin-top:-5px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .TJMolG_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .TJMolG_pluginCard,body[data-dsh-skin-center] .TJMolG_card,body[data-dsh-skin-center] .TJMolG_themeButton,body[data-dsh-skin-center] .TJMolG_button,body[data-dsh-skin-center] .TJMolG_switch,body[data-dsh-skin-center] .TJMolG_switchThumb{transition:none}}body[data-dsh-skin-center] .TJMolG_wallpaperSection{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:10px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperStatus{color:var(--dsw-alias-label-secondary,#6b7280);align-items:center;gap:8px;font-size:12px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperStatusError{color:var(--dsw-alias-state-danger,#c53030)}body[data-dsh-skin-center] .TJMolG_wallpaperControls{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperGrid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;display:grid}body[data-dsh-skin-center] .TJMolG_wallpaperCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;padding:8px;transition:border-color .16s;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_wallpaperThumbWrap{aspect-ratio:16/9;background:var(--dsw-alias-bg-layer-1,#f1f5f9);border-radius:6px;position:relative;overflow:hidden}body[data-dsh-skin-center] .TJMolG_wallpaperThumb{object-fit:cover;width:100%;height:100%;display:block}body[data-dsh-skin-center] .TJMolG_wallpaperThumbEmpty{width:100%;height:100%}body[data-dsh-skin-center] .TJMolG_wallpaperType{color:var(--dsw-alias-label-primary,#172a45);background:var(--dsw-alias-bg-layer-2,#ffffffd9);border-radius:4px;padding:3px 6px;font-size:10.5px;line-height:1;position:absolute;top:6px;left:6px}body[data-dsh-skin-center] .TJMolG_wallpaperThumbWrap .TJMolG_badge{position:absolute;top:6px;right:6px}body[data-dsh-skin-center] .TJMolG_wallpaperName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.35;overflow:hidden}body[data-dsh-skin-center] .TJMolG_wallpaperActions{flex-wrap:wrap;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeCard{overflow:hidden}body[data-dsh-skin-center] .TJMolG_customThemeEditor{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex-direction:column;gap:14px;margin:2px -2px -2px;padding:14px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeScheme{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeFields{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}body[data-dsh-skin-center] .TJMolG_customThemeField,body[data-dsh-skin-center] .TJMolG_customThemeContrast{flex-direction:column;gap:7px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeFieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}body[data-dsh-skin-center] .TJMolG_customThemeInputRow{align-items:center;gap:7px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeColor{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:pointer;border-radius:7px;flex:none;width:34px;height:30px;padding:2px}body[data-dsh-skin-center] .TJMolG_customThemeHex{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major);width:100%;min-width:0;height:30px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;outline:none;padding:0 9px;font-size:12px}body[data-dsh-skin-center] .TJMolG_customThemeHex:focus,body[data-dsh-skin-center] .TJMolG_customThemeColor:focus-visible{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 2px var(--dsw-alias-button-primary-dimmed)}body[data-dsh-skin-center] .TJMolG_customThemeFooter{justify-content:space-between;align-items:center;gap:12px;padding-top:2px;display:flex}@media (width<=680px){body[data-dsh-skin-center] .TJMolG_customThemeFields{grid-template-columns:1fr}body[data-dsh-skin-center] .TJMolG_customThemeFooter{flex-direction:column;align-items:flex-start}}body[data-dsh-skin-center] .TJMolG_wallpaperToolbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_ratingFilterGroup{align-items:center;gap:4px;display:flex}body[data-dsh-skin-center] .TJMolG_ratingFilterButton{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:6px;padding:4px 10px;font-size:12px;transition:all .15s}body[data-dsh-skin-center] .TJMolG_ratingFilterButton:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14);color:var(--dsw-alias-label-primary)}body[data-dsh-skin-center] .TJMolG_ratingFilterActive{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff;border-color:#0000}body[data-dsh-skin-center] .TJMolG_ratingFilterActive:hover{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff}body[data-dsh-skin-center] .TJMolG_wallpaperRating{text-transform:uppercase;border-radius:4px;padding:3px 6px;font-size:10px;font-weight:600;line-height:1;position:absolute;bottom:6px;right:6px}body[data-dsh-skin-center] .TJMolG_ratingG{color:var(--dsw-alias-label-secondary,#94a3b8);background:var(--dsw-alias-bg-layer-2,#0009)}body[data-dsh-skin-center] .TJMolG_ratingPg13{color:#fbbf24;background:#000000b3;border:1px solid #fbbf2466}body[data-dsh-skin-center] .TJMolG_ratingR18{color:#fff;background:#dc2626;font-weight:700;box-shadow:0 1px 4px #dc262666}body[data-dsh-skin-center] .TJMolG_wallpaperPagination{flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin:16px 0 8px;display:flex}body[data-dsh-skin-center] .TJMolG_pageButton{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:center;border-radius:6px;min-width:30px;padding:4px 10px;font-size:12px;transition:all .15s}body[data-dsh-skin-center] .TJMolG_pageButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14);color:var(--dsw-alias-label-primary)}body[data-dsh-skin-center] .TJMolG_pageButton:disabled{opacity:.4;cursor:not-allowed}body[data-dsh-skin-center] .TJMolG_pageButtonActive{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff;border-color:#0000}body[data-dsh-skin-center] .TJMolG_pageEllipsis{color:var(--dsw-alias-label-dimmed,#888);user-select:none;padding:0 4px;font-size:12px}body[data-dsh-skin-center] .TJMolG_pageJumpForm{align-items:center;gap:4px;margin-left:8px;display:inline-flex}body[data-dsh-skin-center] .TJMolG_pageJumpInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major,#0003);width:44px;height:26px;color:var(--dsw-alias-label-primary);text-align:center;border-radius:6px;outline:none;padding:0 4px;font-size:12px}body[data-dsh-skin-center] .TJMolG_pageJumpInput:focus{border-color:var(--dsw-alias-brand-primary)}body[data-dsh-skin-center] .TJMolG_pageTotalInfo{color:var(--dsw-alias-label-dimmed,#888);margin-left:6px;font-size:12px}";
10
+ const css = "body[data-dsh-skin-center] .TJMolG_sectionList{margin:0;padding:0;list-style:none}body[data-dsh-skin-center] .TJMolG_pluginCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}body[data-dsh-skin-center] .TJMolG_pluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_cardHeaderStatic{align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}body[data-dsh-skin-center] .TJMolG_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_pluginName{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:15px;font-weight:600;line-height:1.4;display:flex}body[data-dsh-skin-center] .TJMolG_cardDescription{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_cardBody{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}body[data-dsh-skin-center] .TJMolG_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .TJMolG_intro{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .TJMolG_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .TJMolG_themeButton{font:inherit;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .TJMolG_themeButton:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .TJMolG_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;padding:12px;transition:border-color .16s;display:flex}body[data-dsh-skin-center] .TJMolG_card:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .TJMolG_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .TJMolG_cardTagline{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.45}body[data-dsh-skin-center] .TJMolG_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .TJMolG_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .TJMolG_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_button{font:inherit;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;line-height:1.5;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .TJMolG_button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .TJMolG_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_buttonPrimary{border-color:var(--dsw-alias-button-info-fill);background:var(--dsw-alias-button-info-fill);color:var(--dsw-alias-bg-layer-3)}body[data-dsh-skin-center] .TJMolG_buttonPrimary:hover:not(:disabled),body[data-dsh-skin-center] .TJMolG_buttonPrimary:active:not(:disabled){opacity:.88}body[data-dsh-skin-center] .TJMolG_buttonDanger{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-state-danger-primary,#d93025);background:0 0}body[data-dsh-skin-center] .TJMolG_buttonDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-tertiary,#d9302514)}body[data-dsh-skin-center] .TJMolG_buttonDangerConfirm{border-color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-primary,#d93025);color:#fff}body[data-dsh-skin-center] .TJMolG_toolbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;margin:4px 0 2px;display:flex}body[data-dsh-skin-center] .TJMolG_verifySummary{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:6px 12px;font-size:12px;line-height:1.4}body[data-dsh-skin-center] .TJMolG_verifySummarySuccess{color:var(--dsw-alias-state-success-primary,#0f6b3a);border-color:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_verifySummaryWarning{color:var(--dsw-alias-state-warning-primary,#b06000);border-color:var(--dsw-alias-state-warning-tertiary,#feebd3)}body[data-dsh-skin-center] .TJMolG_badgeSuccess{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .TJMolG_badgeWarning{color:var(--dsw-alias-state-warning-primary,#b06000);background:var(--dsw-alias-state-warning-tertiary,#feebd3)}body[data-dsh-skin-center] .TJMolG_badgeDanger{color:var(--dsw-alias-state-danger-primary,#d93025);background:var(--dsw-alias-state-danger-tertiary,#d930251f)}body[data-dsh-skin-center] .TJMolG_integrityNote{color:var(--dsw-alias-state-warning-primary,#b06000);font-size:11px;line-height:1.4}body[data-dsh-skin-center] .TJMolG_buttonGhost{opacity:.5;background:0 0;border-color:#0000}body[data-dsh-skin-center] .TJMolG_button:disabled{opacity:.5;cursor:default}body[data-dsh-skin-center] .TJMolG_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .TJMolG_enableRow{flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_enableLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}body[data-dsh-skin-center] .TJMolG_enableHint{min-width:100%;color:var(--dsw-alias-label-secondary,#6b7280);flex:1;margin:0;font-size:12px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_switch{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-3,#e2e8f0);cursor:pointer;border-radius:999px;flex:none;align-items:center;width:40px;height:22px;padding:2px;transition:background .12s,border-color .12s;display:inline-flex;position:relative}body[data-dsh-skin-center] .TJMolG_switchOn{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-brand-primary,#2b7cd9)}body[data-dsh-skin-center] .TJMolG_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_switchThumb{background:var(--dsw-alias-label-primary-foreground,#fff);width:18px;height:18px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;transition:transform .12s;display:block;transform:translate(0)}body[data-dsh-skin-center] .TJMolG_switchOn .TJMolG_switchThumb{transform:translate(18px)}body[data-dsh-skin-center] .TJMolG_offNote{color:var(--dsw-alias-label-secondary,#6b7280);margin:0;font-size:12.5px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_backgroundLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}body[data-dsh-skin-center] .TJMolG_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .TJMolG_backgroundRange{background:var(--dsw-alias-label-tertiary,#9aa4b5);background:color-mix(in srgb, var(--dsw-alias-label-tertiary,#9aa4b5) 45%, transparent);width:100%;height:4px;box-shadow:0 0 0 1px var(--dsw-alias-border-l3,#cbd5e1);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;margin:0}body[data-dsh-skin-center] .TJMolG_backgroundRange::-webkit-slider-runnable-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-moz-range-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%;margin-top:-5px}body[data-dsh-skin-center] .TJMolG_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .TJMolG_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .TJMolG_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .TJMolG_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .TJMolG_pluginCard,body[data-dsh-skin-center] .TJMolG_card,body[data-dsh-skin-center] .TJMolG_themeButton,body[data-dsh-skin-center] .TJMolG_button,body[data-dsh-skin-center] .TJMolG_switch,body[data-dsh-skin-center] .TJMolG_switchThumb{transition:none}}body[data-dsh-skin-center] .TJMolG_wallpaperSection{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:10px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperStatus{color:var(--dsw-alias-label-secondary,#6b7280);align-items:center;gap:8px;font-size:12px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperStatusError{color:var(--dsw-alias-state-danger,#c53030)}body[data-dsh-skin-center] .TJMolG_wallpaperControls{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperGrid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;display:grid}body[data-dsh-skin-center] .TJMolG_wallpaperCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;flex-direction:column;gap:6px;padding:8px;transition:border-color .16s;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .TJMolG_wallpaperThumbWrap{aspect-ratio:16/9;background:var(--dsw-alias-bg-layer-1,#f1f5f9);border-radius:6px;position:relative;overflow:hidden}body[data-dsh-skin-center] .TJMolG_wallpaperThumb{object-fit:cover;width:100%;height:100%;display:block}body[data-dsh-skin-center] .TJMolG_wallpaperThumbEmpty{width:100%;height:100%}body[data-dsh-skin-center] .TJMolG_wallpaperType{color:var(--dsw-alias-label-primary,#172a45);background:var(--dsw-alias-bg-layer-2,#ffffffd9);border-radius:4px;padding:3px 6px;font-size:10.5px;line-height:1;position:absolute;top:6px;left:6px}body[data-dsh-skin-center] .TJMolG_wallpaperThumbWrap .TJMolG_badge{position:absolute;top:6px;right:6px}body[data-dsh-skin-center] .TJMolG_wallpaperName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.35;overflow:hidden}body[data-dsh-skin-center] .TJMolG_wallpaperActions{flex-wrap:wrap;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeCard{overflow:hidden}body[data-dsh-skin-center] .TJMolG_customThemeEditor{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex-direction:column;gap:14px;margin:2px -2px -2px;padding:14px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeScheme{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeFields{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}body[data-dsh-skin-center] .TJMolG_customThemeField,body[data-dsh-skin-center] .TJMolG_customThemeContrast{flex-direction:column;gap:7px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeFieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}body[data-dsh-skin-center] .TJMolG_customThemeInputRow{align-items:center;gap:7px;min-width:0;display:flex}body[data-dsh-skin-center] .TJMolG_customThemeColor{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:pointer;border-radius:7px;flex:none;width:34px;height:30px;padding:2px}body[data-dsh-skin-center] .TJMolG_customThemeHex{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major);width:100%;min-width:0;height:30px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;outline:none;padding:0 9px;font-size:12px}body[data-dsh-skin-center] .TJMolG_customThemeHex:focus,body[data-dsh-skin-center] .TJMolG_customThemeColor:focus-visible{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 2px var(--dsw-alias-button-primary-dimmed)}body[data-dsh-skin-center] .TJMolG_customThemeFooter{justify-content:space-between;align-items:center;gap:12px;padding-top:2px;display:flex}@media (width<=680px){body[data-dsh-skin-center] .TJMolG_customThemeFields{grid-template-columns:1fr}body[data-dsh-skin-center] .TJMolG_customThemeFooter{flex-direction:column;align-items:flex-start}}body[data-dsh-skin-center] .TJMolG_wallpaperToolbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin:8px 0;display:flex}body[data-dsh-skin-center] .TJMolG_ratingFilterGroup{align-items:center;gap:4px;display:flex}body[data-dsh-skin-center] .TJMolG_ratingFilterButton{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:6px;padding:4px 10px;font-size:12px;transition:all .15s}body[data-dsh-skin-center] .TJMolG_ratingFilterButton:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14);color:var(--dsw-alias-label-primary)}body[data-dsh-skin-center] .TJMolG_ratingFilterActive{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff;border-color:#0000}body[data-dsh-skin-center] .TJMolG_ratingFilterActive:hover{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff}body[data-dsh-skin-center] .TJMolG_wallpaperRating{text-transform:uppercase;border-radius:4px;padding:3px 6px;font-size:10px;font-weight:600;line-height:1;position:absolute;bottom:6px;right:6px}body[data-dsh-skin-center] .TJMolG_ratingG{color:var(--dsw-alias-label-secondary,#94a3b8);background:var(--dsw-alias-bg-layer-2,#0009)}body[data-dsh-skin-center] .TJMolG_ratingPg13{color:#fbbf24;background:#000000b3;border:1px solid #fbbf2466}body[data-dsh-skin-center] .TJMolG_ratingR18{color:#fff;background:#dc2626;font-weight:700;box-shadow:0 1px 4px #dc262666}body[data-dsh-skin-center] .TJMolG_wallpaperPagination{flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin:16px 0 8px;display:flex}body[data-dsh-skin-center] .TJMolG_pageButton{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:center;border-radius:6px;min-width:30px;padding:4px 10px;font-size:12px;transition:all .15s}body[data-dsh-skin-center] .TJMolG_pageButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14);color:var(--dsw-alias-label-primary)}body[data-dsh-skin-center] .TJMolG_pageButton:disabled{opacity:.4;cursor:not-allowed}body[data-dsh-skin-center] .TJMolG_pageButtonActive{background:var(--dsw-alias-brand-primary,#2563eb);color:#fff;border-color:#0000}body[data-dsh-skin-center] .TJMolG_pageEllipsis{color:var(--dsw-alias-label-dimmed,#888);user-select:none;padding:0 4px;font-size:12px}body[data-dsh-skin-center] .TJMolG_pageJumpForm{align-items:center;gap:4px;margin-left:8px;display:inline-flex}body[data-dsh-skin-center] .TJMolG_pageJumpInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major,#0003);width:44px;height:26px;color:var(--dsw-alias-label-primary);text-align:center;border-radius:6px;outline:none;padding:0 4px;font-size:12px}body[data-dsh-skin-center] .TJMolG_pageJumpInput:focus{border-color:var(--dsw-alias-brand-primary)}body[data-dsh-skin-center] .TJMolG_pageTotalInfo{color:var(--dsw-alias-label-dimmed,#888);margin-left:6px;font-size:12px}body[data-dsh-skin-center] .TJMolG_wallpaperDirs{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperDir{background:var(--dsw-alias-bg-layer-2,#7f7f7f14);border-radius:6px;align-items:center;gap:6px;padding:4px 6px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperDirPath{min-width:0;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:12px;overflow:hidden}body[data-dsh-skin-center] .TJMolG_wallpaperDirRemove{width:20px;height:20px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border:0;border-radius:4px;flex:none;font-size:13px;line-height:1}body[data-dsh-skin-center] .TJMolG_wallpaperDirRemove:hover{color:var(--dsw-alias-state-danger,#c53030)}body[data-dsh-skin-center] .TJMolG_wallpaperDirAdd{align-items:center;gap:6px;display:flex}body[data-dsh-skin-center] .TJMolG_wallpaperDirInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-input-major,#0003);min-width:0;height:28px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;flex:1;padding:0 8px;font-size:12px}body[data-dsh-skin-center] .TJMolG_wallpaperDirInput:focus{border-color:var(--dsw-alias-brand-primary)}body[data-dsh-skin-center] .TJMolG_dirBrowserOverlay{z-index:60;background:#0000006b;justify-content:center;align-items:center;padding:16px;display:flex;position:fixed;inset:0}body[data-dsh-skin-center] .TJMolG_dirBrowser{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,#1c1c1e);width:min(520px,100%);max-height:min(72vh,560px);color:var(--dsw-alias-label-primary);border-radius:12px;outline:none;flex-direction:column;gap:10px;padding:14px;display:flex;box-shadow:0 18px 48px #00000047}body[data-dsh-skin-center] .TJMolG_dirBrowserTitle{font-size:13px;font-weight:600}body[data-dsh-skin-center] .TJMolG_dirBrowserCrumbs{color:var(--dsw-alias-label-secondary,#6b7280);flex-wrap:wrap;align-items:center;gap:2px;font-size:12px;display:flex}body[data-dsh-skin-center] .TJMolG_dirBrowserCrumb{align-items:center;gap:2px;max-width:180px;display:inline-flex}body[data-dsh-skin-center] .TJMolG_dirBrowserCrumbSep{color:var(--dsw-alias-label-dimmed,#888)}body[data-dsh-skin-center] .TJMolG_dirBrowserCrumbButton{max-width:100%;color:inherit;cursor:pointer;text-overflow:ellipsis;white-space:nowrap;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:12px;overflow:hidden}body[data-dsh-skin-center] .TJMolG_dirBrowserCrumbButton:hover{background:var(--dsw-alias-bg-layer-2,#7f7f7f1f);color:var(--dsw-alias-label-primary)}body[data-dsh-skin-center] .TJMolG_dirBrowserList{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;flex:1;gap:2px;min-height:140px;padding:4px;display:flex;overflow:auto}body[data-dsh-skin-center] .TJMolG_dirBrowserEntry{color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;text-overflow:ellipsis;white-space:nowrap;background:0 0;border:0;border-radius:6px;padding:7px 8px;font-size:12.5px;overflow:hidden}body[data-dsh-skin-center] .TJMolG_dirBrowserEntry:hover{background:var(--dsw-alias-bg-layer-2,#7f7f7f1f)}body[data-dsh-skin-center] .TJMolG_dirBrowserNote{color:var(--dsw-alias-label-secondary,#6b7280);padding:6px 8px;font-size:12px}body[data-dsh-skin-center] .TJMolG_dirBrowserError{color:var(--dsw-alias-state-danger,#c53030);word-break:break-all;padding:6px 8px;font-size:12px}body[data-dsh-skin-center] .TJMolG_dirBrowserHidden{color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}body[data-dsh-skin-center] .TJMolG_dirBrowserActions{justify-content:flex-end;gap:8px;display:flex}";
11
11
  const tagId = "@linxin666/dsh-client-ui-skin-center/src/client/skin-center.module.css";
12
12
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
13
13
  const tag = document.createElement("style");
@@ -54,6 +54,19 @@ window.__ModuleLoader__.load({
54
54
  "customThemeHex": "TJMolG_customThemeHex",
55
55
  "customThemeInputRow": "TJMolG_customThemeInputRow",
56
56
  "customThemeScheme": "TJMolG_customThemeScheme",
57
+ "dirBrowser": "TJMolG_dirBrowser",
58
+ "dirBrowserActions": "TJMolG_dirBrowserActions",
59
+ "dirBrowserCrumb": "TJMolG_dirBrowserCrumb",
60
+ "dirBrowserCrumbButton": "TJMolG_dirBrowserCrumbButton",
61
+ "dirBrowserCrumbSep": "TJMolG_dirBrowserCrumbSep",
62
+ "dirBrowserCrumbs": "TJMolG_dirBrowserCrumbs",
63
+ "dirBrowserEntry": "TJMolG_dirBrowserEntry",
64
+ "dirBrowserError": "TJMolG_dirBrowserError",
65
+ "dirBrowserHidden": "TJMolG_dirBrowserHidden",
66
+ "dirBrowserList": "TJMolG_dirBrowserList",
67
+ "dirBrowserNote": "TJMolG_dirBrowserNote",
68
+ "dirBrowserOverlay": "TJMolG_dirBrowserOverlay",
69
+ "dirBrowserTitle": "TJMolG_dirBrowserTitle",
57
70
  "enableHint": "TJMolG_enableHint",
58
71
  "enableLabel": "TJMolG_enableLabel",
59
72
  "enableRow": "TJMolG_enableRow",
@@ -95,6 +108,12 @@ window.__ModuleLoader__.load({
95
108
  "wallpaperActions": "TJMolG_wallpaperActions",
96
109
  "wallpaperCard": "TJMolG_wallpaperCard",
97
110
  "wallpaperControls": "TJMolG_wallpaperControls",
111
+ "wallpaperDir": "TJMolG_wallpaperDir",
112
+ "wallpaperDirAdd": "TJMolG_wallpaperDirAdd",
113
+ "wallpaperDirInput": "TJMolG_wallpaperDirInput",
114
+ "wallpaperDirPath": "TJMolG_wallpaperDirPath",
115
+ "wallpaperDirRemove": "TJMolG_wallpaperDirRemove",
116
+ "wallpaperDirs": "TJMolG_wallpaperDirs",
98
117
  "wallpaperGrid": "TJMolG_wallpaperGrid",
99
118
  "wallpaperName": "TJMolG_wallpaperName",
100
119
  "wallpaperPagination": "TJMolG_wallpaperPagination",
@@ -385,6 +404,31 @@ window.__ModuleLoader__.load({
385
404
  const BACKDROP_ACTIVE_ATTR = "data-dsh-backdrop-active";
386
405
  /** The shared composer-seat neutralizer style's own attribute. */
387
406
  const SCENE_NEUTRALIZER_ATTR = "data-dsh-scene-neutralizer";
407
+ /**
408
+ * Attribute of the composer frost follower: a body-level, fixed, empty element
409
+ * sized to the input card's border box while a scene is active.
410
+ *
411
+ * It exists because the frost must NOT be painted on the card. A non-none
412
+ * backdrop-filter (like transform, filter, contain or perspective) makes an
413
+ * element the CONTAINING BLOCK for its fixed-position descendants, and the
414
+ * official shell renders its tooltips inside the composer card: the composer
415
+ * send arrow's bubble is position: fixed, so with the frost on the card it
416
+ * resolved its viewport coordinates against the card, landed far outside it
417
+ * but still inside the conversation scrollport, added its own height to
418
+ * scrollHeight, and every hover clamped the scroll to the new bottom — the
419
+ * whole conversation jumped by hundreds of pixels and snapped back on the
420
+ * next frame (issue #1724). Documented throughout the catalog skins as the
421
+ * reason a card ancestor never carries a filter.
422
+ *
423
+ * A body-level sibling is not an ancestor of the card, so it may carry the
424
+ * blur; because nested backdrop-filters do not compose, the card itself must
425
+ * stay filter-free for the follower's blur to read through its translucent
426
+ * fill. The element is empty and pointer-events:none, and it is a sibling of
427
+ * the app root, never inside the scrollport.
428
+ */
429
+ const COMPOSER_FROST_ATTR = "data-dsh-composer-frost";
430
+ /** Stacking rung of the follower: above the skin art layers, below the shell. */
431
+ const COMPOSER_FROST_Z_INDEX = "-1";
388
432
  /** Conversation-content marker: set while the active conversation has rows. */
389
433
  const CONVERSATION_CONTENT_ATTR = "data-dsh-conversation-content";
390
434
  /**
@@ -435,6 +479,7 @@ window.__ModuleLoader__.load({
435
479
  ensureSceneNeutralizer(doc);
436
480
  startContentObserver(doc);
437
481
  } else stopContentObserver(doc);
482
+ syncComposerFrost(doc);
438
483
  }
439
484
  /**
440
485
  * Track whether the active conversation scrollport has message rows for the
@@ -446,6 +491,7 @@ window.__ModuleLoader__.load({
446
491
  const has = doc.body !== null && doc.body.querySelector(ACTIVE_CONVERSATION_CONTENT_SELECTOR) !== null;
447
492
  applyMarker(doc.body, CONVERSATION_CONTENT_ATTR, has);
448
493
  applyMarker(doc.documentElement, CONVERSATION_CONTENT_ATTR, has);
494
+ syncComposerFrost(doc);
449
495
  }
450
496
  /**
451
497
  * Coalesce the mutation bursts of a streaming conversation into one content
@@ -493,12 +539,17 @@ window.__ModuleLoader__.load({
493
539
  }
494
540
  applyMarker(doc.body, CONVERSATION_CONTENT_ATTR, false);
495
541
  applyMarker(doc.documentElement, CONVERSATION_CONTENT_ATTR, false);
542
+ syncComposerFrost(doc);
496
543
  }
497
544
  /**
498
545
  * Install the shared composer-seat neutralizer, keyed by head presence so a
499
546
  * cleared head (tests) or a re-mount re-creates it. Without the marker the
500
547
  * rules are inert, so the style can outlive a single mount without changing
501
548
  * any other look.
549
+ *
550
+ * The composer card is deliberately absent from this sheet: its frost rides
551
+ * {@link COMPOSER_FROST_ATTR} instead, because no rule here may put a
552
+ * containing-block property back on the card (issue #1724).
502
553
  */
503
554
  function ensureSceneNeutralizer(doc) {
504
555
  if (doc.head === null) return;
@@ -512,17 +563,156 @@ window.__ModuleLoader__.load({
512
563
  backdrop-filter: none !important;
513
564
  -webkit-backdrop-filter: none !important;
514
565
  }
515
- html[data-dsh-backdrop-active][data-dsh-conversation-content] [data-composer-card] {
566
+ html[data-dsh-backdrop-active][data-dsh-conversation-content] [${COMPOSER_FROST_ATTR}] {
516
567
  backdrop-filter: blur(var(--dsh-input-card-blur, 10px)) !important;
517
568
  -webkit-backdrop-filter: blur(var(--dsh-input-card-blur, 10px)) !important;
518
569
  }
519
570
  `;
520
571
  doc.head.appendChild(style);
521
572
  }
573
+ const frostFollowers = /* @__PURE__ */ new WeakMap();
574
+ /** The composer card the frost tracks, or null while none is mounted. */
575
+ function composerCardOf(doc) {
576
+ return doc.body === null ? null : doc.body.querySelector("[data-composer-card]");
577
+ }
578
+ /**
579
+ * Create the follower on first use and keep it sized to the composer card.
580
+ *
581
+ * The element is empty, fixed and inert: it paints nothing of its own, and it
582
+ * receives no pointer events, so it can never take a tap from the composer or
583
+ * the sidebar. Sizing uses one measure per animation frame (the shape
584
+ * shell-rendering.ts uses for the composer height) rather than a per-mutation
585
+ * read, and writes only when the box actually changed.
586
+ */
587
+ function syncFrostFollower(doc) {
588
+ const follower = frostFollowers.get(doc);
589
+ if (follower === void 0) return;
590
+ const card = composerCardOf(doc);
591
+ if (card === null) return;
592
+ if (follower.observed !== card) {
593
+ if (follower.observed !== null) follower.resizeObserver?.unobserve(follower.observed);
594
+ follower.observed = card;
595
+ follower.resizeObserver?.observe(card);
596
+ follower.applied = "";
597
+ }
598
+ const rect = card.getBoundingClientRect();
599
+ if (rect.width <= 0 || rect.height <= 0) return;
600
+ const radius = doc.defaultView?.getComputedStyle(card).borderRadius ?? "";
601
+ const box = [
602
+ rect.top,
603
+ rect.left,
604
+ rect.width,
605
+ rect.height,
606
+ radius
607
+ ].map((value) => typeof value === "number" ? Math.round(value) : value).join("|");
608
+ if (box === follower.applied) return;
609
+ follower.applied = box;
610
+ const style = follower.el.style;
611
+ style.top = `${Math.round(rect.top)}px`;
612
+ style.left = `${Math.round(rect.left)}px`;
613
+ style.width = `${Math.round(rect.width)}px`;
614
+ style.height = `${Math.round(rect.height)}px`;
615
+ style.borderRadius = radius;
616
+ }
617
+ /** Coalesce a burst of geometry changes into one measurement per frame. */
618
+ function scheduleFrostSync(doc) {
619
+ const follower = frostFollowers.get(doc);
620
+ if (follower === void 0 || follower.frame !== null) return;
621
+ const win = doc.defaultView;
622
+ if (win === null || typeof win.requestAnimationFrame !== "function") {
623
+ syncFrostFollower(doc);
624
+ return;
625
+ }
626
+ follower.frame = win.requestAnimationFrame(() => {
627
+ follower.frame = null;
628
+ if (!frostFollowers.has(doc)) return;
629
+ syncFrostFollower(doc);
630
+ });
631
+ }
632
+ /** Mount the follower while a scene is visible and start tracking the card. */
633
+ function startFrostFollower(doc) {
634
+ if (frostFollowers.has(doc) || doc.body === null) return;
635
+ const el = doc.createElement("div");
636
+ el.setAttribute(COMPOSER_FROST_ATTR, "");
637
+ el.setAttribute("aria-hidden", "true");
638
+ const style = el.style;
639
+ style.position = "fixed";
640
+ style.pointerEvents = "none";
641
+ style.zIndex = COMPOSER_FROST_Z_INDEX;
642
+ style.borderRadius = "inherit";
643
+ const follower = {
644
+ el,
645
+ resizeObserver: null,
646
+ observed: null,
647
+ onResize: null,
648
+ frame: null,
649
+ applied: ""
650
+ };
651
+ doc.body.appendChild(el);
652
+ frostFollowers.set(doc, follower);
653
+ const win = doc.defaultView;
654
+ if (win !== null && typeof win.ResizeObserver === "function") follower.resizeObserver = new win.ResizeObserver(() => scheduleFrostSync(doc));
655
+ if (win !== null && typeof win.addEventListener === "function") {
656
+ follower.onResize = () => scheduleFrostSync(doc);
657
+ win.addEventListener("resize", follower.onResize);
658
+ }
659
+ syncFrostFollower(doc);
660
+ }
661
+ /** Remove the follower and stop every loop that fed it. */
662
+ function stopFrostFollower(doc) {
663
+ const follower = frostFollowers.get(doc);
664
+ if (follower === void 0) return;
665
+ frostFollowers.delete(doc);
666
+ const win = doc.defaultView;
667
+ if (follower.frame !== null && win !== null && typeof win.cancelAnimationFrame === "function") {
668
+ win.cancelAnimationFrame(follower.frame);
669
+ follower.frame = null;
670
+ }
671
+ follower.resizeObserver?.disconnect();
672
+ follower.resizeObserver = null;
673
+ if (follower.onResize !== null) {
674
+ win?.removeEventListener("resize", follower.onResize);
675
+ follower.onResize = null;
676
+ }
677
+ follower.el.remove();
678
+ }
679
+ /**
680
+ * Attach the composer frost to the current scene state. Called from the shared
681
+ * marker sync so the follower exists exactly while a backdrop is visible and
682
+ * the conversation has content — the same gate the card's old frost rule used.
683
+ *
684
+ * The follower tracks the card through a ResizeObserver on the card itself and
685
+ * one interval-free re-schedule on every re-mount, so a conversation switch
686
+ * that replaces the composer keeps the frost aligned without a polling loop.
687
+ */
688
+ function syncComposerFrost(doc) {
689
+ if (!(doc.body !== null && doc.body.hasAttribute("data-dsh-backdrop-active") && doc.body.hasAttribute("data-dsh-conversation-content"))) {
690
+ stopFrostFollower(doc);
691
+ return;
692
+ }
693
+ startFrostFollower(doc);
694
+ }
522
695
  //#endregion
523
696
  //#region src/client/wallpaper.ts
524
697
  /** The section key the Host schema declares (mirrors src/index.ts). */
525
698
  const SKIN_WALLPAPER_NS = "skin-wallpaper";
699
+ /**
700
+ * Map one remote.directoryPicker.pick result onto the outcome the panel
701
+ * switches on. The composed picker refuses the verb with
702
+ * directory-picker/unavailable when it serves the browse capability, so
703
+ * that code is a capability answer, not a failure; every other error still
704
+ * rejects (a transport or assembly fault must never look like a cancel).
705
+ * @param result - the Remote result of the pick call.
706
+ * @returns the pick outcome; throws the Host's message on any other failure.
707
+ */
708
+ function mapDirPickResult(result) {
709
+ if (result.ok) return result.value === null ? { kind: "cancelled" } : {
710
+ kind: "picked",
711
+ path: result.value
712
+ };
713
+ if (result.error.code === "directory-picker/unavailable") return { kind: "unavailable" };
714
+ throw new Error(result.error.message);
715
+ }
526
716
  const clamp = (value, min, max) => Math.max(min, Math.min(max, Math.round(value)));
527
717
  /** Style one fixed, non-interactive, under-everything wallpaper layer. */
528
718
  function styleLayer(element, zIndex, layer) {
@@ -569,6 +759,19 @@ window.__ModuleLoader__.load({
569
759
  return Number.isFinite(numericZIndex) && numericZIndex > MAX_SURFACE_OVERLAY_Z_INDEX;
570
760
  }
571
761
  /**
762
+ * Whether one media-layer child is the sandboxed scene-player frame.
763
+ *
764
+ * Read from the node (tagName) instead of `instanceof HTMLIFrameElement`: the
765
+ * ambient globals belong to the app's realm, and the wallpaper paths run
766
+ * against elements from documents whose realm those bindings cannot see, where
767
+ * the `instanceof` reference throws rather than answering false.
768
+ * @param node - the candidate element.
769
+ * @returns true when it is an iframe.
770
+ */
771
+ function isScenePlayer(node) {
772
+ return node !== null && node.tagName === "IFRAME";
773
+ }
774
+ /**
572
775
  * Default shell-surface detector for WE wallpaper neutralization (#712). A
573
776
  * target must cover most of the visible viewport and paint a nontransparent
574
777
  * background. It deliberately avoids equality against a theme token because
@@ -940,7 +1143,7 @@ window.__ModuleLoader__.load({
940
1143
  }
941
1144
  recoverScenePlayer() {
942
1145
  const scenePlayer = this.mediaLayer?.firstElementChild ?? null;
943
- if (!(scenePlayer instanceof HTMLIFrameElement) || scenePlayer.dataset.dshScenePlayer !== "") return;
1146
+ if (!isScenePlayer(scenePlayer) || scenePlayer.dataset.dshScenePlayer !== "") return;
944
1147
  try {
945
1148
  scenePlayer.contentWindow?.postMessage({ type: "dsh-recover-renderer" }, "*");
946
1149
  } catch {}
@@ -1005,7 +1208,7 @@ window.__ModuleLoader__.load({
1005
1208
  };
1006
1209
  onSceneMessage = (event) => {
1007
1210
  const scenePlayer = this.mediaLayer?.firstElementChild ?? null;
1008
- if (!(scenePlayer instanceof HTMLIFrameElement) || scenePlayer.dataset.dshScenePlayer !== "") return;
1211
+ if (!isScenePlayer(scenePlayer) || scenePlayer.dataset.dshScenePlayer !== "") return;
1009
1212
  if (event.source !== scenePlayer.contentWindow) return;
1010
1213
  if (event.data?.type !== "dsh-scene-needs-reload") return;
1011
1214
  scenePlayer.src = scenePlayer.src;
@@ -1017,7 +1220,7 @@ window.__ModuleLoader__.load({
1017
1220
  else this.videoElement.play()?.catch(() => {});
1018
1221
  }
1019
1222
  const scenePlayer = this.mediaLayer?.firstElementChild ?? null;
1020
- if (scenePlayer instanceof HTMLIFrameElement && scenePlayer.dataset.dshScenePlayer === "") try {
1223
+ if (isScenePlayer(scenePlayer) && scenePlayer.dataset.dshScenePlayer === "") try {
1021
1224
  scenePlayer.contentWindow?.postMessage({
1022
1225
  type: "dsh-set-pause",
1023
1226
  paused: this.doc.hidden
@@ -1107,7 +1310,8 @@ window.__ModuleLoader__.load({
1107
1310
  const child = this.buildMedia(descriptor);
1108
1311
  if (child !== null) {
1109
1312
  this.mediaLayer.appendChild(child);
1110
- if (child instanceof HTMLVideoElement && child.paused) child.play()?.catch(() => {});
1313
+ const VideoCtor = this.doc.defaultView?.HTMLVideoElement;
1314
+ if (VideoCtor !== void 0 && child instanceof VideoCtor && child.paused) child.play()?.catch(() => {});
1111
1315
  }
1112
1316
  } else {
1113
1317
  const child = this.mediaLayer.firstElementChild;
@@ -1124,8 +1328,8 @@ window.__ModuleLoader__.load({
1124
1328
  /** Push the current sizing mode onto the mounted media element. */
1125
1329
  applyFit() {
1126
1330
  const child = this.mediaLayer?.firstElementChild ?? null;
1127
- if (child instanceof HTMLElement) styleCover(child, this.fitValue);
1128
- if (child instanceof HTMLIFrameElement && child.dataset.dshScenePlayer === "") try {
1331
+ if (child !== null && child.nodeType === 1) styleCover(child, this.fitValue);
1332
+ if (child !== null && isScenePlayer(child) && child.dataset.dshScenePlayer === "") try {
1129
1333
  child.contentWindow?.postMessage({
1130
1334
  type: "dsh-set-fit",
1131
1335
  fit: this.fitValue
@@ -1293,7 +1497,7 @@ window.__ModuleLoader__.load({
1293
1497
  while (stack.length > 0) {
1294
1498
  const node = stack.pop();
1295
1499
  if (node === void 0) continue;
1296
- if (node instanceof HTMLElement && !node.hasAttribute("data-dsh-wallpaper-surface") && isSurface(node)) {
1500
+ if (node.nodeType === 1 && !node.hasAttribute("data-dsh-wallpaper-surface") && isSurface(node)) {
1297
1501
  node.setAttribute("data-dsh-wallpaper-surface", "");
1298
1502
  this.taggedSurfaces.add(node);
1299
1503
  }
@@ -1311,7 +1515,7 @@ window.__ModuleLoader__.load({
1311
1515
  while (stack.length > 0) {
1312
1516
  const node = stack.pop();
1313
1517
  if (node === void 0) continue;
1314
- if (node instanceof HTMLElement && !node.hasAttribute("data-dsh-wallpaper-surface") && isFade(node, this.doc)) {
1518
+ if (node.nodeType === 1 && !node.hasAttribute("data-dsh-wallpaper-surface") && isFade(node, this.doc)) {
1315
1519
  node.setAttribute("data-dsh-wallpaper-surface", "");
1316
1520
  this.taggedSurfaces.add(node);
1317
1521
  }
@@ -1335,12 +1539,24 @@ window.__ModuleLoader__.load({
1335
1539
  subtree: true
1336
1540
  });
1337
1541
  }
1338
- /** Incrementally tag added subtrees and untag removed subtrees. */
1542
+ /**
1543
+ * Incrementally tag added subtrees and untag removed subtrees.
1544
+ *
1545
+ * Elements are recognised by nodeType, never by `instanceof HTMLElement`.
1546
+ * Decision: element KIND is read from the node (nodeType / tagName), and a
1547
+ * constructor test, where one is genuinely needed, comes from the owning
1548
+ * document's window (`doc.defaultView.HTMLVideoElement`) — the same rule the
1549
+ * video resume path below already follows. The ambient globals are a single
1550
+ * realm's bindings: they do not describe elements from another document, and
1551
+ * code that reached for them here threw a bare ReferenceError out of an
1552
+ * observer callback. This is a deliberate repository-wide convention, not a
1553
+ * workaround for one sandbox.
1554
+ */
1339
1555
  handleSurfaceMutations(records) {
1340
1556
  if (this.disposed || (this.previewing ?? this.applied) === null) return;
1341
1557
  for (const record of records) {
1342
- for (const node of record.addedNodes) if (node instanceof HTMLElement) this.tagAddedSubtree(node);
1343
- for (const node of record.removedNodes) if (node instanceof HTMLElement) this.untagRemovedSubtree(node);
1558
+ for (const node of record.addedNodes) if (node.nodeType === 1) this.tagAddedSubtree(node);
1559
+ for (const node of record.removedNodes) if (node.nodeType === 1) this.untagRemovedSubtree(node);
1344
1560
  }
1345
1561
  }
1346
1562
  /** Tag newly added elements that qualify as full-viewport surfaces or workspace fades. */
@@ -1359,7 +1575,7 @@ window.__ModuleLoader__.load({
1359
1575
  this.taggedSurfaces.add(node);
1360
1576
  }
1361
1577
  }
1362
- for (const child of Array.from(node.children)) if (child instanceof HTMLElement) stack.push(child);
1578
+ for (const child of Array.from(node.children)) if (child.nodeType === 1) stack.push(child);
1363
1579
  }
1364
1580
  }
1365
1581
  /** Remove tags from a removed subtree and drop its references. */
@@ -1372,7 +1588,7 @@ window.__ModuleLoader__.load({
1372
1588
  node.removeAttribute("data-dsh-wallpaper-surface");
1373
1589
  this.taggedSurfaces.delete(node);
1374
1590
  }
1375
- for (const child of Array.from(node.children)) if (child instanceof HTMLElement) stack.push(child);
1591
+ for (const child of Array.from(node.children)) if (child.nodeType === 1) stack.push(child);
1376
1592
  }
1377
1593
  }
1378
1594
  untagSurfaces() {
@@ -1442,6 +1658,167 @@ window.__ModuleLoader__.load({
1442
1658
  wallpaper.subscribe(restore);
1443
1659
  }
1444
1660
  //#endregion
1661
+ //#region src/client/DirBrowserDialog.tsx
1662
+ /**
1663
+ * In-app folder browser: the picking surface for a Host whose directory
1664
+ * picker composes the browse backend. The adaptive chooser resolves that
1665
+ * backend whenever the web server binds beyond loopback (LAN / remote GUI
1666
+ * access) or the host is launched over SSH, because no OS chooser can reach
1667
+ * such a client; the picker then refuses `pick` and serves listing
1668
+ * primitives instead.
1669
+ *
1670
+ * The dialog drives those primitives one level at a time and owns no path
1671
+ * joining: every crumb and every row is an absolute host path the backend
1672
+ * reported, so a client on another machine still picks a real host folder.
1673
+ * Escape and a backdrop click close it; the chosen path goes back through
1674
+ * `onPick` and the caller persists it.
1675
+ *
1676
+ * @module @linxin666/dsh-client-ui-skin-center/DirBrowserDialog
1677
+ */
1678
+ /** Render the in-app folder browser over the picker's browse primitives. */
1679
+ function DirBrowserDialog({ t, listDir, onPick, onClose }) {
1680
+ const [listing, setListing] = (0, react.useState)(null);
1681
+ const [error, setError] = (0, react.useState)(null);
1682
+ const [busy, setBusy] = (0, react.useState)(true);
1683
+ const [showHidden, setShowHidden] = (0, react.useState)(false);
1684
+ const alive = (0, react.useRef)(true);
1685
+ const panel = (0, react.useRef)(null);
1686
+ (0, react.useEffect)(() => {
1687
+ alive.current = true;
1688
+ return () => {
1689
+ alive.current = false;
1690
+ };
1691
+ }, []);
1692
+ /** Load one level; an absent path loads the host account's home. */
1693
+ const open = (0, react.useCallback)((path) => {
1694
+ setBusy(true);
1695
+ setError(null);
1696
+ listDir(path).then((next) => {
1697
+ if (!alive.current) return;
1698
+ setBusy(false);
1699
+ setListing(next);
1700
+ }, (reason) => {
1701
+ if (!alive.current) return;
1702
+ setBusy(false);
1703
+ setError(reason instanceof Error ? reason.message : String(reason));
1704
+ });
1705
+ }, [listDir]);
1706
+ (0, react.useEffect)(() => {
1707
+ open();
1708
+ }, [open]);
1709
+ (0, react.useEffect)(() => {
1710
+ panel.current?.focus();
1711
+ const onKey = (event) => {
1712
+ if (event.key === "Escape") onClose();
1713
+ };
1714
+ document.addEventListener("keydown", onKey);
1715
+ return () => {
1716
+ document.removeEventListener("keydown", onKey);
1717
+ };
1718
+ }, [onClose]);
1719
+ const entries = (listing?.entries ?? []).filter((entry) => showHidden || !entry.hidden);
1720
+ const failed = error !== null;
1721
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1722
+ className: skin_center_module_css_default.dirBrowserOverlay,
1723
+ onMouseDown: (event) => {
1724
+ if (event.target === event.currentTarget) onClose();
1725
+ },
1726
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1727
+ className: skin_center_module_css_default.dirBrowser,
1728
+ role: "dialog",
1729
+ "aria-modal": "true",
1730
+ "aria-label": t("wallpaperDirBrowseTitle"),
1731
+ tabIndex: -1,
1732
+ ref: panel,
1733
+ children: [
1734
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1735
+ className: skin_center_module_css_default.dirBrowserTitle,
1736
+ children: t("wallpaperDirBrowseTitle")
1737
+ }),
1738
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1739
+ className: skin_center_module_css_default.dirBrowserCrumbs,
1740
+ children: (listing?.crumbs ?? []).map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1741
+ className: skin_center_module_css_default.dirBrowserCrumb,
1742
+ children: [index > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1743
+ className: skin_center_module_css_default.dirBrowserCrumbSep,
1744
+ children: "/"
1745
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1746
+ type: "button",
1747
+ className: skin_center_module_css_default.dirBrowserCrumbButton,
1748
+ title: crumb.path,
1749
+ onClick: () => {
1750
+ open(crumb.path);
1751
+ },
1752
+ children: crumb.name
1753
+ })]
1754
+ }, crumb.path))
1755
+ }),
1756
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1757
+ className: skin_center_module_css_default.dirBrowserList,
1758
+ children: [
1759
+ busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1760
+ className: skin_center_module_css_default.dirBrowserNote,
1761
+ children: t("loading")
1762
+ }),
1763
+ !busy && failed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1764
+ className: skin_center_module_css_default.dirBrowserError,
1765
+ children: [
1766
+ t("wallpaperDirBrowseFailed"),
1767
+ ": ",
1768
+ error
1769
+ ]
1770
+ }),
1771
+ !busy && !failed && entries.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1772
+ className: skin_center_module_css_default.dirBrowserNote,
1773
+ children: t("wallpaperDirBrowseEmpty")
1774
+ }),
1775
+ !busy && !failed && entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1776
+ type: "button",
1777
+ className: skin_center_module_css_default.dirBrowserEntry,
1778
+ title: entry.path,
1779
+ onClick: () => {
1780
+ open(entry.path);
1781
+ },
1782
+ children: entry.name
1783
+ }, entry.path)),
1784
+ !busy && !failed && listing?.truncated === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1785
+ className: skin_center_module_css_default.dirBrowserNote,
1786
+ children: t("wallpaperDirBrowseTruncated")
1787
+ })
1788
+ ]
1789
+ }),
1790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1791
+ className: skin_center_module_css_default.dirBrowserHidden,
1792
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1793
+ type: "checkbox",
1794
+ checked: showHidden,
1795
+ onChange: (event) => {
1796
+ setShowHidden(event.target.checked);
1797
+ }
1798
+ }), t("wallpaperDirBrowseHidden")]
1799
+ }),
1800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1801
+ className: skin_center_module_css_default.dirBrowserActions,
1802
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1803
+ type: "button",
1804
+ className: skin_center_module_css_default.button,
1805
+ onClick: onClose,
1806
+ children: t("cancel")
1807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1808
+ type: "button",
1809
+ className: skin_center_module_css_default.button,
1810
+ disabled: listing === null,
1811
+ onClick: () => {
1812
+ if (listing !== null) onPick(listing.path);
1813
+ },
1814
+ children: t("wallpaperDirBrowseChoose")
1815
+ })]
1816
+ })
1817
+ ]
1818
+ })
1819
+ });
1820
+ }
1821
+ //#endregion
1445
1822
  //#region src/client/SliderControl.tsx
1446
1823
  /**
1447
1824
  * A drag-smooth range slider that decouples the visible value from the
@@ -1627,7 +2004,6 @@ window.__ModuleLoader__.load({
1627
2004
  case "video": return "wallpaperTypeVideo";
1628
2005
  case "web": return "wallpaperTypeWeb";
1629
2006
  case "scene": return "wallpaperTypeScene";
1630
- case "image": return "wallpaperTypeImage";
1631
2007
  default: return "wallpaperTypeApp";
1632
2008
  }
1633
2009
  }
@@ -1674,12 +2050,12 @@ window.__ModuleLoader__.load({
1674
2050
  const [shownVolume, setShownVolume] = useLiveValue$1(volume);
1675
2051
  const [dirInput, setDirInput] = (0, react.useState)("");
1676
2052
  const [picking, setPicking] = (0, react.useState)(false);
2053
+ const [browsing, setBrowsing] = (0, react.useState)(false);
1677
2054
  const [page, setPage] = (0, react.useState)(1);
1678
2055
  const [ratingFilter, setRatingFilter] = (0, react.useState)("g");
1679
2056
  const [jumpInput, setJumpInput] = (0, react.useState)("");
1680
2057
  const [items, setItems] = (0, react.useState)(null);
1681
2058
  const [installDir, setInstallDir] = (0, react.useState)(null);
1682
- const [systemCount, setSystemCount] = (0, react.useState)(0);
1683
2059
  const [loadError, setLoadError] = (0, react.useState)(null);
1684
2060
  const [actionError, setActionError] = (0, react.useState)(null);
1685
2061
  const [workingId, setWorkingId] = (0, react.useState)(null);
@@ -1704,7 +2080,6 @@ window.__ModuleLoader__.load({
1704
2080
  setItems(payload.wallpapers);
1705
2081
  setPage(1);
1706
2082
  setInstallDir(typeof payload.installDir === "string" ? payload.installDir : null);
1707
- setSystemCount(typeof payload.systemCount === "number" ? payload.systemCount : 0);
1708
2083
  const selected = wallpaper.selection();
1709
2084
  wallpaper.sync(resolveSelection(payload.wallpapers, selected) ?? null);
1710
2085
  }).catch((error) => {
@@ -1729,18 +2104,33 @@ window.__ModuleLoader__.load({
1729
2104
  load();
1730
2105
  });
1731
2106
  };
1732
- /** Open the host's native folder picker and add the chosen directory. */
2107
+ /**
2108
+ * Add one picked folder. A native-capability host opens the OS chooser; a
2109
+ * browse-capability host (the adaptive chooser picks it whenever the web
2110
+ * server binds beyond loopback, e.g. LAN remote access) refuses the pick
2111
+ * and gets the in-app browser over the picker's own listing primitives.
2112
+ * Without either capability the manual input stays the fallback.
2113
+ */
1733
2114
  const browseDir = () => {
1734
2115
  const pick = wallpaper.pickDir;
1735
2116
  if (pick === void 0) return;
1736
2117
  setActionError(null);
1737
2118
  setPicking(true);
1738
- pick().then((path) => {
2119
+ pick().then((outcome) => {
1739
2120
  if (!mounted.current) return;
1740
2121
  setPicking(false);
1741
- if (path === null || path.trim() === "") return;
1742
- wallpaper.addDir(path);
1743
- load();
2122
+ if (outcome.kind === "picked") {
2123
+ if (outcome.path.trim() === "") return;
2124
+ wallpaper.addDir(outcome.path);
2125
+ load();
2126
+ return;
2127
+ }
2128
+ if (outcome.kind === "cancelled") return;
2129
+ if (wallpaper.listDir !== void 0) {
2130
+ setBrowsing(true);
2131
+ return;
2132
+ }
2133
+ setActionError(t("wallpaperDirBrowseFailed"));
1744
2134
  }).catch((error) => {
1745
2135
  if (!mounted.current) return;
1746
2136
  setPicking(false);
@@ -1778,572 +2168,583 @@ window.__ModuleLoader__.load({
1778
2168
  const activeSelection = selection;
1779
2169
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1780
2170
  className: skin_center_module_css_default.wallpaperSection,
1781
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1782
- className: skin_center_module_css_default.enableRow,
1783
- children: [
1784
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1785
- className: skin_center_module_css_default.enableLabel,
1786
- title: t("wallpaperEnable"),
1787
- children: t("wallpaperTitle")
1788
- }),
1789
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1790
- type: "button",
1791
- role: "switch",
1792
- "aria-checked": enabled,
1793
- "aria-label": t("wallpaperEnable"),
1794
- className: enabled ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
1795
- onClick: () => {
1796
- wallpaper.setEnabled(!enabled);
1797
- },
1798
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
1799
- }),
1800
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1801
- className: skin_center_module_css_default.enableHint,
1802
- children: t("wallpaperHint")
1803
- })
1804
- ]
1805
- }), enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2171
+ children: [
1806
2172
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1807
- className: skin_center_module_css_default.wallpaperStatus,
1808
- children: [loadError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1809
- className: skin_center_module_css_default.wallpaperStatusError,
1810
- children: [
1811
- t("wallpaperLoadError"),
1812
- ": ",
1813
- loadError
1814
- ]
1815
- }) : items === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("loading") }) : installDir !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1816
- t("wallpaperLibraryFound"),
1817
- " · ",
1818
- items.length
1819
- ] }) : systemCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1820
- t("wallpaperLibrarySystem"),
1821
- " · ",
1822
- items.length
1823
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
1824
- t("wallpaperLibraryManual"),
1825
- " · ",
1826
- items.length
1827
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1828
- type: "button",
1829
- className: skin_center_module_css_default.button,
1830
- onClick: load,
1831
- children: t("wallpaperRefresh")
1832
- })]
1833
- }),
1834
- activeSelection !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1835
- className: skin_center_module_css_default.wallpaperControls,
2173
+ className: skin_center_module_css_default.enableRow,
1836
2174
  children: [
1837
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1838
- className: skin_center_module_css_default.themeRow,
1839
- children: [
1840
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1841
- className: skin_center_module_css_default.themeLabel,
1842
- children: t("wallpaperMode")
1843
- }),
1844
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1845
- type: "button",
1846
- className: skin_center_module_css_default.themeButton + (mode === "live" ? " " + skin_center_module_css_default.themeButtonActive : ""),
1847
- onClick: () => {
1848
- wallpaper.setMode("live");
1849
- },
1850
- children: t("wallpaperModeLive")
1851
- }),
1852
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1853
- type: "button",
1854
- className: skin_center_module_css_default.themeButton + (mode === "frame" ? " " + skin_center_module_css_default.themeButtonActive : ""),
1855
- onClick: () => {
1856
- wallpaper.setMode("frame");
1857
- },
1858
- children: t("wallpaperModeFrame")
1859
- }),
1860
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1861
- type: "button",
1862
- className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonGhost,
1863
- onClick: () => {
1864
- wallpaper.clearSelection();
1865
- },
1866
- children: t("wallpaperClear")
1867
- })
1868
- ]
2175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2176
+ className: skin_center_module_css_default.enableLabel,
2177
+ title: t("wallpaperEnable"),
2178
+ children: t("wallpaperTitle")
1869
2179
  }),
1870
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1871
- className: skin_center_module_css_default.themeRow,
2180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2181
+ type: "button",
2182
+ role: "switch",
2183
+ "aria-checked": enabled,
2184
+ "aria-label": t("wallpaperEnable"),
2185
+ className: enabled ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
2186
+ onClick: () => {
2187
+ wallpaper.setEnabled(!enabled);
2188
+ },
2189
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
2190
+ }),
2191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2192
+ className: skin_center_module_css_default.enableHint,
2193
+ children: t("wallpaperHint")
2194
+ })
2195
+ ]
2196
+ }),
2197
+ enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2199
+ className: skin_center_module_css_default.wallpaperStatus,
2200
+ children: [loadError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2201
+ className: skin_center_module_css_default.wallpaperStatusError,
1872
2202
  children: [
1873
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1874
- className: skin_center_module_css_default.themeLabel,
1875
- children: t("wallpaperFit")
1876
- }),
1877
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1878
- type: "button",
1879
- className: skin_center_module_css_default.themeButton + (fit === "cover" ? " " + skin_center_module_css_default.themeButtonActive : ""),
1880
- onClick: () => {
1881
- wallpaper.setFit("cover");
1882
- },
1883
- children: t("wallpaperFitCover")
1884
- }),
1885
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2203
+ t("wallpaperLoadError"),
2204
+ ": ",
2205
+ loadError
2206
+ ]
2207
+ }) : items === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("loading") }) : installDir !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2208
+ t("wallpaperLibraryFound"),
2209
+ " · ",
2210
+ items.length
2211
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
2212
+ t("wallpaperLibraryManual"),
2213
+ " · ",
2214
+ items.length
2215
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2216
+ type: "button",
2217
+ className: skin_center_module_css_default.button,
2218
+ onClick: load,
2219
+ children: t("wallpaperRefresh")
2220
+ })]
2221
+ }),
2222
+ activeSelection !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2223
+ className: skin_center_module_css_default.wallpaperControls,
2224
+ children: [
2225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2226
+ className: skin_center_module_css_default.themeRow,
2227
+ children: [
2228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2229
+ className: skin_center_module_css_default.themeLabel,
2230
+ children: t("wallpaperMode")
2231
+ }),
2232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2233
+ type: "button",
2234
+ className: skin_center_module_css_default.themeButton + (mode === "live" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2235
+ onClick: () => {
2236
+ wallpaper.setMode("live");
2237
+ },
2238
+ children: t("wallpaperModeLive")
2239
+ }),
2240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2241
+ type: "button",
2242
+ className: skin_center_module_css_default.themeButton + (mode === "frame" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2243
+ onClick: () => {
2244
+ wallpaper.setMode("frame");
2245
+ },
2246
+ children: t("wallpaperModeFrame")
2247
+ }),
2248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2249
+ type: "button",
2250
+ className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonGhost,
2251
+ onClick: () => {
2252
+ wallpaper.clearSelection();
2253
+ },
2254
+ children: t("wallpaperClear")
2255
+ })
2256
+ ]
2257
+ }),
2258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2259
+ className: skin_center_module_css_default.themeRow,
2260
+ children: [
2261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2262
+ className: skin_center_module_css_default.themeLabel,
2263
+ children: t("wallpaperFit")
2264
+ }),
2265
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2266
+ type: "button",
2267
+ className: skin_center_module_css_default.themeButton + (fit === "cover" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2268
+ onClick: () => {
2269
+ wallpaper.setFit("cover");
2270
+ },
2271
+ children: t("wallpaperFitCover")
2272
+ }),
2273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2274
+ type: "button",
2275
+ className: skin_center_module_css_default.themeButton + (fit === "contain" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2276
+ onClick: () => {
2277
+ wallpaper.setFit("contain");
2278
+ },
2279
+ children: t("wallpaperFitContain")
2280
+ }),
2281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2282
+ type: "button",
2283
+ className: skin_center_module_css_default.themeButton + (fit === "fill" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2284
+ onClick: () => {
2285
+ wallpaper.setFit("fill");
2286
+ },
2287
+ children: t("wallpaperFitFill")
2288
+ })
2289
+ ]
2290
+ }),
2291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2292
+ className: skin_center_module_css_default.backgroundRow,
2293
+ children: [
2294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2295
+ className: skin_center_module_css_default.backgroundHead,
2296
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2297
+ className: skin_center_module_css_default.backgroundLabel,
2298
+ children: t("wallpaperDim")
2299
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2300
+ className: skin_center_module_css_default.backgroundValue,
2301
+ "aria-hidden": "true",
2302
+ children: [shownDim, "%"]
2303
+ })]
2304
+ }),
2305
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2306
+ className: skin_center_module_css_default.backgroundRange,
2307
+ min: 0,
2308
+ max: 90,
2309
+ step: 5,
2310
+ value: dim,
2311
+ ariaValuetext: shownDim + "%",
2312
+ ariaLabel: t("wallpaperDim"),
2313
+ onChanging: setShownDim,
2314
+ onChange: (value) => {
2315
+ wallpaper.setDim(value);
2316
+ }
2317
+ }),
2318
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2319
+ className: skin_center_module_css_default.backgroundHead,
2320
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2321
+ className: skin_center_module_css_default.backgroundLabel,
2322
+ children: t("wallpaperOpacity")
2323
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2324
+ className: skin_center_module_css_default.backgroundValue,
2325
+ "aria-hidden": "true",
2326
+ children: [shownOpacity, "%"]
2327
+ })]
2328
+ }),
2329
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2330
+ className: skin_center_module_css_default.backgroundRange,
2331
+ min: 0,
2332
+ max: 100,
2333
+ step: 5,
2334
+ value: opacity,
2335
+ ariaValuetext: shownOpacity + "%",
2336
+ ariaLabel: t("wallpaperOpacity"),
2337
+ onChanging: setShownOpacity,
2338
+ onChange: (value) => {
2339
+ wallpaper.setOpacity(value);
2340
+ }
2341
+ }),
2342
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2343
+ className: skin_center_module_css_default.backgroundHead,
2344
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2345
+ className: skin_center_module_css_default.backgroundLabel,
2346
+ children: t("wallpaperBlur")
2347
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2348
+ className: skin_center_module_css_default.backgroundValue,
2349
+ "aria-hidden": "true",
2350
+ children: [shownBlur, "px"]
2351
+ })]
2352
+ }),
2353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2354
+ className: skin_center_module_css_default.backgroundRange,
2355
+ min: 0,
2356
+ max: 60,
2357
+ step: 1,
2358
+ value: blur,
2359
+ ariaValuetext: shownBlur + "px",
2360
+ ariaLabel: t("wallpaperBlur"),
2361
+ onChanging: setShownBlur,
2362
+ onChange: (value) => {
2363
+ wallpaper.setBlur(value);
2364
+ }
2365
+ })
2366
+ ]
2367
+ }),
2368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2369
+ className: skin_center_module_css_default.enableRow,
2370
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2371
+ className: skin_center_module_css_default.enableLabel,
2372
+ children: t("wallpaperPauseHidden")
2373
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1886
2374
  type: "button",
1887
- className: skin_center_module_css_default.themeButton + (fit === "contain" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2375
+ role: "switch",
2376
+ "aria-checked": pauseOnHidden,
2377
+ "aria-label": t("wallpaperPauseHidden"),
2378
+ className: pauseOnHidden ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
1888
2379
  onClick: () => {
1889
- wallpaper.setFit("contain");
2380
+ wallpaper.setPauseOnHidden(!pauseOnHidden);
1890
2381
  },
1891
- children: t("wallpaperFitContain")
1892
- }),
1893
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2382
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
2383
+ })]
2384
+ }),
2385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2386
+ className: skin_center_module_css_default.enableRow,
2387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2388
+ className: skin_center_module_css_default.enableLabel,
2389
+ title: t("wallpaperSoundHint"),
2390
+ children: t("wallpaperSound")
2391
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1894
2392
  type: "button",
1895
- className: skin_center_module_css_default.themeButton + (fit === "fill" ? " " + skin_center_module_css_default.themeButtonActive : ""),
2393
+ role: "switch",
2394
+ "aria-checked": sound,
2395
+ "aria-label": t("wallpaperSound"),
2396
+ className: sound ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
1896
2397
  onClick: () => {
1897
- wallpaper.setFit("fill");
2398
+ wallpaper.setSound(!sound);
1898
2399
  },
1899
- children: t("wallpaperFitFill")
1900
- })
1901
- ]
1902
- }),
1903
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1904
- className: skin_center_module_css_default.backgroundRow,
1905
- children: [
1906
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1907
- className: skin_center_module_css_default.backgroundHead,
1908
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1909
- className: skin_center_module_css_default.backgroundLabel,
1910
- children: t("wallpaperDim")
1911
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1912
- className: skin_center_module_css_default.backgroundValue,
1913
- "aria-hidden": "true",
1914
- children: [shownDim, "%"]
1915
- })]
1916
- }),
1917
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
1918
- className: skin_center_module_css_default.backgroundRange,
1919
- min: 0,
1920
- max: 90,
1921
- step: 5,
1922
- value: dim,
1923
- ariaValuetext: shownDim + "%",
1924
- ariaLabel: t("wallpaperDim"),
1925
- onChanging: setShownDim,
1926
- onChange: (value) => {
1927
- wallpaper.setDim(value);
1928
- }
1929
- }),
1930
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1931
- className: skin_center_module_css_default.backgroundHead,
1932
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1933
- className: skin_center_module_css_default.backgroundLabel,
1934
- children: t("wallpaperOpacity")
1935
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1936
- className: skin_center_module_css_default.backgroundValue,
1937
- "aria-hidden": "true",
1938
- children: [shownOpacity, "%"]
1939
- })]
1940
- }),
1941
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
1942
- className: skin_center_module_css_default.backgroundRange,
1943
- min: 0,
1944
- max: 100,
1945
- step: 5,
1946
- value: opacity,
1947
- ariaValuetext: shownOpacity + "%",
1948
- ariaLabel: t("wallpaperOpacity"),
1949
- onChanging: setShownOpacity,
1950
- onChange: (value) => {
1951
- wallpaper.setOpacity(value);
1952
- }
1953
- }),
1954
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1955
- className: skin_center_module_css_default.backgroundHead,
1956
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1957
- className: skin_center_module_css_default.backgroundLabel,
1958
- children: t("wallpaperBlur")
1959
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1960
- className: skin_center_module_css_default.backgroundValue,
1961
- "aria-hidden": "true",
1962
- children: [shownBlur, "px"]
1963
- })]
1964
- }),
1965
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
1966
- className: skin_center_module_css_default.backgroundRange,
1967
- min: 0,
1968
- max: 60,
1969
- step: 1,
1970
- value: blur,
1971
- ariaValuetext: shownBlur + "px",
1972
- ariaLabel: t("wallpaperBlur"),
1973
- onChanging: setShownBlur,
1974
- onChange: (value) => {
1975
- wallpaper.setBlur(value);
1976
- }
1977
- })
1978
- ]
1979
- }),
1980
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1981
- className: skin_center_module_css_default.enableRow,
1982
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1983
- className: skin_center_module_css_default.enableLabel,
1984
- children: t("wallpaperPauseHidden")
1985
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1986
- type: "button",
1987
- role: "switch",
1988
- "aria-checked": pauseOnHidden,
1989
- "aria-label": t("wallpaperPauseHidden"),
1990
- className: pauseOnHidden ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
1991
- onClick: () => {
1992
- wallpaper.setPauseOnHidden(!pauseOnHidden);
1993
- },
1994
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
1995
- })]
1996
- }),
1997
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1998
- className: skin_center_module_css_default.enableRow,
1999
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2000
- className: skin_center_module_css_default.enableLabel,
2001
- title: t("wallpaperSoundHint"),
2002
- children: t("wallpaperSound")
2003
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2004
- type: "button",
2005
- role: "switch",
2006
- "aria-checked": sound,
2007
- "aria-label": t("wallpaperSound"),
2008
- className: sound ? skin_center_module_css_default.switch + " " + skin_center_module_css_default.switchOn : skin_center_module_css_default.switch,
2009
- onClick: () => {
2010
- wallpaper.setSound(!sound);
2011
- },
2012
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
2013
- })]
2014
- }),
2015
- sound && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2016
- className: skin_center_module_css_default.backgroundRow,
2017
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2018
- className: skin_center_module_css_default.backgroundHead,
2019
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2020
- className: skin_center_module_css_default.backgroundLabel,
2021
- children: t("wallpaperVolume")
2022
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2023
- className: skin_center_module_css_default.backgroundValue,
2024
- "aria-hidden": "true",
2025
- children: [shownVolume, "%"]
2400
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: skin_center_module_css_default.switchThumb })
2026
2401
  })]
2027
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2028
- className: skin_center_module_css_default.backgroundRange,
2029
- min: 0,
2030
- max: 100,
2031
- step: 5,
2032
- value: volume,
2033
- ariaValuetext: shownVolume + "%",
2034
- ariaLabel: t("wallpaperVolume"),
2035
- onChanging: setShownVolume,
2036
- onChange: (value) => {
2037
- wallpaper.setVolume(value);
2038
- }
2039
- })]
2040
- })
2041
- ]
2042
- }),
2043
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2044
- className: skin_center_module_css_default.wallpaperDirs,
2045
- children: [
2046
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2047
- className: skin_center_module_css_default.themeLabel,
2048
- children: t("wallpaperDirs")
2049
- }),
2050
- dirs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2051
- className: skin_center_module_css_default.backgroundHintMuted,
2052
- children: t("wallpaperDirsEmpty")
2053
- }),
2054
- dirs.map((dir) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2055
- className: skin_center_module_css_default.wallpaperDir,
2056
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2057
- className: skin_center_module_css_default.wallpaperDirPath,
2058
- title: dir,
2059
- children: dir
2060
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2061
- type: "button",
2062
- className: skin_center_module_css_default.wallpaperDirRemove,
2063
- "aria-label": t("wallpaperRemove"),
2064
- onClick: () => {
2065
- wallpaper.removeDir(dir);
2066
- load();
2067
- },
2068
- children: "×"
2069
- })]
2070
- }, dir)),
2071
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2072
- className: skin_center_module_css_default.wallpaperDirAdd,
2073
- children: [
2074
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2075
- className: skin_center_module_css_default.wallpaperDirInput,
2076
- type: "text",
2077
- value: dirInput,
2078
- placeholder: t("wallpaperDirPlaceholder"),
2079
- onChange: (event) => {
2080
- setDirInput(event.target.value);
2402
+ }),
2403
+ sound && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2404
+ className: skin_center_module_css_default.backgroundRow,
2405
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2406
+ className: skin_center_module_css_default.backgroundHead,
2407
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2408
+ className: skin_center_module_css_default.backgroundLabel,
2409
+ children: t("wallpaperVolume")
2410
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2411
+ className: skin_center_module_css_default.backgroundValue,
2412
+ "aria-hidden": "true",
2413
+ children: [shownVolume, "%"]
2414
+ })]
2415
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2416
+ className: skin_center_module_css_default.backgroundRange,
2417
+ min: 0,
2418
+ max: 100,
2419
+ step: 5,
2420
+ value: volume,
2421
+ ariaValuetext: shownVolume + "%",
2422
+ ariaLabel: t("wallpaperVolume"),
2423
+ onChanging: setShownVolume,
2424
+ onChange: (value) => {
2425
+ wallpaper.setVolume(value);
2426
+ }
2427
+ })]
2428
+ })
2429
+ ]
2430
+ }),
2431
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2432
+ className: skin_center_module_css_default.wallpaperDirs,
2433
+ children: [
2434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2435
+ className: skin_center_module_css_default.themeLabel,
2436
+ children: t("wallpaperDirs")
2437
+ }),
2438
+ dirs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2439
+ className: skin_center_module_css_default.backgroundHintMuted,
2440
+ children: t("wallpaperDirsEmpty")
2441
+ }),
2442
+ dirs.map((dir) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2443
+ className: skin_center_module_css_default.wallpaperDir,
2444
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2445
+ className: skin_center_module_css_default.wallpaperDirPath,
2446
+ title: dir,
2447
+ children: dir
2448
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2449
+ type: "button",
2450
+ className: skin_center_module_css_default.wallpaperDirRemove,
2451
+ "aria-label": t("wallpaperRemove"),
2452
+ onClick: () => {
2453
+ wallpaper.removeDir(dir);
2454
+ load();
2081
2455
  },
2082
- onKeyDown: (event) => {
2083
- if (event.key === "Enter" && dirInput.trim() !== "") {
2456
+ children: "×"
2457
+ })]
2458
+ }, dir)),
2459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2460
+ className: skin_center_module_css_default.wallpaperDirAdd,
2461
+ children: [
2462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2463
+ className: skin_center_module_css_default.wallpaperDirInput,
2464
+ type: "text",
2465
+ value: dirInput,
2466
+ placeholder: t("wallpaperDirPlaceholder"),
2467
+ onChange: (event) => {
2468
+ setDirInput(event.target.value);
2469
+ },
2470
+ onKeyDown: (event) => {
2471
+ if (event.key === "Enter" && dirInput.trim() !== "") {
2472
+ wallpaper.addDir(dirInput);
2473
+ setDirInput("");
2474
+ load();
2475
+ }
2476
+ }
2477
+ }),
2478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2479
+ type: "button",
2480
+ className: skin_center_module_css_default.button,
2481
+ disabled: dirInput.trim() === "",
2482
+ onClick: () => {
2084
2483
  wallpaper.addDir(dirInput);
2085
2484
  setDirInput("");
2086
2485
  load();
2087
- }
2088
- }
2089
- }),
2090
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2486
+ },
2487
+ children: t("wallpaperDirAdd")
2488
+ }),
2489
+ wallpaper.pickDir !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2490
+ type: "button",
2491
+ className: skin_center_module_css_default.button,
2492
+ disabled: picking,
2493
+ title: t("wallpaperDirBrowseHint"),
2494
+ onClick: browseDir,
2495
+ children: picking ? t("loading") : t("wallpaperDirBrowse")
2496
+ })
2497
+ ]
2498
+ }),
2499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2500
+ className: skin_center_module_css_default.backgroundHintMuted,
2501
+ children: t("wallpaperDirsHint")
2502
+ })
2503
+ ]
2504
+ }),
2505
+ actionError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2506
+ className: skin_center_module_css_default.error,
2507
+ children: actionError
2508
+ }),
2509
+ writeError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2510
+ className: skin_center_module_css_default.error,
2511
+ role: "alert",
2512
+ children: [
2513
+ t("wallpaperSaveFailed"),
2514
+ ": ",
2515
+ writeError
2516
+ ]
2517
+ }),
2518
+ items !== null && items.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2519
+ className: skin_center_module_css_default.wallpaperToolbar,
2520
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2521
+ className: skin_center_module_css_default.ratingFilterGroup,
2522
+ role: "tablist",
2523
+ "aria-label": t("wallpaperTitle"),
2524
+ children: [
2525
+ "g",
2526
+ "pg13",
2527
+ "r18"
2528
+ ].map((filter) => {
2529
+ const active = ratingFilter === filter;
2530
+ const key = filter === "g" ? "wallpaperRatingG" : filter === "pg13" ? "wallpaperRatingPg13" : "wallpaperRatingR18";
2531
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2091
2532
  type: "button",
2092
- className: skin_center_module_css_default.button,
2093
- disabled: dirInput.trim() === "",
2533
+ role: "tab",
2534
+ "aria-selected": active,
2535
+ className: skin_center_module_css_default.ratingFilterButton + (active ? " " + skin_center_module_css_default.ratingFilterActive : ""),
2094
2536
  onClick: () => {
2095
- wallpaper.addDir(dirInput);
2096
- setDirInput("");
2097
- load();
2537
+ onSelectRatingFilter(filter);
2098
2538
  },
2099
- children: t("wallpaperDirAdd")
2100
- }),
2101
- wallpaper.pickDir !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2102
- type: "button",
2103
- className: skin_center_module_css_default.button,
2104
- disabled: picking,
2105
- title: t("wallpaperDirBrowseHint"),
2106
- onClick: browseDir,
2107
- children: picking ? t("loading") : t("wallpaperDirBrowse")
2108
- })
2109
- ]
2110
- }),
2111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
- className: skin_center_module_css_default.backgroundHintMuted,
2113
- children: t("wallpaperDirsHint")
2539
+ children: t(key)
2540
+ }, filter);
2541
+ })
2542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2543
+ className: skin_center_module_css_default.pageTotalInfo,
2544
+ children: t("wallpaperPageTotal", {
2545
+ page: String(currentPage),
2546
+ total: String(totalPages)
2547
+ })
2548
+ })]
2549
+ }),
2550
+ items !== null && pagedItems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2551
+ className: skin_center_module_css_default.wallpaperGrid,
2552
+ children: pagedItems.map((item) => {
2553
+ const isApplied = item.id === activeSelection;
2554
+ const isMounted = item.id === activeId;
2555
+ const busy = workingId === item.id;
2556
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2557
+ className: skin_center_module_css_default.wallpaperCard,
2558
+ children: [
2559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2560
+ className: skin_center_module_css_default.wallpaperThumbWrap,
2561
+ children: [
2562
+ item.previewUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
2563
+ className: skin_center_module_css_default.wallpaperThumb,
2564
+ src: item.previewUrl,
2565
+ alt: "",
2566
+ loading: "lazy"
2567
+ }) : item.videoUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
2568
+ className: skin_center_module_css_default.wallpaperThumb,
2569
+ src: item.videoUrl,
2570
+ preload: "metadata",
2571
+ muted: true,
2572
+ playsInline: true,
2573
+ "aria-hidden": "true"
2574
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2575
+ className: skin_center_module_css_default.wallpaperThumbEmpty,
2576
+ "aria-hidden": "true"
2577
+ }),
2578
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2579
+ className: skin_center_module_css_default.wallpaperType,
2580
+ children: t(typeKey(item))
2581
+ }),
2582
+ item.rating === "r18" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2583
+ className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingR18,
2584
+ children: "R18"
2585
+ }) : item.rating === "pg13" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2586
+ className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingPg13,
2587
+ children: "PG-13"
2588
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2589
+ className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingG,
2590
+ children: "G"
2591
+ }),
2592
+ isMounted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2593
+ className: skin_center_module_css_default.badge + " " + (trying ? skin_center_module_css_default.badgeTrying : skin_center_module_css_default.badgeActive),
2594
+ children: trying ? t("tryingOn") : t("active")
2595
+ })
2596
+ ]
2597
+ }),
2598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2599
+ className: skin_center_module_css_default.wallpaperName,
2600
+ title: item.title,
2601
+ children: item.title
2602
+ }),
2603
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2604
+ className: skin_center_module_css_default.wallpaperActions,
2605
+ children: [
2606
+ isMounted && trying ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2607
+ type: "button",
2608
+ className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonPrimary,
2609
+ onClick: () => {
2610
+ wallpaper.exitTryOn();
2611
+ },
2612
+ children: t("exitTryOn")
2613
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2614
+ type: "button",
2615
+ className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonPrimary,
2616
+ disabled: !renderable(item) || isMounted && isApplied || busy,
2617
+ onClick: () => {
2618
+ wallpaper.tryOn(descriptorOf(item));
2619
+ },
2620
+ children: t("tryOn")
2621
+ }),
2622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2623
+ type: "button",
2624
+ className: skin_center_module_css_default.button,
2625
+ disabled: !renderable(item) || isApplied || busy,
2626
+ onClick: () => {
2627
+ wallpaper.applySelection(descriptorOf(item));
2628
+ },
2629
+ children: isApplied ? t("active") : t("apply")
2630
+ }),
2631
+ item.source === "imported" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.updateAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2632
+ type: "button",
2633
+ className: skin_center_module_css_default.button,
2634
+ disabled: busy,
2635
+ title: t("wallpaperUpdateAvailable"),
2636
+ onClick: () => {
2637
+ runAction(item.id, "/reimport");
2638
+ },
2639
+ children: busy ? t("loading") : t("wallpaperReimport")
2640
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2641
+ type: "button",
2642
+ className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonGhost,
2643
+ disabled: busy,
2644
+ onClick: () => {
2645
+ runAction(item.id, "/remove", () => {
2646
+ if (wallpaper.selection() === item.id) wallpaper.clearSelection();
2647
+ });
2648
+ },
2649
+ children: t("wallpaperRemove")
2650
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2651
+ type: "button",
2652
+ className: skin_center_module_css_default.button,
2653
+ disabled: busy,
2654
+ title: t("wallpaperImportHint"),
2655
+ onClick: () => {
2656
+ runAction(item.id, "/import");
2657
+ },
2658
+ children: busy ? t("loading") : t("wallpaperImport")
2659
+ })
2660
+ ]
2661
+ })
2662
+ ]
2663
+ }, item.id);
2114
2664
  })
2115
- ]
2116
- }),
2117
- actionError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2118
- className: skin_center_module_css_default.error,
2119
- children: actionError
2120
- }),
2121
- writeError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2122
- className: skin_center_module_css_default.error,
2123
- role: "alert",
2124
- children: [
2125
- t("wallpaperSaveFailed"),
2126
- ": ",
2127
- writeError
2128
- ]
2129
- }),
2130
- items !== null && items.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2131
- className: skin_center_module_css_default.wallpaperToolbar,
2132
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2133
- className: skin_center_module_css_default.ratingFilterGroup,
2134
- role: "tablist",
2665
+ }),
2666
+ totalPages > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2667
+ className: skin_center_module_css_default.wallpaperPagination,
2668
+ role: "navigation",
2135
2669
  "aria-label": t("wallpaperTitle"),
2136
2670
  children: [
2137
- "g",
2138
- "pg13",
2139
- "r18"
2140
- ].map((filter) => {
2141
- const active = ratingFilter === filter;
2142
- const key = filter === "g" ? "wallpaperRatingG" : filter === "pg13" ? "wallpaperRatingPg13" : "wallpaperRatingR18";
2143
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2144
2672
  type: "button",
2145
- role: "tab",
2146
- "aria-selected": active,
2147
- className: skin_center_module_css_default.ratingFilterButton + (active ? " " + skin_center_module_css_default.ratingFilterActive : ""),
2673
+ className: skin_center_module_css_default.pageButton,
2674
+ disabled: currentPage <= 1,
2148
2675
  onClick: () => {
2149
- onSelectRatingFilter(filter);
2676
+ setPage((p) => Math.max(1, p - 1));
2150
2677
  },
2151
- children: t(key)
2152
- }, filter);
2153
- })
2154
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2155
- className: skin_center_module_css_default.pageTotalInfo,
2156
- children: t("wallpaperPageTotal", {
2157
- page: String(currentPage),
2158
- total: String(totalPages)
2159
- })
2160
- })]
2161
- }),
2162
- items !== null && pagedItems.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2163
- className: skin_center_module_css_default.wallpaperGrid,
2164
- children: pagedItems.map((item) => {
2165
- const isApplied = item.id === activeSelection;
2166
- const isMounted = item.id === activeId;
2167
- const busy = workingId === item.id;
2168
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2169
- className: skin_center_module_css_default.wallpaperCard,
2170
- children: [
2171
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2172
- className: skin_center_module_css_default.wallpaperThumbWrap,
2173
- children: [
2174
- item.previewUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
2175
- className: skin_center_module_css_default.wallpaperThumb,
2176
- src: item.previewUrl,
2177
- alt: "",
2178
- loading: "lazy"
2179
- }) : item.videoUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
2180
- className: skin_center_module_css_default.wallpaperThumb,
2181
- src: item.videoUrl,
2182
- preload: "metadata",
2183
- muted: true,
2184
- playsInline: true,
2185
- "aria-hidden": "true"
2186
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2187
- className: skin_center_module_css_default.wallpaperThumbEmpty,
2188
- "aria-hidden": "true"
2189
- }),
2190
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2191
- className: skin_center_module_css_default.wallpaperType,
2192
- children: t(typeKey(item))
2193
- }),
2194
- item.rating === "r18" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2195
- className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingR18,
2196
- children: "R18"
2197
- }) : item.rating === "pg13" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2198
- className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingPg13,
2199
- children: "PG-13"
2200
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2201
- className: skin_center_module_css_default.wallpaperRating + " " + skin_center_module_css_default.ratingG,
2202
- children: "G"
2203
- }),
2204
- isMounted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2205
- className: skin_center_module_css_default.badge + " " + (trying ? skin_center_module_css_default.badgeTrying : skin_center_module_css_default.badgeActive),
2206
- children: trying ? t("tryingOn") : t("active")
2207
- })
2208
- ]
2209
- }),
2210
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2211
- className: skin_center_module_css_default.wallpaperName,
2212
- title: item.title,
2213
- children: item.title
2214
- }),
2215
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2216
- className: skin_center_module_css_default.wallpaperActions,
2217
- children: [
2218
- isMounted && trying ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2219
- type: "button",
2220
- className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonPrimary,
2221
- onClick: () => {
2222
- wallpaper.exitTryOn();
2223
- },
2224
- children: t("exitTryOn")
2225
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2226
- type: "button",
2227
- className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonPrimary,
2228
- disabled: !renderable(item) || isMounted && isApplied || busy,
2229
- onClick: () => {
2230
- wallpaper.tryOn(descriptorOf(item));
2231
- },
2232
- children: t("tryOn")
2233
- }),
2234
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2235
- type: "button",
2236
- className: skin_center_module_css_default.button,
2237
- disabled: !renderable(item) || isApplied || busy,
2238
- onClick: () => {
2239
- wallpaper.applySelection(descriptorOf(item));
2240
- },
2241
- children: isApplied ? t("active") : t("apply")
2242
- }),
2243
- item.source === "imported" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [item.updateAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2244
- type: "button",
2245
- className: skin_center_module_css_default.button,
2246
- disabled: busy,
2247
- title: t("wallpaperUpdateAvailable"),
2248
- onClick: () => {
2249
- runAction(item.id, "/reimport");
2250
- },
2251
- children: busy ? t("loading") : t("wallpaperReimport")
2252
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2253
- type: "button",
2254
- className: skin_center_module_css_default.button + " " + skin_center_module_css_default.buttonGhost,
2255
- disabled: busy,
2256
- onClick: () => {
2257
- runAction(item.id, "/remove", () => {
2258
- if (wallpaper.selection() === item.id) wallpaper.clearSelection();
2259
- });
2260
- },
2261
- children: t("wallpaperRemove")
2262
- })] }) : item.source === "system" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2263
- type: "button",
2264
- className: skin_center_module_css_default.button,
2265
- disabled: busy,
2266
- title: t("wallpaperImportHint"),
2267
- onClick: () => {
2268
- runAction(item.id, "/import");
2269
- },
2270
- children: busy ? t("loading") : t("wallpaperImport")
2271
- })
2272
- ]
2273
- })
2274
- ]
2275
- }, item.id);
2276
- })
2277
- }),
2278
- totalPages > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2279
- className: skin_center_module_css_default.wallpaperPagination,
2280
- role: "navigation",
2281
- "aria-label": t("wallpaperTitle"),
2282
- children: [
2283
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2284
- type: "button",
2285
- className: skin_center_module_css_default.pageButton,
2286
- disabled: currentPage <= 1,
2287
- onClick: () => {
2288
- setPage((p) => Math.max(1, p - 1));
2289
- },
2290
- "aria-label": t("wallpaperPagePrev"),
2291
- children: t("wallpaperPagePrev")
2292
- }),
2293
- paginationRange(currentPage, totalPages).map((p, idx) => {
2294
- if (p === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2295
- className: skin_center_module_css_default.pageEllipsis,
2296
- children: "…"
2297
- }, "ellipsis-" + String(idx));
2298
- const isActive = p === currentPage;
2299
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2678
+ "aria-label": t("wallpaperPagePrev"),
2679
+ children: t("wallpaperPagePrev")
2680
+ }),
2681
+ paginationRange(currentPage, totalPages).map((p, idx) => {
2682
+ if (p === "ellipsis") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2683
+ className: skin_center_module_css_default.pageEllipsis,
2684
+ children: "…"
2685
+ }, "ellipsis-" + String(idx));
2686
+ const isActive = p === currentPage;
2687
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2688
+ type: "button",
2689
+ className: skin_center_module_css_default.pageButton + (isActive ? " " + skin_center_module_css_default.pageButtonActive : ""),
2690
+ "aria-current": isActive ? "page" : void 0,
2691
+ onClick: () => {
2692
+ setPage(p);
2693
+ },
2694
+ children: p
2695
+ }, p);
2696
+ }),
2697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2300
2698
  type: "button",
2301
- className: skin_center_module_css_default.pageButton + (isActive ? " " + skin_center_module_css_default.pageButtonActive : ""),
2302
- "aria-current": isActive ? "page" : void 0,
2699
+ className: skin_center_module_css_default.pageButton,
2700
+ disabled: currentPage >= totalPages,
2303
2701
  onClick: () => {
2304
- setPage(p);
2702
+ setPage((p) => Math.min(totalPages, p + 1));
2305
2703
  },
2306
- children: p
2307
- }, p);
2308
- }),
2309
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2310
- type: "button",
2311
- className: skin_center_module_css_default.pageButton,
2312
- disabled: currentPage >= totalPages,
2313
- onClick: () => {
2314
- setPage((p) => Math.min(totalPages, p + 1));
2315
- },
2316
- "aria-label": t("wallpaperPageNext"),
2317
- children: t("wallpaperPageNext")
2318
- }),
2319
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2320
- className: skin_center_module_css_default.pageJumpForm,
2321
- onSubmit: (e) => {
2322
- e.preventDefault();
2323
- handleJump();
2324
- },
2325
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2326
- type: "text",
2327
- className: skin_center_module_css_default.pageJumpInput,
2328
- value: jumpInput,
2329
- "aria-label": t("wallpaperPageJump"),
2330
- onChange: (e) => {
2331
- setJumpInput(e.target.value);
2332
- }
2333
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2334
- type: "submit",
2335
- className: skin_center_module_css_default.pageButton,
2336
- disabled: jumpInput.trim() === "",
2337
- children: t("wallpaperPageJump")
2338
- })]
2339
- })
2340
- ]
2341
- }),
2342
- items !== null && (items.length === 0 || pagedItems.length === 0) && loadError === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2343
- className: skin_center_module_css_default.backgroundHintMuted,
2344
- children: t("wallpaperEmpty")
2704
+ "aria-label": t("wallpaperPageNext"),
2705
+ children: t("wallpaperPageNext")
2706
+ }),
2707
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
2708
+ className: skin_center_module_css_default.pageJumpForm,
2709
+ onSubmit: (e) => {
2710
+ e.preventDefault();
2711
+ handleJump();
2712
+ },
2713
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2714
+ type: "text",
2715
+ className: skin_center_module_css_default.pageJumpInput,
2716
+ value: jumpInput,
2717
+ "aria-label": t("wallpaperPageJump"),
2718
+ onChange: (e) => {
2719
+ setJumpInput(e.target.value);
2720
+ }
2721
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2722
+ type: "submit",
2723
+ className: skin_center_module_css_default.pageButton,
2724
+ disabled: jumpInput.trim() === "",
2725
+ children: t("wallpaperPageJump")
2726
+ })]
2727
+ })
2728
+ ]
2729
+ }),
2730
+ items !== null && (items.length === 0 || pagedItems.length === 0) && loadError === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2731
+ className: skin_center_module_css_default.backgroundHintMuted,
2732
+ children: t("wallpaperEmpty")
2733
+ })
2734
+ ] }),
2735
+ browsing && wallpaper.listDir !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DirBrowserDialog, {
2736
+ t,
2737
+ listDir: wallpaper.listDir,
2738
+ onPick: (path) => {
2739
+ setBrowsing(false);
2740
+ wallpaper.addDir(path);
2741
+ load();
2742
+ },
2743
+ onClose: () => {
2744
+ setBrowsing(false);
2745
+ }
2345
2746
  })
2346
- ] })]
2747
+ ]
2347
2748
  });
2348
2749
  }
2349
2750
  //#endregion
@@ -3609,11 +4010,10 @@ window.__ModuleLoader__.load({
3609
4010
  backgroundHintInert: "Only applies to skins that paint a backdrop (Blue Fantasy / Whale Song). Applies to the official default automatically once such a skin is active.",
3610
4011
  wallpaperTitle: "Wallpaper Engine",
3611
4012
  wallpaperEnable: "Enable wallpapers",
3612
- wallpaperHint: "Use your local Wallpaper Engine library as the GUI backdrop: video, web, and scene wallpapers render live (scene wallpapers need WebGL).",
4013
+ wallpaperHint: "Use your local Wallpaper Engine library as the GUI backdrop: video, web, and scene wallpapers render live (scene wallpapers need WebGL). On macOS there is no Wallpaper Engine library, so the feature starts off — turn it on, then add a folder of videos.",
3613
4014
  wallpaperLoadError: "Wallpaper library failed to load",
3614
4015
  wallpaperLibraryFound: "Wallpaper Engine library detected",
3615
- wallpaperLibraryManual: "Manual folders only (no Wallpaper Engine install found; set folders in the skin-wallpaper settings)",
3616
- wallpaperLibrarySystem: "macOS wallpapers detected (aerials and Desktop Pictures)",
4016
+ wallpaperLibraryManual: "Folders you added",
3617
4017
  wallpaperRefresh: "Refresh",
3618
4018
  wallpaperMode: "Render mode",
3619
4019
  wallpaperModeLive: "Live",
@@ -3635,21 +4035,25 @@ window.__ModuleLoader__.load({
3635
4035
  wallpaperReimport: "Update",
3636
4036
  wallpaperRemove: "Remove",
3637
4037
  wallpaperUpdateAvailable: "The workshop original changed since import — update the local copy",
3638
- wallpaperEmpty: "No wallpapers found. Subscribe in the Wallpaper Engine workshop, or add manual folders to the skin-wallpaper settings.",
4038
+ wallpaperEmpty: "No wallpapers found. Add a folder of videos below, or subscribe in the Wallpaper Engine workshop on Windows.",
3639
4039
  wallpaperTypeVideo: "Video",
3640
4040
  wallpaperTypeWeb: "Web",
3641
4041
  wallpaperTypeScene: "Scene (static)",
3642
4042
  wallpaperTypeApp: "Unsupported",
3643
- wallpaperTypeImage: "Image",
3644
4043
  wallpaperLoadMore: "Load more",
3645
4044
  wallpaperDirs: "Manual folders",
3646
- wallpaperDirsEmpty: "No manual folders yet.",
3647
- wallpaperDirsHint: "No Wallpaper Engine (e.g. macOS)? Point a folder at any .mp4/.webm files, a wallpaper project folder, or a folder of projects — they become your wallpaper library.",
4045
+ wallpaperDirsEmpty: "No folders yet — add one to load its videos.",
4046
+ wallpaperDirsHint: "On macOS: pick a folder of .mp4/.webm videos (or a wallpaper project folder). On Windows, folders extend the auto-detected Wallpaper Engine library.",
3648
4047
  wallpaperDirPlaceholder: "/path/to/wallpapers or ~/Movies/wallpapers",
3649
4048
  wallpaperDirAdd: "Add",
3650
4049
  wallpaperDirBrowse: "Browse…",
3651
4050
  wallpaperDirBrowseHint: "Pick a folder with the system file manager (Finder / Explorer)",
3652
4051
  wallpaperDirBrowseFailed: "Could not open the system folder picker — type the path manually instead",
4052
+ wallpaperDirBrowseTitle: "Select a folder",
4053
+ wallpaperDirBrowseChoose: "Use this folder",
4054
+ wallpaperDirBrowseEmpty: "No subfolders",
4055
+ wallpaperDirBrowseHidden: "Show hidden folders",
4056
+ wallpaperDirBrowseTruncated: "Too many folders to list; only the beginning is shown.",
3653
4057
  wallpaperSaveFailed: "Could not save wallpaper settings.",
3654
4058
  wallpaperRatingAll: "All",
3655
4059
  wallpaperRatingG: "G",
@@ -3732,11 +4136,10 @@ window.__ModuleLoader__.load({
3732
4136
  backgroundHintInert: "仅对带背景图插画的皮肤(蓝色幻想 / 鲸吟)生效;官方默认无背景图,该滑块对这些皮肤自动生效。",
3733
4137
  wallpaperTitle: "Wallpaper Engine",
3734
4138
  wallpaperEnable: "启用动态壁纸",
3735
- wallpaperHint: "把本机 Wallpaper Engine 壁纸库用作 GUI 背景:视频、网页与场景壁纸均动态渲染(场景壁纸需要 WebGL)。",
4139
+ wallpaperHint: "把本机 Wallpaper Engine 壁纸库用作 GUI 背景:视频、网页与场景壁纸均动态渲染(场景壁纸需要 WebGL)。macOS 没有 Wallpaper Engine 壁纸库,该功能默认关闭;开启后添加一个存放视频的目录即可。",
3736
4140
  wallpaperLoadError: "壁纸库加载失败",
3737
4141
  wallpaperLibraryFound: "已检测到 Wallpaper Engine 壁纸库",
3738
- wallpaperLibraryManual: "仅手动目录(未检测到 Wallpaper Engine 安装,可在 skin-wallpaper 设置里添加目录)",
3739
- wallpaperLibrarySystem: "已检测到 macOS 系统壁纸(航拍与桌面图片)",
4142
+ wallpaperLibraryManual: "来自你添加的目录",
3740
4143
  wallpaperRefresh: "刷新",
3741
4144
  wallpaperMode: "渲染模式",
3742
4145
  wallpaperModeLive: "动态",
@@ -3758,21 +4161,25 @@ window.__ModuleLoader__.load({
3758
4161
  wallpaperReimport: "更新",
3759
4162
  wallpaperRemove: "移除",
3760
4163
  wallpaperUpdateAvailable: "工坊原件在导入后有更新——同步更新本地副本",
3761
- wallpaperEmpty: "未发现壁纸。可先在 Wallpaper Engine 创意工坊订阅,或在 skin-wallpaper 设置里添加手动目录。",
4164
+ wallpaperEmpty: "未发现壁纸。请在下方添加一个存放视频的目录;Windows 上也可先在 Wallpaper Engine 创意工坊订阅。",
3762
4165
  wallpaperTypeVideo: "视频",
3763
4166
  wallpaperTypeWeb: "网页",
3764
4167
  wallpaperTypeScene: "场景(静态)",
3765
4168
  wallpaperTypeApp: "不支持",
3766
- wallpaperTypeImage: "静态图片",
3767
4169
  wallpaperLoadMore: "加载更多",
3768
4170
  wallpaperDirs: "手动目录",
3769
- wallpaperDirsEmpty: "还没有手动目录。",
3770
- wallpaperDirsHint: "没有 Wallpaper Engine(如 macOS)?把任意 .mp4/.webm 视频、单个壁纸项目文件夹或项目合集文件夹加进来,就是你的壁纸库。",
4171
+ wallpaperDirsEmpty: "还没有目录——添加一个以加载其中的视频。",
4172
+ wallpaperDirsHint: "macOS:选择一个存放 .mp4/.webm 视频的目录(或单个壁纸项目文件夹)。Windows:这里添加的目录会扩展自动检测到的 Wallpaper Engine 壁纸库。",
3771
4173
  wallpaperDirPlaceholder: "/path/to/wallpapers 或 ~/Movies/wallpapers",
3772
4174
  wallpaperDirAdd: "添加",
3773
4175
  wallpaperDirBrowse: "浏览…",
3774
4176
  wallpaperDirBrowseHint: "通过系统文件管理器(访达 / 资源管理器)选择文件夹",
3775
4177
  wallpaperDirBrowseFailed: "无法打开系统目录选择框——请手动输入路径",
4178
+ wallpaperDirBrowseTitle: "选择文件夹",
4179
+ wallpaperDirBrowseChoose: "使用此目录",
4180
+ wallpaperDirBrowseEmpty: "没有子目录",
4181
+ wallpaperDirBrowseHidden: "显示隐藏目录",
4182
+ wallpaperDirBrowseTruncated: "目录过多,仅显示开头部分。",
3776
4183
  wallpaperSaveFailed: "壁纸设置保存失败。",
3777
4184
  wallpaperRatingAll: "全部",
3778
4185
  wallpaperRatingG: "G",
@@ -3993,14 +4400,14 @@ window.__ModuleLoader__.load({
3993
4400
  note: "sidebar new-session action (compat seam shields skins from localized labels)"
3994
4401
  },
3995
4402
  {
3996
- selector: "[data-dsh-taskboard-view], [data-dsh-taskboard-board], [data-dsh-taskboard-entry]",
4403
+ selector: "[data-dsh-taskboard-view], [data-dsh-taskboard-board]",
3997
4404
  attrs: [["data-dsh-plugin", "task-board"]],
3998
- note: "task-board panel/board/sidebar entry"
4405
+ note: "task-board panel/board (the sidebar row is shell-owned: its glyph carries data-dsh-panel-entry and the family rule below stamps the row)"
3999
4406
  },
4000
4407
  {
4001
- selector: "[data-dsh-ssh-view], [data-dsh-ssh-entry]",
4408
+ selector: "[data-dsh-ssh-view]",
4002
4409
  attrs: [["data-dsh-plugin", "ssh"]],
4003
- note: "ssh panel/sidebar entry"
4410
+ note: "ssh panel (the sidebar row is shell-owned: its glyph carries data-dsh-panel-entry and the family rule below stamps the row)"
4004
4411
  },
4005
4412
  {
4006
4413
  selector: "[data-gitgraph-chip-anchor], [data-gitgraph-dialog]",
@@ -4013,11 +4420,57 @@ window.__ModuleLoader__.load({
4013
4420
  note: "pet global root"
4014
4421
  },
4015
4422
  {
4016
- selector: "[data-dsh-taskboard-entry], [data-dsh-ssh-entry]",
4423
+ selector: "[class*=\"panelRow\"]:has([data-dsh-panel-entry])",
4017
4424
  attrs: [["data-dsh-part", "sidebar-entry"]],
4018
- note: "shared injected sidebar entry rows"
4425
+ note: "a plugin-registered sidebar row (sidebar.panellist): the shell owns the row box and stamps no hook of its own, so the row is recognised by its css-module class plus the glyph identity the registering plugin outputs — same compat technique as newSession"
4019
4426
  }
4020
4427
  ];
4428
+ /**
4429
+ * The anchors the rule table keys on, as one attribute filter for the
4430
+ * observer: a glyph or row anchor that is SET on an element already in the
4431
+ * tree must re-anchor its ancestors exactly like a newly inserted one.
4432
+ */
4433
+ const ANCHOR_ATTRIBUTES = [
4434
+ "data-dsh-panel-entry",
4435
+ "data-dsh-surface",
4436
+ "data-dsh-part",
4437
+ "data-dsh-plugin",
4438
+ "data-slot",
4439
+ "data-chat-flow-kind",
4440
+ "data-conversation-scroll",
4441
+ "data-turn-tail",
4442
+ "data-composer-input",
4443
+ "data-queue-dock",
4444
+ "data-rightbar-col",
4445
+ "data-shell-overlay",
4446
+ "data-dsh-taskboard-view",
4447
+ "data-dsh-taskboard-board",
4448
+ "data-dsh-ssh-view",
4449
+ "data-dsh-pet-root",
4450
+ "data-gitgraph-chip-anchor",
4451
+ "data-gitgraph-dialog",
4452
+ "data-decoration",
4453
+ "data-streaming",
4454
+ "data-side",
4455
+ "data-phase"
4456
+ ];
4457
+ /**
4458
+ * Safety bound of the ancestor walk, NOT a depth ceiling.
4459
+ *
4460
+ * The walk's normal terminator is the observed root: every node the observer
4461
+ * can report is inside document.body, so the walk returns there (measured —
4462
+ * with this bound removed the walk still stops at body on every delivery).
4463
+ * This constant only fires for a change inside a subtree that is not connected
4464
+ * to the observed root at all, where there is no ancestor to anchor yet and
4465
+ * the node's own childList delivery stamps it once it IS attached.
4466
+ *
4467
+ * It is deliberately far above any real markup depth (the shell's own sidebar
4468
+ * row is three hops deep) instead of a small number that would silently leave
4469
+ * a deeper row unanchored — which is the very defect this walk exists to fix
4470
+ * (issue #1732). A measured sweep anchors through 23 wrapper levels and misses
4471
+ * from 24, i.e. only past the point where the tree is pathological.
4472
+ */
4473
+ const ANCESTOR_HOPS = 24;
4021
4474
  function createSemanticAdapter(doc) {
4022
4475
  const rules = SEMANTIC_RULES_V1.map((rule) => ({
4023
4476
  rule,
@@ -4061,6 +4514,25 @@ window.__ModuleLoader__.load({
4061
4514
  for (const live of rules) live.matchedInPass = 0;
4062
4515
  if (doc.documentElement) applyToTree(doc.documentElement);
4063
4516
  };
4517
+ /**
4518
+ * Re-anchor one changed node and its ancestor chain.
4519
+ *
4520
+ * applyToTree only walks DOWNWARD: a rule whose selector matches an ancestor
4521
+ * of the changed node (the panel-list row is recognised by the glyph INSIDE
4522
+ * it) is never re-evaluated when that descendant arrives. React mounts the
4523
+ * row first and the registering plugin's glyph a tick later, so without this
4524
+ * the row keeps no anchor at all until something forces a full pass
4525
+ * (issue #1732). The walk is bounded and stops at the document root.
4526
+ */
4527
+ const reanchorAround = (node) => {
4528
+ const root = doc.body ?? doc.documentElement;
4529
+ let current = node;
4530
+ for (let hop = 0; current !== null && hop <= ANCESTOR_HOPS; hop += 1) {
4531
+ for (const live of rules) if (live.usable) applyRule(live, current);
4532
+ if (current === root || current === doc.documentElement) return;
4533
+ current = current.parentElement;
4534
+ }
4535
+ };
4064
4536
  return {
4065
4537
  get running() {
4066
4538
  return running;
@@ -4071,12 +4543,23 @@ window.__ModuleLoader__.load({
4071
4543
  fullPass();
4072
4544
  observer = new doc.defaultView.MutationObserver((records) => {
4073
4545
  try {
4074
- for (const record of records) for (const node of Array.from(record.addedNodes)) if (node.nodeType === 1) applyToTree(node);
4546
+ for (const record of records) {
4547
+ for (const node of Array.from(record.addedNodes)) {
4548
+ if (node.nodeType !== 1) continue;
4549
+ const el = node;
4550
+ applyToTree(el);
4551
+ reanchorAround(el.parentElement ?? el);
4552
+ }
4553
+ const target = record.target.nodeType === 1 ? record.target : null;
4554
+ if (target !== null && record.type === "attributes") reanchorAround(target);
4555
+ }
4075
4556
  } catch {}
4076
4557
  });
4077
4558
  observer.observe(doc.body ?? doc.documentElement, {
4078
4559
  childList: true,
4079
- subtree: true
4560
+ subtree: true,
4561
+ attributes: true,
4562
+ attributeFilter: [...ANCHOR_ATTRIBUTES]
4080
4563
  });
4081
4564
  },
4082
4565
  stop() {
@@ -4145,11 +4628,24 @@ window.__ModuleLoader__.load({
4145
4628
  opacity: 1 !important;
4146
4629
  }
4147
4630
  ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.input.dock\"] > *")},
4148
- ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] > *")} {
4631
+ ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] > *")},
4632
+ ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] + *")} {
4149
4633
  /* One skin-driven accessory surface for task and statistics docks. Skins
4150
4634
  automatically follow their existing semantic theme tokens and may
4151
4635
  override the --dsh-composer-accessory-* variables for a stronger
4152
- signature without coupling this adapter to a specific catalog skin. */
4636
+ signature without coupling this adapter to a specific catalog skin.
4637
+
4638
+ The third head covers an accessory that is a SIBLING of the dock slot
4639
+ host rather than a child of it. The slot host is display: contents, so
4640
+ the shell lays its children out as items of the surrounding dock row
4641
+ and the child heads reach them; 0.1.7's context meter was placed beside
4642
+ that host instead of inside it, so it shared the row with the
4643
+ statistics pill but stayed bare (issue #1715) -- one line, two looks.
4644
+ The adjacent-sibling head keeps the row uniform without naming any
4645
+ hashed class or localized label, and covers whatever future accessory
4646
+ the shell seats next to the dock. The plate lands on the accessory's
4647
+ own seat, never on a trigger inside it, so a component's own hover
4648
+ feedback keeps painting on top. */
4153
4649
  background: var(--dsh-composer-accessory-bg, var(--dsw-specific-tip, var(--dsw-alias-bg-layer-1))) !important;
4154
4650
  color: var(--dsh-composer-accessory-color, var(--dsw-alias-label-tertiary)) !important;
4155
4651
  border: var(--dsh-composer-accessory-border, none) !important;
@@ -4158,7 +4654,11 @@ window.__ModuleLoader__.load({
4158
4654
  backdrop-filter: blur(var(--dsh-composer-accessory-blur, var(--dsh-input-card-blur, 10px))) !important;
4159
4655
  -webkit-backdrop-filter: blur(var(--dsh-composer-accessory-blur, var(--dsh-input-card-blur, 10px))) !important;
4160
4656
  }
4161
- ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] > *")} {
4657
+ ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] > *")},
4658
+ ${scoped("[data-phase=\"active\"] [data-slot=\"conversation.composer.dock\"] + *")} {
4659
+ /* Same metrics for a dock child and for the sibling seat beside the dock
4660
+ slot host, so the two accessories of one row end up the same height:
4661
+ the plate must not make one of them look like a different control. */
4162
4662
  margin-top: var(--dsh-composer-accessory-gap, 4px);
4163
4663
  margin-bottom: var(--dsh-composer-accessory-gap, 4px);
4164
4664
  padding-top: 2px;
@@ -4770,6 +5270,7 @@ window.__ModuleLoader__.load({
4770
5270
  let catalog = null;
4771
5271
  let diagnostics = [];
4772
5272
  const listeners = /* @__PURE__ */ new Set();
5273
+ const teardowns = /* @__PURE__ */ new Set();
4773
5274
  const emit = () => {
4774
5275
  for (const listener of listeners) listener();
4775
5276
  };
@@ -4784,6 +5285,10 @@ window.__ModuleLoader__.load({
4784
5285
  const store = {
4785
5286
  controller,
4786
5287
  adapter,
5288
+ doc,
5289
+ window: doc.defaultView ?? window,
5290
+ apiBase,
5291
+ fetchImpl,
4787
5292
  catalog: () => catalog,
4788
5293
  diagnostics: () => diagnostics,
4789
5294
  refreshCatalog,
@@ -4802,6 +5307,9 @@ window.__ModuleLoader__.load({
4802
5307
  adapter.stop();
4803
5308
  disposeShellRendering();
4804
5309
  controller.shutdown();
5310
+ for (const teardown of [...teardowns]) teardown();
5311
+ teardowns.clear();
5312
+ listeners.clear();
4805
5313
  }
4806
5314
  };
4807
5315
  {
@@ -4829,20 +5337,133 @@ window.__ModuleLoader__.load({
4829
5337
  await controller.switchTo(null, null).catch(() => {});
4830
5338
  }
4831
5339
  })();
4832
- if (typeof doc.defaultView !== "undefined" && doc.defaultView !== null) {
4833
- const win = doc.defaultView;
4834
- const onSkinApplied = (e) => {
4835
- const detail = e.detail;
4836
- if (detail && typeof detail.id === "string") refreshCatalog().then(async () => {
4837
- const entry = store.find(detail.id);
4838
- if (entry !== null) await controller.switchTo(detail.id, entry);
4839
- });
4840
- };
4841
- win.addEventListener("dsh-skin-applied", onSkinApplied);
4842
- listeners.add(() => win.removeEventListener("dsh-skin-applied", onSkinApplied));
4843
- }
5340
+ teardowns.add(trackSkinAppliedEvents(store));
4844
5341
  return store;
4845
5342
  }
5343
+ /**
5344
+ * Install the skin-applied contract: a window event carrying the applied skin
5345
+ * id makes this page converge on that skin immediately.
5346
+ *
5347
+ * The v1 runtime booted through a full document reload, so any page that
5348
+ * switched the skin could assume the next load picked the new selection up -
5349
+ * the workshop card simply reloaded. The v2 runtime applies in place, so the
5350
+ * page that is already open has to converge on the write, and the workshop
5351
+ * installs the skin files itself. Legacy-bridge pages announce through that
5352
+ * reload event; wiring it here keeps one converge path instead of two.
5353
+ * @param store - the runtime store the event converges.
5354
+ * @param win - the window whose document the runtime owns.
5355
+ * @returns the idempotent listener teardown.
5356
+ */
5357
+ function trackSkinAppliedEvents(store) {
5358
+ const win = store.window;
5359
+ const onApplied = (event) => {
5360
+ const detail = event.detail;
5361
+ if (detail === null || typeof detail !== "object") return;
5362
+ const id = detail.id;
5363
+ if (typeof id !== "string" || id === "") return;
5364
+ convergeOnSkin(store, id);
5365
+ };
5366
+ win.addEventListener("dsh-skin-applied", onApplied);
5367
+ return () => {
5368
+ win.removeEventListener("dsh-skin-applied", onApplied);
5369
+ };
5370
+ }
5371
+ /** Polling cadence for a selection changed outside this page. */
5372
+ const SELECTION_POLL_MS = 2e3;
5373
+ /** Read the persisted selection; undefined when the endpoint cannot answer. */
5374
+ async function readPersistedSelection(store) {
5375
+ let res;
5376
+ try {
5377
+ res = await store.fetchImpl(`${store.apiBase}/active`);
5378
+ } catch {
5379
+ return;
5380
+ }
5381
+ if (!res.ok) return void 0;
5382
+ let payload;
5383
+ try {
5384
+ payload = await res.json();
5385
+ } catch {
5386
+ return;
5387
+ }
5388
+ if (payload.ok !== true) return void 0;
5389
+ return typeof payload.active === "string" && payload.active !== "" ? payload.active : null;
5390
+ }
5391
+ /**
5392
+ * Converge on the skin the persisted selection names, re-reading the catalog
5393
+ * first when the id is not in the current snapshot (a workshop install lands
5394
+ * after the page read the catalog).
5395
+ */
5396
+ async function convergeOnSelection(store, id) {
5397
+ if (id !== null && store.find(id) === null) try {
5398
+ await store.refreshCatalog();
5399
+ } catch {}
5400
+ if (id === null) {
5401
+ await store.controller.switchTo(null, null);
5402
+ return;
5403
+ }
5404
+ const entry = store.find(id);
5405
+ if (entry === null) return;
5406
+ await store.controller.switchTo(id, entry);
5407
+ }
5408
+ /** Converge on an announced skin id, re-reading the catalog when it is new. */
5409
+ async function convergeOnSkin(store, id) {
5410
+ await convergeOnSelection(store, id);
5411
+ }
5412
+ /**
5413
+ * Follow the persisted selection while this page is open.
5414
+ *
5415
+ * Applying a skin and saving that choice are one action in the GUI, but the
5416
+ * choice is persisted for the NEXT page load. The conventional route is the
5417
+ * official settings form: it writes the settings row this plugin owns, the
5418
+ * Host applies it, and nothing inside this page is told. That row is seeded
5419
+ * from the skin center's own configuration, so the two are the same choice
5420
+ * written twice, and this page reads it back rather than assuming it owns
5421
+ * every write (issue #1740).
5422
+ *
5423
+ * The poll only reads: it never writes the selection back, it skips a value
5424
+ * already applied, and it stops while the page is hidden.
5425
+ * @param store - the booted runtime store.
5426
+ * @param intervalMs - polling cadence in milliseconds.
5427
+ * @returns the idempotent teardown of the poll.
5428
+ */
5429
+ function watchPersistedSelection(store, intervalMs = SELECTION_POLL_MS) {
5430
+ let applied;
5431
+ let timer = null;
5432
+ let stopped = false;
5433
+ const tick = async () => {
5434
+ if (stopped) return;
5435
+ const next = await readPersistedSelection(store);
5436
+ if (stopped || next === void 0 || next === applied) return;
5437
+ applied = next;
5438
+ await convergeOnSelection(store, next);
5439
+ };
5440
+ const stopTimer = () => {
5441
+ if (timer !== null) store.window.clearInterval(timer);
5442
+ timer = null;
5443
+ };
5444
+ const startTimer = () => {
5445
+ if (stopped || timer !== null) return;
5446
+ timer = store.window.setInterval(() => {
5447
+ tick();
5448
+ }, intervalMs);
5449
+ };
5450
+ (async () => {
5451
+ applied = await readPersistedSelection(store);
5452
+ if (!stopped) startTimer();
5453
+ })();
5454
+ const onVisibility = () => {
5455
+ if (store.doc.visibilityState === "visible") {
5456
+ startTimer();
5457
+ tick();
5458
+ } else stopTimer();
5459
+ };
5460
+ store.doc.addEventListener("visibilitychange", onVisibility);
5461
+ return () => {
5462
+ stopped = true;
5463
+ stopTimer();
5464
+ store.doc.removeEventListener("visibilitychange", onVisibility);
5465
+ };
5466
+ }
4846
5467
  //#endregion
4847
5468
  //#region src/client/preview-coordinator.ts
4848
5469
  var PreviewCoordinator = class {
@@ -5106,6 +5727,13 @@ window.__ModuleLoader__.load({
5106
5727
  if (!this.config.applied) throw new Error("custom theme activation was not persisted");
5107
5728
  }
5108
5729
  async deactivate() {
5730
+ if (!this.config.applied) {
5731
+ this.previewingValue = false;
5732
+ this.suspended = false;
5733
+ this.syncDom();
5734
+ this.publish();
5735
+ return;
5736
+ }
5109
5737
  this.config = {
5110
5738
  ...this.config,
5111
5739
  applied: false
@@ -5114,6 +5742,7 @@ window.__ModuleLoader__.load({
5114
5742
  this.suspended = false;
5115
5743
  this.syncDom();
5116
5744
  this.publish();
5745
+ if (this.scope.getSnapshot().status === "unavailable") return;
5117
5746
  await this.queueWrite("applied", false);
5118
5747
  if (this.config.applied) throw new Error("custom theme deactivation was not persisted");
5119
5748
  }
@@ -5171,6 +5800,19 @@ window.__ModuleLoader__.load({
5171
5800
  };
5172
5801
  this.publish();
5173
5802
  }
5803
+ /**
5804
+ * Identity of the settings section a refused write addressed, for the error a
5805
+ * user sees. It carries the Host's own status and revision — which entry
5806
+ * answered, whether it can be written, and at which revision — and never any
5807
+ * stored configuration value, so a refused save can be diagnosed from the
5808
+ * message alone without exposing what the user configured.
5809
+ * @returns a short diagnostic fragment naming the section and its state.
5810
+ */
5811
+ sectionState() {
5812
+ const snapshot = this.scope.getSnapshot();
5813
+ const revision = snapshot.revision === void 0 ? "none" : String(snapshot.revision);
5814
+ return `${SKIN_CUSTOM_THEME_NS}: status ${snapshot.status}, writable ${String(snapshot.writable)}, mode ${snapshot.mode}, revision ${revision}`;
5815
+ }
5174
5816
  queueWrite(field, value) {
5175
5817
  this.pendingWrites += 1;
5176
5818
  const pending = new Promise((resolve, reject) => {
@@ -5192,7 +5834,7 @@ window.__ModuleLoader__.load({
5192
5834
  const write = this.writeQueue.shift();
5193
5835
  if (write === void 0) break;
5194
5836
  try {
5195
- if (!await this.scope.set(write.field, write.value)) throw new Error("the Host did not accept the custom theme setting");
5837
+ if (!await this.scope.set(write.field, write.value)) throw new Error(`the Host did not accept the custom theme setting (${this.sectionState()})`);
5196
5838
  } catch (error) {
5197
5839
  settled.push({
5198
5840
  write,
@@ -5270,6 +5912,59 @@ window.__ModuleLoader__.load({
5270
5912
  };
5271
5913
  }
5272
5914
  //#endregion
5915
+ //#region src/client/settings-entry-id.ts
5916
+ /** Profile entry id the family aggregate's generated row carries. */
5917
+ const AGGREGATE_ENTRY_ID = "web-ui-skin-center";
5918
+ /**
5919
+ * This package's own declared plugin name, which is also the entry id its
5920
+ * standalone bundle patch row carries (cordis.patch.yml) and the `name` the
5921
+ * plugin exports (src/index.ts). It is the one safe guess when the describe
5922
+ * mirror cannot answer, because it names the row this package itself installs
5923
+ * rather than a row that belongs to a different package.
5924
+ */
5925
+ const OWN_ENTRY_ID = "ui-skin-center";
5926
+ /**
5927
+ * Profile entry ids this package's rows can be served under: its own standalone
5928
+ * row, the family aggregate's generated row, and the legacy background
5929
+ * namespace a profile that named the row after it still serves. The served
5930
+ * mirror decides which one is real; this list only ranks the candidates.
5931
+ */
5932
+ const SKIN_CENTER_ENTRY_IDS = [
5933
+ OWN_ENTRY_ID,
5934
+ AGGREGATE_ENTRY_ID,
5935
+ "skin-background"
5936
+ ];
5937
+ /**
5938
+ * The entry id of the first skin-center row the Host says it serves, or `null`
5939
+ * when the mirror cannot say. `null` means "unknown", never "none": the mirror
5940
+ * is asynchronous, so an unreadable snapshot is a pre-boot window, not proof
5941
+ * that this package is absent.
5942
+ * @param forms - the shared configuration forms service.
5943
+ * @param candidates - entry ids to look for, in preference order.
5944
+ * @returns the served entry id, or `null` when the mirror cannot answer.
5945
+ */
5946
+ function servedEntryId(forms, candidates = SKIN_CENTER_ENTRY_IDS) {
5947
+ let served;
5948
+ try {
5949
+ served = forms.describe().getSnapshot().view?.namespaces.map((view) => view.ns);
5950
+ } catch {
5951
+ served = void 0;
5952
+ }
5953
+ if (served === void 0) return null;
5954
+ return candidates.find((id) => served.includes(id)) ?? null;
5955
+ }
5956
+ /**
5957
+ * The entry id to bind, which is this package's OWN row whenever the mirror
5958
+ * cannot name a skin-center row. Addressing another package's row is the #17
5959
+ * failure; addressing this one's own row is safe because an entry that is not
5960
+ * served reports itself `unavailable` instead of silently accepting a write.
5961
+ * @param forms - the shared configuration forms service.
5962
+ * @returns the entry id whose form the plugin should address.
5963
+ */
5964
+ function boundEntryId(forms) {
5965
+ return servedEntryId(forms) ?? OWN_ENTRY_ID;
5966
+ }
5967
+ //#endregion
5273
5968
  //#region src/client/telemetry.ts
5274
5969
  const VISITOR_KEY = "dsh-web-ui-telemetry-visitor";
5275
5970
  const DAY_KEY_PREFIX = "dsh-web-ui-telemetry-day:";
@@ -5277,7 +5972,7 @@ window.__ModuleLoader__.load({
5277
5972
  /** The building package's version, when the bundle carries it. */
5278
5973
  function bakedVersion() {
5279
5974
  try {
5280
- return "0.4.2";
5975
+ return "0.4.4";
5281
5976
  } catch {
5282
5977
  return;
5283
5978
  }
@@ -5344,30 +6039,6 @@ window.__ModuleLoader__.load({
5344
6039
  /** Locale namespace owned by this plugin. */
5345
6040
  const NS = "skinCenter";
5346
6041
  /**
5347
- * Profile entry id the family aggregate's generated row carries.
5348
- */
5349
- const AGGREGATE_ENTRY_ID = "web-ui-skin-center";
5350
- /**
5351
- * Profile entry ids this package's two patch rows carry: the aggregate's
5352
- * generated row and the standalone bundle patch's row (`ui-skin-center`), plus
5353
- * the legacy background namespace as the last resort.
5354
- */
5355
- const SKIN_CENTER_ENTRY_IDS = [
5356
- AGGREGATE_ENTRY_ID,
5357
- "ui-skin-center",
5358
- SKIN_BACKGROUND_NS
5359
- ];
5360
- function servedEntryId(forms) {
5361
- let served;
5362
- try {
5363
- served = forms.describe().getSnapshot().view?.namespaces.map((view) => view.ns);
5364
- } catch {
5365
- served = void 0;
5366
- }
5367
- if (served === void 0) return AGGREGATE_ENTRY_ID;
5368
- return SKIN_CENTER_ENTRY_IDS.find((id) => served.includes(id)) ?? "skin-background";
5369
- }
5370
- /**
5371
6042
  * The configuration form of this plugin's own profile entry.
5372
6043
  *
5373
6044
  * `ctx.configForms` addresses one form per profile entry id and carries no
@@ -5375,9 +6046,15 @@ window.__ModuleLoader__.load({
5375
6046
  * namespace-to-entry mapping the settings group owns: `skin-background` is
5376
6047
  * the namespace this package has always owned, and the bridge resolves it to
5377
6048
  * whichever entry id the profile gave this row. A deployment without the
5378
- * group serves no such mapping — the family namespace then stands in for the
5379
- * entry id (a profile that names the row after it serves the same form), and
5380
- * a page that serves neither reports the form unavailable, which each feature
6049
+ * group serves no such mapping, so the shared forms service is asked which row
6050
+ * it actually serves. Guessing ANOTHER package's entry id is what produced
6051
+ * dsh-skins#17: a standalone install (`ui-skin-center`) bound the aggregate's row
6052
+ * (`web-ui-skin-center`) whenever the describe mirror had not landed yet, and
6053
+ * every `settings.mutate` then addressed an entry the Host does not serve, so
6054
+ * applying and restoring a skin both failed with a rejected write and a
6055
+ * rolled-back switch. The mirror is the only authority on the entry id, so the
6056
+ * one guess that survives an unreadable mirror is this package's OWN row: an
6057
+ * entry that is not served reports itself unavailable, which each feature
5381
6058
  * already handles by keeping its defaults and reporting a failed save.
5382
6059
  * @param ctx - client root context.
5383
6060
  * @returns the entry form carrying every preference family.
@@ -5385,16 +6062,40 @@ window.__ModuleLoader__.load({
5385
6062
  function bindConfigForm(ctx) {
5386
6063
  const binder = ctx.get("webUiSettings");
5387
6064
  if (binder !== void 0 && typeof binder.bind === "function") return binder.bind({ namespace: SKIN_BACKGROUND_NS });
5388
- return ctx.configForms.get(servedEntryId(ctx.configForms));
6065
+ return ctx.configForms.get(boundEntryId(ctx.configForms));
5389
6066
  }
5390
- /** Required services: slots + locale (plugin card), theme (preview toggle), configForms (settings sections), and remote (wallpaper directory picker). */
6067
+ /**
6068
+ * Required services: slots + locale (plugin card), theme (preview toggle),
6069
+ * configForms (settings sections), and the Remote faces the wallpaper feature
6070
+ * calls.
6071
+ *
6072
+ * `remote.directoryPicker` is named explicitly because a generated Remote
6073
+ * namespace is its own Cordis service (`remote.<namespace>`), NOT a property
6074
+ * of `remote`: `ctx.remote.x` is only readable while `remote.x` is injected,
6075
+ * otherwise cordis' context proxy throws
6076
+ * `cannot get property "remote.x" without inject`. `connection` owns the
6077
+ * carrier the namespace service rides on, so it is required first.
6078
+ *
6079
+ * The skin center is mounted inside the dsh-web-all aggregate, whose client
6080
+ * children bring their own nested fiber; naming the namespace here parks this
6081
+ * plugin until the picker namespace is really mounted, which is what makes
6082
+ * `pickDir` available on a Host that serves it (and keeps the browse button
6083
+ * off a deployment that does not).
6084
+ *
6085
+ * Both composed backends register the same namespace, so the namespace's
6086
+ * presence says nothing about which interaction the Host serves: a native
6087
+ * backend opens the OS chooser, a browse backend refuses `pick` and serves
6088
+ * listing primitives instead. The panel decides on the answer, not on
6089
+ * presence (see WallpaperPanel's folder browser).
6090
+ */
5391
6091
  const inject = [
5392
6092
  "slots",
5393
6093
  "locale",
5394
6094
  "theme",
5395
6095
  "configForms",
5396
6096
  "connection",
5397
- "remote"
6097
+ "remote",
6098
+ "remote.directoryPicker"
5398
6099
  ];
5399
6100
  /** Self-report item for the install heartbeat. */
5400
6101
  const SELF_ITEM = [{ name: "@linxin666/dsh-client-ui-skin-center" }];
@@ -5512,6 +6213,7 @@ window.__ModuleLoader__.load({
5512
6213
  installBootRestore(wallpaper);
5513
6214
  const runtime = bootSkinRuntime({ suppressBackgroundMedia: () => wallpaper.enabled() && wallpaper.isDisplaying() });
5514
6215
  ctx.effect(() => () => runtime.shutdown(), "ui-skin-center: runtime shutdown");
6216
+ ctx.effect(() => watchPersistedSelection(runtime), "ui-skin-center: follow the persisted selection");
5515
6217
  ctx.effect(() => wallpaper.subscribe(() => {
5516
6218
  runtime.controller.refresh();
5517
6219
  }), "ui-skin-center: wallpaper priority refresh");
@@ -5558,8 +6260,9 @@ window.__ModuleLoader__.load({
5558
6260
  dirs: () => wallpaper.dirs(),
5559
6261
  addDir: (dir) => wallpaper.addDir(dir),
5560
6262
  removeDir: (dir) => wallpaper.removeDir(dir),
5561
- pickDir: async () => {
5562
- const result = await ctx.remote.directoryPicker.pick();
6263
+ pickDir: async () => mapDirPickResult(await ctx.remote.directoryPicker.pick()),
6264
+ listDir: async (path) => {
6265
+ const result = await ctx.remote.directoryPicker.list(path);
5563
6266
  if (!result.ok) throw new Error(result.error.message);
5564
6267
  return result.value;
5565
6268
  },