deepseek-harness-background 0.5.5 → 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 +7 -5
- package/README.zh.md +7 -5
- package/icon.svg +8 -0
- package/lib/client.js +388 -73
- 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
|
@@ -62,8 +62,9 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
* button, scroll-to-bottom), the load-earlier history button, the composer
|
|
63
63
|
* dock family (agent task strips: TodoPanel / GoalBar / QueueDock) and its
|
|
64
64
|
* takeover panels (approval, question, plan review), the subagent lineage
|
|
65
|
-
* popover, the home hero "preview" badge, the sidebar build badge,
|
|
66
|
-
*
|
|
65
|
+
* popover, the home hero "preview" badge, the sidebar build badge, the turn
|
|
66
|
+
* deliverables (the changed-files card and the diff hover preview it opens),
|
|
67
|
+
* and the timeline rail (the official-rail rules below).
|
|
67
68
|
* Every glassed surface shares ONE recipe:
|
|
68
69
|
* fill from the painter's --dsw-specific-input-major token (same alpha
|
|
69
70
|
* curve + theme dimming), the full shared blur/saturate/brightness chain
|
|
@@ -71,10 +72,14 @@ window.__ModuleLoader__.load({
|
|
|
71
72
|
* surface reads heavier than the composer card. Dialogs, the settings UI,
|
|
72
73
|
* menus, tooltips, toasts and every hover/accent fill keep the OFFICIAL
|
|
73
74
|
* opaque paints — they are reading surfaces and must stay legible.
|
|
75
|
+
* The same rule excludes the HoverCard primitive's compact pod (a fixed
|
|
76
|
+
* #2C2C2E chip): it is hover FEEDBACK, not a floating card, and the official
|
|
77
|
+
* opaque paint is what keeps its label legible over any wallpaper.
|
|
74
78
|
*
|
|
75
79
|
* Selectors target authored attributes (`data-dsh-bg`, `data-composer-card`,
|
|
76
|
-
* `data-terminal`…), the `:global`
|
|
77
|
-
* SUFFIX conventions (`_bubble`,
|
|
80
|
+
* `data-terminal`, `data-changes-hover-preview`…), the `:global`
|
|
81
|
+
* `.md-code-block` anchor, and CSS-module SUFFIX conventions (`_bubble`,
|
|
82
|
+
* `_newSession`…) audited for collisions.
|
|
78
83
|
*
|
|
79
84
|
* MAINTENANCE NOTE: the suffix anchors are the host's only stable-enough
|
|
80
85
|
* integration surface today, but a host restyle that renames them silently
|
|
@@ -83,11 +88,14 @@ window.__ModuleLoader__.load({
|
|
|
83
88
|
* suffix entries.
|
|
84
89
|
* `_bubble` excludes `role="tooltip"` (Tooltip.module.css shares the suffix),
|
|
85
90
|
* `_add` is scoped under `[data-composer-card]` (DiffBlock line markers share
|
|
86
|
-
* it), `_newSession`
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
+
* it), `_newSession` is scoped to the `button` element — an element test, not
|
|
92
|
+
* another suffix exclusion, because 0.1.7-rc.2 added `newSessionLabelMask` /
|
|
93
|
+
* `newSessionContent` / `newSessionShortcut` INSIDE the button and each of
|
|
94
|
+
* those spans would otherwise take the chrome paint (the composer-sized
|
|
95
|
+
* second box the user sees inside the control) — and `_toBottom` excludes
|
|
96
|
+
* `Slot` (the zero-height sticky wrapper) — both share the substring with the
|
|
97
|
+
* chrome control but must keep their own paint, and the subagent popover is
|
|
98
|
+
* `role="tree"` + `_menu` so the generic menu surface stays untouched.
|
|
91
99
|
*/
|
|
92
100
|
const BACKGROUND_CSS = `
|
|
93
101
|
.dsh-bg-layer {
|
|
@@ -96,6 +104,11 @@ window.__ModuleLoader__.load({
|
|
|
96
104
|
.dsh-bg-layer .dsh-bg-image {
|
|
97
105
|
width: 100%; height: 100%; display: block; border: 0; background: transparent;
|
|
98
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%);
|
|
99
112
|
opacity: var(--bg-opacity, 1);
|
|
100
113
|
filter: blur(var(--bg-wallpaper-blur, 0px));
|
|
101
114
|
/* Blur thins the edges; scale the image up to hide the fringe. */
|
|
@@ -112,6 +125,21 @@ window.__ModuleLoader__.load({
|
|
|
112
125
|
body[data-ds-dark-theme] .dsh-bg-scrim {
|
|
113
126
|
background: rgba(0, 0, 0, var(--bg-scrim, 0.25));
|
|
114
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
|
+
}
|
|
115
143
|
|
|
116
144
|
/* Active: make the app frame + sidebar background transparent so all columns
|
|
117
145
|
share the same wallpaper + scrim backdrop. */
|
|
@@ -128,7 +156,6 @@ window.__ModuleLoader__.load({
|
|
|
128
156
|
light glass slightly DIMS, dark glass keeps a slight lift. Gated on the
|
|
129
157
|
GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel
|
|
130
158
|
slider returns these surfaces to the exact official paints. */
|
|
131
|
-
body[data-dsh-bg-glass] [data-composer-card],
|
|
132
159
|
body[data-dsh-bg-glass] [class*="_bubble"]:not([role="tooltip"]),
|
|
133
160
|
/* The read_image toolview (dsh >= 0.1.2-rc.1 ToolRow) collapses its result
|
|
134
161
|
into an image body card; glass it like the other content blocks. */
|
|
@@ -141,7 +168,7 @@ window.__ModuleLoader__.load({
|
|
|
141
168
|
body[data-dsh-bg-glass] [data-web],
|
|
142
169
|
body[data-dsh-bg-glass] [class*="_ioCard"],
|
|
143
170
|
body[data-dsh-bg-glass] [class*="_instructionsCard"],
|
|
144
|
-
body[data-dsh-bg-glass] [class*="_markdown"]
|
|
171
|
+
body[data-dsh-bg-glass] [class*="_markdown"]:not(pre) > code {
|
|
145
172
|
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));
|
|
146
173
|
-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);
|
|
147
174
|
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
@@ -152,6 +179,37 @@ window.__ModuleLoader__.load({
|
|
|
152
179
|
0 12px 40px rgba(0, 0, 0, 0.12);
|
|
153
180
|
}
|
|
154
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
|
+
|
|
155
213
|
/* The sticky code-block header officially occludes scrolled code with
|
|
156
214
|
bg-base (transparent under a wallpaper): refill it with the banner token
|
|
157
215
|
(itself translucent glass) so code stays hidden beneath the sticky bar. */
|
|
@@ -178,7 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
178
236
|
caps), sheen = the shared sheen vars. Hover = the same white glass at the
|
|
179
237
|
boosted alpha var. Gated on data-dsh-bg-glass (off when the panel is
|
|
180
238
|
fully opaque). */
|
|
181
|
-
body[data-dsh-bg-glass] [class*="_newSession"]
|
|
239
|
+
body[data-dsh-bg-glass] button[class*="_newSession"],
|
|
182
240
|
body[data-dsh-bg-glass] [data-composer-card] [class*="_add"],
|
|
183
241
|
body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]) {
|
|
184
242
|
background-color: var(--dsw-specific-input-major);
|
|
@@ -190,15 +248,15 @@ window.__ModuleLoader__.load({
|
|
|
190
248
|
inset 0 0 0 0.5px rgba(255, 255, 255, 0.12),
|
|
191
249
|
0 4px 14px rgba(0, 0, 0, 0.08);
|
|
192
250
|
}
|
|
193
|
-
body[data-dsh-bg-glass] [class*="_newSession"]:
|
|
251
|
+
body[data-dsh-bg-glass] button[class*="_newSession"]:hover,
|
|
194
252
|
body[data-dsh-bg-glass] [data-composer-card] [class*="_add"]:hover:not(:disabled),
|
|
195
253
|
body[data-dsh-bg-glass] [class*="_toBottom"]:not([class*="Slot"]):hover {
|
|
196
254
|
background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
|
|
197
255
|
}
|
|
198
256
|
/* Collapsed rail renders the new-session control as a bare icon — keep the
|
|
199
257
|
official transparent paint (and its official translucent hover). */
|
|
200
|
-
body[data-dsh-bg-glass] [
|
|
201
|
-
body[data-dsh-bg-glass] [
|
|
258
|
+
body[data-dsh-bg-glass] [data-sidebar-collapsed="true"] [class*="_newSession"],
|
|
259
|
+
body[data-dsh-bg-glass] [data-sidebar-collapsed="true"] [class*="_newSession"]:hover {
|
|
202
260
|
background-color: transparent;
|
|
203
261
|
background-image: none;
|
|
204
262
|
box-shadow: none;
|
|
@@ -278,19 +336,74 @@ window.__ModuleLoader__.load({
|
|
|
278
336
|
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
279
337
|
}
|
|
280
338
|
|
|
339
|
+
/* ---- Turn deliverables: the changed-files card + its hover preview -----
|
|
340
|
+
Both paint with the official opaque --dsw-alias-bg-layer-1 (the token the
|
|
341
|
+
painter deliberately keeps opaque, because it backs the settings UI,
|
|
342
|
+
dialogs and menus), so each needs an EXPLICIT fill on top of the shared
|
|
343
|
+
sheen/blur chain — the token-mediated rule above cannot reach them.
|
|
344
|
+
[data-changed-files] is the card a turn tail renders; its header and
|
|
345
|
+
rows carry their own fills, so the card surface alone is glassed and the
|
|
346
|
+
contents keep their official paints. [data-changes-hover-preview] is
|
|
347
|
+
the fixed HoverCard the changed-files rows open on hover and the shared
|
|
348
|
+
component behind every diff preview; its width/height are set by the
|
|
349
|
+
hover-card primitive, so this only supplies the material. The preview's
|
|
350
|
+
official elevation shadow rides its own token untouched — only the fill
|
|
351
|
+
and the filter chain are taken over here. */
|
|
352
|
+
body[data-dsh-bg-glass] [data-changed-files],
|
|
353
|
+
body[data-dsh-bg-glass] [data-changes-hover-preview] {
|
|
354
|
+
background-color: var(--dsw-specific-input-major);
|
|
355
|
+
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));
|
|
356
|
+
-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);
|
|
357
|
+
backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
|
|
358
|
+
box-shadow: var(--dsw-elevation-panel, 0 4px 14px rgba(0, 0, 0, 0.08));
|
|
359
|
+
}
|
|
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
|
+
|
|
281
390
|
/* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------
|
|
282
391
|
The enhance mode borrows the kernel's rail untouched, so its hover
|
|
283
392
|
preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated
|
|
284
393
|
by any overridden token) — the "悬浮反馈形态不透明" complaint. Glass it
|
|
285
394
|
explicitly with the same explicit-fill recipe as the chrome buttons
|
|
286
|
-
(fill = the painter's composer token + shared blur/sheen).
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
395
|
+
(fill = the painter's composer token + shared blur/sheen). The anchor is
|
|
396
|
+
the rail's SEAT, the same one timeline/official-enhance.tsx identifies it
|
|
397
|
+
by: the only nav inside the conversation scrollport. 0.1.7 virtualized
|
|
398
|
+
the rail (commit f89cd786aa), which deleted the turn-natural-height
|
|
399
|
+
inline metric that used to sit on that nav — the nav carries no style
|
|
400
|
+
attribute at all now — so the selector that keyed on it matched nothing
|
|
401
|
+
and the preview went bare. The scrollport attribute and the preview's
|
|
402
|
+
role="tooltip" are the structural hooks; class hashes never are. No edge
|
|
290
403
|
dissolve on the marks column: the first and last tick dashes straddle
|
|
291
404
|
the marks box edges by 1px, so a fade zone sized off the box height
|
|
292
405
|
swallows them instead of the band. */
|
|
293
|
-
body[data-dsh-bg-glass]
|
|
406
|
+
body[data-dsh-bg-glass] [data-conversation-scroll] nav [role="tooltip"] {
|
|
294
407
|
background-color: var(--dsw-specific-input-major);
|
|
295
408
|
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));
|
|
296
409
|
-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);
|
|
@@ -321,6 +434,8 @@ window.__ModuleLoader__.load({
|
|
|
321
434
|
const DEFAULT_FIT = "cover";
|
|
322
435
|
/** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */
|
|
323
436
|
const FIT_MODES = ["cover", "contain"];
|
|
437
|
+
/** Default framing offset — the image sits centered on both axes. */
|
|
438
|
+
const DEFAULT_OFFSET = .5;
|
|
324
439
|
//#endregion
|
|
325
440
|
//#region src/client/backdrop.ts
|
|
326
441
|
/**
|
|
@@ -525,6 +640,8 @@ window.__ModuleLoader__.load({
|
|
|
525
640
|
this.hasSource = true;
|
|
526
641
|
document.body.style;
|
|
527
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));
|
|
528
645
|
this.setVar("--bg-opacity", String(settings.opacity));
|
|
529
646
|
this.setVar("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
|
|
530
647
|
this.setVar("--bg-wallpaper-scale", (1 + settings.wallpaperBlur * .006).toFixed(4));
|
|
@@ -592,7 +709,13 @@ window.__ModuleLoader__.load({
|
|
|
592
709
|
this.setVar("--bg-wallpaper-blur", `${value}px`);
|
|
593
710
|
this.setVar("--bg-wallpaper-scale", (1 + Number(value) * .006).toFixed(4));
|
|
594
711
|
break;
|
|
595
|
-
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));
|
|
596
719
|
}
|
|
597
720
|
}
|
|
598
721
|
/**
|
|
@@ -689,6 +812,8 @@ window.__ModuleLoader__.load({
|
|
|
689
812
|
"--bg-opacity",
|
|
690
813
|
"--bg-scrim",
|
|
691
814
|
"--bg-object-fit",
|
|
815
|
+
"--bg-offset-x",
|
|
816
|
+
"--bg-offset-y",
|
|
692
817
|
"--bg-wallpaper-blur",
|
|
693
818
|
"--bg-wallpaper-scale",
|
|
694
819
|
"--bg-glass-blur",
|
|
@@ -706,6 +831,8 @@ window.__ModuleLoader__.load({
|
|
|
706
831
|
function paintPreviewSurface(el, settings) {
|
|
707
832
|
const s = el.style;
|
|
708
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));
|
|
709
836
|
s.setProperty("--bg-opacity", String(settings.opacity));
|
|
710
837
|
s.setProperty("--bg-scrim", String(settings.scrim));
|
|
711
838
|
s.setProperty("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
|
|
@@ -951,6 +1078,8 @@ window.__ModuleLoader__.load({
|
|
|
951
1078
|
"background.fit": "填充方式",
|
|
952
1079
|
"background.cover": "铺满",
|
|
953
1080
|
"background.contain": "完整",
|
|
1081
|
+
"background.previewPanHint": "拖动预览可调整图片位置,双击居中",
|
|
1082
|
+
"background.repositionReset": "重置位置",
|
|
954
1083
|
"background.clear": "清除背景",
|
|
955
1084
|
"background.uploadFailed": "上传失败,请重试",
|
|
956
1085
|
"background.saveFailed": "保存失败,请重试",
|
|
@@ -982,6 +1111,8 @@ window.__ModuleLoader__.load({
|
|
|
982
1111
|
"background.fit": "Fit",
|
|
983
1112
|
"background.cover": "Cover",
|
|
984
1113
|
"background.contain": "Contain",
|
|
1114
|
+
"background.previewPanHint": "Drag the preview to reposition the image; double-click to recenter",
|
|
1115
|
+
"background.repositionReset": "Reset position",
|
|
985
1116
|
"background.clear": "Clear background",
|
|
986
1117
|
"background.uploadFailed": "Upload failed, try again",
|
|
987
1118
|
"background.saveFailed": "Save failed, try again",
|
|
@@ -1115,7 +1246,7 @@ window.__ModuleLoader__.load({
|
|
|
1115
1246
|
const settingsClient = new SettingsClient();
|
|
1116
1247
|
//#endregion
|
|
1117
1248
|
//#region \0dsh-css:D:\Project\deepseek-harness-background\src\client\SettingsRow.module.css.mjs
|
|
1118
|
-
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}}";
|
|
1119
1250
|
const tagId = "deepseek-harness-background/SettingsRow.module.css";
|
|
1120
1251
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1121
1252
|
const tag = document.createElement("style");
|
|
@@ -1125,44 +1256,46 @@ window.__ModuleLoader__.load({
|
|
|
1125
1256
|
document.head.appendChild(tag);
|
|
1126
1257
|
}
|
|
1127
1258
|
var SettingsRow_module_css_default = {
|
|
1128
|
-
"
|
|
1129
|
-
"
|
|
1130
|
-
"switchKnob": "-ozqdq_switchKnob",
|
|
1131
|
-
"sliderValue": "-ozqdq_sliderValue",
|
|
1132
|
-
"error": "-ozqdq_error",
|
|
1133
|
-
"urlRow": "-ozqdq_urlRow",
|
|
1134
|
-
"btn": "-ozqdq_btn",
|
|
1135
|
-
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1136
|
-
"slider": "-ozqdq_slider",
|
|
1137
|
-
"clearBtn": "-ozqdq_clearBtn",
|
|
1259
|
+
"segBtn": "-ozqdq_segBtn",
|
|
1260
|
+
"noticeBtn": "-ozqdq_noticeBtn",
|
|
1138
1261
|
"preview": "-ozqdq_preview",
|
|
1262
|
+
"previewEmpty": "-ozqdq_previewEmpty",
|
|
1263
|
+
"clearRow": "-ozqdq_clearRow",
|
|
1264
|
+
"previewGlass": "-ozqdq_previewGlass",
|
|
1265
|
+
"segGroup": "-ozqdq_segGroup",
|
|
1266
|
+
"noticeText": "-ozqdq_noticeText",
|
|
1267
|
+
"segActive": "-ozqdq_segActive",
|
|
1268
|
+
"switch": "-ozqdq_switch",
|
|
1269
|
+
"btn": "-ozqdq_btn",
|
|
1270
|
+
"desc": "-ozqdq_desc",
|
|
1271
|
+
"row": "-ozqdq_row",
|
|
1272
|
+
"ratioFrame": "-ozqdq_ratioFrame",
|
|
1273
|
+
"urlRow": "-ozqdq_urlRow",
|
|
1139
1274
|
"urlInput": "-ozqdq_urlInput",
|
|
1140
|
-
"segRow": "-ozqdq_segRow",
|
|
1141
|
-
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1142
1275
|
"titleRow": "-ozqdq_titleRow",
|
|
1276
|
+
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1143
1277
|
"sectionLabel": "-ozqdq_sectionLabel",
|
|
1278
|
+
"section": "-ozqdq_section",
|
|
1279
|
+
"previewGlassText": "-ozqdq_previewGlassText",
|
|
1280
|
+
"segRow": "-ozqdq_segRow",
|
|
1281
|
+
"notice": "-ozqdq_notice",
|
|
1144
1282
|
"sourceRow": "-ozqdq_sourceRow",
|
|
1145
|
-
"
|
|
1146
|
-
"
|
|
1147
|
-
"
|
|
1148
|
-
"
|
|
1149
|
-
"
|
|
1283
|
+
"slider": "-ozqdq_slider",
|
|
1284
|
+
"sliderValue": "-ozqdq_sliderValue",
|
|
1285
|
+
"error": "-ozqdq_error",
|
|
1286
|
+
"hint": "-ozqdq_hint",
|
|
1287
|
+
"enabledBadge": "-ozqdq_enabledBadge",
|
|
1150
1288
|
"btnDanger": "-ozqdq_btnDanger",
|
|
1151
|
-
"
|
|
1152
|
-
"
|
|
1153
|
-
"
|
|
1154
|
-
"
|
|
1289
|
+
"sliderRow": "-ozqdq_sliderRow",
|
|
1290
|
+
"previewImg": "-ozqdq_previewImg",
|
|
1291
|
+
"switchKnob": "-ozqdq_switchKnob",
|
|
1292
|
+
"previewReset": "-ozqdq_previewReset",
|
|
1293
|
+
"title": "-ozqdq_title",
|
|
1294
|
+
"clearBtn": "-ozqdq_clearBtn",
|
|
1155
1295
|
"header": "-ozqdq_header",
|
|
1156
|
-
"section": "-ozqdq_section",
|
|
1157
1296
|
"previewScrim": "-ozqdq_previewScrim",
|
|
1158
|
-
"
|
|
1159
|
-
"
|
|
1160
|
-
"sliderRow": "-ozqdq_sliderRow",
|
|
1161
|
-
"segGroup": "-ozqdq_segGroup",
|
|
1162
|
-
"hint": "-ozqdq_hint",
|
|
1163
|
-
"btnPrimary": "-ozqdq_btnPrimary",
|
|
1164
|
-
"segActive": "-ozqdq_segActive",
|
|
1165
|
-
"clearRow": "-ozqdq_clearRow"
|
|
1297
|
+
"sliderLabel": "-ozqdq_sliderLabel",
|
|
1298
|
+
"segLabel": "-ozqdq_segLabel"
|
|
1166
1299
|
};
|
|
1167
1300
|
//#endregion
|
|
1168
1301
|
//#region src/client/SettingsRow.tsx
|
|
@@ -1195,6 +1328,8 @@ window.__ModuleLoader__.load({
|
|
|
1195
1328
|
blur: 16,
|
|
1196
1329
|
wallpaperBlur: 0,
|
|
1197
1330
|
fit: DEFAULT_FIT,
|
|
1331
|
+
offsetX: DEFAULT_OFFSET,
|
|
1332
|
+
offsetY: DEFAULT_OFFSET,
|
|
1198
1333
|
timeline: true
|
|
1199
1334
|
};
|
|
1200
1335
|
/** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so
|
|
@@ -1237,13 +1372,26 @@ window.__ModuleLoader__.load({
|
|
|
1237
1372
|
const prefix = `${BACKGROUND_API_PREFIX}/image/`;
|
|
1238
1373
|
return url.startsWith(prefix) ? url.slice(prefix.length) : "";
|
|
1239
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
|
+
}
|
|
1240
1388
|
/** Read the settings store (uSES-friendly). */
|
|
1241
1389
|
function readSnapshot() {
|
|
1242
1390
|
return settingsClient.getSnapshot();
|
|
1243
1391
|
}
|
|
1244
1392
|
/** Field-wise equality for distinguishing our own saves from external changes. */
|
|
1245
1393
|
function sameSettings(a, b) {
|
|
1246
|
-
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;
|
|
1247
1395
|
}
|
|
1248
1396
|
/**
|
|
1249
1397
|
* A stepped range control. While dragging it re-renders only itself: the value
|
|
@@ -1337,6 +1485,14 @@ window.__ModuleLoader__.load({
|
|
|
1337
1485
|
const [noticeDismissed, setNoticeDismissed] = (0, react.useState)(false);
|
|
1338
1486
|
const fileInput = (0, react.useRef)(null);
|
|
1339
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);
|
|
1340
1496
|
const draftRef = (0, react.useRef)(DEFAULTS);
|
|
1341
1497
|
const editingRef = (0, react.useRef)(false);
|
|
1342
1498
|
const lastSavedRef = (0, react.useRef)(null);
|
|
@@ -1394,6 +1550,36 @@ window.__ModuleLoader__.load({
|
|
|
1394
1550
|
saveNowRef.current(draftRef.current);
|
|
1395
1551
|
}, 120);
|
|
1396
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
|
+
}, []);
|
|
1397
1583
|
(0, react.useEffect)(() => {
|
|
1398
1584
|
if (snapshot.status !== "ready" || snapshot.value === void 0) return;
|
|
1399
1585
|
const saved = lastSavedRef.current;
|
|
@@ -1494,8 +1680,109 @@ window.__ModuleLoader__.load({
|
|
|
1494
1680
|
setDraft(next);
|
|
1495
1681
|
saveNow(next);
|
|
1496
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
|
+
};
|
|
1497
1781
|
const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url;
|
|
1498
1782
|
const hasImage = sourceUrl !== "";
|
|
1783
|
+
(0, react.useEffect)(() => {
|
|
1784
|
+
setImgMeta(null);
|
|
1785
|
+
}, [sourceUrl]);
|
|
1499
1786
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1500
1787
|
className: SettingsRow_module_css_default.row,
|
|
1501
1788
|
children: [
|
|
@@ -1531,27 +1818,55 @@ window.__ModuleLoader__.load({
|
|
|
1531
1818
|
})]
|
|
1532
1819
|
}),
|
|
1533
1820
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1534
|
-
className: SettingsRow_module_css_default.
|
|
1535
|
-
ref:
|
|
1536
|
-
children:
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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
|
+
})
|
|
1550
1859
|
})
|
|
1551
|
-
})
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
+
})]
|
|
1555
1870
|
})
|
|
1556
1871
|
}),
|
|
1557
1872
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|