@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.
- package/README.md +4 -0
- package/assets/calico/calico-collapsing.apng +0 -0
- package/assets/calico/calico-dozing.apng +0 -0
- package/assets/calico/calico-error.apng +0 -0
- package/assets/calico/calico-happy.apng +0 -0
- package/assets/calico/calico-idle-follow.svg +324 -0
- package/assets/calico/calico-idle.apng +0 -0
- package/assets/calico/calico-mini-alert.apng +0 -0
- package/assets/calico/calico-mini-crabwalk.apng +0 -0
- package/assets/calico/calico-mini-enter.apng +0 -0
- package/assets/calico/calico-mini-happy.apng +0 -0
- package/assets/calico/calico-mini-idle.apng +0 -0
- package/assets/calico/calico-mini-peek.apng +0 -0
- package/assets/calico/calico-mini-sleep.apng +0 -0
- package/assets/calico/calico-notification.apng +0 -0
- package/assets/calico/calico-react-drag.apng +0 -0
- package/assets/calico/calico-react-left.apng +0 -0
- package/assets/calico/calico-react-poke.apng +0 -0
- package/assets/calico/calico-sleeping.apng +0 -0
- package/assets/calico/calico-thinking.apng +0 -0
- package/assets/calico/calico-waking.apng +0 -0
- package/assets/calico/calico-working-building.apng +0 -0
- package/assets/calico/calico-working-carrying.apng +0 -0
- package/assets/calico/calico-working-conducting.apng +0 -0
- package/assets/calico/calico-working-juggling.apng +0 -0
- package/assets/calico/calico-working-sweeping.apng +0 -0
- package/assets/calico/calico-working-typing.apng +0 -0
- package/assets/calico/calico-yawning.apng +0 -0
- package/assets/calico.svg +324 -0
- package/assets/clawd/LICENSE +22 -0
- package/assets/clawd/clawd-about-hero.svg +202 -0
- package/assets/clawd/clawd-collapse-sleep.svg +247 -0
- package/assets/clawd/clawd-error.svg +94 -0
- package/assets/clawd/clawd-happy.svg +161 -0
- package/assets/clawd/clawd-headphones-groove.svg +194 -0
- package/assets/clawd/clawd-idle-bubble.svg +180 -0
- package/assets/clawd/clawd-idle-collapse.svg +101 -0
- package/assets/clawd/clawd-idle-doze.svg +72 -0
- package/assets/clawd/clawd-idle-follow.svg +64 -0
- package/assets/clawd/clawd-idle-living.svg +196 -0
- package/assets/clawd/clawd-idle-look.svg +115 -0
- package/assets/clawd/clawd-idle-reading.svg +252 -0
- package/assets/clawd/clawd-idle-yawn.svg +158 -0
- package/assets/clawd/clawd-mini-alert.svg +129 -0
- package/assets/clawd/clawd-mini-crabwalk.svg +76 -0
- package/assets/clawd/clawd-mini-enter-sleep.svg +64 -0
- package/assets/clawd/clawd-mini-enter.svg +114 -0
- package/assets/clawd/clawd-mini-happy.svg +124 -0
- package/assets/clawd/clawd-mini-idle.svg +83 -0
- package/assets/clawd/clawd-mini-peek.svg +82 -0
- package/assets/clawd/clawd-mini-sleep.svg +112 -0
- package/assets/clawd/clawd-mini-typing.svg +153 -0
- package/assets/clawd/clawd-notification.svg +438 -0
- package/assets/clawd/clawd-react-annoyed.svg +167 -0
- package/assets/clawd/clawd-react-double-jump.svg +229 -0
- package/assets/clawd/clawd-react-double.svg +108 -0
- package/assets/clawd/clawd-react-drag.svg +102 -0
- package/assets/clawd/clawd-react-left.svg +102 -0
- package/assets/clawd/clawd-react-right.svg +102 -0
- package/assets/clawd/clawd-sleeping.svg +118 -0
- package/assets/clawd/clawd-static-base.svg +21 -0
- package/assets/clawd/clawd-wake.svg +277 -0
- package/assets/clawd/clawd-working-building.svg +329 -0
- package/assets/clawd/clawd-working-carrying.svg +178 -0
- package/assets/clawd/clawd-working-debugger.svg +245 -0
- package/assets/clawd/clawd-working-juggling.svg +183 -0
- package/assets/clawd/clawd-working-sweeping.svg +248 -0
- package/assets/clawd/clawd-working-thinking.svg +501 -0
- package/assets/clawd/clawd-working-typing-boss.svg +307 -0
- package/assets/clawd/clawd-working-typing.svg +273 -0
- package/assets/clawd/clawd-working-ultrathink.svg +166 -0
- package/assets/clawd/clawd-working-wizard.svg +356 -0
- package/assets/clawd.svg +64 -0
- package/assets/cloudling/cloudling/cloudling-attention.svg +380 -0
- package/assets/cloudling/cloudling/cloudling-building.svg +667 -0
- package/assets/cloudling/cloudling/cloudling-carrying.svg +604 -0
- package/assets/cloudling/cloudling/cloudling-conducting.svg +616 -0
- package/assets/cloudling/cloudling/cloudling-dozing-to-sleeping.svg +578 -0
- package/assets/cloudling/cloudling/cloudling-dozing.svg +358 -0
- package/assets/cloudling/cloudling/cloudling-error.svg +428 -0
- package/assets/cloudling/cloudling/cloudling-idle-reading.svg +658 -0
- package/assets/cloudling/cloudling/cloudling-idle-to-dozing.svg +568 -0
- package/assets/cloudling/cloudling/cloudling-idle-to-sleeping.svg +811 -0
- package/assets/cloudling/cloudling/cloudling-idle.svg +255 -0
- package/assets/cloudling/cloudling/cloudling-juggling.svg +648 -0
- package/assets/cloudling/cloudling/cloudling-mini-alert.svg +390 -0
- package/assets/cloudling/cloudling/cloudling-mini-crabwalk.svg +335 -0
- package/assets/cloudling/cloudling/cloudling-mini-enter-roll-in.svg +226 -0
- package/assets/cloudling/cloudling/cloudling-mini-enter-sleep.svg +232 -0
- package/assets/cloudling/cloudling/cloudling-mini-happy.svg +371 -0
- package/assets/cloudling/cloudling/cloudling-mini-idle.svg +279 -0
- package/assets/cloudling/cloudling/cloudling-mini-peek.svg +328 -0
- package/assets/cloudling/cloudling/cloudling-mini-sleep.svg +260 -0
- package/assets/cloudling/cloudling/cloudling-mini-typing.svg +692 -0
- package/assets/cloudling/cloudling/cloudling-notification.svg +494 -0
- package/assets/cloudling/cloudling/cloudling-react-drag.svg +83 -0
- package/assets/cloudling/cloudling/cloudling-sleeping-to-idle.svg +555 -0
- package/assets/cloudling/cloudling/cloudling-sleeping.svg +500 -0
- package/assets/cloudling/cloudling/cloudling-sweeping.svg +292 -0
- package/assets/cloudling/cloudling/cloudling-thinking.svg +720 -0
- package/assets/cloudling/cloudling/cloudling-typing.svg +354 -0
- package/assets/cloudling/cloudling-attention.svg +380 -0
- package/assets/cloudling/cloudling-building.svg +667 -0
- package/assets/cloudling/cloudling-carrying.svg +604 -0
- package/assets/cloudling/cloudling-conducting.svg +616 -0
- package/assets/cloudling/cloudling-dozing-to-sleeping.svg +578 -0
- package/assets/cloudling/cloudling-dozing.svg +358 -0
- package/assets/cloudling/cloudling-error.svg +428 -0
- package/assets/cloudling/cloudling-idle-reading.svg +658 -0
- package/assets/cloudling/cloudling-idle-to-dozing.svg +568 -0
- package/assets/cloudling/cloudling-idle-to-sleeping.svg +811 -0
- package/assets/cloudling/cloudling-idle.svg +255 -0
- package/assets/cloudling/cloudling-juggling.svg +648 -0
- package/assets/cloudling/cloudling-mini-alert.svg +390 -0
- package/assets/cloudling/cloudling-mini-crabwalk.svg +335 -0
- package/assets/cloudling/cloudling-mini-enter-roll-in.svg +226 -0
- package/assets/cloudling/cloudling-mini-enter-sleep.svg +232 -0
- package/assets/cloudling/cloudling-mini-happy.svg +371 -0
- package/assets/cloudling/cloudling-mini-idle.svg +279 -0
- package/assets/cloudling/cloudling-mini-peek.svg +328 -0
- package/assets/cloudling/cloudling-mini-sleep.svg +260 -0
- package/assets/cloudling/cloudling-mini-typing.svg +692 -0
- package/assets/cloudling/cloudling-notification.svg +494 -0
- package/assets/cloudling/cloudling-react-drag.svg +83 -0
- package/assets/cloudling/cloudling-sleeping-to-idle.svg +555 -0
- package/assets/cloudling/cloudling-sleeping.svg +500 -0
- package/assets/cloudling/cloudling-sweeping.svg +292 -0
- package/assets/cloudling/cloudling-thinking.svg +720 -0
- package/assets/cloudling/cloudling-typing.svg +354 -0
- package/assets/cloudling.svg +255 -0
- package/assets/ghost-attention.svg +19 -0
- package/assets/ghost-carrying.svg +19 -0
- package/assets/ghost-collapsing.svg +19 -0
- package/assets/ghost-dozing.svg +23 -0
- package/assets/ghost-error.svg +19 -0
- package/assets/ghost-happy.svg +23 -0
- package/assets/ghost-idle-action1.svg +19 -0
- package/assets/ghost-idle-action2.svg +19 -0
- package/assets/ghost-idle-action3.svg +19 -0
- package/assets/ghost-idle.svg +11 -0
- package/assets/ghost-juggling.svg +19 -0
- package/assets/ghost-notification.svg +19 -0
- package/assets/ghost-react-double.svg +23 -0
- package/assets/ghost-react-drag.svg +23 -0
- package/assets/ghost-react-left.svg +19 -0
- package/assets/ghost-react-right.svg +19 -0
- package/assets/ghost-sleeping.svg +19 -0
- package/assets/ghost-sweeping.svg +19 -0
- package/assets/ghost-thinking.svg +19 -0
- package/assets/ghost-waking.svg +19 -0
- package/assets/ghost-working.svg +19 -0
- package/assets/ghost-yawning.svg +23 -0
- package/assets/ghost.svg +11 -0
- package/assets/robot-attention.svg +19 -0
- package/assets/robot-carrying.svg +19 -0
- package/assets/robot-collapsing.svg +19 -0
- package/assets/robot-dozing.svg +23 -0
- package/assets/robot-error.svg +19 -0
- package/assets/robot-happy.svg +23 -0
- package/assets/robot-idle-action1.svg +19 -0
- package/assets/robot-idle-action2.svg +19 -0
- package/assets/robot-idle-action3.svg +19 -0
- package/assets/robot-idle.svg +11 -0
- package/assets/robot-juggling.svg +19 -0
- package/assets/robot-notification.svg +19 -0
- package/assets/robot-react-double.svg +23 -0
- package/assets/robot-react-drag.svg +23 -0
- package/assets/robot-react-left.svg +19 -0
- package/assets/robot-react-right.svg +19 -0
- package/assets/robot-sleeping.svg +19 -0
- package/assets/robot-sweeping.svg +19 -0
- package/assets/robot-thinking.svg +19 -0
- package/assets/robot-waking.svg +19 -0
- package/assets/robot-working.svg +19 -0
- package/assets/robot-yawning.svg +23 -0
- package/assets/robot.svg +11 -0
- package/manifest.json +889 -0
- package/package.json +18 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 36 36" preserveAspectRatio="xMidYMid meet">
|
|
2
|
+
<style><![CDATA[
|
|
3
|
+
.phase-tag {
|
|
4
|
+
position: absolute; top: 14px; left: 50%;
|
|
5
|
+
transform: translateX(-50%);
|
|
6
|
+
padding: 4px 12px; border-radius: 6px;
|
|
7
|
+
background: rgba(255,165,107,.16); color: #FFD3B3;
|
|
8
|
+
font: 700 10px ui-monospace, monospace; letter-spacing: .05em;
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
.timeline {
|
|
12
|
+
position: absolute; left: 16px; right: 16px; bottom: 12px;
|
|
13
|
+
height: 4px; border-radius: 2px; overflow: hidden;
|
|
14
|
+
background: rgba(255,255,255,.06);
|
|
15
|
+
}
|
|
16
|
+
.timeline-fill {
|
|
17
|
+
height: 100%; width: 0; border-radius: 2px;
|
|
18
|
+
background: linear-gradient(to right, #B1A7FF, #FFD3B3, #FCE3B0, #FCC9DD);
|
|
19
|
+
}
|
|
20
|
+
.edge-rim-tight { stroke-opacity: .9; }
|
|
21
|
+
.edge-rim-soft { stroke-opacity: .75; }
|
|
22
|
+
.edge-contact { stroke-opacity: .9; }
|
|
23
|
+
.statusbar {
|
|
24
|
+
min-width: 760px; background: #1f1f26; color: #FFA56B;
|
|
25
|
+
border-radius: 8px; padding: 6px 14px; text-align: center;
|
|
26
|
+
font: 10px ui-monospace, monospace; letter-spacing: .05em;
|
|
27
|
+
}
|
|
28
|
+
@media (prefers-color-scheme: light) {
|
|
29
|
+
.phase-tag { background: rgba(201,122,74,.10); color: #C97A4A; }
|
|
30
|
+
.timeline { background: rgba(57,65,255,.08); }
|
|
31
|
+
.edge-rim-tight { stroke-opacity: .95; }
|
|
32
|
+
.edge-rim-soft { stroke-opacity: .85; }
|
|
33
|
+
.edge-contact { stroke-opacity: .95; }
|
|
34
|
+
.cloud-drop-shadow { flood-opacity: .13; }
|
|
35
|
+
.statusbar { background: #fff; color: #C97A4A; }
|
|
36
|
+
}
|
|
37
|
+
]]></style>
|
|
38
|
+
<defs>
|
|
39
|
+
<linearGradient id="body-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
40
|
+
<stop offset="0" stop-color="#93c5fd"/>
|
|
41
|
+
<stop offset="0.5" stop-color="#60a5fa"/>
|
|
42
|
+
<stop offset="1" stop-color="#3b82f6"/>
|
|
43
|
+
</linearGradient>
|
|
44
|
+
<linearGradient id="eye-grad" x1="0" y1="3" x2="0" y2="21" gradientUnits="userSpaceOnUse">
|
|
45
|
+
<stop offset="0" stop-color="#B1A7FF"/>
|
|
46
|
+
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
47
|
+
<stop offset="1" stop-color="#3941FF"/>
|
|
48
|
+
</linearGradient>
|
|
49
|
+
<radialGradient id="hi-grad" cx="0.45" cy="0.25" r="0.55">
|
|
50
|
+
<stop offset="0" stop-color="#93c5fd" stop-opacity="0.9"/>
|
|
51
|
+
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
52
|
+
</radialGradient>
|
|
53
|
+
|
|
54
|
+
<linearGradient id="warm-back-grad" x1="0" y1="-9" x2="0" y2="9" gradientUnits="userSpaceOnUse">
|
|
55
|
+
<stop offset="0" stop-color="#FFE3CE"/>
|
|
56
|
+
<stop offset="0.5" stop-color="#FFD3B3"/>
|
|
57
|
+
<stop offset="1" stop-color="#FFB89A"/>
|
|
58
|
+
</linearGradient>
|
|
59
|
+
<linearGradient id="warm-front-grad" x1="0" y1="-9" x2="0" y2="9" gradientUnits="userSpaceOnUse">
|
|
60
|
+
<stop offset="0" stop-color="#FFF1CE"/>
|
|
61
|
+
<stop offset="0.5" stop-color="#FCE3B0"/>
|
|
62
|
+
<stop offset="1" stop-color="#F5C374"/>
|
|
63
|
+
</linearGradient>
|
|
64
|
+
<radialGradient id="warm-hi" cx="32%" cy="24%" r="60%">
|
|
65
|
+
<stop offset="0" stop-color="#93c5fd" stop-opacity=".62"/>
|
|
66
|
+
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
67
|
+
</radialGradient>
|
|
68
|
+
|
|
69
|
+
<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"/>
|
|
70
|
+
<clipPath id="cloud-clip"><use href="#cloud-shape"/></clipPath>
|
|
71
|
+
<path id="warm-cloud-path" d="M-15.6 5.4c-2.4 0-4.4-1.8-4.4-4.0 0-1.9 1.5-3.6 3.5-4.0 .4-2.7 3.0-4.8 6.2-4.8 1.2 0 2.3 .3 3.3 .9 1.4-2.1 3.9-3.5 6.8-3.5 3.6 0 6.6 2.1 7.4 5.0 3.4 0 6.0 2.4 6.0 5.3 0 2.8-2.5 5.0-5.6 5.0H-15.6z"/>
|
|
72
|
+
|
|
73
|
+
<filter id="cloud-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
|
74
|
+
<feDropShadow class="cloud-drop-shadow" dx="0" dy="0.6" stdDeviation="0.8" flood-color="#3941FF" flood-opacity="0.35"/>
|
|
75
|
+
</filter>
|
|
76
|
+
<filter id="edge-blur" x="-40%" y="-40%" width="180%" height="180%">
|
|
77
|
+
<feGaussianBlur stdDeviation="0.16"/>
|
|
78
|
+
</filter>
|
|
79
|
+
<filter id="warm-shadow" x="-80%" y="-120%" width="260%" height="340%">
|
|
80
|
+
<feDropShadow dx="0" dy="0.9" stdDeviation="1.05" flood-color="#A2603C" flood-opacity=".18"/>
|
|
81
|
+
</filter>
|
|
82
|
+
<filter id="soft-glow" x="-120%" y="-120%" width="340%" height="340%">
|
|
83
|
+
<feGaussianBlur stdDeviation="0.7" result="blur"/>
|
|
84
|
+
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
85
|
+
</filter>
|
|
86
|
+
<filter id="q-glow" x="-80%" y="-80%" width="260%" height="260%">
|
|
87
|
+
<feGaussianBlur stdDeviation="0.28" result="b"/>
|
|
88
|
+
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
89
|
+
</filter>
|
|
90
|
+
|
|
91
|
+
<symbol id="warm-back-cloud" viewBox="-22 -14 44 28">
|
|
92
|
+
<g filter="url(#warm-shadow)">
|
|
93
|
+
<use href="#warm-cloud-path" fill="url(#warm-back-grad)" stroke="#E69E78" stroke-width="0.55" stroke-opacity="0.32" stroke-linejoin="round"/>
|
|
94
|
+
<ellipse cx="-3.5" cy="-4.5" rx="6" ry="2.8" fill="url(#warm-hi)" opacity=".48"/>
|
|
95
|
+
</g>
|
|
96
|
+
</symbol>
|
|
97
|
+
<symbol id="warm-front-cloud" viewBox="-22 -14 44 28">
|
|
98
|
+
<g filter="url(#warm-shadow)">
|
|
99
|
+
<use href="#warm-cloud-path" fill="url(#warm-front-grad)" stroke="#D9A04F" stroke-width="0.55" stroke-opacity="0.36" stroke-linejoin="round"/>
|
|
100
|
+
<ellipse cx="-3" cy="-4" rx="5.2" ry="2.4" fill="url(#warm-hi)" opacity=".55"/>
|
|
101
|
+
</g>
|
|
102
|
+
</symbol>
|
|
103
|
+
</defs>
|
|
104
|
+
|
|
105
|
+
<!-- 后层暖云 (在 cloudling 后面) -->
|
|
106
|
+
<g id="warmBackLayer" filter="url(#soft-glow)" opacity="0">
|
|
107
|
+
<g id="warmBackRoot">
|
|
108
|
+
<use href="#warm-back-cloud" x="-22" y="-14" width="44" height="28"/>
|
|
109
|
+
</g>
|
|
110
|
+
</g>
|
|
111
|
+
|
|
112
|
+
<!-- cloudling 主体 -->
|
|
113
|
+
<g id="cloudRoot">
|
|
114
|
+
<g id="bodyWarp">
|
|
115
|
+
<g id="cloudShape">
|
|
116
|
+
<g id="edge-system">
|
|
117
|
+
<use class="edge-contact" href="#cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.4" stroke-linejoin="round" transform="translate(0 0.18)" filter="url(#edge-blur)"/>
|
|
118
|
+
<use class="edge-rim-soft" href="#cloud-shape" fill="none" stroke="#7A9DFF" stroke-width="1.2" stroke-linejoin="round" filter="url(#edge-blur)"/>
|
|
119
|
+
<use class="edge-rim-tight" href="#cloud-shape" fill="none" stroke="#aab3c6" stroke-width="0.8" stroke-linejoin="round"/>
|
|
120
|
+
</g>
|
|
121
|
+
<g filter="url(#cloud-shadow)">
|
|
122
|
+
<use href="#cloud-shape" fill="url(#body-grad)"/>
|
|
123
|
+
<rect x="1" y="2" width="22" height="11" fill="url(#hi-grad)" clip-path="url(#cloud-clip)"/>
|
|
124
|
+
</g>
|
|
125
|
+
</g>
|
|
126
|
+
</g>
|
|
127
|
+
<g id="eyeLayer">
|
|
128
|
+
<g id="capsuleEyes">
|
|
129
|
+
<rect id="capL" x="7.7" y="9.5" width="2.6" height="5" rx="1.3" ry="1.3" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
130
|
+
<rect id="capR" x="13.7" y="9.5" width="2.6" height="5" rx="1.3" ry="1.3" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
131
|
+
</g>
|
|
132
|
+
</g>
|
|
133
|
+
</g>
|
|
134
|
+
|
|
135
|
+
<!-- 前层暖云 (在 cloudling 前面) -->
|
|
136
|
+
<g id="warmFrontLayer" filter="url(#soft-glow)" opacity="0">
|
|
137
|
+
<g id="warmFrontRoot">
|
|
138
|
+
<use href="#warm-front-cloud" x="-22" y="-14" width="44" height="28"/>
|
|
139
|
+
</g>
|
|
140
|
+
</g>
|
|
141
|
+
|
|
142
|
+
<!-- 问号 (跟着 cloudling 头顶, 默认隐藏) -->
|
|
143
|
+
<g id="questionMark" opacity="0" filter="url(#q-glow)">
|
|
144
|
+
<g id="qScale" transform="translate(12 0)">
|
|
145
|
+
<path d="M -1.4 -1.6 Q -1.4 -3.0 0 -3.0 Q 1.4 -3.0 1.4 -1.6 Q 1.4 -0.6 0.4 0.0 Q -0.1 0.35 -0.1 1.3"
|
|
146
|
+
fill="none" stroke="url(#eye-grad)" stroke-width="0.72" stroke-linecap="round" stroke-linejoin="round"/>
|
|
147
|
+
<circle cx="-0.1" cy="2.7" r="0.44" fill="url(#eye-grad)"/>
|
|
148
|
+
</g>
|
|
149
|
+
</g>
|
|
150
|
+
<g id="__cloudling-export-shims" opacity="0" pointer-events="none" style="display:none">
|
|
151
|
+
<g id="btn-pause"></g>
|
|
152
|
+
<g id="btn-reset"></g>
|
|
153
|
+
<g id="btn-restart"></g>
|
|
154
|
+
<g id="btn-theme"></g>
|
|
155
|
+
<g id="phaseTag"></g>
|
|
156
|
+
<g id="statusbar"></g>
|
|
157
|
+
<g id="timelineFill"></g>
|
|
158
|
+
</g>
|
|
159
|
+
|
|
160
|
+
<script type="application/ecmascript"><![CDATA[
|
|
161
|
+
const CENTER = { x: 12, y: 12 };
|
|
162
|
+
const DEFAULTS = {
|
|
163
|
+
duration: 6.2,
|
|
164
|
+
gap: 1.0,
|
|
165
|
+
bodyBreath: 0.030,
|
|
166
|
+
rimRotate: 1.5,
|
|
167
|
+
warmSpeed: 1.00,
|
|
168
|
+
warmOp: 0.95,
|
|
169
|
+
barrierGap: 3.0,
|
|
170
|
+
barrierY: 16.5,
|
|
171
|
+
hideTy: 1.0,
|
|
172
|
+
peekTy: -4.5,
|
|
173
|
+
peekTx: 8.5,
|
|
174
|
+
peekLean: 12.5,
|
|
175
|
+
peekScaleDip: 0.075,
|
|
176
|
+
eyeWiden: 0.19,
|
|
177
|
+
rightEyeOffsetX: 1.40,
|
|
178
|
+
rightEyeLift: 0.30,
|
|
179
|
+
qSize: 1.00,
|
|
180
|
+
qOffsetX: -6.0,
|
|
181
|
+
qOffsetY: -10.0,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const controls = Object.fromEntries(Object.entries(DEFAULTS).map(([key, value]) => [key, { value: String(value) }]));
|
|
185
|
+
const cloudRoot = document.getElementById('cloudRoot');
|
|
186
|
+
const bodyWarp = document.getElementById('bodyWarp');
|
|
187
|
+
const cloudShape = document.getElementById('cloudShape');
|
|
188
|
+
const eyeLayer = document.getElementById('eyeLayer');
|
|
189
|
+
const capL = document.getElementById('capL');
|
|
190
|
+
const capR = document.getElementById('capR');
|
|
191
|
+
const warmBackLayer = document.getElementById('warmBackLayer');
|
|
192
|
+
const warmBackRoot = document.getElementById('warmBackRoot');
|
|
193
|
+
const warmFrontLayer = document.getElementById('warmFrontLayer');
|
|
194
|
+
const warmFrontRoot = document.getElementById('warmFrontRoot');
|
|
195
|
+
const questionMark = document.getElementById('questionMark');
|
|
196
|
+
const qScale = document.getElementById('qScale');
|
|
197
|
+
const phaseTag = document.getElementById('phaseTag');
|
|
198
|
+
const timelineFill = document.getElementById('timelineFill');
|
|
199
|
+
const statusbar = document.getElementById('statusbar');
|
|
200
|
+
|
|
201
|
+
let start = performance.now();
|
|
202
|
+
let paused = false;
|
|
203
|
+
let pauseAt = 0;
|
|
204
|
+
|
|
205
|
+
function clamp(v, a = 0, b = 1) { return Math.max(a, Math.min(b, v)); }
|
|
206
|
+
function lerp(a, b, t) { return a + (b - a) * t; }
|
|
207
|
+
function easeIn(t) { return t * t; }
|
|
208
|
+
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
|
|
209
|
+
function easeInOut(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
|
|
210
|
+
function smoother(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
|
|
211
|
+
function wave(u) { return Math.sin(clamp(u) * Math.PI); }
|
|
212
|
+
function remap(t, a, b) { return clamp((t - a) / (b - a)); }
|
|
213
|
+
function enterHold(t, inEnd = 0.18) {
|
|
214
|
+
if (t < inEnd) return easeOut(t / inEnd);
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ===== 9 段时间线: 6.2s 叙事版 =====
|
|
219
|
+
// 1 暖云入场+合拢 0.00 - 1.10
|
|
220
|
+
// 2 左探前压扁蓄力 1.10 - 1.45
|
|
221
|
+
// 3 左探+问号 1.45 - 2.35
|
|
222
|
+
// 4 缩回 1 2.35 - 2.75
|
|
223
|
+
// 5 右探前压扁蓄力 2.75 - 3.10
|
|
224
|
+
// 6 右探+左上右中眨眼 3.10 - 4.65
|
|
225
|
+
// 7 缩回 2 4.65 - 5.05
|
|
226
|
+
// 8 暖云散开中心露出 5.05 - 5.85
|
|
227
|
+
// 9 回到 idle 5.85 - 6.20
|
|
228
|
+
function phaseAt(p) {
|
|
229
|
+
if (p < 1.10 / 6.20) return { name: 'warm-enter', local: remap(p, 0, 1.10 / 6.20) };
|
|
230
|
+
if (p < 1.45 / 6.20) return { name: 'crouch-left', local: remap(p, 1.10 / 6.20, 1.45 / 6.20) };
|
|
231
|
+
if (p < 2.35 / 6.20) return { name: 'peek-left', local: remap(p, 1.45 / 6.20, 2.35 / 6.20) };
|
|
232
|
+
if (p < 2.75 / 6.20) return { name: 'retract-1', local: remap(p, 2.35 / 6.20, 2.75 / 6.20) };
|
|
233
|
+
if (p < 3.10 / 6.20) return { name: 'crouch-right', local: remap(p, 2.75 / 6.20, 3.10 / 6.20) };
|
|
234
|
+
if (p < 4.65 / 6.20) return { name: 'peek-right', local: remap(p, 3.10 / 6.20, 4.65 / 6.20) };
|
|
235
|
+
if (p < 5.05 / 6.20) return { name: 'retract-2', local: remap(p, 4.65 / 6.20, 5.05 / 6.20) };
|
|
236
|
+
if (p < 5.85 / 6.20) return { name: 'cloud-fade', local: remap(p, 5.05 / 6.20, 5.85 / 6.20) };
|
|
237
|
+
return { name: 'recover', local: remap(p, 5.85 / 6.20, 1) };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function setEyeRect(node, cx, cy, w, h) {
|
|
241
|
+
node.setAttribute('width', w.toFixed(3));
|
|
242
|
+
node.setAttribute('height', h.toFixed(3));
|
|
243
|
+
const rx = Math.min(w / 2, h / 2, 1.35);
|
|
244
|
+
node.setAttribute('rx', rx.toFixed(3));
|
|
245
|
+
node.setAttribute('ry', rx.toFixed(3));
|
|
246
|
+
node.setAttribute('x', (cx - w / 2).toFixed(3));
|
|
247
|
+
node.setAttribute('y', (cy - h / 2).toFixed(3));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ===== 暖云更新 =====
|
|
251
|
+
// 屏障位置: 偏向可视中心, 后层 x=warmCenterX-barrierGap, 前层 x=warmCenterX+barrierGap
|
|
252
|
+
// 入场起点: 从画面内侧进入, 避免 Electron 裁切框露出硬边
|
|
253
|
+
// 消散方向: 先淡出再外飘, 避免退出时被 viewBox 切出直边
|
|
254
|
+
function updateWarmClouds(t, p, phase, v) {
|
|
255
|
+
const warmCenterX = 15.4;
|
|
256
|
+
const restBackX = warmCenterX - v.barrierGap;
|
|
257
|
+
const restFrontX = warmCenterX + v.barrierGap;
|
|
258
|
+
const enterBackX = 12.4;
|
|
259
|
+
const enterFrontX = 13.4;
|
|
260
|
+
const restY = v.barrierY;
|
|
261
|
+
const microFloat = Math.sin(t * 1.6) * 0.12;
|
|
262
|
+
|
|
263
|
+
let backX = enterBackX, backY = restY;
|
|
264
|
+
let frontX = enterFrontX, frontY = restY;
|
|
265
|
+
let backOp = 0, frontOp = 0;
|
|
266
|
+
let backRot = 0, frontRot = 0;
|
|
267
|
+
let backScaleX = 1.0, backScaleY = 1.0;
|
|
268
|
+
let frontScaleX = 1.0, frontScaleY = 1.0;
|
|
269
|
+
|
|
270
|
+
if (phase.name === 'idle') {
|
|
271
|
+
backOp = 0; frontOp = 0;
|
|
272
|
+
} else if (phase.name === 'warm-enter') {
|
|
273
|
+
const qBack = easeOut(clamp(phase.local * 1.10 * v.warmSpeed));
|
|
274
|
+
const qFront = easeOut(clamp((phase.local - 0.18) * 1.25 * v.warmSpeed));
|
|
275
|
+
const bump = wave(remap(phase.local, 0.68, 1.0));
|
|
276
|
+
backX = lerp(enterBackX, restBackX, qBack);
|
|
277
|
+
frontX = lerp(enterFrontX, restFrontX, qFront);
|
|
278
|
+
backY = restY + microFloat * 0.5 + bump * 0.24;
|
|
279
|
+
frontY = restY + microFloat + bump * 0.30;
|
|
280
|
+
backScaleX = 1 + bump * 0.035;
|
|
281
|
+
backScaleY = 1 - bump * 0.045;
|
|
282
|
+
frontScaleX = 1 + bump * 0.045;
|
|
283
|
+
frontScaleY = 1 - bump * 0.055;
|
|
284
|
+
backOp = v.warmOp * qBack;
|
|
285
|
+
frontOp = v.warmOp * qFront;
|
|
286
|
+
} else if (phase.name === 'crouch-left' || phase.name === 'peek-left' || phase.name === 'retract-1' ||
|
|
287
|
+
phase.name === 'crouch-right' || phase.name === 'peek-right' || phase.name === 'retract-2') {
|
|
288
|
+
const contactBump = (phase.name === 'crouch-left' || phase.name === 'crouch-right') ? wave(phase.local) : 0;
|
|
289
|
+
const leftPress = phase.name === 'peek-left'
|
|
290
|
+
? enterHold(phase.local, 0.18)
|
|
291
|
+
: (phase.name === 'retract-1' ? 1 - smoother(phase.local) : 0);
|
|
292
|
+
const rightPress = phase.name === 'peek-right'
|
|
293
|
+
? enterHold(phase.local, 0.16)
|
|
294
|
+
: (phase.name === 'retract-2' ? 1 - smoother(phase.local) : 0);
|
|
295
|
+
backX = restBackX + microFloat * 0.3;
|
|
296
|
+
backY = restY + microFloat * 0.5 + contactBump * 0.14 + leftPress * 0.46;
|
|
297
|
+
frontX = restFrontX + microFloat * 0.3;
|
|
298
|
+
frontY = restY + microFloat + contactBump * 0.18 + rightPress * 0.46;
|
|
299
|
+
backRot = -leftPress * 2.6;
|
|
300
|
+
frontRot = rightPress * 2.6;
|
|
301
|
+
backScaleX = 1 + contactBump * 0.018 + leftPress * 0.035;
|
|
302
|
+
backScaleY = 1 - contactBump * 0.025 - leftPress * 0.050;
|
|
303
|
+
frontScaleX = 1 + contactBump * 0.022 + rightPress * 0.035;
|
|
304
|
+
frontScaleY = 1 - contactBump * 0.030 - rightPress * 0.050;
|
|
305
|
+
backOp = v.warmOp;
|
|
306
|
+
frontOp = v.warmOp;
|
|
307
|
+
} else if (phase.name === 'cloud-fade') {
|
|
308
|
+
const q = phase.local;
|
|
309
|
+
const driftQ = smoother(remap(q, 0.18, 1));
|
|
310
|
+
const driftDistance = 42;
|
|
311
|
+
const fade = 1 - smoother(remap(q, 0.08, 0.48));
|
|
312
|
+
backX = restBackX - driftDistance * driftQ;
|
|
313
|
+
backY = restY + microFloat * 0.5 - 1 * driftQ;
|
|
314
|
+
frontX = restFrontX + driftDistance * driftQ;
|
|
315
|
+
frontY = restY + microFloat - 1 * driftQ;
|
|
316
|
+
backRot = -3 * driftQ;
|
|
317
|
+
frontRot = 3 * driftQ;
|
|
318
|
+
backScaleX = 1.0 - driftQ * 0.10;
|
|
319
|
+
backScaleY = 1.0 - driftQ * 0.06;
|
|
320
|
+
frontScaleX = 1.0 - driftQ * 0.10;
|
|
321
|
+
frontScaleY = 1.0 - driftQ * 0.06;
|
|
322
|
+
backOp = v.warmOp * fade;
|
|
323
|
+
frontOp = v.warmOp * fade;
|
|
324
|
+
} else {
|
|
325
|
+
// recover, idle: 已散
|
|
326
|
+
backOp = 0; frontOp = 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (backOp > 0 || frontOp > 0) {
|
|
330
|
+
const warmBreath = Math.sin(t * 1.35) * 0.008;
|
|
331
|
+
backScaleX += warmBreath;
|
|
332
|
+
backScaleY -= warmBreath * 0.45;
|
|
333
|
+
frontScaleX -= warmBreath * 0.55;
|
|
334
|
+
frontScaleY += warmBreath * 0.35;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
warmBackRoot.setAttribute('transform',
|
|
338
|
+
`translate(${backX.toFixed(3)} ${backY.toFixed(3)}) rotate(${backRot.toFixed(2)}) scale(${backScaleX.toFixed(3)} ${backScaleY.toFixed(3)})`);
|
|
339
|
+
warmBackLayer.setAttribute('opacity', backOp.toFixed(3));
|
|
340
|
+
warmFrontRoot.setAttribute('transform',
|
|
341
|
+
`translate(${frontX.toFixed(3)} ${frontY.toFixed(3)}) rotate(${frontRot.toFixed(2)}) scale(${frontScaleX.toFixed(3)} ${frontScaleY.toFixed(3)})`);
|
|
342
|
+
warmFrontLayer.setAttribute('opacity', frontOp.toFixed(3));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ===== 问号更新 =====
|
|
346
|
+
function updateQuestionMark(phase, cloudTx, cloudTy, v) {
|
|
347
|
+
// 只在 peek-left 段显示
|
|
348
|
+
let qOp = 0;
|
|
349
|
+
let qScaleVal = 0;
|
|
350
|
+
let qFloatY = 0;
|
|
351
|
+
if (phase.name === 'peek-left') {
|
|
352
|
+
const q = phase.local;
|
|
353
|
+
// 探头出来后慢慢从头外空白处冒出, 避免贴纸式弹跳.
|
|
354
|
+
const inT = smoother(remap(q, 0.22, 0.68));
|
|
355
|
+
const outT = smoother(remap(q, 0.78, 0.98));
|
|
356
|
+
qOp = inT * (1 - outT);
|
|
357
|
+
qScaleVal = lerp(0.78, 1.0, inT) * (1 - 0.12 * outT);
|
|
358
|
+
qFloatY = lerp(1.05, -0.55, inT) - 0.25 * outT;
|
|
359
|
+
}
|
|
360
|
+
// 问号位置: 跟着 cloudling 头顶 (cloudling 中心 + headOffset)
|
|
361
|
+
const qX = CENTER.x + cloudTx + v.qOffsetX;
|
|
362
|
+
const qY = CENTER.y + cloudTy + v.qOffsetY + qFloatY;
|
|
363
|
+
questionMark.setAttribute('opacity', qOp.toFixed(3));
|
|
364
|
+
qScale.setAttribute('transform',
|
|
365
|
+
`translate(${qX.toFixed(2)} ${qY.toFixed(2)}) scale(${(qScaleVal * v.qSize).toFixed(3)})`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ===== cloudling 更新 =====
|
|
369
|
+
function updateCloud(t, p, phase, v) {
|
|
370
|
+
let tx = 0, ty = 0;
|
|
371
|
+
let sx = 1, sy = 1;
|
|
372
|
+
let rootScale = 1;
|
|
373
|
+
let rot = 0;
|
|
374
|
+
let eyeShiftX = 0, eyeShiftY = 0;
|
|
375
|
+
let widenFactor = 1;
|
|
376
|
+
let heightFactor = 1;
|
|
377
|
+
let cloudOpacity = 1;
|
|
378
|
+
|
|
379
|
+
if (phase.name === 'idle') {
|
|
380
|
+
const breath = Math.sin(t * Math.PI * 2 / 3.6) * 0.012;
|
|
381
|
+
sx = 1 - breath * 0.4;
|
|
382
|
+
sy = 1 + breath;
|
|
383
|
+
} else if (phase.name === 'warm-enter') {
|
|
384
|
+
// cloudling 站在原地, 眼睛追着暖云从左飘到中央
|
|
385
|
+
const q = phase.local;
|
|
386
|
+
const hideSink = smoother(remap(q, 0.58, 1.0));
|
|
387
|
+
ty = v.hideTy * hideSink;
|
|
388
|
+
eyeShiftX = lerp(-0.40, -0.05, smoother(q)); // 看左 → 几乎归正 (云在中下方了)
|
|
389
|
+
eyeShiftY = lerp(0.0, 0.20, smoother(q)); // 由平视 → 略向下看 (云沉到中下)
|
|
390
|
+
const breath = Math.sin(t * Math.PI * 2 / 3.6) * 0.010;
|
|
391
|
+
sx = 1 - breath * 0.4;
|
|
392
|
+
sy = 1 + breath;
|
|
393
|
+
} else if (phase.name === 'crouch-left') {
|
|
394
|
+
// 左探前明确压扁蓄力, 让起跳有准备动作.
|
|
395
|
+
const q = phase.local;
|
|
396
|
+
const crouch = wave(q);
|
|
397
|
+
tx = 0;
|
|
398
|
+
ty = v.hideTy;
|
|
399
|
+
rootScale = 1 - crouch * 0.020;
|
|
400
|
+
sx = 1 + crouch * 0.085;
|
|
401
|
+
sy = 1 - crouch * 0.115;
|
|
402
|
+
rot = -crouch * 1.5;
|
|
403
|
+
eyeShiftY = lerp(0.18, 0.32, crouch);
|
|
404
|
+
cloudOpacity = 1;
|
|
405
|
+
} else if (phase.name === 'peek-left') {
|
|
406
|
+
// 从蓄力释放到左上, 然后停住给问号和小眼神.
|
|
407
|
+
const q = phase.local;
|
|
408
|
+
const outT = enterHold(q, 0.18);
|
|
409
|
+
tx = lerp(0, -v.peekTx, outT);
|
|
410
|
+
ty = lerp(v.hideTy, v.peekTy, outT);
|
|
411
|
+
const peekPulse = Math.sin(clamp(q) * Math.PI) * v.peekScaleDip * 0.16;
|
|
412
|
+
rootScale = 1 - outT * v.peekScaleDip + peekPulse;
|
|
413
|
+
sx = 1 + outT * v.peekScaleDip * 0.34;
|
|
414
|
+
sy = 1 - outT * v.peekScaleDip * 0.38;
|
|
415
|
+
// 眼睛: 露出来后好奇瞪大 + 左右扫
|
|
416
|
+
if (q > 0.24) {
|
|
417
|
+
widenFactor = 1 + v.eyeWiden;
|
|
418
|
+
heightFactor = 1 + 0.04;
|
|
419
|
+
const scanQ = remap(q, 0.28, 0.76);
|
|
420
|
+
eyeShiftX = Math.sin(scanQ * Math.PI * 1.25) * 0.28;
|
|
421
|
+
eyeShiftY = -0.05 + Math.sin(scanQ * Math.PI) * 0.06;
|
|
422
|
+
}
|
|
423
|
+
rot = -outT * v.peekLean + Math.sin(clamp(q) * Math.PI * 2) * 0.45;
|
|
424
|
+
cloudOpacity = 1;
|
|
425
|
+
} else if (phase.name === 'retract-1') {
|
|
426
|
+
// "哧溜" 缩回中央被遮 (easeIn 后段加速)
|
|
427
|
+
const q = phase.local;
|
|
428
|
+
// 0-0.65: 快速缩 (easeIn), 0.65-1.0: 完全藏好
|
|
429
|
+
const inT = q < 0.65 ? easeIn(q / 0.65) : 1.0;
|
|
430
|
+
tx = lerp(-v.peekTx, 0, inT);
|
|
431
|
+
ty = lerp(v.peekTy, v.hideTy, inT);
|
|
432
|
+
rootScale = lerp(1 - v.peekScaleDip, 0.985, inT);
|
|
433
|
+
sx = lerp(1 + v.peekScaleDip * 0.34, 1, inT);
|
|
434
|
+
sy = lerp(1 - v.peekScaleDip * 0.38, 1, inT);
|
|
435
|
+
rot = lerp(-v.peekLean, 0, inT);
|
|
436
|
+
// 缩回时眼睛快速归正
|
|
437
|
+
eyeShiftX = Math.sin(Math.PI * 1.25) * 0.28 * (1 - easeOut(remap(q, 0, 0.38)));
|
|
438
|
+
eyeShiftY = -0.05 * (1 - easeOut(remap(q, 0, 0.38)));
|
|
439
|
+
widenFactor = 1 + v.eyeWiden * (1 - inT);
|
|
440
|
+
cloudOpacity = 1;
|
|
441
|
+
} else if (phase.name === 'crouch-right') {
|
|
442
|
+
// 第二次蓄力稍短, 为右探观察做铺垫.
|
|
443
|
+
const q = phase.local;
|
|
444
|
+
const crouch = wave(q);
|
|
445
|
+
tx = 0;
|
|
446
|
+
ty = v.hideTy;
|
|
447
|
+
rootScale = 1 - crouch * 0.018;
|
|
448
|
+
sx = 1 + crouch * 0.075;
|
|
449
|
+
sy = 1 - crouch * 0.105;
|
|
450
|
+
rot = crouch * 1.3;
|
|
451
|
+
eyeShiftY = lerp(0.16, 0.30, crouch);
|
|
452
|
+
cloudOpacity = 1;
|
|
453
|
+
} else if (phase.name === 'peek-right') {
|
|
454
|
+
// 第二次多停一拍, 像确认状况: 左、上、右、下看一圈再准备缩回.
|
|
455
|
+
const q = phase.local;
|
|
456
|
+
const outT = enterHold(q, 0.16);
|
|
457
|
+
const bodyCheck = Math.sin(remap(q, 0.28, 0.78) * Math.PI * 2) * wave(remap(q, 0.22, 0.92)) * 0.16;
|
|
458
|
+
tx = lerp(0, v.peekTx, outT);
|
|
459
|
+
tx += bodyCheck;
|
|
460
|
+
ty = lerp(v.hideTy, v.peekTy, outT);
|
|
461
|
+
const peekPulse = Math.sin(clamp(q) * Math.PI) * v.peekScaleDip * 0.16;
|
|
462
|
+
rootScale = 1 - outT * v.peekScaleDip + peekPulse;
|
|
463
|
+
sx = 1 + outT * v.peekScaleDip * 0.34;
|
|
464
|
+
sy = 1 - outT * v.peekScaleDip * 0.38;
|
|
465
|
+
const rightEyeBiasT = smoother(remap(q, 0.08, 0.36));
|
|
466
|
+
if (q > 0.18) {
|
|
467
|
+
widenFactor = 1 + v.eyeWiden;
|
|
468
|
+
const scanAmp = 0.28;
|
|
469
|
+
const blink = Math.max(wave(remap(q, 0.78, 0.84)), wave(remap(q, 0.88, 0.94)));
|
|
470
|
+
heightFactor = (1 + 0.04) * (1 - blink * 0.86);
|
|
471
|
+
widenFactor *= 1 + blink * 0.08;
|
|
472
|
+
if (blink > 0.05) {
|
|
473
|
+
eyeShiftX = 0;
|
|
474
|
+
eyeShiftY = 0.03;
|
|
475
|
+
} else if (q < 0.30) {
|
|
476
|
+
eyeShiftX = 0;
|
|
477
|
+
eyeShiftY = 0.00;
|
|
478
|
+
} else if (q < 0.42) {
|
|
479
|
+
eyeShiftX = lerp(0, -0.06, easeInOut(remap(q, 0.30, 0.42)));
|
|
480
|
+
eyeShiftY = lerp(0.00, -0.30, easeOut(remap(q, 0.30, 0.42)));
|
|
481
|
+
} else if (q < 0.50) {
|
|
482
|
+
eyeShiftX = -0.06;
|
|
483
|
+
eyeShiftY = -0.30;
|
|
484
|
+
} else if (q < 0.58) {
|
|
485
|
+
eyeShiftX = lerp(-0.06, scanAmp, easeInOut(remap(q, 0.50, 0.58)));
|
|
486
|
+
eyeShiftY = lerp(-0.30, 0.00, easeInOut(remap(q, 0.50, 0.58)));
|
|
487
|
+
} else if (q < 0.66) {
|
|
488
|
+
eyeShiftX = scanAmp;
|
|
489
|
+
eyeShiftY = 0.00;
|
|
490
|
+
} else if (q < 0.74) {
|
|
491
|
+
eyeShiftX = lerp(scanAmp, 0, easeOut(remap(q, 0.66, 0.74)));
|
|
492
|
+
eyeShiftY = 0.00;
|
|
493
|
+
} else {
|
|
494
|
+
eyeShiftX = 0;
|
|
495
|
+
eyeShiftY = 0.00;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
eyeShiftX += v.rightEyeOffsetX * rightEyeBiasT;
|
|
499
|
+
eyeShiftY -= v.rightEyeLift * rightEyeBiasT;
|
|
500
|
+
rot = outT * v.peekLean - Math.sin(clamp(q) * Math.PI * 2) * 0.45 + bodyCheck * 0.65;
|
|
501
|
+
cloudOpacity = 1;
|
|
502
|
+
} else if (phase.name === 'retract-2') {
|
|
503
|
+
const q = phase.local;
|
|
504
|
+
const inT = q < 0.65 ? easeIn(q / 0.65) : 1.0;
|
|
505
|
+
tx = lerp(v.peekTx, 0, inT);
|
|
506
|
+
ty = lerp(v.peekTy, v.hideTy, inT);
|
|
507
|
+
rootScale = lerp(1 - v.peekScaleDip, 0.985, inT);
|
|
508
|
+
sx = lerp(1 + v.peekScaleDip * 0.34, 1, inT);
|
|
509
|
+
sy = lerp(1 - v.peekScaleDip * 0.38, 1, inT);
|
|
510
|
+
rot = lerp(v.peekLean, 0, inT);
|
|
511
|
+
const eyeReturn = 1 - easeOut(remap(q, 0, 0.35));
|
|
512
|
+
eyeShiftX = v.rightEyeOffsetX * eyeReturn;
|
|
513
|
+
eyeShiftY = -v.rightEyeLift * eyeReturn;
|
|
514
|
+
widenFactor = 1 + v.eyeWiden * (1 - inT);
|
|
515
|
+
cloudOpacity = 1;
|
|
516
|
+
} else if (phase.name === 'cloud-fade') {
|
|
517
|
+
// 云先左右散开, 再露出完整 Cloudling.
|
|
518
|
+
const q = phase.local;
|
|
519
|
+
const reveal = smoother(remap(q, 0.12, 0.42));
|
|
520
|
+
tx = 0;
|
|
521
|
+
ty = lerp(v.hideTy, 0, smoother(remap(q, 0.20, 0.66)));
|
|
522
|
+
rootScale = 0.985 + reveal * 0.015;
|
|
523
|
+
sx = 1 + Math.sin(reveal * Math.PI) * 0.012;
|
|
524
|
+
sy = 1 - Math.sin(reveal * Math.PI) * 0.008;
|
|
525
|
+
cloudOpacity = 1;
|
|
526
|
+
} else if (phase.name === 'recover') {
|
|
527
|
+
// 完整露出后, 眼睛轻扫一下回 idle.
|
|
528
|
+
const q = phase.local;
|
|
529
|
+
if (q < 0.35) {
|
|
530
|
+
tx = 0; ty = 0;
|
|
531
|
+
const local = q / 0.35;
|
|
532
|
+
eyeShiftX = -0.30 * easeOut(local);
|
|
533
|
+
eyeShiftY = 0.04;
|
|
534
|
+
} else if (q < 0.70) {
|
|
535
|
+
tx = 0; ty = 0;
|
|
536
|
+
const local = (q - 0.35) / 0.35;
|
|
537
|
+
eyeShiftX = lerp(-0.30, 0.30, easeInOut(local));
|
|
538
|
+
eyeShiftY = 0.04;
|
|
539
|
+
} else {
|
|
540
|
+
tx = 0; ty = 0;
|
|
541
|
+
const local = (q - 0.70) / 0.30;
|
|
542
|
+
eyeShiftX = lerp(0.30, 0, easeOut(local));
|
|
543
|
+
eyeShiftY = lerp(0.04, 0, local);
|
|
544
|
+
const breath = Math.sin(t * Math.PI * 2 / 3.6) * 0.010;
|
|
545
|
+
sx = 1 - breath * 0.4;
|
|
546
|
+
sy = 1 + breath;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let rimRot = 0;
|
|
551
|
+
if (cloudOpacity > 0.05) {
|
|
552
|
+
const breath = Math.sin(t * Math.PI * 2 / 3.2);
|
|
553
|
+
const liveBreath = breath * v.bodyBreath;
|
|
554
|
+
const rimWave = Math.sin(t * Math.PI * 2 / 4.8 + 0.7);
|
|
555
|
+
rootScale *= 1 + liveBreath;
|
|
556
|
+
sx -= liveBreath * 0.18;
|
|
557
|
+
sy += liveBreath * 0.42;
|
|
558
|
+
rimRot = rimWave * v.rimRotate;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// 应用 transform
|
|
562
|
+
cloudRoot.setAttribute('transform',
|
|
563
|
+
`translate(${tx.toFixed(3)} ${ty.toFixed(3)}) rotate(${rot.toFixed(3)} ${CENTER.x} ${CENTER.y}) translate(${CENTER.x} ${CENTER.y}) scale(${rootScale.toFixed(4)}) translate(${-CENTER.x} ${-CENTER.y})`);
|
|
564
|
+
cloudRoot.setAttribute('opacity', cloudOpacity.toFixed(3));
|
|
565
|
+
bodyWarp.setAttribute('transform',
|
|
566
|
+
`translate(${CENTER.x} ${CENTER.y}) scale(${sx.toFixed(4)} ${sy.toFixed(4)}) translate(${-CENTER.x} ${-CENTER.y})`);
|
|
567
|
+
cloudShape.setAttribute('transform', `rotate(${rimRot.toFixed(3)} ${CENTER.x} ${CENTER.y})`);
|
|
568
|
+
|
|
569
|
+
// 眼睛 layer 整体偏移 (eye-track)
|
|
570
|
+
eyeLayer.setAttribute('transform', `translate(${eyeShiftX.toFixed(3)} ${eyeShiftY.toFixed(3)})`);
|
|
571
|
+
|
|
572
|
+
// 眼睛 capsule 大小
|
|
573
|
+
const eyeW = 2.6 * widenFactor;
|
|
574
|
+
const eyeH = 5.0 * heightFactor;
|
|
575
|
+
setEyeRect(capL, 9, 12, eyeW, eyeH);
|
|
576
|
+
setEyeRect(capR, 15, 12, eyeW, eyeH);
|
|
577
|
+
|
|
578
|
+
return { tx, ty, rot };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function values() {
|
|
582
|
+
const out = {};
|
|
583
|
+
Object.entries(controls).forEach(([key, node]) => {
|
|
584
|
+
out[key] = Number(node.value);
|
|
585
|
+
const label = document.getElementById(`${key}V`);
|
|
586
|
+
if (!label) return;
|
|
587
|
+
if (key === 'duration' || key === 'gap') label.textContent = `${out[key].toFixed(2)} s`;
|
|
588
|
+
else if (key === 'peekLean' || key === 'rimRotate') label.textContent = `${out[key].toFixed(1)}°`;
|
|
589
|
+
else if (key === 'peekScaleDip' || key === 'bodyBreath') label.textContent = out[key].toFixed(3);
|
|
590
|
+
else if (key === 'barrierY' || key === 'hideTy' || key === 'peekTy' || key === 'peekTx' || key === 'qOffsetX' || key === 'qOffsetY' || key === 'barrierGap') label.textContent = out[key].toFixed(1);
|
|
591
|
+
else label.textContent = out[key].toFixed(2);
|
|
592
|
+
});
|
|
593
|
+
return out;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function frame(now) {
|
|
597
|
+
if (!paused) {
|
|
598
|
+
const v = values();
|
|
599
|
+
const cycle = v.duration + v.gap;
|
|
600
|
+
const elapsed = (now - start) / 1000;
|
|
601
|
+
const t = elapsed % cycle;
|
|
602
|
+
const activeT = Math.min(t, v.duration);
|
|
603
|
+
const p = activeT / v.duration;
|
|
604
|
+
const inGap = t > v.duration;
|
|
605
|
+
const phase = inGap ? { name: 'idle', local: 1 } : phaseAt(p);
|
|
606
|
+
updateWarmClouds(elapsed, p, phase, v);
|
|
607
|
+
const state = updateCloud(elapsed, p, phase, v);
|
|
608
|
+
updateQuestionMark(phase, state.tx, state.ty, v);
|
|
609
|
+
phaseTag.textContent = phase.name;
|
|
610
|
+
timelineFill.style.width = `${(p * 100).toFixed(2)}%`;
|
|
611
|
+
statusbar.textContent = `phase: ${phase.name} · loop ${(p * 100).toFixed(1)}% · cloud (${state.tx.toFixed(2)}, ${state.ty.toFixed(2)})`;
|
|
612
|
+
}
|
|
613
|
+
requestAnimationFrame(frame);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
document.getElementById('btn-theme').addEventListener('click', () => {
|
|
617
|
+
document.body.classList.toggle('light');
|
|
618
|
+
});
|
|
619
|
+
document.getElementById('btn-pause').addEventListener('click', () => {
|
|
620
|
+
paused = !paused;
|
|
621
|
+
if (paused) pauseAt = performance.now();
|
|
622
|
+
else start += performance.now() - pauseAt;
|
|
623
|
+
});
|
|
624
|
+
document.getElementById('btn-restart').addEventListener('click', () => {
|
|
625
|
+
start = performance.now();
|
|
626
|
+
});
|
|
627
|
+
document.getElementById('btn-reset').addEventListener('click', () => {
|
|
628
|
+
Object.entries(DEFAULTS).forEach(([key, value]) => {
|
|
629
|
+
controls[key].value = String(value);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
Object.entries(DEFAULTS).forEach(([key, value]) => {
|
|
633
|
+
controls[key].value = String(value);
|
|
634
|
+
});
|
|
635
|
+
values();
|
|
636
|
+
|
|
637
|
+
// debug seek hook
|
|
638
|
+
window.__seekTo = (p) => {
|
|
639
|
+
const v = values();
|
|
640
|
+
paused = true;
|
|
641
|
+
const elapsed = p * v.duration;
|
|
642
|
+
const phase = phaseAt(p);
|
|
643
|
+
updateWarmClouds(elapsed, p, phase, v);
|
|
644
|
+
const state = updateCloud(elapsed, p, phase, v);
|
|
645
|
+
updateQuestionMark(phase, state.tx, state.ty, v);
|
|
646
|
+
phaseTag.textContent = phase.name;
|
|
647
|
+
timelineFill.style.width = `${(p * 100).toFixed(2)}%`;
|
|
648
|
+
statusbar.textContent = `phase: ${phase.name} · loop ${(p * 100).toFixed(1)}% · cloud (${state.tx.toFixed(2)}, ${state.ty.toFixed(2)})`;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const urlP = new URLSearchParams(location.search).get('p');
|
|
652
|
+
if (urlP !== null) {
|
|
653
|
+
setTimeout(() => window.__seekTo(parseFloat(urlP)), 100);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
requestAnimationFrame(frame);
|
|
657
|
+
]]></script>
|
|
658
|
+
</svg>
|