@lemoncat7/dsh-theme-xiaohei 0.3.3 → 0.3.4

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 CHANGED
@@ -1442,12 +1442,10 @@ var XIAOHEI_SIDEBAR_CSS = `
1442
1442
  * Keep the toggle itself native. Opacity is applied to content, never the shell
1443
1443
  * (which also hosts fixed-position settings dialogs). */
1444
1444
  @media (prefers-reduced-motion: no-preference) {
1445
- #root [data-slot='sidebar'] > div[data-xiaohei-sidebar-reveal]:not([class*='_collapsed']) > :not(:first-child),
1446
- #root [data-slot='sidebar'] > div[data-xiaohei-sidebar-reveal]:not([class*='_collapsed']) > div:first-child > button:first-child {
1445
+ #root [data-xiaohei-sidebar-reveal] [data-xiaohei-sidebar-brand] {
1447
1446
  transition: opacity 100ms ease-out;
1448
1447
  }
1449
- #root [data-slot='sidebar'] > div[data-xiaohei-sidebar-reveal='waiting']:not([class*='_collapsed']) > :not(:first-child),
1450
- #root [data-slot='sidebar'] > div[data-xiaohei-sidebar-reveal='waiting']:not([class*='_collapsed']) > div:first-child > button:first-child {
1448
+ #root [data-xiaohei-sidebar-reveal='waiting'] [data-xiaohei-sidebar-brand] {
1451
1449
  opacity: 0 !important;
1452
1450
  visibility: hidden;
1453
1451
  transition: none;
@@ -1596,9 +1594,9 @@ html[data-xiaohei-sidebar-resizing] #dsh-theme-xiaohei\\/sidebar-glass {
1596
1594
  }
1597
1595
 
1598
1596
  /* Keep native rail/wide geometry and one consistent control material. */
1599
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > div:first-child button:last-child,
1600
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1601
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='New session'] {
1597
+ #root [data-slot='sidebar'] button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F']),
1598
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1599
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='New session'] {
1602
1600
  color: var(--xiaohei-sidebar-emphasis);
1603
1601
  transition:
1604
1602
  background-color var(--xiaohei-motion-fast) ease,
@@ -1606,12 +1604,12 @@ html[data-xiaohei-sidebar-resizing] #dsh-theme-xiaohei\\/sidebar-glass {
1606
1604
  transform var(--xiaohei-motion-fast) var(--xiaohei-motion-curve);
1607
1605
  }
1608
1606
 
1609
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > div:first-child button:last-child:hover {
1607
+ #root [data-slot='sidebar'] button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F']):hover {
1610
1608
  background: var(--xiaohei-sidebar-hover);
1611
1609
  }
1612
1610
 
1613
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1614
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='New session'] {
1611
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1612
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='New session'] {
1615
1613
  border: 1px solid var(--xiaohei-sidebar-control-edge);
1616
1614
  border-radius: var(--xiaohei-radius-control);
1617
1615
  background: var(--xiaohei-sidebar-control);
@@ -1622,14 +1620,14 @@ html[data-xiaohei-sidebar-resizing] #dsh-theme-xiaohei\\/sidebar-glass {
1622
1620
  text-align: start;
1623
1621
  }
1624
1622
 
1625
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD']:hover,
1626
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='New session']:hover {
1623
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD']:hover,
1624
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='New session']:hover {
1627
1625
  border-color: var(--xiaohei-sidebar-control-edge-hover);
1628
1626
  background: var(--xiaohei-sidebar-hover);
1629
1627
  }
1630
1628
 
1631
1629
  /* The footer uses a divider instead of another framed card. */
1632
- #root [data-slot='sidebar'] > div:not([class*='_collapsed'])
1630
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F']))
1633
1631
  > :has(> div > [data-slot='sidebar.footer.action']) {
1634
1632
  box-sizing: border-box;
1635
1633
  gap: 2px;
@@ -1702,8 +1700,8 @@ html[data-xiaohei-sidebar-resizing] #dsh-theme-xiaohei\\/sidebar-glass {
1702
1700
  backdrop-filter: none;
1703
1701
  }
1704
1702
 
1705
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1706
- #root [data-slot='sidebar'] > div:not([class*='_collapsed']) > button[aria-label='New session'] {
1703
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='\u65B0\u5EFA\u4F1A\u8BDD'],
1704
+ #root [data-slot='sidebar'] > div:has(button:is([aria-label='Collapse sidebar'], [aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F'])) > button[aria-label='New session'] {
1707
1705
  border-color: ButtonText;
1708
1706
  }
1709
1707
 
@@ -1727,13 +1725,13 @@ html {
1727
1725
  --xiaohei-sidebar-emphasis: #E1E6E7;
1728
1726
  --xiaohei-sidebar-icon: rgb(220 227 229 / 70%);
1729
1727
  --xiaohei-sidebar-hover: rgb(231 238 240 / 6.5%);
1730
- --xiaohei-sidebar-glass-fill: rgb(18 22 25 / 48%);
1728
+ --xiaohei-sidebar-glass-fill: rgb(18 22 25 / 25%);
1731
1729
  --xiaohei-sidebar-glass-solid: #1B2023;
1732
1730
  --xiaohei-sidebar-glass-edge: rgb(255 255 255 / 8%);
1733
1731
  --xiaohei-sidebar-glass-highlight: rgb(255 255 255 / 13%);
1734
1732
  --xiaohei-sidebar-glass-refraction: rgb(255 255 255 / 18%);
1735
1733
  --xiaohei-sidebar-glass-saturation: 102%;
1736
- --xiaohei-sidebar-glass-blur: 18px;
1734
+ --xiaohei-sidebar-glass-blur: 12px;
1737
1735
  --xiaohei-sidebar-glass-radius: 18px;
1738
1736
  --xiaohei-sidebar-control: rgb(231 238 240 / 7%);
1739
1737
  --xiaohei-sidebar-control-edge: rgb(231 238 240 / 8%);
@@ -1841,13 +1839,13 @@ html[data-xiaohei-appearance='light'] {
1841
1839
  --xiaohei-sidebar-emphasis: #3F454C;
1842
1840
  --xiaohei-sidebar-icon: rgb(48 54 61 / 68%);
1843
1841
  --xiaohei-sidebar-hover: rgb(48 54 61 / 6%);
1844
- --xiaohei-sidebar-glass-fill: rgb(247 245 242 / 58%);
1842
+ --xiaohei-sidebar-glass-fill: rgb(247 245 242 / 30%);
1845
1843
  --xiaohei-sidebar-glass-solid: #EDF1F3;
1846
1844
  --xiaohei-sidebar-glass-edge: rgb(37 45 51 / 9%);
1847
1845
  --xiaohei-sidebar-glass-highlight: rgb(255 255 255 / 64%);
1848
1846
  --xiaohei-sidebar-glass-refraction: rgb(255 255 255 / 18%);
1849
1847
  --xiaohei-sidebar-glass-saturation: 102%;
1850
- --xiaohei-sidebar-glass-blur: 18px;
1848
+ --xiaohei-sidebar-glass-blur: 12px;
1851
1849
  --xiaohei-sidebar-control: rgb(255 255 255 / 30%);
1852
1850
  --xiaohei-sidebar-control-edge: rgb(46 51 58 / 8%);
1853
1851
  --xiaohei-sidebar-control-edge-hover: rgb(46 51 58 / 13%);
@@ -3421,7 +3419,13 @@ function createSidebarReveal(doc) {
3421
3419
  let observer;
3422
3420
  let phase = "", width = 0, waiting = false;
3423
3421
  const attribute = "data-xiaohei-sidebar-reveal";
3424
- const readPhase = () => shell?.className.includes("_collapsed") ? "rail" : shell?.className.includes("_fading") ? "closing" : "wide";
3422
+ const readPhase = () => {
3423
+ const state = shell?.closest("[data-sidebar-collapsed]")?.getAttribute("data-sidebar-collapsed");
3424
+ if (state === "true") return "rail";
3425
+ if (state === "false") return "wide";
3426
+ if (shell?.querySelector("button[aria-label='Collapse sidebar'], button[aria-label='\u6536\u8D77\u4FA7\u8FB9\u680F']")) return "wide";
3427
+ return "rail";
3428
+ };
3425
3429
  const apply3 = () => {
3426
3430
  if (!shell) return;
3427
3431
  const target = Number.parseFloat(shell.style.width);
@@ -3447,7 +3451,7 @@ function createSidebarReveal(doc) {
3447
3451
  phase = readPhase();
3448
3452
  if (!shell) return;
3449
3453
  observer = new doc.defaultView.MutationObserver(changed);
3450
- observer.observe(shell, { attributes: true, attributeFilter: ["class", "style"] });
3454
+ observer.observe(column, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-sidebar-collapsed", "aria-label", "style"] });
3451
3455
  apply3();
3452
3456
  },
3453
3457
  resize(nextWidth) {
@@ -3719,8 +3723,9 @@ var CHARACTER_MOTION = { "peek": { "light": { "layers": { "ear0": { "box": [17.5
3719
3723
 
3720
3724
  // src/scene/character-motion-clock.ts
3721
3725
  function createCharacterMotionClock(clock, changed, random = Math.random) {
3722
- let timer, disposed = false, turn = 0, active = "";
3726
+ let timer, disposed = false, active = "";
3723
3727
  let clips;
3728
+ let blinksSinceEar = 0, nextEar = 0;
3724
3729
  const clear = () => {
3725
3730
  if (timer !== void 0) clock.clearTimeout(timer);
3726
3731
  timer = void 0;
@@ -3745,7 +3750,7 @@ function createCharacterMotionClock(clock, changed, random = Math.random) {
3745
3750
  rest();
3746
3751
  return;
3747
3752
  }
3748
- timer = clock.setTimeout(step, 1e3 / 30);
3753
+ timer = clock.setTimeout(step, 1e3 / 60);
3749
3754
  };
3750
3755
  step();
3751
3756
  }
@@ -3753,7 +3758,11 @@ function createCharacterMotionClock(clock, changed, random = Math.random) {
3753
3758
  timer = void 0;
3754
3759
  if (!clips || disposed) return;
3755
3760
  const gestures = Object.keys(clips).filter((name) => name !== "blink" && name !== "attention");
3756
- play(turn++ % 2 === 0 && clips.blink || !gestures.length ? "blink" : gestures[Math.floor(random() * gestures.length)]);
3761
+ const ears = gestures.filter((name) => name.startsWith("ear"));
3762
+ const action = ears.length && blinksSinceEar >= 2 ? ears[nextEar++ % ears.length] : random() < 0.72 && clips.blink || !gestures.length ? "blink" : gestures[Math.floor(random() * gestures.length)];
3763
+ if (action.startsWith("ear")) blinksSinceEar = 0;
3764
+ else blinksSinceEar++;
3765
+ play(action);
3757
3766
  }
3758
3767
  return {
3759
3768
  setClips(next) {
@@ -3761,6 +3770,8 @@ function createCharacterMotionClock(clock, changed, random = Math.random) {
3761
3770
  clear();
3762
3771
  clips = next;
3763
3772
  active = "";
3773
+ blinksSinceEar = 0;
3774
+ nextEar = 0;
3764
3775
  changed(void 0, 0);
3765
3776
  if (clips) timer = clock.setTimeout(start, 1200 + random() * 800);
3766
3777
  },
@@ -3776,6 +3787,206 @@ function createCharacterMotionClock(clock, changed, random = Math.random) {
3776
3787
  };
3777
3788
  }
3778
3789
 
3790
+ // src/scene/character-expression.ts
3791
+ var smooth = (t) => {
3792
+ const x = Math.max(0, Math.min(1, t));
3793
+ return x * x * (3 - 2 * x);
3794
+ };
3795
+ function blinkClosure(progress) {
3796
+ if (progress <= 0 || progress >= 1) return 0;
3797
+ if (progress < 0.3) return smooth(progress / 0.3);
3798
+ if (progress < 0.4) return 1;
3799
+ return 1 - smooth((progress - 0.4) / 0.6);
3800
+ }
3801
+ function attentionBlink(progress) {
3802
+ return blinkClosure((progress - 0.12) / 0.16);
3803
+ }
3804
+
3805
+ // src/scene/character-eye-rig.ts
3806
+ var EYE_RIGS = {
3807
+ seated: [
3808
+ { left: [285, 223], right: [326, 224], upper: [302, 188], lower: [310, 276], iris: [309, 230, 13, 20, 0], skin: [315, 258] },
3809
+ { left: [373, 221], right: [421, 226], upper: [387, 186], lower: [393, 277], iris: [393, 228, 15, 22, 0], skin: [391, 261] }
3810
+ ],
3811
+ peek: [
3812
+ { left: [80, 214], right: [119, 221], upper: [103, 186], lower: [91, 267], iris: [102, 225, 14, 20, 0.25], skin: [110, 253] },
3813
+ { left: [171, 252], right: [210, 265], upper: [195, 221], lower: [175, 300], iris: [186, 260, 15, 20, 0.4], skin: [186, 288] }
3814
+ ],
3815
+ chin: [
3816
+ { left: [252, 252], right: [290, 247], upper: [266, 223], lower: [287, 297], iris: [278, 258, 14, 19, -0.3], skin: [291, 285] },
3817
+ { left: [339, 234], right: [387, 227], upper: [358, 196], lower: [365, 280], iris: [361, 235, 15, 21, -0.25], skin: [367, 269] }
3818
+ ],
3819
+ halfpeek: [
3820
+ { left: [89, 390], right: [159, 395], upper: [116, 337], lower: [110, 471], iris: [116, 401, 23, 30, 0.22], skin: [117, 445] }
3821
+ ]
3822
+ };
3823
+ function clampGaze(x, y) {
3824
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return [0, 0];
3825
+ const length = Math.max(1, Math.hypot(x, y));
3826
+ return [x / length, y / length];
3827
+ }
3828
+ function eyelidControls(eye2, closure) {
3829
+ const t = Math.max(0, Math.min(1, closure));
3830
+ const cornerY = (eye2.left[1] + eye2.right[1]) / 2;
3831
+ const drop = (eye2.lower[1] - cornerY) * 0.24;
3832
+ const middle = [(eye2.left[0] + eye2.right[0]) / 2, cornerY + drop + (eye2.right[0] - eye2.left[0]) * 0.18];
3833
+ const lerp2 = (p) => [p[0] + (middle[0] - p[0]) * t, p[1] + (middle[1] - p[1]) * t];
3834
+ return { upper: lerp2(eye2.upper), lower: lerp2(eye2.lower), left: [eye2.left[0], eye2.left[1] + drop * t], right: [eye2.right[0], eye2.right[1] + drop * t] };
3835
+ }
3836
+
3837
+ // src/scene/character-eye-material.ts
3838
+ function lidY(eye2, control, x) {
3839
+ let low = 0, high = 1;
3840
+ for (let i = 0; i < 14; i++) {
3841
+ const t2 = (low + high) / 2, s2 = 1 - t2;
3842
+ if (s2 * s2 * eye2.left[0] + 2 * s2 * t2 * control[0] + t2 * t2 * eye2.right[0] < x) low = t2;
3843
+ else high = t2;
3844
+ }
3845
+ const t = (low + high) / 2, s = 1 - t;
3846
+ return s * s * eye2.left[1] + 2 * s * t * control[1] + t * t * eye2.right[1];
3847
+ }
3848
+ function prepareEyeMaterial(doc, source, eye2) {
3849
+ const left = Math.floor(eye2.left[0] - 9), right = Math.ceil(eye2.right[0] + 9);
3850
+ const top = Math.floor(Math.min(eye2.upper[1], eye2.left[1], eye2.right[1]) - 5);
3851
+ const bottom = Math.ceil(Math.max(eye2.lower[1], eye2.left[1], eye2.right[1]) + 5);
3852
+ const width = right - left, height = bottom - top;
3853
+ const canvas = () => {
3854
+ const c = doc.createElement("canvas");
3855
+ c.width = width;
3856
+ c.height = height;
3857
+ return c;
3858
+ };
3859
+ const ink = canvas(), skin = canvas(), mask = canvas();
3860
+ const ic = ink.getContext("2d"), sc = skin.getContext("2d"), mc = mask.getContext("2d");
3861
+ const pixels = source.getImageData(0, 0, source.canvas.width, source.canvas.height);
3862
+ const pixel = (x, y) => {
3863
+ const offset = (Math.max(0, Math.min(pixels.height - 1, Math.round(y / 2))) * pixels.width + Math.max(0, Math.min(pixels.width - 1, Math.round(x / 2)))) * 4;
3864
+ return [pixels.data[offset], pixels.data[offset + 1], pixels.data[offset + 2]];
3865
+ };
3866
+ const ref = pixel(...eye2.skin), output = ic.createImageData(width, height), plate = sc.createImageData(width, height);
3867
+ const isSkin = (c) => c.every((v, i) => Math.abs(v - ref[i]) < 25);
3868
+ const skinAt = (x, y, c) => {
3869
+ if (isSkin(c)) return c;
3870
+ const sum = [ref[0] * 0.05, ref[1] * 0.05, ref[2] * 0.05];
3871
+ let weight = 0.05;
3872
+ for (const radius of [6, 12, 20, 30]) for (let i = 0; i < 8; i++) {
3873
+ const p = pixel(x + Math.cos(i * Math.PI / 4) * radius, y + Math.sin(i * Math.PI / 4) * radius);
3874
+ if (!isSkin(p)) continue;
3875
+ const w = 1 / radius;
3876
+ weight += w;
3877
+ for (let k = 0; k < 3; k++) sum[k] += p[k] * w;
3878
+ }
3879
+ return sum.map((v) => Math.round(v / weight));
3880
+ };
3881
+ for (let x = 0; x < width; x++) {
3882
+ const ax = left + x, upper = lidY(eye2, eye2.upper, ax);
3883
+ for (let y = 0; y < height; y++) {
3884
+ const ay = top + y, c = pixel(ax, ay), index = (y * width + x) * 4;
3885
+ const darkness = 1 - (c[0] + c[1] + c[2]) / Math.max(1, ref[0] + ref[1] + ref[2]);
3886
+ const green = c[1] > c[0] * 1.15 && c[1] > c[2] * 1.12;
3887
+ const inBand = ay >= upper - 12 && ay <= upper + 3;
3888
+ const alpha = inBand && !green ? Math.max(0, Math.min(1, (darkness - 0.3) / 0.35)) : 0;
3889
+ output.data.set([...c, Math.round(alpha * 255)], index);
3890
+ plate.data.set([...skinAt(ax, ay, c), 255], index);
3891
+ }
3892
+ }
3893
+ ic.putImageData(output, 0, 0);
3894
+ sc.putImageData(plate, 0, 0);
3895
+ const inkMask = mc.createImageData(width, height);
3896
+ for (let i = 0; i < output.data.length; i += 4) inkMask.data[i + 3] = output.data[i + 3] > 4 ? 255 : 0;
3897
+ mc.putImageData(inkMask, 0, 0);
3898
+ const expanded = canvas(), ec = expanded.getContext("2d");
3899
+ for (const [dx, dy] of [[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1]]) ec.drawImage(mask, dx, dy);
3900
+ mc.drawImage(expanded, 0, 0);
3901
+ mc.translate(-left, -top);
3902
+ mc.beginPath();
3903
+ mc.moveTo(eye2.left[0] - 7, eye2.left[1]);
3904
+ mc.quadraticCurveTo(eye2.upper[0], eye2.upper[1] - 4, eye2.right[0] + 7, eye2.right[1]);
3905
+ mc.quadraticCurveTo(eye2.lower[0], eye2.lower[1] + 20, eye2.left[0] - 7, eye2.left[1]);
3906
+ mc.closePath();
3907
+ mc.fill();
3908
+ sc.globalCompositeOperation = "destination-in";
3909
+ sc.filter = "blur(0.65px)";
3910
+ sc.drawImage(mask, 0, 0);
3911
+ sc.filter = "none";
3912
+ return { ink, skin, left, top, width, height };
3913
+ }
3914
+
3915
+ // src/scene/character-eye-renderer.ts
3916
+ function createEyeRenderer(ctx, body, eyes) {
3917
+ const doc = ctx.canvas.ownerDocument;
3918
+ const source = doc.createElement("canvas");
3919
+ source.width = body.width;
3920
+ source.height = body.height;
3921
+ const sample = source.getContext("2d", { willReadFrequently: true });
3922
+ sample.drawImage(body, 0, 0);
3923
+ const color = (p) => {
3924
+ const data = sample.getImageData(Math.round(p[0] / 2), Math.round(p[1] / 2), 1, 1).data;
3925
+ return [data[0], data[1], data[2]];
3926
+ };
3927
+ const rgb = (c) => `rgb(${c.map((v) => Math.round(v)).join(" ")})`;
3928
+ const prepared = eyes.map((eye2) => {
3929
+ const [cx, cy, rx, ry, angle] = eye2.iris;
3930
+ const iris = doc.createElement("canvas");
3931
+ iris.width = Math.ceil(rx * 2 + 4);
3932
+ iris.height = Math.ceil(ry * 2 + 4);
3933
+ const ic = iris.getContext("2d");
3934
+ ic.translate(iris.width / 2, iris.height / 2);
3935
+ ic.rotate(angle);
3936
+ ic.beginPath();
3937
+ ic.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2);
3938
+ ic.clip();
3939
+ ic.rotate(-angle);
3940
+ ic.drawImage(body, 0, 0, body.width, body.height, -cx, -cy, body.width * 2, body.height * 2);
3941
+ return { eye: eye2, iris, skin: color(eye2.skin), material: prepareEyeMaterial(doc, sample, eye2) };
3942
+ });
3943
+ function aperture(eye2, closure) {
3944
+ const { upper, lower, left, right } = eyelidControls(eye2, closure);
3945
+ ctx.beginPath();
3946
+ ctx.moveTo(...left);
3947
+ ctx.quadraticCurveTo(...upper, ...right);
3948
+ ctx.quadraticCurveTo(...lower, ...left);
3949
+ ctx.closePath();
3950
+ }
3951
+ return (gaze, closure) => {
3952
+ const [gx, gy] = clampGaze(...gaze);
3953
+ ctx.save();
3954
+ ctx.scale(0.5, 0.5);
3955
+ for (const { eye: eye2, iris, skin, material } of prepared) {
3956
+ const [cx, cy, rx, ry] = eye2.iris;
3957
+ ctx.save();
3958
+ ctx.drawImage(material.skin, material.left, material.top);
3959
+ ctx.save();
3960
+ aperture(eye2, 0);
3961
+ ctx.clip();
3962
+ if (closure < 0.999) {
3963
+ ctx.save();
3964
+ aperture(eye2, closure);
3965
+ ctx.clip();
3966
+ const shade = ctx.createLinearGradient(0, cy - ry, 0, cy + ry);
3967
+ const brightness = (skin[0] + skin[1] + skin[2]) / 765;
3968
+ const white = brightness > 0.65 ? [255, 253, 241] : [166, 185, 193];
3969
+ shade.addColorStop(0, rgb(white.map((c) => c * 0.83)));
3970
+ shade.addColorStop(0.5, rgb(white));
3971
+ ctx.fillStyle = shade;
3972
+ ctx.fillRect(cx - 70, cy - 70, 140, 140);
3973
+ const dx = gx * rx * 0.26, dy = gy * ry * 0.13;
3974
+ ctx.drawImage(iris, cx - iris.width / 2 + dx, cy - iris.height / 2 + dy);
3975
+ ctx.restore();
3976
+ }
3977
+ ctx.restore();
3978
+ const lids = eyelidControls(eye2, closure);
3979
+ for (let x = 0; x < material.width; x++) {
3980
+ const ax = material.left + x;
3981
+ const dy = lidY({ ...eye2, left: lids.left, right: lids.right }, lids.upper, ax) - lidY(eye2, eye2.upper, ax);
3982
+ ctx.drawImage(material.ink, x, 0, 1, material.height, ax, material.top + dy, 1, material.height);
3983
+ }
3984
+ ctx.restore();
3985
+ }
3986
+ ctx.restore();
3987
+ };
3988
+ }
3989
+
3779
3990
  // src/scene/character-rig.ts
3780
3991
  var TAIL_JOINTS = [
3781
3992
  [284, 328],
@@ -3786,6 +3997,12 @@ var TAIL_JOINTS = [
3786
3997
  [307, 448],
3787
3998
  [319, 464]
3788
3999
  ];
4000
+ function earOffset(y, top, root, angle) {
4001
+ const span = Math.max(1, root - top);
4002
+ const weight = Math.max(0, Math.min(1, (root - y) / span));
4003
+ if (weight === 0) return 0;
4004
+ return Math.sin(angle) * Math.min(20, span * 0.55) * weight * weight;
4005
+ }
3789
4006
  function solveBones(rest, angles) {
3790
4007
  return rest.reduce((out, p, i) => {
3791
4008
  const parent = out[i - 1];
@@ -3826,11 +4043,13 @@ function skinVertex(v, joints, rest = TAIL_JOINTS) {
3826
4043
  function rigAngles(progress, count, tail) {
3827
4044
  const t = Math.max(0, Math.min(1, progress));
3828
4045
  const envelope = Math.sin(Math.PI * t) ** 2;
3829
- return Array.from({ length: count }, (_, i) => envelope * (tail ? i ? 0.12 : 0 : 0.22) * Math.sin(t * Math.PI * (tail ? 3 : 4) - i * (tail ? 0.65 : 1.1)));
4046
+ return Array.from({ length: count }, (_, i) => envelope * (tail ? i ? 0.12 : 0 : 0.32) * Math.sin(t * Math.PI * (tail ? 3 : 4) - i * (tail ? 0.65 : 1.1)));
3830
4047
  }
3831
4048
 
3832
4049
  // src/scene/character-rig-renderer.ts
3833
- function createRigRenderer(ctx, rig, images) {
4050
+ function createRigRenderer(ctx, rig, images, eyes = []) {
4051
+ const body = images.get(rig.layers.body.src);
4052
+ const drawEyes = body ? createEyeRenderer(ctx, body, eyes) : void 0;
3834
4053
  const tail = rig.layers.tail;
3835
4054
  const vertices = [], triangles = [];
3836
4055
  if (tail) {
@@ -3884,24 +4103,67 @@ function createRigRenderer(ctx, rig, images) {
3884
4103
  ctx.restore();
3885
4104
  }
3886
4105
  }
3887
- return (action, progress) => {
4106
+ return (action, progress, gaze = [0, 0]) => {
3888
4107
  ctx.clearRect(0, 0, rig.size[0], rig.size[1]);
3889
- const ears = rigAngles(progress, rig.ears.length, false);
3890
- rig.ears.forEach(([x, y], i) => {
3891
- ctx.save();
3892
- ctx.translate(x, y);
3893
- ctx.rotate(action === "attention" || action === "ear" + i ? ears[i] : 0);
3894
- ctx.translate(-x, -y);
3895
- layer("ear" + i);
3896
- ctx.restore();
4108
+ const earProgress = action === "attention" ? Math.max(0, Math.min(1, (progress - 0.03) / 0.65)) : progress;
4109
+ const ears = rigAngles(earProgress, rig.ears.length, false);
4110
+ rig.ears.forEach(([, root], i) => {
4111
+ const part = rig.layers["ear" + i], image = part && images.get(part.src);
4112
+ if (!part || !image) return;
4113
+ const angle = action === "attention" || action === "ear" + i ? ears[i] : 0;
4114
+ if (Math.abs(angle) < 1e-8) {
4115
+ layer("ear" + i);
4116
+ return;
4117
+ }
4118
+ const [x, top, right, bottom] = part.box;
4119
+ for (let row = 0; row < image.height; row++) {
4120
+ const y = top + row * (bottom - top) / image.height;
4121
+ ctx.drawImage(image, 0, row, image.width, 1, x + earOffset(y, top, root, angle), y, right - x, (bottom - top) / image.height);
4122
+ }
3897
4123
  });
3898
4124
  layer("body");
3899
- drawTail(action === "tail" || action === "attention" ? progress : 0);
3900
- if (action === "blink") {
3901
- ctx.save();
3902
- ctx.globalAlpha = Math.min(1, Math.sin(Math.PI * progress) * 2);
3903
- layer("blink");
3904
- ctx.restore();
4125
+ drawTail(action === "attention" ? Math.max(0, Math.min(1, (progress - 0.22) / 0.78)) : action === "tail" ? progress : 0);
4126
+ const closure = action === "blink" ? blinkClosure(progress) : action === "attention" ? attentionBlink(progress) : 0;
4127
+ drawEyes?.(gaze, closure);
4128
+ };
4129
+ }
4130
+
4131
+ // src/scene/character-gaze.ts
4132
+ function createCharacterGaze(clock, changed) {
4133
+ let value = [0, 0], target = [0, 0], frame, last = 0, disposed = false;
4134
+ function step(now) {
4135
+ frame = void 0;
4136
+ if (disposed) return;
4137
+ const dt = Math.min(64, Math.max(0, now - last));
4138
+ last = now;
4139
+ const amount = 1 - Math.exp(-dt / 95);
4140
+ value = [value[0] + (target[0] - value[0]) * amount, value[1] + (target[1] - value[1]) * amount];
4141
+ const settled = Math.hypot(value[0] - target[0], value[1] - target[1]) < 1e-3;
4142
+ if (settled) value = target;
4143
+ changed(value);
4144
+ if (!settled) frame = clock.requestAnimationFrame(step);
4145
+ }
4146
+ function stop() {
4147
+ if (frame !== void 0) clock.cancelAnimationFrame(frame);
4148
+ frame = void 0;
4149
+ }
4150
+ return {
4151
+ target(x, y) {
4152
+ if (disposed) return;
4153
+ target = clampGaze(x, y);
4154
+ if (frame === void 0 && Math.hypot(value[0] - target[0], value[1] - target[1]) >= 1e-3) {
4155
+ last = clock.performance.now();
4156
+ frame = clock.requestAnimationFrame(step);
4157
+ }
4158
+ },
4159
+ reset() {
4160
+ stop();
4161
+ target = value = [0, 0];
4162
+ changed(value);
4163
+ },
4164
+ dispose() {
4165
+ disposed = true;
4166
+ stop();
3905
4167
  }
3906
4168
  };
3907
4169
  }
@@ -3913,6 +4175,23 @@ function createCharacterMotion(doc) {
3913
4175
  let disposed = false, part, rig;
3914
4176
  let canvas, render;
3915
4177
  let pose2 = "", appearance = "", revision = 0, nearby = false, lastPointer = 0;
4178
+ let attentionTimer, lastAttention = -Infinity;
4179
+ let gazePoint = [0, 0], activeAction, activeProgress = 0;
4180
+ let bounds;
4181
+ const paint = () => {
4182
+ if (render && part) {
4183
+ render(activeAction, activeProgress, gazePoint);
4184
+ part.dataset.motion = activeAction ?? "rig-rest";
4185
+ }
4186
+ };
4187
+ const gaze = createCharacterGaze(win, (point) => {
4188
+ gazePoint = point;
4189
+ paint();
4190
+ });
4191
+ const cancelAttention = () => {
4192
+ if (attentionTimer !== void 0) win.clearTimeout(attentionTimer);
4193
+ attentionTimer = void 0;
4194
+ };
3916
4195
  function decode(src) {
3917
4196
  if (loading.has(src)) return loading.get(src);
3918
4197
  if (images.has(src)) return Promise.resolve();
@@ -3927,8 +4206,9 @@ function createCharacterMotion(doc) {
3927
4206
  }
3928
4207
  const clock = createCharacterMotionClock(win, (action, progress) => {
3929
4208
  if (!canvas || !part || !render) return;
3930
- render(action, progress);
3931
- part.dataset.motion = action ?? "rig-rest";
4209
+ activeAction = action;
4210
+ activeProgress = progress;
4211
+ paint();
3932
4212
  });
3933
4213
  function update() {
3934
4214
  if (disposed) return;
@@ -3936,8 +4216,13 @@ function createCharacterMotion(doc) {
3936
4216
  const moving = doc.documentElement.hasAttribute("data-xiaohei-sidebar-resizing");
3937
4217
  const next = !doc.hidden && !reduced.matches && !moving && part ? CHARACTER_MOTION[pose2]?.[mode] : void 0;
3938
4218
  if (next === rig && appearance === mode) return;
3939
- clock.setClips(void 0);
4219
+ cancelAttention();
3940
4220
  render = void 0;
4221
+ clock.setClips(void 0);
4222
+ gaze.reset();
4223
+ bounds = void 0;
4224
+ activeAction = void 0;
4225
+ activeProgress = 0;
3941
4226
  const current = ++revision;
3942
4227
  canvas?.remove();
3943
4228
  canvas = void 0;
@@ -3959,9 +4244,9 @@ function createCharacterMotion(doc) {
3959
4244
  return;
3960
4245
  }
3961
4246
  currentPart.append(canvas);
3962
- render = createRigRenderer(ctx, next, images);
4247
+ render = createRigRenderer(ctx, next, images, EYE_RIGS[pose2]);
3963
4248
  clock.setClips({
3964
- blink: { duration: 330 },
4249
+ blink: { duration: 280 },
3965
4250
  ...Object.fromEntries(next.ears.map((_, i) => ["ear" + i, { duration: 1050 }])),
3966
4251
  ...next.layers.tail ? { tail: { duration: 2400 } } : {},
3967
4252
  attention: { duration: 2400 }
@@ -3971,14 +4256,48 @@ function createCharacterMotion(doc) {
3971
4256
  const pointer = (event) => {
3972
4257
  if (!part || !render || event.pointerType !== "mouse") return;
3973
4258
  const now = win.performance.now();
3974
- if (now - lastPointer < 100) return;
3975
- lastPointer = now;
3976
- const rect = part.getBoundingClientRect();
4259
+ if (!bounds || now - lastPointer >= 100) {
4260
+ bounds = part.getBoundingClientRect();
4261
+ lastPointer = now;
4262
+ }
4263
+ const rect = bounds, eyes = EYE_RIGS[pose2];
4264
+ if (rig && eyes?.length) {
4265
+ const scale = Math.min(rect.width / rig.size[0], rect.height / rig.size[1]);
4266
+ const eyeX = eyes.reduce((sum, eye2) => sum + eye2.iris[0], 0) / eyes.length / 2;
4267
+ const eyeY = eyes.reduce((sum, eye2) => sum + eye2.iris[1], 0) / eyes.length / 2;
4268
+ const cx = rect.left + eyeX * scale;
4269
+ const cy = rect.top + (rect.height - rig.size[1] * scale) / 2 + eyeY * scale;
4270
+ gaze.target((event.clientX - cx) / Math.max(220, win.innerWidth * 0.32), (event.clientY - cy) / Math.max(180, win.innerHeight * 0.32));
4271
+ }
3977
4272
  const near = event.clientX >= rect.left - 70 && event.clientX <= rect.right + 70 && event.clientY >= rect.top - 50 && event.clientY <= rect.bottom + 50;
3978
- if (near && !nearby) clock.attention();
4273
+ if (!near) cancelAttention();
4274
+ if (near && !nearby && now - lastAttention > 6e3) {
4275
+ const current = revision;
4276
+ attentionTimer = win.setTimeout(() => {
4277
+ attentionTimer = void 0;
4278
+ if (!disposed && nearby && current === revision) {
4279
+ lastAttention = win.performance.now();
4280
+ clock.attention();
4281
+ }
4282
+ }, 180);
4283
+ }
3979
4284
  nearby = near;
3980
4285
  };
3981
4286
  reduced.addEventListener("change", update);
4287
+ const neutral = () => {
4288
+ gaze.target(0, 0);
4289
+ nearby = false;
4290
+ cancelAttention();
4291
+ };
4292
+ const leave = (event) => {
4293
+ if (!event.relatedTarget) neutral();
4294
+ };
4295
+ const resize = () => {
4296
+ bounds = void 0;
4297
+ };
4298
+ doc.addEventListener("pointerout", leave);
4299
+ win.addEventListener("blur", neutral);
4300
+ win.addEventListener("resize", resize);
3982
4301
  doc.addEventListener("visibilitychange", update);
3983
4302
  doc.addEventListener("pointermove", pointer, { passive: true });
3984
4303
  const observer = new win.MutationObserver(update);
@@ -3986,9 +4305,12 @@ function createCharacterMotion(doc) {
3986
4305
  return {
3987
4306
  setPose(nextPart, nextPose) {
3988
4307
  if (nextPart !== part || pose2 !== nextPose) {
4308
+ cancelAttention();
3989
4309
  revision++;
3990
- clock.setClips(void 0);
3991
4310
  render = void 0;
4311
+ clock.setClips(void 0);
4312
+ gaze.reset();
4313
+ bounds = void 0;
3992
4314
  canvas?.remove();
3993
4315
  canvas = void 0;
3994
4316
  rig = void 0;
@@ -4000,8 +4322,10 @@ function createCharacterMotion(doc) {
4000
4322
  },
4001
4323
  dispose() {
4002
4324
  disposed = true;
4325
+ cancelAttention();
4003
4326
  revision++;
4004
4327
  clock.dispose();
4328
+ gaze.dispose();
4005
4329
  canvas?.remove();
4006
4330
  render = void 0;
4007
4331
  images.clear();
@@ -4011,6 +4335,9 @@ function createCharacterMotion(doc) {
4011
4335
  observer.disconnect();
4012
4336
  doc.removeEventListener("visibilitychange", update);
4013
4337
  doc.removeEventListener("pointermove", pointer);
4338
+ doc.removeEventListener("pointerout", leave);
4339
+ win.removeEventListener("blur", neutral);
4340
+ win.removeEventListener("resize", resize);
4014
4341
  }
4015
4342
  };
4016
4343
  }
@@ -5862,7 +6189,7 @@ var HEIXIU_FACE = {
5862
6189
  var HEIXIU_FOOT = Math.max(...HEIXIU_PROFILE.map((r, i) => r * Math.sin(i / HEIXIU_PROFILE.length * Math.PI * 2))) * 100;
5863
6190
  var HEIXIU_REST = { x: 0, y: 0, sx: 1, sy: 1, roll: 0, lid: 1, cheer: 0 };
5864
6191
  var HEIXIU_REACTIONS = ["greet", "hop", "wiggle"];
5865
- var smooth = (x) => {
6192
+ var smooth2 = (x) => {
5866
6193
  const k = Math.max(0, Math.min(1, x));
5867
6194
  return k * k * (3 - 2 * k);
5868
6195
  };
@@ -5898,7 +6225,7 @@ var HeixiuReactions = class {
5898
6225
  this.aim(0, time);
5899
6226
  }
5900
6227
  aim(direction, time) {
5901
- this.hover = this.hoverFrom + (this.hoverTo - this.hoverFrom) * smooth((time - this.hoverAt) / 0.28);
6228
+ this.hover = this.hoverFrom + (this.hoverTo - this.hoverFrom) * smooth2((time - this.hoverAt) / 0.28);
5902
6229
  this.hoverFrom = this.hover;
5903
6230
  this.hoverTo = Math.max(-1, Math.min(1, direction));
5904
6231
  this.hoverAt = time;
@@ -5954,7 +6281,7 @@ var HeixiuReactions = class {
5954
6281
  }
5955
6282
  sample(time) {
5956
6283
  const out = this.track(time);
5957
- this.hover = this.hoverFrom + (this.hoverTo - this.hoverFrom) * smooth((time - this.hoverAt) / 0.28);
6284
+ this.hover = this.hoverFrom + (this.hoverTo - this.hoverFrom) * smooth2((time - this.hoverAt) / 0.28);
5958
6285
  out.roll += this.hover * 5;
5959
6286
  out.x += this.hover * 4;
5960
6287
  return out;
@@ -7247,6 +7574,7 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7247
7574
  {
7248
7575
  className: className === void 0 ? "xiaohei-brand-mark" : `${className} xiaohei-brand-mark`,
7249
7576
  "data-brand-context": context,
7577
+ "data-xiaohei-sidebar-brand": context === "sidebar" ? "" : void 0,
7250
7578
  "data-metallic-ready": ready || snapshot !== void 0 ? "true" : "false",
7251
7579
  style: geometry,
7252
7580
  "aria-hidden": "true",
@@ -7258,7 +7586,7 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7258
7586
  );
7259
7587
  }
7260
7588
  function XiaoheiBrandName() {
7261
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "xiaohei-brand-name", children: "\u7F57\u5C0F\u9ED1 \xB7 \u5996\u7075\u4F1A\u9986" });
7589
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "xiaohei-brand-name", "data-xiaohei-sidebar-brand": "", children: "\u7F57\u5C0F\u9ED1 \xB7 \u5996\u7075\u4F1A\u9986" });
7262
7590
  }
7263
7591
 
7264
7592
  // src/plugin.ts