@mumulinya167/ccm-pet-assets 2.0.0

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 (178) hide show
  1. package/README.md +4 -0
  2. package/assets/calico/calico-collapsing.apng +0 -0
  3. package/assets/calico/calico-dozing.apng +0 -0
  4. package/assets/calico/calico-error.apng +0 -0
  5. package/assets/calico/calico-happy.apng +0 -0
  6. package/assets/calico/calico-idle-follow.svg +324 -0
  7. package/assets/calico/calico-idle.apng +0 -0
  8. package/assets/calico/calico-mini-alert.apng +0 -0
  9. package/assets/calico/calico-mini-crabwalk.apng +0 -0
  10. package/assets/calico/calico-mini-enter.apng +0 -0
  11. package/assets/calico/calico-mini-happy.apng +0 -0
  12. package/assets/calico/calico-mini-idle.apng +0 -0
  13. package/assets/calico/calico-mini-peek.apng +0 -0
  14. package/assets/calico/calico-mini-sleep.apng +0 -0
  15. package/assets/calico/calico-notification.apng +0 -0
  16. package/assets/calico/calico-react-drag.apng +0 -0
  17. package/assets/calico/calico-react-left.apng +0 -0
  18. package/assets/calico/calico-react-poke.apng +0 -0
  19. package/assets/calico/calico-sleeping.apng +0 -0
  20. package/assets/calico/calico-thinking.apng +0 -0
  21. package/assets/calico/calico-waking.apng +0 -0
  22. package/assets/calico/calico-working-building.apng +0 -0
  23. package/assets/calico/calico-working-carrying.apng +0 -0
  24. package/assets/calico/calico-working-conducting.apng +0 -0
  25. package/assets/calico/calico-working-juggling.apng +0 -0
  26. package/assets/calico/calico-working-sweeping.apng +0 -0
  27. package/assets/calico/calico-working-typing.apng +0 -0
  28. package/assets/calico/calico-yawning.apng +0 -0
  29. package/assets/calico.svg +324 -0
  30. package/assets/clawd/LICENSE +22 -0
  31. package/assets/clawd/clawd-about-hero.svg +202 -0
  32. package/assets/clawd/clawd-collapse-sleep.svg +247 -0
  33. package/assets/clawd/clawd-error.svg +94 -0
  34. package/assets/clawd/clawd-happy.svg +161 -0
  35. package/assets/clawd/clawd-headphones-groove.svg +194 -0
  36. package/assets/clawd/clawd-idle-bubble.svg +180 -0
  37. package/assets/clawd/clawd-idle-collapse.svg +101 -0
  38. package/assets/clawd/clawd-idle-doze.svg +72 -0
  39. package/assets/clawd/clawd-idle-follow.svg +64 -0
  40. package/assets/clawd/clawd-idle-living.svg +196 -0
  41. package/assets/clawd/clawd-idle-look.svg +115 -0
  42. package/assets/clawd/clawd-idle-reading.svg +252 -0
  43. package/assets/clawd/clawd-idle-yawn.svg +158 -0
  44. package/assets/clawd/clawd-mini-alert.svg +129 -0
  45. package/assets/clawd/clawd-mini-crabwalk.svg +76 -0
  46. package/assets/clawd/clawd-mini-enter-sleep.svg +64 -0
  47. package/assets/clawd/clawd-mini-enter.svg +114 -0
  48. package/assets/clawd/clawd-mini-happy.svg +124 -0
  49. package/assets/clawd/clawd-mini-idle.svg +83 -0
  50. package/assets/clawd/clawd-mini-peek.svg +82 -0
  51. package/assets/clawd/clawd-mini-sleep.svg +112 -0
  52. package/assets/clawd/clawd-mini-typing.svg +153 -0
  53. package/assets/clawd/clawd-notification.svg +438 -0
  54. package/assets/clawd/clawd-react-annoyed.svg +167 -0
  55. package/assets/clawd/clawd-react-double-jump.svg +229 -0
  56. package/assets/clawd/clawd-react-double.svg +108 -0
  57. package/assets/clawd/clawd-react-drag.svg +102 -0
  58. package/assets/clawd/clawd-react-left.svg +102 -0
  59. package/assets/clawd/clawd-react-right.svg +102 -0
  60. package/assets/clawd/clawd-sleeping.svg +118 -0
  61. package/assets/clawd/clawd-static-base.svg +21 -0
  62. package/assets/clawd/clawd-wake.svg +277 -0
  63. package/assets/clawd/clawd-working-building.svg +329 -0
  64. package/assets/clawd/clawd-working-carrying.svg +178 -0
  65. package/assets/clawd/clawd-working-debugger.svg +245 -0
  66. package/assets/clawd/clawd-working-juggling.svg +183 -0
  67. package/assets/clawd/clawd-working-sweeping.svg +248 -0
  68. package/assets/clawd/clawd-working-thinking.svg +501 -0
  69. package/assets/clawd/clawd-working-typing-boss.svg +307 -0
  70. package/assets/clawd/clawd-working-typing.svg +273 -0
  71. package/assets/clawd/clawd-working-ultrathink.svg +166 -0
  72. package/assets/clawd/clawd-working-wizard.svg +356 -0
  73. package/assets/clawd.svg +64 -0
  74. package/assets/cloudling/cloudling/cloudling-attention.svg +380 -0
  75. package/assets/cloudling/cloudling/cloudling-building.svg +667 -0
  76. package/assets/cloudling/cloudling/cloudling-carrying.svg +604 -0
  77. package/assets/cloudling/cloudling/cloudling-conducting.svg +616 -0
  78. package/assets/cloudling/cloudling/cloudling-dozing-to-sleeping.svg +578 -0
  79. package/assets/cloudling/cloudling/cloudling-dozing.svg +358 -0
  80. package/assets/cloudling/cloudling/cloudling-error.svg +428 -0
  81. package/assets/cloudling/cloudling/cloudling-idle-reading.svg +658 -0
  82. package/assets/cloudling/cloudling/cloudling-idle-to-dozing.svg +568 -0
  83. package/assets/cloudling/cloudling/cloudling-idle-to-sleeping.svg +811 -0
  84. package/assets/cloudling/cloudling/cloudling-idle.svg +255 -0
  85. package/assets/cloudling/cloudling/cloudling-juggling.svg +648 -0
  86. package/assets/cloudling/cloudling/cloudling-mini-alert.svg +390 -0
  87. package/assets/cloudling/cloudling/cloudling-mini-crabwalk.svg +335 -0
  88. package/assets/cloudling/cloudling/cloudling-mini-enter-roll-in.svg +226 -0
  89. package/assets/cloudling/cloudling/cloudling-mini-enter-sleep.svg +232 -0
  90. package/assets/cloudling/cloudling/cloudling-mini-happy.svg +371 -0
  91. package/assets/cloudling/cloudling/cloudling-mini-idle.svg +279 -0
  92. package/assets/cloudling/cloudling/cloudling-mini-peek.svg +328 -0
  93. package/assets/cloudling/cloudling/cloudling-mini-sleep.svg +260 -0
  94. package/assets/cloudling/cloudling/cloudling-mini-typing.svg +692 -0
  95. package/assets/cloudling/cloudling/cloudling-notification.svg +494 -0
  96. package/assets/cloudling/cloudling/cloudling-react-drag.svg +83 -0
  97. package/assets/cloudling/cloudling/cloudling-sleeping-to-idle.svg +555 -0
  98. package/assets/cloudling/cloudling/cloudling-sleeping.svg +500 -0
  99. package/assets/cloudling/cloudling/cloudling-sweeping.svg +292 -0
  100. package/assets/cloudling/cloudling/cloudling-thinking.svg +720 -0
  101. package/assets/cloudling/cloudling/cloudling-typing.svg +354 -0
  102. package/assets/cloudling/cloudling-attention.svg +380 -0
  103. package/assets/cloudling/cloudling-building.svg +667 -0
  104. package/assets/cloudling/cloudling-carrying.svg +604 -0
  105. package/assets/cloudling/cloudling-conducting.svg +616 -0
  106. package/assets/cloudling/cloudling-dozing-to-sleeping.svg +578 -0
  107. package/assets/cloudling/cloudling-dozing.svg +358 -0
  108. package/assets/cloudling/cloudling-error.svg +428 -0
  109. package/assets/cloudling/cloudling-idle-reading.svg +658 -0
  110. package/assets/cloudling/cloudling-idle-to-dozing.svg +568 -0
  111. package/assets/cloudling/cloudling-idle-to-sleeping.svg +811 -0
  112. package/assets/cloudling/cloudling-idle.svg +255 -0
  113. package/assets/cloudling/cloudling-juggling.svg +648 -0
  114. package/assets/cloudling/cloudling-mini-alert.svg +390 -0
  115. package/assets/cloudling/cloudling-mini-crabwalk.svg +335 -0
  116. package/assets/cloudling/cloudling-mini-enter-roll-in.svg +226 -0
  117. package/assets/cloudling/cloudling-mini-enter-sleep.svg +232 -0
  118. package/assets/cloudling/cloudling-mini-happy.svg +371 -0
  119. package/assets/cloudling/cloudling-mini-idle.svg +279 -0
  120. package/assets/cloudling/cloudling-mini-peek.svg +328 -0
  121. package/assets/cloudling/cloudling-mini-sleep.svg +260 -0
  122. package/assets/cloudling/cloudling-mini-typing.svg +692 -0
  123. package/assets/cloudling/cloudling-notification.svg +494 -0
  124. package/assets/cloudling/cloudling-react-drag.svg +83 -0
  125. package/assets/cloudling/cloudling-sleeping-to-idle.svg +555 -0
  126. package/assets/cloudling/cloudling-sleeping.svg +500 -0
  127. package/assets/cloudling/cloudling-sweeping.svg +292 -0
  128. package/assets/cloudling/cloudling-thinking.svg +720 -0
  129. package/assets/cloudling/cloudling-typing.svg +354 -0
  130. package/assets/cloudling.svg +255 -0
  131. package/assets/ghost-attention.svg +19 -0
  132. package/assets/ghost-carrying.svg +19 -0
  133. package/assets/ghost-collapsing.svg +19 -0
  134. package/assets/ghost-dozing.svg +23 -0
  135. package/assets/ghost-error.svg +19 -0
  136. package/assets/ghost-happy.svg +23 -0
  137. package/assets/ghost-idle-action1.svg +19 -0
  138. package/assets/ghost-idle-action2.svg +19 -0
  139. package/assets/ghost-idle-action3.svg +19 -0
  140. package/assets/ghost-idle.svg +11 -0
  141. package/assets/ghost-juggling.svg +19 -0
  142. package/assets/ghost-notification.svg +19 -0
  143. package/assets/ghost-react-double.svg +23 -0
  144. package/assets/ghost-react-drag.svg +23 -0
  145. package/assets/ghost-react-left.svg +19 -0
  146. package/assets/ghost-react-right.svg +19 -0
  147. package/assets/ghost-sleeping.svg +19 -0
  148. package/assets/ghost-sweeping.svg +19 -0
  149. package/assets/ghost-thinking.svg +19 -0
  150. package/assets/ghost-waking.svg +19 -0
  151. package/assets/ghost-working.svg +19 -0
  152. package/assets/ghost-yawning.svg +23 -0
  153. package/assets/ghost.svg +11 -0
  154. package/assets/robot-attention.svg +19 -0
  155. package/assets/robot-carrying.svg +19 -0
  156. package/assets/robot-collapsing.svg +19 -0
  157. package/assets/robot-dozing.svg +23 -0
  158. package/assets/robot-error.svg +19 -0
  159. package/assets/robot-happy.svg +23 -0
  160. package/assets/robot-idle-action1.svg +19 -0
  161. package/assets/robot-idle-action2.svg +19 -0
  162. package/assets/robot-idle-action3.svg +19 -0
  163. package/assets/robot-idle.svg +11 -0
  164. package/assets/robot-juggling.svg +19 -0
  165. package/assets/robot-notification.svg +19 -0
  166. package/assets/robot-react-double.svg +23 -0
  167. package/assets/robot-react-drag.svg +23 -0
  168. package/assets/robot-react-left.svg +19 -0
  169. package/assets/robot-react-right.svg +19 -0
  170. package/assets/robot-sleeping.svg +19 -0
  171. package/assets/robot-sweeping.svg +19 -0
  172. package/assets/robot-thinking.svg +19 -0
  173. package/assets/robot-waking.svg +19 -0
  174. package/assets/robot-working.svg +19 -0
  175. package/assets/robot-yawning.svg +23 -0
  176. package/assets/robot.svg +11 -0
  177. package/manifest.json +889 -0
  178. package/package.json +18 -0
@@ -0,0 +1,568 @@
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="btn-play"></g>
5
+ <g id="duration-total"></g>
6
+ <g id="motion-controls"></g>
7
+ <g id="motion-summary"></g>
8
+ <g id="phase"></g>
9
+ <g id="scrubber"></g>
10
+ <g id="time"></g>
11
+ <g id="timing-controls"></g>
12
+ </g>
13
+ <script type="application/ecmascript"><![CDATA[
14
+ 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";
15
+
16
+ const SOFT_CLOUD_POINTS = [
17
+ {x:9.064,y:3.344},{x:9.825,y:3.106},{x:10.616,y:3.004},{x:11.413,y:3.040},
18
+ {x:12.192,y:3.210},{x:12.928,y:3.518},{x:13.599,y:3.948},{x:14.255,y:4.291},
19
+ {x:15.046,y:4.188},{x:15.843,y:4.224},{x:16.621,y:4.398},{x:17.357,y:4.706},
20
+ {x:18.029,y:5.134},{x:18.617,y:5.673},{x:19.103,y:6.306},{x:19.471,y:7.013},
21
+ {x:19.715,y:7.772},{x:19.812,y:8.564},{x:19.784,y:9.361},{x:19.797,y:10.105},
22
+ {x:20.287,y:10.735},{x:20.656,y:11.442},{x:20.897,y:12.202},{x:20.995,y:12.994},
23
+ {x:20.964,y:13.791},{x:20.791,y:14.569},{x:20.480,y:15.304},{x:20.052,y:15.977},
24
+ {x:19.516,y:16.567},{x:18.882,y:17.052},{x:18.174,y:17.418},{x:17.539,y:17.808},
25
+ {x:17.231,y:18.545},{x:16.805,y:19.218},{x:16.264,y:19.805},{x:15.633,y:20.292},
26
+ {x:14.925,y:20.660},{x:14.163,y:20.895},{x:13.372,y:20.996},{x:12.575,y:20.964},
27
+ {x:11.796,y:20.793},{x:11.061,y:20.483},{x:10.392,y:20.049},{x:9.736,y:19.714},
28
+ {x:8.946,y:19.811},{x:8.148,y:19.777},{x:7.370,y:19.606},{x:6.634,y:19.299},
29
+ {x:5.962,y:18.868},{x:5.377,y:18.326},{x:4.885,y:17.698},{x:4.522,y:16.989},
30
+ {x:4.283,y:16.228},{x:4.182,y:15.437},{x:4.220,y:14.640},{x:4.179,y:13.907},
31
+ {x:3.696,y:13.273},{x:3.332,y:12.563},{x:3.092,y:11.803},{x:3.003,y:11.010},
32
+ {x:3.038,y:10.213},{x:3.202,y:9.433},{x:3.514,y:8.699},{x:3.944,y:8.027},
33
+ {x:4.484,y:7.440},{x:5.114,y:6.951},{x:5.822,y:6.586},{x:6.445,y:6.191},
34
+ {x:6.752,y:5.455},{x:7.185,y:4.785},{x:7.723,y:4.197},{x:8.356,y:3.711},
35
+ ];
36
+
37
+ let DURATION = 9.03;
38
+ const DOZE = {
39
+ slump: 0.55,
40
+ topSag: 1.42,
41
+ shoulderSag: 0.34,
42
+ spread: 0.82,
43
+ floorY: 19.92,
44
+ floorFlatten: 0.56,
45
+ bottomSquish: 0.82,
46
+ leftLean: -0.18,
47
+ bodyTx: 0,
48
+ bodyTy: 1.15,
49
+ bodyRot: -1.5,
50
+ bodyScale: 1.04,
51
+ eyeY: 13.0,
52
+ eyeGap: 6.12,
53
+ eyeW: 2.70,
54
+ eyeH: 1.28,
55
+ eyeTilt: 7.7,
56
+ eyeStroke: 0.80,
57
+ eyeOpacity: 0.92,
58
+ haloY: 20.62,
59
+ haloRx: 10.8,
60
+ haloRy: 1.28,
61
+ haloOpacity: 0.34,
62
+ glowOpacity: 0.18,
63
+ };
64
+
65
+ const stage = document.getElementById('stage');
66
+ const scrubber = document.getElementById('scrubber');
67
+ const timeOut = document.getElementById('time');
68
+ const phaseEl = document.getElementById('phase');
69
+ const timingControls = document.getElementById('timing-controls');
70
+ const durationTotal = document.getElementById('duration-total');
71
+ const motionControls = document.getElementById('motion-controls');
72
+ const motionSummary = document.getElementById('motion-summary');
73
+ let playing = true;
74
+ let start = performance.now();
75
+ let pausedAt = 0;
76
+ let manualT = 0;
77
+
78
+ const TIMING_CONTROLS = [
79
+ { key: 'idleHold', label: '起始停顿', min: 0.10, max: 1.20, step: 0.01 },
80
+ { key: 'halfClose1', label: '半闭 1', min: 0.25, max: 1.60, step: 0.01 },
81
+ { key: 'wake1', label: '睁眼 1', min: 0.12, max: 0.80, step: 0.01 },
82
+ { key: 'hold1', label: '撑住 1', min: 0.10, max: 1.40, step: 0.01 },
83
+ { key: 'halfClose2', label: '半闭 2', min: 0.25, max: 1.80, step: 0.01 },
84
+ { key: 'wake2', label: '睁眼 2', min: 0.12, max: 0.90, step: 0.01 },
85
+ { key: 'hold2', label: '撑住 2', min: 0.10, max: 1.50, step: 0.01 },
86
+ { key: 'finalClose', label: '最终闭眼', min: 0.25, max: 1.70, step: 0.01 },
87
+ { key: 'slump', label: '软塌下移', min: 0.45, max: 2.20, step: 0.01 },
88
+ { key: 'finalHold', label: '末尾停顿', min: 0.05, max: 0.80, step: 0.01 },
89
+ ];
90
+
91
+ const TIMING = {
92
+ idleHold: 0.35,
93
+ halfClose1: 1.00,
94
+ wake1: 0.30,
95
+ hold1: 0.80,
96
+ halfClose2: 1.59,
97
+ wake2: 0.55,
98
+ hold2: 1.00,
99
+ finalClose: 1.32,
100
+ slump: 1.88,
101
+ finalHold: 0.24,
102
+ };
103
+
104
+ const MOTION_CONTROLS = [
105
+ { key: 'breathAmp', label: '呼吸幅度', min: 0, max: 0.04, step: 0.001, format: (v) => `${(v * 100).toFixed(1)}%` },
106
+ { key: 'breathPeriod', label: '呼吸周期', min: 1.80, max: 6.00, step: 0.05, format: (v) => `${v.toFixed(2)}s` },
107
+ ];
108
+
109
+ const MOTION = {
110
+ breathAmp: 0.018,
111
+ breathPeriod: 3.80,
112
+ };
113
+
114
+ function clamp01(v) { return Math.max(0, Math.min(1, v)); }
115
+ function lerp(a, b, t) { return a + (b - a) * t; }
116
+ function progress(t, a, b) { return clamp01((t - a) / (b - a)); }
117
+ function smoothstep(a, b, t) {
118
+ const x = progress(t, a, b);
119
+ return x * x * (3 - 2 * x);
120
+ }
121
+ function easeInOut(t) {
122
+ return t < .5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2;
123
+ }
124
+ function easeOutBack(t) {
125
+ const c1 = 1.18;
126
+ const c3 = c1 + 1;
127
+ return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
128
+ }
129
+
130
+ function buildTimeline() {
131
+ let t = 0;
132
+ const tl = {};
133
+ tl.idleEnd = t += TIMING.idleHold;
134
+ tl.half1Start = tl.idleEnd;
135
+ tl.half1Peak = t += TIMING.halfClose1;
136
+ tl.wake1End = t += TIMING.wake1;
137
+ tl.hold1End = t += TIMING.hold1;
138
+ tl.half2Start = tl.hold1End;
139
+ tl.half2Peak = t += TIMING.halfClose2;
140
+ tl.wake2End = t += TIMING.wake2;
141
+ tl.hold2End = t += TIMING.hold2;
142
+ tl.closeStart = tl.hold2End;
143
+ tl.closeEnd = t += TIMING.finalClose;
144
+ tl.slumpStart = tl.closeEnd;
145
+ tl.slumpEnd = t += TIMING.slump;
146
+ tl.duration = t += TIMING.finalHold;
147
+ tl.haloStart = tl.slumpStart + TIMING.slump * 0.13;
148
+ tl.dropStart = tl.slumpStart + TIMING.slump * 0.03;
149
+ return tl;
150
+ }
151
+
152
+ let timeline = buildTimeline();
153
+ DURATION = timeline.duration;
154
+
155
+ function syncDuration() {
156
+ timeline = buildTimeline();
157
+ DURATION = timeline.duration;
158
+ scrubber.max = DURATION.toFixed(2);
159
+ if (manualT > DURATION) manualT = DURATION;
160
+ durationTotal.textContent = `total ${DURATION.toFixed(2)} s`;
161
+ }
162
+
163
+ function catmullRomClosedPath(points, tension = 1) {
164
+ const q = (n) => n.toFixed(3);
165
+ let d = `M ${q(points[0].x)} ${q(points[0].y)}`;
166
+ const n = points.length;
167
+ for (let i = 0; i < n; i++) {
168
+ const p0 = points[(i - 1 + n) % n];
169
+ const p1 = points[i];
170
+ const p2 = points[(i + 1) % n];
171
+ const p3 = points[(i + 2) % n];
172
+ const c1x = p1.x + (p2.x - p0.x) * tension / 6;
173
+ const c1y = p1.y + (p2.y - p0.y) * tension / 6;
174
+ const c2x = p2.x - (p3.x - p1.x) * tension / 6;
175
+ const c2y = p2.y - (p3.y - p1.y) * tension / 6;
176
+ d += ` C ${q(c1x)} ${q(c1y)} ${q(c2x)} ${q(c2y)} ${q(p2.x)} ${q(p2.y)}`;
177
+ }
178
+ return `${d} Z`;
179
+ }
180
+
181
+ function makeDozeCloudPath(amount) {
182
+ const mix = clamp01(amount);
183
+ if (mix <= 0.0001) return CLOUD_D;
184
+ const slump = DOZE.slump * mix;
185
+ const points = SOFT_CLOUD_POINTS.map((pt) => {
186
+ const dx = pt.x - 12;
187
+ const distX = Math.abs(dx);
188
+ const sign = dx === 0 ? 0 : Math.sign(dx);
189
+ const topWeight = Math.pow(clamp01((8.2 - pt.y) / 5.2), 0.95);
190
+ const crownCenter = clamp01(1 - distX / 6.6);
191
+ const shoulderWeight = clamp01((pt.y - 6.0) / 8.5) * clamp01((18.6 - pt.y) / 10.5);
192
+ const lowerWeight = Math.pow(clamp01((pt.y - 15.4) / 5.8), 0.78);
193
+ const sideWeight = clamp01((distX - 2.2) / 7.5) * clamp01((20.2 - pt.y) / 16.2);
194
+
195
+ const topSag = slump * topWeight * DOZE.topSag * (0.70 + crownCenter * 0.42);
196
+ const shoulderSag = slump * shoulderWeight * DOZE.shoulderSag;
197
+ const floorCurve = DOZE.floorY + Math.pow(distX / 10, 2) * 0.46 - crownCenter * 0.04;
198
+ const floorPull = (floorCurve - pt.y) * lowerWeight * DOZE.floorFlatten * slump;
199
+ const sideOut = sign * slump * DOZE.spread * (0.18 + sideWeight * 0.76 + lowerWeight * 0.42);
200
+ const bottomInhale = lowerWeight * DOZE.bottomSquish * slump * -0.34;
201
+ const lean = DOZE.leftLean * slump * (0.15 + topWeight * 0.50 + shoulderWeight * 0.30);
202
+
203
+ return {
204
+ x: pt.x + sideOut + lean,
205
+ y: pt.y + topSag + shoulderSag + floorPull + bottomInhale
206
+ };
207
+ });
208
+ return catmullRomClosedPath(points, lerp(0.95, 0.68, mix));
209
+ }
210
+
211
+ function defs(cloudD) {
212
+ return `
213
+ <linearGradient id="body-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
214
+ <stop offset="0" stop-color="#93c5fd"/>
215
+ <stop offset="0.48" stop-color="#f5f6fa"/>
216
+ <stop offset="1" stop-color="#dde2ec"/>
217
+ </linearGradient>
218
+ <linearGradient id="eye-grad" x1="12" y1="8" x2="12" y2="15" gradientUnits="userSpaceOnUse">
219
+ <stop offset="0" stop-color="#B1A7FF"/>
220
+ <stop offset="0.55" stop-color="#7A9DFF"/>
221
+ <stop offset="1" stop-color="#3941FF"/>
222
+ </linearGradient>
223
+ <radialGradient id="hi-grad" cx="0.43" cy="0.22" r="0.62">
224
+ <stop offset="0" stop-color="#93c5fd" stop-opacity="0.82"/>
225
+ <stop offset="0.58" stop-color="#93c5fd" stop-opacity="0.28"/>
226
+ <stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
227
+ </radialGradient>
228
+ <radialGradient id="halo-grad" cx="50%" cy="50%" r="50%">
229
+ <stop offset="0" stop-color="#7A9DFF" stop-opacity="0.55"/>
230
+ <stop offset="0.54" stop-color="#3941FF" stop-opacity="0.24"/>
231
+ <stop offset="1" stop-color="#3941FF" stop-opacity="0"/>
232
+ </radialGradient>
233
+ <filter id="drop" x="-45%" y="-45%" width="190%" height="190%">
234
+ <feDropShadow dx="0" dy="0.72" stdDeviation="0.92" flood-color="#3941FF" flood-opacity="0.35"/>
235
+ </filter>
236
+ <filter id="edge-blur" x="-40%" y="-40%" width="180%" height="180%">
237
+ <feGaussianBlur stdDeviation="0.16"/>
238
+ </filter>
239
+ <filter id="halo-blur" x="-80%" y="-120%" width="260%" height="340%">
240
+ <feGaussianBlur stdDeviation="0.42"/>
241
+ </filter>
242
+ <clipPath id="cloud-clip"><path d="${cloudD}"/></clipPath>
243
+ `;
244
+ }
245
+
246
+ function capsulePath(cx, cy, w, h) {
247
+ const r = w / 2;
248
+ const top = cy - h / 2;
249
+ const bottom = cy + h / 2;
250
+ return [
251
+ `M ${cx.toFixed(2)} ${top.toFixed(2)}`,
252
+ `C ${(cx + r).toFixed(2)} ${top.toFixed(2)} ${(cx + r).toFixed(2)} ${(top + r).toFixed(2)} ${(cx + r).toFixed(2)} ${(top + r).toFixed(2)}`,
253
+ `L ${(cx + r).toFixed(2)} ${(bottom - r).toFixed(2)}`,
254
+ `C ${(cx + r).toFixed(2)} ${bottom.toFixed(2)} ${cx.toFixed(2)} ${bottom.toFixed(2)} ${cx.toFixed(2)} ${bottom.toFixed(2)}`,
255
+ `C ${(cx - r).toFixed(2)} ${bottom.toFixed(2)} ${(cx - r).toFixed(2)} ${(bottom - r).toFixed(2)} ${(cx - r).toFixed(2)} ${(bottom - r).toFixed(2)}`,
256
+ `L ${(cx - r).toFixed(2)} ${(top + r).toFixed(2)}`,
257
+ `C ${(cx - r).toFixed(2)} ${top.toFixed(2)} ${cx.toFixed(2)} ${top.toFixed(2)} ${cx.toFixed(2)} ${top.toFixed(2)} Z`
258
+ ].join(' ');
259
+ }
260
+
261
+ function roundedRectPath(cx, cy, w, h) {
262
+ const r = Math.min(w, h) / 2;
263
+ const k = 0.5522847498;
264
+ const left = cx - w / 2;
265
+ const right = cx + w / 2;
266
+ const top = cy - h / 2;
267
+ const bottom = cy + h / 2;
268
+ const c = r * k;
269
+ return [
270
+ `M ${(left + r).toFixed(2)} ${top.toFixed(2)}`,
271
+ `L ${(right - r).toFixed(2)} ${top.toFixed(2)}`,
272
+ `C ${(right - r + c).toFixed(2)} ${top.toFixed(2)} ${right.toFixed(2)} ${(top + r - c).toFixed(2)} ${right.toFixed(2)} ${(top + r).toFixed(2)}`,
273
+ `L ${right.toFixed(2)} ${(bottom - r).toFixed(2)}`,
274
+ `C ${right.toFixed(2)} ${(bottom - r + c).toFixed(2)} ${(right - r + c).toFixed(2)} ${bottom.toFixed(2)} ${(right - r).toFixed(2)} ${bottom.toFixed(2)}`,
275
+ `L ${(left + r).toFixed(2)} ${bottom.toFixed(2)}`,
276
+ `C ${(left + r - c).toFixed(2)} ${bottom.toFixed(2)} ${left.toFixed(2)} ${(bottom - r + c).toFixed(2)} ${left.toFixed(2)} ${(bottom - r).toFixed(2)}`,
277
+ `L ${left.toFixed(2)} ${(top + r).toFixed(2)}`,
278
+ `C ${left.toFixed(2)} ${(top + r - c).toFixed(2)} ${(left + r - c).toFixed(2)} ${top.toFixed(2)} ${(left + r).toFixed(2)} ${top.toFixed(2)} Z`
279
+ ].join(' ');
280
+ }
281
+
282
+ function closedEyeStrokePath(cx, cy, w, h, tiltDeg, flip) {
283
+ const x1 = cx - w / 2;
284
+ const x2 = cx + w / 2;
285
+ const mid = cy + h * 0.56;
286
+ const c1x = cx - w * 0.18;
287
+ const c2x = cx + w * 0.18;
288
+ const tilt = flip * tiltDeg * 0.018;
289
+ return `M ${x1.toFixed(2)} ${(cy - tilt).toFixed(2)} C ${c1x.toFixed(2)} ${mid.toFixed(2)} ${c2x.toFixed(2)} ${mid.toFixed(2)} ${x2.toFixed(2)} ${(cy + tilt).toFixed(2)}`;
290
+ }
291
+
292
+ function makeClippedCapsuleEye(cx, cy, w, h, lid, clipId) {
293
+ const d = capsulePath(cx, cy, w, h);
294
+ const cut = clamp01(lid);
295
+ if (cut <= 0.012) return `<path d="${d}"/>`;
296
+
297
+ const cutY = cy - h / 2 + h * cut;
298
+ return `
299
+ <clipPath id="${clipId}" clipPathUnits="userSpaceOnUse">
300
+ <rect x="${(cx - w / 2 - 0.18).toFixed(2)}" y="${cutY.toFixed(2)}" width="${(w + 0.36).toFixed(2)}" height="${(h + 0.48).toFixed(2)}"/>
301
+ </clipPath>
302
+ <path d="${d}" clip-path="url(#${clipId})"/>`;
303
+ }
304
+
305
+ function makeClosingSlitEye(cx, cy, w, h, close, clipId) {
306
+ const u = clamp01(close);
307
+ const squeeze = smoothstep(0.42, 0.84, u);
308
+ if (squeeze <= 0.001) return makeClippedCapsuleEye(cx, cy, w, h, u * 0.96, clipId);
309
+
310
+ const cutY = cy - h / 2 + h * u * 0.96;
311
+ const bottomY = cy + h / 2;
312
+ const visibleH = Math.max(0.34, bottomY - cutY);
313
+ const visibleCy = cutY + visibleH / 2;
314
+ const slitH = lerp(visibleH, 0.34, squeeze);
315
+ const slitW = lerp(w, w * 0.90, squeeze);
316
+ const slitCy = lerp(visibleCy, cy + 0.25, squeeze);
317
+ return `<path d="${roundedRectPath(cx, slitCy, slitW, slitH)}"/>`;
318
+ }
319
+
320
+ function makeEyes(eye) {
321
+ const fillGap = 6.00;
322
+ const fillCy = 12.00;
323
+ const fillW = 2.60;
324
+ const fillH = 5.00;
325
+ const fillLeft = 12 - fillGap / 2;
326
+ const fillRight = 12 + fillGap / 2;
327
+
328
+ const closedGap = lerp(5.84, DOZE.eyeGap, eye.drop);
329
+ const closedCy = lerp(12.12, DOZE.eyeY, eye.drop);
330
+ const closedW = lerp(2.28, DOZE.eyeW, eye.drop);
331
+ const closedH = lerp(0.72, DOZE.eyeH, eye.drop);
332
+ const closedTilt = lerp(2.8, DOZE.eyeTilt, eye.drop);
333
+ const closedStroke = lerp(0.62, DOZE.eyeStroke, eye.drop);
334
+ const closedLeft = 12 - closedGap / 2;
335
+ const closedRight = 12 + closedGap / 2;
336
+
337
+ return `
338
+ <g id="eyes-morph">
339
+ <g fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7" opacity="${eye.fillOpacity.toFixed(3)}">
340
+ ${eye.finalClose > 0.001
341
+ ? makeClosingSlitEye(fillLeft, fillCy, fillW, fillH, eye.finalClose, 'eye-l-clip')
342
+ : makeClippedCapsuleEye(fillLeft, fillCy, fillW, fillH, eye.lid, 'eye-l-clip')}
343
+ ${eye.finalClose > 0.001
344
+ ? makeClosingSlitEye(fillRight, fillCy, fillW, fillH, eye.finalClose, 'eye-r-clip')
345
+ : makeClippedCapsuleEye(fillRight, fillCy, fillW, fillH, eye.lid, 'eye-r-clip')}
346
+ </g>
347
+ <g id="eyes-doze" fill="none" stroke="url(#eye-grad)" stroke-width="${closedStroke.toFixed(2)}" stroke-linecap="round" stroke-linejoin="round" opacity="${(eye.closedOpacity * DOZE.eyeOpacity).toFixed(3)}">
348
+ <path d="${closedEyeStrokePath(closedLeft, closedCy, closedW, closedH, closedTilt, -1)}"/>
349
+ <path d="${closedEyeStrokePath(closedRight, closedCy, closedW, closedH, closedTilt, 1)}"/>
350
+ </g>
351
+ </g>
352
+ `;
353
+ }
354
+
355
+ function dozeBeat(t, start, peak, end) {
356
+ return Math.min(smoothstep(start, peak, t), 1 - smoothstep(peak, end, t));
357
+ }
358
+
359
+ function computeFrame(t) {
360
+ const tl = timeline;
361
+ const beatA = dozeBeat(t, tl.half1Start, tl.half1Peak, tl.wake1End);
362
+ const beatB = dozeBeat(t, tl.half2Start, tl.half2Peak, tl.wake2End);
363
+ const closeU = smoothstep(tl.closeStart, tl.closeEnd, t);
364
+ const pathU = easeInOut(progress(t, tl.slumpStart, tl.slumpEnd));
365
+ const settleU = easeInOut(progress(t, tl.dropStart, tl.slumpEnd));
366
+ const wakeA = Math.sin(progress(t, tl.half1Peak, tl.wake1End) * Math.PI) * 0.42;
367
+ const wakeB = Math.sin(progress(t, tl.half2Peak, tl.wake2End) * Math.PI) * 0.34;
368
+ const nodU = Math.max(beatA * 0.72, beatB * 1.05);
369
+ const reboundU = wakeA + wakeB;
370
+ const haloU = smoothstep(tl.haloStart, tl.slumpEnd, t);
371
+ const eyeDropU = easeInOut(progress(t, tl.dropStart, tl.slumpEnd));
372
+ const breathWave = Math.sin((t / MOTION.breathPeriod) * Math.PI * 2);
373
+ const breathFade = lerp(1, 0.72, pathU);
374
+ const breath = breathWave * MOTION.breathAmp * breathFade;
375
+ const sleepyU = Math.max(beatA * 0.82, beatB, closeU);
376
+ const lid = beatA * 0.56 + beatB * 0.66 + closeU * 0.96;
377
+ const fillOpacity = 1 - smoothstep(0.58, 0.88, closeU);
378
+ const closedOpacity = smoothstep(0.58, 1.0, closeU);
379
+
380
+ return {
381
+ pathU,
382
+ haloU,
383
+ eye: {
384
+ sleepy: sleepyU,
385
+ lid,
386
+ finalClose: closeU,
387
+ fillOpacity,
388
+ closedOpacity,
389
+ drop: eyeDropU,
390
+ },
391
+ bodyTx: lerp(0, DOZE.bodyTx, settleU) - 0.05 * nodU,
392
+ bodyTy: lerp(0, DOZE.bodyTy, settleU) + 0.74 * nodU - 0.18 * reboundU,
393
+ bodyRot: lerp(0, DOZE.bodyRot, settleU) - 2.1 * nodU + 0.42 * reboundU,
394
+ bodySx: lerp(1, DOZE.bodyScale, settleU) + 0.025 * nodU + breath * 0.58,
395
+ bodySy: lerp(1, DOZE.bodyScale, settleU) - 0.045 * nodU + breath,
396
+ breath,
397
+ };
398
+ }
399
+
400
+ function phaseName(t) {
401
+ const tl = timeline;
402
+ if (t < tl.idleEnd) return 'idle';
403
+ if (t < tl.half1Peak) return 'half close 1';
404
+ if (t < tl.wake1End) return 'wake 1';
405
+ if (t < tl.hold1End) return 'holding on';
406
+ if (t < tl.half2Peak) return 'half close 2';
407
+ if (t < tl.wake2End) return 'wake 2';
408
+ if (t < tl.hold2End) return 'holding on';
409
+ if (t < tl.closeEnd) return 'natural close';
410
+ if (t < tl.slumpEnd) return 'soft slump';
411
+ return 'dozing hold';
412
+ }
413
+
414
+ function renderFrame(t) {
415
+ const f = computeFrame(t);
416
+ const cloudD = makeDozeCloudPath(f.pathU);
417
+ const bodyTransform = `translate(${f.bodyTx.toFixed(3)} ${f.bodyTy.toFixed(3)}) translate(12 12) rotate(${f.bodyRot.toFixed(3)}) scale(${f.bodySx.toFixed(4)} ${f.bodySy.toFixed(4)}) translate(-12 -12)`;
418
+ stage.innerHTML = `
419
+ <style>
420
+ .edge-rim-tight { stroke-opacity: .9; }
421
+ .edge-rim-soft { stroke-opacity: .75; }
422
+ .edge-contact { stroke-opacity: .9; }
423
+ .page {
424
+ width: min(1040px, 100%);
425
+ display: grid;
426
+ gap: 18px;
427
+ }
428
+ .header {
429
+ display: flex;
430
+ align-items: baseline;
431
+ justify-content: space-between;
432
+ gap: 16px;
433
+ }
434
+ .timeline, .tuning {
435
+ border: 1px solid rgba(149, 170, 255, .22);
436
+ background: rgba(5, 10, 24, .46);
437
+ box-shadow: 0 20px 80px rgba(0, 0, 0, .34);
438
+ border-radius: 8px;
439
+ }
440
+ .timeline {
441
+ padding: 13px;
442
+ display: grid;
443
+ grid-template-columns: 70px 1fr 72px auto auto auto;
444
+ gap: 10px;
445
+ align-items: center;
446
+ }
447
+ .timeline span, .timeline output {
448
+ color: rgba(226, 234, 255, .76);
449
+ font: 11px ui-monospace, SFMono-Regular, Consolas, monospace;
450
+ }
451
+ .phase {
452
+ display: flex;
453
+ gap: 10px;
454
+ flex-wrap: wrap;
455
+ color: rgba(226, 234, 255, .54);
456
+ font-size: 11px;
457
+ }
458
+ .phase b {
459
+ color: rgba(235, 240, 255, .84);
460
+ font-weight: 650;
461
+ }
462
+ .tuning {
463
+ padding: 14px;
464
+ display: grid;
465
+ gap: 12px;
466
+ }
467
+ .tuning-head {
468
+ display: flex;
469
+ align-items: center;
470
+ justify-content: space-between;
471
+ gap: 12px;
472
+ color: rgba(226, 234, 255, .82);
473
+ font-size: 12px;
474
+ font-weight: 650;
475
+ }
476
+ .tuning-head output {
477
+ color: rgba(226, 234, 255, .62);
478
+ font: 11px ui-monospace, SFMono-Regular, Consolas, monospace;
479
+ font-weight: 500;
480
+ }
481
+ .timing-controls {
482
+ display: grid;
483
+ grid-template-columns: repeat(2, minmax(0, 1fr));
484
+ gap: 11px 16px;
485
+ }
486
+ .timing-row {
487
+ display: grid;
488
+ grid-template-columns: 92px 1fr 56px;
489
+ gap: 10px;
490
+ align-items: center;
491
+ }
492
+ .timing-row label {
493
+ color: rgba(226, 234, 255, .72);
494
+ font-size: 12px;
495
+ }
496
+ .timing-row output {
497
+ color: rgba(226, 234, 255, .66);
498
+ font: 11px ui-monospace, SFMono-Regular, Consolas, monospace;
499
+ text-align: right;
500
+ }
501
+ @media (prefers-color-scheme: light) {
502
+ .edge-rim-tight { stroke-opacity: .95; }
503
+ .edge-rim-soft { stroke-opacity: .85; }
504
+ .edge-contact { stroke-opacity: .95; }
505
+ .cloud-drop-shadow { flood-opacity: .13; }
506
+ .timeline, .tuning {
507
+ background: rgba(255, 255, 255, .68);
508
+ border-color: rgba(82, 111, 190, .22);
509
+ box-shadow: 0 20px 70px rgba(58, 78, 130, .14);
510
+ }
511
+ .phase, .timeline span, .timeline output, .tuning-head output, .timing-row label, .timing-row output { color: rgba(21, 33, 63, .66); }
512
+ .phase b { color: rgba(21, 33, 63, .86); }
513
+ .food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
514
+ .food-cloud > path {
515
+ stroke: #5870E8 !important;
516
+ stroke-width: 1.2 !important;
517
+ stroke-opacity: .9 !important;
518
+ vector-effect: non-scaling-stroke !important;
519
+ }
520
+ }
521
+ </style>
522
+ <defs>
523
+ ${defs(cloudD)}
524
+ <path id="cloud-shape" d="${cloudD}"/>
525
+ </defs>
526
+ <g id="halo-layer" opacity="${f.haloU.toFixed(3)}">
527
+ <ellipse cx="12" cy="${DOZE.haloY}" rx="${lerp(7.8, DOZE.haloRx, f.haloU).toFixed(2)}" ry="${lerp(.72, DOZE.haloRy, f.haloU).toFixed(2)}" fill="url(#halo-grad)" opacity="${DOZE.haloOpacity}" filter="url(#halo-blur)"/>
528
+ <ellipse cx="12" cy="${(DOZE.haloY + 0.05).toFixed(2)}" rx="${(DOZE.haloRx * 0.68).toFixed(2)}" ry="${(DOZE.haloRy * 0.46).toFixed(2)}" fill="#7A9DFF" opacity="${DOZE.glowOpacity}" filter="url(#halo-blur)"/>
529
+ </g>
530
+ <g id="body-pose" transform="${bodyTransform}">
531
+ <g id="edge-system">
532
+ <use class="edge-contact" href="#cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.00" stroke-linejoin="round" transform="translate(0 0.16)" filter="url(#edge-blur)"/>
533
+ <use class="edge-rim-soft" href="#cloud-shape" fill="none" stroke="#7A9DFF" stroke-width="0.88" stroke-linejoin="round" filter="url(#edge-blur)"/>
534
+ <use class="edge-rim-tight" href="#cloud-shape" fill="none" stroke="#aab3c6" stroke-width="0.40" stroke-linejoin="round"/>
535
+ </g>
536
+ <g filter="url(#drop)">
537
+ <use href="#cloud-shape" fill="url(#body-grad)"/>
538
+ <rect x="1" y="2.2" width="22" height="10.4" fill="url(#hi-grad)" clip-path="url(#cloud-clip)" opacity=".88"/>
539
+ </g>
540
+ ${makeEyes(f.eye)}
541
+ </g>
542
+ `;
543
+ scrubber.value = t.toFixed(2);
544
+ timeOut.value = `${t.toFixed(2)} s`;
545
+ phaseEl.innerHTML = `<span>phase: <b>${phaseName(t)}</b></span><span>path ${(f.pathU * 100).toFixed(0)}%</span><span>halo ${(f.haloU * 100).toFixed(0)}%</span><span>breath ${(f.breath * 100).toFixed(1)}%</span>`;
546
+ }
547
+
548
+ function currentTime(now) {
549
+ if (!playing) return manualT;
550
+ const t = ((now - start) / 1000);
551
+ return Math.min(t, DURATION);
552
+ }
553
+
554
+ function loop(now) {
555
+ const t = currentTime(now);
556
+ renderFrame(t);
557
+ if (playing && t >= DURATION) {
558
+ playing = false;
559
+ manualT = DURATION;
560
+ { const __btn = document.getElementById('btn-play'); if (__btn) __btn.textContent = 'Play'; }
561
+ }
562
+ requestAnimationFrame(loop);
563
+ }
564
+
565
+ renderFrame(0);
566
+ requestAnimationFrame(loop);
567
+ ]]></script>
568
+ </svg>