@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,279 @@
|
|
|
1
|
+
<svg id="cloudling" xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 36 36" preserveAspectRatio="xMidYMid meet">
|
|
2
|
+
<style><![CDATA[
|
|
3
|
+
.v {
|
|
4
|
+
text-align: right;
|
|
5
|
+
font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
6
|
+
color: #7A9DFF;
|
|
7
|
+
}
|
|
8
|
+
.edge-rim-tight { stroke-opacity: .9; }
|
|
9
|
+
.edge-rim-soft { stroke-opacity: .75; }
|
|
10
|
+
.edge-contact { stroke-opacity: .9; }
|
|
11
|
+
@media (prefers-color-scheme: light) {
|
|
12
|
+
.v { color: #3941FF; }
|
|
13
|
+
.edge-rim-tight { stroke-opacity: .95; }
|
|
14
|
+
.edge-rim-soft { stroke-opacity: .85; }
|
|
15
|
+
.edge-contact { stroke-opacity: .95; }
|
|
16
|
+
.cloud-drop-shadow { flood-opacity: .13; }
|
|
17
|
+
.food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
|
|
18
|
+
.food-cloud > path {
|
|
19
|
+
stroke: #5870E8 !important;
|
|
20
|
+
stroke-width: 1.2 !important;
|
|
21
|
+
stroke-opacity: .9 !important;
|
|
22
|
+
vector-effect: non-scaling-stroke !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]]></style>
|
|
26
|
+
<defs>
|
|
27
|
+
<linearGradient id="body-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
28
|
+
<stop offset="0" stop-color="#93c5fd"/>
|
|
29
|
+
<stop offset="0.5" stop-color="#60a5fa"/>
|
|
30
|
+
<stop offset="1" stop-color="#3b82f6"/>
|
|
31
|
+
</linearGradient>
|
|
32
|
+
<linearGradient id="eye-grad" x1="12" y1="3" x2="12" y2="21" gradientUnits="userSpaceOnUse">
|
|
33
|
+
<stop offset="0" stop-color="#B1A7FF"/>
|
|
34
|
+
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
35
|
+
<stop offset="1" stop-color="#3941FF"/>
|
|
36
|
+
</linearGradient>
|
|
37
|
+
<radialGradient id="hi-grad" cx="0.45" cy="0.25" r="0.55">
|
|
38
|
+
<stop offset="0" stop-color="#93c5fd" stop-opacity="0.9"/>
|
|
39
|
+
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
40
|
+
</radialGradient>
|
|
41
|
+
<filter id="drop" x="-40%" y="-40%" width="180%" height="180%">
|
|
42
|
+
<feDropShadow class="cloud-drop-shadow" dx="0" dy="0.55" stdDeviation="0.72" flood-color="#3941FF" flood-opacity="0.17"/>
|
|
43
|
+
</filter>
|
|
44
|
+
<filter id="edge-blur" x="-40%" y="-40%" width="180%" height="180%">
|
|
45
|
+
<feGaussianBlur stdDeviation="0.16"/>
|
|
46
|
+
</filter>
|
|
47
|
+
<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"/>
|
|
48
|
+
<clipPath id="cloud-clip">
|
|
49
|
+
<use href="#cloud-shape"/>
|
|
50
|
+
</clipPath>
|
|
51
|
+
</defs>
|
|
52
|
+
|
|
53
|
+
<rect x="-12" y="-12" width="48" height="48" fill="transparent"/>
|
|
54
|
+
|
|
55
|
+
<g id="body-js">
|
|
56
|
+
<g id="breath-js">
|
|
57
|
+
<g id="edge-system">
|
|
58
|
+
<use class="edge-contact" href="#cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.00" stroke-linejoin="round" transform="translate(0 .18)" filter="url(#edge-blur)"/>
|
|
59
|
+
<use class="edge-rim-soft" href="#cloud-shape" fill="none" stroke="#7A9DFF" stroke-width=".84" stroke-linejoin="round" filter="url(#edge-blur)"/>
|
|
60
|
+
<use class="edge-rim-tight" href="#cloud-shape" fill="none" stroke="#aab3c6" stroke-width=".38" stroke-linejoin="round"/>
|
|
61
|
+
</g>
|
|
62
|
+
<g filter="url(#drop)">
|
|
63
|
+
<use href="#cloud-shape" fill="url(#body-grad)"/>
|
|
64
|
+
<rect x="1" y="2" width="22" height="11" fill="url(#hi-grad)" clip-path="url(#cloud-clip)"/>
|
|
65
|
+
</g>
|
|
66
|
+
</g>
|
|
67
|
+
</g>
|
|
68
|
+
<g id="eyes-js">
|
|
69
|
+
<g id="blink-js">
|
|
70
|
+
<rect id="eye-l" class="eye" x="6.65" y="9.65" width="2.2" height="4.65" rx="1.1" ry="1.1" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
71
|
+
<rect id="eye-r" class="eye" x="11.05" y="9.65" width="2.2" height="4.65" rx="1.1" ry="1.1" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
72
|
+
</g>
|
|
73
|
+
</g>
|
|
74
|
+
<g id="__cloudling-export-shims" opacity="0" pointer-events="none" style="display:none">
|
|
75
|
+
<g id="offset-readout"></g>
|
|
76
|
+
<g id="scale-readout"></g>
|
|
77
|
+
</g>
|
|
78
|
+
|
|
79
|
+
<script type="application/ecmascript"><![CDATA[
|
|
80
|
+
const svg = document.getElementById("cloudling");
|
|
81
|
+
const eyes = document.getElementById("eyes-js");
|
|
82
|
+
const body = document.getElementById("breath-js");
|
|
83
|
+
const eyeGrad = document.getElementById("eye-grad");
|
|
84
|
+
const eyeL = document.getElementById("eye-l");
|
|
85
|
+
const eyeR = document.getElementById("eye-r");
|
|
86
|
+
const readout = document.getElementById("offset-readout");
|
|
87
|
+
const scaleReadout = document.getElementById("scale-readout");
|
|
88
|
+
|
|
89
|
+
const face = { x: 9.95, y: 11.975 };
|
|
90
|
+
const EYE_BASE = {
|
|
91
|
+
l: { x: 6.65, y: 9.65, h: 4.65, rx: 1.1 },
|
|
92
|
+
r: { x: 11.05, y: 9.65, h: 4.65, rx: 1.1 },
|
|
93
|
+
};
|
|
94
|
+
const params = {
|
|
95
|
+
eyeMax: 2.05,
|
|
96
|
+
yClamp: 0.85,
|
|
97
|
+
maxRotDeg: 13,
|
|
98
|
+
cloudLag: 0.20,
|
|
99
|
+
scaleLow: 0.96,
|
|
100
|
+
breathPeriod: 5.0,
|
|
101
|
+
distNear: 6,
|
|
102
|
+
distFar: 20,
|
|
103
|
+
distMaxScale: 1.15,
|
|
104
|
+
distRecenter: 0.5,
|
|
105
|
+
flowPeriod: 5.5,
|
|
106
|
+
flowAmp: 4,
|
|
107
|
+
blinkInterval: 6.0,
|
|
108
|
+
blinkDur: 0.16,
|
|
109
|
+
eyeTrack: 0.06,
|
|
110
|
+
recenter: 0.15,
|
|
111
|
+
};
|
|
112
|
+
let pointerInside = false;
|
|
113
|
+
let pointer = { ...face };
|
|
114
|
+
let current = { x: 0, y: 0 };
|
|
115
|
+
let target = { x: 0, y: 0 };
|
|
116
|
+
let cloudRotate = 0;
|
|
117
|
+
let currentDistScale = 1.0;
|
|
118
|
+
let lastT = performance.now();
|
|
119
|
+
const startT = performance.now();
|
|
120
|
+
let lowPowerPaused = false;
|
|
121
|
+
let lowPowerPausedAt = 0;
|
|
122
|
+
let lowPowerPausedTotal = 0;
|
|
123
|
+
let rafId = 0;
|
|
124
|
+
let nextBlinkT = 3 + Math.random() * 2;
|
|
125
|
+
let blinkActive = false;
|
|
126
|
+
let blinkStartT = 0;
|
|
127
|
+
|
|
128
|
+
function scheduleLoop() {
|
|
129
|
+
if (!lowPowerPaused && !rafId) rafId = requestAnimationFrame(tick);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
window.__clawdSetLowPowerPaused = paused => {
|
|
133
|
+
const next = !!paused;
|
|
134
|
+
if (lowPowerPaused === next) return;
|
|
135
|
+
lowPowerPaused = next;
|
|
136
|
+
if (next) {
|
|
137
|
+
lowPowerPausedAt = performance.now();
|
|
138
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
139
|
+
rafId = 0;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (lowPowerPausedAt) lowPowerPausedTotal += performance.now() - lowPowerPausedAt;
|
|
143
|
+
lowPowerPausedAt = 0;
|
|
144
|
+
lastT = performance.now();
|
|
145
|
+
scheduleLoop();
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
function clamp(v, min, max) {
|
|
149
|
+
return Math.max(min, Math.min(max, v));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function svgPointFromEvent(e) {
|
|
153
|
+
const r = svg.getBoundingClientRect();
|
|
154
|
+
return {
|
|
155
|
+
x: (e.clientX - r.left) * (48 / r.width) - 12,
|
|
156
|
+
y: (e.clientY - r.top) * (48 / r.height) - 12,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function updateTarget() {
|
|
161
|
+
if (!pointerInside) {
|
|
162
|
+
target.x = 0;
|
|
163
|
+
target.y = 0;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const dx = pointer.x - face.x;
|
|
167
|
+
const dy = pointer.y - face.y;
|
|
168
|
+
const dist = Math.hypot(dx, dy);
|
|
169
|
+
if (dist < 0.01) {
|
|
170
|
+
target.x = 0;
|
|
171
|
+
target.y = 0;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const sat = Math.tanh(dist / 6);
|
|
175
|
+
target.x = (dx / dist) * sat * params.eyeMax;
|
|
176
|
+
target.y = clamp((dy / dist) * sat * params.eyeMax, -params.eyeMax * params.yClamp, params.eyeMax * params.yClamp);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function tick(now) {
|
|
180
|
+
rafId = 0;
|
|
181
|
+
if (lowPowerPaused) return;
|
|
182
|
+
const dt = Math.min(0.05, (now - lastT) / 1000);
|
|
183
|
+
lastT = now;
|
|
184
|
+
const t = (now - startT - lowPowerPausedTotal) / 1000;
|
|
185
|
+
updateTarget();
|
|
186
|
+
const eyeTime = pointerInside ? params.eyeTrack : params.recenter;
|
|
187
|
+
const eyeRate = 1 - Math.exp(-dt / eyeTime);
|
|
188
|
+
current.x += (target.x - current.x) * eyeRate;
|
|
189
|
+
current.y += (target.y - current.y) * eyeRate;
|
|
190
|
+
|
|
191
|
+
const maxRot = params.maxRotDeg * Math.PI / 180;
|
|
192
|
+
const targetRot = (current.x / Math.max(params.eyeMax, 0.01)) * maxRot;
|
|
193
|
+
cloudRotate += (targetRot - cloudRotate) * params.cloudLag;
|
|
194
|
+
|
|
195
|
+
const phase = (Math.cos(2 * Math.PI * t / params.breathPeriod) + 1) / 2;
|
|
196
|
+
const breathScale = params.scaleLow + (1 - params.scaleLow) * phase;
|
|
197
|
+
let targetDistScale = 1.0;
|
|
198
|
+
if (pointerInside) {
|
|
199
|
+
const dx = pointer.x - face.x;
|
|
200
|
+
const dy = pointer.y - face.y;
|
|
201
|
+
const dist = Math.hypot(dx, dy);
|
|
202
|
+
if (dist > params.distNear) {
|
|
203
|
+
const t01 = Math.min((dist - params.distNear) / Math.max(params.distFar - params.distNear, 0.01), 1);
|
|
204
|
+
targetDistScale = 1.0 + (params.distMaxScale - 1.0) * t01;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const distRate = 1 - Math.exp(-dt / params.distRecenter);
|
|
208
|
+
currentDistScale += (targetDistScale - currentDistScale) * distRate;
|
|
209
|
+
const finalScale = breathScale * currentDistScale;
|
|
210
|
+
const angleDeg = cloudRotate * 180 / Math.PI;
|
|
211
|
+
body.setAttribute(
|
|
212
|
+
"transform",
|
|
213
|
+
`translate(12 12) rotate(${angleDeg.toFixed(3)}) scale(${finalScale.toFixed(4)}) translate(-12 -12)`
|
|
214
|
+
);
|
|
215
|
+
const eyeScale = currentDistScale;
|
|
216
|
+
eyes.setAttribute(
|
|
217
|
+
"transform",
|
|
218
|
+
`translate(${current.x.toFixed(3)} ${current.y.toFixed(3)}) translate(${face.x} ${face.y}) scale(${eyeScale.toFixed(4)}) translate(${-face.x} ${-face.y})`
|
|
219
|
+
);
|
|
220
|
+
applyBlink(t);
|
|
221
|
+
const flowOffset = Math.sin(2 * Math.PI * t / params.flowPeriod) * params.flowAmp;
|
|
222
|
+
eyeGrad.setAttribute("y1", (3 + flowOffset).toFixed(2));
|
|
223
|
+
eyeGrad.setAttribute("y2", (21 + flowOffset).toFixed(2));
|
|
224
|
+
readout.textContent = `offset: ${current.x.toFixed(2)}, ${current.y.toFixed(2)}`;
|
|
225
|
+
scaleReadout.textContent = `scale: ${finalScale.toFixed(3)}, rot: ${angleDeg.toFixed(1)}deg`;
|
|
226
|
+
scheduleLoop();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getBlinkScale(t) {
|
|
230
|
+
if (!blinkActive && t > nextBlinkT) {
|
|
231
|
+
blinkActive = true;
|
|
232
|
+
blinkStartT = t;
|
|
233
|
+
}
|
|
234
|
+
if (blinkActive) {
|
|
235
|
+
const d = t - blinkStartT;
|
|
236
|
+
const half = params.blinkDur / 2;
|
|
237
|
+
if (d < half) return 1 - 0.94 * (d / half);
|
|
238
|
+
if (d < params.blinkDur) return 0.06 + 0.94 * ((d - half) / half);
|
|
239
|
+
blinkActive = false;
|
|
240
|
+
nextBlinkT = t + params.blinkInterval * (0.8 + Math.random() * 0.4);
|
|
241
|
+
}
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function setBlinkRect(rect, base, scale) {
|
|
246
|
+
const h = Math.max(base.h * scale, 0.15);
|
|
247
|
+
const y = base.y + (base.h - h) / 2;
|
|
248
|
+
const rx = Math.min(base.rx, h / 2);
|
|
249
|
+
rect.setAttribute("height", h.toFixed(3));
|
|
250
|
+
rect.setAttribute("y", y.toFixed(3));
|
|
251
|
+
rect.setAttribute("rx", rx.toFixed(3));
|
|
252
|
+
rect.setAttribute("ry", rx.toFixed(3));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function applyBlink(t) {
|
|
256
|
+
const scale = getBlinkScale(t);
|
|
257
|
+
setBlinkRect(eyeL, EYE_BASE.l, scale);
|
|
258
|
+
setBlinkRect(eyeR, EYE_BASE.r, scale);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
window.addEventListener("pointermove", e => {
|
|
262
|
+
const r = svg.getBoundingClientRect();
|
|
263
|
+
const inside = e.clientX >= r.left && e.clientX <= r.right
|
|
264
|
+
&& e.clientY >= r.top && e.clientY <= r.bottom;
|
|
265
|
+
pointerInside = inside;
|
|
266
|
+
if (inside) pointer = svgPointFromEvent(e);
|
|
267
|
+
});
|
|
268
|
+
svg.addEventListener("pointerleave", () => {
|
|
269
|
+
pointerInside = false;
|
|
270
|
+
});
|
|
271
|
+
window.__cloudlingSetPointer = payload => {
|
|
272
|
+
if (!payload || !Number.isFinite(payload.x) || !Number.isFinite(payload.y)) return;
|
|
273
|
+
pointer.x = payload.x;
|
|
274
|
+
pointer.y = payload.y;
|
|
275
|
+
pointerInside = !!payload.inside;
|
|
276
|
+
};
|
|
277
|
+
scheduleLoop();
|
|
278
|
+
]]></script>
|
|
279
|
+
</svg>
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<svg id="cloudling" xmlns="http://www.w3.org/2000/svg" viewBox="-6 -6 36 36" preserveAspectRatio="xMidYMid meet">
|
|
2
|
+
<style><![CDATA[
|
|
3
|
+
.v {
|
|
4
|
+
text-align: right;
|
|
5
|
+
font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
6
|
+
color: #7A9DFF;
|
|
7
|
+
}
|
|
8
|
+
.edge-rim-tight { stroke-opacity: .9; }
|
|
9
|
+
.edge-rim-soft { stroke-opacity: .75; }
|
|
10
|
+
.edge-contact { stroke-opacity: .9; }
|
|
11
|
+
@media (prefers-color-scheme: light) {
|
|
12
|
+
.v { color: #3941FF; }
|
|
13
|
+
.edge-rim-tight { stroke-opacity: .95; }
|
|
14
|
+
.edge-rim-soft { stroke-opacity: .85; }
|
|
15
|
+
.edge-contact { stroke-opacity: .95; }
|
|
16
|
+
.cloud-drop-shadow { flood-opacity: .13; }
|
|
17
|
+
.food-cloud-shadow { flood-opacity: .28; flood-color: #5060FF; }
|
|
18
|
+
.food-cloud > path {
|
|
19
|
+
stroke: #5870E8 !important;
|
|
20
|
+
stroke-width: 1.2 !important;
|
|
21
|
+
stroke-opacity: .9 !important;
|
|
22
|
+
vector-effect: non-scaling-stroke !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]]></style>
|
|
26
|
+
<defs>
|
|
27
|
+
<linearGradient id="body-grad" x1="12" y1="2" x2="12" y2="22" gradientUnits="userSpaceOnUse">
|
|
28
|
+
<stop offset="0" stop-color="#93c5fd"/>
|
|
29
|
+
<stop offset="0.5" stop-color="#60a5fa"/>
|
|
30
|
+
<stop offset="1" stop-color="#3b82f6"/>
|
|
31
|
+
</linearGradient>
|
|
32
|
+
<linearGradient id="eye-grad" x1="12" y1="3" x2="12" y2="21" gradientUnits="userSpaceOnUse">
|
|
33
|
+
<stop offset="0" stop-color="#B1A7FF"/>
|
|
34
|
+
<stop offset="0.5" stop-color="#7A9DFF"/>
|
|
35
|
+
<stop offset="1" stop-color="#3941FF"/>
|
|
36
|
+
</linearGradient>
|
|
37
|
+
<radialGradient id="hi-grad" cx="0.45" cy="0.25" r="0.55">
|
|
38
|
+
<stop offset="0" stop-color="#93c5fd" stop-opacity="0.9"/>
|
|
39
|
+
<stop offset="1" stop-color="#93c5fd" stop-opacity="0"/>
|
|
40
|
+
</radialGradient>
|
|
41
|
+
<filter id="drop" x="-40%" y="-40%" width="180%" height="180%">
|
|
42
|
+
<feDropShadow class="cloud-drop-shadow" dx="0" dy="0.55" stdDeviation="0.72" flood-color="#3941FF" flood-opacity="0.17"/>
|
|
43
|
+
</filter>
|
|
44
|
+
<filter id="edge-blur" x="-40%" y="-40%" width="180%" height="180%">
|
|
45
|
+
<feGaussianBlur stdDeviation="0.16"/>
|
|
46
|
+
</filter>
|
|
47
|
+
<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"/>
|
|
48
|
+
<clipPath id="cloud-clip">
|
|
49
|
+
<use href="#cloud-shape"/>
|
|
50
|
+
</clipPath>
|
|
51
|
+
</defs>
|
|
52
|
+
|
|
53
|
+
<rect x="-12" y="-12" width="48" height="48" fill="transparent"/>
|
|
54
|
+
|
|
55
|
+
<g id="pose-js">
|
|
56
|
+
<g id="body-js">
|
|
57
|
+
<g id="breath-js">
|
|
58
|
+
<g id="edge-system">
|
|
59
|
+
<use id="edge-contact" class="edge-contact" href="#cloud-shape" fill="none" stroke="#7d8aa7" stroke-width="1.00" stroke-linejoin="round" transform="translate(0 .18)" filter="url(#edge-blur)"/>
|
|
60
|
+
<use id="edge-soft" class="edge-rim-soft" href="#cloud-shape" fill="none" stroke="#7A9DFF" stroke-width=".84" stroke-linejoin="round" filter="url(#edge-blur)"/>
|
|
61
|
+
<use id="edge-tight" class="edge-rim-tight" href="#cloud-shape" fill="none" stroke="#aab3c6" stroke-width=".38" stroke-linejoin="round"/>
|
|
62
|
+
</g>
|
|
63
|
+
<g filter="url(#drop)">
|
|
64
|
+
<use href="#cloud-shape" fill="url(#body-grad)"/>
|
|
65
|
+
<rect x="1" y="2" width="22" height="11" fill="url(#hi-grad)" clip-path="url(#cloud-clip)"/>
|
|
66
|
+
</g>
|
|
67
|
+
</g>
|
|
68
|
+
</g>
|
|
69
|
+
<g id="eyes-js">
|
|
70
|
+
<g id="blink-js">
|
|
71
|
+
<rect id="eye-l" class="eye" x="6.65" y="9.65" width="2.2" height="4.65" rx="1.1" ry="1.1" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
72
|
+
<rect id="eye-r" class="eye" x="11.05" y="9.65" width="2.2" height="4.65" rx="1.1" ry="1.1" fill="url(#eye-grad)" stroke="#ffffff" stroke-width="0.08" stroke-opacity="0.7"/>
|
|
73
|
+
</g>
|
|
74
|
+
</g>
|
|
75
|
+
</g>
|
|
76
|
+
<g id="__cloudling-export-shims" opacity="0" pointer-events="none" style="display:none">
|
|
77
|
+
<g id="eye-readout"></g>
|
|
78
|
+
<g id="pose-readout"></g>
|
|
79
|
+
<g id="shake-readout"></g>
|
|
80
|
+
</g>
|
|
81
|
+
|
|
82
|
+
<script type="application/ecmascript"><![CDATA[
|
|
83
|
+
const svg = document.getElementById("cloudling");
|
|
84
|
+
const pose = document.getElementById("pose-js");
|
|
85
|
+
const eyes = document.getElementById("eyes-js");
|
|
86
|
+
const body = document.getElementById("breath-js");
|
|
87
|
+
const eyeGrad = document.getElementById("eye-grad");
|
|
88
|
+
const eyeL = document.getElementById("eye-l");
|
|
89
|
+
const eyeR = document.getElementById("eye-r");
|
|
90
|
+
const edgeContact = document.getElementById("edge-contact");
|
|
91
|
+
const edgeSoft = document.getElementById("edge-soft");
|
|
92
|
+
const edgeTight = document.getElementById("edge-tight");
|
|
93
|
+
const poseReadout = document.getElementById("pose-readout");
|
|
94
|
+
const shakeReadout = document.getElementById("shake-readout");
|
|
95
|
+
const eyeReadout = document.getElementById("eye-readout");
|
|
96
|
+
|
|
97
|
+
const MINI_EYE_BASE = {
|
|
98
|
+
l: { cx: 7.75, cy: 11.975, w: 2.20, h: 4.65, rx: 1.10 },
|
|
99
|
+
r: { cx: 12.15, cy: 11.975, w: 2.20, h: 4.65, rx: 1.10 },
|
|
100
|
+
};
|
|
101
|
+
const IDLE_EYE_BASE = {
|
|
102
|
+
l: { cx: 9.00, cy: 12.00, w: 2.60, h: 5.00, rx: 1.30 },
|
|
103
|
+
r: { cx: 15.00, cy: 12.00, w: 2.60, h: 5.00, rx: 1.30 },
|
|
104
|
+
};
|
|
105
|
+
const face = { x: 12.00, y: 12.00 };
|
|
106
|
+
const params = {
|
|
107
|
+
settleTx: -0.22,
|
|
108
|
+
settleTy: -0.04,
|
|
109
|
+
settleScale: 1.012,
|
|
110
|
+
settleRot: -0.65,
|
|
111
|
+
eyeReturnDur: 0.24,
|
|
112
|
+
idleEyeMax: 0.50,
|
|
113
|
+
idleYClamp: 1.00,
|
|
114
|
+
idleRecenter: 0.45,
|
|
115
|
+
duangDelay: 0.10,
|
|
116
|
+
duangDur: 1.12,
|
|
117
|
+
duangPower: 1.16,
|
|
118
|
+
duangCycles: 4.80,
|
|
119
|
+
duangTravel: 0.33,
|
|
120
|
+
duangLift: 0.10,
|
|
121
|
+
duangRot: 4.20,
|
|
122
|
+
duangSquash: 0.048,
|
|
123
|
+
breath: 0.008,
|
|
124
|
+
rimPower: 1.30,
|
|
125
|
+
flowPeriod: 4.8,
|
|
126
|
+
flowAmp: 4.8,
|
|
127
|
+
blinkInterval: 5.2,
|
|
128
|
+
blinkDur: 0.14,
|
|
129
|
+
};
|
|
130
|
+
let startT = performance.now();
|
|
131
|
+
let lastFrameT = 0;
|
|
132
|
+
let nextBlinkT = 2.4 + Math.random() * 1.4;
|
|
133
|
+
let blinkActive = false;
|
|
134
|
+
let blinkStartT = 0;
|
|
135
|
+
let lastState = null;
|
|
136
|
+
let pointerInside = false;
|
|
137
|
+
let pointer = { ...face };
|
|
138
|
+
let idleEye = { x: 0, y: 0 };
|
|
139
|
+
|
|
140
|
+
function clamp(v, min = 0, max = 1) {
|
|
141
|
+
return Math.max(min, Math.min(max, v));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function lerp(a, b, t) {
|
|
145
|
+
return a + (b - a) * t;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function smoothstep(edge0, edge1, x) {
|
|
149
|
+
const t = clamp((x - edge0) / Math.max(edge1 - edge0, 0.0001));
|
|
150
|
+
return t * t * (3 - 2 * t);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function easeOutCubic(t) {
|
|
154
|
+
return 1 - Math.pow(1 - clamp(t), 3);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function duangWave(t) {
|
|
158
|
+
const u = clamp((t - params.duangDelay) / Math.max(params.duangDur, 0.01));
|
|
159
|
+
if (u <= 0 || u >= 1) return 0;
|
|
160
|
+
const envelope = Math.pow(1 - u, 1.35);
|
|
161
|
+
return Math.sin(u * Math.PI * params.duangCycles) * envelope * params.duangPower;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function pointerToSvg(e) {
|
|
165
|
+
const r = svg.getBoundingClientRect();
|
|
166
|
+
const x = -12 + ((e.clientX - r.left) / Math.max(r.width, 1)) * 48;
|
|
167
|
+
const y = -12 + ((e.clientY - r.top) / Math.max(r.height, 1)) * 48;
|
|
168
|
+
pointer = { x, y };
|
|
169
|
+
pointerInside = true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function idleEyeTarget() {
|
|
173
|
+
const dx = pointer.x - face.x;
|
|
174
|
+
const dy = pointer.y - face.y;
|
|
175
|
+
const dist = Math.hypot(dx, dy);
|
|
176
|
+
if (dist < 0.001) return { x: 0, y: 0 };
|
|
177
|
+
const sat = Math.tanh(dist / 6);
|
|
178
|
+
return {
|
|
179
|
+
x: (dx / dist) * sat * params.idleEyeMax,
|
|
180
|
+
y: clamp((dy / dist) * sat * params.idleEyeMax, -params.idleEyeMax * params.idleYClamp, params.idleEyeMax * params.idleYClamp),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function updateIdleEye(dt) {
|
|
185
|
+
const target = pointerInside ? idleEyeTarget() : { x: 0, y: 0 };
|
|
186
|
+
if (pointerInside) {
|
|
187
|
+
idleEye = target;
|
|
188
|
+
} else {
|
|
189
|
+
const recenterRate = 1 - Math.exp(-dt / Math.max(params.idleRecenter, 0.001));
|
|
190
|
+
idleEye.x += (target.x - idleEye.x) * recenterRate;
|
|
191
|
+
idleEye.y += (target.y - idleEye.y) * recenterRate;
|
|
192
|
+
}
|
|
193
|
+
return idleEye;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function stateAt(t, dt) {
|
|
197
|
+
const idle = updateIdleEye(dt);
|
|
198
|
+
const eyeReturn = smoothstep(0, params.eyeReturnDur, t);
|
|
199
|
+
const bodyWake = easeOutCubic(clamp(t / 0.30));
|
|
200
|
+
const spring = duangWave(t);
|
|
201
|
+
const hit = Math.abs(spring);
|
|
202
|
+
const hold = Math.sin(2 * Math.PI * t / 3.9);
|
|
203
|
+
const live = smoothstep(0.65, 1.25, t);
|
|
204
|
+
const bodyStart = 1 - bodyWake;
|
|
205
|
+
const poseTx = params.settleTx - bodyStart * 0.28 + spring * params.duangTravel + live * Math.sin(2 * Math.PI * t / 3.1 + 0.8) * 0.018;
|
|
206
|
+
const poseTy = params.settleTy - hit * params.duangLift + hold * 0.022 * live;
|
|
207
|
+
const poseRot = params.settleRot - bodyStart * 1.15 + spring * params.duangRot + hold * 0.12 * live;
|
|
208
|
+
const sx = params.settleScale + hit * params.duangSquash;
|
|
209
|
+
const sy = params.settleScale - hit * params.duangSquash * 0.72;
|
|
210
|
+
const breathScale = 1 + Math.sin(2 * Math.PI * t / 4.8) * params.breath * live;
|
|
211
|
+
const eyeTx = idle.x * eyeReturn;
|
|
212
|
+
const eyeTy = idle.y * eyeReturn;
|
|
213
|
+
const flowOffset = Math.sin(2 * Math.PI * t / params.flowPeriod) * params.flowAmp;
|
|
214
|
+
return { t, poseTx, poseTy, poseRot, sx, sy, breathScale, eyeTx, eyeTy, eyeMorph: eyeReturn, idleEyeX: idle.x, idleEyeY: idle.y, flowOffset, spring };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function tick(now) {
|
|
218
|
+
const t = (now - startT) / 1000;
|
|
219
|
+
const dt = Math.min(0.05, Math.max(0.001, t - lastFrameT));
|
|
220
|
+
lastFrameT = t;
|
|
221
|
+
renderFrame(t, dt);
|
|
222
|
+
requestAnimationFrame(tick);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renderFrame(t, dt = 1 / 60) {
|
|
226
|
+
const s = stateAt(Math.max(0, t), dt);
|
|
227
|
+
pose.setAttribute(
|
|
228
|
+
"transform",
|
|
229
|
+
`translate(${s.poseTx.toFixed(3)} ${s.poseTy.toFixed(3)}) translate(12 12) rotate(${s.poseRot.toFixed(3)}) scale(${s.sx.toFixed(4)} ${s.sy.toFixed(4)}) translate(-12 -12)`
|
|
230
|
+
);
|
|
231
|
+
body.setAttribute(
|
|
232
|
+
"transform",
|
|
233
|
+
`translate(12 12) scale(${s.breathScale.toFixed(4)}) translate(-12 -12)`
|
|
234
|
+
);
|
|
235
|
+
eyes.setAttribute("transform", `translate(${s.eyeTx.toFixed(3)} ${s.eyeTy.toFixed(3)})`);
|
|
236
|
+
applyEyes(s.t, s.eyeMorph);
|
|
237
|
+
applyEdgePower();
|
|
238
|
+
eyeGrad.setAttribute("y1", (3 + s.flowOffset).toFixed(2));
|
|
239
|
+
eyeGrad.setAttribute("y2", (21 + s.flowOffset).toFixed(2));
|
|
240
|
+
updateReadouts(s);
|
|
241
|
+
lastState = s;
|
|
242
|
+
return s;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getBlinkScale(t) {
|
|
246
|
+
if (!blinkActive && t > nextBlinkT) {
|
|
247
|
+
blinkActive = true;
|
|
248
|
+
blinkStartT = t;
|
|
249
|
+
}
|
|
250
|
+
if (blinkActive) {
|
|
251
|
+
const d = t - blinkStartT;
|
|
252
|
+
const half = params.blinkDur / 2;
|
|
253
|
+
if (d < half) return 1 - 0.94 * (d / half);
|
|
254
|
+
if (d < params.blinkDur) return 0.06 + 0.94 * ((d - half) / half);
|
|
255
|
+
blinkActive = false;
|
|
256
|
+
nextBlinkT = t + params.blinkInterval * (0.8 + Math.random() * 0.4);
|
|
257
|
+
}
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setEyeRect(rect, miniBase, idleBase, blinkScale, morph) {
|
|
262
|
+
const cx = lerp(miniBase.cx, idleBase.cx, morph);
|
|
263
|
+
const cy = lerp(miniBase.cy, idleBase.cy, morph);
|
|
264
|
+
const w = lerp(miniBase.w, idleBase.w, morph);
|
|
265
|
+
const baseH = lerp(miniBase.h, idleBase.h, morph);
|
|
266
|
+
const h = Math.max(baseH * blinkScale, 0.15);
|
|
267
|
+
const x = cx - w / 2;
|
|
268
|
+
const y = cy - h / 2;
|
|
269
|
+
const rx = Math.min(lerp(miniBase.rx, idleBase.rx, morph), w / 2, h / 2);
|
|
270
|
+
rect.setAttribute("x", x.toFixed(3));
|
|
271
|
+
rect.setAttribute("width", w.toFixed(3));
|
|
272
|
+
rect.setAttribute("height", h.toFixed(3));
|
|
273
|
+
rect.setAttribute("y", y.toFixed(3));
|
|
274
|
+
rect.setAttribute("rx", rx.toFixed(3));
|
|
275
|
+
rect.setAttribute("ry", rx.toFixed(3));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function applyEyes(t, morph) {
|
|
279
|
+
const blinkScale = getBlinkScale(t);
|
|
280
|
+
setEyeRect(eyeL, MINI_EYE_BASE.l, IDLE_EYE_BASE.l, blinkScale, morph);
|
|
281
|
+
setEyeRect(eyeR, MINI_EYE_BASE.r, IDLE_EYE_BASE.r, blinkScale, morph);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function applyEdgePower() {
|
|
285
|
+
edgeContact.style.strokeOpacity = (0.08 * params.rimPower).toFixed(3);
|
|
286
|
+
edgeSoft.style.strokeOpacity = (0.06 * params.rimPower).toFixed(3);
|
|
287
|
+
edgeTight.style.strokeOpacity = (0.08 * params.rimPower).toFixed(3);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function updateReadouts(s) {
|
|
291
|
+
poseReadout.textContent = `pose: tx ${params.settleTx.toFixed(2)}, scale ${params.settleScale.toFixed(3)}, rot ${params.settleRot.toFixed(2)}deg`;
|
|
292
|
+
shakeReadout.textContent = `duang: power ${params.duangPower.toFixed(2)} / ${params.duangDur.toFixed(2)}s / cycles ${params.duangCycles.toFixed(2)} / now ${s.spring.toFixed(2)}`;
|
|
293
|
+
eyeReadout.textContent = `eyes: mini 2.2x4.65 gap 4.4 -> idle 2.6x5 gap 6 / morph ${s.eyeMorph.toFixed(2)} / offset ${s.idleEyeX.toFixed(2)}, ${s.idleEyeY.toFixed(2)}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
renderFrame(0);
|
|
297
|
+
|
|
298
|
+
window.__miniPeekDebug = {
|
|
299
|
+
renderAt(seconds) {
|
|
300
|
+
return renderFrame(Number(seconds) || 0);
|
|
301
|
+
},
|
|
302
|
+
getState() {
|
|
303
|
+
return lastState;
|
|
304
|
+
},
|
|
305
|
+
getParams() {
|
|
306
|
+
return { ...params };
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
function restart(now = performance.now()) {
|
|
311
|
+
startT = now;
|
|
312
|
+
lastFrameT = 0;
|
|
313
|
+
nextBlinkT = 2.4 + Math.random() * 1.4;
|
|
314
|
+
blinkActive = false;
|
|
315
|
+
renderFrame(0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
svg.addEventListener("pointerenter", e => {
|
|
319
|
+
pointerToSvg(e);
|
|
320
|
+
restart();
|
|
321
|
+
});
|
|
322
|
+
svg.addEventListener("pointermove", pointerToSvg);
|
|
323
|
+
svg.addEventListener("pointerleave", () => {
|
|
324
|
+
pointerInside = false;
|
|
325
|
+
});
|
|
326
|
+
requestAnimationFrame(tick);
|
|
327
|
+
]]></script>
|
|
328
|
+
</svg>
|