dsh-deepseek-style-theme 2.0.85 → 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.
@@ -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 one recorded deviation (v2.0.74): the
27
- `precision mediump float;` qualifier in FLOW_SHADER and DISPLAY_SHADER was
28
- changed to `precision highp float;`.** The reason is a rendering defect, not a
29
- feature: under ANGLE/D3D11 on Intel integrated GPUs `mediump` is a real 16-bit
30
- float (`min16float`), and the display shader's noise --
31
- `fract(sin(dot(st, vec2(12.9898,78.233))) * 43758.5453123)` -- needs roughly 24
32
- bits of mantissa, so at 16 bits the hash collapses into constant plateaus and the
33
- noise lattice shows up as large drifting squares. WebGL2 guarantees highp in
34
- fragment shaders, and on GPUs that promoted mediump to 32 bits the change is a
35
- no-op. Every other line of every shader is unchanged.
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
- float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); }
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
- var TINT_OPACITY = 0.45;
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;
@@ -2392,11 +2421,23 @@ html[data-dshome-blur="off"] body{
2392
2421
  // `spots` (0 means the composer no longer matches TILT_SELECTOR), and the
2393
2422
  // reduced-motion case logs one info line instead of vanishing.
2394
2423
  const TILT_SELECTOR = '[data-composer-card],.uV2eYG_card';
2395
- const TILT_MAX = 0.0175;
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;
2396
2429
  const TILT_PERSPECTIVE = 800;
2397
- const TILT_SCALE = 1.01;
2430
+ const TILT_SCALE = 1.015;
2398
2431
  const TILT_SETTLE_MS = 240;
2399
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;
2400
2441
  /** The lean's own easing. Mirrors the liquid sheet's values, and is removed
2401
2442
  * together with the inline transform so nothing keeps animating afterwards. */
2402
2443
  const TILT_TRANSITION = 'transform .24s cubic-bezier(.2,.8,.2,1),background-color .18s ease,box-shadow .18s ease';
@@ -2534,10 +2575,17 @@ html[data-dshome-blur="off"] body{
2534
2575
  const onMove = (event) => {
2535
2576
  const hit = spotAt(event.clientX, event.clientY);
2536
2577
  if (hit === null) {
2537
- // Off the card entirely: release only the spot that was leaning.
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.
2538
2581
  if (current !== null) {
2539
- release(current);
2540
- 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
+ }
2541
2589
  }
2542
2590
  return;
2543
2591
  }
@@ -2565,7 +2613,8 @@ html[data-dshome-blur="off"] body{
2565
2613
  const onOut = (event) => {
2566
2614
  if (current === null) return;
2567
2615
  const rect = current.getBoundingClientRect();
2568
- if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) return;
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;
2569
2618
  release(current);
2570
2619
  current = null;
2571
2620
  };
@@ -2602,7 +2651,7 @@ html[data-dshome-blur="off"] body{
2602
2651
  if (!supportsWebgl2()) return startParticles();
2603
2652
  // Which build is running, readable from the page (see README). One line in
2604
2653
  // the console answers "is the bundle I just installed actually live?".
2605
- try { window.__dshomeBuild = '2.0.85 glass-extras'; } catch (error) {}
2654
+ try { window.__dshomeBuild = '2.0.86 glass-on noise-fix'; } catch (error) {}
2606
2655
  const canvas = document.createElement('canvas');
2607
2656
  canvas.setAttribute('data-dsh-deepseek-canvas', '');
2608
2657
  const style = canvas.style;
@@ -2707,7 +2756,32 @@ html[data-dshome-blur="off"] body{
2707
2756
  function startGlassExtras() {
2708
2757
  const stopSpotter = startSpecularSpotter();
2709
2758
  const stopParallax = startSpecularParallax();
2710
- const dispersion = startGlassDispersion({ hue: FLUID_HUES[dshomeColor()] });
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) {}
2711
2785
  // Both recipes share one filter pair, so the recipe selects the scale:
2712
2786
  // 104 for liquid (visible edge refraction) and the untouched 60 for
2713
2787
  // frosted, which is what the pane has always used.
@@ -2741,7 +2815,7 @@ html[data-dshome-blur="off"] body{
2741
2815
  }
2742
2816
 
2743
2817
  function startParticles() {
2744
- try { window.__dshomeBuild = '2.0.85 glass-extras'; } catch (error) {}
2818
+ try { window.__dshomeBuild = '2.0.86 glass-on noise-fix'; } catch (error) {}
2745
2819
  const canvas = document.createElement('canvas');
2746
2820
  canvas.setAttribute('data-dsh-deepseek-canvas', '');
2747
2821
  const style = canvas.style;
@@ -4134,7 +4208,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
4134
4208
  // `origin` was this option's first id and is still accepted from a settings
4135
4209
  // file written before the rename; it reads back as `wide`.
4136
4210
  const DSTT_COMPOSER = ["narrow", "wide", "off"];
4137
- const DSTT_COMPOSER_DEFAULT = "narrow";
4211
+ const DSTT_COMPOSER_DEFAULT = "off";
4138
4212
  const DSTT_COMPOSER_LABELS = {
4139
4213
  narrow: { zh: "窄 8px", en: "Narrow 8px" },
4140
4214
  wide: { zh: "宽 16px", en: "Wide 16px" },