dsh-deepseek-style-theme 2.0.84 → 2.0.86
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/THIRD-PARTY-NOTICES.md +30 -10
- package/lib/client.js +235 -44
- package/lib/index.js +1116 -1107
- package/package.json +1 -1
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -23,16 +23,36 @@ dispersion / specular-parallax subsystem of **dsh-theme-mineradio v2.3.8**:
|
|
|
23
23
|
Only naming and external dependencies were adapted: plugin-scoped identifiers
|
|
24
24
|
were renamed to this plugin's `dshome-` prefix, and the upstream settings/theme
|
|
25
25
|
store and React layer were replaced by function parameters. **The GLSL shader
|
|
26
|
-
sources are copies of upstream, with
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`
|
|
32
|
-
bits of mantissa, so at 16 bits the hash collapses into
|
|
33
|
-
noise lattice shows up as large drifting squares.
|
|
34
|
-
fragment shaders, and on GPUs that promoted mediump
|
|
35
|
-
|
|
26
|
+
sources are copies of upstream, with two recorded deviations:**
|
|
27
|
+
|
|
28
|
+
1. **(v2.0.74)** the `precision mediump float;` qualifier in FLOW_SHADER and
|
|
29
|
+
DISPLAY_SHADER was changed to `precision highp float;`. The reason is a
|
|
30
|
+
rendering defect, not a feature: under ANGLE/D3D11 on Intel integrated GPUs
|
|
31
|
+
`mediump` is a real 16-bit float (`min16float`), and the display shader's noise
|
|
32
|
+
needs roughly 24 bits of mantissa, so at 16 bits the hash collapses into
|
|
33
|
+
constant plateaus and the noise lattice shows up as large drifting squares.
|
|
34
|
+
WebGL2 guarantees highp in fragment shaders, and on GPUs that promoted mediump
|
|
35
|
+
to 32 bits the change is a no-op.
|
|
36
|
+
2. **(v2.0.86)** DISPLAY_SHADER's `random(vec2 st)` -- upstream's classic
|
|
37
|
+
`fract(sin(dot(st, vec2(12.9898,78.233))) * 43758.5453123)` -- was replaced by
|
|
38
|
+
Dave Hoskins' sin-free hash (`fract(vec3(p.xyx) * 0.1031)` plus one dot and one
|
|
39
|
+
multiply). The reason is the same class of defect, one layer down and this time
|
|
40
|
+
precision-independent: this shader scales the noise coordinate to ~1e5
|
|
41
|
+
(`uv *= ns * u_resolution`, doubled again for `n2`), and at that magnitude the
|
|
42
|
+
error left by `sin()`'s argument reduction in float32 is multiplied by 43758
|
|
43
|
+
into ~100 units, so neighbouring cells receive nearly the same value and the
|
|
44
|
+
warp field degenerates into flat plateaus with hard edges (measured on an Intel
|
|
45
|
+
Arc 130V: 39.6% flat 8x8 blocks, an 800x1000px plateau, 60-90/255 jumps along
|
|
46
|
+
its boundary -- the user's 大方块). The sin-free hash keeps every intermediate
|
|
47
|
+
small, so it behaves identically on every GPU, and it preserves upstream's
|
|
48
|
+
per-cell white noise rather than shrinking the domain warp (which would cure the
|
|
49
|
+
symptom by removing the effect). `noise()`'s interpolation and every other line
|
|
50
|
+
of every shader are unchanged.
|
|
51
|
+
|
|
52
|
+
The two deviations are related on purpose: deviation 1 fixed a 16-bit intermediate
|
|
53
|
+
precision collapse, deviation 2 the 32-bit argument-magnitude collapse that
|
|
54
|
+
survived it. Both were diagnosed from the same symptom on the same machine class,
|
|
55
|
+
and both were verified there rather than assumed.
|
|
36
56
|
|
|
37
57
|
Upstream notes that its fluid shader is itself a faithful port of the
|
|
38
58
|
`ds-join-shader-bg` shader in the deepseek.com site bundle, with the shader
|
package/lib/client.js
CHANGED
|
@@ -238,7 +238,27 @@ out vec4 fragColor;
|
|
|
238
238
|
#define PI 3.14159265358979323846
|
|
239
239
|
|
|
240
240
|
vec2 rotate(vec2 uv, float th) { return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv; }
|
|
241
|
-
|
|
241
|
+
/* Upstream's hash is the classic fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123)
|
|
242
|
+
-- no backticks in this comment: it lives inside a template literal, and a pair of
|
|
243
|
+
them ends the literal (tools/parse-smoke.mjs exists because that has happened three
|
|
244
|
+
times). Its argument is NOT small in this shader: uv is scaled by ns * u_resolution
|
|
245
|
+
(about 2900 at the user's viewport) and doubled again for n2, so dot() reaches ~2e5.
|
|
246
|
+
At that magnitude float32 leaves ~2.5e-3 of error inside sin(), the 43758x multiplier
|
|
247
|
+
turns it into ~100 units, and the hash stops behaving like a hash: neighbouring cells
|
|
248
|
+
receive almost the same value, so the warp field degenerates into flat plateaus with
|
|
249
|
+
hard edges -- the 大方块 of the 2026-09-16 fluid report, measured on an Intel Arc 130V
|
|
250
|
+
as 39.6% flat 8x8 blocks, an 800x1000px plateau, and 60-90/255 jumps along its
|
|
251
|
+
boundary (the two earlier fixes, mediump -> highp and RGBA8 -> RGBA16F, were both
|
|
252
|
+
verified active on that machine and the blocks survived, which is what pointed here).
|
|
253
|
+
This is Dave Hoskins' sin-free hash: every intermediate stays small, so it behaves the
|
|
254
|
+
same on every GPU and keeps upstream's per-cell white noise instead of deleting the
|
|
255
|
+
domain warp (the ablation's noDistort variant cures the symptom by removing the
|
|
256
|
+
effect). Recorded as deviation #2 in THIRD-PARTY-NOTICES.md. */
|
|
257
|
+
float random(vec2 st) {
|
|
258
|
+
vec3 p3 = fract(vec3(st.xyx) * 0.1031);
|
|
259
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
260
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
261
|
+
}
|
|
242
262
|
float noise(vec2 st) {
|
|
243
263
|
vec2 i = floor(st); vec2 f = fract(st);
|
|
244
264
|
float a = random(i), b = random(i + vec2(1,0)), c = random(i + vec2(0,1)), d = random(i + vec2(1,1));
|
|
@@ -758,7 +778,16 @@ void main() {
|
|
|
758
778
|
// a separate constant so the frosted recipe keeps receiving 60.
|
|
759
779
|
var LIQUID_REF_SCALE = 104;
|
|
760
780
|
var EDGE_DX = -14;
|
|
761
|
-
|
|
781
|
+
// The lens tint. It is mixed into `|backdrop - backdrop shifted by EDGE_DX|`,
|
|
782
|
+
// which is meant to be non-zero only along a hard edge -- but over a GRADIENT
|
|
783
|
+
// backdrop (the `bold` background recipe is exactly that) the difference is
|
|
784
|
+
// non-zero across the WHOLE pane, so the tint stopped being an edge fringe and
|
|
785
|
+
// became a wash: the first user to see it (2.0.84 restored the mount after ATTR
|
|
786
|
+
// had been missing since 1.43.6) reported "我的玻璃怎么变成凝胶了" with a
|
|
787
|
+
// screenshot of a solid pink composer. 0.45 -> 0.16 keeps the fringing (that
|
|
788
|
+
// comes from the per-channel chromatic displacement, not from the tint) while
|
|
789
|
+
// the wash drops to a hint.
|
|
790
|
+
var TINT_OPACITY = 0.16;
|
|
762
791
|
var TINT_SAT = 0.85;
|
|
763
792
|
var TINT_LIGHT = 0.6;
|
|
764
793
|
var DEFAULT_TINT_HUE = 44;
|
|
@@ -2378,12 +2407,37 @@ html[data-dshome-blur="off"] body{
|
|
|
2378
2407
|
// recipe. Both recipes tilt now, and because the liquid sheet used to be
|
|
2379
2408
|
// the only place carrying `transition: transform`, the motion carries
|
|
2380
2409
|
// its own transition (TILT_TRANSITION) so a recipe cannot lose it.
|
|
2410
|
+
// * 2.0.85 makes ENGAGEMENT GEOMETRIC and the gates honest. Engagement used
|
|
2411
|
+
// to need a `pointerover` whose target was inside the card; that is a
|
|
2412
|
+
// chain of assumptions about product markup (a wrapper with role=button,
|
|
2413
|
+
// a swapped card node, a portal, an overlay) which a theme cannot verify,
|
|
2414
|
+
// and failing it looks exactly like a broken effect -- reported as
|
|
2415
|
+
// "把我的对话框随着鼠标位置而变化的效果补回来". `spotAt()` decides from the
|
|
2416
|
+
// card's own rectangle on every pointermove, so a missed event costs one
|
|
2417
|
+
// frame instead of the whole effect; `controlAt()` remains the single hit
|
|
2418
|
+
// test, and only to answer "is a control under the pointer". The two
|
|
2419
|
+
// SILENT gates are reported now: `__dshomeTilt()` includes `reducedMotion`
|
|
2420
|
+
// (an OS setting that disables the effect and looks like a bug) and
|
|
2421
|
+
// `spots` (0 means the composer no longer matches TILT_SELECTOR), and the
|
|
2422
|
+
// reduced-motion case logs one info line instead of vanishing.
|
|
2381
2423
|
const TILT_SELECTOR = '[data-composer-card],.uV2eYG_card';
|
|
2382
|
-
|
|
2424
|
+
// 0.0175 rad was upstream's conservative lean, and after 2.0.85 finally made
|
|
2425
|
+
// the effect reachable the verdict was "倾斜效果很微弱" -- fair, at ~4 degrees
|
|
2426
|
+
// end to end over a pane this wide it reads as a wobble, not as depth. Raised
|
|
2427
|
+
// ~1.7x, with TILT_RELEASE_PAD keeping the stronger movement from feeding a loop.
|
|
2428
|
+
const TILT_MAX = 0.03;
|
|
2383
2429
|
const TILT_PERSPECTIVE = 800;
|
|
2384
|
-
const TILT_SCALE = 1.
|
|
2430
|
+
const TILT_SCALE = 1.015;
|
|
2385
2431
|
const TILT_SETTLE_MS = 240;
|
|
2386
2432
|
const TILT_COOLDOWN_MS = 200;
|
|
2433
|
+
/**
|
|
2434
|
+
* How far outside the card's own rectangle the pointer must travel before the
|
|
2435
|
+
* lean is released. Engagement is geometric (see spotAt), so without a pad the
|
|
2436
|
+
* lean's own movement could carry the card's edge past a stationary pointer:
|
|
2437
|
+
* out -> release -> back in -> engage, at frame rate. 8px is comfortably more
|
|
2438
|
+
* than the lift moves an edge (1.5% of a ~112px pane is under 2px).
|
|
2439
|
+
*/
|
|
2440
|
+
const TILT_RELEASE_PAD = 8;
|
|
2387
2441
|
/** The lean's own easing. Mirrors the liquid sheet's values, and is removed
|
|
2388
2442
|
* together with the inline transform so nothing keeps animating afterwards. */
|
|
2389
2443
|
const TILT_TRANSITION = 'transform .24s cubic-bezier(.2,.8,.2,1),background-color .18s ease,box-shadow .18s ease';
|
|
@@ -2407,13 +2461,28 @@ html[data-dshome-blur="off"] body{
|
|
|
2407
2461
|
const tilted = new WeakSet();
|
|
2408
2462
|
const settle = new Map();
|
|
2409
2463
|
const reduced = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
2410
|
-
|
|
2411
|
-
//
|
|
2412
|
-
//
|
|
2413
|
-
//
|
|
2464
|
+
// Readable from the page: the effect is guarded several times over, so "it
|
|
2465
|
+
// is missing" is otherwise a guess about which guard ate it.
|
|
2466
|
+
// `__dshomeTilt()` answers that in one call -- including the two SILENT
|
|
2467
|
+
// gates, which is why `reduced` and `spots` are in here at all: an OS-level
|
|
2468
|
+
// "reduce motion" or a composer whose markup no longer matches
|
|
2469
|
+
// TILT_SELECTOR both look exactly like a broken effect.
|
|
2414
2470
|
try {
|
|
2415
|
-
window.__dshomeTilt = () => ({
|
|
2471
|
+
window.__dshomeTilt = () => ({
|
|
2472
|
+
recipe: dsttGetGlass(),
|
|
2473
|
+
reducedMotion: reduced,
|
|
2474
|
+
spots: document.querySelectorAll(TILT_SELECTOR).length,
|
|
2475
|
+
leaning: current !== null,
|
|
2476
|
+
frozen,
|
|
2477
|
+
engagements,
|
|
2478
|
+
sinceRelease: releasedAt === -Infinity ? null : Math.round(performance.now() - releasedAt)
|
|
2479
|
+
});
|
|
2416
2480
|
} catch (error) {}
|
|
2481
|
+
if (reduced) {
|
|
2482
|
+
// Never silent: this is the one gate a user cannot see in any setting.
|
|
2483
|
+
console.info('[dsh-deepseek-style-theme] composer tilt disabled: the system asks for reduced motion (prefers-reduced-motion: reduce)');
|
|
2484
|
+
return () => {};
|
|
2485
|
+
}
|
|
2417
2486
|
const release = (spot) => {
|
|
2418
2487
|
const pending = settle.get(spot);
|
|
2419
2488
|
if (pending !== undefined) {
|
|
@@ -2452,21 +2521,48 @@ html[data-dshome-blur="off"] body{
|
|
|
2452
2521
|
tilted.add(spot);
|
|
2453
2522
|
});
|
|
2454
2523
|
};
|
|
2524
|
+
/**
|
|
2525
|
+
* Which tilt spot contains a point, by RECTANGLE -- deliberately not by
|
|
2526
|
+
* walking `event.target` up to a selector. Engagement used to depend on a
|
|
2527
|
+
* `pointerover` whose target was inside the card, and that is a chain of
|
|
2528
|
+
* assumptions about the product's markup (a wrapper with role=button, a
|
|
2529
|
+
* swapped-in card node, a portal, an overlay) that a theme cannot verify.
|
|
2530
|
+
* The reported symptom of getting it wrong is indistinguishable from a
|
|
2531
|
+
* broken effect: the box just never leans. Geometry is the one thing the
|
|
2532
|
+
* theme can measure for itself.
|
|
2533
|
+
*/
|
|
2534
|
+
const spotAt = (clientX, clientY) => {
|
|
2535
|
+
for (const spot of document.querySelectorAll(TILT_SELECTOR)) {
|
|
2536
|
+
const rect = spot.getBoundingClientRect();
|
|
2537
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
2538
|
+
if (clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom) continue;
|
|
2539
|
+
return { spot, rect };
|
|
2540
|
+
}
|
|
2541
|
+
return null;
|
|
2542
|
+
};
|
|
2543
|
+
/** The topmost element at a point, used only to decide "is this a control". */
|
|
2544
|
+
const controlAt = (clientX, clientY) => {
|
|
2545
|
+
let node = null;
|
|
2546
|
+
try { node = document.elementFromPoint(clientX, clientY); } catch (error) { node = null; }
|
|
2547
|
+
if (node === null || typeof node.closest !== 'function') return false;
|
|
2548
|
+
return node.closest(TILT_CONTROLS) !== null;
|
|
2549
|
+
};
|
|
2455
2550
|
const onOver = (event) => {
|
|
2551
|
+
// Kept for the one thing it does better than a move: it fires the
|
|
2552
|
+
// moment the pointer arrives, before any movement. Everything it
|
|
2553
|
+
// decides is re-derived from geometry, so a missed event costs one
|
|
2554
|
+
// frame, not the effect.
|
|
2456
2555
|
const target = event.target;
|
|
2457
2556
|
if (target === null || typeof target.closest !== 'function') return;
|
|
2458
2557
|
const spot = target.closest(TILT_SELECTOR);
|
|
2459
2558
|
if (spot === null) return;
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (
|
|
2559
|
+
const rect = spot.getBoundingClientRect();
|
|
2560
|
+
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return;
|
|
2561
|
+
if (controlAt(event.clientX, event.clientY)) {
|
|
2463
2562
|
if (current === spot) frozen = true;
|
|
2464
2563
|
return;
|
|
2465
2564
|
}
|
|
2466
|
-
// The cooldown gates a fresh engagement only, never an ongoing lean.
|
|
2467
2565
|
if (!tilted.has(spot) && performance.now() - releasedAt < TILT_COOLDOWN_MS) return;
|
|
2468
|
-
const rect = spot.getBoundingClientRect();
|
|
2469
|
-
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return;
|
|
2470
2566
|
const pending = settle.get(spot);
|
|
2471
2567
|
if (pending !== undefined) {
|
|
2472
2568
|
clearTimeout(pending);
|
|
@@ -2477,24 +2573,48 @@ html[data-dshome-blur="off"] body{
|
|
|
2477
2573
|
paint(spot, rect, event.clientX, event.clientY);
|
|
2478
2574
|
};
|
|
2479
2575
|
const onMove = (event) => {
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2576
|
+
const hit = spotAt(event.clientX, event.clientY);
|
|
2577
|
+
if (hit === null) {
|
|
2578
|
+
// Off the card: release the lean, but only past the release pad --
|
|
2579
|
+
// the lean's own movement must not be able to hand the card back
|
|
2580
|
+
// and forth under a stationary pointer.
|
|
2581
|
+
if (current !== null) {
|
|
2582
|
+
const rect = current.getBoundingClientRect();
|
|
2583
|
+
const outside = event.clientX < rect.left - TILT_RELEASE_PAD || event.clientX > rect.right + TILT_RELEASE_PAD
|
|
2584
|
+
|| event.clientY < rect.top - TILT_RELEASE_PAD || event.clientY > rect.bottom + TILT_RELEASE_PAD;
|
|
2585
|
+
if (outside) {
|
|
2586
|
+
release(current);
|
|
2587
|
+
current = null;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
const { spot, rect } = hit;
|
|
2593
|
+
if (controlAt(event.clientX, event.clientY)) {
|
|
2594
|
+
// A control is under the pointer: hold the lean exactly where it is
|
|
2595
|
+
// (releasing here is what used to feed the ~8 Hz hover loop).
|
|
2596
|
+
if (current === spot) frozen = true;
|
|
2484
2597
|
return;
|
|
2485
2598
|
}
|
|
2599
|
+
// The cooldown gates a fresh engagement only, never an ongoing lean.
|
|
2600
|
+
if (!tilted.has(spot) && current !== spot && performance.now() - releasedAt < TILT_COOLDOWN_MS) return;
|
|
2601
|
+
if (current !== spot) {
|
|
2602
|
+
if (current !== null) release(current);
|
|
2603
|
+
const pending = settle.get(spot);
|
|
2604
|
+
if (pending !== undefined) {
|
|
2605
|
+
clearTimeout(pending);
|
|
2606
|
+
settle.delete(spot);
|
|
2607
|
+
}
|
|
2608
|
+
current = spot;
|
|
2609
|
+
}
|
|
2486
2610
|
frozen = false;
|
|
2487
|
-
paint(
|
|
2611
|
+
paint(spot, rect, event.clientX, event.clientY);
|
|
2488
2612
|
};
|
|
2489
2613
|
const onOut = (event) => {
|
|
2490
2614
|
if (current === null) return;
|
|
2491
|
-
const target = event.target;
|
|
2492
|
-
if (target !== null && typeof target.closest === 'function') {
|
|
2493
|
-
const spot = target.closest(TILT_SELECTOR);
|
|
2494
|
-
if (spot !== null && spot !== current) return;
|
|
2495
|
-
}
|
|
2496
2615
|
const rect = current.getBoundingClientRect();
|
|
2497
|
-
if (event.clientX >= rect.left && event.clientX <= rect.right
|
|
2616
|
+
if (event.clientX >= rect.left - TILT_RELEASE_PAD && event.clientX <= rect.right + TILT_RELEASE_PAD
|
|
2617
|
+
&& event.clientY >= rect.top - TILT_RELEASE_PAD && event.clientY <= rect.bottom + TILT_RELEASE_PAD) return;
|
|
2498
2618
|
release(current);
|
|
2499
2619
|
current = null;
|
|
2500
2620
|
};
|
|
@@ -2531,7 +2651,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2531
2651
|
if (!supportsWebgl2()) return startParticles();
|
|
2532
2652
|
// Which build is running, readable from the page (see README). One line in
|
|
2533
2653
|
// the console answers "is the bundle I just installed actually live?".
|
|
2534
|
-
try { window.__dshomeBuild = '2.0.
|
|
2654
|
+
try { window.__dshomeBuild = '2.0.86 glass-on noise-fix'; } catch (error) {}
|
|
2535
2655
|
const canvas = document.createElement('canvas');
|
|
2536
2656
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2537
2657
|
const style = canvas.style;
|
|
@@ -2573,17 +2693,6 @@ html[data-dshome-blur="off"] body{
|
|
|
2573
2693
|
// `attachFluidInteractions` is therefore NOT wired — it is kept in the
|
|
2574
2694
|
// file, unmodified, because re-enabling button ripples is one call, but
|
|
2575
2695
|
// nothing in this theme uses it today.
|
|
2576
|
-
const stopSpotter = startSpecularSpotter();
|
|
2577
|
-
const stopParallax = startSpecularParallax();
|
|
2578
|
-
const dispersion = startGlassDispersion({ hue: FLUID_HUES[dshomeColor()] });
|
|
2579
|
-
// Both recipes share one filter pair, so the recipe selects the scale:
|
|
2580
|
-
// 104 for liquid (visible edge refraction) and the untouched 60 for
|
|
2581
|
-
// frosted, which is what the pane has always used.
|
|
2582
|
-
const applyGlass = () => {
|
|
2583
|
-
dispersion.setRefraction(dsttGetGlass() === 'liquid' ? LIQUID_REF_SCALE : DEFAULT_REF_SCALE);
|
|
2584
|
-
};
|
|
2585
|
-
applyGlass();
|
|
2586
|
-
const unsubscribeGlass = dsttSubscribe(applyGlass);
|
|
2587
2696
|
// ── palette reconciliation ────────────────────────────────────────────
|
|
2588
2697
|
// One reader for BOTH inputs the palette depends on: the colour token on
|
|
2589
2698
|
// <body> and the dark marker. Reported symptom: "切主题模式背景不变 /
|
|
@@ -2608,7 +2717,6 @@ html[data-dshome-blur="off"] body{
|
|
|
2608
2717
|
};
|
|
2609
2718
|
const unsubscribeColor = subscribeColor((color) => {
|
|
2610
2719
|
syncFluid();
|
|
2611
|
-
try { dispersion.setTint(FLUID_HUES[color] ?? FLUID_HUES.green); } catch (error) {}
|
|
2612
2720
|
});
|
|
2613
2721
|
const unsubscribeDark = darkSync === null ? null : darkSync.subscribe(syncFluid);
|
|
2614
2722
|
// The colour driver writes the token as an attribute; watching it as well
|
|
@@ -2624,19 +2732,90 @@ html[data-dshome-blur="off"] body{
|
|
|
2624
2732
|
return () => {
|
|
2625
2733
|
unsubscribeColor();
|
|
2626
2734
|
if (unsubscribeDark !== null) unsubscribeDark();
|
|
2735
|
+
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2736
|
+
clearInterval(fluidSync);
|
|
2737
|
+
fluid.dispose();
|
|
2738
|
+
canvas.remove();
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
/**
|
|
2743
|
+
* The cursor-following half of the glass: the specular spotter (which marks
|
|
2744
|
+
* the surfaces the highlight may land on), the parallax that writes
|
|
2745
|
+
* `--dshome-spec-x/y` for the hovered surface, and the SVG edge refraction.
|
|
2746
|
+
*
|
|
2747
|
+
* These used to be mounted INSIDE {@link startAmbient}, which tied them to
|
|
2748
|
+
* the fluid layer: turning 「动态背景」 off -- which is exactly what the user
|
|
2749
|
+
* did to stop the iGPU squares -- or choosing ③ 自选背景, or landing on an
|
|
2750
|
+
* engine without WebGL2 (the particle fallback returns before them), silently
|
|
2751
|
+
* took the dialog's cursor-following highlight and its edge refraction with
|
|
2752
|
+
* it. Reported as "把我的对话框随着鼠标位置而变化的效果补回来". They belong to
|
|
2753
|
+
* the glass, not to the flow field, so they mount on their own and survive
|
|
2754
|
+
* every background recipe.
|
|
2755
|
+
*/
|
|
2756
|
+
function startGlassExtras() {
|
|
2757
|
+
const stopSpotter = startSpecularSpotter();
|
|
2758
|
+
const stopParallax = startSpecularParallax();
|
|
2759
|
+
/**
|
|
2760
|
+
* The refraction is OPT-IN, and `off` means "do not mount it at all".
|
|
2761
|
+
*
|
|
2762
|
+
* Two measured reasons, both from the user's machine (2.0.84, the first
|
|
2763
|
+
* build that mounted it since 1.43.6): the tint washed the whole composer
|
|
2764
|
+
* ("玻璃怎么变成凝胶了"), and the SVG filter inside `backdrop-filter` took the
|
|
2765
|
+
* BLUR down with it -- a `url()` that fails to render invalidates the whole
|
|
2766
|
+
* declaration, so the pane lost its frost and read as plain translucency
|
|
2767
|
+
* ("没有背景模糊效果"). A decorative filter must never be able to cost a
|
|
2768
|
+
* surface its glass, so the chain stays out of the DOM unless asked for, and
|
|
2769
|
+
* the switch covers all four surfaces rather than only the composer.
|
|
2770
|
+
*/
|
|
2771
|
+
const dispersion = dsttGetComposer() === 'off'
|
|
2772
|
+
? { setTint() {}, setRefraction() {}, dispose() {} }
|
|
2773
|
+
: startGlassDispersion({ hue: FLUID_HUES[dshomeColor()] });
|
|
2774
|
+
// Readable from the page, next to __dshomeBuild / __dshomeFluidFormat /
|
|
2775
|
+
// __dshomeTilt: "is the refraction in the DOM, and what is driving it" is
|
|
2776
|
+
// the first question any glass report needs answered.
|
|
2777
|
+
try {
|
|
2778
|
+
window.__dshomeDispersion = () => ({
|
|
2779
|
+
composer: dsttGetComposer(),
|
|
2780
|
+
mounted: document.documentElement.hasAttribute(ATTR),
|
|
2781
|
+
filters: document.querySelectorAll('svg filter').length,
|
|
2782
|
+
recipe: dsttGetGlass()
|
|
2783
|
+
});
|
|
2784
|
+
} catch (error) {}
|
|
2785
|
+
// Both recipes share one filter pair, so the recipe selects the scale:
|
|
2786
|
+
// 104 for liquid (visible edge refraction) and the untouched 60 for
|
|
2787
|
+
// frosted, which is what the pane has always used.
|
|
2788
|
+
const applyGlass = () => {
|
|
2789
|
+
dispersion.setRefraction(dsttGetGlass() === 'liquid' ? LIQUID_REF_SCALE : DEFAULT_REF_SCALE);
|
|
2790
|
+
};
|
|
2791
|
+
applyGlass();
|
|
2792
|
+
const unsubscribeGlass = dsttSubscribe(applyGlass);
|
|
2793
|
+
// The tint follows the DSTT colour token. Same belt-and-braces as the
|
|
2794
|
+
// fluid palette: subscribe AND watch the attribute the driver writes,
|
|
2795
|
+
// because the colour driver writes it before this mount exists.
|
|
2796
|
+
const syncTint = () => {
|
|
2797
|
+
try { dispersion.setTint(FLUID_HUES[dshomeColor()] ?? FLUID_HUES.green); } catch (error) {}
|
|
2798
|
+
};
|
|
2799
|
+
const unsubscribeColor = subscribeColor(syncTint);
|
|
2800
|
+
let tokenMirror = null;
|
|
2801
|
+
try {
|
|
2802
|
+
tokenMirror = new MutationObserver(syncTint);
|
|
2803
|
+
tokenMirror.observe(document.body, { attributes: true, attributeFilter: ['data-dshome-color'] });
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
tokenMirror = null;
|
|
2806
|
+
}
|
|
2807
|
+
return () => {
|
|
2808
|
+
unsubscribeColor();
|
|
2627
2809
|
unsubscribeGlass();
|
|
2628
2810
|
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2629
2811
|
dispersion.dispose();
|
|
2630
2812
|
stopParallax();
|
|
2631
2813
|
stopSpotter();
|
|
2632
|
-
clearInterval(fluidSync);
|
|
2633
|
-
fluid.dispose();
|
|
2634
|
-
canvas.remove();
|
|
2635
2814
|
};
|
|
2636
2815
|
}
|
|
2637
2816
|
|
|
2638
2817
|
function startParticles() {
|
|
2639
|
-
try { window.__dshomeBuild = '2.0.
|
|
2818
|
+
try { window.__dshomeBuild = '2.0.86 glass-on noise-fix'; } catch (error) {}
|
|
2640
2819
|
const canvas = document.createElement('canvas');
|
|
2641
2820
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2642
2821
|
const style = canvas.style;
|
|
@@ -4029,7 +4208,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4029
4208
|
// `origin` was this option's first id and is still accepted from a settings
|
|
4030
4209
|
// file written before the rename; it reads back as `wide`.
|
|
4031
4210
|
const DSTT_COMPOSER = ["narrow", "wide", "off"];
|
|
4032
|
-
const DSTT_COMPOSER_DEFAULT = "
|
|
4211
|
+
const DSTT_COMPOSER_DEFAULT = "off";
|
|
4033
4212
|
const DSTT_COMPOSER_LABELS = {
|
|
4034
4213
|
narrow: { zh: "窄 8px", en: "Narrow 8px" },
|
|
4035
4214
|
wide: { zh: "宽 16px", en: "Wide 16px" },
|
|
@@ -4943,6 +5122,18 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4943
5122
|
if (stop !== null) { try { stop(); } catch (error) {} }
|
|
4944
5123
|
};
|
|
4945
5124
|
}, 'deepseek-style-theme: fluid background');
|
|
5125
|
+
// The cursor-following half of the glass mounts ON ITS OWN, with no
|
|
5126
|
+
// dependence on the fluid layer: see startGlassExtras for why (turning
|
|
5127
|
+
// 「动态背景」 off, or ③ 自选背景, or a machine without WebGL2 used to take
|
|
5128
|
+
// the dialog's cursor-following highlight and the edge refraction with it).
|
|
5129
|
+
ctx.effect(() => {
|
|
5130
|
+
try {
|
|
5131
|
+
return startGlassExtras();
|
|
5132
|
+
} catch (error) {
|
|
5133
|
+
console.error('[dsh-deepseek-style-theme] glass extras failed to mount:', error);
|
|
5134
|
+
return () => {};
|
|
5135
|
+
}
|
|
5136
|
+
}, 'deepseek-style-theme: glass extras (specular + dispersion)');
|
|
4946
5137
|
// The composer's hover tilt is independent of the background: it must
|
|
4947
5138
|
// survive a machine without WebGL2, where startAmbient() falls back to
|
|
4948
5139
|
// the 2D particle field.
|