deepseek-harness-background 0.5.6 → 0.6.0
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/README.md +6 -5
- package/README.zh.md +6 -5
- package/icon.svg +8 -0
- package/lib/client.js +334 -53
- package/lib/client.js.map +1 -1
- package/lib/index.js +16 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +12 -8
package/lib/client.js
CHANGED
|
@@ -104,6 +104,11 @@ window.__ModuleLoader__.load({
|
|
|
104
104
|
.dsh-bg-layer .dsh-bg-image {
|
|
105
105
|
width: 100%; height: 100%; display: block; border: 0; background: transparent;
|
|
106
106
|
object-fit: var(--bg-object-fit, cover);
|
|
107
|
+
/* Normalized crop framing: percent object-position is the position of the
|
|
108
|
+
image inside its own overflow, so one stored 0..1 value holds for every
|
|
109
|
+
window size and every source. Both axes are always written; the axis
|
|
110
|
+
with no overflow simply ignores its value. */
|
|
111
|
+
object-position: calc(var(--bg-offset-x, 0.5) * 100%) calc(var(--bg-offset-y, 0.5) * 100%);
|
|
107
112
|
opacity: var(--bg-opacity, 1);
|
|
108
113
|
filter: blur(var(--bg-wallpaper-blur, 0px));
|
|
109
114
|
/* Blur thins the edges; scale the image up to hide the fringe. */
|
|
@@ -120,6 +125,21 @@ window.__ModuleLoader__.load({
|
|
|
120
125
|
body[data-ds-dark-theme] .dsh-bg-scrim {
|
|
121
126
|
background: rgba(0, 0, 0, var(--bg-scrim, 0.25));
|
|
122
127
|
}
|
|
128
|
+
/* Desktop window drag (Electron on macOS): the shell marks a chrome row
|
|
129
|
+
\`data-window-drag\` and one rule subtracts every other body-level box
|
|
130
|
+
(\`html[data-platform='darwin'] body > :not(#root) { -webkit-app-region:
|
|
131
|
+
no-drag; }\`, ui-web base.css). Chromium collects app-region boxes by
|
|
132
|
+
geometry in DOM order and the LAST box containing a point decides, so
|
|
133
|
+
these two decorative layers — body children that follow #root — would
|
|
134
|
+
swallow the drag surface of the whole window (traffic-light strip,
|
|
135
|
+
conversation header, dock strip). They never take the pointer, so they
|
|
136
|
+
must not join the composition at all: a computed \`none\` is the one value
|
|
137
|
+
Chromium does not collect. The shell rule carries #root's id specificity,
|
|
138
|
+
so a plain declaration cannot outrank it — this needs \`!important\`. */
|
|
139
|
+
body > .dsh-bg-layer,
|
|
140
|
+
body > .dsh-bg-scrim {
|
|
141
|
+
-webkit-app-region: none !important;
|
|
142
|
+
}
|
|
123
143
|
|
|
124
144
|
/* Active: make the app frame + sidebar background transparent so all columns
|
|
125
145
|
share the same wallpaper + scrim backdrop. */
|
|
@@ -136,7 +156,6 @@ window.__ModuleLoader__.load({
|
|
|
136
156
|
light glass slightly DIMS, dark glass keeps a slight lift. Gated on the
|
|
137
157
|
GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel
|
|
138
158
|
slider returns these surfaces to the exact official paints. */
|
|
139
|
-
body[data-dsh-bg-glass] [data-composer-card],
|
|
140
159
|
body[data-dsh-bg-glass] [class*="_bubble"]:not([role="tooltip"]),
|
|
141
160
|
/* The read_image toolview (dsh >= 0.1.2-rc.1 ToolRow) collapses its result
|
|
142
161
|
into an image body card; glass it like the other content blocks. */
|
|
@@ -160,6 +179,37 @@ window.__ModuleLoader__.load({
|
|
|
160
179
|
0 12px 40px rgba(0, 0, 0, 0.12);
|
|
161
180
|
}
|
|
162
181
|
|
|
182
|
+
/* ---- Composer card: the filter rides a pseudo-element ------------------
|
|
183
|
+
Chromium makes every element that carries a backdrop-filter a Backdrop
|
|
184
|
+
Root, and a filter inside one can only sample what that element itself
|
|
185
|
+
paints. The composer hosts the picker menu as an absolutely positioned
|
|
186
|
+
child ([data-composer-card] > … conversation.input.overlay), so with the
|
|
187
|
+
filter on the card itself that menu sampled the card's own paint only and
|
|
188
|
+
read as flat transparency over the wallpaper. Moving the filter to a
|
|
189
|
+
positioned ::before breaks the root — the pseudo-element is not an
|
|
190
|
+
ancestor of the menu, so the menu blurs the wallpaper again. The card
|
|
191
|
+
keeps its own frost (the ::before supplies it) and needs no layout change:
|
|
192
|
+
ui-conversation already declares it position: relative as the overlay
|
|
193
|
+
anchor context. */
|
|
194
|
+
body[data-dsh-bg-glass] [data-composer-card] {
|
|
195
|
+
background-color: var(--dsw-specific-input-major);
|
|
196
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
197
|
+
box-shadow:
|
|
198
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32),
|
|
199
|
+
inset 0 -1px 0 rgba(255, 255, 255, 0.08),
|
|
200
|
+
inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),
|
|
201
|
+
0 12px 40px rgba(0, 0, 0, 0.12);
|
|
202
|
+
}
|
|
203
|
+
body[data-dsh-bg-glass] [data-composer-card]::before {
|
|
204
|
+
content: '';
|
|
205
|
+
position: absolute;
|
|
206
|
+
inset: 0;
|
|
207
|
+
z-index: -1;
|
|
208
|
+
border-radius: inherit;
|
|
209
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
210
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
211
|
+
}
|
|
212
|
+
|
|
163
213
|
/* The sticky code-block header officially occludes scrolled code with
|
|
164
214
|
bg-base (transparent under a wallpaper): refill it with the banner token
|
|
165
215
|
(itself translucent glass) so code stays hidden beneath the sticky bar. */
|
|
@@ -308,6 +358,35 @@ window.__ModuleLoader__.load({
|
|
|
308
358
|
box-shadow: var(--dsw-elevation-panel, 0 4px 14px rgba(0, 0, 0, 0.08));
|
|
309
359
|
}
|
|
310
360
|
|
|
361
|
+
/* The card's header paints the official opaque fill (--changes-fill is a
|
|
362
|
+
static neutral), so the glass card underneath never shows through: hand
|
|
363
|
+
that one surface back to the card by clearing its fill, and give the
|
|
364
|
+
header's own hover/focus feedback the translucent interactive token the
|
|
365
|
+
card's rows already use. */
|
|
366
|
+
body[data-dsh-bg-glass] [data-changed-files] > :first-child {
|
|
367
|
+
background-color: transparent;
|
|
368
|
+
}
|
|
369
|
+
body[data-dsh-bg-glass] [data-changed-files] > :first-child:hover:not(:disabled),
|
|
370
|
+
body[data-dsh-bg-glass] [data-changed-files] > :first-child:focus-visible {
|
|
371
|
+
background-color: var(--dsw-alias-interactive-bg-hover);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* ---- Docked stat dialogs (turn time, session stats, token usage) -------
|
|
375
|
+
These portal to document.body, so unlike the picker menu above they do
|
|
376
|
+
reach the wallpaper; their official menu skin is a 40px blur under a flat
|
|
377
|
+
58% fill, which on a soft wallpaper still reads as plain transparency. The
|
|
378
|
+
official fill and its readable contrast stay, and the shared sheen +
|
|
379
|
+
exposure chain joins them to the same glass family as every other panel —
|
|
380
|
+
including the user's blur slider (--bg-glass-blur). Anchors are the
|
|
381
|
+
panels' own detail lists: class hashes are never structural hooks. */
|
|
382
|
+
body[data-dsh-bg-glass] [role="dialog"]:has([data-turn-usage-details]),
|
|
383
|
+
body[data-dsh-bg-glass] [role="dialog"]:has([data-session-stats-details]),
|
|
384
|
+
body[data-dsh-bg-glass] [role="dialog"]:has([data-session-stats-usage]) {
|
|
385
|
+
background-image: linear-gradient(180deg, rgba(255, 255, 255, var(--bg-glass-sheen, 0.07)), rgba(255, 255, 255, var(--bg-glass-sheen-mid, 0.02)) 38%, rgba(255, 255, 255, 0.01));
|
|
386
|
+
-webkit-backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
387
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
388
|
+
}
|
|
389
|
+
|
|
311
390
|
/* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------
|
|
312
391
|
The enhance mode borrows the kernel's rail untouched, so its hover
|
|
313
392
|
preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated
|
|
@@ -355,6 +434,8 @@ window.__ModuleLoader__.load({
|
|
|
355
434
|
const DEFAULT_FIT = "cover";
|
|
356
435
|
/** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */
|
|
357
436
|
const FIT_MODES = ["cover", "contain"];
|
|
437
|
+
/** Default framing offset — the image sits centered on both axes. */
|
|
438
|
+
const DEFAULT_OFFSET = .5;
|
|
358
439
|
//#endregion
|
|
359
440
|
//#region src/client/backdrop.ts
|
|
360
441
|
/**
|
|
@@ -559,6 +640,8 @@ window.__ModuleLoader__.load({
|
|
|
559
640
|
this.hasSource = true;
|
|
560
641
|
document.body.style;
|
|
561
642
|
this.setVar("--bg-object-fit", settings.fit);
|
|
643
|
+
this.setVar("--bg-offset-x", String(settings.offsetX));
|
|
644
|
+
this.setVar("--bg-offset-y", String(settings.offsetY));
|
|
562
645
|
this.setVar("--bg-opacity", String(settings.opacity));
|
|
563
646
|
this.setVar("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
|
|
564
647
|
this.setVar("--bg-wallpaper-scale", (1 + settings.wallpaperBlur * .006).toFixed(4));
|
|
@@ -626,7 +709,13 @@ window.__ModuleLoader__.load({
|
|
|
626
709
|
this.setVar("--bg-wallpaper-blur", `${value}px`);
|
|
627
710
|
this.setVar("--bg-wallpaper-scale", (1 + Number(value) * .006).toFixed(4));
|
|
628
711
|
break;
|
|
629
|
-
case "fit":
|
|
712
|
+
case "fit":
|
|
713
|
+
this.setVar("--bg-object-fit", String(value));
|
|
714
|
+
break;
|
|
715
|
+
case "offsetX":
|
|
716
|
+
this.setVar("--bg-offset-x", String(value));
|
|
717
|
+
break;
|
|
718
|
+
case "offsetY": this.setVar("--bg-offset-y", String(value));
|
|
630
719
|
}
|
|
631
720
|
}
|
|
632
721
|
/**
|
|
@@ -723,6 +812,8 @@ window.__ModuleLoader__.load({
|
|
|
723
812
|
"--bg-opacity",
|
|
724
813
|
"--bg-scrim",
|
|
725
814
|
"--bg-object-fit",
|
|
815
|
+
"--bg-offset-x",
|
|
816
|
+
"--bg-offset-y",
|
|
726
817
|
"--bg-wallpaper-blur",
|
|
727
818
|
"--bg-wallpaper-scale",
|
|
728
819
|
"--bg-glass-blur",
|
|
@@ -740,6 +831,8 @@ window.__ModuleLoader__.load({
|
|
|
740
831
|
function paintPreviewSurface(el, settings) {
|
|
741
832
|
const s = el.style;
|
|
742
833
|
s.setProperty("--bg-object-fit", settings.fit);
|
|
834
|
+
s.setProperty("--bg-offset-x", String(settings.offsetX));
|
|
835
|
+
s.setProperty("--bg-offset-y", String(settings.offsetY));
|
|
743
836
|
s.setProperty("--bg-opacity", String(settings.opacity));
|
|
744
837
|
s.setProperty("--bg-scrim", String(settings.scrim));
|
|
745
838
|
s.setProperty("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
|
|
@@ -985,6 +1078,8 @@ window.__ModuleLoader__.load({
|
|
|
985
1078
|
"background.fit": "填充方式",
|
|
986
1079
|
"background.cover": "铺满",
|
|
987
1080
|
"background.contain": "完整",
|
|
1081
|
+
"background.previewPanHint": "拖动预览可调整图片位置,双击居中",
|
|
1082
|
+
"background.repositionReset": "重置位置",
|
|
988
1083
|
"background.clear": "清除背景",
|
|
989
1084
|
"background.uploadFailed": "上传失败,请重试",
|
|
990
1085
|
"background.saveFailed": "保存失败,请重试",
|
|
@@ -1016,6 +1111,8 @@ window.__ModuleLoader__.load({
|
|
|
1016
1111
|
"background.fit": "Fit",
|
|
1017
1112
|
"background.cover": "Cover",
|
|
1018
1113
|
"background.contain": "Contain",
|
|
1114
|
+
"background.previewPanHint": "Drag the preview to reposition the image; double-click to recenter",
|
|
1115
|
+
"background.repositionReset": "Reset position",
|
|
1019
1116
|
"background.clear": "Clear background",
|
|
1020
1117
|
"background.uploadFailed": "Upload failed, try again",
|
|
1021
1118
|
"background.saveFailed": "Save failed, try again",
|
|
@@ -1149,7 +1246,7 @@ window.__ModuleLoader__.load({
|
|
|
1149
1246
|
const settingsClient = new SettingsClient();
|
|
1150
1247
|
//#endregion
|
|
1151
1248
|
//#region \0dsh-css:D:\Project\deepseek-harness-background\src\client\SettingsRow.module.css.mjs
|
|
1152
|
-
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_notice{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;display:flex}.-ozqdq_noticeText{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.-ozqdq_noticeBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-size:12px;line-height:18px;text-decoration:underline}.-ozqdq_noticeBtn:hover{color:var(--dsw-alias-label-primary)}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;height:132px;position:relative;overflow:hidden}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.42)) brightness(var(--bg-glass-brightness,1));border:1px solid #ffffff38;border-radius:10px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:16px;left:16px;box-shadow:inset 0 1px #ffffff4d}.-ozqdq_previewGlassText{color:#0f172ac7;text-shadow:0 1px 2px #fff6;white-space:nowrap;font-size:11px;line-height:1}body[data-ds-dark-theme] .-ozqdq_previewGlassText{color:#ffffffd9;text-shadow:0 1px 2px #00000059}.-ozqdq_previewEmpty{color:var(--dsw-alias-label-tertiary);justify-content:center;align-items:center;font-size:12px;line-height:1.5;display:flex;position:absolute;inset:0}.-ozqdq_section{flex-direction:column;gap:8px;display:flex}.-ozqdq_sectionLabel{color:var(--dsw-alias-label-tertiary);letter-spacing:.01em;font-size:12px;font-weight:500;line-height:18px}.-ozqdq_sourceRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.-ozqdq_urlRow{align-items:center;gap:8px;display:flex}.-ozqdq_urlInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 10px;font-size:13px;line-height:1.5}.-ozqdq_urlInput::placeholder{color:var(--dsw-alias-label-dimmed)}.-ozqdq_urlInput:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.-ozqdq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.-ozqdq_enabledBadge{white-space:nowrap;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}.-ozqdq_btn{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;transition:background-color .15s,border-color .15s,color .15s;display:inline-flex}.-ozqdq_btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.-ozqdq_btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.-ozqdq_btn:disabled{opacity:.45;cursor:default}.-ozqdq_btnPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.-ozqdq_btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.-ozqdq_btnDanger{color:var(--dsw-alias-label-error)}.-ozqdq_btnDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.-ozqdq_sliderRow{align-items:center;gap:10px;display:flex}.-ozqdq_sliderLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_slider{-webkit-appearance:none;appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.-ozqdq_slider::-webkit-slider-runnable-track{background:linear-gradient(90deg, var(--dsw-alias-brand-primary) var(--bg-fill,0%), var(--dsw-alias-border-l3) var(--bg-fill,0%));border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-track{background:var(--dsw-alias-border-l3);border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-progress{background:var(--dsw-alias-brand-primary);border-radius:2px;height:4px}.-ozqdq_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%;margin-top:-5px}.-ozqdq_slider::-moz-range-thumb{border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%}.-ozqdq_slider:hover::-webkit-slider-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:hover::-moz-range-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:active::-webkit-slider-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:active::-moz-range-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:focus-visible{outline:none}.-ozqdq_slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_slider:focus-visible::-moz-range-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_sliderValue{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);flex:0 0 52px;font-size:13px;line-height:1.5}.-ozqdq_segRow{align-items:center;gap:10px;display:flex}.-ozqdq_segLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_segGroup{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex:1;gap:2px;min-width:0;padding:2px;display:inline-flex}.-ozqdq_segBtn{height:24px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:7px;flex:1;padding:0 16px;font-size:12px;line-height:24px}.-ozqdq_segBtn:hover:not(.-ozqdq_segActive){color:var(--dsw-alias-label-primary)}.-ozqdq_segActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-weight:500}.-ozqdq_error{color:var(--dsw-alias-label-error);font-size:12px;line-height:1.5}.-ozqdq_clearRow{align-items:center;gap:10px;display:flex}.-ozqdq_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:11px;flex:none;width:40px;height:22px;padding:0;transition:background-color .15s;position:relative}.-ozqdq_switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}.-ozqdq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_switchKnob{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;position:absolute;top:3px;left:3px;box-shadow:0 1px 3px #00000047}.-ozqdq_switch[aria-checked=true] .-ozqdq_switchKnob{transform:translate(18px)}@media (prefers-reduced-motion:reduce){.-ozqdq_btn,.-ozqdq_switch,.-ozqdq_switchKnob{transition:none}}";
|
|
1249
|
+
const css = ".-ozqdq_row{border-bottom:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;padding:16px 0;display:flex}.-ozqdq_header{flex-direction:column;gap:4px;display:flex}.-ozqdq_titleRow{justify-content:space-between;align-items:center;gap:12px;min-height:22px;display:flex}.-ozqdq_clearBtn{border-radius:12px;flex:none;height:24px;padding:0 10px;font-size:12px;line-height:24px}.-ozqdq_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.-ozqdq_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.-ozqdq_notice{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;display:flex}.-ozqdq_noticeText{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.-ozqdq_noticeBtn{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;flex:none;padding:0;font-size:12px;line-height:18px;text-decoration:underline}.-ozqdq_noticeBtn:hover{color:var(--dsw-alias-label-primary)}.-ozqdq_ratioFrame{justify-content:center;display:flex}.-ozqdq_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;flex:none;width:100%;height:132px;position:relative;overflow:hidden}.-ozqdq_preview[data-pan]{cursor:move;touch-action:none}.-ozqdq_preview:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_previewImg{object-fit:var(--bg-object-fit,cover);object-position:calc(var(--bg-offset-x,.5) * 100%) calc(var(--bg-offset-y,.5) * 100%);width:100%;height:100%;opacity:var(--bg-opacity,1);filter:blur(var(--bg-wallpaper-blur,0px));transform:scale(var(--bg-wallpaper-scale,1));display:block;position:absolute;inset:0}.-ozqdq_previewScrim{background:rgba(255, 255, 255, var(--bg-scrim,.25));position:absolute;inset:0}body[data-ds-dark-theme] .-ozqdq_previewScrim{background:rgba(0, 0, 0, var(--bg-scrim,.25))}.-ozqdq_previewGlass{background:var(--bg-preview-glass,#ffffff24);width:44%;height:46px;backdrop-filter:blur(var(--bg-glass-blur,16px)) saturate(var(--bg-glass-saturate,1.42)) brightness(var(--bg-glass-brightness,1));border:1px solid #ffffff38;border-radius:10px;justify-content:center;align-items:center;display:flex;position:absolute;bottom:16px;left:16px;box-shadow:inset 0 1px #ffffff4d}.-ozqdq_previewGlassText{color:#0f172ac7;text-shadow:0 1px 2px #fff6;white-space:nowrap;font-size:11px;line-height:1}body[data-ds-dark-theme] .-ozqdq_previewGlassText{color:#ffffffd9;text-shadow:0 1px 2px #00000059}.-ozqdq_previewEmpty{color:var(--dsw-alias-label-tertiary);justify-content:center;align-items:center;font-size:12px;line-height:1.5;display:flex;position:absolute;inset:0}.-ozqdq_previewReset{z-index:2;height:22px;font:inherit;color:#fff;cursor:pointer;backdrop-filter:blur(4px);background:#0f172a73;border:none;border-radius:11px;padding:0 9px;font-size:11px;line-height:22px;transition:background-color .15s;position:absolute;top:8px;right:8px}.-ozqdq_previewReset:hover{background:#0f172aa6}.-ozqdq_section{flex-direction:column;gap:8px;display:flex}.-ozqdq_sectionLabel{color:var(--dsw-alias-label-tertiary);letter-spacing:.01em;font-size:12px;font-weight:500;line-height:18px}.-ozqdq_sourceRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.-ozqdq_urlRow{align-items:center;gap:8px;display:flex}.-ozqdq_urlInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;height:32px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:0 10px;font-size:13px;line-height:1.5}.-ozqdq_urlInput::placeholder{color:var(--dsw-alias-label-dimmed)}.-ozqdq_urlInput:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.-ozqdq_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.-ozqdq_enabledBadge{white-space:nowrap;background:var(--dsw-alias-state-success-tertiary);color:var(--dsw-alias-state-success-primary);border-radius:999px;align-items:center;gap:4px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;display:inline-flex}.-ozqdq_btn{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;white-space:nowrap;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;transition:background-color .15s,border-color .15s,color .15s;display:inline-flex}.-ozqdq_btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.-ozqdq_btn:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active)}.-ozqdq_btn:disabled{opacity:.45;cursor:default}.-ozqdq_btnPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.-ozqdq_btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.-ozqdq_btnDanger{color:var(--dsw-alias-label-error)}.-ozqdq_btnDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.-ozqdq_sliderRow{align-items:center;gap:10px;display:flex}.-ozqdq_sliderLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_slider{-webkit-appearance:none;appearance:none;cursor:pointer;background:0 0;flex:1;min-width:0;height:18px;margin:0}.-ozqdq_slider::-webkit-slider-runnable-track{background:linear-gradient(90deg, var(--dsw-alias-brand-primary) var(--bg-fill,0%), var(--dsw-alias-border-l3) var(--bg-fill,0%));border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-track{background:var(--dsw-alias-border-l3);border-radius:2px;height:4px}.-ozqdq_slider::-moz-range-progress{background:var(--dsw-alias-brand-primary);border-radius:2px;height:4px}.-ozqdq_slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%;margin-top:-5px}.-ozqdq_slider::-moz-range-thumb{border:2px solid var(--dsw-alias-brand-primary);width:14px;height:14px;box-shadow:0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent), 0 1px 3px #00000047;background:#fff;border-radius:50%}.-ozqdq_slider:hover::-webkit-slider-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:hover::-moz-range-thumb{box-shadow:0 0 0 5px color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent), 0 1px 4px #0000004d}.-ozqdq_slider:active::-webkit-slider-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:active::-moz-range-thumb{box-shadow:0 0 0 7px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent), 0 1px 5px #00000052;transform:scale(1.12)}.-ozqdq_slider:focus-visible{outline:none}.-ozqdq_slider:focus-visible::-webkit-slider-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_slider:focus-visible::-moz-range-thumb{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_sliderValue{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);flex:0 0 52px;font-size:13px;line-height:1.5}.-ozqdq_segRow{align-items:center;gap:10px;display:flex}.-ozqdq_segLabel{color:var(--dsw-alias-label-primary);flex:0 0 96px;font-size:13px;line-height:1.5}.-ozqdq_segGroup{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:9px;flex:1;gap:2px;min-width:0;padding:2px;display:inline-flex}.-ozqdq_segBtn{height:24px;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:7px;flex:1;padding:0 16px;font-size:12px;line-height:24px}.-ozqdq_segBtn:hover:not(.-ozqdq_segActive){color:var(--dsw-alias-label-primary)}.-ozqdq_segActive{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-weight:500}.-ozqdq_error{color:var(--dsw-alias-label-error);font-size:12px;line-height:1.5}.-ozqdq_clearRow{align-items:center;gap:10px;display:flex}.-ozqdq_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:11px;flex:none;width:40px;height:22px;padding:0;transition:background-color .15s;position:relative}.-ozqdq_switch[aria-checked=true]{background:var(--dsw-alias-brand-primary)}.-ozqdq_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.-ozqdq_switchKnob{background:#fff;border-radius:50%;width:16px;height:16px;transition:transform .15s;position:absolute;top:3px;left:3px;box-shadow:0 1px 3px #00000047}.-ozqdq_switch[aria-checked=true] .-ozqdq_switchKnob{transform:translate(18px)}@media (prefers-reduced-motion:reduce){.-ozqdq_btn,.-ozqdq_switch,.-ozqdq_switchKnob{transition:none}}";
|
|
1153
1250
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
1154
1251
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1155
1252
|
const tag = document.createElement("style");
|
|
@@ -1159,44 +1256,46 @@ window.__ModuleLoader__.load({
|
|
|
1159
1256
|
document.head.appendChild(tag);
|
|
1160
1257
|
}
|
|
1161
1258
|
var SettingsRow_module_css_default = {
|
|
1162
|
-
"
|
|
1163
|
-
"
|
|
1259
|
+
"segBtn": "-ozqdq_segBtn",
|
|
1260
|
+
"noticeBtn": "-ozqdq_noticeBtn",
|
|
1261
|
+
"preview": "-ozqdq_preview",
|
|
1262
|
+
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1263
|
+
"clearRow": "-ozqdq_clearRow",
|
|
1164
1264
|
"previewGlass": "-ozqdq_previewGlass",
|
|
1165
|
-
"
|
|
1166
|
-
"
|
|
1167
|
-
"
|
|
1265
|
+
"segGroup": "-ozqdq_segGroup",
|
|
1266
|
+
"noticeText": "-ozqdq_noticeText",
|
|
1267
|
+
"segActive": "-ozqdq_segActive",
|
|
1268
|
+
"switch": "-ozqdq_switch",
|
|
1269
|
+
"btn": "-ozqdq_btn",
|
|
1168
1270
|
"desc": "-ozqdq_desc",
|
|
1169
|
-
"
|
|
1170
|
-
"
|
|
1271
|
+
"row": "-ozqdq_row",
|
|
1272
|
+
"ratioFrame": "-ozqdq_ratioFrame",
|
|
1273
|
+
"urlRow": "-ozqdq_urlRow",
|
|
1274
|
+
"urlInput": "-ozqdq_urlInput",
|
|
1171
1275
|
"titleRow": "-ozqdq_titleRow",
|
|
1172
|
-
"
|
|
1276
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1277
|
+
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1173
1278
|
"section": "-ozqdq_section",
|
|
1174
|
-
"
|
|
1175
|
-
"
|
|
1176
|
-
"
|
|
1177
|
-
"
|
|
1178
|
-
"
|
|
1179
|
-
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1279
|
+
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1280
|
+
"segRow": "-ozqdq_segRow",
|
|
1281
|
+
"notice": "-ozqdq_notice",
|
|
1282
|
+
"sourceRow": "-ozqdq_sourceRow",
|
|
1283
|
+
"slider": "-ozqdq_slider",
|
|
1180
1284
|
"sliderValue": "-ozqdq_sliderValue",
|
|
1181
|
-
"header": "-ozqdq_header",
|
|
1182
1285
|
"error": "-ozqdq_error",
|
|
1183
|
-
"urlInput": "-ozqdq_urlInput",
|
|
1184
1286
|
"hint": "-ozqdq_hint",
|
|
1287
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1185
1288
|
"btnDanger": "-ozqdq_btnDanger",
|
|
1186
|
-
"
|
|
1289
|
+
"sliderRow": "-ozqdq_sliderRow",
|
|
1187
1290
|
"previewImg": "-ozqdq_previewImg",
|
|
1188
|
-
"
|
|
1189
|
-
"
|
|
1190
|
-
"
|
|
1191
|
-
"
|
|
1192
|
-
"
|
|
1193
|
-
"
|
|
1194
|
-
"
|
|
1195
|
-
"
|
|
1196
|
-
"notice": "-ozqdq_notice",
|
|
1197
|
-
"segBtn": "-ozqdq_segBtn",
|
|
1198
|
-
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1199
|
-
"slider": "-ozqdq_slider"
|
|
1291
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
1292
|
+
"previewReset": "-ozqdq_previewReset",
|
|
1293
|
+
"title": "-ozqdq_title",
|
|
1294
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
1295
|
+
"header": "-ozqdq_header",
|
|
1296
|
+
"previewScrim": "-ozqdq_previewScrim",
|
|
1297
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1298
|
+
"segLabel": "-ozqdq_segLabel"
|
|
1200
1299
|
};
|
|
1201
1300
|
//#endregion
|
|
1202
1301
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1229,6 +1328,8 @@ window.__ModuleLoader__.load({
|
|
|
1229
1328
|
blur: 16,
|
|
1230
1329
|
wallpaperBlur: 0,
|
|
1231
1330
|
fit: DEFAULT_FIT,
|
|
1331
|
+
offsetX: DEFAULT_OFFSET,
|
|
1332
|
+
offsetY: DEFAULT_OFFSET,
|
|
1232
1333
|
timeline: true
|
|
1233
1334
|
};
|
|
1234
1335
|
/** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so
|
|
@@ -1271,13 +1372,26 @@ window.__ModuleLoader__.load({
|
|
|
1271
1372
|
const prefix = `${BACKGROUND_API_PREFIX}/image/`;
|
|
1272
1373
|
return url.startsWith(prefix) ? url.slice(prefix.length) : "";
|
|
1273
1374
|
}
|
|
1375
|
+
/** Cap on the preview card's height. Past it the card shrinks as a whole —
|
|
1376
|
+
* width follows the ratio — and centers, so a very tall (or very wide) window
|
|
1377
|
+
* keeps the exact viewport ratio instead of squashing the crop. */
|
|
1378
|
+
const PREVIEW_MAX_HEIGHT = 360;
|
|
1379
|
+
/** Clamp a framing offset into its schema domain, trimming the float dust the
|
|
1380
|
+
* pointer-delta division leaves behind (0.30000000000000004 → 0.3). */
|
|
1381
|
+
function clampOffset(value) {
|
|
1382
|
+
return Math.round((value < 0 ? 0 : value > 1 ? 1 : value) * 1e4) / 1e4;
|
|
1383
|
+
}
|
|
1384
|
+
/** Round ratio dust out of an inline px box (400 / (4/3) is 300.00000000000006). */
|
|
1385
|
+
function trimPx(value) {
|
|
1386
|
+
return Math.round(value * 100) / 100;
|
|
1387
|
+
}
|
|
1274
1388
|
/** Read the settings store (uSES-friendly). */
|
|
1275
1389
|
function readSnapshot() {
|
|
1276
1390
|
return settingsClient.getSnapshot();
|
|
1277
1391
|
}
|
|
1278
1392
|
/** Field-wise equality for distinguishing our own saves from external changes. */
|
|
1279
1393
|
function sameSettings(a, b) {
|
|
1280
|
-
return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit && a.timeline === b.timeline;
|
|
1394
|
+
return a.enabled === b.enabled && a.uploadId === b.uploadId && a.url === b.url && a.opacity === b.opacity && a.scrim === b.scrim && a.panelOpacity === b.panelOpacity && a.blur === b.blur && a.wallpaperBlur === b.wallpaperBlur && a.fit === b.fit && a.offsetX === b.offsetX && a.offsetY === b.offsetY && a.timeline === b.timeline;
|
|
1281
1395
|
}
|
|
1282
1396
|
/**
|
|
1283
1397
|
* A stepped range control. While dragging it re-renders only itself: the value
|
|
@@ -1371,6 +1485,14 @@ window.__ModuleLoader__.load({
|
|
|
1371
1485
|
const [noticeDismissed, setNoticeDismissed] = (0, react.useState)(false);
|
|
1372
1486
|
const fileInput = (0, react.useRef)(null);
|
|
1373
1487
|
const previewRef = (0, react.useRef)(null);
|
|
1488
|
+
const frameRef = (0, react.useRef)(null);
|
|
1489
|
+
const [viewport, setViewport] = (0, react.useState)(() => ({
|
|
1490
|
+
w: window.innerWidth,
|
|
1491
|
+
h: window.innerHeight
|
|
1492
|
+
}));
|
|
1493
|
+
const [frameWidth, setFrameWidth] = (0, react.useState)(0);
|
|
1494
|
+
const [imgMeta, setImgMeta] = (0, react.useState)(null);
|
|
1495
|
+
const panRef = (0, react.useRef)(null);
|
|
1374
1496
|
const draftRef = (0, react.useRef)(DEFAULTS);
|
|
1375
1497
|
const editingRef = (0, react.useRef)(false);
|
|
1376
1498
|
const lastSavedRef = (0, react.useRef)(null);
|
|
@@ -1428,6 +1550,36 @@ window.__ModuleLoader__.load({
|
|
|
1428
1550
|
saveNowRef.current(draftRef.current);
|
|
1429
1551
|
}, 120);
|
|
1430
1552
|
}, []);
|
|
1553
|
+
(0, react.useEffect)(() => {
|
|
1554
|
+
let raf = 0;
|
|
1555
|
+
const sync = () => {
|
|
1556
|
+
raf = 0;
|
|
1557
|
+
setViewport({
|
|
1558
|
+
w: window.innerWidth,
|
|
1559
|
+
h: window.innerHeight
|
|
1560
|
+
});
|
|
1561
|
+
};
|
|
1562
|
+
const onResize = () => {
|
|
1563
|
+
if (raf !== 0) return;
|
|
1564
|
+
raf = window.requestAnimationFrame(sync);
|
|
1565
|
+
};
|
|
1566
|
+
window.addEventListener("resize", onResize);
|
|
1567
|
+
return () => {
|
|
1568
|
+
window.removeEventListener("resize", onResize);
|
|
1569
|
+
if (raf !== 0) window.cancelAnimationFrame(raf);
|
|
1570
|
+
};
|
|
1571
|
+
}, []);
|
|
1572
|
+
(0, react.useEffect)(() => {
|
|
1573
|
+
const el = frameRef.current;
|
|
1574
|
+
if (el === null) return;
|
|
1575
|
+
if (typeof ResizeObserver === "undefined") {
|
|
1576
|
+
setFrameWidth(el.clientWidth);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
const observer = new ResizeObserver(() => setFrameWidth(el.clientWidth));
|
|
1580
|
+
observer.observe(el);
|
|
1581
|
+
return () => observer.disconnect();
|
|
1582
|
+
}, []);
|
|
1431
1583
|
(0, react.useEffect)(() => {
|
|
1432
1584
|
if (snapshot.status !== "ready" || snapshot.value === void 0) return;
|
|
1433
1585
|
const saved = lastSavedRef.current;
|
|
@@ -1528,8 +1680,109 @@ window.__ModuleLoader__.load({
|
|
|
1528
1680
|
setDraft(next);
|
|
1529
1681
|
saveNow(next);
|
|
1530
1682
|
};
|
|
1683
|
+
/** Natural aspect ratio of the loaded source (null until loaded / on error). */
|
|
1684
|
+
const imageAspect = imgMeta !== null && imgMeta.w > 0 && imgMeta.h > 0 ? imgMeta.w / imgMeta.h : null;
|
|
1685
|
+
/** Preview card box at the live viewport ratio, capped at PREVIEW_MAX_HEIGHT
|
|
1686
|
+
* (past the cap the card shrinks as a whole and the frame centers it); a
|
|
1687
|
+
* width of 0 means "not measured yet" (CSS fallbacks apply). */
|
|
1688
|
+
const cardRatio = viewport.w > 0 && viewport.h > 0 ? viewport.w / viewport.h : 16 / 9;
|
|
1689
|
+
const cardWidth = frameWidth > 0 ? trimPx(Math.min(frameWidth, PREVIEW_MAX_HEIGHT * cardRatio)) : 0;
|
|
1690
|
+
const cardHeight = cardWidth > 0 ? trimPx(cardWidth / cardRatio) : 0;
|
|
1691
|
+
/** The axis the cover crop has slack on — and therefore the only one the
|
|
1692
|
+
* user may pan. A landscape source in a portrait frame overflows sideways
|
|
1693
|
+
* (pan x); a portrait source in a landscape frame overflows vertically
|
|
1694
|
+
* (pan y). null for `contain`, an unloaded source, an unmeasured card or an
|
|
1695
|
+
* exact ratio match (nothing to pan, and no division by zero). */
|
|
1696
|
+
const panAxis = draft.fit === "cover" && imageAspect !== null && cardWidth > 0 && Math.abs(imageAspect - cardRatio) >= 1e-4 ? imageAspect > cardRatio ? "x" : "y" : null;
|
|
1697
|
+
/** Whether a pan has moved the crop off center (drives the recenter chip). */
|
|
1698
|
+
const offsetMoved = draft.offsetX !== .5 || draft.offsetY !== .5;
|
|
1699
|
+
/** Overflow (px) of the rendered image past the card on one axis, measured
|
|
1700
|
+
* from the live DOM (the same frame the wallpaper layer uses), or null when
|
|
1701
|
+
* that axis has nothing to pan. */
|
|
1702
|
+
const overflowFor = (axis) => {
|
|
1703
|
+
const el = previewRef.current;
|
|
1704
|
+
if (el === null || imgMeta === null) return null;
|
|
1705
|
+
const fw = el.clientWidth;
|
|
1706
|
+
const fh = el.clientHeight;
|
|
1707
|
+
if (fw <= 0 || fh <= 0 || imgMeta.w <= 0 || imgMeta.h <= 0) return null;
|
|
1708
|
+
const scale = Math.max(fw / imgMeta.w, fh / imgMeta.h);
|
|
1709
|
+
const overflow = axis === "x" ? imgMeta.w * scale - fw : imgMeta.h * scale - fh;
|
|
1710
|
+
return overflow > 0 ? overflow : null;
|
|
1711
|
+
};
|
|
1712
|
+
/** Write one framing offset live: CSS variables only, no React state — the
|
|
1713
|
+
* pan hot path mirrors ControlSlider's onLive. */
|
|
1714
|
+
const setOffsetLive = (key, value) => {
|
|
1715
|
+
const next = {
|
|
1716
|
+
...draftRef.current,
|
|
1717
|
+
[key]: value
|
|
1718
|
+
};
|
|
1719
|
+
draftRef.current = next;
|
|
1720
|
+
paintBackgroundKnob(key, value);
|
|
1721
|
+
if (previewRef.current) paintPreviewSurface(previewRef.current, next);
|
|
1722
|
+
};
|
|
1723
|
+
const handlePanDown = (e) => {
|
|
1724
|
+
if (panAxis === null) return;
|
|
1725
|
+
const overflow = overflowFor(panAxis);
|
|
1726
|
+
if (overflow === null) return;
|
|
1727
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1728
|
+
e.preventDefault();
|
|
1729
|
+
markEditing();
|
|
1730
|
+
panRef.current = {
|
|
1731
|
+
axis: panAxis,
|
|
1732
|
+
startPos: panAxis === "x" ? e.clientX : e.clientY,
|
|
1733
|
+
startOffset: panAxis === "x" ? draftRef.current.offsetX : draftRef.current.offsetY,
|
|
1734
|
+
overflow
|
|
1735
|
+
};
|
|
1736
|
+
};
|
|
1737
|
+
const handlePanMove = (e) => {
|
|
1738
|
+
const pan = panRef.current;
|
|
1739
|
+
if (pan === null) return;
|
|
1740
|
+
const delta = (pan.axis === "x" ? e.clientX : e.clientY) - pan.startPos;
|
|
1741
|
+
setOffsetLive(pan.axis === "x" ? "offsetX" : "offsetY", clampOffset(pan.startOffset - delta / pan.overflow));
|
|
1742
|
+
};
|
|
1743
|
+
const handlePanUp = (e) => {
|
|
1744
|
+
if (panRef.current === null) return;
|
|
1745
|
+
panRef.current = null;
|
|
1746
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
|
|
1747
|
+
setDraft({ ...draftRef.current });
|
|
1748
|
+
onCommit();
|
|
1749
|
+
};
|
|
1750
|
+
/** Arrow-key pan on the movable axis (Shift = coarse step). */
|
|
1751
|
+
const nudgeOffset = (axis, direction, coarse) => {
|
|
1752
|
+
const key = axis === "x" ? "offsetX" : "offsetY";
|
|
1753
|
+
setOffsetLive(key, clampOffset(draftRef.current[key] + direction * (coarse ? .1 : .02)));
|
|
1754
|
+
setDraft({ ...draftRef.current });
|
|
1755
|
+
onCommit();
|
|
1756
|
+
};
|
|
1757
|
+
const handlePanKeyDown = (e) => {
|
|
1758
|
+
if (panAxis === null) return;
|
|
1759
|
+
if (panAxis === "x" && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
|
|
1760
|
+
e.preventDefault();
|
|
1761
|
+
nudgeOffset("x", e.key === "ArrowLeft" ? -1 : 1, e.shiftKey);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (panAxis === "y" && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
|
|
1765
|
+
e.preventDefault();
|
|
1766
|
+
nudgeOffset("y", e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
/** Recenter the crop on both axes (recenter chip / double-click). */
|
|
1770
|
+
const handleResetOffset = () => {
|
|
1771
|
+
const next = {
|
|
1772
|
+
...draftRef.current,
|
|
1773
|
+
offsetX: DEFAULT_OFFSET,
|
|
1774
|
+
offsetY: DEFAULT_OFFSET
|
|
1775
|
+
};
|
|
1776
|
+
draftRef.current = next;
|
|
1777
|
+
setDraft(next);
|
|
1778
|
+
paintLive(next);
|
|
1779
|
+
saveNow(next);
|
|
1780
|
+
};
|
|
1531
1781
|
const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url;
|
|
1532
1782
|
const hasImage = sourceUrl !== "";
|
|
1783
|
+
(0, react.useEffect)(() => {
|
|
1784
|
+
setImgMeta(null);
|
|
1785
|
+
}, [sourceUrl]);
|
|
1533
1786
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1534
1787
|
className: SettingsRow_module_css_default.row,
|
|
1535
1788
|
children: [
|
|
@@ -1565,27 +1818,55 @@ window.__ModuleLoader__.load({
|
|
|
1565
1818
|
})]
|
|
1566
1819
|
}),
|
|
1567
1820
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1568
|
-
className: SettingsRow_module_css_default.
|
|
1569
|
-
ref:
|
|
1570
|
-
children:
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1821
|
+
className: SettingsRow_module_css_default.ratioFrame,
|
|
1822
|
+
ref: frameRef,
|
|
1823
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1824
|
+
className: SettingsRow_module_css_default.preview,
|
|
1825
|
+
ref: previewRef,
|
|
1826
|
+
"data-pan": panAxis ?? void 0,
|
|
1827
|
+
style: cardHeight > 0 ? {
|
|
1828
|
+
width: `${cardWidth}px`,
|
|
1829
|
+
height: `${cardHeight}px`
|
|
1830
|
+
} : void 0,
|
|
1831
|
+
tabIndex: panAxis === null ? void 0 : 0,
|
|
1832
|
+
"aria-label": panAxis === null ? void 0 : t("background.previewPanHint"),
|
|
1833
|
+
onPointerDown: handlePanDown,
|
|
1834
|
+
onPointerMove: handlePanMove,
|
|
1835
|
+
onPointerUp: handlePanUp,
|
|
1836
|
+
onPointerCancel: handlePanUp,
|
|
1837
|
+
onKeyDown: handlePanKeyDown,
|
|
1838
|
+
onDoubleClick: panAxis === null ? void 0 : handleResetOffset,
|
|
1839
|
+
children: [hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
1841
|
+
className: SettingsRow_module_css_default.previewImg,
|
|
1842
|
+
src: sourceUrl,
|
|
1843
|
+
alt: "",
|
|
1844
|
+
draggable: false,
|
|
1845
|
+
referrerPolicy: "no-referrer",
|
|
1846
|
+
onLoad: (e) => setImgMeta({
|
|
1847
|
+
w: e.currentTarget.naturalWidth,
|
|
1848
|
+
h: e.currentTarget.naturalHeight
|
|
1849
|
+
}),
|
|
1850
|
+
onError: () => setImgMeta(null)
|
|
1851
|
+
}),
|
|
1852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: SettingsRow_module_css_default.previewScrim }),
|
|
1853
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1854
|
+
className: SettingsRow_module_css_default.previewGlass,
|
|
1855
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1856
|
+
className: SettingsRow_module_css_default.previewGlassText,
|
|
1857
|
+
children: t("background.previewGlass")
|
|
1858
|
+
})
|
|
1584
1859
|
})
|
|
1585
|
-
})
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1860
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1861
|
+
className: SettingsRow_module_css_default.previewEmpty,
|
|
1862
|
+
children: t("background.noPreview")
|
|
1863
|
+
}), panAxis !== null && offsetMoved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1864
|
+
type: "button",
|
|
1865
|
+
className: SettingsRow_module_css_default.previewReset,
|
|
1866
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
1867
|
+
onClick: handleResetOffset,
|
|
1868
|
+
children: t("background.repositionReset")
|
|
1869
|
+
})]
|
|
1589
1870
|
})
|
|
1590
1871
|
}),
|
|
1591
1872
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|