@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.
Files changed (141) hide show
  1. package/assets/calico/calico-idle-follow.svg +324 -324
  2. package/assets/calico.svg +324 -324
  3. package/assets/clawd/LICENSE +22 -22
  4. package/assets/clawd/clawd-about-hero.svg +202 -202
  5. package/assets/clawd/clawd-collapse-sleep.svg +247 -247
  6. package/assets/clawd/clawd-error.svg +94 -94
  7. package/assets/clawd/clawd-happy.svg +160 -160
  8. package/assets/clawd/clawd-headphones-groove.svg +194 -194
  9. package/assets/clawd/clawd-idle-bubble.svg +180 -180
  10. package/assets/clawd/clawd-idle-collapse.svg +101 -101
  11. package/assets/clawd/clawd-idle-doze.svg +72 -72
  12. package/assets/clawd/clawd-idle-follow.svg +64 -64
  13. package/assets/clawd/clawd-idle-living.svg +195 -195
  14. package/assets/clawd/clawd-idle-look.svg +115 -115
  15. package/assets/clawd/clawd-idle-reading.svg +252 -252
  16. package/assets/clawd/clawd-idle-yawn.svg +158 -158
  17. package/assets/clawd/clawd-mini-alert.svg +129 -129
  18. package/assets/clawd/clawd-mini-crabwalk.svg +76 -76
  19. package/assets/clawd/clawd-mini-enter-sleep.svg +64 -64
  20. package/assets/clawd/clawd-mini-enter.svg +114 -114
  21. package/assets/clawd/clawd-mini-happy.svg +124 -124
  22. package/assets/clawd/clawd-mini-idle.svg +83 -83
  23. package/assets/clawd/clawd-mini-peek.svg +82 -82
  24. package/assets/clawd/clawd-mini-sleep.svg +112 -112
  25. package/assets/clawd/clawd-mini-typing.svg +153 -153
  26. package/assets/clawd/clawd-notification.svg +438 -438
  27. package/assets/clawd/clawd-react-annoyed.svg +167 -167
  28. package/assets/clawd/clawd-react-double-jump.svg +229 -229
  29. package/assets/clawd/clawd-react-double.svg +108 -108
  30. package/assets/clawd/clawd-react-drag.svg +102 -102
  31. package/assets/clawd/clawd-react-left.svg +102 -102
  32. package/assets/clawd/clawd-react-right.svg +102 -102
  33. package/assets/clawd/clawd-sleeping.svg +117 -117
  34. package/assets/clawd/clawd-static-base.svg +20 -20
  35. package/assets/clawd/clawd-wake.svg +277 -277
  36. package/assets/clawd/clawd-working-building.svg +329 -329
  37. package/assets/clawd/clawd-working-carrying.svg +178 -178
  38. package/assets/clawd/clawd-working-debugger.svg +245 -245
  39. package/assets/clawd/clawd-working-juggling.svg +183 -183
  40. package/assets/clawd/clawd-working-sweeping.svg +248 -248
  41. package/assets/clawd/clawd-working-thinking.svg +501 -501
  42. package/assets/clawd/clawd-working-typing-boss.svg +307 -307
  43. package/assets/clawd/clawd-working-typing.svg +273 -273
  44. package/assets/clawd/clawd-working-ultrathink.svg +166 -166
  45. package/assets/clawd/clawd-working-wizard.svg +356 -356
  46. package/assets/clawd.svg +64 -64
  47. package/assets/cloudling/cloudling/cloudling-attention.svg +379 -379
  48. package/assets/cloudling/cloudling/cloudling-building.svg +667 -667
  49. package/assets/cloudling/cloudling/cloudling-carrying.svg +604 -604
  50. package/assets/cloudling/cloudling/cloudling-conducting.svg +616 -616
  51. package/assets/cloudling/cloudling/cloudling-dozing-to-sleeping.svg +578 -578
  52. package/assets/cloudling/cloudling/cloudling-dozing.svg +358 -358
  53. package/assets/cloudling/cloudling/cloudling-error.svg +428 -428
  54. package/assets/cloudling/cloudling/cloudling-idle-reading.svg +658 -658
  55. package/assets/cloudling/cloudling/cloudling-idle-to-dozing.svg +568 -568
  56. package/assets/cloudling/cloudling/cloudling-idle-to-sleeping.svg +811 -811
  57. package/assets/cloudling/cloudling/cloudling-idle.svg +255 -255
  58. package/assets/cloudling/cloudling/cloudling-juggling.svg +648 -648
  59. package/assets/cloudling/cloudling/cloudling-mini-alert.svg +389 -389
  60. package/assets/cloudling/cloudling/cloudling-mini-crabwalk.svg +335 -335
  61. package/assets/cloudling/cloudling/cloudling-mini-enter-roll-in.svg +225 -225
  62. package/assets/cloudling/cloudling/cloudling-mini-enter-sleep.svg +231 -231
  63. package/assets/cloudling/cloudling/cloudling-mini-happy.svg +370 -370
  64. package/assets/cloudling/cloudling/cloudling-mini-idle.svg +279 -279
  65. package/assets/cloudling/cloudling/cloudling-mini-peek.svg +327 -327
  66. package/assets/cloudling/cloudling/cloudling-mini-sleep.svg +260 -260
  67. package/assets/cloudling/cloudling/cloudling-mini-typing.svg +691 -691
  68. package/assets/cloudling/cloudling/cloudling-notification.svg +493 -493
  69. package/assets/cloudling/cloudling/cloudling-react-drag.svg +83 -83
  70. package/assets/cloudling/cloudling/cloudling-sleeping-to-idle.svg +555 -555
  71. package/assets/cloudling/cloudling/cloudling-sleeping.svg +500 -500
  72. package/assets/cloudling/cloudling/cloudling-sweeping.svg +292 -292
  73. package/assets/cloudling/cloudling/cloudling-thinking.svg +719 -719
  74. package/assets/cloudling/cloudling/cloudling-typing.svg +354 -354
  75. package/assets/cloudling/cloudling-attention.svg +379 -379
  76. package/assets/cloudling/cloudling-building.svg +667 -667
  77. package/assets/cloudling/cloudling-carrying.svg +604 -604
  78. package/assets/cloudling/cloudling-conducting.svg +616 -616
  79. package/assets/cloudling/cloudling-dozing-to-sleeping.svg +578 -578
  80. package/assets/cloudling/cloudling-dozing.svg +358 -358
  81. package/assets/cloudling/cloudling-error.svg +428 -428
  82. package/assets/cloudling/cloudling-idle-reading.svg +658 -658
  83. package/assets/cloudling/cloudling-idle-to-dozing.svg +568 -568
  84. package/assets/cloudling/cloudling-idle-to-sleeping.svg +811 -811
  85. package/assets/cloudling/cloudling-idle.svg +255 -255
  86. package/assets/cloudling/cloudling-juggling.svg +648 -648
  87. package/assets/cloudling/cloudling-mini-alert.svg +389 -389
  88. package/assets/cloudling/cloudling-mini-crabwalk.svg +335 -335
  89. package/assets/cloudling/cloudling-mini-enter-roll-in.svg +225 -225
  90. package/assets/cloudling/cloudling-mini-enter-sleep.svg +231 -231
  91. package/assets/cloudling/cloudling-mini-happy.svg +370 -370
  92. package/assets/cloudling/cloudling-mini-idle.svg +279 -279
  93. package/assets/cloudling/cloudling-mini-peek.svg +327 -327
  94. package/assets/cloudling/cloudling-mini-sleep.svg +260 -260
  95. package/assets/cloudling/cloudling-mini-typing.svg +691 -691
  96. package/assets/cloudling/cloudling-notification.svg +493 -493
  97. package/assets/cloudling/cloudling-react-drag.svg +83 -83
  98. package/assets/cloudling/cloudling-sleeping-to-idle.svg +555 -555
  99. package/assets/cloudling/cloudling-sleeping.svg +500 -500
  100. package/assets/cloudling/cloudling-sweeping.svg +292 -292
  101. package/assets/cloudling/cloudling-thinking.svg +719 -719
  102. package/assets/cloudling/cloudling-typing.svg +354 -354
  103. package/assets/cloudling.svg +255 -255
  104. package/assets/ghost-attention.svg +19 -19
  105. package/assets/ghost-carrying.svg +19 -19
  106. package/assets/ghost-collapsing.svg +19 -19
  107. package/assets/ghost-dozing.svg +23 -23
  108. package/assets/ghost-error.svg +19 -19
  109. package/assets/ghost-happy.svg +23 -23
  110. package/assets/ghost-idle.svg +11 -11
  111. package/assets/ghost-juggling.svg +19 -19
  112. package/assets/ghost-notification.svg +19 -19
  113. package/assets/ghost-react-double.svg +23 -23
  114. package/assets/ghost-react-drag.svg +23 -23
  115. package/assets/ghost-react-left.svg +19 -19
  116. package/assets/ghost-react-right.svg +19 -19
  117. package/assets/ghost-sleeping.svg +19 -19
  118. package/assets/ghost-sweeping.svg +19 -19
  119. package/assets/ghost-waking.svg +19 -19
  120. package/assets/ghost-yawning.svg +23 -23
  121. package/assets/ghost.svg +11 -11
  122. package/assets/robot-attention.svg +19 -19
  123. package/assets/robot-carrying.svg +19 -19
  124. package/assets/robot-collapsing.svg +19 -19
  125. package/assets/robot-dozing.svg +23 -23
  126. package/assets/robot-error.svg +19 -19
  127. package/assets/robot-happy.svg +23 -23
  128. package/assets/robot-idle.svg +11 -11
  129. package/assets/robot-juggling.svg +19 -19
  130. package/assets/robot-notification.svg +19 -19
  131. package/assets/robot-react-double.svg +23 -23
  132. package/assets/robot-react-drag.svg +23 -23
  133. package/assets/robot-react-left.svg +19 -19
  134. package/assets/robot-react-right.svg +19 -19
  135. package/assets/robot-sleeping.svg +19 -19
  136. package/assets/robot-sweeping.svg +19 -19
  137. package/assets/robot-waking.svg +19 -19
  138. package/assets/robot-yawning.svg +23 -23
  139. package/assets/robot.svg +11 -11
  140. package/manifest.json +280 -280
  141. 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>