dsh-background-by-model 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +180 -160
- package/CHANGELOG.zh.md +181 -159
- package/LICENSE +21 -21
- package/README.md +297 -283
- package/README.zh.md +297 -283
- package/cordis.patch.yml +8 -8
- package/lib/client.js +704 -49
- package/lib/client.js.map +1 -1
- package/lib/index.js +86 -8
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -26,7 +26,7 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
pageProfile: "配置",
|
|
27
27
|
descInterface: "为主界面的各个区域单独调节透明度与模糊,营造空间层次感。这里的设置对所有模型生效",
|
|
28
28
|
descModelBg: "每条规则 = 一个匹配串 + 一组背景图与外观。切换模型时,从上往下找第一条匹配串出现在模型名里的规则;都没中就使用第 1 条。一条规则可以放多张图,按你设的节奏轮换",
|
|
29
|
-
descProfile: "
|
|
29
|
+
descProfile: "调整壁纸切换效果,并把当前全部规则连同图片导出备份,或从文件一键恢复",
|
|
30
30
|
uiTitle: "主界面",
|
|
31
31
|
uiOpacity: "透明度",
|
|
32
32
|
uiBlur: "模糊度",
|
|
@@ -142,7 +142,22 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
importFail: "导入失败,文件格式不正确",
|
|
143
143
|
footerTag: "按模型换背景插件",
|
|
144
144
|
holidayTitle: "节日特殊背景",
|
|
145
|
-
holidayEnable: "启用节日特殊背景(中秋当天、国庆 10 月 1–7 日)"
|
|
145
|
+
holidayEnable: "启用节日特殊背景(中秋当天、国庆 10 月 1–7 日)",
|
|
146
|
+
trTitle: "切换效果",
|
|
147
|
+
trEffect: "切换方式",
|
|
148
|
+
trEffectFade: "淡入淡出",
|
|
149
|
+
trEffectNone: "直接切换",
|
|
150
|
+
trEffectZoom: "缩放淡入",
|
|
151
|
+
trEffectSlide: "平移推入",
|
|
152
|
+
trEasing: "缓动",
|
|
153
|
+
trEasingEase: "标准",
|
|
154
|
+
trEasingLinear: "匀速",
|
|
155
|
+
trEasingOut: "先快后慢",
|
|
156
|
+
trEasingInOut: "两端慢",
|
|
157
|
+
trDuration: "切换时长",
|
|
158
|
+
trPreview: "试放",
|
|
159
|
+
trHint: "效果对所有换图生效:切模型、轮换、手动下一张、节日。「直接切换」和 0 秒一样是硬切",
|
|
160
|
+
trReducedHint: "系统开启了「减少动态效果」,实际切换不会播放动画"
|
|
146
161
|
};
|
|
147
162
|
const en = {
|
|
148
163
|
nav: "Model background",
|
|
@@ -152,7 +167,7 @@ window.__ModuleLoader__.load({
|
|
|
152
167
|
pageProfile: "Profile",
|
|
153
168
|
descInterface: "Tune opacity and blur per surface to build depth. These settings are global and apply to every model",
|
|
154
169
|
descModelBg: "Each rule is a match string plus a set of wallpapers and one look. On a model switch the list is scanned top-down for the first match string contained in the model name; if nothing hits, rule 1 is used. A rule can hold several images and rotate through them on a rhythm you set",
|
|
155
|
-
descProfile: "
|
|
170
|
+
descProfile: "Tune how the wallpaper switches, and back up every rule together with its image — or restore one from a file",
|
|
156
171
|
uiTitle: "Interface",
|
|
157
172
|
uiOpacity: "Opacity",
|
|
158
173
|
uiBlur: "Blur",
|
|
@@ -268,9 +283,106 @@ window.__ModuleLoader__.load({
|
|
|
268
283
|
importFail: "Import failed — invalid file",
|
|
269
284
|
footerTag: "Per-model wallpaper plugin",
|
|
270
285
|
holidayTitle: "Holiday backgrounds",
|
|
271
|
-
holidayEnable: "Enable holiday backgrounds (Mid-Autumn day, Oct 1–7)"
|
|
286
|
+
holidayEnable: "Enable holiday backgrounds (Mid-Autumn day, Oct 1–7)",
|
|
287
|
+
trTitle: "Switch effect",
|
|
288
|
+
trEffect: "Transition",
|
|
289
|
+
trEffectFade: "Cross-fade",
|
|
290
|
+
trEffectNone: "Instant",
|
|
291
|
+
trEffectZoom: "Zoom in",
|
|
292
|
+
trEffectSlide: "Slide in",
|
|
293
|
+
trEasing: "Easing",
|
|
294
|
+
trEasingEase: "Standard",
|
|
295
|
+
trEasingLinear: "Linear",
|
|
296
|
+
trEasingOut: "Ease out",
|
|
297
|
+
trEasingInOut: "Ease in-out",
|
|
298
|
+
trDuration: "Duration",
|
|
299
|
+
trPreview: "Preview",
|
|
300
|
+
trHint: "The effect applies to every change: model switch, rotation, manual next, holidays. “Instant” and 0 s are the same hard cut",
|
|
301
|
+
trReducedHint: "Your system asks for reduced motion, so a real switch will not animate"
|
|
272
302
|
};
|
|
273
303
|
//#endregion
|
|
304
|
+
//#region src/client/transition.ts
|
|
305
|
+
/**
|
|
306
|
+
* Resolve the duration one switch uses, in ms (`0` = a hard cut).
|
|
307
|
+
*
|
|
308
|
+
* 1. `effect: 'none'` is a hard cut whatever the duration says — asking for no
|
|
309
|
+
* animation and asking for 0 ms are the same request, and the effect is the
|
|
310
|
+
* more explicit of the two;
|
|
311
|
+
* 2. otherwise the global `durationMs` decides, because a rule has no notion of
|
|
312
|
+
* a transition and there is exactly one duration for every switch.
|
|
313
|
+
*
|
|
314
|
+
* A non-finite or negative value answers `0` (a hard cut) rather than a default:
|
|
315
|
+
* animating for an unknown length is the one answer that cannot be right. The
|
|
316
|
+
* input is already clamped by the shared sanitizer (`./schema`), so this is a
|
|
317
|
+
* guard against a hand-edited config, not a second clamp.
|
|
318
|
+
*/
|
|
319
|
+
function resolveFadeMs(transition) {
|
|
320
|
+
if (transition.effect === "none") return 0;
|
|
321
|
+
return Number.isFinite(transition.durationMs) && transition.durationMs > 0 ? transition.durationMs : 0;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* The transform the incoming layer starts from, per effect.
|
|
325
|
+
*
|
|
326
|
+
* `zoom` starts ABOVE 1 and settles back to exactly `none`, never below or past
|
|
327
|
+
* it: the wallpaper would otherwise be left larger than the viewport, cropping a
|
|
328
|
+
* `fit` framing the user committed by hand. `slide` is a small push because a
|
|
329
|
+
* full-width travel of a full-bleed wallpaper reads as two wallpapers, not one
|
|
330
|
+
* transition.
|
|
331
|
+
*/
|
|
332
|
+
const START_TRANSFORM = {
|
|
333
|
+
fade: "none",
|
|
334
|
+
none: "none",
|
|
335
|
+
zoom: "scale(1.06)",
|
|
336
|
+
slide: "translateX(6%)"
|
|
337
|
+
};
|
|
338
|
+
/** The properties one effect actually animates — an unchanged property must not
|
|
339
|
+
* be put on the transition list, or `fade` would stop being byte-for-byte the
|
|
340
|
+
* cross-fade every release so far performed. */
|
|
341
|
+
function animatedProps(effect) {
|
|
342
|
+
return effect === "fade" ? ["opacity"] : ["opacity", "transform"];
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Decide one switch: whether to animate, from what, to what, and on what curve.
|
|
346
|
+
*
|
|
347
|
+
* `canAnimate` is the caller's answer to "is there anything to animate from" —
|
|
348
|
+
* a first paint has no previous layer, and a wallpaper at 0% opacity is not on
|
|
349
|
+
* screen — plus the window-visibility question the caller already owns. It is
|
|
350
|
+
* passed in rather than computed here so this stays a pure function of its
|
|
351
|
+
* inputs.
|
|
352
|
+
*
|
|
353
|
+
* `reducedMotion` is honoured as a hard veto rather than a shortened duration:
|
|
354
|
+
* the point of the preference is that the interface stops moving, and a fast
|
|
355
|
+
* animation is still an animation.
|
|
356
|
+
*/
|
|
357
|
+
function transitionPlan(input) {
|
|
358
|
+
const still = {
|
|
359
|
+
opacity: "1",
|
|
360
|
+
transform: "none"
|
|
361
|
+
};
|
|
362
|
+
const moves = input.durationMs > 0;
|
|
363
|
+
if (!input.canAnimate || input.reducedMotion || input.effect === "none" || !moves) return {
|
|
364
|
+
animate: false,
|
|
365
|
+
durationMs: 0,
|
|
366
|
+
from: still,
|
|
367
|
+
to: still,
|
|
368
|
+
transition: "none",
|
|
369
|
+
willChange: ""
|
|
370
|
+
};
|
|
371
|
+
const props = animatedProps(input.effect);
|
|
372
|
+
const timing = `${input.durationMs}ms ${input.easing}`;
|
|
373
|
+
return {
|
|
374
|
+
animate: true,
|
|
375
|
+
durationMs: input.durationMs,
|
|
376
|
+
from: {
|
|
377
|
+
opacity: "0",
|
|
378
|
+
transform: START_TRANSFORM[input.effect]
|
|
379
|
+
},
|
|
380
|
+
to: still,
|
|
381
|
+
transition: props.map((p) => `${p} ${timing}`).join(", "),
|
|
382
|
+
willChange: props.join(", ")
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
274
386
|
//#region src/holiday.ts
|
|
275
387
|
const HOLIDAYS = [{
|
|
276
388
|
id: "mid-autumn",
|
|
@@ -449,15 +561,12 @@ window.__ModuleLoader__.load({
|
|
|
449
561
|
const ROTATE_MIN_MS = 5e3;
|
|
450
562
|
/** Slowest allowed dwell time (24 h): one image per day is still "rotation". */
|
|
451
563
|
const ROTATE_MAX_MS = 864e5;
|
|
452
|
-
/** Longest allowed cross-fade; 0 is a hard cut. */
|
|
453
|
-
const ROTATE_FADE_MAX_MS = 3e3;
|
|
454
564
|
/** Everything off: the shipped default, and what a config predating this feature gets. */
|
|
455
565
|
const DEFAULT_ROTATION = {
|
|
456
566
|
enabled: false,
|
|
457
567
|
intervalMs: 6e4,
|
|
458
568
|
order: "order",
|
|
459
|
-
advanceOnSwitch: false
|
|
460
|
-
fadeMs: 320
|
|
569
|
+
advanceOnSwitch: false
|
|
461
570
|
};
|
|
462
571
|
/** A fresh rotation block (never hand out the shared default object). */
|
|
463
572
|
function defaultRotation() {
|
|
@@ -514,6 +623,32 @@ window.__ModuleLoader__.load({
|
|
|
514
623
|
rightbar: 0,
|
|
515
624
|
input: 0
|
|
516
625
|
};
|
|
626
|
+
/** Longest allowed switch duration; 0 is a hard cut. */
|
|
627
|
+
const TRANSITION_MAX_MS = 3e3;
|
|
628
|
+
/** Every accepted `effect`, in UI order. */
|
|
629
|
+
const TRANSITION_EFFECTS = [
|
|
630
|
+
"fade",
|
|
631
|
+
"none",
|
|
632
|
+
"zoom",
|
|
633
|
+
"slide"
|
|
634
|
+
];
|
|
635
|
+
/** Every accepted `easing`, in UI order. */
|
|
636
|
+
const TRANSITION_EASINGS = [
|
|
637
|
+
"ease",
|
|
638
|
+
"linear",
|
|
639
|
+
"ease-out",
|
|
640
|
+
"ease-in-out"
|
|
641
|
+
];
|
|
642
|
+
/** The shipped default: the cross-fade this plugin already performed, at 320 ms. */
|
|
643
|
+
const DEFAULT_TRANSITION = {
|
|
644
|
+
effect: "fade",
|
|
645
|
+
easing: "ease",
|
|
646
|
+
durationMs: 320
|
|
647
|
+
};
|
|
648
|
+
/** A fresh transition block (never hand out the shared default object). */
|
|
649
|
+
function defaultTransition() {
|
|
650
|
+
return { ...DEFAULT_TRANSITION };
|
|
651
|
+
}
|
|
517
652
|
/** Every slot a rule owns, in order. The one thing the render and store layers
|
|
518
653
|
* ask of the image list, so a rule's slots are never enumerated ad hoc. */
|
|
519
654
|
function ruleSlots(rule) {
|
|
@@ -617,6 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
617
752
|
rules: [],
|
|
618
753
|
opacities: { ...DEFAULT_PART_OPACITIES },
|
|
619
754
|
blurs: { ...DEFAULT_PART_BLURS },
|
|
755
|
+
transition: defaultTransition(),
|
|
620
756
|
settingsOpacity: 1,
|
|
621
757
|
chatTextOpacity: 0,
|
|
622
758
|
trajectoryOpacity: 1,
|
|
@@ -662,8 +798,7 @@ window.__ModuleLoader__.load({
|
|
|
662
798
|
enabled: r.enabled === true,
|
|
663
799
|
intervalMs: clamp$1(r.intervalMs, ROTATE_MIN_MS, ROTATE_MAX_MS, d.intervalMs),
|
|
664
800
|
order: ROTATE_ORDERS.includes(r.order) ? r.order : d.order,
|
|
665
|
-
advanceOnSwitch: r.advanceOnSwitch === true
|
|
666
|
-
fadeMs: clamp$1(r.fadeMs, 0, ROTATE_FADE_MAX_MS, d.fadeMs)
|
|
801
|
+
advanceOnSwitch: r.advanceOnSwitch === true
|
|
667
802
|
};
|
|
668
803
|
}
|
|
669
804
|
/** Own-property test for values that may be anything at all (a raw JSON entry). */
|
|
@@ -671,6 +806,54 @@ window.__ModuleLoader__.load({
|
|
|
671
806
|
return Object.prototype.hasOwnProperty.call(o, key);
|
|
672
807
|
}
|
|
673
808
|
/**
|
|
809
|
+
* The duration a config written before the switch effect carries on any of its
|
|
810
|
+
* rules, or `undefined` when it carries none worth adopting.
|
|
811
|
+
*
|
|
812
|
+
* `rotate.fadeMs` was the per-rule duration from 0.7.0 on, and it had NO control
|
|
813
|
+
* anywhere — it could only be hand-edited, which is why the global setting
|
|
814
|
+
* replaced it. This is the one-shot lift that keeps such a hand-tuned value from
|
|
815
|
+
* being silently dropped on upgrade: the first rule whose value differs from the
|
|
816
|
+
* 320 ms every build shipped is adopted as the global duration. Values that are
|
|
817
|
+
* all the default are ignored, so an ordinary config keeps the ordinary default,
|
|
818
|
+
* and rules that DISAGREE cannot be represented by one global number — the first
|
|
819
|
+
* deliberate value wins, and the others are gone with the field they lived on.
|
|
820
|
+
*/
|
|
821
|
+
function legacyFadeMs(rawRules) {
|
|
822
|
+
if (!Array.isArray(rawRules)) return void 0;
|
|
823
|
+
for (const entry of rawRules) {
|
|
824
|
+
const rotate = entry?.rotate;
|
|
825
|
+
if (rotate === null || typeof rotate !== "object") continue;
|
|
826
|
+
const value = rotate.fadeMs;
|
|
827
|
+
if (typeof value !== "number" || !isFinite(value)) continue;
|
|
828
|
+
if (value === DEFAULT_TRANSITION.durationMs) continue;
|
|
829
|
+
return clamp$1(value, 0, TRANSITION_MAX_MS, DEFAULT_TRANSITION.durationMs);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Coerce one persisted transition block; a config written before this setting
|
|
834
|
+
* existed has no `transition` key at all and gets the shipped default, which is
|
|
835
|
+
* the behaviour it already had.
|
|
836
|
+
*
|
|
837
|
+
* `legacyMs` is the per-rule `fadeMs` this shape replaced (see `legacyFadeMs`),
|
|
838
|
+
* adopted only when the incoming config has no `durationMs` of its own — so a
|
|
839
|
+
* deliberate hand-tuned blend survives the upgrade instead of being dropped with
|
|
840
|
+
* the field it used to live on.
|
|
841
|
+
*
|
|
842
|
+
* `durationMs` is clamped with `0` kept as a real value (a hard cut) rather than
|
|
843
|
+
* read as "absent": 0 is exactly how a hard cut is asked for without changing
|
|
844
|
+
* the effect.
|
|
845
|
+
*/
|
|
846
|
+
function normalizeTransition(raw, legacyMs) {
|
|
847
|
+
const t = raw ?? {};
|
|
848
|
+
const d = DEFAULT_TRANSITION;
|
|
849
|
+
const fallback = typeof legacyMs === "number" && isFinite(legacyMs) ? legacyMs : d.durationMs;
|
|
850
|
+
return {
|
|
851
|
+
effect: TRANSITION_EFFECTS.includes(t.effect) ? t.effect : d.effect,
|
|
852
|
+
easing: TRANSITION_EASINGS.includes(t.easing) ? t.easing : d.easing,
|
|
853
|
+
durationMs: clamp$1(t.durationMs, 0, TRANSITION_MAX_MS, fallback)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
674
857
|
* Coerce one persisted image entry, or null when it lacks a usable slot.
|
|
675
858
|
*
|
|
676
859
|
* `inherit` is the RULE's own color, and it is applied only to an entry that has
|
|
@@ -807,6 +990,7 @@ window.__ModuleLoader__.load({
|
|
|
807
990
|
rules,
|
|
808
991
|
opacities,
|
|
809
992
|
blurs,
|
|
993
|
+
transition: normalizeTransition(r.transition, legacyFadeMs(r.rules)),
|
|
810
994
|
settingsOpacity: clamp01(r.settingsOpacity, 1),
|
|
811
995
|
chatTextOpacity: clamp01(r.chatTextOpacity, 0),
|
|
812
996
|
trajectoryOpacity: clamp01(r.trajectoryOpacity, 1),
|
|
@@ -1084,13 +1268,26 @@ window.__ModuleLoader__.load({
|
|
|
1084
1268
|
function rBgState() {
|
|
1085
1269
|
return activeImage()?.bgState ?? DEFAULT_BG_STATE;
|
|
1086
1270
|
}
|
|
1087
|
-
/**
|
|
1088
|
-
|
|
1089
|
-
|
|
1271
|
+
/**
|
|
1272
|
+
* The GLOBAL wallpaper-switch transition (effect, easing, duration).
|
|
1273
|
+
*
|
|
1274
|
+
* Read from the config on every apply rather than cached: the Config page edits
|
|
1275
|
+
* it while a wallpaper is on screen, and the next switch has to use what the
|
|
1276
|
+
* user just chose.
|
|
1277
|
+
*/
|
|
1278
|
+
function rTransition() {
|
|
1279
|
+
return cfg.transition ?? DEFAULT_TRANSITION;
|
|
1090
1280
|
}
|
|
1091
|
-
/**
|
|
1281
|
+
/**
|
|
1282
|
+
* Duration the next switch runs for, in ms (`0` = a hard cut).
|
|
1283
|
+
*
|
|
1284
|
+
* The two rungs are `resolveFadeMs`'s business (./transition): the effect can
|
|
1285
|
+
* veto the animation outright, and otherwise the global duration decides. It is
|
|
1286
|
+
* deliberately NOT a per-rule value — a rule has no notion of a transition, and
|
|
1287
|
+
* the per-rule field this replaced had no control anywhere to set it with.
|
|
1288
|
+
*/
|
|
1092
1289
|
function rFadeMs() {
|
|
1093
|
-
return
|
|
1290
|
+
return resolveFadeMs(rTransition());
|
|
1094
1291
|
}
|
|
1095
1292
|
/** Paintable URL of the active rule's CURRENT image, or null when it has none. */
|
|
1096
1293
|
function rWp() {
|
|
@@ -1313,7 +1510,7 @@ window.__ModuleLoader__.load({
|
|
|
1313
1510
|
const d = data;
|
|
1314
1511
|
const slots = Array.isArray(d.slots) ? d.slots.filter((s) => typeof s === "string") : [];
|
|
1315
1512
|
const schema = typeof d.schema === "number" && isFinite(d.schema) ? d.schema : null;
|
|
1316
|
-
hostShape = schema === null ? "legacy" : schema >=
|
|
1513
|
+
hostShape = schema === null ? "legacy" : schema >= 5 ? "current" : "legacy";
|
|
1317
1514
|
if (hostShape === "legacy") holdWrite();
|
|
1318
1515
|
return {
|
|
1319
1516
|
config: d.config,
|
|
@@ -1371,6 +1568,60 @@ window.__ModuleLoader__.load({
|
|
|
1371
1568
|
return parts.length > 0 ? parts.join(" ") : null;
|
|
1372
1569
|
}
|
|
1373
1570
|
//#endregion
|
|
1571
|
+
//#region src/client/palette-fade.ts
|
|
1572
|
+
/**
|
|
1573
|
+
* Whether a palette change may interpolate at all.
|
|
1574
|
+
*
|
|
1575
|
+
* Only while the SCHEME stays put. A light↔dark flip is not a tint of the same
|
|
1576
|
+
* palette, it is a different palette: interpolating between them walks the whole
|
|
1577
|
+
* interface through mid-tones whose ink is wrong in both directions (dark text
|
|
1578
|
+
* half-way into a dark palette), which looks worse than the switch it replaces.
|
|
1579
|
+
* The wallpaper has no such problem — one image fades into another either way —
|
|
1580
|
+
* so its own animation is deliberately untouched by this.
|
|
1581
|
+
*
|
|
1582
|
+
* `previous === null` means nothing has been painted yet (the first apply after a
|
|
1583
|
+
* load): there is no colour to come from, so there is nothing to fade.
|
|
1584
|
+
*/
|
|
1585
|
+
function paletteFadeAllowed(previous, next) {
|
|
1586
|
+
return previous === next;
|
|
1587
|
+
}
|
|
1588
|
+
/** One `@property` block per registered token. */
|
|
1589
|
+
function registrations(names, initial) {
|
|
1590
|
+
return names.map((n) => `@property ${n}{syntax:'<color>';inherits:true;initial-value:${initial[n]}}`).join("");
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* The stylesheet for one state: the registrations always, the transition only
|
|
1594
|
+
* while a palette change is actually being painted.
|
|
1595
|
+
*
|
|
1596
|
+
* `durationMs = 0` deliberately emits NO transition rule. That is not only
|
|
1597
|
+
* "instant": it is what keeps a slider drag instant, because the same call sites
|
|
1598
|
+
* rewrite the `body{…}` rule on every frame of a drag and a live transition on
|
|
1599
|
+
* those tokens would make the alphas lag behind the pointer.
|
|
1600
|
+
*/
|
|
1601
|
+
function paletteFadeCss(input) {
|
|
1602
|
+
const names = Object.keys(input.initial).filter((n) => n.startsWith("--"));
|
|
1603
|
+
if (names.length === 0) return "";
|
|
1604
|
+
const css = registrations(names, input.initial);
|
|
1605
|
+
if (!(input.durationMs > 0)) return css;
|
|
1606
|
+
const timing = (n) => `${n} ${input.durationMs}ms ${input.easing}`;
|
|
1607
|
+
return `${css}${input.scopes.map((s) => ({
|
|
1608
|
+
selector: s.selector,
|
|
1609
|
+
transitions: [...s.tokens.filter((n) => names.includes(n)), ...s.properties ?? []]
|
|
1610
|
+
})).filter((s) => s.transitions.length > 0).map((s) => `${s.selector}{transition:${s.transitions.map(timing).join(",")}}`).join("")}`;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* The same fade for an element the plugin paints INLINE (its columns and cards).
|
|
1614
|
+
*
|
|
1615
|
+
* No custom property is involved there — the element's own `background-color` is
|
|
1616
|
+
* what changes — so the standard property is what has to be told. `0` means
|
|
1617
|
+
* `none` rather than an empty value: an empty value would leave whatever the host
|
|
1618
|
+
* had declared on that element, and the point of the 0 case is that a drag must
|
|
1619
|
+
* not fade at all.
|
|
1620
|
+
*/
|
|
1621
|
+
function paletteFadeInline(durationMs, easing) {
|
|
1622
|
+
return durationMs > 0 ? `background-color ${durationMs}ms ${easing}` : "none";
|
|
1623
|
+
}
|
|
1624
|
+
//#endregion
|
|
1374
1625
|
//#region src/client/utils/color.ts
|
|
1375
1626
|
function hsvToHsl(h, s, v) {
|
|
1376
1627
|
const l = v * (1 - s / 2);
|
|
@@ -1890,6 +2141,11 @@ window.__ModuleLoader__.load({
|
|
|
1890
2141
|
|
|
1891
2142
|
/* ── profile page ────────────────────────────────────────────────────────── */
|
|
1892
2143
|
.dab-profile-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));gap:13px}
|
|
2144
|
+
/* The switch-effect preview: a two-tone swatch that replays the chosen effect in
|
|
2145
|
+
the panel. It exists because a wallpaper fading into ITSELF is invisible —
|
|
2146
|
+
the one thing a transition setting must not leave the user guessing about. */
|
|
2147
|
+
.dab-tr-preview{position:relative;overflow:hidden;flex:1;min-width:0;height:74px;border-radius:12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);background-image:linear-gradient(120deg,color-mix(in srgb,var(--dsw-alias-label-tertiary) 24%,transparent),transparent 70%)}
|
|
2148
|
+
.dab-tr-preview-in{position:absolute;inset:0;background-image:linear-gradient(120deg,var(--dsw-alias-brand-primary),color-mix(in srgb,var(--dsw-alias-brand-primary) 50%,var(--dsw-alias-bg-layer-1)))}
|
|
1893
2149
|
.dab-profile-ico{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;margin-bottom:13px;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
1894
2150
|
.dab-profile-title{font-size:14px;font-weight:650}
|
|
1895
2151
|
.dab-profile-desc{font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.55;margin:5px 0 15px}
|
|
@@ -2323,19 +2579,70 @@ window.__ModuleLoader__.load({
|
|
|
2323
2579
|
}
|
|
2324
2580
|
let pendingOps = null;
|
|
2325
2581
|
let tokensRaf = null;
|
|
2582
|
+
/** When the pending frame was armed, so a frame that never ran can be spotted. */
|
|
2583
|
+
let tokensArmedAt = 0;
|
|
2584
|
+
/** A frame outstanding for longer than this is treated as lost (see below). */
|
|
2585
|
+
const STALE_TOKENS_RAF_MS = 1e3;
|
|
2586
|
+
/** The fingerprint of the palette + colour in force; see `applyCustomTokensNow`. */
|
|
2587
|
+
function paletteKey(src, color) {
|
|
2588
|
+
return src.own ? `own:${color[0]}|${color[1]}|${color[2]}` : `host:${HOST_SURFACE_TOKENS.map((n) => src.tokens[n] ?? "").join("|")}`;
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Re-emit the interface palette, coalescing a slider drag into one frame.
|
|
2592
|
+
*
|
|
2593
|
+
* A PALETTE change is never coalesced, and that distinction is the whole point:
|
|
2594
|
+
* this call is the only writer of the palette's INK. Every other surface has a
|
|
2595
|
+
* second, synchronous writer (the dialog's layer tokens come from
|
|
2596
|
+
* `applySettingsOverrides`, the columns' backgrounds from the view cards, the
|
|
2597
|
+
* `--dsh-any-op-*` alphas from the same callback as the ink), so a palette update
|
|
2598
|
+
* that is deferred and then lost leaves the surfaces on the NEW colour and the
|
|
2599
|
+
* ink on the previous one — a dark palette with dark text, which reads as
|
|
2600
|
+
* "the theme colour did nothing" and only a reload clears it.
|
|
2601
|
+
*
|
|
2602
|
+
* Measured in the field: a window that is not rendering can swallow the frame
|
|
2603
|
+
* (the id stays armed, so every later update parks behind it), and a failure
|
|
2604
|
+
* inside the write used to be swallowed by an empty `catch`. Both are handled
|
|
2605
|
+
* here — a changed palette goes through synchronously, a stale frame is dropped
|
|
2606
|
+
* instead of blocking forever, and the failure is logged (see `applyCustomTokensNow`).
|
|
2607
|
+
*/
|
|
2326
2608
|
function applyCustomTokens(ops) {
|
|
2327
2609
|
pendingOps = ops;
|
|
2328
|
-
|
|
2610
|
+
const src = palette();
|
|
2611
|
+
if (src !== null && paletteKey(src, rColor()) !== baseTokenKey) {
|
|
2612
|
+
flushTokens();
|
|
2613
|
+
return lastFadeable;
|
|
2614
|
+
}
|
|
2615
|
+
if (tokensRaf !== null) {
|
|
2616
|
+
if (Date.now() - tokensArmedAt <= STALE_TOKENS_RAF_MS) return false;
|
|
2617
|
+
window.cancelAnimationFrame(tokensRaf);
|
|
2618
|
+
tokensRaf = null;
|
|
2619
|
+
}
|
|
2620
|
+
tokensArmedAt = Date.now();
|
|
2329
2621
|
tokensRaf = requestAnimationFrame(() => {
|
|
2330
2622
|
tokensRaf = null;
|
|
2331
|
-
|
|
2332
|
-
const o = pendingOps;
|
|
2333
|
-
pendingOps = null;
|
|
2334
|
-
applyCustomTokensNow(o);
|
|
2623
|
+
flushTokens();
|
|
2335
2624
|
});
|
|
2625
|
+
return false;
|
|
2626
|
+
}
|
|
2627
|
+
/** Run the pending token write now (one funnel, so no path can skip it). */
|
|
2628
|
+
function flushTokens() {
|
|
2629
|
+
if (pendingOps === null) return;
|
|
2630
|
+
const o = pendingOps;
|
|
2631
|
+
pendingOps = null;
|
|
2632
|
+
applyCustomTokensNow(o);
|
|
2336
2633
|
}
|
|
2337
2634
|
let lastBgKey = "";
|
|
2635
|
+
/**
|
|
2636
|
+
* Whether the palette write in flight may fade — the answer `applyCustomTokens`
|
|
2637
|
+
* hands back to the callers that paint their own surfaces inline.
|
|
2638
|
+
*
|
|
2639
|
+
* It has to travel out of `applyCustomTokensNow` (which is where the decision is
|
|
2640
|
+
* made) because those callers run AFTER it in the same task, and re-deciding
|
|
2641
|
+
* there would be a second opinion on the same question.
|
|
2642
|
+
*/
|
|
2643
|
+
let lastFadeable = false;
|
|
2338
2644
|
function applyCustomTokensNow(ops) {
|
|
2645
|
+
lastFadeable = false;
|
|
2339
2646
|
const src = palette();
|
|
2340
2647
|
if (src === null) {
|
|
2341
2648
|
clearCustomTokens();
|
|
@@ -2347,8 +2654,18 @@ window.__ModuleLoader__.load({
|
|
|
2347
2654
|
const [h, s, l] = rColor();
|
|
2348
2655
|
try {
|
|
2349
2656
|
const forceDark = own && l < .55;
|
|
2350
|
-
const key =
|
|
2351
|
-
|
|
2657
|
+
const key = paletteKey(src, [
|
|
2658
|
+
h,
|
|
2659
|
+
s,
|
|
2660
|
+
l
|
|
2661
|
+
]);
|
|
2662
|
+
const paletteChanged = key !== baseTokenKey;
|
|
2663
|
+
const scheme = own ? forceDark ? "dark" : "light" : resolveHostDark() ? "dark" : "light";
|
|
2664
|
+
const fadeable = paletteChanged && paletteFadeAllowed(paletteScheme, scheme);
|
|
2665
|
+
lastFadeable = fadeable;
|
|
2666
|
+
applyPaletteFade(fadeable);
|
|
2667
|
+
if (paletteChanged) {
|
|
2668
|
+
paletteScheme = scheme;
|
|
2352
2669
|
if (own) {
|
|
2353
2670
|
if (forceDark) document.body.setAttribute(DARK_ATTR, OWN_SCHEME);
|
|
2354
2671
|
else document.body.removeAttribute(DARK_ATTR);
|
|
@@ -2371,13 +2688,16 @@ window.__ModuleLoader__.load({
|
|
|
2371
2688
|
const bgKey = `${baseTokenKey}|${ops.bg}`;
|
|
2372
2689
|
if (bgKey !== lastBgKey) {
|
|
2373
2690
|
lastBgKey = bgKey;
|
|
2374
|
-
applyPartOpacities(ops);
|
|
2691
|
+
applyPartOpacities(ops, fadeable);
|
|
2375
2692
|
}
|
|
2376
|
-
} catch {
|
|
2693
|
+
} catch (e) {
|
|
2694
|
+
console.warn("dsh-background-by-model: could not re-emit the palette tokens", e);
|
|
2695
|
+
}
|
|
2377
2696
|
}
|
|
2378
2697
|
const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
|
|
2379
2698
|
const SETTINGS_STYLE_RULE = `${SETTINGS_PANEL_SEL}{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2));backdrop-filter:var(--dsh-any-blur-settings,none);}html[${THEMED_ATTR}] ${SETTINGS_PANEL_SEL}{--dsw-alias-bg-layer-1:var(--dsh-any-bg-settings-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-bg-settings-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-bg-settings-layer-3)}${SETTINGS_PANEL_SEL} .dab-card{backdrop-filter:var(--dsh-any-blur-card-panels,none);-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,none)}`;
|
|
2380
|
-
const
|
|
2699
|
+
const CORDIS_PANEL_SEL = "[data-cordis-panel]";
|
|
2700
|
+
const INPUT_BLUR_RULE = `[data-composer-card],${CORDIS_PANEL_SEL}{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}${CORDIS_PANEL_SEL}{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}`;
|
|
2381
2701
|
function applyInputBlur(px) {
|
|
2382
2702
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
|
|
2383
2703
|
else document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
@@ -2408,7 +2728,148 @@ window.__ModuleLoader__.load({
|
|
|
2408
2728
|
write("--dsh-any-traj-layer-2", "--dsw-alias-bg-layer-2");
|
|
2409
2729
|
write("--dsh-any-traj-layer-3", "--dsw-alias-bg-layer-3");
|
|
2410
2730
|
}
|
|
2411
|
-
const
|
|
2731
|
+
const TRAJECTORY_ROOT_SEL = "[data-conversation-composer-overlay]";
|
|
2732
|
+
/** Elements whose own token declarations change when the palette does: the body
|
|
2733
|
+
* (which everything else inherits), and the two surfaces that RE-declare the
|
|
2734
|
+
* layer tokens instead of inheriting them.
|
|
2735
|
+
*
|
|
2736
|
+
* The three layer tokens are also the ONLY ones those two surfaces may carry a
|
|
2737
|
+
* transition for. Handing them the full palette is not "more of the same": a
|
|
2738
|
+
* transition on an INHERITED token makes the element interpolate towards the
|
|
2739
|
+
* stale value it saw when the fade was armed, and everything under it rides that
|
|
2740
|
+
* lagged copy — the settings dialog's own controls (the holiday switch fill, the
|
|
2741
|
+
* active chips) then hold the previous theme colour for the whole switch and jump
|
|
2742
|
+
* at the end. See ./palette-fade for the measurement. */
|
|
2743
|
+
const FADE_LAYER_TOKENS = [
|
|
2744
|
+
"--dsw-alias-bg-layer-1",
|
|
2745
|
+
"--dsw-alias-bg-layer-2",
|
|
2746
|
+
"--dsw-alias-bg-layer-3"
|
|
2747
|
+
];
|
|
2748
|
+
/** The token names the plugin re-emits. One palette is enough: every branch
|
|
2749
|
+
* defines the same set, and an unknown name would only be registered when the
|
|
2750
|
+
* host can answer for it (see `fadeInitials`). */
|
|
2751
|
+
function fadeTokenNames() {
|
|
2752
|
+
return Object.keys(genTokens(220, .55, .25).tokens);
|
|
2753
|
+
}
|
|
2754
|
+
/** One `transition` list per element that DECLARES palette tokens, each carrying
|
|
2755
|
+
* only the tokens that element actually declares.
|
|
2756
|
+
*
|
|
2757
|
+
* Two of the entries carry a STANDARD property instead, because those surfaces
|
|
2758
|
+
* are not painted from a registered token: the settings dialog's plate and the
|
|
2759
|
+
* file-preview panel read a colour the plugin owns as a plain variable
|
|
2760
|
+
* (`--dsh-any-bg-settings-surface`, `--dsh-any-bg-rightbar`) and would otherwise
|
|
2761
|
+
* snap in one frame while every surface around them fades. The Cordis panel is
|
|
2762
|
+
* the third case of the same kind — it re-declares `--dsw-specific-menu` from
|
|
2763
|
+
* `--dsh-any-op-menu-cordis`, which is also unregistered, but since the token it
|
|
2764
|
+
* re-declares IS one, naming the token is what reaches it. */
|
|
2765
|
+
function fadeScopes() {
|
|
2766
|
+
return [
|
|
2767
|
+
{
|
|
2768
|
+
selector: "body",
|
|
2769
|
+
tokens: fadeTokenNames()
|
|
2770
|
+
},
|
|
2771
|
+
{
|
|
2772
|
+
selector: SETTINGS_PANEL_SEL,
|
|
2773
|
+
tokens: FADE_LAYER_TOKENS,
|
|
2774
|
+
properties: ["background-color"]
|
|
2775
|
+
},
|
|
2776
|
+
{
|
|
2777
|
+
selector: TRAJECTORY_ROOT_SEL,
|
|
2778
|
+
tokens: FADE_LAYER_TOKENS
|
|
2779
|
+
},
|
|
2780
|
+
{
|
|
2781
|
+
selector: RIGHTBAR_SEL,
|
|
2782
|
+
tokens: [],
|
|
2783
|
+
properties: ["background-color"]
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
selector: CORDIS_PANEL_SEL,
|
|
2787
|
+
tokens: ["--dsw-specific-menu"]
|
|
2788
|
+
}
|
|
2789
|
+
];
|
|
2790
|
+
}
|
|
2791
|
+
let fadeStyleEl = null;
|
|
2792
|
+
let fadeInitials = null;
|
|
2793
|
+
let fadeCssCache = "";
|
|
2794
|
+
/** The scheme the last written palette had, so a FLIP can be told from a tint. */
|
|
2795
|
+
let paletteScheme = null;
|
|
2796
|
+
function ensureFadeSheet() {
|
|
2797
|
+
if (fadeStyleEl?.isConnected) return fadeStyleEl;
|
|
2798
|
+
fadeStyleEl = document.createElement("style");
|
|
2799
|
+
fadeStyleEl.dataset.plugin = "dsh-background-by-model-fade";
|
|
2800
|
+
markOwnSheet(fadeStyleEl);
|
|
2801
|
+
document.head.appendChild(fadeStyleEl);
|
|
2802
|
+
return fadeStyleEl;
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* The host's own value for each token — the `initial-value` a registration needs,
|
|
2806
|
+
* and the test for whether a token may be registered at all.
|
|
2807
|
+
*
|
|
2808
|
+
* Read with the plugin's token sheet muted, for the same reason `readHostPalette`
|
|
2809
|
+
* does it: our rule re-emits the very names being asked for. Cached, because the
|
|
2810
|
+
* answer only changes with the host's scheme and a stale hint is only ever used
|
|
2811
|
+
* for a token the host does not publish anyway.
|
|
2812
|
+
*/
|
|
2813
|
+
function fadeInitialsOf(names) {
|
|
2814
|
+
if (fadeInitials !== null) return fadeInitials;
|
|
2815
|
+
const sheet = tokenStyleEl;
|
|
2816
|
+
const muted = sheet !== null && sheet.isConnected && !sheet.disabled;
|
|
2817
|
+
if (muted) sheet.disabled = true;
|
|
2818
|
+
try {
|
|
2819
|
+
const computed = getComputedStyle(document.body);
|
|
2820
|
+
const out = {};
|
|
2821
|
+
for (const name of names) {
|
|
2822
|
+
const used = resolveCssColor(computed.getPropertyValue(name));
|
|
2823
|
+
if (used !== null) out[name] = used;
|
|
2824
|
+
}
|
|
2825
|
+
fadeInitials = out;
|
|
2826
|
+
return out;
|
|
2827
|
+
} catch {
|
|
2828
|
+
fadeInitials = {};
|
|
2829
|
+
return fadeInitials;
|
|
2830
|
+
} finally {
|
|
2831
|
+
if (muted) sheet.disabled = false;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
/** How long a colour change may take, or `0` for "do not animate it".
|
|
2835
|
+
*
|
|
2836
|
+
* The same decision the wallpaper layer makes (`transitionPlan` over the global
|
|
2837
|
+
* effect, the resolved duration and the OS preference), so the two halves of one
|
|
2838
|
+
* switch can never disagree about whether they are animating. */
|
|
2839
|
+
function paletteFadeMs() {
|
|
2840
|
+
const tr = rTransition();
|
|
2841
|
+
const plan = transitionPlan({
|
|
2842
|
+
effect: tr.effect,
|
|
2843
|
+
easing: tr.easing,
|
|
2844
|
+
durationMs: rFadeMs(),
|
|
2845
|
+
canAnimate: true,
|
|
2846
|
+
reducedMotion: prefersReducedMotion()
|
|
2847
|
+
});
|
|
2848
|
+
return plan.animate ? plan.durationMs : 0;
|
|
2849
|
+
}
|
|
2850
|
+
/** Arm the fade for a palette change, or disarm it for anything else.
|
|
2851
|
+
*
|
|
2852
|
+
* `animate` is false on every pass that only moves an alpha (a slider drag): the
|
|
2853
|
+
* tokens are rewritten there too, and a live transition would make the surface
|
|
2854
|
+
* lag behind the pointer. */
|
|
2855
|
+
function applyPaletteFade(animate) {
|
|
2856
|
+
const initials = fadeInitialsOf(fadeTokenNames());
|
|
2857
|
+
const css = paletteFadeCss({
|
|
2858
|
+
scopes: fadeScopes(),
|
|
2859
|
+
initial: initials,
|
|
2860
|
+
durationMs: animate ? paletteFadeMs() : 0,
|
|
2861
|
+
easing: rTransition().easing
|
|
2862
|
+
});
|
|
2863
|
+
if (css === fadeCssCache) return;
|
|
2864
|
+
fadeCssCache = css;
|
|
2865
|
+
ensureFadeSheet().textContent = css;
|
|
2866
|
+
}
|
|
2867
|
+
/** Inline `transition` for an element the plugin paints itself; `0` disarms it. */
|
|
2868
|
+
function fadeInlineFor(animate) {
|
|
2869
|
+
return paletteFadeInline(animate ? paletteFadeMs() : 0, rTransition().easing);
|
|
2870
|
+
}
|
|
2871
|
+
const RIGHTBAR_SEL = "div[data-sidebar-right-panel]:not(:has([data-dockkit-content],[data-dockkit-empty]))[data-sidebar-right-open],div[data-sidebar-right-panel] [data-dockkit-content]:not([data-dockkit-float]),div[data-sidebar-right-panel] [data-dockkit-empty]";
|
|
2872
|
+
const RIGHTBAR_STYLE_RULE = `${RIGHTBAR_SEL}{background:var(--dsh-any-bg-rightbar,var(--dsw-alias-bg-base))!important}div[data-sidebar-right-panel][data-sidebar-right-open]{-webkit-backdrop-filter:var(--dsh-any-blur-rightbar,none);backdrop-filter:var(--dsh-any-blur-rightbar,none)}`;
|
|
2412
2873
|
/** Paint the file-preview panel's surface from its own slider. */
|
|
2413
2874
|
function applyRightbarOverrides(op) {
|
|
2414
2875
|
const root = document.documentElement;
|
|
@@ -2485,7 +2946,7 @@ window.__ModuleLoader__.load({
|
|
|
2485
2946
|
* the frame. The frame's translucent bg-base sits UNDER the sidebar, so
|
|
2486
2947
|
* reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
|
|
2487
2948
|
* the alpha onto the columns keeps the sidebar owned by its own slider. */
|
|
2488
|
-
function applyPartOpacities(ops) {
|
|
2949
|
+
function applyPartOpacities(ops, fade = false) {
|
|
2489
2950
|
discoverParts();
|
|
2490
2951
|
if (frameEl === null) return;
|
|
2491
2952
|
const base = palette()?.tokens["--dsw-alias-bg-base"];
|
|
@@ -2498,8 +2959,15 @@ window.__ModuleLoader__.load({
|
|
|
2498
2959
|
return;
|
|
2499
2960
|
}
|
|
2500
2961
|
frameEl.style.background = "transparent";
|
|
2501
|
-
|
|
2502
|
-
if (
|
|
2962
|
+
const transition = fadeInlineFor(fade);
|
|
2963
|
+
if (centerEl !== null) {
|
|
2964
|
+
centerEl.style.transition = transition;
|
|
2965
|
+
centerEl.style.background = withAlpha(base, ops.bg);
|
|
2966
|
+
}
|
|
2967
|
+
if (detailsEl !== null) {
|
|
2968
|
+
detailsEl.style.transition = transition;
|
|
2969
|
+
detailsEl.style.background = withAlpha(base, ops.bg);
|
|
2970
|
+
}
|
|
2503
2971
|
}
|
|
2504
2972
|
/** Blur of the option panels inside the settings dialog (.dab-card), owned by
|
|
2505
2973
|
* the "dialog option panel" (card) blur slider. Written as a plugin-owned
|
|
@@ -2709,10 +3177,11 @@ window.__ModuleLoader__.load({
|
|
|
2709
3177
|
if (!tableFixStyleEl.isConnected) document.head.appendChild(tableFixStyleEl);
|
|
2710
3178
|
}
|
|
2711
3179
|
/** Re-derive the conversation view cards from the current config. */
|
|
2712
|
-
function applyViewCards() {
|
|
3180
|
+
function applyViewCards(fade = false) {
|
|
2713
3181
|
discoverParts();
|
|
2714
3182
|
if (centerEl === null) return;
|
|
2715
3183
|
const surface = palette()?.tokens["--dsw-alias-bg-layer-1"];
|
|
3184
|
+
const transition = fadeInlineFor(fade);
|
|
2716
3185
|
VIEW_CARDS.forEach((spec, i) => {
|
|
2717
3186
|
const target = discoverViewTarget(i, spec);
|
|
2718
3187
|
if (target === null) return;
|
|
@@ -2722,6 +3191,7 @@ window.__ModuleLoader__.load({
|
|
|
2722
3191
|
if (!plain) {
|
|
2723
3192
|
if (!target.hasAttribute(spec.mark)) stashCardPrev(target, spec.prev, false);
|
|
2724
3193
|
const borderAlpha = opacity > 0 ? Math.min(1, opacity * 1.5) : blurPx > 0 ? .35 : 0;
|
|
3194
|
+
target.style.transition = transition;
|
|
2725
3195
|
target.style.background = surface !== void 0 ? withAlpha(surface, opacity) : "transparent";
|
|
2726
3196
|
target.style.border = surface !== void 0 ? `1px solid ${withAlpha(surface, borderAlpha)}` : "1px solid transparent";
|
|
2727
3197
|
target.style.borderRadius = "16px";
|
|
@@ -2844,6 +3314,13 @@ window.__ModuleLoader__.load({
|
|
|
2844
3314
|
l.el.style.backgroundImage = "";
|
|
2845
3315
|
l.el.style.backgroundSize = "";
|
|
2846
3316
|
l.el.style.backgroundPosition = "";
|
|
3317
|
+
l.el.style.transform = "none";
|
|
3318
|
+
}
|
|
3319
|
+
/** Write the two properties a switch animates. Both are always written, so a
|
|
3320
|
+
* layer can never keep half of a previous effect. */
|
|
3321
|
+
function applyLayerStyle(el, style) {
|
|
3322
|
+
el.style.opacity = style.opacity;
|
|
3323
|
+
el.style.transform = style.transform;
|
|
2847
3324
|
}
|
|
2848
3325
|
/** Intrinsic-size cache for the center mode (native pixels of the current image). */
|
|
2849
3326
|
let imgNat = null;
|
|
@@ -2912,13 +3389,18 @@ window.__ModuleLoader__.load({
|
|
|
2912
3389
|
});
|
|
2913
3390
|
}
|
|
2914
3391
|
}
|
|
2915
|
-
/** Whether
|
|
2916
|
-
*
|
|
2917
|
-
|
|
3392
|
+
/** Whether there is anything to animate FROM: a wallpaper on screen that is
|
|
3393
|
+
* actually visible. The duration question belongs to `rFadeMs`, and the
|
|
3394
|
+
* `reduced-motion` veto to `transitionPlan` — this answers only the layer half,
|
|
3395
|
+
* so a first paint (no previous image) never fades in from nothing. */
|
|
3396
|
+
function canAnimateFrom(from) {
|
|
2918
3397
|
if (from.url === null) return false;
|
|
2919
|
-
|
|
3398
|
+
return rWop() > .01;
|
|
3399
|
+
}
|
|
3400
|
+
/** Whether the OS asks for reduced motion right now. */
|
|
3401
|
+
function prefersReducedMotion() {
|
|
2920
3402
|
const mm = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
2921
|
-
return mm
|
|
3403
|
+
return mm !== null && mm.matches;
|
|
2922
3404
|
}
|
|
2923
3405
|
/** End an in-flight fade: promote the incoming layer, free the one under it. */
|
|
2924
3406
|
function finishFade(idx) {
|
|
@@ -2931,6 +3413,7 @@ window.__ModuleLoader__.load({
|
|
|
2931
3413
|
front = idx;
|
|
2932
3414
|
const el = layers[idx].el;
|
|
2933
3415
|
el.style.opacity = "1";
|
|
3416
|
+
el.style.transform = "none";
|
|
2934
3417
|
el.style.willChange = "";
|
|
2935
3418
|
clearLayer(layers[idx === 0 ? 1 : 0]);
|
|
2936
3419
|
}
|
|
@@ -2947,6 +3430,7 @@ window.__ModuleLoader__.load({
|
|
|
2947
3430
|
const el = layers[idx].el;
|
|
2948
3431
|
el.style.transition = "none";
|
|
2949
3432
|
el.style.opacity = "1";
|
|
3433
|
+
el.style.transform = "none";
|
|
2950
3434
|
el.style.willChange = "";
|
|
2951
3435
|
clearLayer(layers[idx === 0 ? 1 : 0]);
|
|
2952
3436
|
}
|
|
@@ -2962,6 +3446,7 @@ window.__ModuleLoader__.load({
|
|
|
2962
3446
|
const el = layers[idx].el;
|
|
2963
3447
|
el.style.transition = "none";
|
|
2964
3448
|
el.style.opacity = "0";
|
|
3449
|
+
el.style.transform = "none";
|
|
2965
3450
|
el.style.willChange = "";
|
|
2966
3451
|
clearLayer(layers[idx]);
|
|
2967
3452
|
}
|
|
@@ -2992,27 +3477,34 @@ window.__ModuleLoader__.load({
|
|
|
2992
3477
|
const idx = front === 0 ? 1 : 0;
|
|
2993
3478
|
const target = ls[idx];
|
|
2994
3479
|
const el = target.el;
|
|
3480
|
+
const tr = rTransition();
|
|
3481
|
+
const plan = transitionPlan({
|
|
3482
|
+
effect: tr.effect,
|
|
3483
|
+
easing: tr.easing,
|
|
3484
|
+
durationMs: rFadeMs(),
|
|
3485
|
+
canAnimate: canAnimateFrom(from),
|
|
3486
|
+
reducedMotion: prefersReducedMotion()
|
|
3487
|
+
});
|
|
2995
3488
|
el.style.transition = "none";
|
|
2996
|
-
el.style.
|
|
3489
|
+
el.style.willChange = plan.willChange;
|
|
3490
|
+
applyLayerStyle(el, plan.from);
|
|
2997
3491
|
el.style.zIndex = "1";
|
|
2998
3492
|
from.el.style.zIndex = "0";
|
|
2999
3493
|
paintLayer(target, url);
|
|
3000
3494
|
applyWpEffects();
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
el.style.opacity = "1";
|
|
3495
|
+
if (!plan.animate) {
|
|
3496
|
+
applyLayerStyle(el, plan.to);
|
|
3004
3497
|
front = idx;
|
|
3005
3498
|
clearLayer(from);
|
|
3006
3499
|
return;
|
|
3007
3500
|
}
|
|
3008
|
-
el.style.willChange = "opacity";
|
|
3009
3501
|
pending = idx;
|
|
3010
3502
|
requestAnimationFrame(() => {
|
|
3011
3503
|
if (pending !== idx || layers === null) return;
|
|
3012
|
-
el.style.transition =
|
|
3013
|
-
el.
|
|
3504
|
+
el.style.transition = plan.transition;
|
|
3505
|
+
applyLayerStyle(el, plan.to);
|
|
3014
3506
|
});
|
|
3015
|
-
fadeTimer = window.setTimeout(() => finishFade(idx),
|
|
3507
|
+
fadeTimer = window.setTimeout(() => finishFade(idx), plan.durationMs + 160);
|
|
3016
3508
|
}
|
|
3017
3509
|
function applyWpEffects() {
|
|
3018
3510
|
if (wpEl === null || layers === null) return;
|
|
@@ -3039,11 +3531,12 @@ window.__ModuleLoader__.load({
|
|
|
3039
3531
|
clearCustomTokens();
|
|
3040
3532
|
clearOwnedSurfaces();
|
|
3041
3533
|
}
|
|
3042
|
-
applyCustomTokens(rOps());
|
|
3534
|
+
const paletteMoved = applyCustomTokens(rOps());
|
|
3043
3535
|
applySettingsOverrides(rSop());
|
|
3044
3536
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
3045
3537
|
applyRightbarOverrides(rRightbarOpacity());
|
|
3046
3538
|
applyPartBlurs(rBlurs());
|
|
3539
|
+
applyViewCards(paletteMoved);
|
|
3047
3540
|
}
|
|
3048
3541
|
function teardownWp() {
|
|
3049
3542
|
dropWpContainer();
|
|
@@ -5637,11 +6130,58 @@ window.__ModuleLoader__.load({
|
|
|
5637
6130
|
}
|
|
5638
6131
|
//#endregion
|
|
5639
6132
|
//#region src/client/components/pages/ProfilePage.tsx
|
|
6133
|
+
/** Chip labels, keyed by the value they set — so no chip can be built from a
|
|
6134
|
+
* value the schema does not accept, and `check:ui` still sees every key used. */
|
|
6135
|
+
const EFFECT_KEYS = {
|
|
6136
|
+
fade: "trEffectFade",
|
|
6137
|
+
none: "trEffectNone",
|
|
6138
|
+
zoom: "trEffectZoom",
|
|
6139
|
+
slide: "trEffectSlide"
|
|
6140
|
+
};
|
|
6141
|
+
const EASING_KEYS = {
|
|
6142
|
+
ease: "trEasingEase",
|
|
6143
|
+
linear: "trEasingLinear",
|
|
6144
|
+
"ease-out": "trEasingOut",
|
|
6145
|
+
"ease-in-out": "trEasingInOut"
|
|
6146
|
+
};
|
|
5640
6147
|
function ProfilePage({ p, notify }) {
|
|
5641
|
-
const { t, exportTheme, importTheme } = p;
|
|
6148
|
+
const { t, exportTheme, importTheme, setTransition } = p;
|
|
5642
6149
|
const importRef = (0, react.useRef)(null);
|
|
5643
6150
|
p.useStore((s) => s.rev + s.rulesRev);
|
|
5644
6151
|
const holidays = cfg.holidays;
|
|
6152
|
+
const tr = cfg.transition;
|
|
6153
|
+
const previewRef = (0, react.useRef)(null);
|
|
6154
|
+
/** The OS preference, read at render: it vetoes the real switch (see
|
|
6155
|
+
* `transitionPlan`), so the card has to say so rather than show a preview of
|
|
6156
|
+
* something the interface will not do. */
|
|
6157
|
+
const reduced = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
6158
|
+
/**
|
|
6159
|
+
* Play the effect on the preview swatch, for the duration a real switch would
|
|
6160
|
+
* use right now: the wallpaper fading into ITSELF is invisible, which is the
|
|
6161
|
+
* one thing a transition setting must not leave the user guessing about.
|
|
6162
|
+
*/
|
|
6163
|
+
const play = () => {
|
|
6164
|
+
const el = previewRef.current;
|
|
6165
|
+
if (el === null) return;
|
|
6166
|
+
const plan = transitionPlan({
|
|
6167
|
+
effect: tr.effect,
|
|
6168
|
+
easing: tr.easing,
|
|
6169
|
+
durationMs: rFadeMs(),
|
|
6170
|
+
canAnimate: true,
|
|
6171
|
+
reducedMotion: false
|
|
6172
|
+
});
|
|
6173
|
+
el.style.transition = "none";
|
|
6174
|
+
el.style.opacity = plan.from.opacity;
|
|
6175
|
+
el.style.transform = plan.from.transform;
|
|
6176
|
+
requestAnimationFrame(() => {
|
|
6177
|
+
el.style.transition = plan.transition;
|
|
6178
|
+
el.style.opacity = plan.to.opacity;
|
|
6179
|
+
el.style.transform = plan.to.transform;
|
|
6180
|
+
});
|
|
6181
|
+
};
|
|
6182
|
+
(0, react.useEffect)(() => {
|
|
6183
|
+
if (!reduced) play();
|
|
6184
|
+
}, [tr.effect, tr.easing]);
|
|
5645
6185
|
const onImport = async (file) => {
|
|
5646
6186
|
try {
|
|
5647
6187
|
const ok = await importTheme(file);
|
|
@@ -5689,9 +6229,106 @@ window.__ModuleLoader__.load({
|
|
|
5689
6229
|
})]
|
|
5690
6230
|
})
|
|
5691
6231
|
}),
|
|
6232
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
6233
|
+
className: "dab-card dab-rise",
|
|
6234
|
+
style: { "--d": 1 },
|
|
6235
|
+
children: [
|
|
6236
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6237
|
+
className: "dab-part-head",
|
|
6238
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6239
|
+
className: "dab-part-ico",
|
|
6240
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SparkleIcon, { size: 16 })
|
|
6241
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6242
|
+
className: "dab-part-name",
|
|
6243
|
+
children: t("trTitle")
|
|
6244
|
+
})]
|
|
6245
|
+
}),
|
|
6246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6247
|
+
className: "dab-swatch-title",
|
|
6248
|
+
children: t("trEffect")
|
|
6249
|
+
}),
|
|
6250
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6251
|
+
className: "dab-chip-row",
|
|
6252
|
+
children: TRANSITION_EFFECTS.map((effect) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6253
|
+
type: "button",
|
|
6254
|
+
className: `dab-chip${tr.effect === effect ? " is-active" : ""}`,
|
|
6255
|
+
onClick: () => setTransition({ effect }),
|
|
6256
|
+
children: t(EFFECT_KEYS[effect])
|
|
6257
|
+
}, effect))
|
|
6258
|
+
}),
|
|
6259
|
+
tr.effect === "none" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6260
|
+
className: "dab-swatch-title",
|
|
6261
|
+
style: { marginTop: 12 },
|
|
6262
|
+
children: t("trEasing")
|
|
6263
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6264
|
+
className: "dab-chip-row",
|
|
6265
|
+
children: TRANSITION_EASINGS.map((easing) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6266
|
+
type: "button",
|
|
6267
|
+
className: `dab-chip${tr.easing === easing ? " is-active" : ""}`,
|
|
6268
|
+
onClick: () => setTransition({ easing }),
|
|
6269
|
+
children: t(EASING_KEYS[easing])
|
|
6270
|
+
}, easing))
|
|
6271
|
+
})] }),
|
|
6272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6273
|
+
className: "dab-swatch-title",
|
|
6274
|
+
style: { marginTop: 12 },
|
|
6275
|
+
children: t("trDuration")
|
|
6276
|
+
}),
|
|
6277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6278
|
+
style: { marginTop: 2 },
|
|
6279
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
6280
|
+
label: t("trDuration"),
|
|
6281
|
+
min: 0,
|
|
6282
|
+
max: 3e3,
|
|
6283
|
+
step: 50,
|
|
6284
|
+
def: tr.durationMs,
|
|
6285
|
+
fmt: (v) => `${v} ms`,
|
|
6286
|
+
onInput: (v) => {
|
|
6287
|
+
cfg.transition = normalizeTransition({
|
|
6288
|
+
...cfg.transition,
|
|
6289
|
+
durationMs: v
|
|
6290
|
+
});
|
|
6291
|
+
saveConfig();
|
|
6292
|
+
},
|
|
6293
|
+
onChange: (v) => setTransition({ durationMs: v })
|
|
6294
|
+
})
|
|
6295
|
+
}),
|
|
6296
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6297
|
+
style: {
|
|
6298
|
+
display: "flex",
|
|
6299
|
+
alignItems: "center",
|
|
6300
|
+
gap: 12,
|
|
6301
|
+
marginTop: 13
|
|
6302
|
+
},
|
|
6303
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6304
|
+
className: "dab-tr-preview",
|
|
6305
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6306
|
+
ref: previewRef,
|
|
6307
|
+
className: "dab-tr-preview-in"
|
|
6308
|
+
})
|
|
6309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6310
|
+
type: "button",
|
|
6311
|
+
className: "dab-btn",
|
|
6312
|
+
disabled: reduced,
|
|
6313
|
+
onClick: play,
|
|
6314
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SparkleIcon, { size: 14 }), t("trPreview")]
|
|
6315
|
+
})]
|
|
6316
|
+
}),
|
|
6317
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6318
|
+
className: "dab-hint",
|
|
6319
|
+
style: { marginTop: 10 },
|
|
6320
|
+
children: t("trHint")
|
|
6321
|
+
}),
|
|
6322
|
+
reduced ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6323
|
+
className: "dab-hint",
|
|
6324
|
+
style: { marginTop: 6 },
|
|
6325
|
+
children: t("trReducedHint")
|
|
6326
|
+
}) : null
|
|
6327
|
+
]
|
|
6328
|
+
}),
|
|
5692
6329
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5693
6330
|
className: "dab-profile-grid dab-rise",
|
|
5694
|
-
style: { "--d":
|
|
6331
|
+
style: { "--d": 3 },
|
|
5695
6332
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5696
6333
|
className: "dab-card dab-card-hover",
|
|
5697
6334
|
children: [
|
|
@@ -5755,7 +6392,7 @@ window.__ModuleLoader__.load({
|
|
|
5755
6392
|
}),
|
|
5756
6393
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
5757
6394
|
className: "dab-footer dab-rise",
|
|
5758
|
-
style: { "--d":
|
|
6395
|
+
style: { "--d": 4 },
|
|
5759
6396
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5760
6397
|
className: "dab-footer-mono",
|
|
5761
6398
|
children: "dsh-background-by-model"
|
|
@@ -6642,6 +7279,24 @@ window.__ModuleLoader__.load({
|
|
|
6642
7279
|
saveConfig();
|
|
6643
7280
|
sync();
|
|
6644
7281
|
},
|
|
7282
|
+
/**
|
|
7283
|
+
* Patch the GLOBAL wallpaper-switch transition (effect, easing, where the
|
|
7284
|
+
* duration comes from).
|
|
7285
|
+
*
|
|
7286
|
+
* Deliberately no `applyWp()`: this setting decides what the NEXT switch
|
|
7287
|
+
* looks like and nothing on screen changes right now, so re-running the
|
|
7288
|
+
* whole apply (token rewrite, every interface part) for something that
|
|
7289
|
+
* paints nothing would be the expensive way to say "saved". The Config
|
|
7290
|
+
* page's own preview plays the effect instead.
|
|
7291
|
+
*/
|
|
7292
|
+
setTransition: (patch) => {
|
|
7293
|
+
cfg.transition = normalizeTransition({
|
|
7294
|
+
...cfg.transition,
|
|
7295
|
+
...patch
|
|
7296
|
+
});
|
|
7297
|
+
sync();
|
|
7298
|
+
saveConfig();
|
|
7299
|
+
},
|
|
6645
7300
|
setHolidaysEnabled: (v) => {
|
|
6646
7301
|
cfg.holidays.enabled = v;
|
|
6647
7302
|
persistConfig();
|
|
@@ -6657,7 +7312,7 @@ window.__ModuleLoader__.load({
|
|
|
6657
7312
|
if (url !== null) images[slot] = url;
|
|
6658
7313
|
}
|
|
6659
7314
|
const payload = {
|
|
6660
|
-
version:
|
|
7315
|
+
version: 6,
|
|
6661
7316
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6662
7317
|
config: cfg,
|
|
6663
7318
|
images
|