@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,354 @@
1
+ <svg id="cloudling" xmlns="http://www.w3.org/2000/svg" width="880" height="720" viewBox="-32 -24 88 72" preserveAspectRatio="xMidYMid meet">
2
+ <style><![CDATA[
3
+ .edge-rim-tight { stroke-opacity: .65; }
4
+ .edge-rim-soft { stroke-opacity: .50; }
5
+ .edge-contact { stroke-opacity: .65; }
6
+ @media (prefers-color-scheme: light) {
7
+ .edge-rim-tight { stroke-opacity: .75; }
8
+ .edge-rim-soft { stroke-opacity: .60; }
9
+ .edge-contact { stroke-opacity: .75; }
10
+ .cloud-drop-shadow { flood-opacity: .13; }
11
+ .food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
12
+ .food-cloud > path {
13
+ stroke: #5870E8 !important;
14
+ stroke-width: 1.2 !important;
15
+ stroke-opacity: .9 !important;
16
+ vector-effect: non-scaling-stroke !important;
17
+ }
18
+ }
19
+ ]]></style>
20
+ <defs>
21
+ <linearGradient id="body-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
22
+ <stop offset="0" stop-color="#f0f9ff"/>
23
+ <stop offset="0.5" stop-color="#e0f2fe"/>
24
+ <stop offset="1" stop-color="#bae6fd"/>
25
+ </linearGradient>
26
+ <linearGradient id="eye-grad" x1="12" y1="3" x2="12" y2="21" gradientUnits="userSpaceOnUse">
27
+ <stop offset="0" stop-color="#B1A7FF"/>
28
+ <stop offset="0.5" stop-color="#7A9DFF"/>
29
+ <stop offset="1" stop-color="#3941FF"/>
30
+ </linearGradient>
31
+ <radialGradient id="hi-grad" cx="0.45" cy="0.25" r="0.55">
32
+ <stop offset="0" stop-color="#f0f9ff" stop-opacity="0.9"/>
33
+ <stop offset="1" stop-color="#f0f9ff" stop-opacity="0"/>
34
+ </radialGradient>
35
+ <filter id="drop" x="-40%" y="-40%" width="180%" height="180%">
36
+ <feDropShadow class="cloud-drop-shadow" dx="0" dy="0.6" stdDeviation="0.8" flood-color="#3941FF" flood-opacity="0.22"/>
37
+ </filter>
38
+ <filter id="edge-blur" x="-40%" y="-40%" width="180%" height="180%">
39
+ <feGaussianBlur stdDeviation="0.16"/>
40
+ </filter>
41
+ <path id="cloud-shape" 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"/>
42
+ <clipPath id="cloud-clip">
43
+ <use href="#cloud-shape"/>
44
+ </clipPath>
45
+ </defs>
46
+
47
+ <!-- 云朵 layer (rotate + scale, 配置不变 — drop shadow + radial highlight + body-grad) -->
48
+ <g id="cloud-group">
49
+ <g id="edge-system">
50
+ <use class="edge-contact" href="#cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.05" stroke-linejoin="round" transform="translate(0 0.18)" filter="url(#edge-blur)"/>
51
+ <use class="edge-rim-soft" href="#cloud-shape" fill="none" stroke="#7A9DFF" stroke-width="0.90" stroke-linejoin="round" filter="url(#edge-blur)"/>
52
+ <use class="edge-rim-tight" href="#cloud-shape" fill="none" stroke="#aab3c6" stroke-width="0.42" stroke-linejoin="round"/>
53
+ </g>
54
+ <g filter="url(#drop)">
55
+ <use href="#cloud-shape" fill="url(#body-grad)"/>
56
+ <rect x="1" y="2" width="22" height="11" fill="url(#hi-grad)" clip-path="url(#cloud-clip)"/>
57
+ </g>
58
+ </g>
59
+
60
+ <!-- 眼睛 layer (只 scale 不 rotate, 字符直立; d/fill/stroke 由 JS 实时填充) -->
61
+ <g id="eye-group">
62
+ <path id="eye-l-path" stroke-linecap="round" stroke-linejoin="round"/>
63
+ <path id="eye-r-path" stroke-linecap="round" stroke-linejoin="round"/>
64
+ </g>
65
+
66
+ <script type="application/ecmascript"><![CDATA[
67
+ const cloudGroup = document.getElementById('cloud-group');
68
+ const eyeGroup = document.getElementById('eye-group');
69
+ const eyeL = document.getElementById('eye-l-path');
70
+ const eyeR = document.getElementById('eye-r-path');
71
+ const eyeGrad = document.getElementById('eye-grad');
72
+
73
+ /* token 库保留原版 8 个; 组合池过滤纯随机撞出的丑 pair. */
74
+ const TOKENS = ['gt', 'lt', 'und', 'colon', 'eq', 'plus', 'slash', 'bslash'];
75
+ const A_PAIRS = [
76
+ ['lt', 'gt'], ['lt', 'eq'], ['gt', 'eq'], ['eq', 'gt'], ['eq', 'lt'],
77
+ ['gt', 'und'], ['lt', 'und'], ['slash', 'und'], ['und', 'bslash'],
78
+ ['eq', 'slash'], ['eq', 'bslash'],
79
+ ];
80
+ const B_PAIRS = [
81
+ ['colon', 'gt'], ['colon', 'slash'], ['colon', 'bslash'], ['colon', 'und'],
82
+ ['plus', 'und'], ['slash', 'plus'], ['bslash', 'plus'],
83
+ ];
84
+ const ACTIVE_PAIRS = [...A_PAIRS, ...A_PAIRS, ...A_PAIRS, ...B_PAIRS];
85
+
86
+ /* token builder: 返回 { d, mode, sw }. mode='stroke' 用描边, mode='fill' 用填充 (圆点类). */
87
+ function tok(d, mode, sw) { return { d, mode, sw }; }
88
+
89
+ const TOKEN_BUILDERS = {
90
+ /* > chevron (描边折角) */
91
+ gt: (cx, cy) => tok(
92
+ `M ${cx-0.78} ${cy-1.58} L ${cx+0.74} ${cy} L ${cx-0.78} ${cy+1.58}`,
93
+ 'stroke', 1.16
94
+ ),
95
+ /* < chevron (反向) */
96
+ lt: (cx, cy) => tok(
97
+ `M ${cx+0.78} ${cy-1.58} L ${cx-0.74} ${cy} L ${cx+0.78} ${cy+1.58}`,
98
+ 'stroke', 1.16
99
+ ),
100
+ /* _ underscore (下划线) */
101
+ und: (cx, cy) => tok(
102
+ `M ${cx-1.18} ${cy+1.42} L ${cx+1.18} ${cy+1.42}`,
103
+ 'stroke', 1.10
104
+ ),
105
+ /* : colon (上下两个圆点, fill 实心) */
106
+ colon: (cx, cy) => {
107
+ const r = 0.64;
108
+ return tok(
109
+ `M ${cx-r} ${cy-0.94} A ${r} ${r} 0 1 0 ${cx+r} ${cy-0.94} A ${r} ${r} 0 1 0 ${cx-r} ${cy-0.94} Z ` +
110
+ `M ${cx-r} ${cy+0.94} A ${r} ${r} 0 1 0 ${cx+r} ${cy+0.94} A ${r} ${r} 0 1 0 ${cx-r} ${cy+0.94} Z`,
111
+ 'fill', 0
112
+ );
113
+ },
114
+ /* = 两道横线 (多线交叉降到 0.9 防糊) */
115
+ eq: (cx, cy) => tok(
116
+ `M ${cx-1.08} ${cy-0.72} L ${cx+1.08} ${cy-0.72} ` +
117
+ `M ${cx-1.08} ${cy+0.72} L ${cx+1.08} ${cy+0.72}`,
118
+ 'stroke', 0.84
119
+ ),
120
+ /* + 十字 (多线交叉 0.9) */
121
+ plus: (cx, cy) => tok(
122
+ `M ${cx-0.96} ${cy} L ${cx+0.96} ${cy} ` +
123
+ `M ${cx} ${cy-0.96} L ${cx} ${cy+0.96}`,
124
+ 'stroke', 0.82
125
+ ),
126
+ /* / slash 斜杠 (左下到右上) */
127
+ slash: (cx, cy) => tok(
128
+ `M ${cx-0.92} ${cy+1.48} L ${cx+0.92} ${cy-1.48}`,
129
+ 'stroke', 1.16
130
+ ),
131
+ /* \ bslash 反斜杠 (左上到右下) */
132
+ bslash: (cx, cy) => tok(
133
+ `M ${cx-0.92} ${cy-1.48} L ${cx+0.92} ${cy+1.48}`,
134
+ 'stroke', 1.16
135
+ )
136
+ };
137
+
138
+ /* 把 token 应用到 path 元素 (每次切换调一次, 切 fill/stroke 模式 + d + stroke-width + 字符尺寸缩放) */
139
+ function applyToken(el, cx, cy, tokenId, sx = 1, sy = 1, opacity = 1) {
140
+ const t = TOKEN_BUILDERS[tokenId](cx, cy);
141
+ el.setAttribute('d', t.d);
142
+ if (t.mode === 'fill') {
143
+ el.setAttribute('fill', 'url(#eye-grad)');
144
+ el.setAttribute('stroke', 'none');
145
+ el.removeAttribute('stroke-width');
146
+ } else {
147
+ el.setAttribute('fill', 'none');
148
+ el.setAttribute('stroke', 'url(#eye-grad)');
149
+ el.setAttribute('stroke-width', t.sw);
150
+ }
151
+ el.setAttribute('opacity', opacity.toFixed(3));
152
+ /* 以字符中心为锚整体缩放 (stroke 跟随放大, 视觉一致) */
153
+ el.setAttribute(
154
+ 'transform',
155
+ `translate(${cx} ${cy}) scale(${(P.EYE_SIZE * sx).toFixed(3)} ${(P.EYE_SIZE * sy).toFixed(3)}) translate(${-cx} ${-cy})`
156
+ );
157
+ }
158
+
159
+ const P = {
160
+ /* 字符尺寸 (鹿鹿 2026-04-26 锁定 1.40×) */
161
+ EYE_SIZE: 1.40,
162
+ /* 卡顿旋转 (鹿鹿 2026-04-26 锁定, 总周期 4.45s) */
163
+ ROT_AMOUNT: 190, ROT_DIR: -1,
164
+ T_SPIN: 1.40, OVERSHOOT: 8,
165
+ T_HOLD_FAR: 0.85, T_BACK: 1.00, T_HOLD_HOME: 1.20,
166
+ /* 大呼吸 */
167
+ SCALE_LOW: 0.94, SCALE_HIGH: 1.05, BREATH_PERIOD: 1.6,
168
+ /* 字符切换 (角速度阈值, 大角度 — 旋转一周才换 1-2 次) */
169
+ SWITCH_DEG_L: 96, SWITCH_DEG_R: 96, SWITCH_JITTER: 0.25,
170
+ SWITCH_SOFT_MS: 90,
171
+ /* 流动渐变 */
172
+ FLOW_PERIOD: 3.0, FLOW_AMP: 4
173
+ };
174
+
175
+ /* 字符切换状态 */
176
+ let lastLeft = 'gt', lastRight = 'und';
177
+ let switchAccumL = 0, switchAccumR = 0;
178
+ let nextThresholdL = P.SWITCH_DEG_L;
179
+ let nextThresholdR = P.SWITCH_DEG_R;
180
+ let switchStartL = -Infinity;
181
+ let switchStartR = -Infinity;
182
+ let rngState = 0xC10D1E;
183
+
184
+ function rand() {
185
+ rngState = (rngState * 1664525 + 1013904223) >>> 0;
186
+ return rngState / 4294967296;
187
+ }
188
+ function choose(list) { return list[Math.floor(rand() * list.length) % list.length]; }
189
+ function jitterFactor(j) { return 1 + (rand() * 2 - 1) * j; }
190
+ function pairKey(l, r) { return `${l}|${r}`; }
191
+ const A_PAIR_KEYS = new Set(A_PAIRS.map(([l, r]) => pairKey(l, r)));
192
+
193
+ function currentPairIsA() {
194
+ return A_PAIR_KEYS.has(pairKey(lastLeft, lastRight));
195
+ }
196
+
197
+ function getPhaseInfo(t) {
198
+ const cycle = P.T_SPIN + P.T_HOLD_FAR + P.T_BACK + P.T_HOLD_HOME;
199
+ const phase = t % Math.max(cycle, 0.01);
200
+ if (phase < P.T_SPIN) {
201
+ return { name: 'spin', u: phase / Math.max(P.T_SPIN, 0.01), aOnly: phase / Math.max(P.T_SPIN, 0.01) > .80 };
202
+ }
203
+ if (phase < P.T_SPIN + P.T_HOLD_FAR) {
204
+ return { name: 'holdFar', u: 1, aOnly: true };
205
+ }
206
+ if (phase < P.T_SPIN + P.T_HOLD_FAR + P.T_BACK) {
207
+ const u = (phase - P.T_SPIN - P.T_HOLD_FAR) / Math.max(P.T_BACK, 0.01);
208
+ return { name: 'back', u, aOnly: u > .75 };
209
+ }
210
+ return { name: 'holdHome', u: 1, aOnly: true };
211
+ }
212
+
213
+ function settleToA(now) {
214
+ if (currentPairIsA()) return;
215
+ const sameRight = A_PAIRS.filter(([l, r]) => r === lastRight && l !== lastLeft);
216
+ const sameLeft = A_PAIRS.filter(([l, r]) => l === lastLeft && r !== lastRight);
217
+ if (sameRight.length) {
218
+ lastLeft = choose(sameRight)[0];
219
+ switchStartL = now;
220
+ } else if (sameLeft.length) {
221
+ lastRight = choose(sameLeft)[1];
222
+ switchStartR = now;
223
+ } else {
224
+ const [l, r] = choose(A_PAIRS);
225
+ lastLeft = l;
226
+ lastRight = r;
227
+ switchStartL = now;
228
+ switchStartR = now;
229
+ }
230
+ }
231
+
232
+ /* 选 token: 另一只眼固定, 从 A/B 组合池中过滤候选; A-only 区间只用 A 档. */
233
+ function pickToken(side, prev, otherEye, aOnly) {
234
+ const pool = aOnly ? A_PAIRS : ACTIVE_PAIRS;
235
+ const candidates = pool
236
+ .filter(([l, r]) => side === 'left' ? r === otherEye : l === otherEye)
237
+ .map(([l, r]) => side === 'left' ? l : r)
238
+ .filter(token => token !== prev && token !== otherEye);
239
+ if (candidates.length) return choose(candidates);
240
+ return prev;
241
+ }
242
+
243
+ function switchSoft(startMs, nowMs) {
244
+ const p = Math.min(1, Math.max(0, (nowMs - startMs) / P.SWITCH_SOFT_MS));
245
+ const e = easeOutCubic(p);
246
+ return {
247
+ sx: 1.025 - 0.025 * e,
248
+ sy: 0.56 + 0.44 * e,
249
+ opacity: 0.76 + 0.24 * e
250
+ };
251
+ }
252
+
253
+ /* easing */
254
+ function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
255
+ function easeInOutCubic(t) { return t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2; }
256
+ function spinOutWithOvershoot(k, overshootRatio) {
257
+ if (k <= 0.78) {
258
+ const k2 = k / 0.78;
259
+ return (1 + overshootRatio) * easeOutCubic(k2);
260
+ } else {
261
+ const k2 = (k - 0.78) / 0.22;
262
+ return (1 + overshootRatio) - overshootRatio * easeOutCubic(k2);
263
+ }
264
+ }
265
+
266
+ /* 主旋转: 周期内分四段 */
267
+ function getRotation(t) {
268
+ const cycle = P.T_SPIN + P.T_HOLD_FAR + P.T_BACK + P.T_HOLD_HOME;
269
+ const phase = t % Math.max(cycle, 0.01);
270
+ const amount = P.ROT_AMOUNT * P.ROT_DIR;
271
+ const overshootRatio = P.OVERSHOOT / Math.max(P.ROT_AMOUNT, 0.01);
272
+
273
+ if (phase < P.T_SPIN) {
274
+ return amount * spinOutWithOvershoot(phase / P.T_SPIN, overshootRatio);
275
+ } else if (phase < P.T_SPIN + P.T_HOLD_FAR) {
276
+ return amount;
277
+ } else if (phase < P.T_SPIN + P.T_HOLD_FAR + P.T_BACK) {
278
+ const k = (phase - P.T_SPIN - P.T_HOLD_FAR) / P.T_BACK;
279
+ return amount * (1 - easeInOutCubic(k));
280
+ } else {
281
+ return 0;
282
+ }
283
+ }
284
+
285
+ /* 初始字符 */
286
+ applyToken(eyeL, 9, 12, lastLeft);
287
+ applyToken(eyeR, 15, 12, lastRight);
288
+
289
+ let prevAngle = 0;
290
+ let lastT = performance.now();
291
+ const startT = performance.now();
292
+
293
+ function loop(now) {
294
+ const dt = (now - lastT) / 1000;
295
+ lastT = now;
296
+ const t = (now - startT) / 1000;
297
+ const phaseInfo = getPhaseInfo(t);
298
+
299
+ /* 卡顿旋转 */
300
+ const angleDeg = getRotation(t);
301
+ /* 角速度 (用于字符切换累积) */
302
+ const angularVelDeg = Math.abs(angleDeg - prevAngle) / Math.max(dt, 0.001);
303
+ prevAngle = angleDeg;
304
+
305
+ /* 大幅呼吸 */
306
+ const phase = (Math.cos(2 * Math.PI * t / Math.max(P.BREATH_PERIOD, 0.01)) + 1) / 2;
307
+ const scale = P.SCALE_LOW + (P.SCALE_HIGH - P.SCALE_LOW) * phase;
308
+
309
+ /* 应用 cloud transform */
310
+ cloudGroup.setAttribute(
311
+ 'transform',
312
+ `translate(12 12) rotate(${angleDeg.toFixed(3)}) scale(${scale.toFixed(4)}) translate(-12 -12)`
313
+ );
314
+
315
+ /* 眼睛 group: 只 scale, 字符直立 */
316
+ eyeGroup.setAttribute(
317
+ 'transform',
318
+ `translate(12 12) scale(${scale.toFixed(4)}) translate(-12 -12)`
319
+ );
320
+
321
+ /* ⭐ 字符联动切换: 累积转过的角度, 到阈值才切换 */
322
+ if (phaseInfo.aOnly) settleToA(now);
323
+ const angleStep = angularVelDeg * dt;
324
+ switchAccumL += angleStep;
325
+ switchAccumR += angleStep;
326
+ if (switchAccumL >= nextThresholdL) {
327
+ lastLeft = pickToken('left', lastLeft, lastRight, phaseInfo.aOnly);
328
+ switchStartL = now;
329
+ switchAccumL = 0;
330
+ nextThresholdL = P.SWITCH_DEG_L * jitterFactor(P.SWITCH_JITTER);
331
+ }
332
+ if (switchAccumR >= nextThresholdR) {
333
+ lastRight = pickToken('right', lastRight, lastLeft, phaseInfo.aOnly);
334
+ switchStartR = now;
335
+ switchAccumR = 0;
336
+ nextThresholdR = P.SWITCH_DEG_R * jitterFactor(P.SWITCH_JITTER);
337
+ }
338
+
339
+ const softL = switchSoft(switchStartL, now);
340
+ const softR = switchSoft(switchStartR, now);
341
+ applyToken(eyeL, 9, 12, lastLeft, softL.sx, softL.sy, softL.opacity);
342
+ applyToken(eyeR, 15, 12, lastRight, softR.sx, softR.sy, softR.opacity);
343
+
344
+ /* 流动渐变 (stroke 用 url(#eye-grad), 流动让描边色微妙变化) */
345
+ const flowOffset = Math.sin(2 * Math.PI * t / Math.max(P.FLOW_PERIOD, 0.01)) * P.FLOW_AMP;
346
+ eyeGrad.setAttribute('y1', (3 + flowOffset).toFixed(2));
347
+ eyeGrad.setAttribute('y2', (21 + flowOffset).toFixed(2));
348
+
349
+ requestAnimationFrame(loop);
350
+ }
351
+
352
+ requestAnimationFrame(loop);
353
+ ]]></script>
354
+ </svg>