dsh-deepseek-style-theme 1.43.8 → 1.43.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +26 -2088
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
// The loader requires this bundle to register itself under the id the host asked
|
|
2
|
-
// for, and that id is the package NAME of whichever copy is installed -- which is
|
|
3
|
-
// not one value during a rename. The package moved from
|
|
4
|
-
// @dsh-external/dsh-deepseek-style-theme to the unscoped dsh-deepseek-style-theme
|
|
5
|
-
// in 1.43.1, so a profile installed before the rename asks for the old id and one
|
|
6
|
-
// installed after asks for the new one. A single hard-coded literal therefore
|
|
7
|
-
// breaks whichever half it does not match, and BOTH directions were hit for real,
|
|
8
|
-
// one machine each:
|
|
9
|
-
//
|
|
10
|
-
// literal old, install new -> "loaded without registering \"dsh-deepseek-style-theme\""
|
|
11
|
-
// literal new, install old -> "loaded without registering \"@dsh-external/...\""
|
|
12
|
-
//
|
|
13
|
-
// So register under BOTH ids with the same factory: the loader finds the one it
|
|
14
|
-
// wants and the other entry is simply never asked for. The registrations are
|
|
15
|
-
// guarded individually because a loader might reject an id it did not request,
|
|
16
|
-
// and that must not take the working one down with it.
|
|
17
1
|
const dshDeepseekStyleThemeFactory = (require) => {
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
var module = { exports: {} };
|
|
3
|
+
var exports = module.exports;
|
|
20
4
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
21
5
|
const React = require("react");
|
|
22
6
|
|
|
@@ -42,952 +26,6 @@ const dshDeepseekStyleThemeFactory = (require) => {
|
|
|
42
26
|
// interpolated value there would be DOM XSS.
|
|
43
27
|
const PLUGIN_ID = "dsh-deepseek-style-theme";
|
|
44
28
|
|
|
45
|
-
// ─── fluid background + liquid-glass refraction ───────────────────────
|
|
46
|
-
// Ported from dsh-theme-mineradio v2.3.8 (MIT, Copyright (c) 2026 John Wu).
|
|
47
|
-
// See the licence header on the block below; the notice must be preserved.
|
|
48
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
-
// deepseek-style-theme — fluid shader + glass dispersion port
|
|
50
|
-
//
|
|
51
|
-
// The fluid shader (GLSL programs, the WebGL2 ping-pong fluid solver, the
|
|
52
|
-
// pointer/click interaction feeds) and the glass chromatic-dispersion filter
|
|
53
|
-
// plus the specular-highlight cursor parallax below are ported from
|
|
54
|
-
// `dsh-theme-mineradio` v2.3.8 — MIT, Copyright (c) 2026 John Wu.
|
|
55
|
-
// The upstream MIT licence notice MUST be preserved wherever this code ships.
|
|
56
|
-
//
|
|
57
|
-
// Only naming and external dependencies were adapted; the three GLSL shader
|
|
58
|
-
// sources (VERTEX_SHADER / FLOW_SHADER / DISPLAY_SHADER) are byte-for-byte
|
|
59
|
-
// copies of upstream, including `#version 300 es`, every precision qualifier
|
|
60
|
-
// and every in-shader comment.
|
|
61
|
-
//
|
|
62
|
-
// External dependencies found in the upstream module and what was done:
|
|
63
|
-
//
|
|
64
|
-
// INLINED VERBATIM (they sit outside the ported ranges upstream and are
|
|
65
|
-
// small pure helpers the ported code cannot run without):
|
|
66
|
-
// • `uv(canvas, clientX, clientY)` — upstream fluid-interactions.ts
|
|
67
|
-
// line 916, used by
|
|
68
|
-
// attachFluidInteractions.
|
|
69
|
-
// • `hslToRgb01(h, s, l)` — upstream glass-dispersion.ts
|
|
70
|
-
// line 2409, used by tintMatrix.
|
|
71
|
-
// • `filterMarkup(id, refractId, tintId, map)` — upstream glass-dispersion.ts
|
|
72
|
-
// line 2443, used by
|
|
73
|
-
// startGlassDispersion to build
|
|
74
|
-
// the SVG <filter> pair.
|
|
75
|
-
// • `closestSpot(target)`, `visualRect(spot)`,
|
|
76
|
-
// `inside(visual, clientX, clientY)` — upstream spot-core.ts
|
|
77
|
-
// lines 1711-1732, used by
|
|
78
|
-
// startSpecularParallax.
|
|
79
|
-
//
|
|
80
|
-
// CONSTANTS TAKEN FROM ADJACENT UPSTREAM DECLARATIONS (needed to make the
|
|
81
|
-
// requested subset self-contained; upstream declares them beside it):
|
|
82
|
-
// • TINT_ID / REFRACT_ID / TALL_FILTER_ID / TALL_TINT_ID /
|
|
83
|
-
// TALL_REFRACT_ID / DEFAULT_REF_SCALE / EDGE_DX / TINT_SAT / TINT_LIGHT
|
|
84
|
-
// — values unchanged, only FILTER_ID's value renamed.
|
|
85
|
-
// • SPOT_ATTR / SPOT_SELECTOR (upstream spot-core.ts lines 1707-1710)
|
|
86
|
-
// — renamed to the `dshome-` prefix.
|
|
87
|
-
//
|
|
88
|
-
// RENAMED (no longer refers to the upstream plugin):
|
|
89
|
-
// • FILTER_ID "mineradio-glass-dispersion" → "dshome-glass-dispersion"
|
|
90
|
-
// • every other SVG filter/tint/refract id → "dshome-glass-dispersion-*"
|
|
91
|
-
// • ATTR "data-dsh-dispersion" → "data-dshome-dispersion"
|
|
92
|
-
// • SPOT_ATTR "data-dsh-aqua-spot" → "data-dshome-spot"
|
|
93
|
-
// • SPEC_X / SPEC_Y --dsh-aqua-spec-x/y → --dshome-spec-x/y
|
|
94
|
-
// • console tags "ui-aqua fluid shader" / "ui-aqua fluid link" →
|
|
95
|
-
// "deepseek-style-theme fluid shader" /
|
|
96
|
-
// "deepseek-style-theme fluid link"
|
|
97
|
-
//
|
|
98
|
-
// DELIBERATELY NOT PORTED (upstream settings/theme-store/React layer). Each
|
|
99
|
-
// was replaced by a parameter or a local default:
|
|
100
|
-
// • `this.settings.dispersionHue` (dispersionTintHue()) — upstream read the
|
|
101
|
-
// user's hue knob and an extracted-wallpaper hue. REPLACED by the `hue`
|
|
102
|
-
// argument of startGlassDispersion({ hue }), defaulting to the upstream
|
|
103
|
-
// default DEFAULT_TINT_HUE = 44. The caller owns the hue source.
|
|
104
|
-
// • `this.settings.dispersionRefract` — upstream called
|
|
105
|
-
// handle.setRefraction(n) on every settings change. The caller now calls
|
|
106
|
-
// the returned handle's setRefraction(n) itself.
|
|
107
|
-
// • `this.settings.fluidHue` / `fluidDepth` / `rainbow` / `stationOffset`
|
|
108
|
-
// and the helper `fluidToneColors()` (upstream line 125) plus
|
|
109
|
-
// `HUE_BASE` — NONE of these are ported. attachFluidShader takes a
|
|
110
|
-
// complete params object; SITE_FLUID_PARAMS is exported as the upstream
|
|
111
|
-
// default (its own three palette colours), so the caller supplies its
|
|
112
|
-
// own palette by spreading SITE_FLUID_PARAMS and overriding color1..3.
|
|
113
|
-
// • `this.settings.audioReact` / the mic analyser (createAudioReactivity)
|
|
114
|
-
// — not ported. The audio coupling survives as the ported handle method
|
|
115
|
-
// `setAudioLow(level)`; the caller feeds it, or ignores it.
|
|
116
|
-
// • upstream's React component and the `[data-dsh-aqua-fluid-canvas]` /
|
|
117
|
-
// `[data-dsh-aqua-ambient]` mount lookups — the caller passes the canvas
|
|
118
|
-
// element and the { main, mainCanvas } handle explicitly.
|
|
119
|
-
//
|
|
120
|
-
// DOM hooks the ported code still reads (product/platform markup, intentionally
|
|
121
|
-
// NOT renamed): `[data-composer-card]` and `[data-dsh-stats]` inside
|
|
122
|
-
// visualRect(), and the `button` closest-match in attachFluidInteractions.
|
|
123
|
-
//
|
|
124
|
-
// Indentation note: this fragment is tab-indented at a base of 2 tabs to match
|
|
125
|
-
// the target factory body. Line indentation INSIDE the three GLSL template
|
|
126
|
-
// literals is string data and was left exactly as upstream, per the verbatim
|
|
127
|
-
// requirement; tab-indenting those lines would have altered the shipped shader
|
|
128
|
-
// text.
|
|
129
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
130
|
-
|
|
131
|
-
var SITE_FLUID_PARAMS = {
|
|
132
|
-
mouseRadius: 0.22,
|
|
133
|
-
mouseStrength: 1.1,
|
|
134
|
-
decay: 0.96,
|
|
135
|
-
distortBoost: 1.35,
|
|
136
|
-
noiseBoost: 0,
|
|
137
|
-
swirlBoost: 0.45,
|
|
138
|
-
speed: 14,
|
|
139
|
-
distortion: 20,
|
|
140
|
-
swirl: 12,
|
|
141
|
-
swirlIterations: 8,
|
|
142
|
-
scale: 0.5,
|
|
143
|
-
rotation: -5,
|
|
144
|
-
proportion: 50,
|
|
145
|
-
softness: 100,
|
|
146
|
-
shapeScale: 10,
|
|
147
|
-
offsetX: 0,
|
|
148
|
-
offsetY: 65,
|
|
149
|
-
color1: "#8AA3D6",
|
|
150
|
-
color2: "#FFFFFF",
|
|
151
|
-
color3: "#FFFFFF"
|
|
152
|
-
};
|
|
153
|
-
var VERTEX_SHADER = `#version 300 es
|
|
154
|
-
in vec4 a_position;
|
|
155
|
-
out vec2 vUv;
|
|
156
|
-
void main() {
|
|
157
|
-
vUv = a_position.xy * 0.5 + 0.5;
|
|
158
|
-
gl_Position = a_position;
|
|
159
|
-
}
|
|
160
|
-
`;
|
|
161
|
-
var FLOW_SHADER = `#version 300 es
|
|
162
|
-
precision mediump float;
|
|
163
|
-
in vec2 vUv;
|
|
164
|
-
uniform sampler2D u_prev;
|
|
165
|
-
uniform vec2 u_mouse;
|
|
166
|
-
uniform vec2 u_velocity;
|
|
167
|
-
uniform float u_brushRadius;
|
|
168
|
-
uniform float u_brushStrength;
|
|
169
|
-
uniform float u_decay;
|
|
170
|
-
out vec4 fragColor;
|
|
171
|
-
|
|
172
|
-
void main() {
|
|
173
|
-
vec4 prev = texture(u_prev, vUv);
|
|
174
|
-
|
|
175
|
-
prev.r *= u_decay;
|
|
176
|
-
prev.gb = mix(vec2(0.5), prev.gb, u_decay);
|
|
177
|
-
|
|
178
|
-
float dist = distance(vUv, u_mouse);
|
|
179
|
-
|
|
180
|
-
float influence = exp(-dist * dist / (u_brushRadius * u_brushRadius * 0.5));
|
|
181
|
-
influence = max(0.0, influence - 0.01);
|
|
182
|
-
|
|
183
|
-
float speed = length(u_velocity);
|
|
184
|
-
float presenceStrength = u_brushStrength * 0.3;
|
|
185
|
-
float velBonus = min(speed * 3.0, 0.7) * u_brushStrength;
|
|
186
|
-
float totalStrength = presenceStrength + velBonus;
|
|
187
|
-
|
|
188
|
-
prev.r = max(prev.r, influence * totalStrength);
|
|
189
|
-
float blendAmt = influence * min(totalStrength, 0.4) * 0.3;
|
|
190
|
-
prev.g = mix(prev.g, clamp(u_velocity.x * 2.0 + 0.5, 0.0, 1.0), blendAmt);
|
|
191
|
-
prev.b = mix(prev.b, clamp(u_velocity.y * 2.0 + 0.5, 0.0, 1.0), blendAmt);
|
|
192
|
-
|
|
193
|
-
fragColor = prev;
|
|
194
|
-
}
|
|
195
|
-
`;
|
|
196
|
-
var DISPLAY_SHADER = `#version 300 es
|
|
197
|
-
precision mediump float;
|
|
198
|
-
in vec2 vUv;
|
|
199
|
-
uniform float u_time;
|
|
200
|
-
uniform float u_pixelRatio;
|
|
201
|
-
uniform vec2 u_resolution;
|
|
202
|
-
uniform float u_scale;
|
|
203
|
-
uniform float u_rotation;
|
|
204
|
-
uniform vec4 u_color1, u_color2, u_color3;
|
|
205
|
-
uniform float u_colorCount;
|
|
206
|
-
uniform float u_proportion;
|
|
207
|
-
uniform float u_softness;
|
|
208
|
-
uniform float u_shape;
|
|
209
|
-
uniform float u_shapeScale;
|
|
210
|
-
uniform float u_distortion;
|
|
211
|
-
uniform float u_swirl;
|
|
212
|
-
uniform float u_swirlIterations;
|
|
213
|
-
uniform vec2 u_offset;
|
|
214
|
-
uniform sampler2D u_flowmap;
|
|
215
|
-
uniform float u_distortBoost;
|
|
216
|
-
uniform float u_noiseBoost;
|
|
217
|
-
uniform float u_swirlBoost;
|
|
218
|
-
out vec4 fragColor;
|
|
219
|
-
|
|
220
|
-
#define TWO_PI 6.28318530718
|
|
221
|
-
#define PI 3.14159265358979323846
|
|
222
|
-
|
|
223
|
-
vec2 rotate(vec2 uv, float th) { return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv; }
|
|
224
|
-
float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); }
|
|
225
|
-
float noise(vec2 st) {
|
|
226
|
-
vec2 i = floor(st); vec2 f = fract(st);
|
|
227
|
-
float a = random(i), b = random(i + vec2(1,0)), c = random(i + vec2(0,1)), d = random(i + vec2(1,1));
|
|
228
|
-
vec2 u = f*f*(3.0-2.0*f);
|
|
229
|
-
return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
vec3 blend_multi(float mixer, float softness) {
|
|
233
|
-
float edge = 1.0 - softness;
|
|
234
|
-
vec3 col = u_color1.rgb;
|
|
235
|
-
if (u_colorCount > 1.5) { col = mix(col, u_color2.rgb, smoothstep(0.0 + 0.35*edge, 0.7 - 0.35*edge, mixer)); }
|
|
236
|
-
if (u_colorCount > 2.5) { col = mix(col, u_color3.rgb, smoothstep(0.3 + 0.35*edge, 1.0 - 0.35*edge, mixer)); }
|
|
237
|
-
return col;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
void main() {
|
|
241
|
-
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
|
242
|
-
float t = .5 * u_time;
|
|
243
|
-
float ns = .0005 + .006 * u_scale;
|
|
244
|
-
uv -= .5; uv *= (ns * u_resolution); uv = rotate(uv, u_rotation * .5 * PI);
|
|
245
|
-
uv /= u_pixelRatio; uv += .5; uv += u_offset;
|
|
246
|
-
|
|
247
|
-
vec2 fragUV = gl_FragCoord.xy / u_resolution.xy;
|
|
248
|
-
vec4 flow = texture(u_flowmap, fragUV);
|
|
249
|
-
float influence = flow.r;
|
|
250
|
-
vec2 flowDir = (flow.gb - 0.5) * 2.0;
|
|
251
|
-
|
|
252
|
-
float n1 = noise(uv + t), n2 = noise(uv*2. - t);
|
|
253
|
-
float angle = n1 * TWO_PI;
|
|
254
|
-
|
|
255
|
-
float totalDistortion = u_distortion + influence * u_distortBoost;
|
|
256
|
-
uv.x += 4. * totalDistortion * n2 * cos(angle);
|
|
257
|
-
uv.y += 4. * totalDistortion * n2 * sin(angle);
|
|
258
|
-
|
|
259
|
-
uv += flowDir * influence * 0.15;
|
|
260
|
-
|
|
261
|
-
if (influence > 0.001) {
|
|
262
|
-
float localNoise = noise(uv * 2.0 + t * 1.5);
|
|
263
|
-
uv += influence * u_noiseBoost * vec2(cos(localNoise * TWO_PI), sin(localNoise * TWO_PI));
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
float iters = ceil(clamp(u_swirlIterations, 1., 30.));
|
|
267
|
-
float swirlAmt = clamp(u_swirl, 0., 2.) + influence * u_swirlBoost;
|
|
268
|
-
for (float i = 1.; i <= 30.0; i++) {
|
|
269
|
-
if (i > iters) break;
|
|
270
|
-
uv.x += swirlAmt / i * cos(t + i*1.5*uv.y);
|
|
271
|
-
uv.y += swirlAmt / i * cos(t + i*1.*uv.x);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
float proportion = clamp(u_proportion, 0., 1.);
|
|
275
|
-
vec2 cuv = uv * (.5 + 3.5 * u_shapeScale);
|
|
276
|
-
float shape = .5 + .5 * sin(cuv.x) * cos(cuv.y);
|
|
277
|
-
float mixer = shape + .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5);
|
|
278
|
-
vec3 col = blend_multi(mixer, clamp(u_softness, 0., 1.));
|
|
279
|
-
fragColor = vec4(col, 1.0);
|
|
280
|
-
}
|
|
281
|
-
`;
|
|
282
|
-
function hexToRgb(value) {
|
|
283
|
-
const hex = value.replace("#", "");
|
|
284
|
-
return [
|
|
285
|
-
parseInt(hex.slice(0, 2), 16) / 255,
|
|
286
|
-
parseInt(hex.slice(2, 4), 16) / 255,
|
|
287
|
-
parseInt(hex.slice(4, 6), 16) / 255
|
|
288
|
-
];
|
|
289
|
-
}
|
|
290
|
-
function attachFluidShader(canvas, params) {
|
|
291
|
-
const gl = canvas.getContext("webgl2", {
|
|
292
|
-
alpha: true,
|
|
293
|
-
premultipliedAlpha: false,
|
|
294
|
-
powerPreference: "low-power"
|
|
295
|
-
});
|
|
296
|
-
if (gl === null) {
|
|
297
|
-
return {
|
|
298
|
-
setParams: () => {
|
|
299
|
-
},
|
|
300
|
-
stir: () => {
|
|
301
|
-
},
|
|
302
|
-
setAudioLow: () => {
|
|
303
|
-
},
|
|
304
|
-
setRunning: () => {
|
|
305
|
-
},
|
|
306
|
-
dispose: () => {
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
const compile = (type, source) => {
|
|
311
|
-
const shader = gl.createShader(type);
|
|
312
|
-
if (shader === null) return null;
|
|
313
|
-
gl.shaderSource(shader, source);
|
|
314
|
-
gl.compileShader(shader);
|
|
315
|
-
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
316
|
-
console.error("deepseek-style-theme fluid shader:", gl.getShaderInfoLog(shader));
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
|
-
return shader;
|
|
320
|
-
};
|
|
321
|
-
const link = (fragment) => {
|
|
322
|
-
const vertex = compile(gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
323
|
-
const frag = compile(gl.FRAGMENT_SHADER, fragment);
|
|
324
|
-
if (vertex === null || frag === null) return null;
|
|
325
|
-
const program = gl.createProgram();
|
|
326
|
-
if (program === null) return null;
|
|
327
|
-
gl.attachShader(program, vertex);
|
|
328
|
-
gl.attachShader(program, frag);
|
|
329
|
-
gl.linkProgram(program);
|
|
330
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
331
|
-
console.error("deepseek-style-theme fluid link:", gl.getProgramInfoLog(program));
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
return program;
|
|
335
|
-
};
|
|
336
|
-
const flowProgram = link(FLOW_SHADER);
|
|
337
|
-
const displayProgram = link(DISPLAY_SHADER);
|
|
338
|
-
if (flowProgram === null || displayProgram === null) {
|
|
339
|
-
return {
|
|
340
|
-
setParams: () => {
|
|
341
|
-
},
|
|
342
|
-
stir: () => {
|
|
343
|
-
},
|
|
344
|
-
setAudioLow: () => {
|
|
345
|
-
},
|
|
346
|
-
setRunning: () => {
|
|
347
|
-
},
|
|
348
|
-
dispose: () => {
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
const flow = {
|
|
353
|
-
prev: gl.getUniformLocation(flowProgram, "u_prev"),
|
|
354
|
-
mouse: gl.getUniformLocation(flowProgram, "u_mouse"),
|
|
355
|
-
velocity: gl.getUniformLocation(flowProgram, "u_velocity"),
|
|
356
|
-
brushRadius: gl.getUniformLocation(flowProgram, "u_brushRadius"),
|
|
357
|
-
brushStrength: gl.getUniformLocation(flowProgram, "u_brushStrength"),
|
|
358
|
-
decay: gl.getUniformLocation(flowProgram, "u_decay")
|
|
359
|
-
};
|
|
360
|
-
const display = {
|
|
361
|
-
time: gl.getUniformLocation(displayProgram, "u_time"),
|
|
362
|
-
pixelRatio: gl.getUniformLocation(displayProgram, "u_pixelRatio"),
|
|
363
|
-
resolution: gl.getUniformLocation(displayProgram, "u_resolution"),
|
|
364
|
-
scale: gl.getUniformLocation(displayProgram, "u_scale"),
|
|
365
|
-
rotation: gl.getUniformLocation(displayProgram, "u_rotation"),
|
|
366
|
-
offset: gl.getUniformLocation(displayProgram, "u_offset"),
|
|
367
|
-
color1: gl.getUniformLocation(displayProgram, "u_color1"),
|
|
368
|
-
color2: gl.getUniformLocation(displayProgram, "u_color2"),
|
|
369
|
-
color3: gl.getUniformLocation(displayProgram, "u_color3"),
|
|
370
|
-
colorCount: gl.getUniformLocation(displayProgram, "u_colorCount"),
|
|
371
|
-
proportion: gl.getUniformLocation(displayProgram, "u_proportion"),
|
|
372
|
-
softness: gl.getUniformLocation(displayProgram, "u_softness"),
|
|
373
|
-
shape: gl.getUniformLocation(displayProgram, "u_shape"),
|
|
374
|
-
shapeScale: gl.getUniformLocation(displayProgram, "u_shapeScale"),
|
|
375
|
-
distortion: gl.getUniformLocation(displayProgram, "u_distortion"),
|
|
376
|
-
swirl: gl.getUniformLocation(displayProgram, "u_swirl"),
|
|
377
|
-
swirlIterations: gl.getUniformLocation(displayProgram, "u_swirlIterations"),
|
|
378
|
-
flowmap: gl.getUniformLocation(displayProgram, "u_flowmap"),
|
|
379
|
-
distortBoost: gl.getUniformLocation(displayProgram, "u_distortBoost"),
|
|
380
|
-
noiseBoost: gl.getUniformLocation(displayProgram, "u_noiseBoost"),
|
|
381
|
-
swirlBoost: gl.getUniformLocation(displayProgram, "u_swirlBoost")
|
|
382
|
-
};
|
|
383
|
-
const quadBuffer = gl.createBuffer();
|
|
384
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
385
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
386
|
-
const bindQuad = (program) => {
|
|
387
|
-
const position = gl.getAttribLocation(program, "a_position");
|
|
388
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
|
|
389
|
-
gl.enableVertexAttribArray(position);
|
|
390
|
-
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
|
|
391
|
-
};
|
|
392
|
-
const makeTarget = (width2, height2, initial2) => {
|
|
393
|
-
const tex = gl.createTexture();
|
|
394
|
-
if (tex === null) throw new Error("deepseek-style-theme fluid: texture allocation failed");
|
|
395
|
-
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
396
|
-
if (initial2 !== void 0) {
|
|
397
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width2, height2, 0, gl.RGBA, gl.UNSIGNED_BYTE, initial2);
|
|
398
|
-
} else {
|
|
399
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width2, height2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
400
|
-
}
|
|
401
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
402
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
403
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
404
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
405
|
-
const fbo = gl.createFramebuffer();
|
|
406
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
407
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
|
|
408
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
409
|
-
return { fbo, tex };
|
|
410
|
-
};
|
|
411
|
-
let width = 0;
|
|
412
|
-
let height = 0;
|
|
413
|
-
let flowWidth = 0;
|
|
414
|
-
let flowHeight = 0;
|
|
415
|
-
let flip = false;
|
|
416
|
-
let current = { ...params };
|
|
417
|
-
let audioLow = 0;
|
|
418
|
-
const pointer = { x: 0.5, y: 0.5, smoothX: 0.5, smoothY: 0.5, vx: 0, vy: 0, svx: 0, svy: 0 };
|
|
419
|
-
const dprCap = Math.min(window.devicePixelRatio || 1, 1.5);
|
|
420
|
-
width = Math.round(canvas.clientWidth * dprCap);
|
|
421
|
-
height = Math.round(canvas.clientHeight * dprCap);
|
|
422
|
-
canvas.width = width;
|
|
423
|
-
canvas.height = height;
|
|
424
|
-
flowWidth = Math.round(width / 4);
|
|
425
|
-
flowHeight = Math.round(height / 4);
|
|
426
|
-
const initial = new Uint8Array(flowWidth * flowHeight * 4);
|
|
427
|
-
for (let i = 0; i < flowWidth * flowHeight; i += 1) {
|
|
428
|
-
initial[4 * i] = 0;
|
|
429
|
-
initial[4 * i + 1] = 128;
|
|
430
|
-
initial[4 * i + 2] = 128;
|
|
431
|
-
initial[4 * i + 3] = 255;
|
|
432
|
-
}
|
|
433
|
-
let targetA = makeTarget(flowWidth, flowHeight, initial);
|
|
434
|
-
let targetB = makeTarget(flowWidth, flowHeight, initial);
|
|
435
|
-
const coarse = window.matchMedia("(hover: none), (pointer: coarse)").matches;
|
|
436
|
-
const ua = navigator;
|
|
437
|
-
const windows = ua.userAgentData ? ua.userAgentData.platform === "Windows" : navigator.userAgent.includes("Windows");
|
|
438
|
-
const onMouseMove = (event) => {
|
|
439
|
-
const rect = canvas.getBoundingClientRect();
|
|
440
|
-
pointer.x = (event.clientX - rect.left) / rect.width;
|
|
441
|
-
pointer.y = 1 - (event.clientY - rect.top) / rect.height;
|
|
442
|
-
};
|
|
443
|
-
// Local adaptation: upstream always feeds the cursor into the flow field
|
|
444
|
-
// (except on touch and Windows). DSTT gates it behind its own
|
|
445
|
-
// `fluidBrush` setting, which ships OFF — the brush is the most
|
|
446
|
-
// expensive part of the simulation and leaves a visible wake trailing
|
|
447
|
-
// the pointer across the whole background.
|
|
448
|
-
if (!coarse && !windows && params.mouseFeed !== false) window.addEventListener("mousemove", onMouseMove);
|
|
449
|
-
const start = performance.now();
|
|
450
|
-
let raf = 0;
|
|
451
|
-
let running = false;
|
|
452
|
-
let previous = 0;
|
|
453
|
-
const step = 1e3 / 30;
|
|
454
|
-
const frame = (now) => {
|
|
455
|
-
raf = requestAnimationFrame(frame);
|
|
456
|
-
if (now - previous < step) return;
|
|
457
|
-
previous = now - (now - previous) % step;
|
|
458
|
-
const ratio = Math.min(window.devicePixelRatio || 1, 1.5);
|
|
459
|
-
const nextWidth = Math.round(canvas.clientWidth * ratio);
|
|
460
|
-
const nextHeight = Math.round(canvas.clientHeight * ratio);
|
|
461
|
-
if (nextWidth > 0 && nextHeight > 0 && (nextWidth !== width || nextHeight !== height)) {
|
|
462
|
-
width = nextWidth;
|
|
463
|
-
height = nextHeight;
|
|
464
|
-
canvas.width = width;
|
|
465
|
-
canvas.height = height;
|
|
466
|
-
}
|
|
467
|
-
const p = current;
|
|
468
|
-
const s = pointer;
|
|
469
|
-
s.svx *= 0.94;
|
|
470
|
-
s.svy *= 0.94;
|
|
471
|
-
s.smoothX += (s.x - s.smoothX) * 0.12;
|
|
472
|
-
s.smoothY += (s.y - s.smoothY) * 0.12;
|
|
473
|
-
s.svx += ((s.x - s.smoothX) * 0.5 - s.svx) * 0.15;
|
|
474
|
-
s.svy += ((s.y - s.smoothY) * 0.5 - s.svy) * 0.15;
|
|
475
|
-
const read = flip ? targetA : targetB;
|
|
476
|
-
const write = flip ? targetB : targetA;
|
|
477
|
-
flip = !flip;
|
|
478
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, write.fbo);
|
|
479
|
-
gl.viewport(0, 0, flowWidth, flowHeight);
|
|
480
|
-
gl.useProgram(flowProgram);
|
|
481
|
-
bindQuad(flowProgram);
|
|
482
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
483
|
-
gl.bindTexture(gl.TEXTURE_2D, read.tex);
|
|
484
|
-
gl.uniform1i(flow.prev, 0);
|
|
485
|
-
gl.uniform2f(flow.mouse, s.smoothX, s.smoothY);
|
|
486
|
-
gl.uniform2f(flow.velocity, s.svx, s.svy);
|
|
487
|
-
gl.uniform1f(flow.brushRadius, p.mouseRadius);
|
|
488
|
-
gl.uniform1f(flow.brushStrength, p.mouseStrength);
|
|
489
|
-
gl.uniform1f(flow.decay, p.decay);
|
|
490
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
491
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
492
|
-
gl.viewport(0, 0, width, height);
|
|
493
|
-
gl.useProgram(displayProgram);
|
|
494
|
-
bindQuad(displayProgram);
|
|
495
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
496
|
-
gl.bindTexture(gl.TEXTURE_2D, write.tex);
|
|
497
|
-
gl.uniform1i(display.flowmap, 0);
|
|
498
|
-
const time = (performance.now() - start) * 1e-3 * (p.speed * (1 + audioLow * 1.3) / 100);
|
|
499
|
-
gl.uniform1f(display.time, time);
|
|
500
|
-
gl.uniform1f(display.pixelRatio, window.devicePixelRatio || 1);
|
|
501
|
-
gl.uniform2f(display.resolution, width, height);
|
|
502
|
-
gl.uniform1f(display.scale, p.scale);
|
|
503
|
-
gl.uniform1f(display.rotation, p.rotation / 90);
|
|
504
|
-
gl.uniform2f(display.offset, p.offsetX / 100, p.offsetY / 100);
|
|
505
|
-
const c1 = hexToRgb(p.color1 || "#2E58A4");
|
|
506
|
-
const c2 = hexToRgb(p.color2 || "#D2E2EE");
|
|
507
|
-
const c3 = hexToRgb(p.color3 || "#FFFFFF");
|
|
508
|
-
gl.uniform4f(display.color1, c1[0], c1[1], c1[2], 1);
|
|
509
|
-
gl.uniform4f(display.color2, c2[0], c2[1], c2[2], 1);
|
|
510
|
-
gl.uniform4f(display.color3, c3[0], c3[1], c3[2], 1);
|
|
511
|
-
gl.uniform1f(display.colorCount, 3);
|
|
512
|
-
gl.uniform1f(display.proportion, p.proportion / 100);
|
|
513
|
-
gl.uniform1f(display.softness, p.softness / 100);
|
|
514
|
-
gl.uniform1f(display.shape, 0);
|
|
515
|
-
gl.uniform1f(display.shapeScale, p.shapeScale / 100);
|
|
516
|
-
gl.uniform1f(display.distortion, p.distortion / 100);
|
|
517
|
-
gl.uniform1f(display.swirl, p.swirl / 50);
|
|
518
|
-
gl.uniform1f(display.swirlIterations, p.swirlIterations);
|
|
519
|
-
gl.uniform1f(display.distortBoost, p.distortBoost * (1 + audioLow * 0.85));
|
|
520
|
-
gl.uniform1f(display.noiseBoost, p.noiseBoost);
|
|
521
|
-
gl.uniform1f(display.swirlBoost, p.swirlBoost * (1 + audioLow * 0.6));
|
|
522
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
523
|
-
};
|
|
524
|
-
const handle = {
|
|
525
|
-
setParams: (next) => {
|
|
526
|
-
current = { ...next };
|
|
527
|
-
if (!running) {
|
|
528
|
-
frame(performance.now());
|
|
529
|
-
cancelAnimationFrame(raf);
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
stir: (x, y, vx, vy) => {
|
|
533
|
-
pointer.x += (x - pointer.x) * 0.35;
|
|
534
|
-
pointer.y += (y - pointer.y) * 0.35;
|
|
535
|
-
pointer.svx += (vx - pointer.svx) * 0.3;
|
|
536
|
-
pointer.svy += (vy - pointer.svy) * 0.3;
|
|
537
|
-
},
|
|
538
|
-
setAudioLow: (level) => {
|
|
539
|
-
audioLow = Math.max(0, Math.min(1, level));
|
|
540
|
-
},
|
|
541
|
-
setRunning: (on) => {
|
|
542
|
-
if (on) {
|
|
543
|
-
if (running) return;
|
|
544
|
-
running = true;
|
|
545
|
-
previous = 0;
|
|
546
|
-
raf = requestAnimationFrame(frame);
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
if (!running) return;
|
|
550
|
-
running = false;
|
|
551
|
-
cancelAnimationFrame(raf);
|
|
552
|
-
},
|
|
553
|
-
dispose: () => {
|
|
554
|
-
cancelAnimationFrame(raf);
|
|
555
|
-
window.removeEventListener("mousemove", onMouseMove);
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
559
|
-
frame(performance.now());
|
|
560
|
-
cancelAnimationFrame(raf);
|
|
561
|
-
return handle;
|
|
562
|
-
}
|
|
563
|
-
running = true;
|
|
564
|
-
raf = requestAnimationFrame(frame);
|
|
565
|
-
return handle;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Inlined from upstream `src/client/fluid-interactions.ts` (line 916):
|
|
569
|
-
// canvas-relative pointer position, flipped to GL bottom-left origin.
|
|
570
|
-
function uv(canvas, clientX, clientY) {
|
|
571
|
-
const rect = canvas.getBoundingClientRect();
|
|
572
|
-
return {
|
|
573
|
-
x: rect.width <= 0 ? 0.5 : (clientX - rect.left) / rect.width,
|
|
574
|
-
y: rect.height <= 0 ? 0.5 : 1 - (clientY - rect.top) / rect.height
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function attachFluidInteractions(targets) {
|
|
578
|
-
const { main, mainCanvas } = targets;
|
|
579
|
-
const lastStir = /* @__PURE__ */ new WeakMap();
|
|
580
|
-
const ripples = /* @__PURE__ */ new Set();
|
|
581
|
-
const stirButton = (button, strength) => {
|
|
582
|
-
const now = performance.now();
|
|
583
|
-
const previous = lastStir.get(button) ?? 0;
|
|
584
|
-
if (now - previous < 160) return;
|
|
585
|
-
lastStir.set(button, now);
|
|
586
|
-
const rect = button.getBoundingClientRect();
|
|
587
|
-
const point = uv(mainCanvas, rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
588
|
-
main.stir(point.x, point.y, 0, -strength);
|
|
589
|
-
};
|
|
590
|
-
const ripple = (cx, cy) => {
|
|
591
|
-
const rect = mainCanvas.getBoundingClientRect();
|
|
592
|
-
if (rect.width <= 0 || rect.height <= 0) return;
|
|
593
|
-
const ux = (cx - rect.left) / rect.width;
|
|
594
|
-
const uy = 1 - (cy - rect.top) / rect.height;
|
|
595
|
-
const start = performance.now();
|
|
596
|
-
const duration = 1500;
|
|
597
|
-
const maxRadius = 120;
|
|
598
|
-
const count = 8;
|
|
599
|
-
const step = () => {
|
|
600
|
-
const t = performance.now() - start;
|
|
601
|
-
if (t > duration) return;
|
|
602
|
-
const k = t / duration;
|
|
603
|
-
const radius = maxRadius * k * k;
|
|
604
|
-
const strength = 0.05 * (1 - k);
|
|
605
|
-
const spin = 0.4 * k;
|
|
606
|
-
for (let i = 0; i < count; i += 1) {
|
|
607
|
-
const angle = i / count * Math.PI * 2 + spin;
|
|
608
|
-
const px = ux + radius * Math.cos(angle) / rect.width;
|
|
609
|
-
const py = uy + radius * Math.sin(angle) / rect.height;
|
|
610
|
-
main.stir(px, py, Math.cos(angle) * strength, -Math.sin(angle) * strength);
|
|
611
|
-
}
|
|
612
|
-
const id2 = requestAnimationFrame(step);
|
|
613
|
-
ripples.add(id2);
|
|
614
|
-
};
|
|
615
|
-
const id = requestAnimationFrame(step);
|
|
616
|
-
ripples.add(id);
|
|
617
|
-
};
|
|
618
|
-
const onPointerOver = (event) => {
|
|
619
|
-
const button = event.target?.closest?.("button");
|
|
620
|
-
if (button !== void 0 && button !== null) stirButton(button, 0.04);
|
|
621
|
-
};
|
|
622
|
-
const onClick = (event) => {
|
|
623
|
-
const button = event.target?.closest?.("button");
|
|
624
|
-
if (button === void 0 || button === null) return;
|
|
625
|
-
const now = performance.now();
|
|
626
|
-
const previous = lastStir.get(button) ?? 0;
|
|
627
|
-
if (now - previous < 500) return;
|
|
628
|
-
lastStir.set(button, now);
|
|
629
|
-
const rect = button.getBoundingClientRect();
|
|
630
|
-
ripple(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
631
|
-
};
|
|
632
|
-
document.addEventListener("pointerover", onPointerOver, { capture: true });
|
|
633
|
-
document.addEventListener("click", onClick, { capture: true });
|
|
634
|
-
return () => {
|
|
635
|
-
for (const id of ripples) cancelAnimationFrame(id);
|
|
636
|
-
ripples.clear();
|
|
637
|
-
document.removeEventListener("pointerover", onPointerOver, { capture: true });
|
|
638
|
-
document.removeEventListener("click", onClick, { capture: true });
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// ── glass chromatic dispersion ───────────────────────────────────────
|
|
643
|
-
var FILTER_ID = "dshome-glass-dispersion";
|
|
644
|
-
var TINT_ID = "dshome-glass-dispersion-tint";
|
|
645
|
-
var REFRACT_ID = "dshome-glass-dispersion-refract";
|
|
646
|
-
var TALL_FILTER_ID = "dshome-glass-dispersion-tall";
|
|
647
|
-
var TALL_TINT_ID = "dshome-glass-dispersion-tall-tint";
|
|
648
|
-
var TALL_REFRACT_ID = "dshome-glass-dispersion-tall-refract";
|
|
649
|
-
// Two composer filters: `narrow` (an 8px band) and `wide` (16px).
|
|
650
|
-
//
|
|
651
|
-
// The band is sized by what the user can actually SEE, which is the top and
|
|
652
|
-
// bottom edges: the card's top abuts the 任务 bar and its bottom abuts the
|
|
653
|
-
// status line, so displacing the backdrop across those hard edges shows,
|
|
654
|
-
// while left and right sit over a continuous background where the same
|
|
655
|
-
// displacement changes nothing. The user accepted that asymmetry ("允许左右
|
|
656
|
-
// 无反射"), so these maps are tuned for the VERTICAL band and the horizontal
|
|
657
|
-
// one is left wherever it lands.
|
|
658
|
-
//
|
|
659
|
-
// Vertical band on screen = (edge + softness) / mapHeight * 1.56 * cardHeight
|
|
660
|
-
// -- the filter region is 156% of the surface's height (see filterMarkup) --
|
|
661
|
-
// so at the upstream 400x92 aspect and a 112px-tall card that is 1.90x the
|
|
662
|
-
// map inset: 1.7 + 2.5 gives 8px, 3.4 + 5 gives 16px.
|
|
663
|
-
var COMPOSER_FILTER_ID = "dshome-glass-dispersion-composer";
|
|
664
|
-
var COMPOSER_TINT_ID = "dshome-glass-dispersion-composer-tint";
|
|
665
|
-
var COMPOSER_REFRACT_ID = "dshome-glass-dispersion-composer-refract";
|
|
666
|
-
var WIDE_FILTER_ID = "dshome-glass-dispersion-composer-wide";
|
|
667
|
-
var WIDE_TINT_ID = "dshome-glass-dispersion-composer-wide-tint";
|
|
668
|
-
var WIDE_REFRACT_ID = "dshome-glass-dispersion-composer-wide-refract";
|
|
669
|
-
var COMPOSER_MAP_W = 400;
|
|
670
|
-
var COMPOSER_MAP_H = 92;
|
|
671
|
-
var COMPOSER_MAP_RADIUS = 50;
|
|
672
|
-
var COMPOSER_MAP_EDGE = 1.7;
|
|
673
|
-
var COMPOSER_MAP_SOFTNESS = 2.5;
|
|
674
|
-
var COMPOSER_WIDE_EDGE = 3.4;
|
|
675
|
-
var COMPOSER_WIDE_SOFTNESS = 5;
|
|
676
|
-
// Chromatic aberration, composer filters only. 0.18 splits the base
|
|
677
|
-
// displacement into three: red at 118%, blue at 82%, green in the middle,
|
|
678
|
-
// which is what makes a lens edge fringe instead of merely tinting. Kept to
|
|
679
|
-
// the two composer filters because it triples the displacement passes, and
|
|
680
|
-
// because the shared filters have to keep upstream's exact shape for the
|
|
681
|
-
// frosted recipe.
|
|
682
|
-
var COMPOSER_ABERRATION = 0.18;
|
|
683
|
-
var DEFAULT_REF_SCALE = 60;
|
|
684
|
-
// The liquid recipe drives the same feDisplacementMap harder: at 60 the
|
|
685
|
-
// bend is there but only just legible once the blur sits on top of it, and
|
|
686
|
-
// "you can see the edge refract" is the whole point of that recipe. Kept as
|
|
687
|
-
// a separate constant so the frosted recipe keeps receiving 60.
|
|
688
|
-
var LIQUID_REF_SCALE = 104;
|
|
689
|
-
var EDGE_DX = -14;
|
|
690
|
-
var TINT_OPACITY = 0.45;
|
|
691
|
-
var TINT_SAT = 0.85;
|
|
692
|
-
var TINT_LIGHT = 0.6;
|
|
693
|
-
var DEFAULT_TINT_HUE = 44;
|
|
694
|
-
// Upstream derives the inset from the map's SHORT side (7% of it, halved),
|
|
695
|
-
// which is only uniform if the map is square. The composer passes `edge`
|
|
696
|
-
// instead: one absolute inset for both axes, which comes out uniform as
|
|
697
|
-
// long as the map's aspect matches the filter region's (see COMPOSER_MAP_*).
|
|
698
|
-
// Defaults reproduce upstream's numbers exactly, so the frosted recipe's
|
|
699
|
-
// map stays byte for byte what it was.
|
|
700
|
-
function buildDisplacementMapDataUrl(width = 400, height = 92, radius = 50, edge = null, softness = 11) {
|
|
701
|
-
const w = Math.max(240, Math.round(width));
|
|
702
|
-
const h = Math.max(48, Math.round(height));
|
|
703
|
-
const r = Math.max(12, Math.round(radius));
|
|
704
|
-
const inset = edge === null ? Math.min(w, h) * 0.035 : Math.max(0, edge);
|
|
705
|
-
const innerW = Math.max(1, w - inset * 2);
|
|
706
|
-
const innerH = Math.max(1, h - inset * 2);
|
|
707
|
-
const svg2 = `<svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="glass-x" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#000"/><stop offset="100%" stop-color="#f00"/></linearGradient><linearGradient id="glass-y" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" stop-color="#000"/><stop offset="100%" stop-color="#00f"/></linearGradient></defs><rect x="0" y="0" width="${w}" height="${h}" fill="#000"/><rect x="0" y="0" width="${w}" height="${h}" rx="${r}" fill="url(#glass-x)"/><rect x="0" y="0" width="${w}" height="${h}" rx="${r}" fill="url(#glass-y)" style="mix-blend-mode:screen"/><rect x="${inset.toFixed(2)}" y="${inset.toFixed(2)}" width="${innerW.toFixed(2)}" height="${innerH.toFixed(2)}" rx="${r}" fill="hsl(0 0% 50% / 1)" style="filter:blur(${softness})"/></svg>`;
|
|
708
|
-
return `data:image/svg+xml,${encodeURIComponent(svg2)}`;
|
|
709
|
-
}
|
|
710
|
-
// Inlined from upstream `src/client/glass-dispersion.ts` (line 2409) —
|
|
711
|
-
// required by tintMatrix below.
|
|
712
|
-
function hslToRgb01(h, s, l) {
|
|
713
|
-
const hue = (h % 360 + 360) % 360 / 360;
|
|
714
|
-
if (s === 0) return [l, l, l];
|
|
715
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
716
|
-
const p = 2 * l - q;
|
|
717
|
-
const channel = (t) => {
|
|
718
|
-
let tt = t;
|
|
719
|
-
if (tt < 0) tt += 1;
|
|
720
|
-
if (tt > 1) tt -= 1;
|
|
721
|
-
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
|
722
|
-
if (tt < 1 / 2) return q;
|
|
723
|
-
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
|
724
|
-
return p;
|
|
725
|
-
};
|
|
726
|
-
return [channel(hue + 1 / 3), channel(hue), channel(hue - 1 / 3)];
|
|
727
|
-
}
|
|
728
|
-
function tintMatrix(hue, opacity) {
|
|
729
|
-
const [r, g, b] = hslToRgb01(hue, TINT_SAT, TINT_LIGHT);
|
|
730
|
-
const lr = (0.2126 * opacity).toFixed(4);
|
|
731
|
-
const lg = (0.7152 * opacity).toFixed(4);
|
|
732
|
-
const lb = (0.0722 * opacity).toFixed(4);
|
|
733
|
-
return `0 0 0 0 ${r.toFixed(4)} 0 0 0 0 ${g.toFixed(4)} 0 0 0 0 ${b.toFixed(4)} ${lr} ${lg} ${lb} 0 0`;
|
|
734
|
-
}
|
|
735
|
-
function supportsSvgBackdropFilter() {
|
|
736
|
-
try {
|
|
737
|
-
const ua = navigator.userAgent || "";
|
|
738
|
-
if (/Safari/.test(ua) && !/Chrome/.test(ua) || /Firefox/.test(ua)) return false;
|
|
739
|
-
const div = document.createElement("div");
|
|
740
|
-
div.style.backdropFilter = `url(#${FILTER_ID})`;
|
|
741
|
-
return div.style.backdropFilter !== "";
|
|
742
|
-
} catch {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
// Inlined from upstream `src/client/glass-dispersion.ts` (line 2443) —
|
|
747
|
-
// builds the wide/tall SVG <filter> pair startGlassDispersion injects.
|
|
748
|
-
//
|
|
749
|
-
// `aberration` (0 = off, upstream's shape byte for byte) adds true
|
|
750
|
-
// chromatic aberration: the backdrop is displaced TWICE MORE at slightly
|
|
751
|
-
// different scales, and the result is assembled channel by channel -- R
|
|
752
|
-
// from the stronger displacement, B from the weaker, G from the base. That
|
|
753
|
-
// is what puts a red/blue fringe on a real lens edge; the tinted
|
|
754
|
-
// difference-blend below is only a colour wash and does not separate
|
|
755
|
-
// channels at all.
|
|
756
|
-
//
|
|
757
|
-
// The two extra maps carry a data-aberration factor so setRefraction() can
|
|
758
|
-
// rescale them in step with the base one instead of flattening all three
|
|
759
|
-
// to the same value.
|
|
760
|
-
function filterMarkup(id, refractId, tintId, map, aberration = 0) {
|
|
761
|
-
const chromatic = aberration <= 0 ? '' :
|
|
762
|
-
`<feDisplacementMap data-aberration="${(1 + aberration).toFixed(3)}" in="SourceGraphic" in2="map" scale="${DEFAULT_REF_SCALE}" xChannelSelector="R" yChannelSelector="B" result="refractR"></feDisplacementMap>` +
|
|
763
|
-
`<feDisplacementMap data-aberration="${(1 - aberration).toFixed(3)}" in="SourceGraphic" in2="map" scale="${DEFAULT_REF_SCALE}" xChannelSelector="R" yChannelSelector="B" result="refractB"></feDisplacementMap>` +
|
|
764
|
-
`<feColorMatrix in="refracted" type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="onlyG"></feColorMatrix>` +
|
|
765
|
-
`<feColorMatrix in="refractR" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="onlyR"></feColorMatrix>` +
|
|
766
|
-
`<feColorMatrix in="refractB" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="onlyB"></feColorMatrix>` +
|
|
767
|
-
`<feBlend in="onlyG" in2="onlyR" mode="screen" result="rgMerge"></feBlend>` +
|
|
768
|
-
`<feBlend in="rgMerge" in2="onlyB" mode="screen" result="aberrated"></feBlend>`;
|
|
769
|
-
const merged = chromatic === '' ? 'refracted' : 'aberrated';
|
|
770
|
-
return `<filter id="${id}" color-interpolation-filters="sRGB" x="-12%" y="-28%" width="124%" height="156%"><feImage href="${map}" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map"></feImage><feDisplacementMap id="${refractId}" in="SourceGraphic" in2="map" scale="${DEFAULT_REF_SCALE}" xChannelSelector="R" yChannelSelector="B" result="refracted"></feDisplacementMap>${chromatic}<feOffset in="${merged}" dx="${EDGE_DX}" dy="0" result="shifted"></feOffset><feBlend in="shifted" in2="${merged}" mode="difference" result="edgeDiff"></feBlend><feColorMatrix id="${tintId}" in="edgeDiff" type="matrix" values="${tintMatrix(DEFAULT_TINT_HUE, TINT_OPACITY)}" result="edgeTint"></feColorMatrix><feBlend in="${merged}" in2="edgeTint" mode="screen" result="output"></feBlend><feGaussianBlur in="output" stdDeviation="0.5"></feGaussianBlur></filter>`;
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Mount the two SVG backdrop filters and set `ATTR` on <html> so the
|
|
774
|
-
* stylesheet can opt surfaces into the dispersion. Returns a handle whose
|
|
775
|
-
* `setTint(hue)` / `setRefraction(scale)` accept values from the caller
|
|
776
|
-
* (upstream read them from its own settings store).
|
|
777
|
-
* @param {{ hue?: number, refraction?: number }} [options]
|
|
778
|
-
* @returns {{ setTint: (hue: number) => void, setRefraction: (scale: number) => void, dispose: () => void }}
|
|
779
|
-
*/
|
|
780
|
-
function startGlassDispersion(options) {
|
|
781
|
-
const hue = options !== undefined && typeof options.hue === "number" ? options.hue : DEFAULT_TINT_HUE;
|
|
782
|
-
const root = document.documentElement;
|
|
783
|
-
if (root.hasAttribute(ATTR) || document.getElementById(FILTER_ID) !== null) {
|
|
784
|
-
return { setTint() {
|
|
785
|
-
}, setRefraction() {
|
|
786
|
-
}, dispose() {
|
|
787
|
-
} };
|
|
788
|
-
}
|
|
789
|
-
if (!supportsSvgBackdropFilter()) {
|
|
790
|
-
return { setTint() {
|
|
791
|
-
}, setRefraction() {
|
|
792
|
-
}, dispose() {
|
|
793
|
-
} };
|
|
794
|
-
}
|
|
795
|
-
const mapWide = buildDisplacementMapDataUrl(400, 92, 50);
|
|
796
|
-
const mapTall = buildDisplacementMapDataUrl(92, 400, 50);
|
|
797
|
-
const mapComposer = buildDisplacementMapDataUrl(COMPOSER_MAP_W, COMPOSER_MAP_H, COMPOSER_MAP_RADIUS, COMPOSER_MAP_EDGE, COMPOSER_MAP_SOFTNESS);
|
|
798
|
-
const mapComposerWide = buildDisplacementMapDataUrl(COMPOSER_MAP_W, COMPOSER_MAP_H, COMPOSER_MAP_RADIUS, COMPOSER_WIDE_EDGE, COMPOSER_WIDE_SOFTNESS);
|
|
799
|
-
const markup = `<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" width="0" height="0" style="position:absolute;overflow:hidden"><defs>` + filterMarkup(FILTER_ID, REFRACT_ID, TINT_ID, mapWide) + filterMarkup(TALL_FILTER_ID, TALL_REFRACT_ID, TALL_TINT_ID, mapTall) + filterMarkup(COMPOSER_FILTER_ID, COMPOSER_REFRACT_ID, COMPOSER_TINT_ID, mapComposer, COMPOSER_ABERRATION) + filterMarkup(WIDE_FILTER_ID, WIDE_REFRACT_ID, WIDE_TINT_ID, mapComposerWide, COMPOSER_ABERRATION) + `</defs></svg>`;
|
|
800
|
-
const container = document.createElement("div");
|
|
801
|
-
container.setAttribute("aria-hidden", "true");
|
|
802
|
-
container.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;";
|
|
803
|
-
container.innerHTML = markup;
|
|
804
|
-
document.body.appendChild(container);
|
|
805
|
-
const tint = container.querySelectorAll("feColorMatrix");
|
|
806
|
-
const refract = container.querySelectorAll("feDisplacementMap");
|
|
807
|
-
root.setAttribute(ATTR, "");
|
|
808
|
-
const handle = {
|
|
809
|
-
setTint(nextHue) {
|
|
810
|
-
const values = tintMatrix(nextHue, TINT_OPACITY);
|
|
811
|
-
tint.forEach((node) => node.setAttribute("values", values));
|
|
812
|
-
},
|
|
813
|
-
setRefraction(scale) {
|
|
814
|
-
// The aberration maps are the same displacement at a slightly
|
|
815
|
-
// different scale, so they carry a factor and are rescaled in step;
|
|
816
|
-
// flattening them all to one value would erase the fringing.
|
|
817
|
-
const base = Math.max(0, Math.min(140, Number(scale) || 0));
|
|
818
|
-
refract.forEach((node) => {
|
|
819
|
-
const factor = Number(node.getAttribute("data-aberration"));
|
|
820
|
-
const value = Number.isFinite(factor) && factor > 0 ? base * factor : base;
|
|
821
|
-
node.setAttribute("scale", String(Math.max(0, Math.min(140, value))));
|
|
822
|
-
});
|
|
823
|
-
},
|
|
824
|
-
dispose() {
|
|
825
|
-
root.removeAttribute(ATTR);
|
|
826
|
-
container.remove();
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
if (options !== undefined && typeof options.refraction === "number") handle.setRefraction(options.refraction);
|
|
830
|
-
handle.setTint(hue);
|
|
831
|
-
return handle;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// ── specular-highlight cursor parallax ───────────────────────────────
|
|
835
|
-
// SPOT_ATTR / SPEC_X / SPEC_Y/ SPOT_SELECTOR renamed to the `dshome-`
|
|
836
|
-
// prefix. The caller decides which elements carry `data-dshome-spot`;
|
|
837
|
-
// declare them as `data-dshome-spot` in the stylesheet/DOM layer.
|
|
838
|
-
var SPOT_ATTR = "data-dshome-spot";
|
|
839
|
-
var SPOT_SELECTOR = `[${SPOT_ATTR}]`;
|
|
840
|
-
var SPEC_X = "--dshome-spec-x";
|
|
841
|
-
var SPEC_Y = "--dshome-spec-y";
|
|
842
|
-
// Inlined from upstream `src/client/spot-core.ts` (lines 1711-1713).
|
|
843
|
-
function closestSpot(target) {
|
|
844
|
-
return target instanceof Element ? target.closest(SPOT_SELECTOR) : null;
|
|
845
|
-
}
|
|
846
|
-
// Inlined from upstream `src/client/spot-core.ts` (lines 1717-1729). The
|
|
847
|
-
// `[data-composer-card]` / `[data-dsh-stats]` selectors are product markup
|
|
848
|
-
// and are deliberately left unrenamed; a mount that lacks them simply
|
|
849
|
-
// falls back to the spot's own border box.
|
|
850
|
-
function visualRect(spot) {
|
|
851
|
-
if (spot.querySelector("[data-composer-card]") !== null) {
|
|
852
|
-
const card = spot.querySelector("[data-composer-card]");
|
|
853
|
-
const r0 = card.getBoundingClientRect();
|
|
854
|
-
const stats = spot.querySelector("[data-dsh-stats]");
|
|
855
|
-
if (stats === null) return r0;
|
|
856
|
-
const r1 = stats.getBoundingClientRect();
|
|
857
|
-
const left = Math.min(r0.left, r1.left);
|
|
858
|
-
const top = Math.min(r0.top, r1.top);
|
|
859
|
-
return new DOMRect(left, top, Math.max(r0.right, r1.right) - left, Math.max(r0.bottom, r1.bottom) - top);
|
|
860
|
-
}
|
|
861
|
-
return spot.getBoundingClientRect();
|
|
862
|
-
}
|
|
863
|
-
// Inlined from upstream `src/client/spot-core.ts` (lines 1730-1732).
|
|
864
|
-
function inside(visual, clientX, clientY) {
|
|
865
|
-
return clientX >= visual.left && clientX <= visual.right && clientY >= visual.top && clientY <= visual.bottom;
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Write normalised cursor coordinates into `--dshome-spec-x` /
|
|
869
|
-
* `--dshome-spec-y` on the hovered `[data-dshome-spot]` element.
|
|
870
|
-
* @returns {() => void} disposer that detaches every listener and clears vars.
|
|
871
|
-
*/
|
|
872
|
-
function startSpecularParallax() {
|
|
873
|
-
let current = null;
|
|
874
|
-
let visual = null;
|
|
875
|
-
let raf = 0;
|
|
876
|
-
const clear = () => {
|
|
877
|
-
if (current === null) return;
|
|
878
|
-
current.style.removeProperty(SPEC_X);
|
|
879
|
-
current.style.removeProperty(SPEC_Y);
|
|
880
|
-
current = null;
|
|
881
|
-
visual = null;
|
|
882
|
-
};
|
|
883
|
-
const paint = (clientX, clientY) => {
|
|
884
|
-
if (raf !== 0) return;
|
|
885
|
-
raf = requestAnimationFrame(() => {
|
|
886
|
-
raf = 0;
|
|
887
|
-
if (current === null || visual === null) return;
|
|
888
|
-
if (!inside(visual, clientX, clientY)) {
|
|
889
|
-
clear();
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
const sx = Math.min(1, Math.max(-1, (clientX - visual.left) / visual.width * 2 - 1));
|
|
893
|
-
const sy = Math.min(1, Math.max(-1, (clientY - visual.top) / visual.height * 2 - 1));
|
|
894
|
-
current.style.setProperty(SPEC_X, sx.toFixed(4));
|
|
895
|
-
current.style.setProperty(SPEC_Y, sy.toFixed(4));
|
|
896
|
-
});
|
|
897
|
-
};
|
|
898
|
-
const onMove = (event) => {
|
|
899
|
-
const spot = closestSpot(event.target);
|
|
900
|
-
if (spot === null || spot !== current) return;
|
|
901
|
-
paint(event.clientX, event.clientY);
|
|
902
|
-
};
|
|
903
|
-
const onOver = (event) => {
|
|
904
|
-
const spot = closestSpot(event.target);
|
|
905
|
-
if (spot === null) return;
|
|
906
|
-
const rect = visualRect(spot);
|
|
907
|
-
if (!inside(rect, event.clientX, event.clientY)) return;
|
|
908
|
-
current = spot;
|
|
909
|
-
visual = rect;
|
|
910
|
-
paint(event.clientX, event.clientY);
|
|
911
|
-
};
|
|
912
|
-
const onOut = (event) => {
|
|
913
|
-
const spot = closestSpot(event.target);
|
|
914
|
-
if (spot === null || spot !== current) return;
|
|
915
|
-
if (visual !== null && inside(visual, event.clientX, event.clientY)) return;
|
|
916
|
-
clear();
|
|
917
|
-
};
|
|
918
|
-
document.addEventListener("pointermove", onMove, { passive: true });
|
|
919
|
-
document.addEventListener("pointerover", onOver, { passive: true });
|
|
920
|
-
document.addEventListener("pointerout", onOut, { passive: true });
|
|
921
|
-
return () => {
|
|
922
|
-
document.removeEventListener("pointermove", onMove);
|
|
923
|
-
document.removeEventListener("pointerover", onOver);
|
|
924
|
-
document.removeEventListener("pointerout", onOut);
|
|
925
|
-
if (raf !== 0) cancelAnimationFrame(raf);
|
|
926
|
-
clear();
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
931
|
-
// PUBLIC API exposed by this fragment (all declarations are inert until called;
|
|
932
|
-
// nothing here runs at load time):
|
|
933
|
-
//
|
|
934
|
-
// SITE_FLUID_PARAMS : object
|
|
935
|
-
// Upstream's default fluid parameter set (mouse radius/strength, decay,
|
|
936
|
-
// distortion, swirl, scale, offsets and three palette colours). Spread it
|
|
937
|
-
// and override `color1`/`color2`/`color3` (and any knob) to theme the
|
|
938
|
-
// fluid: `{ ...SITE_FLUID_PARAMS, color1: '#8AA3D6' }`.
|
|
939
|
-
//
|
|
940
|
-
// attachFluidShader(canvas, params) -> handle
|
|
941
|
-
// canvas: HTMLCanvasElement in the DOM and sized by CSS. params: a full
|
|
942
|
-
// parameter object (see SITE_FLUID_PARAMS). Returns:
|
|
943
|
-
// setParams(next) -> void swap params; repaints once if paused
|
|
944
|
-
// stir(x, y, vx, vy) -> void inject a velocity impulse at uv (0..1)
|
|
945
|
-
// setAudioLow(level) -> void 0..1 audio boost for speed/boost knobs
|
|
946
|
-
// setRunning(on) -> void start/stop the rAF loop
|
|
947
|
-
// dispose() -> void cancel rAF + drop the mousemove listener
|
|
948
|
-
// Degrades to no-op methods when WebGL2 is unavailable or a shader fails to
|
|
949
|
-
// compile/link. Never throws for that reason.
|
|
950
|
-
//
|
|
951
|
-
// attachFluidInteractions({ main, mainCanvas }) -> dispose
|
|
952
|
-
// Wires document-level pointerover/click capture so hovering a <button>
|
|
953
|
-
// nudges the fluid and clicking one emits a ripple. `main` is the handle
|
|
954
|
-
// from attachFluidShader; `mainCanvas` is the same canvas element passed
|
|
955
|
-
// there. Returns a () => void that cancels in-flight ripples and detaches
|
|
956
|
-
// both listeners.
|
|
957
|
-
//
|
|
958
|
-
// startGlassDispersion(options?) -> { setTint(hue), setRefraction(scale), dispose() }
|
|
959
|
-
// options: { hue?: number, refraction?: number } — both optional.
|
|
960
|
-
// `hue` (default 44) tints the dispersion edge; `refraction` (default 60,
|
|
961
|
-
// clamped 0..140) is the displacement scale. Injects one hidden <svg> with
|
|
962
|
-
// a wide and a tall filter and sets `data-dshome-dispersion` on <html> so
|
|
963
|
-
// CSS can apply `backdrop-filter: url(#dshome-glass-dispersion)`. Returns
|
|
964
|
-
// no-op methods (idempotent, safe to call twice) when the document already
|
|
965
|
-
// has one or the engine does not support SVG backdrop filters.
|
|
966
|
-
//
|
|
967
|
-
// startSpecularParallax() -> dispose
|
|
968
|
-
// Tracks the cursor over any element carrying `data-dshome-spot` and
|
|
969
|
-
// writes normalised -1..1 coordinates to `--dshome-spec-x` /
|
|
970
|
-
// `--dshome-spec-y` on that element each animation frame. Requires the
|
|
971
|
-
// caller to mark spot elements with `data-dshome-spot` (SPOT_ATTR) and to
|
|
972
|
-
// consume the two custom properties in CSS. Returns a () => void that
|
|
973
|
-
// detaches all three document listeners, cancels the pending frame and
|
|
974
|
-
// clears both properties.
|
|
975
|
-
//
|
|
976
|
-
// hexToRgb(value) -> [r, g, b] (0..1) — internal helper, exported for reuse.
|
|
977
|
-
// buildDisplacementMapDataUrl(w?, h?, r?) -> string — SVG data URL.
|
|
978
|
-
// tintMatrix(hue, opacity) -> string — feColorMatrix values string.
|
|
979
|
-
// supportsSvgBackdropFilter() -> boolean — engine capability probe.
|
|
980
|
-
// FILTER_ID / TALL_FILTER_ID / ATTR / SPOT_ATTR / SPEC_X / SPEC_Y — the
|
|
981
|
-
// ids and attribute/var names the shipped CSS must match.
|
|
982
|
-
//
|
|
983
|
-
// Typical mount:
|
|
984
|
-
// const fluid = attachFluidShader(canvas, { ...SITE_FLUID_PARAMS, color1: c1 });
|
|
985
|
-
// const stopInteractions = attachFluidInteractions({ main: fluid, mainCanvas: canvas });
|
|
986
|
-
// const dispersion = startGlassDispersion({ hue: 44, refraction: 60 });
|
|
987
|
-
// const stopParallax = startSpecularParallax();
|
|
988
|
-
// Typical dispose: stopParallax(); dispersion.dispose(); stopInteractions(); fluid.dispose();
|
|
989
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
990
|
-
|
|
991
29
|
// CORE_CSS: ours only (`:root` tokens, `html/body`, keyframes, stable
|
|
992
30
|
// `[role=menu]`, `.dshome-*`). No product class names — always injected.
|
|
993
31
|
// Color model: `data-dshome-color` ∈ {green(base), red(鲜红 peak), blue}
|
|
@@ -995,20 +33,6 @@ void main() {
|
|
|
995
33
|
const CORE_CSS = `
|
|
996
34
|
:root{--ds-brand:#059669;--ds-brand-deep:#047857;--ds-brand-mid:#10B981;--ds-brand-light:#34D399;--ds-glass-blur:24px}
|
|
997
35
|
html,body{background-color:transparent!important}
|
|
998
|
-
/* THE LABEL TOKENS ARE NOT PINNED HERE, AND MUST NOT BE AGAIN.
|
|
999
|
-
A previous attempt at the invisible-text bug declared
|
|
1000
|
-
body{--dsw-alias-label-primary:...!important} under our own scheme marker,
|
|
1001
|
-
so that background and text could never end up on opposite sides. It did fix
|
|
1002
|
-
that, and it broke something worse: those !important declarations outrank the
|
|
1003
|
-
PRODUCT's tokens, so when the user switched appearance the product changed its
|
|
1004
|
-
own tokens and this stylesheet kept painting the old ones. The UI looked like
|
|
1005
|
-
the switch was dead.
|
|
1006
|
-
Measured on the real page: with the theme's style tags removed the switch
|
|
1007
|
-
works, with them present it does not, and reverting the header z-index changes
|
|
1008
|
-
nothing -- i.e. it is this sheet, and it is these declarations.
|
|
1009
|
-
The invisible-text hazard stays open on purpose. A stuck appearance switch is
|
|
1010
|
-
a broken control; near-white text on a near-white page at least still reads
|
|
1011
|
-
once the marker agrees. Do not re-add these two rules. */
|
|
1012
36
|
body{background:radial-gradient(1200px 700px at 12% -10%,color-mix(in srgb,var(--ds-brand) 14%,transparent),transparent 60%),radial-gradient(1000px 600px at 105% 12%,color-mix(in srgb,var(--ds-brand-light) 16%,transparent),transparent 55%),radial-gradient(900px 700px at 50% 115%,color-mix(in srgb,var(--ds-brand) 10%,transparent),transparent 60%),linear-gradient(180deg,#fbfbfd 0%,#f5f6fa 100%);background-attachment:fixed}
|
|
1013
37
|
body[data-dshome-color=red]{--ds-brand:#F5222D;--ds-brand-deep:#CF1322;--ds-brand-mid:#FF4D4F;--ds-brand-light:#FF7875}
|
|
1014
38
|
body[data-dshome-color=blue]{--ds-brand:#4D6BFE;--ds-brand-deep:#3A65C2;--ds-brand-mid:#4176E6;--ds-brand-light:#73A3D2}
|
|
@@ -1180,12 +204,7 @@ body[data-dshome-dark] .YDXeBa_sessionRow.YDXeBa_selected{background:linear-grad
|
|
|
1180
204
|
.wSkVaW_scrollBody{padding-top:56px}
|
|
1181
205
|
.wSkVaW_viewArea{padding:0 18px}
|
|
1182
206
|
.wSkVaW_viewArea{animation:dshome-view-enter .45s ease}
|
|
1183
|
-
|
|
1184
|
-
40px-wide full-height strips at z-index 8, while this bar sat at 5 — so the
|
|
1185
|
-
cursor anywhere inside the title bar's own box turned into col-resize and the
|
|
1186
|
-
bar's controls were unreachable. 9 puts the bar above the grips; the grips
|
|
1187
|
-
stay grabbable everywhere the bar is not (above y=8, below y=46). */
|
|
1188
|
-
.wSkVaW_header{position:absolute;top:8px;left:24px;right:24px;z-index:9;min-height:36px;padding:4px 12px;display:flex;flex-direction:row;align-items:center;gap:8px;justify-content:flex-start;border:0!important;border-radius:0;background:transparent!important;transition:border-radius .3s ease}
|
|
207
|
+
.wSkVaW_header{position:absolute;top:8px;left:24px;right:24px;z-index:5;min-height:36px;padding:4px 12px;display:flex;flex-direction:row;align-items:center;gap:8px;justify-content:flex-start;border:0!important;border-radius:0;background:transparent!important;transition:border-radius .3s ease}
|
|
1189
208
|
.wSkVaW_header::before{content:"";position:absolute;inset:0;z-index:-1;background:transparent;border:1px solid transparent;border-radius:inherit;-webkit-backdrop-filter:none;backdrop-filter:none;transition:background .3s ease,border-color .3s ease,box-shadow .3s ease}
|
|
1190
209
|
.wSkVaW_header:hover{border-radius:999px}
|
|
1191
210
|
.wSkVaW_titleRow{display:contents}
|
|
@@ -1292,101 +311,6 @@ body[data-dshome-dark] .VOzbGW_panel{background:rgba(13,30,60,.96)!important}
|
|
|
1292
311
|
` }
|
|
1293
312
|
];
|
|
1294
313
|
|
|
1295
|
-
// LIQUID GLASS — the "wet glass" recipe, imitated from
|
|
1296
|
-
// deepseek-harness-background (MIT, Copyright (c) 2026 HaoyueQin): a
|
|
1297
|
-
// translucent fill, a vertical sheen gradient that is brightest along the
|
|
1298
|
-
// top edge and gone by ~38%, one shared backdrop chain
|
|
1299
|
-
// (blur + saturate + brightness + contrast), an inset top highlight and a
|
|
1300
|
-
// hairline ring, with hover only brightening the fill. Every knob is a
|
|
1301
|
-
// --dshome-glass-* property so the theme stays one recipe.
|
|
1302
|
-
//
|
|
1303
|
-
// The specular highlight is composed as an EXTRA background layer rather
|
|
1304
|
-
// than a pseudo-element: `--dshome-spec-x/y` (centre-relative, -1..1) are
|
|
1305
|
-
// written by startSpecular() on the hovered surface and
|
|
1306
|
-
// `--dshome-glass-spec` fades it, so a surface that already spends its
|
|
1307
|
-
// ::after on backdrop blur still gets a moving highlight.
|
|
1308
|
-
//
|
|
1309
|
-
// Refraction is a deliberately narrower layer: `backdrop-filter: url(#…)`
|
|
1310
|
-
// with the feDisplacementMap filter from dsh-theme-mineradio bends the
|
|
1311
|
-
// backdrop at the edges of the few LARGE surfaces only. Chromium is the
|
|
1312
|
-
// only engine that supports an SVG filter inside backdrop-filter and it
|
|
1313
|
-
// costs a filter pass per element, so the rule is gated on the
|
|
1314
|
-
// `data-dshome-dispersion` attribute that startGlassDispersion() stamps on
|
|
1315
|
-
// <html> only where it really mounted — every other browser silently keeps
|
|
1316
|
-
// the plain chain.
|
|
1317
|
-
const GLASS_CSS = `
|
|
1318
|
-
:root{
|
|
1319
|
-
--dshome-glass-blur:16px;
|
|
1320
|
-
--dshome-glass-saturate:1.42;
|
|
1321
|
-
--dshome-glass-brightness:1;
|
|
1322
|
-
--dshome-glass-contrast:1.01;
|
|
1323
|
-
--dshome-glass-sheen:.14;
|
|
1324
|
-
--dshome-glass-sheen-mid:.05;
|
|
1325
|
-
--dshome-glass-edge:rgba(255,255,255,.62);
|
|
1326
|
-
--dshome-glass-edge-low:rgba(255,255,255,.14);
|
|
1327
|
-
--dshome-glass-ring:rgba(255,255,255,.34);
|
|
1328
|
-
--dshome-glass-shadow:0 12px 40px rgba(0,0,0,.12);
|
|
1329
|
-
--dshome-glass-spec:0;
|
|
1330
|
-
}
|
|
1331
|
-
body[data-dshome-dark]{
|
|
1332
|
-
--dshome-glass-sheen:.16;
|
|
1333
|
-
--dshome-glass-sheen-mid:.06;
|
|
1334
|
-
--dshome-glass-edge:rgba(255,255,255,.30);
|
|
1335
|
-
--dshome-glass-edge-low:rgba(255,255,255,.07);
|
|
1336
|
-
--dshome-glass-ring:rgba(255,255,255,.18);
|
|
1337
|
-
--dshome-glass-shadow:0 14px 44px rgba(0,0,0,.45);
|
|
1338
|
-
}
|
|
1339
|
-
html .hHd-Xa_root::before,
|
|
1340
|
-
html .uV2eYG_card::after,
|
|
1341
|
-
html .gdEzaW_bubble,
|
|
1342
|
-
html .fV0t5q_root,
|
|
1343
|
-
html [data-composer-card],
|
|
1344
|
-
html .VOzbGW_panel,
|
|
1345
|
-
html .dshome-sa,
|
|
1346
|
-
html .dshome-file-menu{
|
|
1347
|
-
background-image:
|
|
1348
|
-
radial-gradient(340px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .30)), transparent 62%),
|
|
1349
|
-
linear-gradient(180deg, rgba(255,255,255,var(--dshome-glass-sheen)), rgba(255,255,255,var(--dshome-glass-sheen-mid)) 38%, rgba(255,255,255,.01))!important;
|
|
1350
|
-
-webkit-backdrop-filter:blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate)) brightness(var(--dshome-glass-brightness)) contrast(var(--dshome-glass-contrast))!important;
|
|
1351
|
-
backdrop-filter:blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate)) brightness(var(--dshome-glass-brightness)) contrast(var(--dshome-glass-contrast))!important;
|
|
1352
|
-
box-shadow:inset 0 1px 0 var(--dshome-glass-edge),inset 0 -1px 0 var(--dshome-glass-edge-low),inset 0 0 0 .5px var(--dshome-glass-ring),var(--dshome-glass-shadow);
|
|
1353
|
-
transition:background-color .18s ease,box-shadow .18s ease;
|
|
1354
|
-
}
|
|
1355
|
-
/* The specular layer is multiplied by --dshome-glass-spec, so it has to be
|
|
1356
|
-
switched on: while the cursor is over a spot surface the highlight follows
|
|
1357
|
-
--dshome-spec-x/y (written per element by startSpecularParallax). */
|
|
1358
|
-
html [data-dshome-spot]:hover{--dshome-glass-spec:1;}
|
|
1359
|
-
html [data-composer-card]:hover{--dshome-glass-spec:1;--dshome-glass-sheen:.12;}
|
|
1360
|
-
html .gdEzaW_bubble:hover,html .dshome-sa:hover,html .dshome-file-menu:hover{
|
|
1361
|
-
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .16))!important;
|
|
1362
|
-
}
|
|
1363
|
-
body[data-dshome-dark] .gdEzaW_bubble:hover,
|
|
1364
|
-
body[data-dshome-dark] .dshome-sa:hover,
|
|
1365
|
-
body[data-dshome-dark] .dshome-file-menu:hover{
|
|
1366
|
-
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .06))!important;
|
|
1367
|
-
}
|
|
1368
|
-
html[data-dshome-dispersion] .hHd-Xa_root::before,
|
|
1369
|
-
html[data-dshome-dispersion] .gdEzaW_bubble,
|
|
1370
|
-
html[data-dshome-dispersion] .VOzbGW_panel{
|
|
1371
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1372
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1373
|
-
}
|
|
1374
|
-
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1375
|
-
html .hHd-Xa_root::before,html .uV2eYG_card::after,html .gdEzaW_bubble,html [data-composer-card],html .VOzbGW_panel,html .dshome-sa,html .dshome-file-menu{
|
|
1376
|
-
background-color:rgba(255,255,255,.86)!important;
|
|
1377
|
-
}
|
|
1378
|
-
body[data-dshome-dark] .hHd-Xa_root::before,
|
|
1379
|
-
body[data-dshome-dark] .uV2eYG_card::after,
|
|
1380
|
-
body[data-dshome-dark] .gdEzaW_bubble,
|
|
1381
|
-
body[data-dshome-dark] [data-composer-card],
|
|
1382
|
-
body[data-dshome-dark] .VOzbGW_panel,
|
|
1383
|
-
body[data-dshome-dark] .dshome-sa,
|
|
1384
|
-
body[data-dshome-dark] .dshome-file-menu{
|
|
1385
|
-
background-color:rgba(13,30,60,.92)!important;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
`;
|
|
1389
|
-
|
|
1390
314
|
/** Append a style tag; returns a disposer that removes it. */
|
|
1391
315
|
function injectStyle(css, label) {
|
|
1392
316
|
const tag = document.createElement('style');
|
|
@@ -1397,287 +321,6 @@ html[data-dshome-dispersion] .VOzbGW_panel{
|
|
|
1397
321
|
return () => tag.remove();
|
|
1398
322
|
}
|
|
1399
323
|
|
|
1400
|
-
// LIQUID GLASS — the second recipe, selected by the durable `glassStyle`
|
|
1401
|
-
// setting and gated entirely on `html[data-dshome-glass="liquid"]`. The
|
|
1402
|
-
// frosted block above is deliberately left byte-identical: switching the
|
|
1403
|
-
// setting back has to reproduce the look the user approved, so every rule
|
|
1404
|
-
// in here is additive and nothing above is edited.
|
|
1405
|
-
//
|
|
1406
|
-
// What makes it "liquid" rather than "frosted":
|
|
1407
|
-
// * the fill drops from a .42 white wash to a barely-there .10, and the
|
|
1408
|
-
// top sheen from .14 to .07, so the pane reads as glass over the fluid
|
|
1409
|
-
// rather than as a white card;
|
|
1410
|
-
// * the backdrop chain leans on saturation (1.75) instead of brightness,
|
|
1411
|
-
// which is what makes the colours behind the pane bloom;
|
|
1412
|
-
// * the shared feDisplacementMap runs at a much larger scale (see
|
|
1413
|
-
// LIQUID_REF_SCALE below), so the edge refraction is actually visible;
|
|
1414
|
-
// * a conic-gradient highlight sweeps the perimeter on its own, driven by
|
|
1415
|
-
// a registered <angle> property rather than by the pointer.
|
|
1416
|
-
//
|
|
1417
|
-
// Surfaces: exactly the four the user named — the sidebar shell, the
|
|
1418
|
-
// composer, the title bar and the user's own messages. Refraction is one
|
|
1419
|
-
// SVG filter pass per element in Chromium, so nothing else is added.
|
|
1420
|
-
//
|
|
1421
|
-
// The sweep lives on ::after because the product has already claimed
|
|
1422
|
-
// ::before on the sidebar and the title bar (measured, not assumed); the
|
|
1423
|
-
// composer claims both, which is why its edge highlight stays the
|
|
1424
|
-
// brand-tinted conic border the patch block already animates.
|
|
1425
|
-
// A user-authored message is not one flow kind but two: the message that
|
|
1426
|
-
// opens a turn is `data-chat-flow-kind="user"`, and a note typed while the
|
|
1427
|
-
// agent is already working is `"steering"`. Measured on the current build,
|
|
1428
|
-
// a transcript can contain ONLY steering bubbles — anchoring the selector
|
|
1429
|
-
// on "user" alone silently skipped every bubble on the page.
|
|
1430
|
-
//
|
|
1431
|
-
// The second half covers the gap the user hit: a message typed while the
|
|
1432
|
-
// agent is still working is painted in the transcript BEFORE it is picked
|
|
1433
|
-
// up, and at that moment it sits outside any flow-kind container, so the
|
|
1434
|
-
// two anchored selectors miss it and it renders unglazed until the turn
|
|
1435
|
-
// consumes it. Rather than enumerate the container it lands in, this side
|
|
1436
|
-
// takes any bubble that is NOT part of an assistant step — which is the
|
|
1437
|
-
// only place a bubble must not be glassed.
|
|
1438
|
-
const LIQUID_BUBBLE = ':is([data-chat-flow-kind="user"],[data-chat-flow-kind="steering"]) [class*="bubble" i],[class*="bubble" i]:not([data-chat-flow-kind="assistant-step"] *)';
|
|
1439
|
-
const LIQUID_CSS = `
|
|
1440
|
-
@property --dshome-liquid-sweep{syntax:"<angle>";inherits:false;initial-value:0deg}
|
|
1441
|
-
@keyframes dshome-liquid-sweep{from{--dshome-liquid-sweep:0deg}to{--dshome-liquid-sweep:360deg}}
|
|
1442
|
-
html[data-dshome-glass="liquid"]{
|
|
1443
|
-
--dshome-liquid-blur:11px;
|
|
1444
|
-
--dshome-liquid-saturate:1.75;
|
|
1445
|
-
--dshome-liquid-brightness:1.05;
|
|
1446
|
-
--dshome-liquid-contrast:1.02;
|
|
1447
|
-
--dshome-liquid-fill:rgb(255 255 255 / .14);
|
|
1448
|
-
/* The sidebar is the largest pane on screen, so a fill that reads as "barely
|
|
1449
|
-
there" on the composer leaves it washed out against the fluid; it carries a
|
|
1450
|
-
little more body of its own, and more of it again in light mode where the
|
|
1451
|
-
pane has to read as white glass rather than as tinted fluid. */
|
|
1452
|
-
--dshome-liquid-fill-sidebar:rgb(255 255 255 / .30);
|
|
1453
|
-
/* The sidebar is the boundary with the conversation pane, and it blurs harder
|
|
1454
|
-
than everything else on purpose: at the shared 11px the flow behind it stays
|
|
1455
|
-
legible and the pane never separates from the transcript. */
|
|
1456
|
-
--dshome-liquid-blur-sidebar:24px;
|
|
1457
|
-
/* The composer blurs less than the rest on purpose. Filters in a backdrop
|
|
1458
|
-
chain apply left to right, so url(refract) followed by blur(N) blurs the
|
|
1459
|
-
refraction AFTER it happens: a band narrower than about the blur radius is
|
|
1460
|
-
simply erased. At 11px the purpose-built ~4px band was invisible, which is
|
|
1461
|
-
exactly what was reported. 8px lets a ~10px band read while still frosting
|
|
1462
|
-
the backdrop. */
|
|
1463
|
-
--dshome-liquid-blur-composer:8px;
|
|
1464
|
-
/* One extra veil on top of the sidebar's glass. The pane sits over the fluid
|
|
1465
|
-
at roughly the same tone as the conversation it borders, so without this
|
|
1466
|
-
lift the two read as one surface. It is white in light mode. */
|
|
1467
|
-
--dshome-liquid-veil-sidebar:rgb(255 255 255 / .18);
|
|
1468
|
-
--dshome-liquid-sheen:.07;
|
|
1469
|
-
--dshome-liquid-edge:rgba(255,255,255,.60);
|
|
1470
|
-
--dshome-liquid-edge-low:rgba(255,255,255,.10);
|
|
1471
|
-
--dshome-liquid-ring:rgba(255,255,255,.26);
|
|
1472
|
-
--dshome-liquid-sweep-hi:rgba(255,255,255,.85);
|
|
1473
|
-
--dshome-liquid-sweep-lo:rgba(255,255,255,.28);
|
|
1474
|
-
--dshome-liquid-shadow:0 18px 54px rgba(9,20,44,.16);
|
|
1475
|
-
}
|
|
1476
|
-
body[data-dshome-dark]{
|
|
1477
|
-
--dshome-liquid-fill:rgb(255 255 255 / .07);
|
|
1478
|
-
/* Night mode goes the other way: the pane is a dark slab over a dark flow, so
|
|
1479
|
-
it is tinted towards the shell colour instead of towards white — dark
|
|
1480
|
-
enough to read as black glass, light enough to keep the flow visible. */
|
|
1481
|
-
--dshome-liquid-fill-sidebar:rgb(6 14 32 / .32);
|
|
1482
|
-
/* Night mode's veil is BLACK, the same idea inverted: a dark pane over a dark
|
|
1483
|
-
flow is lifted off it by deepening the pane, not by washing it grey. */
|
|
1484
|
-
--dshome-liquid-veil-sidebar:rgb(0 0 0 / .22);
|
|
1485
|
-
--dshome-liquid-sheen:.05;
|
|
1486
|
-
--dshome-liquid-edge:rgba(255,255,255,.34);
|
|
1487
|
-
--dshome-liquid-edge-low:rgba(255,255,255,.05);
|
|
1488
|
-
--dshome-liquid-ring:rgba(255,255,255,.14);
|
|
1489
|
-
--dshome-liquid-sweep-hi:rgba(255,255,255,.55);
|
|
1490
|
-
--dshome-liquid-sweep-lo:rgba(255,255,255,.16);
|
|
1491
|
-
--dshome-liquid-shadow:0 20px 60px rgba(0,0,0,.5);
|
|
1492
|
-
}
|
|
1493
|
-
/* The title bar is deliberately NOT always-on: the user's note on it is
|
|
1494
|
-
"rounded corners, and only once the mouse is over it" — the header becomes a
|
|
1495
|
-
pill on :hover (the patch block already sets border-radius:999px, and
|
|
1496
|
-
::before inherits it), so the glass is scoped to that same hover state
|
|
1497
|
-
instead of turning the strip into a permanent bar. */
|
|
1498
|
-
/* The selectors carry an extra body step on purpose. The patch blocks are
|
|
1499
|
-
injected lazily when their anchor appears — later than this sheet — and their
|
|
1500
|
-
dark variants (body[data-dshome-dark] .uV2eYG_card) tie with a
|
|
1501
|
-
html[attr] .surface selector on specificity, at which point source order
|
|
1502
|
-
hands the pane back to the old fill. The extra type selector breaks the tie. */
|
|
1503
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before,
|
|
1504
|
-
html[data-dshome-glass="liquid"] body [data-composer-card],
|
|
1505
|
-
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1506
|
-
html[data-dshome-glass="liquid"] body ${LIQUID_BUBBLE}{
|
|
1507
|
-
background-color:var(--dshome-liquid-fill)!important;
|
|
1508
|
-
background-image:
|
|
1509
|
-
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
1510
|
-
linear-gradient(180deg, rgba(255,255,255,var(--dshome-liquid-sheen)), rgba(255,255,255,.015) 34%, rgba(255,255,255,0))!important;
|
|
1511
|
-
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1512
|
-
backdrop-filter:blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1513
|
-
box-shadow:inset 0 1px 0 var(--dshome-liquid-edge),inset 0 -1px 0 var(--dshome-liquid-edge-low),inset 0 0 0 .5px var(--dshome-liquid-ring),var(--dshome-liquid-shadow)!important;
|
|
1514
|
-
}
|
|
1515
|
-
/* The composer is the one surface the old recipe paints TWICE: the patch block
|
|
1516
|
-
blurs .uV2eYG_card::after (and the frosted list paints it too), and the card
|
|
1517
|
-
itself is in both lists, so the backdrop was blurred twice over — which is what
|
|
1518
|
-
turned the pane into a smear. Under liquid the card element IS the glass, so
|
|
1519
|
-
the redundant layer is switched off rather than re-tuned. */
|
|
1520
|
-
html[data-dshome-glass="liquid"] [data-composer-card]::after{
|
|
1521
|
-
-webkit-backdrop-filter:none!important;
|
|
1522
|
-
backdrop-filter:none!important;
|
|
1523
|
-
background-image:none!important;
|
|
1524
|
-
box-shadow:none!important;
|
|
1525
|
-
}
|
|
1526
|
-
/* The frosted recipe gives the composer a brand-tinted conic ring, painted by
|
|
1527
|
-
the patch block at inset:-1px and spun by dsh-border-spin. Under liquid the
|
|
1528
|
-
user asked for it to go: it sits one pixel OUTSIDE the card's own 1.5px white
|
|
1529
|
-
border, so the pair reads as a coloured line with a white line outside it, and
|
|
1530
|
-
two competing edges is what liquid glass is not. content:none removes the
|
|
1531
|
-
pseudo-element outright rather than painting it transparent. */
|
|
1532
|
-
html[data-dshome-glass="liquid"] body [data-composer-card]::before{
|
|
1533
|
-
content:none!important;
|
|
1534
|
-
background:none!important;
|
|
1535
|
-
animation:none!important;
|
|
1536
|
-
}
|
|
1537
|
-
/* With the ring gone the card's own 1.5px white border is the only edge left,
|
|
1538
|
-
and on its own it reads as a flat white outline rather than as glass — which
|
|
1539
|
-
is what the user saw next. Liquid's edge is the inset hairline plus the
|
|
1540
|
-
refraction, so the border goes transparent. The WIDTH stays: removing it
|
|
1541
|
-
would resize the box and shift every control inside by 3px. */
|
|
1542
|
-
html[data-dshome-glass="liquid"] body [data-composer-card]{
|
|
1543
|
-
border-color:transparent!important;
|
|
1544
|
-
}
|
|
1545
|
-
/* The sidebar pane carries its own fill and its own shape: rounded only on the
|
|
1546
|
-
edge that faces the conversation, so it reads as a panel clipped to the
|
|
1547
|
-
window rather than as a full-bleed column. The hairline ring in the shared
|
|
1548
|
-
box-shadow above follows the radius. */
|
|
1549
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1550
|
-
background-color:var(--dshome-liquid-fill-sidebar)!important;
|
|
1551
|
-
border-radius:0 22px 22px 0;
|
|
1552
|
-
/* The veil rides on top of the two layers the shared rule sets, so the
|
|
1553
|
-
sidebar keeps its cursor-follow highlight and its sheen underneath. */
|
|
1554
|
-
background-image:
|
|
1555
|
-
linear-gradient(var(--dshome-liquid-veil-sidebar),var(--dshome-liquid-veil-sidebar)),
|
|
1556
|
-
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
1557
|
-
linear-gradient(180deg, rgba(255,255,255,var(--dshome-liquid-sheen)), rgba(255,255,255,.015) 34%, rgba(255,255,255,0))!important;
|
|
1558
|
-
}
|
|
1559
|
-
/* The message bubble is the one surface that is not already positioned. */
|
|
1560
|
-
html[data-dshome-glass="liquid"] ${LIQUID_BUBBLE}{
|
|
1561
|
-
position:relative;
|
|
1562
|
-
transition:background-color .18s ease,box-shadow .18s ease;
|
|
1563
|
-
}
|
|
1564
|
-
/* The composer leans under the cursor (see startComposerTilt). The transition
|
|
1565
|
-
is what turns the release into a settle rather than a snap: the ported
|
|
1566
|
-
routine writes the neutral transform back and only drops the inline
|
|
1567
|
-
properties 240ms later. */
|
|
1568
|
-
html[data-dshome-glass="liquid"] [data-composer-card]{
|
|
1569
|
-
transition:transform .24s cubic-bezier(.2,.8,.2,1),background-color .18s ease,box-shadow .18s ease!important;
|
|
1570
|
-
will-change:transform;
|
|
1571
|
-
}
|
|
1572
|
-
html[data-dshome-glass="liquid"] ${LIQUID_BUBBLE}:hover{
|
|
1573
|
-
background-color:rgb(255 255 255 / .18)!important;
|
|
1574
|
-
}
|
|
1575
|
-
body[data-dshome-dark] ${LIQUID_BUBBLE}:hover{
|
|
1576
|
-
background-color:rgb(255 255 255 / .10)!important;
|
|
1577
|
-
}
|
|
1578
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::after{
|
|
1579
|
-
border-radius:0 22px 22px 0;
|
|
1580
|
-
}
|
|
1581
|
-
/* The travelling edge highlight. mask-composite:exclude turns the filled
|
|
1582
|
-
conic gradient into a 1px perimeter band; padding:1px on the pseudo-element
|
|
1583
|
-
sets that band's width without touching the product's box model.
|
|
1584
|
-
It is drawn on the SIDEBAR ONLY. The user asked for it off the composer and
|
|
1585
|
-
off the message bubbles under liquid: on a small pill the band reads as a
|
|
1586
|
-
bright white outline rather than as light travelling a glass edge, and on the
|
|
1587
|
-
composer it doubled up with the card's own border. The bubbles and the
|
|
1588
|
-
composer keep liquid's other three marks — the translucent fill, the sheen
|
|
1589
|
-
and the refraction. */
|
|
1590
|
-
html[data-dshome-glass="liquid"] .hHd-Xa_root::after{
|
|
1591
|
-
content:"";position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:inherit;
|
|
1592
|
-
padding:1px;
|
|
1593
|
-
background:conic-gradient(from var(--dshome-liquid-sweep),
|
|
1594
|
-
var(--dshome-liquid-sweep-lo) 0deg,var(--dshome-liquid-sweep-hi) 22deg,var(--dshome-liquid-sweep-lo) 64deg,
|
|
1595
|
-
rgba(255,255,255,.05) 140deg,var(--dshome-liquid-sweep-hi) 190deg,rgba(255,255,255,.05) 248deg,
|
|
1596
|
-
var(--dshome-liquid-sweep-lo) 360deg);
|
|
1597
|
-
-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
|
|
1598
|
-
-webkit-mask-composite:xor;
|
|
1599
|
-
mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
|
|
1600
|
-
mask-composite:exclude;
|
|
1601
|
-
animation:dshome-liquid-sweep 6.5s linear infinite;
|
|
1602
|
-
}
|
|
1603
|
-
/* The composer's edge refraction. Three states, selected by the durable
|
|
1604
|
-
composerRefraction setting (see html[data-dshome-composer]):
|
|
1605
|
-
narrow (default) -- an 8px band top and bottom
|
|
1606
|
-
wide -- a 16px band top and bottom
|
|
1607
|
-
off -- no dispersion at all
|
|
1608
|
-
Left and right are left as they fall: the band is there, but over a
|
|
1609
|
-
continuous background a horizontal displacement has nothing to show, which
|
|
1610
|
-
the user accepted. */
|
|
1611
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1612
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body ${LIQUID_BUBBLE}{
|
|
1613
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1614
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1615
|
-
}
|
|
1616
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="narrow"] body [data-composer-card]{
|
|
1617
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion-composer) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1618
|
-
backdrop-filter:url(#dshome-glass-dispersion-composer) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1619
|
-
}
|
|
1620
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="wide"] body [data-composer-card]{
|
|
1621
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion-composer-wide) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1622
|
-
backdrop-filter:url(#dshome-glass-dispersion-composer-wide) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1623
|
-
}
|
|
1624
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="off"] body [data-composer-card]{
|
|
1625
|
-
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1626
|
-
backdrop-filter:blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1627
|
-
}
|
|
1628
|
-
/* The workspace list is faded out at its bottom by a gradient whose end stop is
|
|
1629
|
-
the sidebar-fill token — an OPAQUE colour matched to the product's own sidebar
|
|
1630
|
-
background. Over a translucent glass pane that stop no longer matches what is
|
|
1631
|
-
actually behind it, so the fade shows up as a lighter band above 设置 (the
|
|
1632
|
-
user's red box). The pane itself already separates the list from the
|
|
1633
|
-
conversation, so the fade is dropped. Scoped to the sidebar and to the
|
|
1634
|
-
product's own <hash>_fade naming so nothing else is touched. */
|
|
1635
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root [class*="_fade" i]{
|
|
1636
|
-
background-image:none!important;
|
|
1637
|
-
}
|
|
1638
|
-
/* The sidebar's own, heavier blur. Written after the two rules above because it
|
|
1639
|
-
ties with the dispersion one on specificity and so has to come later. */
|
|
1640
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1641
|
-
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1642
|
-
backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1643
|
-
}
|
|
1644
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1645
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1646
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1647
|
-
}
|
|
1648
|
-
/* Backdrop blur, switched off by the durable backdropBlur setting. Each glass
|
|
1649
|
-
recipe reads its radius from a variable, so turning the frosting off is one
|
|
1650
|
-
0px per variable -- the translucency, the sheen and the edge refraction all
|
|
1651
|
-
stay exactly as they were. Written with a body step so it also out-ranks the
|
|
1652
|
-
html[data-dshome-glass] block the liquid radii are declared in. */
|
|
1653
|
-
html[data-dshome-blur="off"],
|
|
1654
|
-
html[data-dshome-blur="off"] body{
|
|
1655
|
-
--dshome-glass-blur:0px;
|
|
1656
|
-
--dshome-liquid-blur:0px;
|
|
1657
|
-
--dshome-liquid-blur-sidebar:0px;
|
|
1658
|
-
--dshome-liquid-blur-composer:0px;
|
|
1659
|
-
}
|
|
1660
|
-
@media (prefers-reduced-motion:reduce){
|
|
1661
|
-
html[data-dshome-glass="liquid"] .hHd-Xa_root::after{
|
|
1662
|
-
animation:none;
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1666
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before,
|
|
1667
|
-
html[data-dshome-glass="liquid"] body [data-composer-card],
|
|
1668
|
-
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1669
|
-
html[data-dshome-glass="liquid"] body ${LIQUID_BUBBLE}{
|
|
1670
|
-
background-color:rgba(255,255,255,.86)!important;
|
|
1671
|
-
}
|
|
1672
|
-
body[data-dshome-dark] .hHd-Xa_root::before,
|
|
1673
|
-
body[data-dshome-dark] [data-composer-card],
|
|
1674
|
-
body[data-dshome-dark] .wSkVaW_header:hover::before,
|
|
1675
|
-
body[data-dshome-dark] ${LIQUID_BUBBLE}{
|
|
1676
|
-
background-color:rgba(13,30,60,.92)!important;
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
`;
|
|
1680
|
-
|
|
1681
324
|
/**
|
|
1682
325
|
* Inject `css` only while `anchor` matches the DOM. If the anchor is
|
|
1683
326
|
* absent at call time, wait up to 30s for it (covers shells that render
|
|
@@ -1728,32 +371,8 @@ html[data-dshome-blur="off"] body{
|
|
|
1728
371
|
* subscription is unavailable.
|
|
1729
372
|
*/
|
|
1730
373
|
function createDarkSync(ctx, theme) {
|
|
1731
|
-
/** Last value we wrote to the body; null until the first apply(). */
|
|
1732
|
-
let lastDark = null;
|
|
1733
|
-
/**
|
|
1734
|
-
* Scheme-change listeners. The dark marker is a marker only if someone
|
|
1735
|
-
* reads it: the stylesheet matches on it, and the ambient background reads
|
|
1736
|
-
* it for its palette (see startAmbient). A listener that throws must not
|
|
1737
|
-
* cost the others their update, so each call is boxed.
|
|
1738
|
-
*/
|
|
1739
|
-
const darkListeners = new Set();
|
|
1740
|
-
const notifyDark = (dark) => {
|
|
1741
|
-
for (const fn of darkListeners) {
|
|
1742
|
-
try { fn(dark); } catch (error) {}
|
|
1743
|
-
}
|
|
1744
|
-
};
|
|
1745
374
|
const apply = () => {
|
|
1746
375
|
let dark = false;
|
|
1747
|
-
// Two signals, and BOTH of them lie in some state -- measured, not
|
|
1748
|
-
// assumed:
|
|
1749
|
-
// * the theme service reported `light` while the page was in dark
|
|
1750
|
-
// mode, so treating it as the only source painted a dark page light;
|
|
1751
|
-
// * `data-ds-dark-theme` was still on <body> after a switch back to
|
|
1752
|
-
// light, so treating IT as authoritative painted a light page dark.
|
|
1753
|
-
// This is the precedence that shipped through 1.43.1 and behaved in both
|
|
1754
|
-
// modes: ask the service first, and let the product's own attribute
|
|
1755
|
-
// override a "not dark" answer. Keep it, and sanity-check the signal
|
|
1756
|
-
// before touching it again.
|
|
1757
376
|
if (theme !== undefined && theme !== null && typeof theme.getTheme === 'function') {
|
|
1758
377
|
try {
|
|
1759
378
|
const snap = theme.getTheme();
|
|
@@ -1765,440 +384,28 @@ html[data-dshome-blur="off"] body{
|
|
|
1765
384
|
}
|
|
1766
385
|
}
|
|
1767
386
|
if (!dark) dark = document.body.hasAttribute('data-ds-dark-theme');
|
|
1768
|
-
// Remember what we actually put on the body, so a later apply() that
|
|
1769
|
-
// lands on the same answer does not wake the background up for
|
|
1770
|
-
// nothing. `lastDark` is null until the first write.
|
|
1771
|
-
const changed = lastDark !== dark;
|
|
1772
|
-
lastDark = dark;
|
|
1773
387
|
if (dark) document.body.setAttribute('data-dshome-dark', '');
|
|
1774
388
|
else document.body.removeAttribute('data-dshome-dark');
|
|
1775
|
-
// Publish the write. The marker is what EVERY dark rule and the fluid
|
|
1776
|
-
// palette read, so a light/dark switch that only set the attribute left
|
|
1777
|
-
// the background on the scheme it was mounted with -- reported as
|
|
1778
|
-
// "切到深色背景还是蓝白".
|
|
1779
|
-
if (changed) notifyDark(dark);
|
|
1780
389
|
};
|
|
390
|
+
apply();
|
|
1781
391
|
let unsubscribe = null;
|
|
1782
392
|
try {
|
|
1783
393
|
unsubscribe = ctx.on('theme/change', apply);
|
|
1784
394
|
} catch (error) {
|
|
1785
395
|
unsubscribe = null;
|
|
1786
396
|
}
|
|
1787
|
-
// Always mirror the product's attribute, not only when the theme service
|
|
1788
|
-
// is missing: this is the self-healing half. A missed `theme/change`
|
|
1789
|
-
// used to leave the marker stale until the next reload.
|
|
1790
397
|
let mirror = null;
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
return {
|
|
1798
|
-
/**
|
|
1799
|
-
* Subscribe to light/dark *writes*. Nothing is replayed on subscribe:
|
|
1800
|
-
* a reader that needs the current value reads the attribute (or calls
|
|
1801
|
-
* dshomeDark()), which keeps one source of truth.
|
|
1802
|
-
*/
|
|
1803
|
-
subscribe: (fn) => {
|
|
1804
|
-
darkListeners.add(fn);
|
|
1805
|
-
return () => darkListeners.delete(fn);
|
|
1806
|
-
},
|
|
1807
|
-
dispose: () => {
|
|
1808
|
-
darkListeners.clear();
|
|
1809
|
-
if (unsubscribe !== null) unsubscribe();
|
|
1810
|
-
if (mirror !== null) mirror.disconnect();
|
|
1811
|
-
}
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
/**
|
|
1816
|
-
* Own the glass-recipe marker: mirror the durable `glassStyle` choice onto
|
|
1817
|
-
* <html data-dshome-glass>, which is the single switch the stylesheet gates
|
|
1818
|
-
* the liquid recipe on. The attribute is set before the first paint with the
|
|
1819
|
-
* default (`liquid`), so the only flicker is the one a user who picked
|
|
1820
|
-
* `frosted` sees during the initial loopback round trip.
|
|
1821
|
-
*/
|
|
1822
|
-
function createGlassSync(ctx) {
|
|
1823
|
-
const root = document.documentElement;
|
|
1824
|
-
const apply = () => {
|
|
1825
|
-
root.setAttribute('data-dshome-glass', dsttGetGlass());
|
|
1826
|
-
root.setAttribute('data-dshome-composer', dsttGetComposer());
|
|
1827
|
-
root.setAttribute('data-dshome-blur', dsttGetBlur() ? 'on' : 'off');
|
|
1828
|
-
};
|
|
1829
|
-
apply();
|
|
1830
|
-
const unsubscribe = dsttSubscribe(apply);
|
|
1831
|
-
return () => {
|
|
1832
|
-
unsubscribe();
|
|
1833
|
-
root.removeAttribute('data-dshome-glass');
|
|
1834
|
-
root.removeAttribute('data-dshome-composer');
|
|
1835
|
-
root.removeAttribute('data-dshome-blur');
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
/**
|
|
1840
|
-
* Fluid palette per DSTT colour, built the way the upstream fluid builds its
|
|
1841
|
-
* own: three tones off ONE hue with high saturation and a lightness ramp
|
|
1842
|
-
* (dsh-theme-mineradio's `fluidToneColors`).
|
|
1843
|
-
*
|
|
1844
|
-
* This replaces hand-picked hex triples, and the measurement is why. On the
|
|
1845
|
-
* live page, with the colour token green, the canvas read 235,240,252 while
|
|
1846
|
-
* the CSS background behind it read 233,242,241: the light green set
|
|
1847
|
-
* (#4FBE92 / #FFFFFF / #D6F2E4) is three near-white tones, and three
|
|
1848
|
-
* near-white tones average into a wash in which the eye reads the blue
|
|
1849
|
-
* channel. Hiding the canvas turned the page green instantly -- the canvas
|
|
1850
|
-
* was painting the wash over it. Upstream's lead tone is hsl(hue, 1, .27..),
|
|
1851
|
-
* i.e. genuinely saturated, which is what makes a hue swap visible.
|
|
1852
|
-
*
|
|
1853
|
-
* Hues stay the site's own (FLUID_HUES below).
|
|
1854
|
-
*/
|
|
1855
|
-
const FLUID_DEPTH = { light: 22, dark: 26 };
|
|
1856
|
-
|
|
1857
|
-
/** hsl -> #rrggbb, same shape as the upstream helper. */
|
|
1858
|
-
function fluidHsl(h, s, l) {
|
|
1859
|
-
const hue = ((h % 360) + 360) % 360;
|
|
1860
|
-
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
1861
|
-
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));
|
|
1862
|
-
const m = l - c / 2;
|
|
1863
|
-
const seg = Math.floor(hue / 60) % 6;
|
|
1864
|
-
const rgb = seg === 0 ? [c, x, 0]
|
|
1865
|
-
: seg === 1 ? [x, c, 0]
|
|
1866
|
-
: seg === 2 ? [0, c, x]
|
|
1867
|
-
: seg === 3 ? [0, x, c]
|
|
1868
|
-
: seg === 4 ? [x, 0, c]
|
|
1869
|
-
: [c, 0, x];
|
|
1870
|
-
return '#' + rgb.map((v) => Math.round((v + m) * 255).toString(16).padStart(2, '0')).join('');
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
/**
|
|
1874
|
-
* The three fluid tones for one hue. `depth` is upstream's knob: 0 is the
|
|
1875
|
-
* deepest and most saturated, 100 the palest. The dark scheme walks the same
|
|
1876
|
-
* ramp so the flow still reads as a dark shell.
|
|
1877
|
-
*/
|
|
1878
|
-
function fluidToneColors(dark, hue, depth) {
|
|
1879
|
-
const d = Math.min(1, Math.max(0, depth / 100));
|
|
1880
|
-
const ramp = (deep, mid, pale) => (d < 0.5
|
|
1881
|
-
? deep + (mid - deep) * d / 0.5
|
|
1882
|
-
: mid + (pale - mid) * (d - 0.5) / 0.5);
|
|
1883
|
-
if (dark) {
|
|
1884
|
-
return [
|
|
1885
|
-
fluidHsl(hue, 0.85, ramp(0, 0.46, 0.62)),
|
|
1886
|
-
fluidHsl(hue, 0.9, ramp(0, 0.305, 0.45)),
|
|
1887
|
-
fluidHsl(hue, 0.5, ramp(0, 0.075, 0.1))
|
|
1888
|
-
];
|
|
1889
|
-
}
|
|
1890
|
-
return [
|
|
1891
|
-
fluidHsl(hue, 1, ramp(0.27, 0.45, 0.9)),
|
|
1892
|
-
fluidHsl(hue, 0.55, 0.86),
|
|
1893
|
-
fluidHsl(hue, 0.25, 0.955)
|
|
1894
|
-
];
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
/** Edge-tint hue for the glass dispersion, following the DSTT colour. */
|
|
1898
|
-
const FLUID_HUES = { green: 152, blue: 221, red: 356 };
|
|
1899
|
-
|
|
1900
|
-
/**
|
|
1901
|
-
* A cheap key over everything the palette derives from: the colour token and
|
|
1902
|
-
* the scheme marker, both on <body>.
|
|
1903
|
-
*
|
|
1904
|
-
* The ambient fluid compares this every rendered frame. Why polling instead
|
|
1905
|
-
* of trusting the notification chain: measured on the live page with the
|
|
1906
|
-
* instrumented build, the canvas was mounted once, the page WAS running the
|
|
1907
|
-
* patched bundle, and the fluid was demonstrably alive (both programs
|
|
1908
|
-
* compiled and linked, ping-pong FBOs complete, a draw pair per frame,
|
|
1909
|
-
* glError 0) -- yet u_color1..3 kept arriving as the light-blue triple for
|
|
1910
|
-
* the entire session while <body> said green. So the mount had resolved the
|
|
1911
|
-
* palette before the colour driver's first write, and nothing later re-read
|
|
1912
|
-
* the token. Two getAttribute calls per frame when nothing changed, and it
|
|
1913
|
-
* cannot miss a writer the way a listener can.
|
|
1914
|
-
*/
|
|
1915
|
-
const dsttMarkerKey = () => (document.body.getAttribute('data-dshome-color') || 'green')
|
|
1916
|
-
+ '|' + (document.body.hasAttribute('data-dshome-dark') ? 'dark' : 'light');
|
|
1917
|
-
|
|
1918
|
-
/** The active DSTT colour token. */
|
|
1919
|
-
function dshomeColor() {
|
|
1920
|
-
const value = document.body.getAttribute('data-dshome-color');
|
|
1921
|
-
return value === 'red' || value === 'blue' ? value : 'green';
|
|
1922
|
-
}
|
|
1923
|
-
function dshomeDark() {
|
|
1924
|
-
return document.body.hasAttribute('data-dshome-dark');
|
|
1925
|
-
}
|
|
1926
|
-
/** The whole fluid parameter set for the current DSTT colour and scheme. */
|
|
1927
|
-
function dshomeFluidParams() {
|
|
1928
|
-
const dark = dshomeDark();
|
|
1929
|
-
const pick = fluidToneColors(dark, FLUID_HUES[dshomeColor()], dark ? FLUID_DEPTH.dark : FLUID_DEPTH.light);
|
|
1930
|
-
// `mouseFeed` is ours, not upstream's: false keeps the cursor from
|
|
1931
|
-
// writing a wake into the flow field (see the DSTT `fluidBrush` setting).
|
|
1932
|
-
return { ...SITE_FLUID_PARAMS, color1: pick[0], color2: pick[1], color3: pick[2], mouseFeed: dsttBrush() };
|
|
1933
|
-
}
|
|
1934
|
-
/** The durable `fluidBrush` preference; off unless the host reports it on. */
|
|
1935
|
-
function dsttBrush() {
|
|
1936
|
-
try {
|
|
1937
|
-
return dsttState.fluidBrush === true;
|
|
1938
|
-
} catch (error) {
|
|
1939
|
-
return false;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
/** WebGL2 is probed on a throwaway canvas so the real one keeps its context. */
|
|
1943
|
-
function supportsWebgl2() {
|
|
1944
|
-
try {
|
|
1945
|
-
const probe = document.createElement('canvas');
|
|
1946
|
-
const gl = probe.getContext('webgl2');
|
|
1947
|
-
if (gl === null) return false;
|
|
1948
|
-
const lose = gl.getExtension('WEBGL_lose_context');
|
|
1949
|
-
if (lose !== null) lose.loseContext();
|
|
1950
|
-
return true;
|
|
1951
|
-
} catch (error) {
|
|
1952
|
-
return false;
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
/**
|
|
1956
|
-
* Mark glass surfaces as specular spots. The ported startSpecularParallax
|
|
1957
|
-
* only tracks elements carrying the spot attribute, and stamping it on
|
|
1958
|
-
* pointerover avoids writing attributes onto product elements that React
|
|
1959
|
-
* re-renders. Registered before the parallax listener so the stamp lands on
|
|
1960
|
-
* the same event the parallax handler then reads.
|
|
1961
|
-
*/
|
|
1962
|
-
/**
|
|
1963
|
-
* Specular spot surfaces: the composer card, the sidebar shell and the
|
|
1964
|
-
* user's own messages. The transcript pane and the centre column are
|
|
1965
|
-
* deliberately NOT spots — a cursor-following highlight across that much
|
|
1966
|
-
* empty area reads as a large white patch sliding over the background.
|
|
1967
|
-
*
|
|
1968
|
-
* The message bubble is matched through the product's own
|
|
1969
|
-
* `data-chat-flow-kind="user"` marker rather than a class name: the hashed
|
|
1970
|
-
* `.gdEzaW_bubble` this list used to carry matches nothing on the current
|
|
1971
|
-
* build, which is how the old entry went dead without anyone noticing.
|
|
1972
|
-
*/
|
|
1973
|
-
const SPOT_SURFACES = `${LIQUID_BUBBLE},.uV2eYG_card,.dshome-sa,.dshome-file-menu,.hHd-Xa_root,[data-composer-card]`;
|
|
1974
|
-
function startSpecularSpotter() {
|
|
1975
|
-
const onOver = (event) => {
|
|
1976
|
-
const target = event.target;
|
|
1977
|
-
if (target === null || typeof target.closest !== 'function') return;
|
|
1978
|
-
const surface = target.closest(SPOT_SURFACES);
|
|
1979
|
-
if (surface === null) return;
|
|
1980
|
-
if (surface.getAttribute(SPOT_ATTR) === null) surface.setAttribute(SPOT_ATTR, '');
|
|
1981
|
-
};
|
|
1982
|
-
document.addEventListener('pointerover', onOver, true);
|
|
1983
|
-
return () => document.removeEventListener('pointerover', onOver, true);
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
// ── composer hover tilt ───────────────────────────────────────────────
|
|
1987
|
-
// Ported from dsh-theme-mineradio's `startSpotlight` tilt branch (MIT,
|
|
1988
|
-
// Copyright (c) 2026 John Wu), including its constants: the pane lifts by
|
|
1989
|
-
// scale(1.01) and leans up to TILT_MAX radians towards whichever side the
|
|
1990
|
-
// cursor is on, about its own centre, under an 800px perspective. The
|
|
1991
|
-
// cursor-relative offsets are the same half-range clamp, and the release
|
|
1992
|
-
// writes the neutral transform first and only drops the inline properties
|
|
1993
|
-
// SETTLE_MS later, so the stylesheet's transition animates the return
|
|
1994
|
-
// instead of snapping.
|
|
1995
|
-
//
|
|
1996
|
-
// Only the composer is tiltable here. mineradio tilts every spot it owns;
|
|
1997
|
-
// in this theme the other spots are the sidebar shell and the transcript's
|
|
1998
|
-
// message bubbles, where a whole-column lean reads as a bug rather than as
|
|
1999
|
-
// depth. It is also gated on the liquid recipe, because the frosted pane is
|
|
2000
|
-
// meant to stay observably identical to what shipped before.
|
|
2001
|
-
const TILT_SELECTOR = '[data-composer-card],.uV2eYG_card';
|
|
2002
|
-
const TILT_MAX = 0.0175;
|
|
2003
|
-
const TILT_PERSPECTIVE = 800;
|
|
2004
|
-
const TILT_SCALE = 1.01;
|
|
2005
|
-
const TILT_SETTLE_MS = 240;
|
|
2006
|
-
function startComposerTilt() {
|
|
2007
|
-
let current = null;
|
|
2008
|
-
let raf = 0;
|
|
2009
|
-
const tilted = new WeakSet();
|
|
2010
|
-
const settle = new Map();
|
|
2011
|
-
const reduced = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
2012
|
-
if (reduced) return () => {};
|
|
2013
|
-
const release = (spot) => {
|
|
2014
|
-
const pending = settle.get(spot);
|
|
2015
|
-
if (pending !== undefined) {
|
|
2016
|
-
clearTimeout(pending);
|
|
2017
|
-
settle.delete(spot);
|
|
2018
|
-
}
|
|
2019
|
-
if (!tilted.has(spot)) return;
|
|
2020
|
-
tilted.delete(spot);
|
|
2021
|
-
spot.style.transform = 'perspective(' + TILT_PERSPECTIVE + 'px) rotateX(0rad) rotateY(0rad) scale(1)';
|
|
2022
|
-
const handle = window.setTimeout(() => {
|
|
2023
|
-
settle.delete(spot);
|
|
2024
|
-
spot.style.removeProperty('transform');
|
|
2025
|
-
spot.style.removeProperty('transform-origin');
|
|
2026
|
-
}, TILT_SETTLE_MS);
|
|
2027
|
-
settle.set(spot, handle);
|
|
2028
|
-
};
|
|
2029
|
-
const paint = (spot, rect, clientX, clientY) => {
|
|
2030
|
-
if (raf !== 0) return;
|
|
2031
|
-
raf = requestAnimationFrame(() => {
|
|
2032
|
-
raf = 0;
|
|
2033
|
-
if (current !== spot) return;
|
|
2034
|
-
if (dsttGetGlass() !== 'liquid') {
|
|
2035
|
-
release(spot);
|
|
2036
|
-
return;
|
|
2037
|
-
}
|
|
2038
|
-
if (clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom) return;
|
|
2039
|
-
const dx = Math.min(0.5, Math.max(-0.5, (clientX - rect.left) / rect.width - 0.5));
|
|
2040
|
-
const dy = Math.min(0.5, Math.max(-0.5, (clientY - rect.top) / rect.height - 0.5));
|
|
2041
|
-
spot.style.transformOrigin = (rect.width / 2) + 'px ' + (rect.height / 2) + 'px';
|
|
2042
|
-
spot.style.transform = 'perspective(' + TILT_PERSPECTIVE + 'px)'
|
|
2043
|
-
+ ' rotateX(' + (TILT_MAX * -2 * dy).toFixed(5) + 'rad)'
|
|
2044
|
-
+ ' rotateY(' + (TILT_MAX * 2 * dx).toFixed(5) + 'rad)'
|
|
2045
|
-
+ ' scale(' + TILT_SCALE + ')';
|
|
2046
|
-
tilted.add(spot);
|
|
2047
|
-
});
|
|
2048
|
-
};
|
|
2049
|
-
const onOver = (event) => {
|
|
2050
|
-
if (dsttGetGlass() !== 'liquid') return;
|
|
2051
|
-
const target = event.target;
|
|
2052
|
-
if (target === null || typeof target.closest !== 'function') return;
|
|
2053
|
-
const spot = target.closest(TILT_SELECTOR);
|
|
2054
|
-
if (spot === null) return;
|
|
2055
|
-
const rect = spot.getBoundingClientRect();
|
|
2056
|
-
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return;
|
|
2057
|
-
const pending = settle.get(spot);
|
|
2058
|
-
if (pending !== undefined) {
|
|
2059
|
-
clearTimeout(pending);
|
|
2060
|
-
settle.delete(spot);
|
|
2061
|
-
}
|
|
2062
|
-
current = spot;
|
|
2063
|
-
paint(spot, rect, event.clientX, event.clientY);
|
|
2064
|
-
};
|
|
2065
|
-
const onMove = (event) => {
|
|
2066
|
-
if (current === null) return;
|
|
2067
|
-
paint(current, current.getBoundingClientRect(), event.clientX, event.clientY);
|
|
2068
|
-
};
|
|
2069
|
-
const onOut = (event) => {
|
|
2070
|
-
if (current === null) return;
|
|
2071
|
-
const target = event.target;
|
|
2072
|
-
if (target !== null && typeof target.closest === 'function') {
|
|
2073
|
-
const spot = target.closest(TILT_SELECTOR);
|
|
2074
|
-
if (spot !== null && spot !== current) return;
|
|
2075
|
-
}
|
|
2076
|
-
const rect = current.getBoundingClientRect();
|
|
2077
|
-
if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) return;
|
|
2078
|
-
release(current);
|
|
2079
|
-
current = null;
|
|
2080
|
-
};
|
|
2081
|
-
document.addEventListener('pointerover', onOver, { passive: true });
|
|
2082
|
-
document.addEventListener('pointermove', onMove, { passive: true });
|
|
2083
|
-
document.addEventListener('pointerout', onOut, { passive: true });
|
|
2084
|
-
return () => {
|
|
2085
|
-
document.removeEventListener('pointerover', onOver);
|
|
2086
|
-
document.removeEventListener('pointermove', onMove);
|
|
2087
|
-
document.removeEventListener('pointerout', onOut);
|
|
2088
|
-
if (raf !== 0) cancelAnimationFrame(raf);
|
|
2089
|
-
for (const id of settle.values()) clearTimeout(id);
|
|
2090
|
-
settle.clear();
|
|
2091
|
-
for (const spot of document.querySelectorAll(TILT_SELECTOR)) {
|
|
2092
|
-
spot.style.removeProperty('transform');
|
|
2093
|
-
spot.style.removeProperty('transform-origin');
|
|
398
|
+
if (unsubscribe === null || theme === undefined || theme === null || typeof theme.getTheme !== 'function') {
|
|
399
|
+
try {
|
|
400
|
+
mirror = new MutationObserver(apply);
|
|
401
|
+
mirror.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] });
|
|
402
|
+
} catch (error) {
|
|
403
|
+
mirror = null;
|
|
2094
404
|
}
|
|
2095
|
-
current = null;
|
|
2096
|
-
};
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
/**
|
|
2100
|
-
* Ambient background layer: the DSTT flow field, a WebGL2 two-pass fluid
|
|
2101
|
-
* simulation ported from dsh-theme-mineradio, replacing the particle field.
|
|
2102
|
-
* The particles stay the fallback — {@link startParticles} remains the whole
|
|
2103
|
-
* implementation for engines without WebGL2 — so a missing WebGL2 downgrades
|
|
2104
|
-
* the background instead of blanking it.
|
|
2105
|
-
*
|
|
2106
|
-
* Every colour knob follows the DSTT colour token, so a mode switch re-tints
|
|
2107
|
-
* the flow and the glass edge tint without a remount.
|
|
2108
|
-
*/
|
|
2109
|
-
function startAmbient() {
|
|
2110
|
-
if (!supportsWebgl2()) return startParticles();
|
|
2111
|
-
// Which build is running, readable from the page. The fluid palette is
|
|
2112
|
-
// resolved at mount and re-derived from the DOM afterwards (syncFluid +
|
|
2113
|
-
// markerPoll below); when a report says the background did not follow the
|
|
2114
|
-
// colour token, this marker is the first thing to check in the console:
|
|
2115
|
-
// window.__dshomeBuild -> '1.43.7 palette-poll'
|
|
2116
|
-
// It is also what tools/dstt-dark-report.js prints as buildMarker.
|
|
2117
|
-
try { window.__dshomeBuild = '1.43.7 palette-poll'; } catch (error) {}
|
|
2118
|
-
const canvas = document.createElement('canvas');
|
|
2119
|
-
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2120
|
-
const style = canvas.style;
|
|
2121
|
-
style.position = 'fixed';
|
|
2122
|
-
style.top = '0';
|
|
2123
|
-
style.left = '0';
|
|
2124
|
-
style.width = '100%';
|
|
2125
|
-
style.height = '100%';
|
|
2126
|
-
style.zIndex = '-1';
|
|
2127
|
-
style.pointerEvents = 'none';
|
|
2128
|
-
document.body.appendChild(canvas);
|
|
2129
|
-
|
|
2130
|
-
const fluid = attachFluidShader(canvas, dshomeFluidParams());
|
|
2131
|
-
// The fluid is deliberately autonomous: no pointer feed (the `fluidBrush`
|
|
2132
|
-
// setting, off by default) and no button stir either. The ported
|
|
2133
|
-
// `attachFluidInteractions` is therefore NOT wired — it is kept in the
|
|
2134
|
-
// file, unmodified, because re-enabling button ripples is one call, but
|
|
2135
|
-
// nothing in this theme uses it today.
|
|
2136
|
-
const stopSpotter = startSpecularSpotter();
|
|
2137
|
-
const stopParallax = startSpecularParallax();
|
|
2138
|
-
const dispersion = startGlassDispersion({ hue: FLUID_HUES[dshomeColor()] });
|
|
2139
|
-
// Both recipes share one filter pair, so the recipe selects the scale:
|
|
2140
|
-
// 104 for liquid (visible edge refraction) and the untouched 60 for
|
|
2141
|
-
// frosted, which is what the pane has always used.
|
|
2142
|
-
const applyGlass = () => {
|
|
2143
|
-
dispersion.setRefraction(dsttGetGlass() === 'liquid' ? LIQUID_REF_SCALE : DEFAULT_REF_SCALE);
|
|
2144
|
-
};
|
|
2145
|
-
applyGlass();
|
|
2146
|
-
const unsubscribeGlass = dsttSubscribe(applyGlass);
|
|
2147
|
-
// ── palette reconciliation ────────────────────────────────────────────
|
|
2148
|
-
// One reader for BOTH inputs the palette depends on: the colour token on
|
|
2149
|
-
// <body> and the dark marker. Reported symptom: "切主题模式背景不变 /
|
|
2150
|
-
// 深色模式背景还是蓝白". The colour half of that was a real dead end --
|
|
2151
|
-
// `subscribeColor` only fires when the notified colour string CHANGES, so
|
|
2152
|
-
// the boot race (this canvas mounts, then the durable mode arrives over
|
|
2153
|
-
// the async bridge and re-resolves the colour) left the flow painted from
|
|
2154
|
-
// whatever the driver had guessed first, and a mode switch that resolved
|
|
2155
|
-
// to the same colour never notified at all. The scheme half had no
|
|
2156
|
-
// listener: a MutationObserver was watching the marker, but `darkSync`
|
|
2157
|
-
// pointedly does not set a *changed* value twice, and the marker itself
|
|
2158
|
-
// was the only thing written.
|
|
2159
|
-
// So: derive from the DOM on every signal instead of trusting the event to
|
|
2160
|
-
// carry it, and box every listener so one throw cannot cost the rest.
|
|
2161
|
-
let fluidParams = JSON.stringify(dshomeFluidParams());
|
|
2162
|
-
const syncFluid = () => {
|
|
2163
|
-
const next = dshomeFluidParams();
|
|
2164
|
-
const key = JSON.stringify(next);
|
|
2165
|
-
if (key === fluidParams) return;
|
|
2166
|
-
fluidParams = key;
|
|
2167
|
-
try { fluid.setParams(next); } catch (error) {}
|
|
2168
|
-
};
|
|
2169
|
-
const unsubscribeColor = subscribeColor((color) => {
|
|
2170
|
-
syncFluid();
|
|
2171
|
-
try { dispersion.setTint(FLUID_HUES[color] ?? FLUID_HUES.green); } catch (error) {}
|
|
2172
|
-
});
|
|
2173
|
-
const unsubscribeDark = darkSync === null ? null : darkSync.subscribe(syncFluid);
|
|
2174
|
-
// The colour driver writes the token as an attribute; watching it as well
|
|
2175
|
-
// keeps this correct no matter who wrote it (the peak-hour chain, a manual
|
|
2176
|
-
// mode pick, or a future caller that forgets to notify).
|
|
2177
|
-
let tokenMirror = null;
|
|
2178
|
-
try {
|
|
2179
|
-
tokenMirror = new MutationObserver(syncFluid);
|
|
2180
|
-
tokenMirror.observe(document.body, { attributes: true, attributeFilter: ['data-dshome-color'] });
|
|
2181
|
-
} catch (error) {
|
|
2182
|
-
tokenMirror = null;
|
|
2183
405
|
}
|
|
2184
|
-
// The backstop that actually fixed it. Events above are the fast path;
|
|
2185
|
-
// this poll is the guarantee. It is the same decision as
|
|
2186
|
-
// dsttMarkerKey()'s comment describes -- the palette is derived from the
|
|
2187
|
-
// DOM, so it is re-derived from the DOM, once every half second, for as
|
|
2188
|
-
// long as the canvas lives. syncFluid() itself is a no-op when the key is
|
|
2189
|
-
// unchanged, so the steady-state cost is two getAttribute calls.
|
|
2190
|
-
const markerPoll = setInterval(syncFluid, 500);
|
|
2191
406
|
return () => {
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (unsubscribeDark !== null) unsubscribeDark();
|
|
2195
|
-
unsubscribeGlass();
|
|
2196
|
-
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2197
|
-
dispersion.dispose();
|
|
2198
|
-
stopParallax();
|
|
2199
|
-
stopSpotter();
|
|
2200
|
-
fluid.dispose();
|
|
2201
|
-
canvas.remove();
|
|
407
|
+
if (unsubscribe !== null) unsubscribe();
|
|
408
|
+
if (mirror !== null) mirror.disconnect();
|
|
2202
409
|
};
|
|
2203
410
|
}
|
|
2204
411
|
|
|
@@ -2315,20 +522,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2315
522
|
const colors = palette();
|
|
2316
523
|
for (const p of parts) p.c = colors[(Math.random() * colors.length) | 0];
|
|
2317
524
|
};
|
|
2318
|
-
// Two inputs again: the colour token (notified) and the dark marker
|
|
2319
|
-
// (written by darkSync). The particle palette is per scheme, so a
|
|
2320
|
-
// light/dark switch has to re-roll the colours too -- otherwise the
|
|
2321
|
-
// fallback background keeps the scheme it was mounted with, exactly the
|
|
2322
|
-
// bug the fluid path had.
|
|
2323
525
|
const unsubscribeColor = subscribeColor(refreshColors);
|
|
2324
|
-
const unsubscribeDark = darkSync === null ? null : darkSync.subscribe(refreshColors);
|
|
2325
|
-
let tokenMirror = null;
|
|
2326
|
-
try {
|
|
2327
|
-
tokenMirror = new MutationObserver(refreshColors);
|
|
2328
|
-
tokenMirror.observe(document.body, { attributes: true, attributeFilter: ['data-dshome-color'] });
|
|
2329
|
-
} catch (error) {
|
|
2330
|
-
tokenMirror = null;
|
|
2331
|
-
}
|
|
2332
526
|
|
|
2333
527
|
window.addEventListener('resize', resize);
|
|
2334
528
|
resize();
|
|
@@ -2340,8 +534,6 @@ html[data-dshome-blur="off"] body{
|
|
|
2340
534
|
cancelAnimationFrame(rafId);
|
|
2341
535
|
window.removeEventListener('resize', resize);
|
|
2342
536
|
unsubscribeColor();
|
|
2343
|
-
if (unsubscribeDark !== null) unsubscribeDark();
|
|
2344
|
-
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2345
537
|
canvas.remove();
|
|
2346
538
|
};
|
|
2347
539
|
}
|
|
@@ -3564,26 +1756,6 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3564
1756
|
const DSTT_SET = "dstt.mode.set";
|
|
3565
1757
|
/** Peak windows, [startHour, endHour), judged in Beijing time (UTC+8). */
|
|
3566
1758
|
const PEAK_RANGES = [[9, 12], [14, 18]];
|
|
3567
|
-
// The two glass recipes the stylesheet gates on <html data-dshome-glass>.
|
|
3568
|
-
// `liquid` is the default; `frosted` is the pane this theme shipped before.
|
|
3569
|
-
const DSTT_GLASS_STYLES = ["liquid", "frosted"];
|
|
3570
|
-
const DSTT_GLASS_DEFAULT = "liquid";
|
|
3571
|
-
const DSTT_GLASS_LABELS = {
|
|
3572
|
-
liquid: { zh: "液态", en: "Liquid" },
|
|
3573
|
-
frosted: { zh: "白磨砂", en: "Frosted" }
|
|
3574
|
-
};
|
|
3575
|
-
// How wide the composer's edge refraction is under liquid, measured on the
|
|
3576
|
-
// top and bottom edges -- the only two where a displacement has anything to
|
|
3577
|
-
// show. The stylesheet gates the three states on <html data-dshome-composer>.
|
|
3578
|
-
// `origin` was this option's first id and is still accepted from a settings
|
|
3579
|
-
// file written before the rename; it reads back as `wide`.
|
|
3580
|
-
const DSTT_COMPOSER = ["narrow", "wide", "off"];
|
|
3581
|
-
const DSTT_COMPOSER_DEFAULT = "narrow";
|
|
3582
|
-
const DSTT_COMPOSER_LABELS = {
|
|
3583
|
-
narrow: { zh: "窄 8px", en: "Narrow 8px" },
|
|
3584
|
-
wide: { zh: "宽 16px", en: "Wide 16px" },
|
|
3585
|
-
off: { zh: "无", en: "Off" }
|
|
3586
|
-
};
|
|
3587
1759
|
|
|
3588
1760
|
/**
|
|
3589
1761
|
* Shared in-memory mirror of the durable DSTT mode. The settings domain's
|
|
@@ -3591,104 +1763,8 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3591
1763
|
* plugin's own loopback channel instead; every reader (the color driver,
|
|
3592
1764
|
* the settings tab) subscribes to this mirror.
|
|
3593
1765
|
*/
|
|
3594
|
-
const dsttState = { mode: "peakvalley-redblue",
|
|
3595
|
-
// Fields the user has chosen in this session. A host half that predates a
|
|
3596
|
-
// field ignores it and answers without it -- or with its own default -- so
|
|
3597
|
-
// without this a fresh choice gets overwritten by the next sync and the
|
|
3598
|
-
// option appears to snap straight back to its default.
|
|
3599
|
-
const dsttTouched = new Set();
|
|
1766
|
+
const dsttState = { mode: "peakvalley-redblue", listeners: new Set() };
|
|
3600
1767
|
const dsttGetMode = () => dsttState.mode;
|
|
3601
|
-
const dsttGetGlass = () => (DSTT_GLASS_STYLES.includes(dsttState.glassStyle) ? dsttState.glassStyle : DSTT_GLASS_DEFAULT);
|
|
3602
|
-
const dsttGetComposer = () => {
|
|
3603
|
-
if (dsttState.composer === "origin") return "wide";
|
|
3604
|
-
return DSTT_COMPOSER.includes(dsttState.composer) ? dsttState.composer : DSTT_COMPOSER_DEFAULT;
|
|
3605
|
-
};
|
|
3606
|
-
const dsttGetBlur = () => dsttState.blur !== false;
|
|
3607
|
-
/**
|
|
3608
|
-
* Turn the glass frosting on or off. Same shape as the other CSS-only
|
|
3609
|
-
* preferences: apply locally first, then let the host persist it, and treat
|
|
3610
|
-
* its answer as confirmation only.
|
|
3611
|
-
*/
|
|
3612
|
-
async function dsttSetBlur(ctx, next) {
|
|
3613
|
-
dsttState.blur = next === true;
|
|
3614
|
-
dsttTouched.add("blur");
|
|
3615
|
-
dsttNotify();
|
|
3616
|
-
try {
|
|
3617
|
-
const result = await bridgeCall(DSTT_SET, { mode: dsttState.mode, backdropBlur: dsttState.blur });
|
|
3618
|
-
if (result !== undefined && result !== null && result.ok === true) {
|
|
3619
|
-
return result.value.backdropBlur === dsttState.blur;
|
|
3620
|
-
}
|
|
3621
|
-
} catch (error) {
|
|
3622
|
-
// Fall through: the local choice stands, it just was not stored.
|
|
3623
|
-
}
|
|
3624
|
-
return false;
|
|
3625
|
-
}
|
|
3626
|
-
/**
|
|
3627
|
-
* Persist the composer's refraction width through the bridge.
|
|
3628
|
-
*
|
|
3629
|
-
* Applied locally FIRST, before the round trip. Both this and glassStyle
|
|
3630
|
-
* only flip a CSS attribute, so the choice is fully usable the moment it is
|
|
3631
|
-
* made — and it has to be, because a running dsh keeps the host half it
|
|
3632
|
-
* booted with: until `dsh web` is restarted, `dstt.mode.set` ignores fields
|
|
3633
|
-
* it does not know and answers without them, which used to make a brand new
|
|
3634
|
-
* setting look broken. The cost of applying first is that a choice made
|
|
3635
|
-
* against a stale host is not persisted and reverts on reload, so the
|
|
3636
|
-
* return value still reports the truth.
|
|
3637
|
-
*/
|
|
3638
|
-
async function dsttSetComposer(ctx, next) {
|
|
3639
|
-
if (!DSTT_COMPOSER.includes(next)) return false;
|
|
3640
|
-
dsttState.composer = next;
|
|
3641
|
-
dsttTouched.add("composer");
|
|
3642
|
-
dsttNotify();
|
|
3643
|
-
try {
|
|
3644
|
-
const result = await bridgeCall(DSTT_SET, { mode: dsttState.mode, composerRefraction: next });
|
|
3645
|
-
// The response is used ONLY to confirm. It must never be written back
|
|
3646
|
-
// into the mirror: a host that accepts the call but does not store the
|
|
3647
|
-
// field answers with its own value (the default, `narrow`), and copying
|
|
3648
|
-
// that in is what made the option snap straight back the moment the
|
|
3649
|
-
// round trip landed.
|
|
3650
|
-
if (result !== undefined && result !== null && result.ok === true && DSTT_COMPOSER.includes(result.value.composerRefraction)) {
|
|
3651
|
-
return result.value.composerRefraction === next;
|
|
3652
|
-
}
|
|
3653
|
-
} catch (error) {
|
|
3654
|
-
// Fall through: the local choice stands, it just was not stored.
|
|
3655
|
-
}
|
|
3656
|
-
return false;
|
|
3657
|
-
}
|
|
3658
|
-
/** Persist the glass recipe through the bridge; resolves with success.
|
|
3659
|
-
* Applied locally first for the same reason as dsttSetComposer above. */
|
|
3660
|
-
async function dsttSetGlass(ctx, next) {
|
|
3661
|
-
if (!DSTT_GLASS_STYLES.includes(next)) return false;
|
|
3662
|
-
dsttState.glassStyle = next;
|
|
3663
|
-
dsttTouched.add("glassStyle");
|
|
3664
|
-
dsttNotify();
|
|
3665
|
-
try {
|
|
3666
|
-
const result = await bridgeCall(DSTT_SET, { mode: dsttState.mode, glassStyle: next });
|
|
3667
|
-
// Confirmation only -- see dsttSetComposer for why the host's echo must
|
|
3668
|
-
// not be copied back into the mirror.
|
|
3669
|
-
if (result !== undefined && result !== null && result.ok === true && DSTT_GLASS_STYLES.includes(result.value.glassStyle)) {
|
|
3670
|
-
return result.value.glassStyle === next;
|
|
3671
|
-
}
|
|
3672
|
-
} catch (error) {
|
|
3673
|
-
// Fall through: the local choice stands, it just was not stored.
|
|
3674
|
-
}
|
|
3675
|
-
return false;
|
|
3676
|
-
}
|
|
3677
|
-
const dsttGetBrush = () => dsttState.fluidBrush === true;
|
|
3678
|
-
/** Persist the fluid-brush preference (off by default) through the bridge. */
|
|
3679
|
-
async function dsttSetBrush(ctx, next) {
|
|
3680
|
-
try {
|
|
3681
|
-
const result = await bridgeCall(DSTT_SET, { mode: dsttState.mode, fluidBrush: next === true });
|
|
3682
|
-
if (result !== undefined && result !== null && result.ok === true) {
|
|
3683
|
-
dsttState.fluidBrush = result.value.fluidBrush === true;
|
|
3684
|
-
dsttNotify();
|
|
3685
|
-
return true;
|
|
3686
|
-
}
|
|
3687
|
-
} catch (error) {
|
|
3688
|
-
// Fall through to the caller's failure handling.
|
|
3689
|
-
}
|
|
3690
|
-
return false;
|
|
3691
|
-
}
|
|
3692
1768
|
const dsttSubscribe = (listener) => {
|
|
3693
1769
|
dsttState.listeners.add(listener);
|
|
3694
1770
|
return () => dsttState.listeners.delete(listener);
|
|
@@ -3718,24 +1794,12 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3718
1794
|
return await response.json();
|
|
3719
1795
|
}
|
|
3720
1796
|
|
|
3721
|
-
/** Fallback used when the host half is unavailable. */
|
|
3722
|
-
const dsttGetBlurFallback = () => dsttState.blur !== false;
|
|
3723
|
-
|
|
3724
1797
|
/** Pull the durable mode from the host half once. */
|
|
3725
1798
|
async function dsttSync(ctx) {
|
|
3726
1799
|
try {
|
|
3727
1800
|
const result = await bridgeCall(DSTT_GET, {});
|
|
3728
1801
|
if (result !== undefined && result !== null && result.ok === true && DSTT_MODES.includes(result.value.mode)) {
|
|
3729
1802
|
dsttState.mode = result.value.mode;
|
|
3730
|
-
dsttState.fluidBrush = result.value.fluidBrush === true;
|
|
3731
|
-
// An older host half answers without a glass recipe: keep the
|
|
3732
|
-
// default rather than clearing the attribute to nothing. And a
|
|
3733
|
-
// field the user has already chosen this session is never taken
|
|
3734
|
-
// back from the host -- a stale host answers with its own default,
|
|
3735
|
-
// which is what made a fresh choice look like it snapped back.
|
|
3736
|
-
if (!dsttTouched.has("glassStyle") && DSTT_GLASS_STYLES.includes(result.value.glassStyle)) dsttState.glassStyle = result.value.glassStyle;
|
|
3737
|
-
if (!dsttTouched.has("composer") && DSTT_COMPOSER.includes(result.value.composerRefraction)) dsttState.composer = result.value.composerRefraction;
|
|
3738
|
-
if (!dsttTouched.has("blur") && typeof result.value.backdropBlur === "boolean") dsttState.blur = result.value.backdropBlur;
|
|
3739
1803
|
dsttNotify();
|
|
3740
1804
|
}
|
|
3741
1805
|
} catch (error) {}
|
|
@@ -3900,18 +1964,10 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3900
1964
|
|
|
3901
1965
|
anchor();
|
|
3902
1966
|
|
|
3903
|
-
// The durable mode crosses an async bridge (dsttSync), so the anchor above
|
|
3904
|
-
// may have resolved the colour from the DEFAULT mode -- its 常态绿/常态蓝
|
|
3905
|
-
// token then never appears, and the background keeps the first guess. One
|
|
3906
|
-
// extra read after the sync has had its turn fixes the boot race, and it
|
|
3907
|
-
// is idempotent, so it cannot fight a later answer.
|
|
3908
|
-
const bootTimer = setTimeout(anchor, 0);
|
|
3909
|
-
|
|
3910
1967
|
try { unsubscribe = dsttSubscribe(anchor); } catch (error) { unsubscribe = null; }
|
|
3911
1968
|
|
|
3912
1969
|
return () => {
|
|
3913
1970
|
stopped = true;
|
|
3914
|
-
clearTimeout(bootTimer);
|
|
3915
1971
|
if (chainTimer !== 0) clearTimeout(chainTimer);
|
|
3916
1972
|
if (unsubscribe !== null) unsubscribe();
|
|
3917
1973
|
document.removeEventListener('visibilitychange', onVisibility);
|
|
@@ -3985,30 +2041,6 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3985
2041
|
if (dstt === undefined || next === mode) return;
|
|
3986
2042
|
dstt.set(next);
|
|
3987
2043
|
};
|
|
3988
|
-
// The brush is a durable preference, not local state: flipping it asks the
|
|
3989
|
-
// host to persist it and the mirror re-renders this panel.
|
|
3990
|
-
const brushOn = dstt !== undefined && typeof dstt.brush === 'function' ? dstt.brush() === true : false;
|
|
3991
|
-
const toggleBrush = () => {
|
|
3992
|
-
if (dstt === undefined || typeof dstt.setBrush !== 'function') return;
|
|
3993
|
-
dstt.setBrush(!brushOn);
|
|
3994
|
-
};
|
|
3995
|
-
// The glass recipe is durable too, and the mirror re-renders this panel
|
|
3996
|
-
// when the host confirms the write.
|
|
3997
|
-
const glassNow = dstt !== undefined && typeof dstt.glass === 'function' ? dstt.glass() : DSTT_GLASS_DEFAULT;
|
|
3998
|
-
const pickGlass = (next) => {
|
|
3999
|
-
if (dstt === undefined || typeof dstt.setGlass !== 'function' || next === glassNow) return;
|
|
4000
|
-
dstt.setGlass(next);
|
|
4001
|
-
};
|
|
4002
|
-
const composerNow = dstt !== undefined && typeof dstt.composer === 'function' ? dstt.composer() : DSTT_COMPOSER_DEFAULT;
|
|
4003
|
-
const pickComposer = (next) => {
|
|
4004
|
-
if (dstt === undefined || typeof dstt.setComposer !== 'function' || next === composerNow) return;
|
|
4005
|
-
dstt.setComposer(next);
|
|
4006
|
-
};
|
|
4007
|
-
const blurOn = dstt !== undefined && typeof dstt.blur === 'function' ? dstt.blur() !== false : true;
|
|
4008
|
-
const toggleBlur = () => {
|
|
4009
|
-
if (dstt === undefined || typeof dstt.setBlur !== 'function') return;
|
|
4010
|
-
dstt.setBlur(!blurOn);
|
|
4011
|
-
};
|
|
4012
2044
|
|
|
4013
2045
|
return React.createElement('div', { className: 'dshome-dstt' },
|
|
4014
2046
|
React.createElement('h3', null,
|
|
@@ -4032,62 +2064,6 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4032
2064
|
className: 'dshome-dstt-seg-btn' + (mode === m ? ' dshome-on' : ''),
|
|
4033
2065
|
onClick: () => pickMode(m)
|
|
4034
2066
|
}, isZh ? DSTT_LABELS[m].zh : DSTT_LABELS[m].en)))),
|
|
4035
|
-
React.createElement('div', { className: 'dshome-dstt-row' },
|
|
4036
|
-
React.createElement('div', { className: 'dshome-dstt-row-body' },
|
|
4037
|
-
React.createElement('div', { className: 'dshome-dstt-row-title' }, isZh ? '流体跟随笔刷' : 'Fluid follow brush'),
|
|
4038
|
-
React.createElement('div', { className: 'dshome-dstt-row-hint' }, isZh
|
|
4039
|
-
? '开启后光标会把背景流场搅出一条跟随的尾迹。它是整个模拟里最贵的一部分,默认关闭;改完刷新页面生效。'
|
|
4040
|
-
: 'Lets the cursor stir a trail into the background flow. It is the most expensive part of the simulation, so it is off by default; reload after changing.')),
|
|
4041
|
-
React.createElement('button', {
|
|
4042
|
-
type: 'button',
|
|
4043
|
-
role: 'switch',
|
|
4044
|
-
'aria-checked': brushOn,
|
|
4045
|
-
className: 'dshome-dstt-seg-btn' + (brushOn ? ' dshome-on' : ''),
|
|
4046
|
-
onClick: toggleBrush
|
|
4047
|
-
}, brushOn ? (isZh ? '已开启' : 'On') : (isZh ? '已关闭' : 'Off'))),
|
|
4048
|
-
React.createElement('div', { className: 'dshome-dstt-row' },
|
|
4049
|
-
React.createElement('div', { className: 'dshome-dstt-row-body' },
|
|
4050
|
-
React.createElement('div', { className: 'dshome-dstt-row-title' }, isZh ? '玻璃风格' : 'Glass style'),
|
|
4051
|
-
React.createElement('div', { className: 'dshome-dstt-row-hint' }, isZh
|
|
4052
|
-
? '液态:透镜折射 + 会走圈的边缘高光,更通透(本次方向)。白磨砂:此前那套白磨砂 + 蓝变光边框,观感与从前完全一致。'
|
|
4053
|
-
: 'Liquid: lens refraction plus a highlight that travels the border, far more translucent (the current direction). Frosted: the white frosted pane with the blue shifting border, pixel-identical to what shipped before.')),
|
|
4054
|
-
React.createElement('div', { className: 'dshome-dstt-seg', role: 'radiogroup', 'aria-label': isZh ? '玻璃风格' : 'Glass style' },
|
|
4055
|
-
DSTT_GLASS_STYLES.map((style) => React.createElement('button', {
|
|
4056
|
-
key: style,
|
|
4057
|
-
type: 'button',
|
|
4058
|
-
role: 'radio',
|
|
4059
|
-
'aria-checked': glassNow === style,
|
|
4060
|
-
className: 'dshome-dstt-seg-btn' + (glassNow === style ? ' dshome-on' : ''),
|
|
4061
|
-
onClick: () => pickGlass(style)
|
|
4062
|
-
}, isZh ? DSTT_GLASS_LABELS[style].zh : DSTT_GLASS_LABELS[style].en)))),
|
|
4063
|
-
React.createElement('div', { className: 'dshome-dstt-row' },
|
|
4064
|
-
React.createElement('div', { className: 'dshome-dstt-row-body' },
|
|
4065
|
-
React.createElement('div', { className: 'dshome-dstt-row-title' }, isZh ? '对话框边缘倒影' : 'Composer edge reflection'),
|
|
4066
|
-
React.createElement('div', { className: 'dshome-dstt-row-hint' }, isZh
|
|
4067
|
-
? '液态下对话框边缘的折射带宽(按上下量——左右背后是连续背景,同样的位移看不出变化)。窄 8px(默认)/ 宽 16px / 无,三者都是专为这张卡片做的位移图。'
|
|
4068
|
-
: 'Width of the lens band at the composer edge under liquid, measured top and bottom — left and right sit over a continuous backdrop where the same displacement shows nothing. Narrow 8px (default) / Wide 16px / Off; each is a map built for this card.')),
|
|
4069
|
-
React.createElement('div', { className: 'dshome-dstt-seg', role: 'radiogroup', 'aria-label': isZh ? '对话框边缘倒影' : 'Composer edge reflection' },
|
|
4070
|
-
DSTT_COMPOSER.map((choice) => React.createElement('button', {
|
|
4071
|
-
key: choice,
|
|
4072
|
-
type: 'button',
|
|
4073
|
-
role: 'radio',
|
|
4074
|
-
'aria-checked': composerNow === choice,
|
|
4075
|
-
className: 'dshome-dstt-seg-btn' + (composerNow === choice ? ' dshome-on' : ''),
|
|
4076
|
-
onClick: () => pickComposer(choice)
|
|
4077
|
-
}, isZh ? DSTT_COMPOSER_LABELS[choice].zh : DSTT_COMPOSER_LABELS[choice].en)))),
|
|
4078
|
-
React.createElement('div', { className: 'dshome-dstt-row' },
|
|
4079
|
-
React.createElement('div', { className: 'dshome-dstt-row-body' },
|
|
4080
|
-
React.createElement('div', { className: 'dshome-dstt-row-title' }, isZh ? '背景模糊(磨砂)' : 'Backdrop blur'),
|
|
4081
|
-
React.createElement('div', { className: 'dshome-dstt-row-hint' }, isZh
|
|
4082
|
-
? '玻璃面板后面那层虚化。关闭后玻璃变成纯半透明:通透度、顶部光泽、边缘折射都保留,只是不再把背后的东西磨掉。'
|
|
4083
|
-
: 'The frosting behind every glass pane. Off leaves the translucency, the sheen and the edge refraction intact and only stops blurring what is behind the glass.')),
|
|
4084
|
-
React.createElement('button', {
|
|
4085
|
-
type: 'button',
|
|
4086
|
-
role: 'switch',
|
|
4087
|
-
'aria-checked': blurOn,
|
|
4088
|
-
className: 'dshome-dstt-seg-btn' + (blurOn ? ' dshome-on' : ''),
|
|
4089
|
-
onClick: toggleBlur
|
|
4090
|
-
}, blurOn ? (isZh ? '已开启' : 'On') : (isZh ? '已关闭' : 'Off'))),
|
|
4091
2067
|
React.createElement('div', { className: 'dshome-dstt-status', 'data-peak': timeSliced && peakNow },
|
|
4092
2068
|
'当前:' + colorName + ' · ' + status));
|
|
4093
2069
|
}
|
|
@@ -4115,22 +2091,8 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4115
2091
|
|
|
4116
2092
|
// Dark marker: owned attribute driven by the theme service, never by
|
|
4117
2093
|
// the product's attribute name. Must precede the patch blocks so the
|
|
4118
|
-
// dark selectors match from the first paint.
|
|
4119
|
-
|
|
4120
|
-
// alone was not enough, since nothing re-read it when the scheme changed.
|
|
4121
|
-
let darkSync = null;
|
|
4122
|
-
ctx.effect(() => {
|
|
4123
|
-
const handle = createDarkSync(ctx, theme);
|
|
4124
|
-
darkSync = handle;
|
|
4125
|
-
return () => {
|
|
4126
|
-
if (darkSync === handle) darkSync = null;
|
|
4127
|
-
handle.dispose();
|
|
4128
|
-
};
|
|
4129
|
-
}, 'deepseek-style-theme: dark sync');
|
|
4130
|
-
|
|
4131
|
-
// Glass-recipe marker: same idea, and it has to be on <html> before the
|
|
4132
|
-
// first paint of the glass surfaces or the pane flashes the other recipe.
|
|
4133
|
-
ctx.effect(() => createGlassSync(ctx), 'deepseek-style-theme: glass style sync');
|
|
2094
|
+
// dark selectors match from the first paint.
|
|
2095
|
+
ctx.effect(() => createDarkSync(ctx, theme), 'deepseek-style-theme: dark sync');
|
|
4134
2096
|
|
|
4135
2097
|
// Guarded product-surface patches: each block activates only while
|
|
4136
2098
|
// its anchor class exists in the DOM.
|
|
@@ -4138,17 +2100,6 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4138
2100
|
ctx.effect(() => installGuarded(block.anchor, block.css), 'deepseek-style-theme: patch ' + block.anchor);
|
|
4139
2101
|
}
|
|
4140
2102
|
|
|
4141
|
-
// Liquid glass wins by SPECIFICITY, not by order: the patch blocks are
|
|
4142
|
-
// injected lazily when their anchor appears and set `background`
|
|
4143
|
-
// (shorthand) with !important, so every glass selector below is written
|
|
4144
|
-
// as `html .surface` — one type selector of extra weight — instead of
|
|
4145
|
-
// racing their insertion order.
|
|
4146
|
-
ctx.effect(() => injectStyle(GLASS_CSS, 'glass.css'), 'deepseek-style-theme: liquid glass');
|
|
4147
|
-
// The liquid recipe is a second sheet rather than an edit of the first:
|
|
4148
|
-
// every rule inside is gated on html[data-dshome-glass="liquid"], so the
|
|
4149
|
-
// frosted pane keeps rendering exactly what it rendered before.
|
|
4150
|
-
ctx.effect(() => injectStyle(LIQUID_CSS, 'liquid-glass.css'), 'deepseek-style-theme: apple liquid glass');
|
|
4151
|
-
|
|
4152
2103
|
if (theme !== undefined) {
|
|
4153
2104
|
const disposeTokens = theme.overrideTokens('deepseek-style-theme', {
|
|
4154
2105
|
'--dsw-alias-bg-base': { light: '#f6f7fb', dark: '#0a1a3a' },
|
|
@@ -4169,14 +2120,8 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4169
2120
|
}
|
|
4170
2121
|
|
|
4171
2122
|
ctx.effect(() => {
|
|
4172
|
-
try { return
|
|
4173
|
-
}
|
|
4174
|
-
// The composer's hover tilt is independent of the background: it must
|
|
4175
|
-
// survive a machine without WebGL2, where startAmbient() falls back to
|
|
4176
|
-
// the 2D particle field.
|
|
4177
|
-
ctx.effect(() => {
|
|
4178
|
-
try { return startComposerTilt(); } catch (error) { return () => {}; }
|
|
4179
|
-
}, 'deepseek-style-theme: composer hover tilt');
|
|
2123
|
+
try { return startParticles(); } catch (error) { return () => {}; }
|
|
2124
|
+
});
|
|
4180
2125
|
ctx.effect(() => {
|
|
4181
2126
|
try { return watchSettingsSection(); } catch (error) { return () => {}; }
|
|
4182
2127
|
});
|
|
@@ -4211,14 +2156,6 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4211
2156
|
dstt: {
|
|
4212
2157
|
mode: dsttGetMode,
|
|
4213
2158
|
set: (mode) => dsttSetMode(ctx, mode),
|
|
4214
|
-
brush: dsttGetBrush,
|
|
4215
|
-
setBrush: (next) => dsttSetBrush(ctx, next),
|
|
4216
|
-
glass: dsttGetGlass,
|
|
4217
|
-
setGlass: (next) => dsttSetGlass(ctx, next),
|
|
4218
|
-
composer: dsttGetComposer,
|
|
4219
|
-
setComposer: (next) => dsttSetComposer(ctx, next),
|
|
4220
|
-
blur: dsttGetBlur,
|
|
4221
|
-
setBlur: (next) => dsttSetBlur(ctx, next),
|
|
4222
2159
|
subscribe: dsttSubscribe
|
|
4223
2160
|
},
|
|
4224
2161
|
locale: ctx.get('locale')
|
|
@@ -4232,11 +2169,12 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4232
2169
|
return module.exports;
|
|
4233
2170
|
};
|
|
4234
2171
|
|
|
4235
|
-
// Both ids, same factory.
|
|
4236
|
-
//
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
} catch (error) {}
|
|
2172
|
+
// Both ids, same factory. The package moved from
|
|
2173
|
+
// @dsh-external/dsh-deepseek-style-theme to the unscoped
|
|
2174
|
+
// dsh-deepseek-style-theme, and the loader asks a bundle to register under
|
|
2175
|
+
// whichever id the installed copy carries -- while this rolled-back file
|
|
2176
|
+
// still carries the old literal internally. Registering both, each guarded,
|
|
2177
|
+
// means either host finds the id it wants and the other entry is simply
|
|
2178
|
+
// never requested.
|
|
2179
|
+
try { window.__ModuleLoader__.load({ id: "dsh-deepseek-style-theme", factory: dshDeepseekStyleThemeFactory }); } catch (error) {}
|
|
2180
|
+
try { window.__ModuleLoader__.load({ id: "@dsh-external/dsh-deepseek-style-theme", factory: dshDeepseekStyleThemeFactory }); } catch (error) {}
|