dsh-deepseek-style-theme 2.0.84 → 2.0.85
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 +155 -38
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -2378,6 +2378,19 @@ html[data-dshome-blur="off"] body{
|
|
|
2378
2378
|
// recipe. Both recipes tilt now, and because the liquid sheet used to be
|
|
2379
2379
|
// the only place carrying `transition: transform`, the motion carries
|
|
2380
2380
|
// its own transition (TILT_TRANSITION) so a recipe cannot lose it.
|
|
2381
|
+
// * 2.0.85 makes ENGAGEMENT GEOMETRIC and the gates honest. Engagement used
|
|
2382
|
+
// to need a `pointerover` whose target was inside the card; that is a
|
|
2383
|
+
// chain of assumptions about product markup (a wrapper with role=button,
|
|
2384
|
+
// a swapped card node, a portal, an overlay) which a theme cannot verify,
|
|
2385
|
+
// and failing it looks exactly like a broken effect -- reported as
|
|
2386
|
+
// "把我的对话框随着鼠标位置而变化的效果补回来". `spotAt()` decides from the
|
|
2387
|
+
// card's own rectangle on every pointermove, so a missed event costs one
|
|
2388
|
+
// frame instead of the whole effect; `controlAt()` remains the single hit
|
|
2389
|
+
// test, and only to answer "is a control under the pointer". The two
|
|
2390
|
+
// SILENT gates are reported now: `__dshomeTilt()` includes `reducedMotion`
|
|
2391
|
+
// (an OS setting that disables the effect and looks like a bug) and
|
|
2392
|
+
// `spots` (0 means the composer no longer matches TILT_SELECTOR), and the
|
|
2393
|
+
// reduced-motion case logs one info line instead of vanishing.
|
|
2381
2394
|
const TILT_SELECTOR = '[data-composer-card],.uV2eYG_card';
|
|
2382
2395
|
const TILT_MAX = 0.0175;
|
|
2383
2396
|
const TILT_PERSPECTIVE = 800;
|
|
@@ -2407,13 +2420,28 @@ html[data-dshome-blur="off"] body{
|
|
|
2407
2420
|
const tilted = new WeakSet();
|
|
2408
2421
|
const settle = new Map();
|
|
2409
2422
|
const reduced = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
2410
|
-
|
|
2411
|
-
//
|
|
2412
|
-
//
|
|
2413
|
-
//
|
|
2423
|
+
// Readable from the page: the effect is guarded several times over, so "it
|
|
2424
|
+
// is missing" is otherwise a guess about which guard ate it.
|
|
2425
|
+
// `__dshomeTilt()` answers that in one call -- including the two SILENT
|
|
2426
|
+
// gates, which is why `reduced` and `spots` are in here at all: an OS-level
|
|
2427
|
+
// "reduce motion" or a composer whose markup no longer matches
|
|
2428
|
+
// TILT_SELECTOR both look exactly like a broken effect.
|
|
2414
2429
|
try {
|
|
2415
|
-
window.__dshomeTilt = () => ({
|
|
2430
|
+
window.__dshomeTilt = () => ({
|
|
2431
|
+
recipe: dsttGetGlass(),
|
|
2432
|
+
reducedMotion: reduced,
|
|
2433
|
+
spots: document.querySelectorAll(TILT_SELECTOR).length,
|
|
2434
|
+
leaning: current !== null,
|
|
2435
|
+
frozen,
|
|
2436
|
+
engagements,
|
|
2437
|
+
sinceRelease: releasedAt === -Infinity ? null : Math.round(performance.now() - releasedAt)
|
|
2438
|
+
});
|
|
2416
2439
|
} catch (error) {}
|
|
2440
|
+
if (reduced) {
|
|
2441
|
+
// Never silent: this is the one gate a user cannot see in any setting.
|
|
2442
|
+
console.info('[dsh-deepseek-style-theme] composer tilt disabled: the system asks for reduced motion (prefers-reduced-motion: reduce)');
|
|
2443
|
+
return () => {};
|
|
2444
|
+
}
|
|
2417
2445
|
const release = (spot) => {
|
|
2418
2446
|
const pending = settle.get(spot);
|
|
2419
2447
|
if (pending !== undefined) {
|
|
@@ -2452,21 +2480,48 @@ html[data-dshome-blur="off"] body{
|
|
|
2452
2480
|
tilted.add(spot);
|
|
2453
2481
|
});
|
|
2454
2482
|
};
|
|
2483
|
+
/**
|
|
2484
|
+
* Which tilt spot contains a point, by RECTANGLE -- deliberately not by
|
|
2485
|
+
* walking `event.target` up to a selector. Engagement used to depend on a
|
|
2486
|
+
* `pointerover` whose target was inside the card, and that is a chain of
|
|
2487
|
+
* assumptions about the product's markup (a wrapper with role=button, a
|
|
2488
|
+
* swapped-in card node, a portal, an overlay) that a theme cannot verify.
|
|
2489
|
+
* The reported symptom of getting it wrong is indistinguishable from a
|
|
2490
|
+
* broken effect: the box just never leans. Geometry is the one thing the
|
|
2491
|
+
* theme can measure for itself.
|
|
2492
|
+
*/
|
|
2493
|
+
const spotAt = (clientX, clientY) => {
|
|
2494
|
+
for (const spot of document.querySelectorAll(TILT_SELECTOR)) {
|
|
2495
|
+
const rect = spot.getBoundingClientRect();
|
|
2496
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
2497
|
+
if (clientX < rect.left || clientX > rect.right || clientY < rect.top || clientY > rect.bottom) continue;
|
|
2498
|
+
return { spot, rect };
|
|
2499
|
+
}
|
|
2500
|
+
return null;
|
|
2501
|
+
};
|
|
2502
|
+
/** The topmost element at a point, used only to decide "is this a control". */
|
|
2503
|
+
const controlAt = (clientX, clientY) => {
|
|
2504
|
+
let node = null;
|
|
2505
|
+
try { node = document.elementFromPoint(clientX, clientY); } catch (error) { node = null; }
|
|
2506
|
+
if (node === null || typeof node.closest !== 'function') return false;
|
|
2507
|
+
return node.closest(TILT_CONTROLS) !== null;
|
|
2508
|
+
};
|
|
2455
2509
|
const onOver = (event) => {
|
|
2510
|
+
// Kept for the one thing it does better than a move: it fires the
|
|
2511
|
+
// moment the pointer arrives, before any movement. Everything it
|
|
2512
|
+
// decides is re-derived from geometry, so a missed event costs one
|
|
2513
|
+
// frame, not the effect.
|
|
2456
2514
|
const target = event.target;
|
|
2457
2515
|
if (target === null || typeof target.closest !== 'function') return;
|
|
2458
2516
|
const spot = target.closest(TILT_SELECTOR);
|
|
2459
2517
|
if (spot === null) return;
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
if (
|
|
2518
|
+
const rect = spot.getBoundingClientRect();
|
|
2519
|
+
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return;
|
|
2520
|
+
if (controlAt(event.clientX, event.clientY)) {
|
|
2463
2521
|
if (current === spot) frozen = true;
|
|
2464
2522
|
return;
|
|
2465
2523
|
}
|
|
2466
|
-
// The cooldown gates a fresh engagement only, never an ongoing lean.
|
|
2467
2524
|
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
2525
|
const pending = settle.get(spot);
|
|
2471
2526
|
if (pending !== undefined) {
|
|
2472
2527
|
clearTimeout(pending);
|
|
@@ -2477,22 +2532,38 @@ html[data-dshome-blur="off"] body{
|
|
|
2477
2532
|
paint(spot, rect, event.clientX, event.clientY);
|
|
2478
2533
|
};
|
|
2479
2534
|
const onMove = (event) => {
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2535
|
+
const hit = spotAt(event.clientX, event.clientY);
|
|
2536
|
+
if (hit === null) {
|
|
2537
|
+
// Off the card entirely: release only the spot that was leaning.
|
|
2538
|
+
if (current !== null) {
|
|
2539
|
+
release(current);
|
|
2540
|
+
current = null;
|
|
2541
|
+
}
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
const { spot, rect } = hit;
|
|
2545
|
+
if (controlAt(event.clientX, event.clientY)) {
|
|
2546
|
+
// A control is under the pointer: hold the lean exactly where it is
|
|
2547
|
+
// (releasing here is what used to feed the ~8 Hz hover loop).
|
|
2548
|
+
if (current === spot) frozen = true;
|
|
2484
2549
|
return;
|
|
2485
2550
|
}
|
|
2551
|
+
// The cooldown gates a fresh engagement only, never an ongoing lean.
|
|
2552
|
+
if (!tilted.has(spot) && current !== spot && performance.now() - releasedAt < TILT_COOLDOWN_MS) return;
|
|
2553
|
+
if (current !== spot) {
|
|
2554
|
+
if (current !== null) release(current);
|
|
2555
|
+
const pending = settle.get(spot);
|
|
2556
|
+
if (pending !== undefined) {
|
|
2557
|
+
clearTimeout(pending);
|
|
2558
|
+
settle.delete(spot);
|
|
2559
|
+
}
|
|
2560
|
+
current = spot;
|
|
2561
|
+
}
|
|
2486
2562
|
frozen = false;
|
|
2487
|
-
paint(
|
|
2563
|
+
paint(spot, rect, event.clientX, event.clientY);
|
|
2488
2564
|
};
|
|
2489
2565
|
const onOut = (event) => {
|
|
2490
2566
|
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
2567
|
const rect = current.getBoundingClientRect();
|
|
2497
2568
|
if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) return;
|
|
2498
2569
|
release(current);
|
|
@@ -2531,7 +2602,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2531
2602
|
if (!supportsWebgl2()) return startParticles();
|
|
2532
2603
|
// Which build is running, readable from the page (see README). One line in
|
|
2533
2604
|
// the console answers "is the bundle I just installed actually live?".
|
|
2534
|
-
try { window.__dshomeBuild = '2.0.
|
|
2605
|
+
try { window.__dshomeBuild = '2.0.85 glass-extras'; } catch (error) {}
|
|
2535
2606
|
const canvas = document.createElement('canvas');
|
|
2536
2607
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2537
2608
|
const style = canvas.style;
|
|
@@ -2573,17 +2644,6 @@ html[data-dshome-blur="off"] body{
|
|
|
2573
2644
|
// `attachFluidInteractions` is therefore NOT wired — it is kept in the
|
|
2574
2645
|
// file, unmodified, because re-enabling button ripples is one call, but
|
|
2575
2646
|
// 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
2647
|
// ── palette reconciliation ────────────────────────────────────────────
|
|
2588
2648
|
// One reader for BOTH inputs the palette depends on: the colour token on
|
|
2589
2649
|
// <body> and the dark marker. Reported symptom: "切主题模式背景不变 /
|
|
@@ -2608,7 +2668,6 @@ html[data-dshome-blur="off"] body{
|
|
|
2608
2668
|
};
|
|
2609
2669
|
const unsubscribeColor = subscribeColor((color) => {
|
|
2610
2670
|
syncFluid();
|
|
2611
|
-
try { dispersion.setTint(FLUID_HUES[color] ?? FLUID_HUES.green); } catch (error) {}
|
|
2612
2671
|
});
|
|
2613
2672
|
const unsubscribeDark = darkSync === null ? null : darkSync.subscribe(syncFluid);
|
|
2614
2673
|
// The colour driver writes the token as an attribute; watching it as well
|
|
@@ -2624,19 +2683,65 @@ html[data-dshome-blur="off"] body{
|
|
|
2624
2683
|
return () => {
|
|
2625
2684
|
unsubscribeColor();
|
|
2626
2685
|
if (unsubscribeDark !== null) unsubscribeDark();
|
|
2686
|
+
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2687
|
+
clearInterval(fluidSync);
|
|
2688
|
+
fluid.dispose();
|
|
2689
|
+
canvas.remove();
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
/**
|
|
2694
|
+
* The cursor-following half of the glass: the specular spotter (which marks
|
|
2695
|
+
* the surfaces the highlight may land on), the parallax that writes
|
|
2696
|
+
* `--dshome-spec-x/y` for the hovered surface, and the SVG edge refraction.
|
|
2697
|
+
*
|
|
2698
|
+
* These used to be mounted INSIDE {@link startAmbient}, which tied them to
|
|
2699
|
+
* the fluid layer: turning 「动态背景」 off -- which is exactly what the user
|
|
2700
|
+
* did to stop the iGPU squares -- or choosing ③ 自选背景, or landing on an
|
|
2701
|
+
* engine without WebGL2 (the particle fallback returns before them), silently
|
|
2702
|
+
* took the dialog's cursor-following highlight and its edge refraction with
|
|
2703
|
+
* it. Reported as "把我的对话框随着鼠标位置而变化的效果补回来". They belong to
|
|
2704
|
+
* the glass, not to the flow field, so they mount on their own and survive
|
|
2705
|
+
* every background recipe.
|
|
2706
|
+
*/
|
|
2707
|
+
function startGlassExtras() {
|
|
2708
|
+
const stopSpotter = startSpecularSpotter();
|
|
2709
|
+
const stopParallax = startSpecularParallax();
|
|
2710
|
+
const dispersion = startGlassDispersion({ hue: FLUID_HUES[dshomeColor()] });
|
|
2711
|
+
// Both recipes share one filter pair, so the recipe selects the scale:
|
|
2712
|
+
// 104 for liquid (visible edge refraction) and the untouched 60 for
|
|
2713
|
+
// frosted, which is what the pane has always used.
|
|
2714
|
+
const applyGlass = () => {
|
|
2715
|
+
dispersion.setRefraction(dsttGetGlass() === 'liquid' ? LIQUID_REF_SCALE : DEFAULT_REF_SCALE);
|
|
2716
|
+
};
|
|
2717
|
+
applyGlass();
|
|
2718
|
+
const unsubscribeGlass = dsttSubscribe(applyGlass);
|
|
2719
|
+
// The tint follows the DSTT colour token. Same belt-and-braces as the
|
|
2720
|
+
// fluid palette: subscribe AND watch the attribute the driver writes,
|
|
2721
|
+
// because the colour driver writes it before this mount exists.
|
|
2722
|
+
const syncTint = () => {
|
|
2723
|
+
try { dispersion.setTint(FLUID_HUES[dshomeColor()] ?? FLUID_HUES.green); } catch (error) {}
|
|
2724
|
+
};
|
|
2725
|
+
const unsubscribeColor = subscribeColor(syncTint);
|
|
2726
|
+
let tokenMirror = null;
|
|
2727
|
+
try {
|
|
2728
|
+
tokenMirror = new MutationObserver(syncTint);
|
|
2729
|
+
tokenMirror.observe(document.body, { attributes: true, attributeFilter: ['data-dshome-color'] });
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
tokenMirror = null;
|
|
2732
|
+
}
|
|
2733
|
+
return () => {
|
|
2734
|
+
unsubscribeColor();
|
|
2627
2735
|
unsubscribeGlass();
|
|
2628
2736
|
if (tokenMirror !== null) tokenMirror.disconnect();
|
|
2629
2737
|
dispersion.dispose();
|
|
2630
2738
|
stopParallax();
|
|
2631
2739
|
stopSpotter();
|
|
2632
|
-
clearInterval(fluidSync);
|
|
2633
|
-
fluid.dispose();
|
|
2634
|
-
canvas.remove();
|
|
2635
2740
|
};
|
|
2636
2741
|
}
|
|
2637
2742
|
|
|
2638
2743
|
function startParticles() {
|
|
2639
|
-
try { window.__dshomeBuild = '2.0.
|
|
2744
|
+
try { window.__dshomeBuild = '2.0.85 glass-extras'; } catch (error) {}
|
|
2640
2745
|
const canvas = document.createElement('canvas');
|
|
2641
2746
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2642
2747
|
const style = canvas.style;
|
|
@@ -4943,6 +5048,18 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4943
5048
|
if (stop !== null) { try { stop(); } catch (error) {} }
|
|
4944
5049
|
};
|
|
4945
5050
|
}, 'deepseek-style-theme: fluid background');
|
|
5051
|
+
// The cursor-following half of the glass mounts ON ITS OWN, with no
|
|
5052
|
+
// dependence on the fluid layer: see startGlassExtras for why (turning
|
|
5053
|
+
// 「动态背景」 off, or ③ 自选背景, or a machine without WebGL2 used to take
|
|
5054
|
+
// the dialog's cursor-following highlight and the edge refraction with it).
|
|
5055
|
+
ctx.effect(() => {
|
|
5056
|
+
try {
|
|
5057
|
+
return startGlassExtras();
|
|
5058
|
+
} catch (error) {
|
|
5059
|
+
console.error('[dsh-deepseek-style-theme] glass extras failed to mount:', error);
|
|
5060
|
+
return () => {};
|
|
5061
|
+
}
|
|
5062
|
+
}, 'deepseek-style-theme: glass extras (specular + dispersion)');
|
|
4946
5063
|
// The composer's hover tilt is independent of the background: it must
|
|
4947
5064
|
// survive a machine without WebGL2, where startAmbient() falls back to
|
|
4948
5065
|
// the 2D particle field.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deepseek-style-theme",
|
|
3
3
|
"description": "DeepSeek official-home style theme: WebGL fluid-flow background, liquid glass with SVG edge refraction, glass sidebar and composer, frosted header, running-subagent progress panel, and DeepSeek brand link for the dsh web GUI",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.85",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"deepseek",
|