@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/README.md +5 -5
- package/lib/bloub-heixiu/runtime.js +17 -3
- package/lib/chrome/conversation-messages.js +8 -8
- package/lib/chrome/sidebar.d.ts +1 -1
- package/lib/chrome/sidebar.js +13 -15
- package/lib/chrome/tokens.d.ts +1 -1
- package/lib/chrome/tokens.js +4 -4
- package/lib/client.js +413 -99
- package/lib/gaze.js +15 -2
- package/lib/metallic-brand-mark.js +13 -40
- package/lib/scene/character-expression.d.ts +4 -0
- package/lib/scene/character-expression.js +15 -0
- package/lib/scene/character-eye-material.d.ts +13 -0
- package/lib/scene/character-eye-material.js +88 -0
- package/lib/scene/character-eye-renderer.d.ts +5 -0
- package/lib/scene/character-eye-renderer.js +80 -0
- package/lib/scene/character-eye-rig.d.ts +20 -0
- package/lib/scene/character-eye-rig.js +35 -0
- package/lib/scene/character-gaze.d.ts +16 -0
- package/lib/scene/character-gaze.js +36 -0
- package/lib/scene/character-motion-clock.d.ts +1 -1
- package/lib/scene/character-motion-clock.js +17 -4
- package/lib/scene/character-motion.js +66 -12
- package/lib/scene/character-rig-renderer.d.ts +3 -2
- package/lib/scene/character-rig-renderer.js +28 -18
- package/lib/scene/character-rig.d.ts +2 -0
- package/lib/scene/character-rig.js +9 -1
- package/lib/sidebar-reveal.js +11 -3
- package/package.json +1 -1
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:
|
|
932
|
-
backdrop-filter:
|
|
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:
|
|
967
|
-
backdrop-filter:
|
|
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:
|
|
1000
|
-
backdrop-filter:
|
|
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:
|
|
1012
|
-
backdrop-filter:
|
|
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-
|
|
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-
|
|
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']
|
|
1600
|
-
#root [data-slot='sidebar'] > div:
|
|
1601
|
-
#root [data-slot='sidebar'] > div:
|
|
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']
|
|
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:
|
|
1614
|
-
#root [data-slot='sidebar'] > div:
|
|
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:
|
|
1626
|
-
#root [data-slot='sidebar'] > div:
|
|
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:
|
|
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:
|
|
1706
|
-
#root [data-slot='sidebar'] > div:
|
|
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 /
|
|
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:
|
|
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 /
|
|
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:
|
|
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 = () =>
|
|
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(
|
|
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,
|
|
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 /
|
|
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
|
-
|
|
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.
|
|
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
|
|
3890
|
-
rig.ears.
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
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 === "
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
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
|
-
|
|
3931
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
3975
|
-
|
|
3976
|
-
|
|
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
|
|
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
|
|
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) *
|
|
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) *
|
|
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
|
-
|
|
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(
|
|
6476
|
-
},
|
|
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
|
|
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)(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
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
|
-
|
|
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
|