@lemoncat7/dsh-theme-xiaohei 0.3.2 → 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
@@ -928,8 +928,8 @@ var XIAOHEI_CONVERSATION_MESSAGES_CSS = `
928
928
  inset 0 1px 0 var(--xiaohei-conversation-highlight),
929
929
  inset 1px 0 0 color-mix(in srgb, var(--xiaohei-conversation-highlight) 42%, transparent),
930
930
  0 7px 22px var(--xiaohei-conversation-shadow) !important;
931
- -webkit-backdrop-filter: blur(var(--xiaohei-conversation-blur)) saturate(124%);
932
- backdrop-filter: blur(var(--xiaohei-conversation-blur)) saturate(124%);
931
+ -webkit-backdrop-filter: none;
932
+ backdrop-filter: none;
933
933
  overflow-wrap: anywhere;
934
934
  }
935
935
 
@@ -963,8 +963,8 @@ var XIAOHEI_CONVERSATION_MESSAGES_CSS = `
963
963
  inset 0 1px 0 var(--xiaohei-conversation-highlight),
964
964
  inset -1px 0 0 color-mix(in srgb, var(--xiaohei-conversation-highlight) 42%, transparent),
965
965
  0 7px 22px var(--xiaohei-conversation-shadow) !important;
966
- -webkit-backdrop-filter: blur(var(--xiaohei-conversation-blur)) saturate(124%);
967
- backdrop-filter: blur(var(--xiaohei-conversation-blur)) saturate(124%);
966
+ -webkit-backdrop-filter: none;
967
+ backdrop-filter: none;
968
968
  overflow-wrap: anywhere;
969
969
  }
970
970
 
@@ -996,8 +996,8 @@ var XIAOHEI_CONVERSATION_MESSAGES_CSS = `
996
996
  box-shadow:
997
997
  inset 0 1px 0 color-mix(in srgb, var(--xiaohei-conversation-highlight) 52%, transparent),
998
998
  0 4px 14px color-mix(in srgb, var(--xiaohei-conversation-shadow) 58%, transparent);
999
- -webkit-backdrop-filter: blur(12px) saturate(118%);
1000
- backdrop-filter: blur(12px) saturate(118%);
999
+ -webkit-backdrop-filter: none;
1000
+ backdrop-filter: none;
1001
1001
  overflow: hidden;
1002
1002
  }
1003
1003
 
@@ -1008,8 +1008,8 @@ var XIAOHEI_CONVERSATION_MESSAGES_CSS = `
1008
1008
  border-radius: 9px;
1009
1009
  background: var(--xiaohei-conversation-meta-surface) !important;
1010
1010
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--xiaohei-conversation-highlight) 48%, transparent);
1011
- -webkit-backdrop-filter: blur(10px) saturate(116%);
1012
- backdrop-filter: blur(10px) saturate(116%);
1011
+ -webkit-backdrop-filter: none;
1012
+ backdrop-filter: none;
1013
1013
  }
1014
1014
 
1015
1015
  #root [data-chat-flow-kind='context'],
@@ -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;
@@ -6380,6 +6707,8 @@ function installComposerBloub(doc = typeof document === "undefined" ? void 0 : d
6380
6707
  let hero = null, heroRow = null;
6381
6708
  let bounds;
6382
6709
  let layoutFrame, animationFrame, wake;
6710
+ let frameTimer;
6711
+ let lastPointerStamp = -Infinity;
6383
6712
  let time = 0, lastStamp, activeUntil = 2.5, actionUntil = 0, reactionIndex = 0;
6384
6713
  let pointerSide = 1;
6385
6714
  let special = false, specialIndex = 0;
@@ -6406,8 +6735,10 @@ function installComposerBloub(doc = typeof document === "undefined" ? void 0 : d
6406
6735
  ambientTimer = void 0;
6407
6736
  flight.cancel();
6408
6737
  if (animationFrame !== void 0) win.cancelAnimationFrame(animationFrame);
6738
+ if (frameTimer !== void 0) win.clearTimeout(frameTimer);
6409
6739
  if (wake !== void 0) win.clearTimeout(wake);
6410
6740
  animationFrame = void 0;
6741
+ frameTimer = void 0;
6411
6742
  wake = void 0;
6412
6743
  lastStamp = void 0;
6413
6744
  }
@@ -6442,7 +6773,7 @@ function installComposerBloub(doc = typeof document === "undefined" ? void 0 : d
6442
6773
  }
6443
6774
  function animateFor(seconds) {
6444
6775
  activeUntil = Math.max(activeUntil, time + seconds);
6445
- if (!allowed() || animationFrame !== void 0) return;
6776
+ if (!allowed() || animationFrame !== void 0 || frameTimer !== void 0) return;
6446
6777
  if (wake !== void 0) win.clearTimeout(wake);
6447
6778
  wake = void 0;
6448
6779
  lastStamp = void 0;
@@ -6467,13 +6798,16 @@ function installComposerBloub(doc = typeof document === "undefined" ? void 0 : d
6467
6798
  }
6468
6799
  renderer.render(engine.sample(time), reactions.sample(time));
6469
6800
  if (time < activeUntil || actionUntil || liveliness(time).lid < 0.99) {
6470
- animationFrame = win.requestAnimationFrame(tick);
6801
+ frameTimer = win.setTimeout(() => {
6802
+ frameTimer = void 0;
6803
+ if (allowed()) animationFrame = win.requestAnimationFrame(tick);
6804
+ }, 1e3 / 30);
6471
6805
  } else {
6472
6806
  lastStamp = void 0;
6473
6807
  wake = win.setTimeout(() => {
6474
6808
  wake = void 0;
6475
- animateFor(2);
6476
- }, 3200);
6809
+ animateFor(1);
6810
+ }, 8e3);
6477
6811
  }
6478
6812
  }
6479
6813
  function syncPlayback() {
@@ -6567,6 +6901,8 @@ function installComposerBloub(doc = typeof document === "undefined" ? void 0 : d
6567
6901
  leave();
6568
6902
  return;
6569
6903
  }
6904
+ if (event.timeStamp - lastPointerStamp < 1e3 / 30) return;
6905
+ lastPointerStamp = event.timeStamp;
6570
6906
  const dx = event.clientX - (bounds.left + bounds.width / 2);
6571
6907
  const dy = event.clientY - (bounds.top + bounds.height / 2);
6572
6908
  follow(dx, dy);
@@ -6946,7 +7282,7 @@ var XIAOHEI_THEME_TOKEN_OVERRIDES = Object.fromEntries(
6946
7282
  // src/metallic-brand-mark.tsx
6947
7283
  var import_react = require("react");
6948
7284
  var import_jsx_runtime = require("react/jsx-runtime");
6949
- var sidebarMetallicSnapshot;
7285
+ var metallicSnapshots = /* @__PURE__ */ new Map();
6950
7286
  var VERTEX_SHADER = `#version 300 es
6951
7287
  precision highp float;
6952
7288
  in vec2 a_position;
@@ -7125,14 +7461,14 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7125
7461
  const [ready, setReady] = (0, import_react.useState)(false);
7126
7462
  const visualSize = Math.max(24, size + 4);
7127
7463
  const context = size >= 32 ? "hero" : "sidebar";
7128
- const [snapshot, setSnapshot] = (0, import_react.useState)(context === "sidebar" ? sidebarMetallicSnapshot : void 0);
7464
+ const [snapshot, setSnapshot] = (0, import_react.useState)(metallicSnapshots.get(visualSize));
7129
7465
  (0, import_react.useEffect)(() => {
7130
7466
  if (snapshot !== void 0) return;
7131
7467
  const canvas = canvasRef.current;
7132
7468
  if (canvas === null) return;
7133
7469
  const gl = canvas.getContext("webgl2", {
7134
7470
  alpha: true,
7135
- antialias: true,
7471
+ antialias: false,
7136
7472
  powerPreference: "low-power",
7137
7473
  premultipliedAlpha: true
7138
7474
  });
@@ -7165,7 +7501,7 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7165
7501
  UNIFORM_NAMES.map((name) => [name, gl.getUniformLocation(program, name)])
7166
7502
  );
7167
7503
  const pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
7168
- const renderSize = Math.max(96, Math.ceil(visualSize * pixelRatio * 2));
7504
+ const renderSize = Math.max(64, Math.ceil(visualSize * pixelRatio));
7169
7505
  canvas.width = renderSize;
7170
7506
  canvas.height = renderSize;
7171
7507
  gl.viewport(0, 0, renderSize, renderSize);
@@ -7196,27 +7532,13 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7196
7532
  setRgb(gl, uniforms.u_darkColor, [0.025, 0.04, 0.045]);
7197
7533
  setRgb(gl, uniforms.u_tint, [0.84, 0.92, 0.89]);
7198
7534
  let disposed = false;
7199
- let visible = true;
7200
- let animationFrame;
7201
- let lastFrame = 0;
7202
- let metallicTime = 1450;
7203
- const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
7535
+ const metallicTime = 1450;
7204
7536
  const texture = gl.createTexture();
7205
7537
  const image = new Image();
7206
7538
  const draw = () => {
7207
7539
  gl.uniform1f(uniforms.u_time, metallicTime);
7208
7540
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
7209
7541
  };
7210
- const render = (now) => {
7211
- if (disposed) return;
7212
- if (visible && document.visibilityState !== "hidden" && now - lastFrame >= 40) {
7213
- const delta = lastFrame === 0 ? 0 : Math.min(80, now - lastFrame);
7214
- lastFrame = now;
7215
- metallicTime += delta * 0.11;
7216
- draw();
7217
- }
7218
- animationFrame = window.requestAnimationFrame(render);
7219
- };
7220
7542
  image.onload = () => {
7221
7543
  if (disposed || texture === null) return;
7222
7544
  gl.activeTexture(gl.TEXTURE0);
@@ -7229,24 +7551,15 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7229
7551
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
7230
7552
  gl.uniform1i(uniforms.u_tex, 0);
7231
7553
  draw();
7232
- if (context === "sidebar") {
7233
- sidebarMetallicSnapshot = canvas.toDataURL();
7234
- setSnapshot(sidebarMetallicSnapshot);
7235
- return;
7236
- }
7554
+ const result = canvas.toDataURL();
7555
+ metallicSnapshots.set(visualSize, result);
7556
+ setSnapshot(result);
7237
7557
  setReady(true);
7238
- if (!reduceMotion.matches) animationFrame = window.requestAnimationFrame(render);
7239
7558
  };
7240
7559
  image.src = XIAOHEI_BRAND_AVATAR;
7241
- const visibilityObserver = typeof IntersectionObserver === "function" ? new IntersectionObserver((entries) => {
7242
- visible = entries.some((entry) => entry.isIntersecting);
7243
- if (visible) lastFrame = 0;
7244
- }) : void 0;
7245
- visibilityObserver?.observe(canvas);
7246
7560
  return () => {
7247
7561
  disposed = true;
7248
- if (animationFrame !== void 0) window.cancelAnimationFrame(animationFrame);
7249
- visibilityObserver?.disconnect();
7562
+ image.onload = null;
7250
7563
  if (texture !== null) gl.deleteTexture(texture);
7251
7564
  gl.deleteBuffer(buffer);
7252
7565
  gl.deleteProgram(program);
@@ -7261,6 +7574,7 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7261
7574
  {
7262
7575
  className: className === void 0 ? "xiaohei-brand-mark" : `${className} xiaohei-brand-mark`,
7263
7576
  "data-brand-context": context,
7577
+ "data-xiaohei-sidebar-brand": context === "sidebar" ? "" : void 0,
7264
7578
  "data-metallic-ready": ready || snapshot !== void 0 ? "true" : "false",
7265
7579
  style: geometry,
7266
7580
  "aria-hidden": "true",
@@ -7272,7 +7586,7 @@ function XiaoheiMetallicBrandMark({ size, className }) {
7272
7586
  );
7273
7587
  }
7274
7588
  function XiaoheiBrandName() {
7275
- 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" });
7276
7590
  }
7277
7591
 
7278
7592
  // src/plugin.ts