@mumulinya167/ccm-pet-assets 2.0.0 → 2.0.2
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/assets/calico/calico-idle-follow.svg +324 -324
- package/assets/calico.svg +324 -324
- package/assets/clawd/LICENSE +22 -22
- package/assets/clawd/clawd-about-hero.svg +202 -202
- package/assets/clawd/clawd-collapse-sleep.svg +247 -247
- package/assets/clawd/clawd-error.svg +94 -94
- package/assets/clawd/clawd-happy.svg +160 -160
- package/assets/clawd/clawd-headphones-groove.svg +194 -194
- package/assets/clawd/clawd-idle-bubble.svg +180 -180
- package/assets/clawd/clawd-idle-collapse.svg +101 -101
- package/assets/clawd/clawd-idle-doze.svg +72 -72
- package/assets/clawd/clawd-idle-follow.svg +64 -64
- package/assets/clawd/clawd-idle-living.svg +195 -195
- package/assets/clawd/clawd-idle-look.svg +115 -115
- package/assets/clawd/clawd-idle-reading.svg +252 -252
- package/assets/clawd/clawd-idle-yawn.svg +158 -158
- package/assets/clawd/clawd-mini-alert.svg +129 -129
- package/assets/clawd/clawd-mini-crabwalk.svg +76 -76
- package/assets/clawd/clawd-mini-enter-sleep.svg +64 -64
- package/assets/clawd/clawd-mini-enter.svg +114 -114
- package/assets/clawd/clawd-mini-happy.svg +124 -124
- package/assets/clawd/clawd-mini-idle.svg +83 -83
- package/assets/clawd/clawd-mini-peek.svg +82 -82
- package/assets/clawd/clawd-mini-sleep.svg +112 -112
- package/assets/clawd/clawd-mini-typing.svg +153 -153
- package/assets/clawd/clawd-notification.svg +438 -438
- package/assets/clawd/clawd-react-annoyed.svg +167 -167
- package/assets/clawd/clawd-react-double-jump.svg +229 -229
- package/assets/clawd/clawd-react-double.svg +108 -108
- package/assets/clawd/clawd-react-drag.svg +102 -102
- package/assets/clawd/clawd-react-left.svg +102 -102
- package/assets/clawd/clawd-react-right.svg +102 -102
- package/assets/clawd/clawd-sleeping.svg +117 -117
- package/assets/clawd/clawd-static-base.svg +20 -20
- package/assets/clawd/clawd-wake.svg +277 -277
- package/assets/clawd/clawd-working-building.svg +329 -329
- package/assets/clawd/clawd-working-carrying.svg +178 -178
- package/assets/clawd/clawd-working-debugger.svg +245 -245
- package/assets/clawd/clawd-working-juggling.svg +183 -183
- package/assets/clawd/clawd-working-sweeping.svg +248 -248
- package/assets/clawd/clawd-working-thinking.svg +501 -501
- package/assets/clawd/clawd-working-typing-boss.svg +307 -307
- package/assets/clawd/clawd-working-typing.svg +273 -273
- package/assets/clawd/clawd-working-ultrathink.svg +166 -166
- package/assets/clawd/clawd-working-wizard.svg +356 -356
- package/assets/clawd.svg +64 -64
- package/assets/cloudling/cloudling/cloudling-attention.svg +379 -379
- package/assets/cloudling/cloudling/cloudling-building.svg +667 -667
- package/assets/cloudling/cloudling/cloudling-carrying.svg +604 -604
- package/assets/cloudling/cloudling/cloudling-conducting.svg +616 -616
- package/assets/cloudling/cloudling/cloudling-dozing-to-sleeping.svg +578 -578
- package/assets/cloudling/cloudling/cloudling-dozing.svg +358 -358
- package/assets/cloudling/cloudling/cloudling-error.svg +428 -428
- package/assets/cloudling/cloudling/cloudling-idle-reading.svg +658 -658
- package/assets/cloudling/cloudling/cloudling-idle-to-dozing.svg +568 -568
- package/assets/cloudling/cloudling/cloudling-idle-to-sleeping.svg +811 -811
- package/assets/cloudling/cloudling/cloudling-idle.svg +255 -255
- package/assets/cloudling/cloudling/cloudling-juggling.svg +648 -648
- package/assets/cloudling/cloudling/cloudling-mini-alert.svg +389 -389
- package/assets/cloudling/cloudling/cloudling-mini-crabwalk.svg +335 -335
- package/assets/cloudling/cloudling/cloudling-mini-enter-roll-in.svg +225 -225
- package/assets/cloudling/cloudling/cloudling-mini-enter-sleep.svg +231 -231
- package/assets/cloudling/cloudling/cloudling-mini-happy.svg +370 -370
- package/assets/cloudling/cloudling/cloudling-mini-idle.svg +279 -279
- package/assets/cloudling/cloudling/cloudling-mini-peek.svg +327 -327
- package/assets/cloudling/cloudling/cloudling-mini-sleep.svg +260 -260
- package/assets/cloudling/cloudling/cloudling-mini-typing.svg +691 -691
- package/assets/cloudling/cloudling/cloudling-notification.svg +493 -493
- package/assets/cloudling/cloudling/cloudling-react-drag.svg +83 -83
- package/assets/cloudling/cloudling/cloudling-sleeping-to-idle.svg +555 -555
- package/assets/cloudling/cloudling/cloudling-sleeping.svg +500 -500
- package/assets/cloudling/cloudling/cloudling-sweeping.svg +292 -292
- package/assets/cloudling/cloudling/cloudling-thinking.svg +719 -719
- package/assets/cloudling/cloudling/cloudling-typing.svg +354 -354
- package/assets/cloudling/cloudling-attention.svg +379 -379
- package/assets/cloudling/cloudling-building.svg +667 -667
- package/assets/cloudling/cloudling-carrying.svg +604 -604
- package/assets/cloudling/cloudling-conducting.svg +616 -616
- package/assets/cloudling/cloudling-dozing-to-sleeping.svg +578 -578
- package/assets/cloudling/cloudling-dozing.svg +358 -358
- package/assets/cloudling/cloudling-error.svg +428 -428
- package/assets/cloudling/cloudling-idle-reading.svg +658 -658
- package/assets/cloudling/cloudling-idle-to-dozing.svg +568 -568
- package/assets/cloudling/cloudling-idle-to-sleeping.svg +811 -811
- package/assets/cloudling/cloudling-idle.svg +255 -255
- package/assets/cloudling/cloudling-juggling.svg +648 -648
- package/assets/cloudling/cloudling-mini-alert.svg +389 -389
- package/assets/cloudling/cloudling-mini-crabwalk.svg +335 -335
- package/assets/cloudling/cloudling-mini-enter-roll-in.svg +225 -225
- package/assets/cloudling/cloudling-mini-enter-sleep.svg +231 -231
- package/assets/cloudling/cloudling-mini-happy.svg +370 -370
- package/assets/cloudling/cloudling-mini-idle.svg +279 -279
- package/assets/cloudling/cloudling-mini-peek.svg +327 -327
- package/assets/cloudling/cloudling-mini-sleep.svg +260 -260
- package/assets/cloudling/cloudling-mini-typing.svg +691 -691
- package/assets/cloudling/cloudling-notification.svg +493 -493
- package/assets/cloudling/cloudling-react-drag.svg +83 -83
- package/assets/cloudling/cloudling-sleeping-to-idle.svg +555 -555
- package/assets/cloudling/cloudling-sleeping.svg +500 -500
- package/assets/cloudling/cloudling-sweeping.svg +292 -292
- package/assets/cloudling/cloudling-thinking.svg +719 -719
- package/assets/cloudling/cloudling-typing.svg +354 -354
- package/assets/cloudling.svg +255 -255
- package/assets/ghost-attention.svg +19 -19
- package/assets/ghost-carrying.svg +19 -19
- package/assets/ghost-collapsing.svg +19 -19
- package/assets/ghost-dozing.svg +23 -23
- package/assets/ghost-error.svg +19 -19
- package/assets/ghost-happy.svg +23 -23
- package/assets/ghost-idle.svg +11 -11
- package/assets/ghost-juggling.svg +19 -19
- package/assets/ghost-notification.svg +19 -19
- package/assets/ghost-react-double.svg +23 -23
- package/assets/ghost-react-drag.svg +23 -23
- package/assets/ghost-react-left.svg +19 -19
- package/assets/ghost-react-right.svg +19 -19
- package/assets/ghost-sleeping.svg +19 -19
- package/assets/ghost-sweeping.svg +19 -19
- package/assets/ghost-waking.svg +19 -19
- package/assets/ghost-yawning.svg +23 -23
- package/assets/ghost.svg +11 -11
- package/assets/robot-attention.svg +19 -19
- package/assets/robot-carrying.svg +19 -19
- package/assets/robot-collapsing.svg +19 -19
- package/assets/robot-dozing.svg +23 -23
- package/assets/robot-error.svg +19 -19
- package/assets/robot-happy.svg +23 -23
- package/assets/robot-idle.svg +11 -11
- package/assets/robot-juggling.svg +19 -19
- package/assets/robot-notification.svg +19 -19
- package/assets/robot-react-double.svg +23 -23
- package/assets/robot-react-drag.svg +23 -23
- package/assets/robot-react-left.svg +19 -19
- package/assets/robot-react-right.svg +19 -19
- package/assets/robot-sleeping.svg +19 -19
- package/assets/robot-sweeping.svg +19 -19
- package/assets/robot-waking.svg +19 -19
- package/assets/robot-yawning.svg +23 -23
- package/assets/robot.svg +11 -11
- package/manifest.json +280 -280
- package/package.json +1 -1
|
@@ -1,500 +1,500 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg id="stage" xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 36 36" preserveAspectRatio="xMidYMid meet">
|
|
3
|
-
<g id="__cloudling-export-shims" opacity="0" pointer-events="none" style="display:none">
|
|
4
|
-
<g id="breath-group"></g>
|
|
5
|
-
<g id="hat-sway"></g>
|
|
6
|
-
<g id="root-svg"></g>
|
|
7
|
-
<g id="z-container"></g>
|
|
8
|
-
</g>
|
|
9
|
-
<script type="application/ecmascript"><![CDATA[
|
|
10
|
-
const CLOUD_D = "M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388z";
|
|
11
|
-
|
|
12
|
-
/* =========================================================================
|
|
13
|
-
* 默认锁定参数
|
|
14
|
-
* ========================================================================= */
|
|
15
|
-
const DEFAULTS = {
|
|
16
|
-
// 静帧摆位 (鹿鹿 2026-04-28 调好)
|
|
17
|
-
bodyTilt: -6,
|
|
18
|
-
hatTx: -2,
|
|
19
|
-
hatTy: -0.4,
|
|
20
|
-
hatRot: -10,
|
|
21
|
-
hatScale: 0.98,
|
|
22
|
-
maskTx: 0,
|
|
23
|
-
maskTy: -0.6,
|
|
24
|
-
maskRot: -9,
|
|
25
|
-
maskScale: 1.00,
|
|
26
|
-
|
|
27
|
-
// 主层: 呼吸
|
|
28
|
-
breathPeriod: 4,
|
|
29
|
-
breathLow: 0.96,
|
|
30
|
-
breathHigh: 1.02,
|
|
31
|
-
|
|
32
|
-
// 次层: 帽子独立 sway (异步)
|
|
33
|
-
hatSwayPeriod: 6,
|
|
34
|
-
hatSwayAmp: 2,
|
|
35
|
-
|
|
36
|
-
// 梦境层: Z 字
|
|
37
|
-
zEnable: 1,
|
|
38
|
-
zInterval: 1.8,
|
|
39
|
-
zLifetime: 2.9,
|
|
40
|
-
zStartX: 19.4,
|
|
41
|
-
zStartY: 3.6,
|
|
42
|
-
zRiseDist: 8,
|
|
43
|
-
zDriftX: 4,
|
|
44
|
-
zMaxSize: 1.6,
|
|
45
|
-
zMinSize: 1.3,
|
|
46
|
-
zStroke: 0.55,
|
|
47
|
-
zMaxOpacity: 0.45,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
let params = { ...DEFAULTS };
|
|
51
|
-
let paused = false;
|
|
52
|
-
|
|
53
|
-
/* =========================================================================
|
|
54
|
-
* defs
|
|
55
|
-
* ========================================================================= */
|
|
56
|
-
function commonDefs(p) {
|
|
57
|
-
return `
|
|
58
|
-
<linearGradient id="${p}-body" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
59
|
-
<stop offset="0" stop-color="#93c5fd"/>
|
|
60
|
-
<stop offset="0.5" stop-color="#60a5fa"/>
|
|
61
|
-
<stop offset="1" stop-color="#3b82f6"/>
|
|
62
|
-
</linearGradient>
|
|
63
|
-
<linearGradient id="${p}-eye" x1="0" y1="3" x2="0" y2="21" gradientUnits="userSpaceOnUse">
|
|
64
|
-
<stop offset="0" stop-color="#B1A7FF"/>
|
|
65
|
-
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
66
|
-
<stop offset="1" stop-color="#3941FF"/>
|
|
67
|
-
</linearGradient>
|
|
68
|
-
<radialGradient id="${p}-hi" cx="0.45" cy="0.25" r="0.55">
|
|
69
|
-
<stop offset="0" stop-color="#93c5fd" stop-opacity="0.9"/>
|
|
70
|
-
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
71
|
-
</radialGradient>
|
|
72
|
-
<linearGradient id="${p}-hat" x1="11" y1="-3" x2="14" y2="6" gradientUnits="userSpaceOnUse">
|
|
73
|
-
<stop offset="0" stop-color="#7AA0FF"/>
|
|
74
|
-
<stop offset="0.45" stop-color="#3941FF"/>
|
|
75
|
-
<stop offset="1" stop-color="#1F2BC2"/>
|
|
76
|
-
</linearGradient>
|
|
77
|
-
<linearGradient id="${p}-cuff" x1="12" y1="3.0" x2="12" y2="8.0" gradientUnits="userSpaceOnUse">
|
|
78
|
-
<stop offset="0" stop-color="#93c5fd"/>
|
|
79
|
-
<stop offset="0.5" stop-color="#fafbff"/>
|
|
80
|
-
<stop offset="1" stop-color="#e8ecf6"/>
|
|
81
|
-
</linearGradient>
|
|
82
|
-
<linearGradient id="${p}-star-yellow" x1="0" y1="-2" x2="0" y2="2" gradientUnits="userSpaceOnUse">
|
|
83
|
-
<stop offset="0" stop-color="#FFE9A8"/>
|
|
84
|
-
<stop offset="0.5" stop-color="#FFD360"/>
|
|
85
|
-
<stop offset="1" stop-color="#F5A623"/>
|
|
86
|
-
</linearGradient>
|
|
87
|
-
<filter id="${p}-star-glow" x="-80%" y="-80%" width="260%" height="260%">
|
|
88
|
-
<feDropShadow dx="0" dy="0.4" stdDeviation="0.5" flood-color="#F5A623" flood-opacity="0.32"/>
|
|
89
|
-
</filter>
|
|
90
|
-
<linearGradient id="${p}-z-grad" x1="0" y1="0" x2="0" y2="1">
|
|
91
|
-
<stop offset="0" stop-color="#B1A7FF"/>
|
|
92
|
-
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
93
|
-
<stop offset="1" stop-color="#3941FF"/>
|
|
94
|
-
</linearGradient>
|
|
95
|
-
<filter id="${p}-z-glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
96
|
-
<feDropShadow dx="0" dy="0.3" stdDeviation="0.35" flood-color="#7A9DFF" flood-opacity="0.45"/>
|
|
97
|
-
</filter>
|
|
98
|
-
<filter id="${p}-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
99
|
-
<feDropShadow class="cloud-drop-shadow" dx="0" dy="0.6" stdDeviation="0.8" flood-color="#3941FF" flood-opacity="0.35"/>
|
|
100
|
-
</filter>
|
|
101
|
-
<filter id="${p}-edge-blur" x="-40%" y="-40%" width="180%" height="180%">
|
|
102
|
-
<feGaussianBlur stdDeviation="0.16"/>
|
|
103
|
-
</filter>
|
|
104
|
-
<filter id="${p}-soft" x="-30%" y="-30%" width="160%" height="160%">
|
|
105
|
-
<feGaussianBlur stdDeviation="0.18"/>
|
|
106
|
-
</filter>
|
|
107
|
-
<clipPath id="${p}-clip"><path d="${CLOUD_D}"/></clipPath>
|
|
108
|
-
`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/* =========================================================================
|
|
112
|
-
* 部件 (从 sleeping-static-pose v1 复制)
|
|
113
|
-
* ========================================================================= */
|
|
114
|
-
function makeFluffyCuff(p, cx, cy, rx, ry, asym, bumps) {
|
|
115
|
-
let parts = [];
|
|
116
|
-
const circles = [];
|
|
117
|
-
for (let i = 0; i < bumps; i++) {
|
|
118
|
-
const ang = (i * 2 * Math.PI) / bumps - Math.PI / 2;
|
|
119
|
-
const x = cx + Math.cos(ang) * rx;
|
|
120
|
-
const y = cy + Math.sin(ang) * ry;
|
|
121
|
-
const sizeY = -Math.sin(ang);
|
|
122
|
-
const sizeBoost = 0.18 * (sizeY + 1) / 2 + 0.10;
|
|
123
|
-
const jitter = ((i * 13) % 7) / 35;
|
|
124
|
-
let r = 0.62 + sizeBoost + jitter;
|
|
125
|
-
if (asym !== 0) {
|
|
126
|
-
const leftRight = Math.cos(ang);
|
|
127
|
-
r *= 1 + asym * leftRight * 0.28;
|
|
128
|
-
}
|
|
129
|
-
circles.push({ x, y, r });
|
|
130
|
-
}
|
|
131
|
-
const innerRy = ry + 0.40;
|
|
132
|
-
parts.push(`<g filter="url(#${p}-soft)">`);
|
|
133
|
-
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${(rx + 0.30).toFixed(2)}" ry="${(innerRy + 0.30).toFixed(2)}" fill="#7A9DFF" opacity="0.50"/>`);
|
|
134
|
-
circles.forEach(c => {
|
|
135
|
-
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${(c.r + 0.30).toFixed(2)}" fill="#7A9DFF" opacity="0.50"/>`);
|
|
136
|
-
});
|
|
137
|
-
parts.push(`</g>`);
|
|
138
|
-
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${(rx + 0.15).toFixed(2)}" ry="${(innerRy + 0.15).toFixed(2)}" fill="#aab3c6" opacity="0.65"/>`);
|
|
139
|
-
circles.forEach(c => {
|
|
140
|
-
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${(c.r + 0.15).toFixed(2)}" fill="#aab3c6" opacity="0.65"/>`);
|
|
141
|
-
});
|
|
142
|
-
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${rx.toFixed(2)}" ry="${innerRy.toFixed(2)}" fill="url(#${p}-cuff)"/>`);
|
|
143
|
-
circles.forEach(c => {
|
|
144
|
-
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${c.r.toFixed(2)}" fill="url(#${p}-cuff)"/>`);
|
|
145
|
-
});
|
|
146
|
-
parts.push(`<ellipse cx="${cx}" cy="${(cy - ry * 0.45).toFixed(2)}" rx="${(rx * 0.78).toFixed(2)}" ry="${(ry * 0.32).toFixed(2)}" fill="none" stroke="#ffffff" stroke-opacity="0.55" stroke-width="0.14"/>`);
|
|
147
|
-
return parts.join('');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function makeBentHat(bend) {
|
|
151
|
-
const baseLeft = { x: 8.5, y: 5.0 };
|
|
152
|
-
const baseRight = { x: 15.5, y: 5.0 };
|
|
153
|
-
const elbowX = 12 + bend * 2.2;
|
|
154
|
-
const elbowY = -0.5 + bend * 2.3;
|
|
155
|
-
const tipX = 11 - bend * 5.5;
|
|
156
|
-
const tipY = 0.0 + bend * 0.5;
|
|
157
|
-
const tipBottomX = tipX + 1.2 + bend * 0.4;
|
|
158
|
-
const tipBottomY = tipY + 1.4 + bend * 0.3;
|
|
159
|
-
const c1 = { x: baseRight.x + 0.5, y: baseRight.y - 1.5 };
|
|
160
|
-
const c2 = { x: elbowX + 0.8, y: elbowY + 1.0 };
|
|
161
|
-
const c3 = { x: elbowX - 0.5, y: elbowY - 0.8 };
|
|
162
|
-
const c4 = { x: tipX + 1.5, y: tipY - 0.5 };
|
|
163
|
-
const c5 = { x: tipX - 0.3, y: tipY + 0.6 };
|
|
164
|
-
const c6 = { x: tipBottomX - 1.0, y: tipBottomY };
|
|
165
|
-
const c7 = { x: 8.5, y: 4.0 };
|
|
166
|
-
return {
|
|
167
|
-
path: `M ${baseLeft.x} ${baseLeft.y} Q 12 5.55 ${baseRight.x} ${baseRight.y} C ${c1.x.toFixed(2)} ${c1.y.toFixed(2)} ${c2.x.toFixed(2)} ${c2.y.toFixed(2)} ${elbowX.toFixed(2)} ${elbowY.toFixed(2)} C ${c3.x.toFixed(2)} ${c3.y.toFixed(2)} ${c4.x.toFixed(2)} ${c4.y.toFixed(2)} ${tipX.toFixed(2)} ${tipY.toFixed(2)} C ${c5.x.toFixed(2)} ${c5.y.toFixed(2)} ${c6.x.toFixed(2)} ${c6.y.toFixed(2)} ${tipBottomX.toFixed(2)} ${tipBottomY.toFixed(2)} C ${c7.x.toFixed(2)} ${c7.y.toFixed(2)} 8.0 4.5 ${baseLeft.x} ${baseLeft.y} Z`
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function makeStar(cx, cy, r, rotationDeg, cornerRound, chubbiness, innerRatio) {
|
|
172
|
-
const innerR = r * innerRatio;
|
|
173
|
-
const rotRad = (rotationDeg * Math.PI) / 180;
|
|
174
|
-
const verts = [];
|
|
175
|
-
for (let i = 0; i < 10; i++) {
|
|
176
|
-
const ang = (i * Math.PI / 5) + rotRad - Math.PI / 2;
|
|
177
|
-
const radius = i % 2 === 0 ? r : innerR;
|
|
178
|
-
verts.push({ x: cx + Math.cos(ang) * radius, y: cy + Math.sin(ang) * radius });
|
|
179
|
-
}
|
|
180
|
-
const inP = [], outP = [];
|
|
181
|
-
for (let i = 0; i < 10; i++) {
|
|
182
|
-
const prev = verts[(i - 1 + 10) % 10];
|
|
183
|
-
const cur = verts[i];
|
|
184
|
-
const next = verts[(i + 1) % 10];
|
|
185
|
-
const v1x = prev.x - cur.x, v1y = prev.y - cur.y;
|
|
186
|
-
const v1Len = Math.sqrt(v1x * v1x + v1y * v1y);
|
|
187
|
-
const v2x = next.x - cur.x, v2y = next.y - cur.y;
|
|
188
|
-
const v2Len = Math.sqrt(v2x * v2x + v2y * v2y);
|
|
189
|
-
const cr = Math.min(cornerRound, v1Len * 0.48, v2Len * 0.48);
|
|
190
|
-
inP[i] = { x: cur.x + (v1x / v1Len) * cr, y: cur.y + (v1y / v1Len) * cr };
|
|
191
|
-
outP[i] = { x: cur.x + (v2x / v2Len) * cr, y: cur.y + (v2y / v2Len) * cr };
|
|
192
|
-
}
|
|
193
|
-
let path = `M ${inP[0].x.toFixed(2)} ${inP[0].y.toFixed(2)} `;
|
|
194
|
-
for (let i = 0; i < 10; i++) {
|
|
195
|
-
path += `Q ${verts[i].x.toFixed(2)} ${verts[i].y.toFixed(2)} ${outP[i].x.toFixed(2)} ${outP[i].y.toFixed(2)} `;
|
|
196
|
-
const nextI = (i + 1) % 10;
|
|
197
|
-
const start = outP[i], end = inP[nextI];
|
|
198
|
-
if (chubbiness > 0) {
|
|
199
|
-
const midX = (start.x + end.x) / 2, midY = (start.y + end.y) / 2;
|
|
200
|
-
const dx = midX - cx, dy = midY - cy;
|
|
201
|
-
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
202
|
-
const offset = chubbiness * r * 0.32;
|
|
203
|
-
const ctrlX = midX + (dx / len) * offset, ctrlY = midY + (dy / len) * offset;
|
|
204
|
-
path += `Q ${ctrlX.toFixed(2)} ${ctrlY.toFixed(2)} ${end.x.toFixed(2)} ${end.y.toFixed(2)} `;
|
|
205
|
-
} else {
|
|
206
|
-
path += `L ${end.x.toFixed(2)} ${end.y.toFixed(2)} `;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
path += 'Z';
|
|
210
|
-
return path;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const STAR_LOCKED = {
|
|
214
|
-
cx: 8.55, cy: 0.35, size: 1.00, rotation: 30,
|
|
215
|
-
cornerRound: 0.27, chubbiness: 0, innerRatio: 0.56
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const SLEEP_MASK_PATH = {
|
|
219
|
-
d: "M488.728,341.587l-7.846-2.599c-0.405-16.992-7.605-47.34-48.625-70.941c-38.301-22.036-96.652-33.215-173.431-33.224c-76.822,0-135.146,11.165-173.438,33.183c-42.335,24.344-48.659,55.883-48.661,72.516c0,2.608,0.135,5.183,0.352,7.735h-2.649c-0.001,0-0.001,0-0.002,0c-9.635,0-17.446,7.811-17.448,17.445c-0.001,9.636,7.81,17.449,17.445,17.45l13,0.001c15.257,28.43,45.267,47.814,79.728,47.818c20.016-0.001,36.647-11.511,54.256-23.695c20.65-14.29,44.048-30.481,77.388-30.481c33.359,0.003,56.757,16.204,77.402,30.497c17.61,12.192,34.24,23.707,54.251,23.709h0.012c24.153,0,46.861-9.404,63.941-26.481c8.937-8.936,15.751-19.422,20.239-30.839l3.111,1.031c1.82,0.604,3.67,0.89,5.489,0.89c7.318,0,14.133-4.64,16.56-11.966C502.835,354.489,497.876,344.617,488.728,341.587Z",
|
|
220
|
-
bboxX: 15, bboxY: 232, bboxW: 487, bboxH: 200
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
function makeSleepMask(p, tx, ty, rot, scale) {
|
|
224
|
-
const cx = 12 + tx;
|
|
225
|
-
const cy = 12 + ty;
|
|
226
|
-
const W = 16.61 * scale;
|
|
227
|
-
const H = 7.55 * scale;
|
|
228
|
-
const eyeScale = 1.18;
|
|
229
|
-
const eyeRot = 5;
|
|
230
|
-
const eyeOffsetY = -0.25;
|
|
231
|
-
const lashW = 2.45;
|
|
232
|
-
const lashH = 0.70;
|
|
233
|
-
const lashStroke = 0.45;
|
|
234
|
-
const lashGap = 6.00 * scale;
|
|
235
|
-
const lashHairLen = 0.66;
|
|
236
|
-
const lashHairCount = 3;
|
|
237
|
-
|
|
238
|
-
const sx = W / SLEEP_MASK_PATH.bboxW;
|
|
239
|
-
const sy = H / SLEEP_MASK_PATH.bboxH;
|
|
240
|
-
const cxOrig = SLEEP_MASK_PATH.bboxX + SLEEP_MASK_PATH.bboxW / 2;
|
|
241
|
-
const cyOrig = SLEEP_MASK_PATH.bboxY + SLEEP_MASK_PATH.bboxH / 2;
|
|
242
|
-
const body = `<g transform="translate(${cx} ${cy}) scale(${sx.toFixed(5)} ${sy.toFixed(5)}) translate(${(-cxOrig).toFixed(2)} ${(-cyOrig).toFixed(2)})">
|
|
243
|
-
<path d="${SLEEP_MASK_PATH.d}" fill="#22305A" stroke="#000000" stroke-width="0.22" stroke-linejoin="round" stroke-linecap="round" vector-effect="non-scaling-stroke"/>
|
|
244
|
-
</g>`;
|
|
245
|
-
|
|
246
|
-
const arcLocal = `<path d="M ${(-lashW/2).toFixed(2)} 0 Q 0 ${(lashH * 2).toFixed(2)} ${(lashW/2).toFixed(2)} 0" fill="none" stroke="#FFFFFF" stroke-width="${lashStroke}" stroke-linecap="round"/>`;
|
|
247
|
-
|
|
248
|
-
const hairs = [];
|
|
249
|
-
const tPad = 0.18;
|
|
250
|
-
for (let i = 0; i < lashHairCount; i++) {
|
|
251
|
-
const t = tPad + (i / (lashHairCount - 1)) * (1 - 2 * tPad);
|
|
252
|
-
const sxh = (1-t)*(1-t)*(-lashW/2) + 2*(1-t)*t*0 + t*t*(lashW/2);
|
|
253
|
-
const syh = (1-t)*(1-t)*0 + 2*(1-t)*t*(2*lashH) + t*t*0;
|
|
254
|
-
const tangX = 2*(1-t)*(0 - (-lashW/2)) + 2*t*((lashW/2) - 0);
|
|
255
|
-
const tangY = 2*(1-t)*((2*lashH) - 0) + 2*t*(0 - (2*lashH));
|
|
256
|
-
const tLen = Math.hypot(tangX, tangY);
|
|
257
|
-
const normX1 = -tangY / tLen, normY1 = tangX / tLen;
|
|
258
|
-
const normX2 = tangY / tLen, normY2 = -tangX / tLen;
|
|
259
|
-
const useFirst = normY1 > 0;
|
|
260
|
-
const dirX = useFirst ? normX1 : normX2;
|
|
261
|
-
const dirY = useFirst ? normY1 : normY2;
|
|
262
|
-
const ex = sxh + dirX * lashHairLen;
|
|
263
|
-
const ey = syh + dirY * lashHairLen;
|
|
264
|
-
hairs.push(`<line x1="${sxh.toFixed(2)}" y1="${syh.toFixed(2)}" x2="${ex.toFixed(2)}" y2="${ey.toFixed(2)}" stroke="#FFFFFF" stroke-width="${(lashStroke * 0.85).toFixed(2)}" stroke-linecap="round" opacity="0.92"/>`);
|
|
265
|
-
}
|
|
266
|
-
const hairsLocal = hairs.join('');
|
|
267
|
-
|
|
268
|
-
const eyeL_cx = cx - lashGap / 2;
|
|
269
|
-
const eyeR_cx = cx + lashGap / 2;
|
|
270
|
-
const wrapEye = (ecx, sign) => `<g transform="translate(${ecx.toFixed(2)} ${(cy + eyeOffsetY).toFixed(2)}) rotate(${(sign * eyeRot).toFixed(2)}) scale(${eyeScale.toFixed(3)})">
|
|
271
|
-
${arcLocal}
|
|
272
|
-
${hairsLocal}
|
|
273
|
-
</g>`;
|
|
274
|
-
const lashes = wrapEye(eyeL_cx, -1) + wrapEye(eyeR_cx, +1);
|
|
275
|
-
|
|
276
|
-
return `
|
|
277
|
-
<g class="sleep-mask" transform="rotate(${rot} ${cx} ${cy})" filter="url(#${p}-shadow)">
|
|
278
|
-
${body}
|
|
279
|
-
${lashes}
|
|
280
|
-
</g>
|
|
281
|
-
`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function makeCloudling(p) {
|
|
285
|
-
return `
|
|
286
|
-
<g class="cloudling-base">
|
|
287
|
-
<g class="edge-system">
|
|
288
|
-
<use class="edge-contact" href="#${p}-cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.4" stroke-linejoin="round" transform="translate(0 0.18)" filter="url(#${p}-edge-blur)"/>
|
|
289
|
-
<use class="edge-rim-soft" href="#${p}-cloud-shape" fill="none" stroke="#7A9DFF" stroke-width="1.2" stroke-linejoin="round" filter="url(#${p}-edge-blur)"/>
|
|
290
|
-
<use class="edge-rim-tight" href="#${p}-cloud-shape" fill="none" stroke="#aab3c6" stroke-width="0.8" stroke-linejoin="round"/>
|
|
291
|
-
</g>
|
|
292
|
-
<g filter="url(#${p}-shadow)">
|
|
293
|
-
<use href="#${p}-cloud-shape" fill="url(#${p}-body)"/>
|
|
294
|
-
<rect x="1" y="2" width="22" height="11" fill="url(#${p}-hi)" clip-path="url(#${p}-clip)"/>
|
|
295
|
-
</g>
|
|
296
|
-
</g>
|
|
297
|
-
`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/* =========================================================================
|
|
301
|
-
* Z 字 path - 苹果精致风手写体
|
|
302
|
-
* 流畅手写 Z = 上横 + 下笔斜对角(略带圆滑) + 下横
|
|
303
|
-
* 用 stroke 描边 + linecap=round + 紫蓝渐变 + glow
|
|
304
|
-
* ========================================================================= */
|
|
305
|
-
function makeZPath() {
|
|
306
|
-
// 在局部坐标 (-1, -1) 到 (1, 1) 区间内画 Z
|
|
307
|
-
// 上横: (-1, -1) → (1, -1) 但末端略弯 (Q 控制点)
|
|
308
|
-
// 斜杠: (1, -1) → (-1, 1) 略带 S 弧
|
|
309
|
-
// 下横: (-1, 1) → (1, 1) 末端略弯
|
|
310
|
-
return "M -1.0 -1.0 Q 0.0 -1.0 1.0 -1.0 Q 1.05 -0.85 0.6 -0.4 Q 0 0.1 -0.6 0.6 Q -1.05 0.95 -1.0 1.0 Q 0.0 1.0 1.0 1.0";
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/* =========================================================================
|
|
314
|
-
* 渲染静态结构 + 用 JS RAF 驱动 transform / Z 字
|
|
315
|
-
* ========================================================================= */
|
|
316
|
-
const stage = document.getElementById('stage');
|
|
317
|
-
let svgRoot = null;
|
|
318
|
-
let cloudGroup = null;
|
|
319
|
-
let hatGroup = null;
|
|
320
|
-
let zContainer = null;
|
|
321
|
-
|
|
322
|
-
function renderStage() {
|
|
323
|
-
const p = 'sl';
|
|
324
|
-
const hatData = makeBentHat(0.4);
|
|
325
|
-
const cuffSVG = makeFluffyCuff(p, 12, 5.4, 3.7, 0.85, 0, 18);
|
|
326
|
-
const s = STAR_LOCKED;
|
|
327
|
-
const starPath = makeStar(s.cx, s.cy, s.size, s.rotation, s.cornerRound, s.chubbiness, s.innerRatio);
|
|
328
|
-
const cloudling = makeCloudling(p);
|
|
329
|
-
const mask = makeSleepMask(p, params.maskTx, params.maskTy, params.maskRot, params.maskScale);
|
|
330
|
-
|
|
331
|
-
// 帽子结构: 整顶帽子作为一个 group, transform-origin 在 cuff 底部 (12, 5.4)
|
|
332
|
-
// 帽子 sway 围绕这个点 rotate
|
|
333
|
-
const hatStaticTransform = `translate(${params.hatTx} ${params.hatTy}) rotate(${params.hatRot} 12 5.4) scale(${params.hatScale})`;
|
|
334
|
-
const hatInner = `
|
|
335
|
-
<g filter="url(#${p}-shadow)">
|
|
336
|
-
<path d="${hatData.path}" fill="url(#${p}-hat)"/>
|
|
337
|
-
</g>
|
|
338
|
-
<g class="cuff-fluffy">${cuffSVG}</g>
|
|
339
|
-
<g class="star-piece" filter="url(#${p}-star-glow)">
|
|
340
|
-
<path d="${starPath}" fill="url(#${p}-star-yellow)" stroke="#ffffff" stroke-width="0.06" stroke-opacity="0.7" stroke-linejoin="round"/>
|
|
341
|
-
</g>
|
|
342
|
-
`;
|
|
343
|
-
|
|
344
|
-
stage.innerHTML = `
|
|
345
|
-
<style>
|
|
346
|
-
.edge-rim-tight { stroke-opacity: .9; }
|
|
347
|
-
.edge-rim-soft { stroke-opacity: .75; }
|
|
348
|
-
.edge-contact { stroke-opacity: .9; }
|
|
349
|
-
@media (prefers-color-scheme: light) {
|
|
350
|
-
.edge-rim-tight { stroke-opacity: .95; }
|
|
351
|
-
.edge-rim-soft { stroke-opacity: .85; }
|
|
352
|
-
.edge-contact { stroke-opacity: .95; }
|
|
353
|
-
.cloud-drop-shadow { flood-opacity: .13; }
|
|
354
|
-
.food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
|
|
355
|
-
.food-cloud > path {
|
|
356
|
-
stroke: #5870E8 !important;
|
|
357
|
-
stroke-width: 1.2 !important;
|
|
358
|
-
stroke-opacity: .9 !important;
|
|
359
|
-
vector-effect: non-scaling-stroke !important;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
</style>
|
|
363
|
-
<defs>
|
|
364
|
-
${commonDefs(p)}
|
|
365
|
-
<path id="${p}-cloud-shape" d="${CLOUD_D}"/>
|
|
366
|
-
</defs>
|
|
367
|
-
<g class="scene" transform="rotate(${params.bodyTilt} 12 12)">
|
|
368
|
-
<!-- 主层: 呼吸 group, 包住 cloudling + hat + mask 整体 scale -->
|
|
369
|
-
<g id="breath-group" style="transform-box: fill-box;">
|
|
370
|
-
${cloudling}
|
|
371
|
-
<!-- 帽子: 自己的 sway transform 套在静态摆位之上 -->
|
|
372
|
-
<g id="hat-static" transform="${hatStaticTransform}">
|
|
373
|
-
<g id="hat-sway" style="transform-origin: 12px 5.4px;">
|
|
374
|
-
${hatInner}
|
|
375
|
-
</g>
|
|
376
|
-
</g>
|
|
377
|
-
${mask}
|
|
378
|
-
</g>
|
|
379
|
-
<!-- 梦境层: Z 字 (在呼吸外, 不跟着主体起伏) -->
|
|
380
|
-
<g id="z-container"></g>
|
|
381
|
-
</g>
|
|
382
|
-
`;
|
|
383
|
-
|
|
384
|
-
svgRoot = document.getElementById('root-svg');
|
|
385
|
-
cloudGroup = document.getElementById('breath-group');
|
|
386
|
-
hatGroup = document.getElementById('hat-sway');
|
|
387
|
-
zContainer = document.getElementById('z-container');
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/* =========================================================================
|
|
391
|
-
* Z 字 lifecycle 管理
|
|
392
|
-
* ========================================================================= */
|
|
393
|
-
const zList = []; // { bornAt, el }
|
|
394
|
-
let lastZSpawn = -999;
|
|
395
|
-
|
|
396
|
-
function spawnZ(now) {
|
|
397
|
-
const p = 'sl';
|
|
398
|
-
const el = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
399
|
-
el.setAttribute('class', 'z-particle');
|
|
400
|
-
el.innerHTML = `
|
|
401
|
-
<g filter="url(#${p}-z-glow)">
|
|
402
|
-
<path d="${makeZPath()}" fill="none" stroke="url(#${p}-z-grad)" stroke-width="${params.zStroke}" stroke-linecap="round" stroke-linejoin="round"/>
|
|
403
|
-
</g>
|
|
404
|
-
`;
|
|
405
|
-
zContainer.appendChild(el);
|
|
406
|
-
zList.push({ bornAt: now, el });
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function updateZs(t) {
|
|
410
|
-
// 出生新 Z
|
|
411
|
-
if (params.zEnable && t - lastZSpawn >= params.zInterval) {
|
|
412
|
-
spawnZ(t);
|
|
413
|
-
lastZSpawn = t;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// 更新所有 Z 的 transform / opacity
|
|
417
|
-
for (let i = zList.length - 1; i >= 0; i--) {
|
|
418
|
-
const z = zList[i];
|
|
419
|
-
const age = t - z.bornAt;
|
|
420
|
-
const life = params.zLifetime;
|
|
421
|
-
|
|
422
|
-
if (age >= life) {
|
|
423
|
-
z.el.remove();
|
|
424
|
-
zList.splice(i, 1);
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const u = age / life; // 0 → 1
|
|
429
|
-
|
|
430
|
-
// 位置: 起点 (zStartX, zStartY) → 飘升 + 右飘
|
|
431
|
-
const x = params.zStartX + params.zDriftX * u;
|
|
432
|
-
const y = params.zStartY - params.zRiseDist * u;
|
|
433
|
-
|
|
434
|
-
// 大小: 出生 max → 末段 min
|
|
435
|
-
const size = params.zMaxSize + (params.zMinSize - params.zMaxSize) * u;
|
|
436
|
-
|
|
437
|
-
// 透明度: 三段: 淡入 (0-0.18) → 平稳 (0.18-0.7) → 淡出 (0.7-1)
|
|
438
|
-
let opacity;
|
|
439
|
-
if (u < 0.18) {
|
|
440
|
-
opacity = (u / 0.18) * params.zMaxOpacity;
|
|
441
|
-
} else if (u < 0.7) {
|
|
442
|
-
opacity = params.zMaxOpacity;
|
|
443
|
-
} else {
|
|
444
|
-
opacity = params.zMaxOpacity * (1 - (u - 0.7) / 0.3);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// 微小的 sway (Z 字飘动时左右摆一点)
|
|
448
|
-
const sway = Math.sin(age * 2.0) * 0.3;
|
|
449
|
-
|
|
450
|
-
z.el.setAttribute('transform', `translate(${(x + sway).toFixed(2)} ${y.toFixed(2)}) scale(${size.toFixed(2)})`);
|
|
451
|
-
z.el.setAttribute('opacity', opacity.toFixed(3));
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/* =========================================================================
|
|
456
|
-
* 主循环
|
|
457
|
-
* ========================================================================= */
|
|
458
|
-
let startT = performance.now();
|
|
459
|
-
let pausedAt = 0;
|
|
460
|
-
let elapsedOffset = 0;
|
|
461
|
-
|
|
462
|
-
function loop(now) {
|
|
463
|
-
if (paused) {
|
|
464
|
-
requestAnimationFrame(loop);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
const t = (now - startT) / 1000 + elapsedOffset;
|
|
468
|
-
|
|
469
|
-
// 主层呼吸: scale 在 breathLow .. breathHigh 之间正弦
|
|
470
|
-
const breathPhase = (Math.cos(2 * Math.PI * t / params.breathPeriod) + 1) / 2; // 0..1
|
|
471
|
-
const breathScale = params.breathLow + (params.breathHigh - params.breathLow) * breathPhase;
|
|
472
|
-
if (cloudGroup) {
|
|
473
|
-
cloudGroup.setAttribute('transform', `translate(12 12) scale(${breathScale.toFixed(4)}) translate(-12 -12)`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// 次层帽子 sway: 异步 sin, 围绕 (12, 5.4) rotate
|
|
477
|
-
const hatPhase = Math.sin(2 * Math.PI * t / params.hatSwayPeriod);
|
|
478
|
-
const hatRotDeg = hatPhase * params.hatSwayAmp;
|
|
479
|
-
if (hatGroup) {
|
|
480
|
-
hatGroup.setAttribute('transform', `rotate(${hatRotDeg.toFixed(3)} 12 5.4)`);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Z 字
|
|
484
|
-
updateZs(t);
|
|
485
|
-
|
|
486
|
-
requestAnimationFrame(loop);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function resetClock() {
|
|
490
|
-
startT = performance.now();
|
|
491
|
-
elapsedOffset = 0;
|
|
492
|
-
zList.forEach(z => z.el.remove());
|
|
493
|
-
zList.length = 0;
|
|
494
|
-
lastZSpawn = -999;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
renderStage();
|
|
498
|
-
requestAnimationFrame(loop);
|
|
499
|
-
]]></script>
|
|
500
|
-
</svg>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="stage" xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 36 36" preserveAspectRatio="xMidYMid meet">
|
|
3
|
+
<g id="__cloudling-export-shims" opacity="0" pointer-events="none" style="display:none">
|
|
4
|
+
<g id="breath-group"></g>
|
|
5
|
+
<g id="hat-sway"></g>
|
|
6
|
+
<g id="root-svg"></g>
|
|
7
|
+
<g id="z-container"></g>
|
|
8
|
+
</g>
|
|
9
|
+
<script type="application/ecmascript"><![CDATA[
|
|
10
|
+
const CLOUD_D = "M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388z";
|
|
11
|
+
|
|
12
|
+
/* =========================================================================
|
|
13
|
+
* 默认锁定参数
|
|
14
|
+
* ========================================================================= */
|
|
15
|
+
const DEFAULTS = {
|
|
16
|
+
// 静帧摆位 (鹿鹿 2026-04-28 调好)
|
|
17
|
+
bodyTilt: -6,
|
|
18
|
+
hatTx: -2,
|
|
19
|
+
hatTy: -0.4,
|
|
20
|
+
hatRot: -10,
|
|
21
|
+
hatScale: 0.98,
|
|
22
|
+
maskTx: 0,
|
|
23
|
+
maskTy: -0.6,
|
|
24
|
+
maskRot: -9,
|
|
25
|
+
maskScale: 1.00,
|
|
26
|
+
|
|
27
|
+
// 主层: 呼吸
|
|
28
|
+
breathPeriod: 4,
|
|
29
|
+
breathLow: 0.96,
|
|
30
|
+
breathHigh: 1.02,
|
|
31
|
+
|
|
32
|
+
// 次层: 帽子独立 sway (异步)
|
|
33
|
+
hatSwayPeriod: 6,
|
|
34
|
+
hatSwayAmp: 2,
|
|
35
|
+
|
|
36
|
+
// 梦境层: Z 字
|
|
37
|
+
zEnable: 1,
|
|
38
|
+
zInterval: 1.8,
|
|
39
|
+
zLifetime: 2.9,
|
|
40
|
+
zStartX: 19.4,
|
|
41
|
+
zStartY: 3.6,
|
|
42
|
+
zRiseDist: 8,
|
|
43
|
+
zDriftX: 4,
|
|
44
|
+
zMaxSize: 1.6,
|
|
45
|
+
zMinSize: 1.3,
|
|
46
|
+
zStroke: 0.55,
|
|
47
|
+
zMaxOpacity: 0.45,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let params = { ...DEFAULTS };
|
|
51
|
+
let paused = false;
|
|
52
|
+
|
|
53
|
+
/* =========================================================================
|
|
54
|
+
* defs
|
|
55
|
+
* ========================================================================= */
|
|
56
|
+
function commonDefs(p) {
|
|
57
|
+
return `
|
|
58
|
+
<linearGradient id="${p}-body" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
59
|
+
<stop offset="0" stop-color="#93c5fd"/>
|
|
60
|
+
<stop offset="0.5" stop-color="#60a5fa"/>
|
|
61
|
+
<stop offset="1" stop-color="#3b82f6"/>
|
|
62
|
+
</linearGradient>
|
|
63
|
+
<linearGradient id="${p}-eye" x1="0" y1="3" x2="0" y2="21" gradientUnits="userSpaceOnUse">
|
|
64
|
+
<stop offset="0" stop-color="#B1A7FF"/>
|
|
65
|
+
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
66
|
+
<stop offset="1" stop-color="#3941FF"/>
|
|
67
|
+
</linearGradient>
|
|
68
|
+
<radialGradient id="${p}-hi" cx="0.45" cy="0.25" r="0.55">
|
|
69
|
+
<stop offset="0" stop-color="#93c5fd" stop-opacity="0.9"/>
|
|
70
|
+
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
71
|
+
</radialGradient>
|
|
72
|
+
<linearGradient id="${p}-hat" x1="11" y1="-3" x2="14" y2="6" gradientUnits="userSpaceOnUse">
|
|
73
|
+
<stop offset="0" stop-color="#7AA0FF"/>
|
|
74
|
+
<stop offset="0.45" stop-color="#3941FF"/>
|
|
75
|
+
<stop offset="1" stop-color="#1F2BC2"/>
|
|
76
|
+
</linearGradient>
|
|
77
|
+
<linearGradient id="${p}-cuff" x1="12" y1="3.0" x2="12" y2="8.0" gradientUnits="userSpaceOnUse">
|
|
78
|
+
<stop offset="0" stop-color="#93c5fd"/>
|
|
79
|
+
<stop offset="0.5" stop-color="#fafbff"/>
|
|
80
|
+
<stop offset="1" stop-color="#e8ecf6"/>
|
|
81
|
+
</linearGradient>
|
|
82
|
+
<linearGradient id="${p}-star-yellow" x1="0" y1="-2" x2="0" y2="2" gradientUnits="userSpaceOnUse">
|
|
83
|
+
<stop offset="0" stop-color="#FFE9A8"/>
|
|
84
|
+
<stop offset="0.5" stop-color="#FFD360"/>
|
|
85
|
+
<stop offset="1" stop-color="#F5A623"/>
|
|
86
|
+
</linearGradient>
|
|
87
|
+
<filter id="${p}-star-glow" x="-80%" y="-80%" width="260%" height="260%">
|
|
88
|
+
<feDropShadow dx="0" dy="0.4" stdDeviation="0.5" flood-color="#F5A623" flood-opacity="0.32"/>
|
|
89
|
+
</filter>
|
|
90
|
+
<linearGradient id="${p}-z-grad" x1="0" y1="0" x2="0" y2="1">
|
|
91
|
+
<stop offset="0" stop-color="#B1A7FF"/>
|
|
92
|
+
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
93
|
+
<stop offset="1" stop-color="#3941FF"/>
|
|
94
|
+
</linearGradient>
|
|
95
|
+
<filter id="${p}-z-glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
96
|
+
<feDropShadow dx="0" dy="0.3" stdDeviation="0.35" flood-color="#7A9DFF" flood-opacity="0.45"/>
|
|
97
|
+
</filter>
|
|
98
|
+
<filter id="${p}-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
99
|
+
<feDropShadow class="cloud-drop-shadow" dx="0" dy="0.6" stdDeviation="0.8" flood-color="#3941FF" flood-opacity="0.35"/>
|
|
100
|
+
</filter>
|
|
101
|
+
<filter id="${p}-edge-blur" x="-40%" y="-40%" width="180%" height="180%">
|
|
102
|
+
<feGaussianBlur stdDeviation="0.16"/>
|
|
103
|
+
</filter>
|
|
104
|
+
<filter id="${p}-soft" x="-30%" y="-30%" width="160%" height="160%">
|
|
105
|
+
<feGaussianBlur stdDeviation="0.18"/>
|
|
106
|
+
</filter>
|
|
107
|
+
<clipPath id="${p}-clip"><path d="${CLOUD_D}"/></clipPath>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* =========================================================================
|
|
112
|
+
* 部件 (从 sleeping-static-pose v1 复制)
|
|
113
|
+
* ========================================================================= */
|
|
114
|
+
function makeFluffyCuff(p, cx, cy, rx, ry, asym, bumps) {
|
|
115
|
+
let parts = [];
|
|
116
|
+
const circles = [];
|
|
117
|
+
for (let i = 0; i < bumps; i++) {
|
|
118
|
+
const ang = (i * 2 * Math.PI) / bumps - Math.PI / 2;
|
|
119
|
+
const x = cx + Math.cos(ang) * rx;
|
|
120
|
+
const y = cy + Math.sin(ang) * ry;
|
|
121
|
+
const sizeY = -Math.sin(ang);
|
|
122
|
+
const sizeBoost = 0.18 * (sizeY + 1) / 2 + 0.10;
|
|
123
|
+
const jitter = ((i * 13) % 7) / 35;
|
|
124
|
+
let r = 0.62 + sizeBoost + jitter;
|
|
125
|
+
if (asym !== 0) {
|
|
126
|
+
const leftRight = Math.cos(ang);
|
|
127
|
+
r *= 1 + asym * leftRight * 0.28;
|
|
128
|
+
}
|
|
129
|
+
circles.push({ x, y, r });
|
|
130
|
+
}
|
|
131
|
+
const innerRy = ry + 0.40;
|
|
132
|
+
parts.push(`<g filter="url(#${p}-soft)">`);
|
|
133
|
+
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${(rx + 0.30).toFixed(2)}" ry="${(innerRy + 0.30).toFixed(2)}" fill="#7A9DFF" opacity="0.50"/>`);
|
|
134
|
+
circles.forEach(c => {
|
|
135
|
+
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${(c.r + 0.30).toFixed(2)}" fill="#7A9DFF" opacity="0.50"/>`);
|
|
136
|
+
});
|
|
137
|
+
parts.push(`</g>`);
|
|
138
|
+
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${(rx + 0.15).toFixed(2)}" ry="${(innerRy + 0.15).toFixed(2)}" fill="#aab3c6" opacity="0.65"/>`);
|
|
139
|
+
circles.forEach(c => {
|
|
140
|
+
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${(c.r + 0.15).toFixed(2)}" fill="#aab3c6" opacity="0.65"/>`);
|
|
141
|
+
});
|
|
142
|
+
parts.push(`<ellipse cx="${cx}" cy="${cy}" rx="${rx.toFixed(2)}" ry="${innerRy.toFixed(2)}" fill="url(#${p}-cuff)"/>`);
|
|
143
|
+
circles.forEach(c => {
|
|
144
|
+
parts.push(`<circle cx="${c.x.toFixed(2)}" cy="${c.y.toFixed(2)}" r="${c.r.toFixed(2)}" fill="url(#${p}-cuff)"/>`);
|
|
145
|
+
});
|
|
146
|
+
parts.push(`<ellipse cx="${cx}" cy="${(cy - ry * 0.45).toFixed(2)}" rx="${(rx * 0.78).toFixed(2)}" ry="${(ry * 0.32).toFixed(2)}" fill="none" stroke="#ffffff" stroke-opacity="0.55" stroke-width="0.14"/>`);
|
|
147
|
+
return parts.join('');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function makeBentHat(bend) {
|
|
151
|
+
const baseLeft = { x: 8.5, y: 5.0 };
|
|
152
|
+
const baseRight = { x: 15.5, y: 5.0 };
|
|
153
|
+
const elbowX = 12 + bend * 2.2;
|
|
154
|
+
const elbowY = -0.5 + bend * 2.3;
|
|
155
|
+
const tipX = 11 - bend * 5.5;
|
|
156
|
+
const tipY = 0.0 + bend * 0.5;
|
|
157
|
+
const tipBottomX = tipX + 1.2 + bend * 0.4;
|
|
158
|
+
const tipBottomY = tipY + 1.4 + bend * 0.3;
|
|
159
|
+
const c1 = { x: baseRight.x + 0.5, y: baseRight.y - 1.5 };
|
|
160
|
+
const c2 = { x: elbowX + 0.8, y: elbowY + 1.0 };
|
|
161
|
+
const c3 = { x: elbowX - 0.5, y: elbowY - 0.8 };
|
|
162
|
+
const c4 = { x: tipX + 1.5, y: tipY - 0.5 };
|
|
163
|
+
const c5 = { x: tipX - 0.3, y: tipY + 0.6 };
|
|
164
|
+
const c6 = { x: tipBottomX - 1.0, y: tipBottomY };
|
|
165
|
+
const c7 = { x: 8.5, y: 4.0 };
|
|
166
|
+
return {
|
|
167
|
+
path: `M ${baseLeft.x} ${baseLeft.y} Q 12 5.55 ${baseRight.x} ${baseRight.y} C ${c1.x.toFixed(2)} ${c1.y.toFixed(2)} ${c2.x.toFixed(2)} ${c2.y.toFixed(2)} ${elbowX.toFixed(2)} ${elbowY.toFixed(2)} C ${c3.x.toFixed(2)} ${c3.y.toFixed(2)} ${c4.x.toFixed(2)} ${c4.y.toFixed(2)} ${tipX.toFixed(2)} ${tipY.toFixed(2)} C ${c5.x.toFixed(2)} ${c5.y.toFixed(2)} ${c6.x.toFixed(2)} ${c6.y.toFixed(2)} ${tipBottomX.toFixed(2)} ${tipBottomY.toFixed(2)} C ${c7.x.toFixed(2)} ${c7.y.toFixed(2)} 8.0 4.5 ${baseLeft.x} ${baseLeft.y} Z`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function makeStar(cx, cy, r, rotationDeg, cornerRound, chubbiness, innerRatio) {
|
|
172
|
+
const innerR = r * innerRatio;
|
|
173
|
+
const rotRad = (rotationDeg * Math.PI) / 180;
|
|
174
|
+
const verts = [];
|
|
175
|
+
for (let i = 0; i < 10; i++) {
|
|
176
|
+
const ang = (i * Math.PI / 5) + rotRad - Math.PI / 2;
|
|
177
|
+
const radius = i % 2 === 0 ? r : innerR;
|
|
178
|
+
verts.push({ x: cx + Math.cos(ang) * radius, y: cy + Math.sin(ang) * radius });
|
|
179
|
+
}
|
|
180
|
+
const inP = [], outP = [];
|
|
181
|
+
for (let i = 0; i < 10; i++) {
|
|
182
|
+
const prev = verts[(i - 1 + 10) % 10];
|
|
183
|
+
const cur = verts[i];
|
|
184
|
+
const next = verts[(i + 1) % 10];
|
|
185
|
+
const v1x = prev.x - cur.x, v1y = prev.y - cur.y;
|
|
186
|
+
const v1Len = Math.sqrt(v1x * v1x + v1y * v1y);
|
|
187
|
+
const v2x = next.x - cur.x, v2y = next.y - cur.y;
|
|
188
|
+
const v2Len = Math.sqrt(v2x * v2x + v2y * v2y);
|
|
189
|
+
const cr = Math.min(cornerRound, v1Len * 0.48, v2Len * 0.48);
|
|
190
|
+
inP[i] = { x: cur.x + (v1x / v1Len) * cr, y: cur.y + (v1y / v1Len) * cr };
|
|
191
|
+
outP[i] = { x: cur.x + (v2x / v2Len) * cr, y: cur.y + (v2y / v2Len) * cr };
|
|
192
|
+
}
|
|
193
|
+
let path = `M ${inP[0].x.toFixed(2)} ${inP[0].y.toFixed(2)} `;
|
|
194
|
+
for (let i = 0; i < 10; i++) {
|
|
195
|
+
path += `Q ${verts[i].x.toFixed(2)} ${verts[i].y.toFixed(2)} ${outP[i].x.toFixed(2)} ${outP[i].y.toFixed(2)} `;
|
|
196
|
+
const nextI = (i + 1) % 10;
|
|
197
|
+
const start = outP[i], end = inP[nextI];
|
|
198
|
+
if (chubbiness > 0) {
|
|
199
|
+
const midX = (start.x + end.x) / 2, midY = (start.y + end.y) / 2;
|
|
200
|
+
const dx = midX - cx, dy = midY - cy;
|
|
201
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
202
|
+
const offset = chubbiness * r * 0.32;
|
|
203
|
+
const ctrlX = midX + (dx / len) * offset, ctrlY = midY + (dy / len) * offset;
|
|
204
|
+
path += `Q ${ctrlX.toFixed(2)} ${ctrlY.toFixed(2)} ${end.x.toFixed(2)} ${end.y.toFixed(2)} `;
|
|
205
|
+
} else {
|
|
206
|
+
path += `L ${end.x.toFixed(2)} ${end.y.toFixed(2)} `;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
path += 'Z';
|
|
210
|
+
return path;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const STAR_LOCKED = {
|
|
214
|
+
cx: 8.55, cy: 0.35, size: 1.00, rotation: 30,
|
|
215
|
+
cornerRound: 0.27, chubbiness: 0, innerRatio: 0.56
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const SLEEP_MASK_PATH = {
|
|
219
|
+
d: "M488.728,341.587l-7.846-2.599c-0.405-16.992-7.605-47.34-48.625-70.941c-38.301-22.036-96.652-33.215-173.431-33.224c-76.822,0-135.146,11.165-173.438,33.183c-42.335,24.344-48.659,55.883-48.661,72.516c0,2.608,0.135,5.183,0.352,7.735h-2.649c-0.001,0-0.001,0-0.002,0c-9.635,0-17.446,7.811-17.448,17.445c-0.001,9.636,7.81,17.449,17.445,17.45l13,0.001c15.257,28.43,45.267,47.814,79.728,47.818c20.016-0.001,36.647-11.511,54.256-23.695c20.65-14.29,44.048-30.481,77.388-30.481c33.359,0.003,56.757,16.204,77.402,30.497c17.61,12.192,34.24,23.707,54.251,23.709h0.012c24.153,0,46.861-9.404,63.941-26.481c8.937-8.936,15.751-19.422,20.239-30.839l3.111,1.031c1.82,0.604,3.67,0.89,5.489,0.89c7.318,0,14.133-4.64,16.56-11.966C502.835,354.489,497.876,344.617,488.728,341.587Z",
|
|
220
|
+
bboxX: 15, bboxY: 232, bboxW: 487, bboxH: 200
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
function makeSleepMask(p, tx, ty, rot, scale) {
|
|
224
|
+
const cx = 12 + tx;
|
|
225
|
+
const cy = 12 + ty;
|
|
226
|
+
const W = 16.61 * scale;
|
|
227
|
+
const H = 7.55 * scale;
|
|
228
|
+
const eyeScale = 1.18;
|
|
229
|
+
const eyeRot = 5;
|
|
230
|
+
const eyeOffsetY = -0.25;
|
|
231
|
+
const lashW = 2.45;
|
|
232
|
+
const lashH = 0.70;
|
|
233
|
+
const lashStroke = 0.45;
|
|
234
|
+
const lashGap = 6.00 * scale;
|
|
235
|
+
const lashHairLen = 0.66;
|
|
236
|
+
const lashHairCount = 3;
|
|
237
|
+
|
|
238
|
+
const sx = W / SLEEP_MASK_PATH.bboxW;
|
|
239
|
+
const sy = H / SLEEP_MASK_PATH.bboxH;
|
|
240
|
+
const cxOrig = SLEEP_MASK_PATH.bboxX + SLEEP_MASK_PATH.bboxW / 2;
|
|
241
|
+
const cyOrig = SLEEP_MASK_PATH.bboxY + SLEEP_MASK_PATH.bboxH / 2;
|
|
242
|
+
const body = `<g transform="translate(${cx} ${cy}) scale(${sx.toFixed(5)} ${sy.toFixed(5)}) translate(${(-cxOrig).toFixed(2)} ${(-cyOrig).toFixed(2)})">
|
|
243
|
+
<path d="${SLEEP_MASK_PATH.d}" fill="#22305A" stroke="#000000" stroke-width="0.22" stroke-linejoin="round" stroke-linecap="round" vector-effect="non-scaling-stroke"/>
|
|
244
|
+
</g>`;
|
|
245
|
+
|
|
246
|
+
const arcLocal = `<path d="M ${(-lashW/2).toFixed(2)} 0 Q 0 ${(lashH * 2).toFixed(2)} ${(lashW/2).toFixed(2)} 0" fill="none" stroke="#FFFFFF" stroke-width="${lashStroke}" stroke-linecap="round"/>`;
|
|
247
|
+
|
|
248
|
+
const hairs = [];
|
|
249
|
+
const tPad = 0.18;
|
|
250
|
+
for (let i = 0; i < lashHairCount; i++) {
|
|
251
|
+
const t = tPad + (i / (lashHairCount - 1)) * (1 - 2 * tPad);
|
|
252
|
+
const sxh = (1-t)*(1-t)*(-lashW/2) + 2*(1-t)*t*0 + t*t*(lashW/2);
|
|
253
|
+
const syh = (1-t)*(1-t)*0 + 2*(1-t)*t*(2*lashH) + t*t*0;
|
|
254
|
+
const tangX = 2*(1-t)*(0 - (-lashW/2)) + 2*t*((lashW/2) - 0);
|
|
255
|
+
const tangY = 2*(1-t)*((2*lashH) - 0) + 2*t*(0 - (2*lashH));
|
|
256
|
+
const tLen = Math.hypot(tangX, tangY);
|
|
257
|
+
const normX1 = -tangY / tLen, normY1 = tangX / tLen;
|
|
258
|
+
const normX2 = tangY / tLen, normY2 = -tangX / tLen;
|
|
259
|
+
const useFirst = normY1 > 0;
|
|
260
|
+
const dirX = useFirst ? normX1 : normX2;
|
|
261
|
+
const dirY = useFirst ? normY1 : normY2;
|
|
262
|
+
const ex = sxh + dirX * lashHairLen;
|
|
263
|
+
const ey = syh + dirY * lashHairLen;
|
|
264
|
+
hairs.push(`<line x1="${sxh.toFixed(2)}" y1="${syh.toFixed(2)}" x2="${ex.toFixed(2)}" y2="${ey.toFixed(2)}" stroke="#FFFFFF" stroke-width="${(lashStroke * 0.85).toFixed(2)}" stroke-linecap="round" opacity="0.92"/>`);
|
|
265
|
+
}
|
|
266
|
+
const hairsLocal = hairs.join('');
|
|
267
|
+
|
|
268
|
+
const eyeL_cx = cx - lashGap / 2;
|
|
269
|
+
const eyeR_cx = cx + lashGap / 2;
|
|
270
|
+
const wrapEye = (ecx, sign) => `<g transform="translate(${ecx.toFixed(2)} ${(cy + eyeOffsetY).toFixed(2)}) rotate(${(sign * eyeRot).toFixed(2)}) scale(${eyeScale.toFixed(3)})">
|
|
271
|
+
${arcLocal}
|
|
272
|
+
${hairsLocal}
|
|
273
|
+
</g>`;
|
|
274
|
+
const lashes = wrapEye(eyeL_cx, -1) + wrapEye(eyeR_cx, +1);
|
|
275
|
+
|
|
276
|
+
return `
|
|
277
|
+
<g class="sleep-mask" transform="rotate(${rot} ${cx} ${cy})" filter="url(#${p}-shadow)">
|
|
278
|
+
${body}
|
|
279
|
+
${lashes}
|
|
280
|
+
</g>
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function makeCloudling(p) {
|
|
285
|
+
return `
|
|
286
|
+
<g class="cloudling-base">
|
|
287
|
+
<g class="edge-system">
|
|
288
|
+
<use class="edge-contact" href="#${p}-cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.4" stroke-linejoin="round" transform="translate(0 0.18)" filter="url(#${p}-edge-blur)"/>
|
|
289
|
+
<use class="edge-rim-soft" href="#${p}-cloud-shape" fill="none" stroke="#7A9DFF" stroke-width="1.2" stroke-linejoin="round" filter="url(#${p}-edge-blur)"/>
|
|
290
|
+
<use class="edge-rim-tight" href="#${p}-cloud-shape" fill="none" stroke="#aab3c6" stroke-width="0.8" stroke-linejoin="round"/>
|
|
291
|
+
</g>
|
|
292
|
+
<g filter="url(#${p}-shadow)">
|
|
293
|
+
<use href="#${p}-cloud-shape" fill="url(#${p}-body)"/>
|
|
294
|
+
<rect x="1" y="2" width="22" height="11" fill="url(#${p}-hi)" clip-path="url(#${p}-clip)"/>
|
|
295
|
+
</g>
|
|
296
|
+
</g>
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* =========================================================================
|
|
301
|
+
* Z 字 path - 苹果精致风手写体
|
|
302
|
+
* 流畅手写 Z = 上横 + 下笔斜对角(略带圆滑) + 下横
|
|
303
|
+
* 用 stroke 描边 + linecap=round + 紫蓝渐变 + glow
|
|
304
|
+
* ========================================================================= */
|
|
305
|
+
function makeZPath() {
|
|
306
|
+
// 在局部坐标 (-1, -1) 到 (1, 1) 区间内画 Z
|
|
307
|
+
// 上横: (-1, -1) → (1, -1) 但末端略弯 (Q 控制点)
|
|
308
|
+
// 斜杠: (1, -1) → (-1, 1) 略带 S 弧
|
|
309
|
+
// 下横: (-1, 1) → (1, 1) 末端略弯
|
|
310
|
+
return "M -1.0 -1.0 Q 0.0 -1.0 1.0 -1.0 Q 1.05 -0.85 0.6 -0.4 Q 0 0.1 -0.6 0.6 Q -1.05 0.95 -1.0 1.0 Q 0.0 1.0 1.0 1.0";
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* =========================================================================
|
|
314
|
+
* 渲染静态结构 + 用 JS RAF 驱动 transform / Z 字
|
|
315
|
+
* ========================================================================= */
|
|
316
|
+
const stage = document.getElementById('stage');
|
|
317
|
+
let svgRoot = null;
|
|
318
|
+
let cloudGroup = null;
|
|
319
|
+
let hatGroup = null;
|
|
320
|
+
let zContainer = null;
|
|
321
|
+
|
|
322
|
+
function renderStage() {
|
|
323
|
+
const p = 'sl';
|
|
324
|
+
const hatData = makeBentHat(0.4);
|
|
325
|
+
const cuffSVG = makeFluffyCuff(p, 12, 5.4, 3.7, 0.85, 0, 18);
|
|
326
|
+
const s = STAR_LOCKED;
|
|
327
|
+
const starPath = makeStar(s.cx, s.cy, s.size, s.rotation, s.cornerRound, s.chubbiness, s.innerRatio);
|
|
328
|
+
const cloudling = makeCloudling(p);
|
|
329
|
+
const mask = makeSleepMask(p, params.maskTx, params.maskTy, params.maskRot, params.maskScale);
|
|
330
|
+
|
|
331
|
+
// 帽子结构: 整顶帽子作为一个 group, transform-origin 在 cuff 底部 (12, 5.4)
|
|
332
|
+
// 帽子 sway 围绕这个点 rotate
|
|
333
|
+
const hatStaticTransform = `translate(${params.hatTx} ${params.hatTy}) rotate(${params.hatRot} 12 5.4) scale(${params.hatScale})`;
|
|
334
|
+
const hatInner = `
|
|
335
|
+
<g filter="url(#${p}-shadow)">
|
|
336
|
+
<path d="${hatData.path}" fill="url(#${p}-hat)"/>
|
|
337
|
+
</g>
|
|
338
|
+
<g class="cuff-fluffy">${cuffSVG}</g>
|
|
339
|
+
<g class="star-piece" filter="url(#${p}-star-glow)">
|
|
340
|
+
<path d="${starPath}" fill="url(#${p}-star-yellow)" stroke="#ffffff" stroke-width="0.06" stroke-opacity="0.7" stroke-linejoin="round"/>
|
|
341
|
+
</g>
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
stage.innerHTML = `
|
|
345
|
+
<style>
|
|
346
|
+
.edge-rim-tight { stroke-opacity: .9; }
|
|
347
|
+
.edge-rim-soft { stroke-opacity: .75; }
|
|
348
|
+
.edge-contact { stroke-opacity: .9; }
|
|
349
|
+
@media (prefers-color-scheme: light) {
|
|
350
|
+
.edge-rim-tight { stroke-opacity: .95; }
|
|
351
|
+
.edge-rim-soft { stroke-opacity: .85; }
|
|
352
|
+
.edge-contact { stroke-opacity: .95; }
|
|
353
|
+
.cloud-drop-shadow { flood-opacity: .13; }
|
|
354
|
+
.food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
|
|
355
|
+
.food-cloud > path {
|
|
356
|
+
stroke: #5870E8 !important;
|
|
357
|
+
stroke-width: 1.2 !important;
|
|
358
|
+
stroke-opacity: .9 !important;
|
|
359
|
+
vector-effect: non-scaling-stroke !important;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
</style>
|
|
363
|
+
<defs>
|
|
364
|
+
${commonDefs(p)}
|
|
365
|
+
<path id="${p}-cloud-shape" d="${CLOUD_D}"/>
|
|
366
|
+
</defs>
|
|
367
|
+
<g class="scene" transform="rotate(${params.bodyTilt} 12 12)">
|
|
368
|
+
<!-- 主层: 呼吸 group, 包住 cloudling + hat + mask 整体 scale -->
|
|
369
|
+
<g id="breath-group" style="transform-box: fill-box;">
|
|
370
|
+
${cloudling}
|
|
371
|
+
<!-- 帽子: 自己的 sway transform 套在静态摆位之上 -->
|
|
372
|
+
<g id="hat-static" transform="${hatStaticTransform}">
|
|
373
|
+
<g id="hat-sway" style="transform-origin: 12px 5.4px;">
|
|
374
|
+
${hatInner}
|
|
375
|
+
</g>
|
|
376
|
+
</g>
|
|
377
|
+
${mask}
|
|
378
|
+
</g>
|
|
379
|
+
<!-- 梦境层: Z 字 (在呼吸外, 不跟着主体起伏) -->
|
|
380
|
+
<g id="z-container"></g>
|
|
381
|
+
</g>
|
|
382
|
+
`;
|
|
383
|
+
|
|
384
|
+
svgRoot = document.getElementById('root-svg');
|
|
385
|
+
cloudGroup = document.getElementById('breath-group');
|
|
386
|
+
hatGroup = document.getElementById('hat-sway');
|
|
387
|
+
zContainer = document.getElementById('z-container');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* =========================================================================
|
|
391
|
+
* Z 字 lifecycle 管理
|
|
392
|
+
* ========================================================================= */
|
|
393
|
+
const zList = []; // { bornAt, el }
|
|
394
|
+
let lastZSpawn = -999;
|
|
395
|
+
|
|
396
|
+
function spawnZ(now) {
|
|
397
|
+
const p = 'sl';
|
|
398
|
+
const el = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
399
|
+
el.setAttribute('class', 'z-particle');
|
|
400
|
+
el.innerHTML = `
|
|
401
|
+
<g filter="url(#${p}-z-glow)">
|
|
402
|
+
<path d="${makeZPath()}" fill="none" stroke="url(#${p}-z-grad)" stroke-width="${params.zStroke}" stroke-linecap="round" stroke-linejoin="round"/>
|
|
403
|
+
</g>
|
|
404
|
+
`;
|
|
405
|
+
zContainer.appendChild(el);
|
|
406
|
+
zList.push({ bornAt: now, el });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function updateZs(t) {
|
|
410
|
+
// 出生新 Z
|
|
411
|
+
if (params.zEnable && t - lastZSpawn >= params.zInterval) {
|
|
412
|
+
spawnZ(t);
|
|
413
|
+
lastZSpawn = t;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 更新所有 Z 的 transform / opacity
|
|
417
|
+
for (let i = zList.length - 1; i >= 0; i--) {
|
|
418
|
+
const z = zList[i];
|
|
419
|
+
const age = t - z.bornAt;
|
|
420
|
+
const life = params.zLifetime;
|
|
421
|
+
|
|
422
|
+
if (age >= life) {
|
|
423
|
+
z.el.remove();
|
|
424
|
+
zList.splice(i, 1);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const u = age / life; // 0 → 1
|
|
429
|
+
|
|
430
|
+
// 位置: 起点 (zStartX, zStartY) → 飘升 + 右飘
|
|
431
|
+
const x = params.zStartX + params.zDriftX * u;
|
|
432
|
+
const y = params.zStartY - params.zRiseDist * u;
|
|
433
|
+
|
|
434
|
+
// 大小: 出生 max → 末段 min
|
|
435
|
+
const size = params.zMaxSize + (params.zMinSize - params.zMaxSize) * u;
|
|
436
|
+
|
|
437
|
+
// 透明度: 三段: 淡入 (0-0.18) → 平稳 (0.18-0.7) → 淡出 (0.7-1)
|
|
438
|
+
let opacity;
|
|
439
|
+
if (u < 0.18) {
|
|
440
|
+
opacity = (u / 0.18) * params.zMaxOpacity;
|
|
441
|
+
} else if (u < 0.7) {
|
|
442
|
+
opacity = params.zMaxOpacity;
|
|
443
|
+
} else {
|
|
444
|
+
opacity = params.zMaxOpacity * (1 - (u - 0.7) / 0.3);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 微小的 sway (Z 字飘动时左右摆一点)
|
|
448
|
+
const sway = Math.sin(age * 2.0) * 0.3;
|
|
449
|
+
|
|
450
|
+
z.el.setAttribute('transform', `translate(${(x + sway).toFixed(2)} ${y.toFixed(2)}) scale(${size.toFixed(2)})`);
|
|
451
|
+
z.el.setAttribute('opacity', opacity.toFixed(3));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* =========================================================================
|
|
456
|
+
* 主循环
|
|
457
|
+
* ========================================================================= */
|
|
458
|
+
let startT = performance.now();
|
|
459
|
+
let pausedAt = 0;
|
|
460
|
+
let elapsedOffset = 0;
|
|
461
|
+
|
|
462
|
+
function loop(now) {
|
|
463
|
+
if (paused) {
|
|
464
|
+
requestAnimationFrame(loop);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const t = (now - startT) / 1000 + elapsedOffset;
|
|
468
|
+
|
|
469
|
+
// 主层呼吸: scale 在 breathLow .. breathHigh 之间正弦
|
|
470
|
+
const breathPhase = (Math.cos(2 * Math.PI * t / params.breathPeriod) + 1) / 2; // 0..1
|
|
471
|
+
const breathScale = params.breathLow + (params.breathHigh - params.breathLow) * breathPhase;
|
|
472
|
+
if (cloudGroup) {
|
|
473
|
+
cloudGroup.setAttribute('transform', `translate(12 12) scale(${breathScale.toFixed(4)}) translate(-12 -12)`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 次层帽子 sway: 异步 sin, 围绕 (12, 5.4) rotate
|
|
477
|
+
const hatPhase = Math.sin(2 * Math.PI * t / params.hatSwayPeriod);
|
|
478
|
+
const hatRotDeg = hatPhase * params.hatSwayAmp;
|
|
479
|
+
if (hatGroup) {
|
|
480
|
+
hatGroup.setAttribute('transform', `rotate(${hatRotDeg.toFixed(3)} 12 5.4)`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Z 字
|
|
484
|
+
updateZs(t);
|
|
485
|
+
|
|
486
|
+
requestAnimationFrame(loop);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function resetClock() {
|
|
490
|
+
startT = performance.now();
|
|
491
|
+
elapsedOffset = 0;
|
|
492
|
+
zList.forEach(z => z.el.remove());
|
|
493
|
+
zList.length = 0;
|
|
494
|
+
lastZSpawn = -999;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
renderStage();
|
|
498
|
+
requestAnimationFrame(loop);
|
|
499
|
+
]]></script>
|
|
500
|
+
</svg>
|