dsh-any-background 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -20
- package/README.zh.md +19 -20
- package/lib/client.js +947 -37
- package/lib/client.js.map +1 -1
- package/lib/index.js +389 -24
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -24,10 +24,12 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
close: "关闭",
|
|
25
25
|
pageColor: "色彩",
|
|
26
26
|
pageInterface: "界面",
|
|
27
|
+
pageFont: "字体",
|
|
27
28
|
pageBackground: "背景",
|
|
28
29
|
pageProfile: "配置",
|
|
29
30
|
descColor: "拖动色轮或输入精确数值,整个界面的配色将实时跟随变化",
|
|
30
31
|
descInterface: "为主界面的各个区域单独调节透明度与模糊,营造空间层次感",
|
|
32
|
+
descFont: "上传自定义字体为整套界面换字形,并按区域为文字添加描边,让内容在复杂的壁纸上依然清晰",
|
|
31
33
|
descBackground: "上传一张图片或一段视频作为壁纸并自由摆放,或让算法为你实时生成动态背景",
|
|
32
34
|
descProfile: "一键套用外观预设,保存多套配置随时切换,设置昼夜自动换装,或导出/导入完整主题",
|
|
33
35
|
colorTitle: "主题色",
|
|
@@ -46,6 +48,25 @@ window.__ModuleLoader__.load({
|
|
|
46
48
|
uiTrajectory: "轨迹页",
|
|
47
49
|
uiPanelRegion: "better-sidebar 工作台",
|
|
48
50
|
uiProduced: "产出物/高亮内容",
|
|
51
|
+
fontPageTitle: "字体与描边",
|
|
52
|
+
fontTitle: "自定义字体",
|
|
53
|
+
fontHint: "支持 ttf/otf/woff/woff2 字体文件(上限 100MB),将应用到整个界面的文字;代码块的等宽字体保持不变。中文字体通常有几十 MB,首次上传与刷新加载需要片刻",
|
|
54
|
+
fontUpload: "上传字体",
|
|
55
|
+
fontUploading: "上传中…",
|
|
56
|
+
fontRemove: "移除字体",
|
|
57
|
+
fontEnabled: "启用字体",
|
|
58
|
+
fontNone: "未设置,使用宿主默认字体",
|
|
59
|
+
fontFail: "字体上传失败,请确认是有效的 ttf/otf/woff/woff2 文件",
|
|
60
|
+
strokeTitle: "文字描边",
|
|
61
|
+
strokeHint: "按区域为文字添加描边:粗细为 0 表示关闭,「自动」会生成与文字颜色相反的颜色,「主题色」跟随当前主题色。代码块与图标已自动豁免,多色语法不会糊成一团",
|
|
62
|
+
strokeWidth: "描边粗细",
|
|
63
|
+
strokeColor: "描边颜色",
|
|
64
|
+
strokeAuto: "自动反色",
|
|
65
|
+
strokeGray: "灰色",
|
|
66
|
+
strokeBlack: "黑色",
|
|
67
|
+
strokeWhite: "白色",
|
|
68
|
+
strokeTheme: "主题色",
|
|
69
|
+
strokeCustom: "自定义",
|
|
49
70
|
bgTitle: "背景",
|
|
50
71
|
bgChoose: "选择图片或视频",
|
|
51
72
|
bgRemove: "移除背景",
|
|
@@ -73,7 +94,7 @@ window.__ModuleLoader__.load({
|
|
|
73
94
|
liveBadge: "动态壁纸",
|
|
74
95
|
bgHint: "「适应」布局模式下悬停预览可编辑图片/视频的位置与缩放(其余布局模式按模式自动排布);动态生成模式下可选择类型、预设与参数。绑定种子后刷新页面壁纸保持不变,取消绑定则每次刷新随机换新",
|
|
75
96
|
editorTitle: "背景编辑器",
|
|
76
|
-
editorHint: "
|
|
97
|
+
editorHint: "拖动移动画面,滚轮或双指缩放大小",
|
|
77
98
|
editorCommit: "确认",
|
|
78
99
|
editorCancel: "取消",
|
|
79
100
|
editorReset: "重置",
|
|
@@ -189,10 +210,12 @@ window.__ModuleLoader__.load({
|
|
|
189
210
|
close: "Close",
|
|
190
211
|
pageColor: "Color",
|
|
191
212
|
pageInterface: "Interface",
|
|
213
|
+
pageFont: "Font",
|
|
192
214
|
pageBackground: "Background",
|
|
193
215
|
pageProfile: "Profile",
|
|
194
216
|
descColor: "Drag the wheel or type exact values — the whole UI follows live",
|
|
195
217
|
descInterface: "Tune opacity and blur per surface to build depth",
|
|
218
|
+
descFont: "Swap the interface typeface with your own font file, and outline text per region so it stays legible over busy wallpapers",
|
|
196
219
|
descBackground: "Upload an image or a video as wallpaper and place it freely, or let the algorithm paint a live background",
|
|
197
220
|
descProfile: "Apply a one-click preset, save multiple named profiles, schedule a day/night switch, or export/import the full theme",
|
|
198
221
|
colorTitle: "Theme color",
|
|
@@ -211,6 +234,25 @@ window.__ModuleLoader__.load({
|
|
|
211
234
|
uiTrajectory: "Trajectory view",
|
|
212
235
|
uiPanelRegion: "better-sidebar workbench",
|
|
213
236
|
uiProduced: "Produced / highlights",
|
|
237
|
+
fontPageTitle: "Font & outline",
|
|
238
|
+
fontTitle: "Custom font",
|
|
239
|
+
fontHint: "Accepts ttf/otf/woff/woff2 files (up to 100MB) and applies them to the whole interface; code blocks keep their monospace stack. CJK fonts routinely span tens of MB, so the first upload and each reload take a moment",
|
|
240
|
+
fontUpload: "Upload font",
|
|
241
|
+
fontUploading: "Uploading…",
|
|
242
|
+
fontRemove: "Remove font",
|
|
243
|
+
fontEnabled: "Font enabled",
|
|
244
|
+
fontNone: "None — host default",
|
|
245
|
+
fontFail: "Upload failed — make sure it is a valid ttf/otf/woff/woff2 file",
|
|
246
|
+
strokeTitle: "Text outline",
|
|
247
|
+
strokeHint: "Outline text per region: width 0 turns it off, \"auto\" picks the inverse of the text color, and \"theme\" follows the current accent. Code blocks and icons are exempted automatically, so multi-color syntax never smears",
|
|
248
|
+
strokeWidth: "Outline width",
|
|
249
|
+
strokeColor: "Outline color",
|
|
250
|
+
strokeAuto: "Auto contrast",
|
|
251
|
+
strokeGray: "Gray",
|
|
252
|
+
strokeBlack: "Black",
|
|
253
|
+
strokeWhite: "White",
|
|
254
|
+
strokeTheme: "Accent",
|
|
255
|
+
strokeCustom: "Custom",
|
|
214
256
|
bgTitle: "Background",
|
|
215
257
|
bgChoose: "Choose image or video",
|
|
216
258
|
bgRemove: "Remove background",
|
|
@@ -238,7 +280,7 @@ window.__ModuleLoader__.load({
|
|
|
238
280
|
liveBadge: "Live wallpaper",
|
|
239
281
|
bgHint: "In Fit layout mode hover the preview to reposition and zoom the image/video (other modes arrange it automatically); in generated mode pick a type, preset and parameters. With the seed locked the wallpaper stays the same across refreshes; unlocked, every refresh randomizes it",
|
|
240
282
|
editorTitle: "Background editor",
|
|
241
|
-
editorHint: "Drag to move
|
|
283
|
+
editorHint: "Drag to move; scroll or pinch with two fingers to zoom",
|
|
242
284
|
editorCommit: "Confirm",
|
|
243
285
|
editorCancel: "Cancel",
|
|
244
286
|
editorReset: "Reset",
|
|
@@ -353,25 +395,72 @@ window.__ModuleLoader__.load({
|
|
|
353
395
|
const DEFAULT_CONFIG = {
|
|
354
396
|
color: null,
|
|
355
397
|
opacities: {
|
|
356
|
-
bg: .
|
|
357
|
-
sidebar: .
|
|
358
|
-
card:
|
|
359
|
-
input:
|
|
398
|
+
bg: .5,
|
|
399
|
+
sidebar: .5,
|
|
400
|
+
card: .5,
|
|
401
|
+
input: .5
|
|
360
402
|
},
|
|
361
403
|
blurs: {
|
|
362
|
-
bg:
|
|
363
|
-
sidebar:
|
|
364
|
-
card:
|
|
365
|
-
settings:
|
|
366
|
-
chat:
|
|
367
|
-
trajectory:
|
|
368
|
-
input:
|
|
369
|
-
panel:
|
|
370
|
-
produced:
|
|
404
|
+
bg: 30,
|
|
405
|
+
sidebar: 30,
|
|
406
|
+
card: 30,
|
|
407
|
+
settings: 30,
|
|
408
|
+
chat: 30,
|
|
409
|
+
trajectory: 30,
|
|
410
|
+
input: 30,
|
|
411
|
+
panel: 30,
|
|
412
|
+
produced: 30
|
|
371
413
|
},
|
|
372
|
-
|
|
414
|
+
strokes: {
|
|
415
|
+
bg: {
|
|
416
|
+
width: 0,
|
|
417
|
+
color: "auto",
|
|
418
|
+
customColor: "#808080"
|
|
419
|
+
},
|
|
420
|
+
sidebar: {
|
|
421
|
+
width: 0,
|
|
422
|
+
color: "auto",
|
|
423
|
+
customColor: "#808080"
|
|
424
|
+
},
|
|
425
|
+
card: {
|
|
426
|
+
width: 0,
|
|
427
|
+
color: "auto",
|
|
428
|
+
customColor: "#808080"
|
|
429
|
+
},
|
|
430
|
+
settings: {
|
|
431
|
+
width: 0,
|
|
432
|
+
color: "auto",
|
|
433
|
+
customColor: "#808080"
|
|
434
|
+
},
|
|
435
|
+
chat: {
|
|
436
|
+
width: 0,
|
|
437
|
+
color: "auto",
|
|
438
|
+
customColor: "#808080"
|
|
439
|
+
},
|
|
440
|
+
trajectory: {
|
|
441
|
+
width: 0,
|
|
442
|
+
color: "auto",
|
|
443
|
+
customColor: "#808080"
|
|
444
|
+
},
|
|
445
|
+
input: {
|
|
446
|
+
width: 0,
|
|
447
|
+
color: "auto",
|
|
448
|
+
customColor: "#808080"
|
|
449
|
+
},
|
|
450
|
+
panel: {
|
|
451
|
+
width: 0,
|
|
452
|
+
color: "auto",
|
|
453
|
+
customColor: "#808080"
|
|
454
|
+
},
|
|
455
|
+
produced: {
|
|
456
|
+
width: 0,
|
|
457
|
+
color: "auto",
|
|
458
|
+
customColor: "#808080"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
settingsOpacity: .5,
|
|
373
462
|
wallpaperOpacity: 1,
|
|
374
|
-
blur:
|
|
463
|
+
blur: 30,
|
|
375
464
|
bgState: {
|
|
376
465
|
zoom: 1,
|
|
377
466
|
x: 0,
|
|
@@ -389,12 +478,14 @@ window.__ModuleLoader__.load({
|
|
|
389
478
|
backgroundType: "image",
|
|
390
479
|
bgMode: "fit",
|
|
391
480
|
videoMime: null,
|
|
481
|
+
fontMime: null,
|
|
482
|
+
fontEnabled: true,
|
|
392
483
|
generatedBg: null,
|
|
393
484
|
regenerateOnReload: false,
|
|
394
|
-
chatTextOpacity:
|
|
395
|
-
trajectoryOpacity:
|
|
396
|
-
panelOpacity:
|
|
397
|
-
producedOpacity:
|
|
485
|
+
chatTextOpacity: .5,
|
|
486
|
+
trajectoryOpacity: .5,
|
|
487
|
+
panelOpacity: .5,
|
|
488
|
+
producedOpacity: .5,
|
|
398
489
|
profiles: [],
|
|
399
490
|
rotation: {
|
|
400
491
|
enabled: false,
|
|
@@ -420,6 +511,7 @@ window.__ModuleLoader__.load({
|
|
|
420
511
|
...DEFAULT_CONFIG,
|
|
421
512
|
opacities: { ...DEFAULT_CONFIG.opacities },
|
|
422
513
|
blurs: { ...DEFAULT_CONFIG.blurs },
|
|
514
|
+
strokes: structuredClone(DEFAULT_CONFIG.strokes),
|
|
423
515
|
bgState: { ...DEFAULT_CONFIG.bgState },
|
|
424
516
|
videoBgState: { ...DEFAULT_CONFIG.videoBgState }
|
|
425
517
|
};
|
|
@@ -540,6 +632,49 @@ window.__ModuleLoader__.load({
|
|
|
540
632
|
}
|
|
541
633
|
return out;
|
|
542
634
|
}
|
|
635
|
+
const STROKE_GROUPS = [
|
|
636
|
+
"bg",
|
|
637
|
+
"sidebar",
|
|
638
|
+
"card",
|
|
639
|
+
"settings",
|
|
640
|
+
"chat",
|
|
641
|
+
"trajectory",
|
|
642
|
+
"input",
|
|
643
|
+
"panel",
|
|
644
|
+
"produced"
|
|
645
|
+
];
|
|
646
|
+
const STROKE_COLOR_KEYS = [
|
|
647
|
+
"auto",
|
|
648
|
+
"gray",
|
|
649
|
+
"black",
|
|
650
|
+
"white",
|
|
651
|
+
"theme",
|
|
652
|
+
"custom"
|
|
653
|
+
];
|
|
654
|
+
const HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
655
|
+
const DEFAULT_STROKE = {
|
|
656
|
+
width: 0,
|
|
657
|
+
color: "auto",
|
|
658
|
+
customColor: "#808080"
|
|
659
|
+
};
|
|
660
|
+
function adoptStroke(raw) {
|
|
661
|
+
const s = raw ?? {};
|
|
662
|
+
return {
|
|
663
|
+
width: typeof s.width === "number" && isFinite(s.width) ? Math.min(4, Math.max(0, s.width)) : DEFAULT_STROKE.width,
|
|
664
|
+
color: STROKE_COLOR_KEYS.includes(s.color) ? s.color : DEFAULT_STROKE.color,
|
|
665
|
+
customColor: typeof s.customColor === "string" && HEX_RE.test(s.customColor) ? s.customColor : DEFAULT_STROKE.customColor
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
/** Move a possibly-partial strokes map into the full shape the UI reads. */
|
|
669
|
+
function strokesFrom(raw) {
|
|
670
|
+
const s = raw ?? {};
|
|
671
|
+
const out = {};
|
|
672
|
+
for (const k of STROKE_GROUPS) out[k] = adoptStroke(s[k]);
|
|
673
|
+
return out;
|
|
674
|
+
}
|
|
675
|
+
function rStrokes() {
|
|
676
|
+
return strokesFrom(cfg.strokes);
|
|
677
|
+
}
|
|
543
678
|
function rWop() {
|
|
544
679
|
return clamp01(cfg.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
545
680
|
}
|
|
@@ -611,6 +746,7 @@ window.__ModuleLoader__.load({
|
|
|
611
746
|
color: cfg.color,
|
|
612
747
|
opacities: { ...rOps() },
|
|
613
748
|
blurs: { ...rBlurs() },
|
|
749
|
+
strokes: rStrokes(),
|
|
614
750
|
settingsOpacity: rSop(),
|
|
615
751
|
wallpaperOpacity: rWop(),
|
|
616
752
|
blur: rBl(),
|
|
@@ -631,6 +767,7 @@ window.__ModuleLoader__.load({
|
|
|
631
767
|
...DEFAULT_CONFIG.blurs,
|
|
632
768
|
...ap.blurs ?? {}
|
|
633
769
|
};
|
|
770
|
+
cfg.strokes = strokesFrom(ap.strokes);
|
|
634
771
|
cfg.settingsOpacity = clamp01(ap.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
|
|
635
772
|
cfg.wallpaperOpacity = clamp01(ap.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
636
773
|
cfg.blur = typeof ap.blur === "number" ? Math.min(60, Math.max(0, ap.blur)) : DEFAULT_CONFIG.blur;
|
|
@@ -664,6 +801,7 @@ window.__ModuleLoader__.load({
|
|
|
664
801
|
...DEFAULT_CONFIG.blurs,
|
|
665
802
|
...ac.blurs ?? {}
|
|
666
803
|
},
|
|
804
|
+
strokes: strokesFrom(ac.strokes),
|
|
667
805
|
settingsOpacity: clamp01(ac.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
668
806
|
wallpaperOpacity: clamp01(ac.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
669
807
|
blur: num(ac.blur, DEFAULT_CONFIG.blur),
|
|
@@ -761,6 +899,7 @@ window.__ModuleLoader__.load({
|
|
|
761
899
|
input: num(ops.input, DEFAULT_CONFIG.opacities.input)
|
|
762
900
|
},
|
|
763
901
|
blurs,
|
|
902
|
+
strokes: strokesFrom(c.strokes),
|
|
764
903
|
settingsOpacity: num(c.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
765
904
|
wallpaperOpacity: num(c.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
766
905
|
blur: num(c.blur, DEFAULT_CONFIG.blur),
|
|
@@ -769,6 +908,8 @@ window.__ModuleLoader__.load({
|
|
|
769
908
|
backgroundType: bgType,
|
|
770
909
|
bgMode,
|
|
771
910
|
videoMime: typeof c.videoMime === "string" ? c.videoMime : null,
|
|
911
|
+
fontMime: typeof c.fontMime === "string" ? c.fontMime : null,
|
|
912
|
+
fontEnabled: typeof c.fontEnabled === "boolean" ? c.fontEnabled : DEFAULT_CONFIG.fontEnabled,
|
|
772
913
|
generatedBg: generatedBg ? normalizeGeneratedBg(generatedBg) : null,
|
|
773
914
|
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
774
915
|
chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
@@ -828,9 +969,15 @@ window.__ModuleLoader__.load({
|
|
|
828
969
|
const WALLPAPER_UPLOAD_URL = "/dsh-any-background/wallpaper/upload";
|
|
829
970
|
/** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
|
|
830
971
|
const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
|
|
972
|
+
/** Same-origin serve URL of the persisted custom font (enough for @font-face). */
|
|
973
|
+
const FONT_SERVE_URL = "/dsh-any-background/font";
|
|
974
|
+
/** HTTP route custom fonts are POSTed to as raw bytes (see uploadFont). */
|
|
975
|
+
const FONT_UPLOAD_URL = "/dsh-any-background/font/upload";
|
|
831
976
|
const RPC_NS = "dshAnyBackground";
|
|
832
977
|
const rpcEndpoint = (method) => `${RPC_NS}/${method}`;
|
|
833
978
|
let rpcCallFn = null;
|
|
979
|
+
/** Serve URL of the persisted custom font, filled by loadPersisted. */
|
|
980
|
+
let fontServeUrl = null;
|
|
834
981
|
function initRpc(call) {
|
|
835
982
|
rpcCallFn = call;
|
|
836
983
|
}
|
|
@@ -868,7 +1015,10 @@ window.__ModuleLoader__.load({
|
|
|
868
1015
|
/** Load the persisted theme (config + wallpaper URL + video URL) from the node
|
|
869
1016
|
* half. Image and video both travel as serve URLs — never bytes — so this RPC
|
|
870
1017
|
* stays tiny. Resolves true when the server advanced a due wallpaper rotation
|
|
871
|
-
* during the read — the restored wallpaper is then already the new pick.
|
|
1018
|
+
* during the read — the restored wallpaper is then already the new pick.
|
|
1019
|
+
* `firstRun` means the server had no theme-config.json and just materialized
|
|
1020
|
+
* its defaults: the caller should persist the browser side's own defaults and
|
|
1021
|
+
* re-read once so every slider starts from a value that is really on disk. */
|
|
872
1022
|
async function loadPersisted() {
|
|
873
1023
|
const data = await rpcCall("read", {});
|
|
874
1024
|
if (data && typeof data === "object") {
|
|
@@ -878,10 +1028,18 @@ window.__ModuleLoader__.load({
|
|
|
878
1028
|
else if (d.wallpaperUrl === null) setWpImageUrl(null);
|
|
879
1029
|
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
880
1030
|
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
1031
|
+
if (typeof d.fontUrl === "string") fontServeUrl = d.fontUrl;
|
|
1032
|
+
else if (d.fontUrl === null) fontServeUrl = null;
|
|
881
1033
|
if (cfg.backgroundType === "image") setWpUrl(rWpImage());
|
|
882
|
-
return
|
|
1034
|
+
return {
|
|
1035
|
+
rotated: d.rotated === true,
|
|
1036
|
+
firstRun: d.firstRun === true
|
|
1037
|
+
};
|
|
883
1038
|
}
|
|
884
|
-
return
|
|
1039
|
+
return {
|
|
1040
|
+
rotated: false,
|
|
1041
|
+
firstRun: false
|
|
1042
|
+
};
|
|
885
1043
|
}
|
|
886
1044
|
/** Persist a wallpaper (null removes it); one-shot, no debounce. */
|
|
887
1045
|
function persistWallpaper(dataUrl) {
|
|
@@ -987,6 +1145,28 @@ window.__ModuleLoader__.load({
|
|
|
987
1145
|
return false;
|
|
988
1146
|
}
|
|
989
1147
|
}
|
|
1148
|
+
/** Upload a custom font's raw bytes over HTTP; resolves the sniffed MIME on
|
|
1149
|
+
* success (the server validates the container from magic bytes) or null. */
|
|
1150
|
+
async function uploadFont(blob) {
|
|
1151
|
+
try {
|
|
1152
|
+
const res = await fetch(FONT_UPLOAD_URL, {
|
|
1153
|
+
method: "POST",
|
|
1154
|
+
headers: { "Content-Type": blob.type || "application/octet-stream" },
|
|
1155
|
+
body: blob
|
|
1156
|
+
});
|
|
1157
|
+
if (!res.ok) return null;
|
|
1158
|
+
const data = await res.json().catch(() => null);
|
|
1159
|
+
return typeof data?.mime === "string" ? data.mime : "font/ttf";
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
console.warn("dsh-any-background: font upload failed", e);
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/** Remove the stored custom font (server deletes every variant + clears the
|
|
1166
|
+
* recorded MIME); resolves true once the slot is empty. */
|
|
1167
|
+
async function removeFont() {
|
|
1168
|
+
return await rpcCall("removeFont", {}) === true;
|
|
1169
|
+
}
|
|
990
1170
|
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
991
1171
|
async function rotationAdd(dataUrl, thumb) {
|
|
992
1172
|
if (!rpcCallFn) return {
|
|
@@ -2400,14 +2580,34 @@ window.__ModuleLoader__.load({
|
|
|
2400
2580
|
if (rSchemeOverride() !== "auto") return genTokens(220, .04, scheme === "dark" ? .14 : .92, scheme).tokens;
|
|
2401
2581
|
return null;
|
|
2402
2582
|
}
|
|
2583
|
+
/** Surface colors the opacity sliders fade when the plugin has NO palette of
|
|
2584
|
+
* its own (no picked color, no forced scheme), read live from the host's
|
|
2585
|
+
* resolved tokens on `:root` — the same trick applyPanelOverrides uses for the
|
|
2586
|
+
* workbench panel. Without this source the per-part alpha is never emitted at
|
|
2587
|
+
* all and every opacity slider looks inert until the user drags it once.
|
|
2588
|
+
* The sliders only ever supply the alpha: the host still decides the colors,
|
|
2589
|
+
* so a custom host skin survives. Returns null when the host exposes none. */
|
|
2590
|
+
function readHostOpacityTokens() {
|
|
2591
|
+
if (typeof getComputedStyle === "undefined") return null;
|
|
2592
|
+
const cs = getComputedStyle(document.documentElement);
|
|
2593
|
+
const out = {};
|
|
2594
|
+
let found = false;
|
|
2595
|
+
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) {
|
|
2596
|
+
const v = cs.getPropertyValue(name).trim();
|
|
2597
|
+
if (v === "") continue;
|
|
2598
|
+
out[name] = v;
|
|
2599
|
+
found = true;
|
|
2600
|
+
}
|
|
2601
|
+
return found ? out : null;
|
|
2602
|
+
}
|
|
2403
2603
|
function applyCustomTokensNow(ops) {
|
|
2404
2604
|
const hasColor = rHasColor();
|
|
2405
2605
|
const override = rSchemeOverride();
|
|
2406
2606
|
const [h, s, l] = rColor();
|
|
2407
2607
|
const scheme = rScheme();
|
|
2408
2608
|
const verdict = rBgDark();
|
|
2409
|
-
const
|
|
2410
|
-
const tokens = { ...
|
|
2609
|
+
const surfaces = paletteTokens() ?? readHostOpacityTokens();
|
|
2610
|
+
const tokens = { ...surfaces };
|
|
2411
2611
|
if (override === "auto") {
|
|
2412
2612
|
const fontDark = hasColor ? rColorScheme() === "dark" : verdict;
|
|
2413
2613
|
if (fontDark !== null && fontDark !== void 0) {
|
|
@@ -2431,10 +2631,17 @@ window.__ModuleLoader__.load({
|
|
|
2431
2631
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
2432
2632
|
appliedTokenNames = Object.keys(tokens);
|
|
2433
2633
|
}
|
|
2434
|
-
if (
|
|
2634
|
+
if (surfaces === null) {
|
|
2635
|
+
if (!hasColor && override === "auto" && verdict === null) clearThemeTokens();
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2435
2638
|
const root = document.documentElement;
|
|
2436
|
-
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names)
|
|
2437
|
-
|
|
2639
|
+
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) {
|
|
2640
|
+
if (surfaces[name] === void 0) continue;
|
|
2641
|
+
root.style.setProperty(OPACITY_VARS[name], toRgba(surfaces[name], ops[g.part]));
|
|
2642
|
+
}
|
|
2643
|
+
const menu = surfaces["--dsw-specific-menu"];
|
|
2644
|
+
if (menu !== void 0) root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(menu, ops.input));
|
|
2438
2645
|
const bgKey = `${baseTokenKey}|${ops.bg}`;
|
|
2439
2646
|
if (bgKey !== lastBgKey) {
|
|
2440
2647
|
lastBgKey = bgKey;
|
|
@@ -2461,6 +2668,151 @@ window.__ModuleLoader__.load({
|
|
|
2461
2668
|
const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3);position:fixed!important;z-index:26!important}[data-sidebar-right-panel][data-sidebar-right-panel=\"fullscreen\"]{z-index:40!important}";
|
|
2462
2669
|
const PANEL_BLUR_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}";
|
|
2463
2670
|
const PRODUCED_RULE = "[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}";
|
|
2671
|
+
const STROKE_RULE = [
|
|
2672
|
+
`${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
|
|
2673
|
+
"[role=\"menu\"]:not([data-dockkit-tab-menu]),[role=\"listbox\"],body>[role=\"tree\"],body>[role=\"dialog\"]:not([aria-modal=\"true\"]){-webkit-text-stroke:var(--dsh-any-stroke-card-w,0px) var(--dsh-any-stroke-card-c,transparent);paint-order:stroke fill}",
|
|
2674
|
+
"[data-composer-card],[data-cordis-panel],[data-composer-card] textarea,[data-composer-card] input,[data-composer-card] [contenteditable],[data-cordis-panel] input,[data-cordis-panel] textarea{-webkit-text-stroke:var(--dsh-any-stroke-input-w,0px) var(--dsh-any-stroke-input-c,transparent);paint-order:stroke fill}",
|
|
2675
|
+
`[data-chat-flow]{-webkit-text-stroke:var(--dsh-any-stroke-chat-w,0px) var(--dsh-any-stroke-chat-c,transparent);paint-order:stroke fill}`,
|
|
2676
|
+
`[data-conversation-composer-overlay]{-webkit-text-stroke:var(--dsh-any-stroke-trajectory-w,0px) var(--dsh-any-stroke-trajectory-c,transparent);paint-order:stroke fill}`,
|
|
2677
|
+
"[data-code-block-content] pre,[data-code-block-content],[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-text-stroke:var(--dsh-any-stroke-produced-w,0px) var(--dsh-any-stroke-produced-c,transparent);paint-order:stroke fill}",
|
|
2678
|
+
`[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-text-stroke:var(--dsh-any-stroke-panel-w,0px) var(--dsh-any-stroke-panel-c,transparent);paint-order:stroke fill}`,
|
|
2679
|
+
`svg{-webkit-text-stroke-width:0!important;paint-order:normal!important}`,
|
|
2680
|
+
`::placeholder{-webkit-text-stroke-width:0!important}`
|
|
2681
|
+
].join("");
|
|
2682
|
+
/** Groups whose stroke lands on the structurally-discovered columns (inline),
|
|
2683
|
+
* versus every group served by the static STROKE_RULE selectors. */
|
|
2684
|
+
const STROKE_INLINE_GROUPS = ["bg", "sidebar"];
|
|
2685
|
+
const STROKE_VAR_GROUPS = [
|
|
2686
|
+
"card",
|
|
2687
|
+
"settings",
|
|
2688
|
+
"chat",
|
|
2689
|
+
"trajectory",
|
|
2690
|
+
"input",
|
|
2691
|
+
"panel",
|
|
2692
|
+
"produced"
|
|
2693
|
+
];
|
|
2694
|
+
/** Resolve one group's stroke color key into a concrete CSS color.
|
|
2695
|
+
* 'auto' contrasts the FONT direction (white fonts → black stroke and vice
|
|
2696
|
+
* versa — mirrors the label flip in applyCustomTokensNow); 'theme' follows
|
|
2697
|
+
* the live palette's brand primary (picked color → generated palette, else
|
|
2698
|
+
* the host's own resolved token). */
|
|
2699
|
+
function strokeColor(s) {
|
|
2700
|
+
switch (s.color) {
|
|
2701
|
+
case "gray": return "#808080";
|
|
2702
|
+
case "black": return "#000";
|
|
2703
|
+
case "white": return "#fff";
|
|
2704
|
+
case "custom": return s.customColor;
|
|
2705
|
+
case "theme":
|
|
2706
|
+
if (rHasColor() || rSchemeOverride() !== "auto") {
|
|
2707
|
+
const [h, sa, l] = rColor();
|
|
2708
|
+
return genTokens(h, sa, l, rColorScheme()).tokens["--dsw-alias-brand-primary"] ?? "#808080";
|
|
2709
|
+
}
|
|
2710
|
+
if (typeof getComputedStyle !== "undefined") {
|
|
2711
|
+
const v = getComputedStyle(document.documentElement).getPropertyValue("--dsw-alias-brand-primary").trim();
|
|
2712
|
+
if (v !== "") return v;
|
|
2713
|
+
}
|
|
2714
|
+
return "#808080";
|
|
2715
|
+
default: {
|
|
2716
|
+
let fontDark;
|
|
2717
|
+
if (rHasColor()) fontDark = rColorScheme() === "dark";
|
|
2718
|
+
else if (rSchemeOverride() !== "auto") fontDark = rScheme() === "dark";
|
|
2719
|
+
else if (rBgDark() !== null) fontDark = rBgDark() === true;
|
|
2720
|
+
else fontDark = false;
|
|
2721
|
+
return fontDark ? "#000" : "#fff";
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
/** Write every group's stroke width/color variables + the two column strokes.
|
|
2726
|
+
* Called from applyWp so palette/verdict changes re-derive 'auto'/'theme'. */
|
|
2727
|
+
function applyStrokes() {
|
|
2728
|
+
const strokes = rStrokes();
|
|
2729
|
+
const root = document.documentElement;
|
|
2730
|
+
for (const g of STROKE_VAR_GROUPS) {
|
|
2731
|
+
const s = strokes[g];
|
|
2732
|
+
root.style.setProperty(`--dsh-any-stroke-${g}-w`, `${s.width}px`);
|
|
2733
|
+
root.style.setProperty(`--dsh-any-stroke-${g}-c`, strokeColor(s));
|
|
2734
|
+
}
|
|
2735
|
+
discoverParts();
|
|
2736
|
+
for (const g of STROKE_INLINE_GROUPS) {
|
|
2737
|
+
const el = g === "bg" ? centerEl : sidebarEl;
|
|
2738
|
+
if (el === null) continue;
|
|
2739
|
+
const s = strokes[g];
|
|
2740
|
+
if (s.width > 0) {
|
|
2741
|
+
el.style.setProperty("-webkit-text-stroke", `${s.width}px ${strokeColor(s)}`);
|
|
2742
|
+
el.style.setProperty("paint-order", "stroke fill");
|
|
2743
|
+
} else {
|
|
2744
|
+
el.style.removeProperty("-webkit-text-stroke");
|
|
2745
|
+
el.style.removeProperty("paint-order");
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
/** Live per-group stroke update during slider drag (no full re-apply). */
|
|
2750
|
+
function setPartStroke(part, s) {
|
|
2751
|
+
if (STROKE_VAR_GROUPS.includes(part)) {
|
|
2752
|
+
document.documentElement.style.setProperty(`--dsh-any-stroke-${part}-w`, `${s.width}px`);
|
|
2753
|
+
document.documentElement.style.setProperty(`--dsh-any-stroke-${part}-c`, strokeColor(s));
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
discoverParts();
|
|
2757
|
+
const el = part === "bg" ? centerEl : sidebarEl;
|
|
2758
|
+
if (el === null) return;
|
|
2759
|
+
if (s.width > 0) {
|
|
2760
|
+
el.style.setProperty("-webkit-text-stroke", `${s.width}px ${strokeColor(s)}`);
|
|
2761
|
+
el.style.setProperty("paint-order", "stroke fill");
|
|
2762
|
+
} else {
|
|
2763
|
+
el.style.removeProperty("-webkit-text-stroke");
|
|
2764
|
+
el.style.removeProperty("paint-order");
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
/** Teardown only: drop every stroke variable and column inline stroke. */
|
|
2768
|
+
function removeStrokes() {
|
|
2769
|
+
const root = document.documentElement;
|
|
2770
|
+
for (const g of [...STROKE_VAR_GROUPS, ...STROKE_INLINE_GROUPS]) {
|
|
2771
|
+
root.style.removeProperty(`--dsh-any-stroke-${g}-w`);
|
|
2772
|
+
root.style.removeProperty(`--dsh-any-stroke-${g}-c`);
|
|
2773
|
+
}
|
|
2774
|
+
for (const el of [centerEl, sidebarEl]) {
|
|
2775
|
+
if (el === null) continue;
|
|
2776
|
+
el.style.removeProperty("-webkit-text-stroke");
|
|
2777
|
+
el.style.removeProperty("paint-order");
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
const FONT_FAMILY = "DAnyFont";
|
|
2781
|
+
let fontStyleEl = null;
|
|
2782
|
+
function fontFormatForMime(mime) {
|
|
2783
|
+
switch (mime) {
|
|
2784
|
+
case "font/woff2": return "woff2";
|
|
2785
|
+
case "font/woff": return "woff";
|
|
2786
|
+
case "font/otf": return "opentype";
|
|
2787
|
+
default: return "truetype";
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
/** Apply or clear the custom interface font. `url` is the serve URL (null =
|
|
2791
|
+
* nothing stored); `enabled` gates the token override without deleting the
|
|
2792
|
+
* file. The original host stack is re-read on every apply so a host skin
|
|
2793
|
+
* change is picked up, and stays as the fallback after 'DAnyFont'. */
|
|
2794
|
+
function applyFontFace(url, enabled, mime) {
|
|
2795
|
+
if (url === null || !enabled) {
|
|
2796
|
+
fontStyleEl?.remove();
|
|
2797
|
+
fontStyleEl = null;
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
if (fontStyleEl === null || !fontStyleEl.isConnected) {
|
|
2801
|
+
fontStyleEl = document.createElement("style");
|
|
2802
|
+
fontStyleEl.dataset.plugin = "dsh-any-background-font";
|
|
2803
|
+
document.head.appendChild(fontStyleEl);
|
|
2804
|
+
}
|
|
2805
|
+
let stack = "";
|
|
2806
|
+
if (typeof getComputedStyle !== "undefined") stack = getComputedStyle(document.documentElement).getPropertyValue("--dsw-font-family").trim();
|
|
2807
|
+
if (stack === "") stack = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif";
|
|
2808
|
+
const fmt = fontFormatForMime(mime);
|
|
2809
|
+
fontStyleEl.textContent = `@font-face{font-family:'${FONT_FAMILY}';src:url('${url}') format('${fmt}');font-display:swap}body{--dsw-font-family:'${FONT_FAMILY}',${stack}}input,textarea,select,button{font-family:var(--dsw-font-family)}`;
|
|
2810
|
+
}
|
|
2811
|
+
/** Teardown only: drop the @font-face + token override. */
|
|
2812
|
+
function removeFontFace() {
|
|
2813
|
+
fontStyleEl?.remove();
|
|
2814
|
+
fontStyleEl = null;
|
|
2815
|
+
}
|
|
2464
2816
|
/** The host's own default colors for the panel layer tokens, used when the
|
|
2465
2817
|
* plugin has no palette (no picked color, no wallpaper verdict, no forced
|
|
2466
2818
|
* scheme). Without these the panel's re-scope would resolve to invalid vars
|
|
@@ -2888,6 +3240,7 @@ window.__ModuleLoader__.load({
|
|
|
2888
3240
|
const ds = el.dataset;
|
|
2889
3241
|
ds[prev + "Bg"] = el.style.getPropertyValue("background");
|
|
2890
3242
|
if (plain) return;
|
|
3243
|
+
ds[prev + "BoxSizing"] = el.style.getPropertyValue("box-sizing");
|
|
2891
3244
|
ds[prev + "Border"] = el.style.getPropertyValue("border");
|
|
2892
3245
|
ds[prev + "Radius"] = el.style.getPropertyValue("border-radius");
|
|
2893
3246
|
ds[prev + "Padding"] = el.style.getPropertyValue("padding");
|
|
@@ -2902,9 +3255,11 @@ window.__ModuleLoader__.load({
|
|
|
2902
3255
|
};
|
|
2903
3256
|
restore("background", ds[prev + "Bg"]);
|
|
2904
3257
|
if (!plain) {
|
|
3258
|
+
restore("box-sizing", ds[prev + "BoxSizing"]);
|
|
2905
3259
|
restore("border", ds[prev + "Border"]);
|
|
2906
3260
|
restore("border-radius", ds[prev + "Radius"]);
|
|
2907
3261
|
restore("padding", ds[prev + "Padding"]);
|
|
3262
|
+
delete ds[prev + "BoxSizing"];
|
|
2908
3263
|
delete ds[prev + "Border"];
|
|
2909
3264
|
delete ds[prev + "Radius"];
|
|
2910
3265
|
delete ds[prev + "Padding"];
|
|
@@ -2974,6 +3329,7 @@ window.__ModuleLoader__.load({
|
|
|
2974
3329
|
target.style.border = surface !== void 0 ? `1px solid ${toRgba(surface, borderAlpha)}` : "1px solid transparent";
|
|
2975
3330
|
target.style.borderRadius = "16px";
|
|
2976
3331
|
target.style.padding = "18px";
|
|
3332
|
+
target.style.boxSizing = "border-box";
|
|
2977
3333
|
}
|
|
2978
3334
|
target.setAttribute(spec.mark, "1");
|
|
2979
3335
|
setBlur(target, blurPx);
|
|
@@ -3297,14 +3653,14 @@ window.__ModuleLoader__.load({
|
|
|
3297
3653
|
wpVerdict = null;
|
|
3298
3654
|
setBgDark(null);
|
|
3299
3655
|
}
|
|
3300
|
-
|
|
3301
|
-
else if (!url) clearThemeTokens();
|
|
3656
|
+
applyCustomTokens(rOps());
|
|
3302
3657
|
if (rHasColor()) {
|
|
3303
3658
|
applySettingsOverrides(rSop());
|
|
3304
3659
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
3305
3660
|
}
|
|
3306
3661
|
applyPanelOverrides(rPanelOpacity());
|
|
3307
3662
|
applyPartBlurs(rBlurs());
|
|
3663
|
+
applyStrokes();
|
|
3308
3664
|
}
|
|
3309
3665
|
function teardownWp() {
|
|
3310
3666
|
clearDynamicBg();
|
|
@@ -3349,6 +3705,8 @@ window.__ModuleLoader__.load({
|
|
|
3349
3705
|
pendingOps = null;
|
|
3350
3706
|
tableFixStyleEl?.remove();
|
|
3351
3707
|
tableFixStyleEl = null;
|
|
3708
|
+
removeStrokes();
|
|
3709
|
+
removeFontFace();
|
|
3352
3710
|
setBlur(frameEl, 0);
|
|
3353
3711
|
setBlur(sidebarEl, 0);
|
|
3354
3712
|
setBlur(centerEl, 0);
|
|
@@ -3556,6 +3914,29 @@ window.__ModuleLoader__.load({
|
|
|
3556
3914
|
.dab-toggle.is-on{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
|
|
3557
3915
|
.dab-toggle.is-on .dab-toggle-knob{transform:translateX(18px);background:#fff}
|
|
3558
3916
|
|
|
3917
|
+
/* ── custom font card ────────────────────────────────────────────────────── */
|
|
3918
|
+
.dab-font-title{display:flex;align-items:center;gap:11px}
|
|
3919
|
+
.dab-font-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}
|
|
3920
|
+
.dab-font-name{font-family:var(--dab-mono);font-size:13.5px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary));background:var(--dsw-alias-bg-layer-2);border-radius:99px;padding:5px 11px;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3921
|
+
.dab-font-name.is-empty{color:var(--dsw-alias-label-tertiary)}
|
|
3922
|
+
.dab-font-actions{display:flex;align-items:center;gap:8px;margin-left:auto}
|
|
3923
|
+
/* The font card's labels read one step larger than the rest of the panel —
|
|
3924
|
+
they carry most of the explanation on this page. Scoped to the card so the
|
|
3925
|
+
same .dab-hint / .dab-btn elsewhere keep their size. */
|
|
3926
|
+
.dab-font-actions .dab-btn{font-size:14.5px}
|
|
3927
|
+
.dab-font-card .dab-hint{font-size:13.5px}
|
|
3928
|
+
|
|
3929
|
+
/* ── text-stroke color dots ──────────────────────────────────────────────── */
|
|
3930
|
+
.dab-stroke-dots{display:flex;align-items:center;gap:9px;margin-top:14px}
|
|
3931
|
+
.dab-stroke-dot-wrap{position:relative;width:18px;height:18px;flex:none}
|
|
3932
|
+
.dab-stroke-dot{width:18px;height:18px;padding:0;border-radius:50%;border:1.5px solid var(--dsw-alias-border-l2);background-clip:padding-box;cursor:pointer;transition:transform .18s cubic-bezier(.34,1.56,.64,1),box-shadow .18s ease}
|
|
3933
|
+
.dab-stroke-dot:hover{transform:scale(1.16)}
|
|
3934
|
+
/* 'auto' has no fixed value — half light / half dark reads as "derived". */
|
|
3935
|
+
.dab-stroke-dot-auto{background:conic-gradient(from -90deg,#fff 0 50%,#000 50% 100%)}
|
|
3936
|
+
.dab-stroke-dot.is-active{box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-1),0 0 0 3.5px var(--dsw-alias-brand-primary);border-color:transparent}
|
|
3937
|
+
/* The native color picker sits invisible on top of the custom swatch. */
|
|
3938
|
+
.dab-stroke-dot-input{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;border:0;border-radius:50%;background:none;opacity:0;cursor:pointer}
|
|
3939
|
+
|
|
3559
3940
|
/* ── background preview hero ─────────────────────────────────────────────── */
|
|
3560
3941
|
.dab-hero{position:relative;border-radius:16px;overflow:hidden;aspect-ratio:16/9;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2)}
|
|
3561
3942
|
.dab-hero-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:transform .5s cubic-bezier(.22,1,.36,1)}
|
|
@@ -3985,6 +4366,12 @@ window.__ModuleLoader__.load({
|
|
|
3985
4366
|
className,
|
|
3986
4367
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
3987
4368
|
});
|
|
4369
|
+
/** Letterform glyph: a stroked capital A (the font/type page + nav entry). */
|
|
4370
|
+
const FontIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
4371
|
+
size,
|
|
4372
|
+
className,
|
|
4373
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 12.2 8 3.8l4 8.4" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.6 9.6h4.8" })]
|
|
4374
|
+
});
|
|
3988
4375
|
const PlayIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3989
4376
|
size,
|
|
3990
4377
|
className,
|
|
@@ -4975,7 +5362,7 @@ window.__ModuleLoader__.load({
|
|
|
4975
5362
|
}
|
|
4976
5363
|
//#endregion
|
|
4977
5364
|
//#region src/client/components/pages/InterfacePage.tsx
|
|
4978
|
-
const PARTS = [
|
|
5365
|
+
const PARTS$1 = [
|
|
4979
5366
|
{
|
|
4980
5367
|
opKey: "bg",
|
|
4981
5368
|
labelKey: "uiOpacityBg",
|
|
@@ -5025,7 +5412,7 @@ window.__ModuleLoader__.load({
|
|
|
5025
5412
|
function InterfacePage({ p }) {
|
|
5026
5413
|
const { t, setOps, setBlurs, setSop, setPanelOp } = p;
|
|
5027
5414
|
const hasBetterSidebar = useBetterSidebar();
|
|
5028
|
-
const parts = PARTS.filter((part) => !(part.isPanel && !hasBetterSidebar));
|
|
5415
|
+
const parts = PARTS$1.filter((part) => !(part.isPanel && !hasBetterSidebar));
|
|
5029
5416
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
5030
5417
|
className: "dab-head dab-rise",
|
|
5031
5418
|
style: { "--d": 0 },
|
|
@@ -5152,10 +5539,327 @@ window.__ModuleLoader__.load({
|
|
|
5152
5539
|
})] });
|
|
5153
5540
|
}
|
|
5154
5541
|
//#endregion
|
|
5542
|
+
//#region src/client/components/pages/FontPage.tsx
|
|
5543
|
+
const PARTS = [
|
|
5544
|
+
{
|
|
5545
|
+
key: "bg",
|
|
5546
|
+
labelKey: "uiOpacityBg",
|
|
5547
|
+
Icon: CanvasIcon
|
|
5548
|
+
},
|
|
5549
|
+
{
|
|
5550
|
+
key: "sidebar",
|
|
5551
|
+
labelKey: "uiOpacitySide",
|
|
5552
|
+
Icon: SidebarIcon
|
|
5553
|
+
},
|
|
5554
|
+
{
|
|
5555
|
+
key: "card",
|
|
5556
|
+
labelKey: "uiOpacityCard",
|
|
5557
|
+
Icon: ChatIcon
|
|
5558
|
+
},
|
|
5559
|
+
{
|
|
5560
|
+
key: "input",
|
|
5561
|
+
labelKey: "uiOpacityInput",
|
|
5562
|
+
Icon: InputIcon
|
|
5563
|
+
},
|
|
5564
|
+
{
|
|
5565
|
+
key: "settings",
|
|
5566
|
+
labelKey: "uiSop",
|
|
5567
|
+
Icon: GearIcon
|
|
5568
|
+
},
|
|
5569
|
+
{
|
|
5570
|
+
key: "chat",
|
|
5571
|
+
labelKey: "uiChatRegion",
|
|
5572
|
+
Icon: TextIcon
|
|
5573
|
+
},
|
|
5574
|
+
{
|
|
5575
|
+
key: "trajectory",
|
|
5576
|
+
labelKey: "uiTrajectory",
|
|
5577
|
+
Icon: TrajectoryIcon
|
|
5578
|
+
},
|
|
5579
|
+
{
|
|
5580
|
+
key: "produced",
|
|
5581
|
+
labelKey: "uiProduced",
|
|
5582
|
+
Icon: TextIcon
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
key: "panel",
|
|
5586
|
+
labelKey: "uiPanelRegion",
|
|
5587
|
+
Icon: PanelIcon,
|
|
5588
|
+
needsSidebar: true
|
|
5589
|
+
}
|
|
5590
|
+
];
|
|
5591
|
+
const COLOR_KEYS = [
|
|
5592
|
+
"auto",
|
|
5593
|
+
"gray",
|
|
5594
|
+
"black",
|
|
5595
|
+
"white",
|
|
5596
|
+
"theme",
|
|
5597
|
+
"custom"
|
|
5598
|
+
];
|
|
5599
|
+
const COLOR_LABEL_KEYS = {
|
|
5600
|
+
auto: "strokeAuto",
|
|
5601
|
+
gray: "strokeGray",
|
|
5602
|
+
black: "strokeBlack",
|
|
5603
|
+
white: "strokeWhite",
|
|
5604
|
+
theme: "strokeTheme",
|
|
5605
|
+
custom: "strokeCustom"
|
|
5606
|
+
};
|
|
5607
|
+
/** Swatch fill for one preset key. 'auto' gets its half-black/half-white
|
|
5608
|
+
* conic gradient from CSS, and 'custom' shows the stored hex. */
|
|
5609
|
+
function dotStyle(key, s) {
|
|
5610
|
+
switch (key) {
|
|
5611
|
+
case "gray": return { background: "#808080" };
|
|
5612
|
+
case "black": return { background: "#000" };
|
|
5613
|
+
case "white": return { background: "#fff" };
|
|
5614
|
+
case "theme": return { background: "var(--dsw-alias-brand-primary)" };
|
|
5615
|
+
case "custom": return { background: s.customColor };
|
|
5616
|
+
default: return {};
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
function FontPage({ p, notify }) {
|
|
5620
|
+
const { t, setFont, removeFont, setFontEnabled, setStrokes } = p;
|
|
5621
|
+
const hasBetterSidebar = useBetterSidebar();
|
|
5622
|
+
const fileRef = (0, react.useRef)(null);
|
|
5623
|
+
const [strokes, setStrokesState] = (0, react.useState)(() => rStrokes());
|
|
5624
|
+
const [fontInfo, setFontInfo] = (0, react.useState)(() => ({
|
|
5625
|
+
mime: cfg.fontMime,
|
|
5626
|
+
enabled: cfg.fontEnabled,
|
|
5627
|
+
name: null
|
|
5628
|
+
}));
|
|
5629
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5630
|
+
const hasFont = fontInfo.mime !== null;
|
|
5631
|
+
const fontLabel = fontInfo.name ?? (fontInfo.mime !== null ? fontInfo.mime.replace("font/", "").toUpperCase() : t("fontNone"));
|
|
5632
|
+
/** Write one group's stroke: cfg + live DOM now, disk on commit. `live`
|
|
5633
|
+
* marks slider drags (debounced save, no full re-apply). */
|
|
5634
|
+
const patch = (key, next, live) => {
|
|
5635
|
+
const map = {
|
|
5636
|
+
...strokes,
|
|
5637
|
+
[key]: {
|
|
5638
|
+
...strokes[key],
|
|
5639
|
+
...next
|
|
5640
|
+
}
|
|
5641
|
+
};
|
|
5642
|
+
setStrokesState(map);
|
|
5643
|
+
cfg.strokes = map;
|
|
5644
|
+
setPartStroke(key, map[key]);
|
|
5645
|
+
if (live) saveConfig();
|
|
5646
|
+
else setStrokes(map);
|
|
5647
|
+
};
|
|
5648
|
+
const pickFont = async (file) => {
|
|
5649
|
+
setBusy(true);
|
|
5650
|
+
setFontInfo({
|
|
5651
|
+
...fontInfo,
|
|
5652
|
+
name: file.name
|
|
5653
|
+
});
|
|
5654
|
+
const ok = await setFont(file);
|
|
5655
|
+
setBusy(false);
|
|
5656
|
+
if (ok) setFontInfo({
|
|
5657
|
+
mime: cfg.fontMime,
|
|
5658
|
+
enabled: cfg.fontEnabled,
|
|
5659
|
+
name: file.name
|
|
5660
|
+
});
|
|
5661
|
+
else {
|
|
5662
|
+
setFontInfo({
|
|
5663
|
+
mime: cfg.fontMime,
|
|
5664
|
+
enabled: cfg.fontEnabled,
|
|
5665
|
+
name: null
|
|
5666
|
+
});
|
|
5667
|
+
notify(t("fontFail"), false);
|
|
5668
|
+
}
|
|
5669
|
+
};
|
|
5670
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
5672
|
+
className: "dab-head dab-rise",
|
|
5673
|
+
style: { "--d": 0 },
|
|
5674
|
+
children: [
|
|
5675
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5676
|
+
className: "dab-overline",
|
|
5677
|
+
children: "Typography"
|
|
5678
|
+
}),
|
|
5679
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
5680
|
+
className: "dab-h1",
|
|
5681
|
+
children: t("fontPageTitle")
|
|
5682
|
+
}),
|
|
5683
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5684
|
+
className: "dab-desc",
|
|
5685
|
+
children: t("descFont")
|
|
5686
|
+
})
|
|
5687
|
+
]
|
|
5688
|
+
}),
|
|
5689
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5690
|
+
className: "dab-card dab-rise dab-font-card",
|
|
5691
|
+
style: { "--d": 1 },
|
|
5692
|
+
children: [
|
|
5693
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5694
|
+
className: "dab-row-head",
|
|
5695
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5696
|
+
className: "dab-font-title",
|
|
5697
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5698
|
+
className: "dab-part-ico",
|
|
5699
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextIcon, { size: 16 })
|
|
5700
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5701
|
+
className: "dab-part-name",
|
|
5702
|
+
children: t("fontTitle")
|
|
5703
|
+
})]
|
|
5704
|
+
}), hasFont ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5705
|
+
type: "button",
|
|
5706
|
+
className: `dab-toggle${fontInfo.enabled ? " is-on" : ""}`,
|
|
5707
|
+
role: "switch",
|
|
5708
|
+
"aria-checked": fontInfo.enabled,
|
|
5709
|
+
"aria-label": t("fontEnabled"),
|
|
5710
|
+
onClick: () => {
|
|
5711
|
+
const v = !fontInfo.enabled;
|
|
5712
|
+
setFontEnabled(v);
|
|
5713
|
+
setFontInfo({
|
|
5714
|
+
...fontInfo,
|
|
5715
|
+
enabled: v
|
|
5716
|
+
});
|
|
5717
|
+
},
|
|
5718
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
5719
|
+
}) : null]
|
|
5720
|
+
}),
|
|
5721
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5722
|
+
className: "dab-font-row",
|
|
5723
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5724
|
+
className: `dab-font-name${hasFont ? "" : " is-empty"}`,
|
|
5725
|
+
title: fontLabel,
|
|
5726
|
+
children: fontLabel
|
|
5727
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5728
|
+
className: "dab-font-actions",
|
|
5729
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5730
|
+
type: "button",
|
|
5731
|
+
className: "dab-btn dab-btn-primary",
|
|
5732
|
+
disabled: busy,
|
|
5733
|
+
onClick: () => fileRef.current?.click(),
|
|
5734
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UploadIcon, { size: 14 }), busy ? t("fontUploading") : t("fontUpload")]
|
|
5735
|
+
}), hasFont ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5736
|
+
type: "button",
|
|
5737
|
+
className: "dab-btn dab-btn-danger",
|
|
5738
|
+
onClick: () => {
|
|
5739
|
+
removeFont();
|
|
5740
|
+
setFontInfo({
|
|
5741
|
+
mime: null,
|
|
5742
|
+
enabled: cfg.fontEnabled,
|
|
5743
|
+
name: null
|
|
5744
|
+
});
|
|
5745
|
+
},
|
|
5746
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("fontRemove")]
|
|
5747
|
+
}) : null]
|
|
5748
|
+
})]
|
|
5749
|
+
}),
|
|
5750
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5751
|
+
className: "dab-hint",
|
|
5752
|
+
style: { marginTop: 10 },
|
|
5753
|
+
children: t("fontHint")
|
|
5754
|
+
}),
|
|
5755
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5756
|
+
ref: fileRef,
|
|
5757
|
+
type: "file",
|
|
5758
|
+
accept: ".ttf,.otf,.woff,.woff2,font/*",
|
|
5759
|
+
style: { display: "none" },
|
|
5760
|
+
onChange: (e) => {
|
|
5761
|
+
const f = e.target.files?.[0];
|
|
5762
|
+
e.target.value = "";
|
|
5763
|
+
if (f) pickFont(f);
|
|
5764
|
+
}
|
|
5765
|
+
})
|
|
5766
|
+
]
|
|
5767
|
+
}),
|
|
5768
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5769
|
+
className: "dab-card dab-rise",
|
|
5770
|
+
style: { "--d": 2 },
|
|
5771
|
+
children: [
|
|
5772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5773
|
+
className: "dab-part-head",
|
|
5774
|
+
style: { marginBottom: 10 },
|
|
5775
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5776
|
+
className: "dab-part-name",
|
|
5777
|
+
children: t("strokeTitle")
|
|
5778
|
+
})
|
|
5779
|
+
}),
|
|
5780
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5781
|
+
className: "dab-hint",
|
|
5782
|
+
style: { marginBottom: 14 },
|
|
5783
|
+
children: t("strokeHint")
|
|
5784
|
+
}),
|
|
5785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5786
|
+
className: "dab-grid-parts",
|
|
5787
|
+
children: PARTS.filter((part) => !(part.needsSidebar && !hasBetterSidebar)).map((part, i) => {
|
|
5788
|
+
const s = strokes[part.key];
|
|
5789
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5790
|
+
className: "dab-card dab-card-hover dab-rise",
|
|
5791
|
+
style: { "--d": i + 3 },
|
|
5792
|
+
children: [
|
|
5793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5794
|
+
className: "dab-part-head",
|
|
5795
|
+
children: [
|
|
5796
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5797
|
+
className: "dab-part-ico",
|
|
5798
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(part.Icon, { size: 16 })
|
|
5799
|
+
}),
|
|
5800
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5801
|
+
className: "dab-part-name",
|
|
5802
|
+
children: t(part.labelKey)
|
|
5803
|
+
}),
|
|
5804
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5805
|
+
className: "dab-part-badge",
|
|
5806
|
+
children: [s.width, "px"]
|
|
5807
|
+
})
|
|
5808
|
+
]
|
|
5809
|
+
}),
|
|
5810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
5811
|
+
label: t("strokeWidth"),
|
|
5812
|
+
min: 0,
|
|
5813
|
+
max: 4,
|
|
5814
|
+
step: .5,
|
|
5815
|
+
def: s.width,
|
|
5816
|
+
fmt: (v) => `${v}px`,
|
|
5817
|
+
onInput: (v) => patch(part.key, { width: v }, true),
|
|
5818
|
+
onChange: (v) => patch(part.key, { width: v }, false)
|
|
5819
|
+
}),
|
|
5820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5821
|
+
className: "dab-stroke-dots",
|
|
5822
|
+
children: COLOR_KEYS.map((key) => {
|
|
5823
|
+
const active = s.color === key;
|
|
5824
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5825
|
+
className: "dab-stroke-dot-wrap",
|
|
5826
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5827
|
+
type: "button",
|
|
5828
|
+
className: `dab-stroke-dot${key === "auto" ? " dab-stroke-dot-auto" : ""}${active ? " is-active" : ""}`,
|
|
5829
|
+
style: dotStyle(key, s),
|
|
5830
|
+
title: t(COLOR_LABEL_KEYS[key]),
|
|
5831
|
+
"aria-label": t(COLOR_LABEL_KEYS[key]),
|
|
5832
|
+
"aria-pressed": active,
|
|
5833
|
+
onClick: () => patch(part.key, { color: key }, false)
|
|
5834
|
+
}), key === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5835
|
+
type: "color",
|
|
5836
|
+
className: "dab-stroke-dot-input",
|
|
5837
|
+
value: s.customColor,
|
|
5838
|
+
"aria-label": t("strokeCustom"),
|
|
5839
|
+
onChange: (e) => patch(part.key, {
|
|
5840
|
+
color: "custom",
|
|
5841
|
+
customColor: e.target.value
|
|
5842
|
+
}, false)
|
|
5843
|
+
}) : null]
|
|
5844
|
+
}, key);
|
|
5845
|
+
})
|
|
5846
|
+
})
|
|
5847
|
+
]
|
|
5848
|
+
}, part.key);
|
|
5849
|
+
})
|
|
5850
|
+
})
|
|
5851
|
+
]
|
|
5852
|
+
})
|
|
5853
|
+
] });
|
|
5854
|
+
}
|
|
5855
|
+
//#endregion
|
|
5155
5856
|
//#region src/client/components/BgEditor.tsx
|
|
5156
5857
|
/** Placement slot for the active background type: video framing lives in its
|
|
5157
5858
|
* own state so image and video edits never overwrite each other. */
|
|
5158
5859
|
const activeState = () => cfg.backgroundType === "video" ? cfg.videoBgState : cfg.bgState;
|
|
5860
|
+
/** Zoom is shared by every input path (wheel + pinch) so all pictures animate
|
|
5861
|
+
* between the same bounds. */
|
|
5862
|
+
const clampZoom = (z) => Math.max(.1, Math.min(10, z));
|
|
5159
5863
|
function BgEditor({ url, t, onClose, onCommit }) {
|
|
5160
5864
|
const pw = Math.min(window.innerWidth * .75, 860);
|
|
5161
5865
|
const ph = Math.round(pw * window.innerHeight / window.innerWidth);
|
|
@@ -5181,6 +5885,19 @@ window.__ModuleLoader__.load({
|
|
|
5181
5885
|
spx: 0,
|
|
5182
5886
|
spy: 0
|
|
5183
5887
|
});
|
|
5888
|
+
const viewRef = (0, react.useRef)({
|
|
5889
|
+
zoom,
|
|
5890
|
+
pos
|
|
5891
|
+
});
|
|
5892
|
+
viewRef.current = {
|
|
5893
|
+
zoom,
|
|
5894
|
+
pos
|
|
5895
|
+
};
|
|
5896
|
+
/** Pinch gesture baseline (two fingers down): finger spread + midpoint and
|
|
5897
|
+
* the transform that was live at that moment. */
|
|
5898
|
+
const pinchRef = (0, react.useRef)(null);
|
|
5899
|
+
/** Single-finger pan baseline. */
|
|
5900
|
+
const touchDragRef = (0, react.useRef)(null);
|
|
5184
5901
|
(0, react.useEffect)(() => {
|
|
5185
5902
|
const img = new Image();
|
|
5186
5903
|
img.onload = () => {
|
|
@@ -5242,7 +5959,7 @@ window.__ModuleLoader__.load({
|
|
|
5242
5959
|
const rect = el.getBoundingClientRect();
|
|
5243
5960
|
const mx = rect.width / 2, my = rect.height / 2;
|
|
5244
5961
|
const factor = e.deltaY > 0 ? .97 : 1.03;
|
|
5245
|
-
const nz =
|
|
5962
|
+
const nz = clampZoom(zoom * factor);
|
|
5246
5963
|
const nx = mx - (mx - pos.x) * (nz / zoom);
|
|
5247
5964
|
const ny = my - (my - pos.y) * (nz / zoom);
|
|
5248
5965
|
setZoom(nz);
|
|
@@ -5257,6 +5974,97 @@ window.__ModuleLoader__.load({
|
|
|
5257
5974
|
el.addEventListener("wheel", onWheelCb, { passive: false });
|
|
5258
5975
|
return () => el.removeEventListener("wheel", onWheelCb);
|
|
5259
5976
|
}, [onWheelCb]);
|
|
5977
|
+
const onTouchStartCb = (0, react.useCallback)((e) => {
|
|
5978
|
+
const el = containerRef.current;
|
|
5979
|
+
if (!el) return;
|
|
5980
|
+
if (e.touches.length === 1) {
|
|
5981
|
+
pinchRef.current = null;
|
|
5982
|
+
const tp = e.touches[0];
|
|
5983
|
+
touchDragRef.current = {
|
|
5984
|
+
sx: tp.clientX,
|
|
5985
|
+
sy: tp.clientY,
|
|
5986
|
+
spx: viewRef.current.pos.x,
|
|
5987
|
+
spy: viewRef.current.pos.y
|
|
5988
|
+
};
|
|
5989
|
+
return;
|
|
5990
|
+
}
|
|
5991
|
+
if (e.touches.length >= 2) {
|
|
5992
|
+
touchDragRef.current = null;
|
|
5993
|
+
const rect = el.getBoundingClientRect();
|
|
5994
|
+
const a = e.touches[0], b = e.touches[1];
|
|
5995
|
+
pinchRef.current = {
|
|
5996
|
+
dist: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY),
|
|
5997
|
+
zoom: viewRef.current.zoom,
|
|
5998
|
+
cx: (a.clientX + b.clientX) / 2 - rect.left,
|
|
5999
|
+
cy: (a.clientY + b.clientY) / 2 - rect.top,
|
|
6000
|
+
px: viewRef.current.pos.x,
|
|
6001
|
+
py: viewRef.current.pos.y
|
|
6002
|
+
};
|
|
6003
|
+
}
|
|
6004
|
+
}, []);
|
|
6005
|
+
const onTouchMoveCb = (0, react.useCallback)((e) => {
|
|
6006
|
+
const el = containerRef.current;
|
|
6007
|
+
if (!el) return;
|
|
6008
|
+
if (e.touches.length >= 2 && pinchRef.current !== null) {
|
|
6009
|
+
e.preventDefault();
|
|
6010
|
+
const p = pinchRef.current;
|
|
6011
|
+
if (p.dist <= 0) return;
|
|
6012
|
+
const rect = el.getBoundingClientRect();
|
|
6013
|
+
const a = e.touches[0], b = e.touches[1];
|
|
6014
|
+
const dist = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
|
|
6015
|
+
const cx = (a.clientX + b.clientX) / 2 - rect.left;
|
|
6016
|
+
const cy = (a.clientY + b.clientY) / 2 - rect.top;
|
|
6017
|
+
const nz = clampZoom(p.zoom * (dist / p.dist));
|
|
6018
|
+
setZoom(nz);
|
|
6019
|
+
setPos({
|
|
6020
|
+
x: cx - (p.cx - p.px) / p.zoom * nz,
|
|
6021
|
+
y: cy - (p.cy - p.py) / p.zoom * nz
|
|
6022
|
+
});
|
|
6023
|
+
return;
|
|
6024
|
+
}
|
|
6025
|
+
if (e.touches.length === 1 && touchDragRef.current !== null) {
|
|
6026
|
+
e.preventDefault();
|
|
6027
|
+
const d = touchDragRef.current;
|
|
6028
|
+
const tp = e.touches[0];
|
|
6029
|
+
setPos({
|
|
6030
|
+
x: d.spx + tp.clientX - d.sx,
|
|
6031
|
+
y: d.spy + tp.clientY - d.sy
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
}, []);
|
|
6035
|
+
const onTouchEndCb = (0, react.useCallback)((e) => {
|
|
6036
|
+
if (e.touches.length === 1) {
|
|
6037
|
+
const tp = e.touches[0];
|
|
6038
|
+
touchDragRef.current = {
|
|
6039
|
+
sx: tp.clientX,
|
|
6040
|
+
sy: tp.clientY,
|
|
6041
|
+
spx: viewRef.current.pos.x,
|
|
6042
|
+
spy: viewRef.current.pos.y
|
|
6043
|
+
};
|
|
6044
|
+
pinchRef.current = null;
|
|
6045
|
+
} else if (e.touches.length === 0) {
|
|
6046
|
+
touchDragRef.current = null;
|
|
6047
|
+
pinchRef.current = null;
|
|
6048
|
+
}
|
|
6049
|
+
}, []);
|
|
6050
|
+
(0, react.useEffect)(() => {
|
|
6051
|
+
const el = containerRef.current;
|
|
6052
|
+
if (!el) return;
|
|
6053
|
+
el.addEventListener("touchstart", onTouchStartCb, { passive: false });
|
|
6054
|
+
el.addEventListener("touchmove", onTouchMoveCb, { passive: false });
|
|
6055
|
+
el.addEventListener("touchend", onTouchEndCb, { passive: false });
|
|
6056
|
+
el.addEventListener("touchcancel", onTouchEndCb, { passive: false });
|
|
6057
|
+
return () => {
|
|
6058
|
+
el.removeEventListener("touchstart", onTouchStartCb);
|
|
6059
|
+
el.removeEventListener("touchmove", onTouchMoveCb);
|
|
6060
|
+
el.removeEventListener("touchend", onTouchEndCb);
|
|
6061
|
+
el.removeEventListener("touchcancel", onTouchEndCb);
|
|
6062
|
+
};
|
|
6063
|
+
}, [
|
|
6064
|
+
onTouchStartCb,
|
|
6065
|
+
onTouchMoveCb,
|
|
6066
|
+
onTouchEndCb
|
|
6067
|
+
]);
|
|
5260
6068
|
const resetView = (0, react.useCallback)(() => {
|
|
5261
6069
|
if (imgSize.w === 0) return;
|
|
5262
6070
|
setZoom(1);
|
|
@@ -5290,7 +6098,10 @@ window.__ModuleLoader__.load({
|
|
|
5290
6098
|
background: "#000",
|
|
5291
6099
|
cursor: "grab",
|
|
5292
6100
|
width: pw,
|
|
5293
|
-
height: ph
|
|
6101
|
+
height: ph,
|
|
6102
|
+
touchAction: "none",
|
|
6103
|
+
WebkitUserSelect: "none",
|
|
6104
|
+
userSelect: "none"
|
|
5294
6105
|
},
|
|
5295
6106
|
onMouseDown: onDown,
|
|
5296
6107
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
@@ -6075,6 +6886,27 @@ window.__ModuleLoader__.load({
|
|
|
6075
6886
|
panel: 0,
|
|
6076
6887
|
produced: 0
|
|
6077
6888
|
};
|
|
6889
|
+
/** Presets ship with strokes off — text outlines are an opt-in look. Fresh
|
|
6890
|
+
* objects per call so a profile restore can never alias another preset's
|
|
6891
|
+
* stroke config (applyAppearance copies, but mutation-proofing is free). */
|
|
6892
|
+
const zeroStrokes = () => {
|
|
6893
|
+
const off = () => ({
|
|
6894
|
+
width: 0,
|
|
6895
|
+
color: "auto",
|
|
6896
|
+
customColor: "#808080"
|
|
6897
|
+
});
|
|
6898
|
+
return {
|
|
6899
|
+
bg: off(),
|
|
6900
|
+
sidebar: off(),
|
|
6901
|
+
card: off(),
|
|
6902
|
+
settings: off(),
|
|
6903
|
+
chat: off(),
|
|
6904
|
+
trajectory: off(),
|
|
6905
|
+
input: off(),
|
|
6906
|
+
panel: off(),
|
|
6907
|
+
produced: off()
|
|
6908
|
+
};
|
|
6909
|
+
};
|
|
6078
6910
|
const BUILTIN_PRESETS = [
|
|
6079
6911
|
{
|
|
6080
6912
|
key: "default",
|
|
@@ -6087,6 +6919,7 @@ window.__ModuleLoader__.load({
|
|
|
6087
6919
|
input: 1
|
|
6088
6920
|
},
|
|
6089
6921
|
blurs: { ...zeroBlurs },
|
|
6922
|
+
strokes: zeroStrokes(),
|
|
6090
6923
|
settingsOpacity: 1,
|
|
6091
6924
|
wallpaperOpacity: 1,
|
|
6092
6925
|
blur: 0,
|
|
@@ -6120,6 +6953,7 @@ window.__ModuleLoader__.load({
|
|
|
6120
6953
|
input: 14,
|
|
6121
6954
|
panel: 12
|
|
6122
6955
|
},
|
|
6956
|
+
strokes: zeroStrokes(),
|
|
6123
6957
|
settingsOpacity: .88,
|
|
6124
6958
|
wallpaperOpacity: 1,
|
|
6125
6959
|
blur: 0,
|
|
@@ -6140,6 +6974,7 @@ window.__ModuleLoader__.load({
|
|
|
6140
6974
|
input: 1
|
|
6141
6975
|
},
|
|
6142
6976
|
blurs: { ...zeroBlurs },
|
|
6977
|
+
strokes: zeroStrokes(),
|
|
6143
6978
|
settingsOpacity: 1,
|
|
6144
6979
|
wallpaperOpacity: 1,
|
|
6145
6980
|
blur: 0,
|
|
@@ -6173,6 +7008,7 @@ window.__ModuleLoader__.load({
|
|
|
6173
7008
|
input: 16,
|
|
6174
7009
|
panel: 16
|
|
6175
7010
|
},
|
|
7011
|
+
strokes: zeroStrokes(),
|
|
6176
7012
|
settingsOpacity: .85,
|
|
6177
7013
|
wallpaperOpacity: .92,
|
|
6178
7014
|
blur: 2,
|
|
@@ -6206,6 +7042,7 @@ window.__ModuleLoader__.load({
|
|
|
6206
7042
|
input: 20,
|
|
6207
7043
|
panel: 18
|
|
6208
7044
|
},
|
|
7045
|
+
strokes: zeroStrokes(),
|
|
6209
7046
|
settingsOpacity: .8,
|
|
6210
7047
|
wallpaperOpacity: 1,
|
|
6211
7048
|
blur: 0,
|
|
@@ -6230,6 +7067,7 @@ window.__ModuleLoader__.load({
|
|
|
6230
7067
|
input: 1
|
|
6231
7068
|
},
|
|
6232
7069
|
blurs: { ...zeroBlurs },
|
|
7070
|
+
strokes: zeroStrokes(),
|
|
6233
7071
|
settingsOpacity: 1,
|
|
6234
7072
|
wallpaperOpacity: 1,
|
|
6235
7073
|
blur: 0,
|
|
@@ -6689,6 +7527,14 @@ window.__ModuleLoader__.load({
|
|
|
6689
7527
|
Icon: LayersIcon,
|
|
6690
7528
|
node: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InterfacePage, { p: props })
|
|
6691
7529
|
},
|
|
7530
|
+
{
|
|
7531
|
+
label: t("pageFont"),
|
|
7532
|
+
Icon: FontIcon,
|
|
7533
|
+
node: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FontPage, {
|
|
7534
|
+
p: props,
|
|
7535
|
+
notify
|
|
7536
|
+
})
|
|
7537
|
+
},
|
|
6692
7538
|
{
|
|
6693
7539
|
label: t("pageBackground"),
|
|
6694
7540
|
Icon: PhotoIcon,
|
|
@@ -6826,7 +7672,7 @@ window.__ModuleLoader__.load({
|
|
|
6826
7672
|
}, "dsh-any-background: skin dispose");
|
|
6827
7673
|
const styleEl = document.createElement("style");
|
|
6828
7674
|
styleEl.dataset.plugin = "dsh-any-background";
|
|
6829
|
-
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
7675
|
+
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}${STROKE_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
6830
7676
|
document.head.appendChild(styleEl);
|
|
6831
7677
|
ctx.effect(() => () => {
|
|
6832
7678
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
@@ -7022,8 +7868,14 @@ window.__ModuleLoader__.load({
|
|
|
7022
7868
|
applyWp();
|
|
7023
7869
|
syncBg();
|
|
7024
7870
|
watchParts();
|
|
7025
|
-
loadPersisted().then(async (serverRotated) => {
|
|
7871
|
+
loadPersisted().then(async ({ rotated: serverRotated, firstRun }) => {
|
|
7872
|
+
if (firstRun) {
|
|
7873
|
+
persistConfig();
|
|
7874
|
+
await loadPersisted();
|
|
7875
|
+
applyWp();
|
|
7876
|
+
}
|
|
7026
7877
|
syncMetaNow();
|
|
7878
|
+
applyFontFace(fontServeUrl, cfg.fontEnabled, cfg.fontMime);
|
|
7027
7879
|
if (rHasColor()) {
|
|
7028
7880
|
const [h, s, l] = rColor();
|
|
7029
7881
|
registerCustom(h, s, l);
|
|
@@ -7126,6 +7978,28 @@ window.__ModuleLoader__.load({
|
|
|
7126
7978
|
if (ok) persistConfig();
|
|
7127
7979
|
});
|
|
7128
7980
|
};
|
|
7981
|
+
const FONT_EXT_MIME = {
|
|
7982
|
+
woff2: "font/woff2",
|
|
7983
|
+
woff: "font/woff",
|
|
7984
|
+
otf: "font/otf",
|
|
7985
|
+
ttf: "font/ttf"
|
|
7986
|
+
};
|
|
7987
|
+
const fontMimeFromName = (name) => {
|
|
7988
|
+
const ext = name.split(".").pop()?.toLowerCase() ?? "";
|
|
7989
|
+
return FONT_EXT_MIME[ext] ?? "font/ttf";
|
|
7990
|
+
};
|
|
7991
|
+
let fontPreviewUrl = null;
|
|
7992
|
+
const releaseFontPreview = (delay) => {
|
|
7993
|
+
if (fontPreviewUrl === null) return;
|
|
7994
|
+
const url = fontPreviewUrl;
|
|
7995
|
+
fontPreviewUrl = null;
|
|
7996
|
+
window.setTimeout(() => URL.revokeObjectURL(url), delay);
|
|
7997
|
+
};
|
|
7998
|
+
/** Point the @font-face at whatever is authoritative right now: the
|
|
7999
|
+
* persisted slot when one exists, otherwise nothing. */
|
|
8000
|
+
const applyStoredFont = () => {
|
|
8001
|
+
applyFontFace(cfg.fontMime !== null ? FONT_SERVE_URL : null, cfg.fontEnabled, cfg.fontMime);
|
|
8002
|
+
};
|
|
7129
8003
|
const [wh, ws, wl] = rColor();
|
|
7130
8004
|
const [dh, ds, dv] = hslToHsv(wh, ws, wl);
|
|
7131
8005
|
return {
|
|
@@ -7232,6 +8106,41 @@ window.__ModuleLoader__.load({
|
|
|
7232
8106
|
syncBg();
|
|
7233
8107
|
saveConfig();
|
|
7234
8108
|
},
|
|
8109
|
+
setStrokes: (strokes) => {
|
|
8110
|
+
cfg.strokes = strokes;
|
|
8111
|
+
applyStrokes();
|
|
8112
|
+
saveConfig();
|
|
8113
|
+
},
|
|
8114
|
+
setFont: async (file) => {
|
|
8115
|
+
releaseFontPreview(0);
|
|
8116
|
+
const localUrl = URL.createObjectURL(file);
|
|
8117
|
+
fontPreviewUrl = localUrl;
|
|
8118
|
+
applyFontFace(localUrl, true, fontMimeFromName(file.name));
|
|
8119
|
+
const mime = await uploadFont(file);
|
|
8120
|
+
if (mime === null) {
|
|
8121
|
+
applyStoredFont();
|
|
8122
|
+
releaseFontPreview(4e3);
|
|
8123
|
+
return false;
|
|
8124
|
+
}
|
|
8125
|
+
cfg.fontMime = mime;
|
|
8126
|
+
cfg.fontEnabled = true;
|
|
8127
|
+
applyFontFace(FONT_SERVE_URL, true, mime);
|
|
8128
|
+
releaseFontPreview(4e3);
|
|
8129
|
+
persistConfig();
|
|
8130
|
+
return true;
|
|
8131
|
+
},
|
|
8132
|
+
removeFont: () => {
|
|
8133
|
+
releaseFontPreview(0);
|
|
8134
|
+
cfg.fontMime = null;
|
|
8135
|
+
applyFontFace(null, false, null);
|
|
8136
|
+
removeFont();
|
|
8137
|
+
persistConfig();
|
|
8138
|
+
},
|
|
8139
|
+
setFontEnabled: (v) => {
|
|
8140
|
+
cfg.fontEnabled = v;
|
|
8141
|
+
applyStoredFont();
|
|
8142
|
+
persistConfig();
|
|
8143
|
+
},
|
|
7235
8144
|
setWop: (v) => {
|
|
7236
8145
|
cfg.wallpaperOpacity = v;
|
|
7237
8146
|
applyWp();
|
|
@@ -7358,6 +8267,7 @@ window.__ModuleLoader__.load({
|
|
|
7358
8267
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
7359
8268
|
}
|
|
7360
8269
|
persistConfig();
|
|
8270
|
+
syncMetaNow();
|
|
7361
8271
|
if (rHasColor()) {
|
|
7362
8272
|
const [h, s, l] = rColor();
|
|
7363
8273
|
registerCustom(h, s, l);
|