deepseek-harness-background 0.5.6 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -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,30 @@ 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: the shell marks a chrome row \`data-window-drag\` and
129
+ 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, and on Windows the caption strip that
136
+ owns drag and double-click maximize). They never take the pointer, so they
137
+ must not join the composition at all.
138
+
139
+ The value that achieves that is \`initial\`, NOT \`none\`: Chromium maps the
140
+ \`none\` keyword onto \`no-drag\`, so a \`-webkit-app-region: none\` layer IS
141
+ collected and DOES punch a hole exactly like \`no-drag\` does. Measured, not
142
+ assumed — getComputedStyle on the Windows 0.2.0-rc.2 desktop build returns
143
+ "no-drag" for it, and with these two full-viewport layers in that state the
144
+ whole window stopped dragging and double-click maximize stopped working;
145
+ \`initial\` is the one declaration that reads back as "none" and leaves the
146
+ composition. The shell rule carries #root's id specificity, so a plain
147
+ declaration cannot outrank it — this needs \`!important\`. */
148
+ body > .dsh-bg-layer,
149
+ body > .dsh-bg-scrim {
150
+ -webkit-app-region: initial !important;
151
+ }
123
152
 
124
153
  /* Active: make the app frame + sidebar background transparent so all columns
125
154
  share the same wallpaper + scrim backdrop. */
@@ -136,7 +165,6 @@ window.__ModuleLoader__.load({
136
165
  light glass slightly DIMS, dark glass keeps a slight lift. Gated on the
137
166
  GLASS ATTR (wallpaper active AND panelOpacity < 1): maxing the panel
138
167
  slider returns these surfaces to the exact official paints. */
139
- body[data-dsh-bg-glass] [data-composer-card],
140
168
  body[data-dsh-bg-glass] [class*="_bubble"]:not([role="tooltip"]),
141
169
  /* The read_image toolview (dsh >= 0.1.2-rc.1 ToolRow) collapses its result
142
170
  into an image body card; glass it like the other content blocks. */
@@ -160,6 +188,37 @@ window.__ModuleLoader__.load({
160
188
  0 12px 40px rgba(0, 0, 0, 0.12);
161
189
  }
162
190
 
191
+ /* ---- Composer card: the filter rides a pseudo-element ------------------
192
+ Chromium makes every element that carries a backdrop-filter a Backdrop
193
+ Root, and a filter inside one can only sample what that element itself
194
+ paints. The composer hosts the picker menu as an absolutely positioned
195
+ child ([data-composer-card] > … conversation.input.overlay), so with the
196
+ filter on the card itself that menu sampled the card's own paint only and
197
+ read as flat transparency over the wallpaper. Moving the filter to a
198
+ positioned ::before breaks the root — the pseudo-element is not an
199
+ ancestor of the menu, so the menu blurs the wallpaper again. The card
200
+ keeps its own frost (the ::before supplies it) and needs no layout change:
201
+ ui-conversation already declares it position: relative as the overlay
202
+ anchor context. */
203
+ body[data-dsh-bg-glass] [data-composer-card] {
204
+ background-color: var(--dsw-specific-input-major);
205
+ 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));
206
+ box-shadow:
207
+ inset 0 1px 0 rgba(255, 255, 255, 0.32),
208
+ inset 0 -1px 0 rgba(255, 255, 255, 0.08),
209
+ inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),
210
+ 0 12px 40px rgba(0, 0, 0, 0.12);
211
+ }
212
+ body[data-dsh-bg-glass] [data-composer-card]::before {
213
+ content: '';
214
+ position: absolute;
215
+ inset: 0;
216
+ z-index: -1;
217
+ border-radius: inherit;
218
+ -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);
219
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
220
+ }
221
+
163
222
  /* The sticky code-block header officially occludes scrolled code with
164
223
  bg-base (transparent under a wallpaper): refill it with the banner token
165
224
  (itself translucent glass) so code stays hidden beneath the sticky bar. */
@@ -308,6 +367,78 @@ window.__ModuleLoader__.load({
308
367
  box-shadow: var(--dsw-elevation-panel, 0 4px 14px rgba(0, 0, 0, 0.08));
309
368
  }
310
369
 
370
+ /* The card's header paints the official opaque fill (--changes-fill is a
371
+ static neutral), so the glass card underneath never shows through: hand
372
+ that one surface back to the card by clearing its fill, and give the
373
+ header's own hover/focus feedback the translucent interactive token the
374
+ card's rows already use. The anchor is the header ELEMENT, not the card's
375
+ first child: a single-file turn renders that same header through the
376
+ HoverCard primitive, which wraps its anchor in a span (ui-primitives
377
+ HoverCard.tsx), so a child-position anchor reached only that wrapper and
378
+ left the real header's fill in place — the whole card stayed opaque. */
379
+ body[data-dsh-bg-glass] [data-changed-files] button[class*="_header"] {
380
+ background-color: transparent;
381
+ }
382
+ body[data-dsh-bg-glass] [data-changed-files] button[class*="_header"]:hover:not(:disabled),
383
+ body[data-dsh-bg-glass] [data-changed-files] button[class*="_header"]:focus-visible {
384
+ background-color: var(--dsw-alias-interactive-bg-hover);
385
+ }
386
+
387
+ /* ---- Delivered file cards ([data-presented-file]) ---------------------
388
+ The deliverable grid's file card paints --deliverable-fill, a static
389
+ neutral (--dsw-static-neutral-50, or 850 in dark) that no overridden token
390
+ reaches, so it stayed fully opaque over every wallpaper. It takes the same
391
+ explicit-fill recipe as the changed-files card, plus its own hover state:
392
+ the module's .file:hover repaints the fill with --deliverable-hover, the
393
+ other static neutral. This rule outranks it (two attributes + body against
394
+ one class + pseudo-class), so the fill and the sheen both survive hover.
395
+ The card's inner tile and icon already paint with translucent color-mix
396
+ fills and need nothing here. */
397
+ body[data-dsh-bg-glass] [data-presented-file] {
398
+ background-color: var(--dsw-specific-input-major);
399
+ 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));
400
+ -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);
401
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
402
+ box-shadow:
403
+ inset 0 1px 0 rgba(255, 255, 255, 0.32),
404
+ inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),
405
+ 0 4px 14px rgba(0, 0, 0, 0.08);
406
+ }
407
+ body[data-dsh-bg-glass] [data-presented-file]:hover {
408
+ background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
409
+ }
410
+
411
+ /* ---- Docked stat dialogs (turn time, session stats, token usage) -------
412
+ These portal to document.body, so unlike the picker menu above they do
413
+ reach the wallpaper; their official menu skin is a 40px blur under a flat
414
+ 58% fill, which on a soft wallpaper still reads as plain transparency. The
415
+ official fill and its readable contrast stay, and the shared sheen +
416
+ exposure chain joins them to the same glass family as every other panel —
417
+ including the user's blur slider (--bg-glass-blur). Anchors are the
418
+ panels' own detail lists: class hashes are never structural hooks. */
419
+ body[data-dsh-bg-glass] [role="dialog"]:has([data-turn-usage-details]),
420
+ body[data-dsh-bg-glass] [role="dialog"]:has([data-session-stats-details]),
421
+ body[data-dsh-bg-glass] [role="dialog"]:has([data-session-stats-usage]) {
422
+ 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));
423
+ -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);
424
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
425
+ }
426
+
427
+ /* ---- Sidebar bonus notice (the account notice card) -------------------
428
+ The account notice portals an aside straight onto document.body and paints
429
+ --dsw-specific-input-major — a token the painter DOES turn translucent —
430
+ but its own module adds no backdrop-filter, so over a wallpaper it reads as
431
+ flat transparency instead of frosted glass. Same shape as the dock family
432
+ above: token fill + the shared sheen and exposure chain. The anchor is that
433
+ aside with its own role="status" child (the notice copy); the host ships no
434
+ data attribute for the card, and the direct-child step keeps the rule on
435
+ the portaled body child rather than any nested status region. */
436
+ body[data-dsh-bg-glass] > aside:has(> [role="status"]) {
437
+ 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));
438
+ -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);
439
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
440
+ }
441
+
311
442
  /* ---- Official turn rail (dsh >= 0.1.2, TurnNavigator) -----------------
312
443
  The enhance mode borrows the kernel's rail untouched, so its hover
313
444
  preview card keeps the OFFICIAL opaque fill (bg-layer-1, not mediated
@@ -330,6 +461,106 @@ window.__ModuleLoader__.load({
330
461
  -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);
331
462
  backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
332
463
  }
464
+
465
+ /* ---- Transcript tool cards the whitelist missed -----------------------
466
+ Three tool-row surfaces still painted an opaque ground over a wallpaper.
467
+ Two of them carry a fill no overridden token reaches: the present tool's
468
+ result box paints --dsw-alias-bg-layer-1 (the token the painter keeps
469
+ opaque on purpose, because it backs the settings UI and the dialogs), and
470
+ the schedule_create card reuses the delivery card's static neutral
471
+ (--card-fill = --dsw-static-neutral-50 / 850 — its own module header says
472
+ the box is the delivery card). Both need an explicit fill on top of
473
+ the shared sheen chain, and the schedule card's own hover repaints from the
474
+ other static neutral, so the glass fill has to be restated there.
475
+
476
+ The turn-trigger card is the odd one out: 0.2.0-rc.2 split its fill per
477
+ scheme (--dsw-alias-turn-trigger-bg, set to the painter's
478
+ --dsw-alias-markdown-code-block in light but to an untouched
479
+ --dsw-alias-interactive-bg-hover in dark), which left the very same card
480
+ translucent in light and fully opaque in dark. One explicit fill — the
481
+ painter's composer token, the same one every other glass surface uses —
482
+ ends that split, for the resting and the hover state alike.
483
+
484
+ Anchors are the rows' own data-tool / data-turn-trigger marks; class hashes
485
+ never are. */
486
+ body[data-dsh-bg-glass] [data-tool="present"] pre,
487
+ body[data-dsh-bg-glass] [data-tool="schedule_create"],
488
+ body[data-dsh-bg-glass] [data-turn-trigger] {
489
+ background-color: var(--dsw-specific-input-major);
490
+ 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));
491
+ -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);
492
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
493
+ box-shadow:
494
+ inset 0 1px 0 rgba(255, 255, 255, 0.32),
495
+ inset 0 0 0 0.5px rgba(255, 255, 255, 0.08),
496
+ 0 4px 14px rgba(0, 0, 0, 0.08);
497
+ }
498
+ body[data-dsh-bg-glass] [data-tool="schedule_create"]:hover,
499
+ body[data-dsh-bg-glass] [data-turn-trigger]:hover {
500
+ background-color: color-mix(in srgb, rgb(255 255 255) calc(var(--bg-glass-alpha-strong, 0.8) * 100%), transparent);
501
+ }
502
+
503
+ /* ---- Platform overlay return bar (Account settings -> usage / top-up) --
504
+ The isolated native Platform view paints its whole surface with
505
+ --dsw-alias-bg-base, the very token the painter sets to transparent while a
506
+ wallpaper is active, so the 48px return bar went see-through even though
507
+ the native child below it is opaque. dsh itself calls this surface the
508
+ opaque native Platform page (ui-chat README), so the ground comes back
509
+ here, from the two literals --dsw-alias-bg-base is defined with in
510
+ ui-theme, one per scheme. The anchor is the overlay's own aria-modal dialog
511
+ with the Platform return bar as a direct child: no other dialog carries
512
+ data-window-drag. Gated on data-dsh-bg (a wallpaper is active), NOT on the
513
+ glass gate — a fully opaque panel setting must not hand this surface back
514
+ to transparency. */
515
+ body[data-dsh-bg] [role="dialog"][aria-modal="true"]:has(> header[data-window-drag]) {
516
+ background-color: var(--dsw-static-neutral-bluish-00);
517
+ }
518
+ body[data-ds-dark-theme][data-dsh-bg] [role="dialog"][aria-modal="true"]:has(> header[data-window-drag]) {
519
+ background-color: var(--dsw-static-neutral-bluish-950);
520
+ }
521
+
522
+ /* ---- Other surfaces that lean on the base token for their ground ------
523
+ The same mechanism as the Platform overlay above, in three more places.
524
+ dsh paints each of these with --dsw-alias-bg-base and leans on that fill:
525
+ - a Reasoning row's pinned disclosure header masks the thinking prose
526
+ scrolling under it (the sheet's own selector is
527
+ .root[data-expanded] [data-open] [data-disclosure-row]);
528
+ - the compaction marker's pinned toggle masks the summary prose under it
529
+ (its sheet says so out loud: the fill is what masks the prose);
530
+ - the inspect buttons of the tool rows and of the skill row are quiet
531
+ in-flow affordances that deliberately take the base ground instead of
532
+ the heavier overlay token.
533
+ With the base token transparent they lose that ground and the prose they
534
+ are supposed to mask shows through. The literals below are the token's own
535
+ two definitions, one per scheme; the anchors are ReasoningRow's own
536
+ data-variant mark, the compaction icon the marker ships, and the class
537
+ suffix those inspect buttons share across three packages. */
538
+ body[data-dsh-bg] [data-variant="think"][data-expanded] [data-open] [data-disclosure-row],
539
+ body[data-dsh-bg] [class*="_compactionButton"]:has([data-compaction-icon="context"]),
540
+ body[data-dsh-bg] [class*="_inspectButton"] {
541
+ background-color: var(--dsw-static-neutral-bluish-00);
542
+ }
543
+ body[data-ds-dark-theme][data-dsh-bg] [data-variant="think"][data-expanded] [data-open] [data-disclosure-row],
544
+ body[data-ds-dark-theme][data-dsh-bg] [class*="_compactionButton"]:has([data-compaction-icon="context"]),
545
+ body[data-ds-dark-theme][data-dsh-bg] [class*="_inspectButton"] {
546
+ background-color: var(--dsw-static-neutral-bluish-950);
547
+ }
548
+
549
+ /* ---- The ask-user transcript card -------------------------------------
550
+ A message-flow card like the changed-files one, but its ground is
551
+ --dsw-alias-bg-base rather than an opaque-token family the painter never
552
+ overrides — so with the base transparent the card had no ground at all and
553
+ its question/answer text sat straight on the wallpaper. It takes the
554
+ shared frosted recipe. The anchor is the tool row's own mark plus the card
555
+ class, because the card element itself carries no data attribute; the
556
+ class is matched by SUFFIX, so a sibling class such as _cardBody can never
557
+ be swept in. */
558
+ body[data-dsh-bg-glass] [data-tool="ask_user_question"] [class$="_card"] {
559
+ background-color: var(--dsw-specific-input-major);
560
+ 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));
561
+ -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);
562
+ backdrop-filter: blur(var(--bg-glass-blur, 16px)) saturate(var(--bg-glass-saturate, 1.42)) brightness(var(--bg-glass-brightness, 1)) contrast(1.01);
563
+ }
333
564
  `;
334
565
  /**
335
566
  * Inject the backdrop stylesheet once. Safe to call repeatedly; a second call
@@ -355,6 +586,8 @@ window.__ModuleLoader__.load({
355
586
  const DEFAULT_FIT = "cover";
356
587
  /** Image positioning modes. `cover` fills the frame (cropping as needed); `contain` fits the whole image inside the frame (letterboxing). */
357
588
  const FIT_MODES = ["cover", "contain"];
589
+ /** Default framing offset — the image sits centered on both axes. */
590
+ const DEFAULT_OFFSET = .5;
358
591
  //#endregion
359
592
  //#region src/client/backdrop.ts
360
593
  /**
@@ -559,6 +792,8 @@ window.__ModuleLoader__.load({
559
792
  this.hasSource = true;
560
793
  document.body.style;
561
794
  this.setVar("--bg-object-fit", settings.fit);
795
+ this.setVar("--bg-offset-x", String(settings.offsetX));
796
+ this.setVar("--bg-offset-y", String(settings.offsetY));
562
797
  this.setVar("--bg-opacity", String(settings.opacity));
563
798
  this.setVar("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
564
799
  this.setVar("--bg-wallpaper-scale", (1 + settings.wallpaperBlur * .006).toFixed(4));
@@ -626,7 +861,13 @@ window.__ModuleLoader__.load({
626
861
  this.setVar("--bg-wallpaper-blur", `${value}px`);
627
862
  this.setVar("--bg-wallpaper-scale", (1 + Number(value) * .006).toFixed(4));
628
863
  break;
629
- case "fit": this.setVar("--bg-object-fit", String(value));
864
+ case "fit":
865
+ this.setVar("--bg-object-fit", String(value));
866
+ break;
867
+ case "offsetX":
868
+ this.setVar("--bg-offset-x", String(value));
869
+ break;
870
+ case "offsetY": this.setVar("--bg-offset-y", String(value));
630
871
  }
631
872
  }
632
873
  /**
@@ -723,6 +964,8 @@ window.__ModuleLoader__.load({
723
964
  "--bg-opacity",
724
965
  "--bg-scrim",
725
966
  "--bg-object-fit",
967
+ "--bg-offset-x",
968
+ "--bg-offset-y",
726
969
  "--bg-wallpaper-blur",
727
970
  "--bg-wallpaper-scale",
728
971
  "--bg-glass-blur",
@@ -740,6 +983,8 @@ window.__ModuleLoader__.load({
740
983
  function paintPreviewSurface(el, settings) {
741
984
  const s = el.style;
742
985
  s.setProperty("--bg-object-fit", settings.fit);
986
+ s.setProperty("--bg-offset-x", String(settings.offsetX));
987
+ s.setProperty("--bg-offset-y", String(settings.offsetY));
743
988
  s.setProperty("--bg-opacity", String(settings.opacity));
744
989
  s.setProperty("--bg-scrim", String(settings.scrim));
745
990
  s.setProperty("--bg-wallpaper-blur", `${settings.wallpaperBlur}px`);
@@ -985,6 +1230,8 @@ window.__ModuleLoader__.load({
985
1230
  "background.fit": "填充方式",
986
1231
  "background.cover": "铺满",
987
1232
  "background.contain": "完整",
1233
+ "background.previewPanHint": "拖动预览可调整图片位置,双击居中",
1234
+ "background.repositionReset": "重置位置",
988
1235
  "background.clear": "清除背景",
989
1236
  "background.uploadFailed": "上传失败,请重试",
990
1237
  "background.saveFailed": "保存失败,请重试",
@@ -1016,6 +1263,8 @@ window.__ModuleLoader__.load({
1016
1263
  "background.fit": "Fit",
1017
1264
  "background.cover": "Cover",
1018
1265
  "background.contain": "Contain",
1266
+ "background.previewPanHint": "Drag the preview to reposition the image; double-click to recenter",
1267
+ "background.repositionReset": "Reset position",
1019
1268
  "background.clear": "Clear background",
1020
1269
  "background.uploadFailed": "Upload failed, try again",
1021
1270
  "background.saveFailed": "Save failed, try again",
@@ -1149,7 +1398,7 @@ window.__ModuleLoader__.load({
1149
1398
  const settingsClient = new SettingsClient();
1150
1399
  //#endregion
1151
1400
  //#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}}";
1401
+ 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
1402
  const tagId = "deepseek-harness-background/SettingsRow.module.css";
1154
1403
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1155
1404
  const tag = document.createElement("style");
@@ -1159,44 +1408,46 @@ window.__ModuleLoader__.load({
1159
1408
  document.head.appendChild(tag);
1160
1409
  }
1161
1410
  var SettingsRow_module_css_default = {
1162
- "sectionLabel": "-ozqdq_sectionLabel",
1411
+ "preview": "-ozqdq_preview",
1412
+ "notice": "-ozqdq_notice",
1163
1413
  "row": "-ozqdq_row",
1414
+ "section": "-ozqdq_section",
1415
+ "slider": "-ozqdq_slider",
1416
+ "clearRow": "-ozqdq_clearRow",
1164
1417
  "previewGlass": "-ozqdq_previewGlass",
1165
- "switchKnob": "-ozqdq_switchKnob",
1166
- "clearBtn": "-ozqdq_clearBtn",
1418
+ "previewReset": "-ozqdq_previewReset",
1419
+ "btnDanger": "-ozqdq_btnDanger",
1167
1420
  "segRow": "-ozqdq_segRow",
1168
- "desc": "-ozqdq_desc",
1169
- "sourceRow": "-ozqdq_sourceRow",
1170
- "btnPrimary": "-ozqdq_btnPrimary",
1421
+ "ratioFrame": "-ozqdq_ratioFrame",
1171
1422
  "titleRow": "-ozqdq_titleRow",
1172
- "noticeBtn": "-ozqdq_noticeBtn",
1173
- "section": "-ozqdq_section",
1423
+ "header": "-ozqdq_header",
1424
+ "title": "-ozqdq_title",
1425
+ "clearBtn": "-ozqdq_clearBtn",
1426
+ "btnPrimary": "-ozqdq_btnPrimary",
1174
1427
  "sliderRow": "-ozqdq_sliderRow",
1175
- "enabledBadge": "-ozqdq_enabledBadge",
1176
- "noticeText": "-ozqdq_noticeText",
1177
- "urlRow": "-ozqdq_urlRow",
1178
- "previewScrim": "-ozqdq_previewScrim",
1179
- "sliderLabel": "-ozqdq_sliderLabel",
1180
1428
  "sliderValue": "-ozqdq_sliderValue",
1181
- "header": "-ozqdq_header",
1429
+ "segActive": "-ozqdq_segActive",
1430
+ "switch": "-ozqdq_switch",
1431
+ "switchKnob": "-ozqdq_switchKnob",
1432
+ "desc": "-ozqdq_desc",
1182
1433
  "error": "-ozqdq_error",
1183
- "urlInput": "-ozqdq_urlInput",
1184
1434
  "hint": "-ozqdq_hint",
1185
- "btnDanger": "-ozqdq_btnDanger",
1186
- "title": "-ozqdq_title",
1187
- "previewImg": "-ozqdq_previewImg",
1188
- "segLabel": "-ozqdq_segLabel",
1189
- "switch": "-ozqdq_switch",
1190
- "preview": "-ozqdq_preview",
1191
- "segGroup": "-ozqdq_segGroup",
1435
+ "sourceRow": "-ozqdq_sourceRow",
1436
+ "enabledBadge": "-ozqdq_enabledBadge",
1192
1437
  "previewGlassText": "-ozqdq_previewGlassText",
1193
- "segActive": "-ozqdq_segActive",
1194
- "btn": "-ozqdq_btn",
1195
- "clearRow": "-ozqdq_clearRow",
1196
- "notice": "-ozqdq_notice",
1197
- "segBtn": "-ozqdq_segBtn",
1438
+ "segLabel": "-ozqdq_segLabel",
1439
+ "sliderLabel": "-ozqdq_sliderLabel",
1198
1440
  "previewEmpty": "-ozqdq_previewEmpty",
1199
- "slider": "-ozqdq_slider"
1441
+ "noticeBtn": "-ozqdq_noticeBtn",
1442
+ "sectionLabel": "-ozqdq_sectionLabel",
1443
+ "noticeText": "-ozqdq_noticeText",
1444
+ "previewImg": "-ozqdq_previewImg",
1445
+ "previewScrim": "-ozqdq_previewScrim",
1446
+ "btn": "-ozqdq_btn",
1447
+ "urlRow": "-ozqdq_urlRow",
1448
+ "segGroup": "-ozqdq_segGroup",
1449
+ "urlInput": "-ozqdq_urlInput",
1450
+ "segBtn": "-ozqdq_segBtn"
1200
1451
  };
1201
1452
  //#endregion
1202
1453
  //#region src/client/SettingsRow.tsx
@@ -1229,6 +1480,8 @@ window.__ModuleLoader__.load({
1229
1480
  blur: 16,
1230
1481
  wallpaperBlur: 0,
1231
1482
  fit: DEFAULT_FIT,
1483
+ offsetX: DEFAULT_OFFSET,
1484
+ offsetY: DEFAULT_OFFSET,
1232
1485
  timeline: true
1233
1486
  };
1234
1487
  /** Per-field slider geometry: 5% steps for ratios, fine px steps for radii so
@@ -1271,13 +1524,26 @@ window.__ModuleLoader__.load({
1271
1524
  const prefix = `${BACKGROUND_API_PREFIX}/image/`;
1272
1525
  return url.startsWith(prefix) ? url.slice(prefix.length) : "";
1273
1526
  }
1527
+ /** Cap on the preview card's height. Past it the card shrinks as a whole —
1528
+ * width follows the ratio — and centers, so a very tall (or very wide) window
1529
+ * keeps the exact viewport ratio instead of squashing the crop. */
1530
+ const PREVIEW_MAX_HEIGHT = 360;
1531
+ /** Clamp a framing offset into its schema domain, trimming the float dust the
1532
+ * pointer-delta division leaves behind (0.30000000000000004 → 0.3). */
1533
+ function clampOffset(value) {
1534
+ return Math.round((value < 0 ? 0 : value > 1 ? 1 : value) * 1e4) / 1e4;
1535
+ }
1536
+ /** Round ratio dust out of an inline px box (400 / (4/3) is 300.00000000000006). */
1537
+ function trimPx(value) {
1538
+ return Math.round(value * 100) / 100;
1539
+ }
1274
1540
  /** Read the settings store (uSES-friendly). */
1275
1541
  function readSnapshot() {
1276
1542
  return settingsClient.getSnapshot();
1277
1543
  }
1278
1544
  /** Field-wise equality for distinguishing our own saves from external changes. */
1279
1545
  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;
1546
+ 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
1547
  }
1282
1548
  /**
1283
1549
  * A stepped range control. While dragging it re-renders only itself: the value
@@ -1371,6 +1637,14 @@ window.__ModuleLoader__.load({
1371
1637
  const [noticeDismissed, setNoticeDismissed] = (0, react.useState)(false);
1372
1638
  const fileInput = (0, react.useRef)(null);
1373
1639
  const previewRef = (0, react.useRef)(null);
1640
+ const frameRef = (0, react.useRef)(null);
1641
+ const [viewport, setViewport] = (0, react.useState)(() => ({
1642
+ w: window.innerWidth,
1643
+ h: window.innerHeight
1644
+ }));
1645
+ const [frameWidth, setFrameWidth] = (0, react.useState)(0);
1646
+ const [imgMeta, setImgMeta] = (0, react.useState)(null);
1647
+ const panRef = (0, react.useRef)(null);
1374
1648
  const draftRef = (0, react.useRef)(DEFAULTS);
1375
1649
  const editingRef = (0, react.useRef)(false);
1376
1650
  const lastSavedRef = (0, react.useRef)(null);
@@ -1428,6 +1702,36 @@ window.__ModuleLoader__.load({
1428
1702
  saveNowRef.current(draftRef.current);
1429
1703
  }, 120);
1430
1704
  }, []);
1705
+ (0, react.useEffect)(() => {
1706
+ let raf = 0;
1707
+ const sync = () => {
1708
+ raf = 0;
1709
+ setViewport({
1710
+ w: window.innerWidth,
1711
+ h: window.innerHeight
1712
+ });
1713
+ };
1714
+ const onResize = () => {
1715
+ if (raf !== 0) return;
1716
+ raf = window.requestAnimationFrame(sync);
1717
+ };
1718
+ window.addEventListener("resize", onResize);
1719
+ return () => {
1720
+ window.removeEventListener("resize", onResize);
1721
+ if (raf !== 0) window.cancelAnimationFrame(raf);
1722
+ };
1723
+ }, []);
1724
+ (0, react.useEffect)(() => {
1725
+ const el = frameRef.current;
1726
+ if (el === null) return;
1727
+ if (typeof ResizeObserver === "undefined") {
1728
+ setFrameWidth(el.clientWidth);
1729
+ return;
1730
+ }
1731
+ const observer = new ResizeObserver(() => setFrameWidth(el.clientWidth));
1732
+ observer.observe(el);
1733
+ return () => observer.disconnect();
1734
+ }, []);
1431
1735
  (0, react.useEffect)(() => {
1432
1736
  if (snapshot.status !== "ready" || snapshot.value === void 0) return;
1433
1737
  const saved = lastSavedRef.current;
@@ -1528,8 +1832,109 @@ window.__ModuleLoader__.load({
1528
1832
  setDraft(next);
1529
1833
  saveNow(next);
1530
1834
  };
1835
+ /** Natural aspect ratio of the loaded source (null until loaded / on error). */
1836
+ const imageAspect = imgMeta !== null && imgMeta.w > 0 && imgMeta.h > 0 ? imgMeta.w / imgMeta.h : null;
1837
+ /** Preview card box at the live viewport ratio, capped at PREVIEW_MAX_HEIGHT
1838
+ * (past the cap the card shrinks as a whole and the frame centers it); a
1839
+ * width of 0 means "not measured yet" (CSS fallbacks apply). */
1840
+ const cardRatio = viewport.w > 0 && viewport.h > 0 ? viewport.w / viewport.h : 16 / 9;
1841
+ const cardWidth = frameWidth > 0 ? trimPx(Math.min(frameWidth, PREVIEW_MAX_HEIGHT * cardRatio)) : 0;
1842
+ const cardHeight = cardWidth > 0 ? trimPx(cardWidth / cardRatio) : 0;
1843
+ /** The axis the cover crop has slack on — and therefore the only one the
1844
+ * user may pan. A landscape source in a portrait frame overflows sideways
1845
+ * (pan x); a portrait source in a landscape frame overflows vertically
1846
+ * (pan y). null for `contain`, an unloaded source, an unmeasured card or an
1847
+ * exact ratio match (nothing to pan, and no division by zero). */
1848
+ const panAxis = draft.fit === "cover" && imageAspect !== null && cardWidth > 0 && Math.abs(imageAspect - cardRatio) >= 1e-4 ? imageAspect > cardRatio ? "x" : "y" : null;
1849
+ /** Whether a pan has moved the crop off center (drives the recenter chip). */
1850
+ const offsetMoved = draft.offsetX !== .5 || draft.offsetY !== .5;
1851
+ /** Overflow (px) of the rendered image past the card on one axis, measured
1852
+ * from the live DOM (the same frame the wallpaper layer uses), or null when
1853
+ * that axis has nothing to pan. */
1854
+ const overflowFor = (axis) => {
1855
+ const el = previewRef.current;
1856
+ if (el === null || imgMeta === null) return null;
1857
+ const fw = el.clientWidth;
1858
+ const fh = el.clientHeight;
1859
+ if (fw <= 0 || fh <= 0 || imgMeta.w <= 0 || imgMeta.h <= 0) return null;
1860
+ const scale = Math.max(fw / imgMeta.w, fh / imgMeta.h);
1861
+ const overflow = axis === "x" ? imgMeta.w * scale - fw : imgMeta.h * scale - fh;
1862
+ return overflow > 0 ? overflow : null;
1863
+ };
1864
+ /** Write one framing offset live: CSS variables only, no React state — the
1865
+ * pan hot path mirrors ControlSlider's onLive. */
1866
+ const setOffsetLive = (key, value) => {
1867
+ const next = {
1868
+ ...draftRef.current,
1869
+ [key]: value
1870
+ };
1871
+ draftRef.current = next;
1872
+ paintBackgroundKnob(key, value);
1873
+ if (previewRef.current) paintPreviewSurface(previewRef.current, next);
1874
+ };
1875
+ const handlePanDown = (e) => {
1876
+ if (panAxis === null) return;
1877
+ const overflow = overflowFor(panAxis);
1878
+ if (overflow === null) return;
1879
+ e.currentTarget.setPointerCapture(e.pointerId);
1880
+ e.preventDefault();
1881
+ markEditing();
1882
+ panRef.current = {
1883
+ axis: panAxis,
1884
+ startPos: panAxis === "x" ? e.clientX : e.clientY,
1885
+ startOffset: panAxis === "x" ? draftRef.current.offsetX : draftRef.current.offsetY,
1886
+ overflow
1887
+ };
1888
+ };
1889
+ const handlePanMove = (e) => {
1890
+ const pan = panRef.current;
1891
+ if (pan === null) return;
1892
+ const delta = (pan.axis === "x" ? e.clientX : e.clientY) - pan.startPos;
1893
+ setOffsetLive(pan.axis === "x" ? "offsetX" : "offsetY", clampOffset(pan.startOffset - delta / pan.overflow));
1894
+ };
1895
+ const handlePanUp = (e) => {
1896
+ if (panRef.current === null) return;
1897
+ panRef.current = null;
1898
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
1899
+ setDraft({ ...draftRef.current });
1900
+ onCommit();
1901
+ };
1902
+ /** Arrow-key pan on the movable axis (Shift = coarse step). */
1903
+ const nudgeOffset = (axis, direction, coarse) => {
1904
+ const key = axis === "x" ? "offsetX" : "offsetY";
1905
+ setOffsetLive(key, clampOffset(draftRef.current[key] + direction * (coarse ? .1 : .02)));
1906
+ setDraft({ ...draftRef.current });
1907
+ onCommit();
1908
+ };
1909
+ const handlePanKeyDown = (e) => {
1910
+ if (panAxis === null) return;
1911
+ if (panAxis === "x" && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
1912
+ e.preventDefault();
1913
+ nudgeOffset("x", e.key === "ArrowLeft" ? -1 : 1, e.shiftKey);
1914
+ return;
1915
+ }
1916
+ if (panAxis === "y" && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
1917
+ e.preventDefault();
1918
+ nudgeOffset("y", e.key === "ArrowUp" ? -1 : 1, e.shiftKey);
1919
+ }
1920
+ };
1921
+ /** Recenter the crop on both axes (recenter chip / double-click). */
1922
+ const handleResetOffset = () => {
1923
+ const next = {
1924
+ ...draftRef.current,
1925
+ offsetX: DEFAULT_OFFSET,
1926
+ offsetY: DEFAULT_OFFSET
1927
+ };
1928
+ draftRef.current = next;
1929
+ setDraft(next);
1930
+ paintLive(next);
1931
+ saveNow(next);
1932
+ };
1531
1933
  const sourceUrl = draft.uploadId ? `${BACKGROUND_API_PREFIX}/image/${draft.uploadId}` : draft.url;
1532
1934
  const hasImage = sourceUrl !== "";
1935
+ (0, react.useEffect)(() => {
1936
+ setImgMeta(null);
1937
+ }, [sourceUrl]);
1533
1938
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1534
1939
  className: SettingsRow_module_css_default.row,
1535
1940
  children: [
@@ -1565,27 +1970,55 @@ window.__ModuleLoader__.load({
1565
1970
  })]
1566
1971
  }),
1567
1972
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1568
- className: SettingsRow_module_css_default.preview,
1569
- ref: previewRef,
1570
- children: hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1572
- className: SettingsRow_module_css_default.previewImg,
1573
- src: sourceUrl,
1574
- alt: "",
1575
- draggable: false,
1576
- referrerPolicy: "no-referrer"
1577
- }),
1578
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: SettingsRow_module_css_default.previewScrim }),
1579
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1580
- className: SettingsRow_module_css_default.previewGlass,
1581
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1582
- className: SettingsRow_module_css_default.previewGlassText,
1583
- children: t("background.previewGlass")
1973
+ className: SettingsRow_module_css_default.ratioFrame,
1974
+ ref: frameRef,
1975
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1976
+ className: SettingsRow_module_css_default.preview,
1977
+ ref: previewRef,
1978
+ "data-pan": panAxis ?? void 0,
1979
+ style: cardHeight > 0 ? {
1980
+ width: `${cardWidth}px`,
1981
+ height: `${cardHeight}px`
1982
+ } : void 0,
1983
+ tabIndex: panAxis === null ? void 0 : 0,
1984
+ "aria-label": panAxis === null ? void 0 : t("background.previewPanHint"),
1985
+ onPointerDown: handlePanDown,
1986
+ onPointerMove: handlePanMove,
1987
+ onPointerUp: handlePanUp,
1988
+ onPointerCancel: handlePanUp,
1989
+ onKeyDown: handlePanKeyDown,
1990
+ onDoubleClick: panAxis === null ? void 0 : handleResetOffset,
1991
+ children: [hasImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1993
+ className: SettingsRow_module_css_default.previewImg,
1994
+ src: sourceUrl,
1995
+ alt: "",
1996
+ draggable: false,
1997
+ referrerPolicy: "no-referrer",
1998
+ onLoad: (e) => setImgMeta({
1999
+ w: e.currentTarget.naturalWidth,
2000
+ h: e.currentTarget.naturalHeight
2001
+ }),
2002
+ onError: () => setImgMeta(null)
2003
+ }),
2004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: SettingsRow_module_css_default.previewScrim }),
2005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2006
+ className: SettingsRow_module_css_default.previewGlass,
2007
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2008
+ className: SettingsRow_module_css_default.previewGlassText,
2009
+ children: t("background.previewGlass")
2010
+ })
1584
2011
  })
1585
- })
1586
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1587
- className: SettingsRow_module_css_default.previewEmpty,
1588
- children: t("background.noPreview")
2012
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2013
+ className: SettingsRow_module_css_default.previewEmpty,
2014
+ children: t("background.noPreview")
2015
+ }), panAxis !== null && offsetMoved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2016
+ type: "button",
2017
+ className: SettingsRow_module_css_default.previewReset,
2018
+ onPointerDown: (e) => e.stopPropagation(),
2019
+ onClick: handleResetOffset,
2020
+ children: t("background.repositionReset")
2021
+ })]
1589
2022
  })
1590
2023
  }),
1591
2024
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {