clip-pal 0.1.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.
@@ -0,0 +1,929 @@
1
+ /* clip-pal — everything the pal needs to move.
2
+ Import once: `import "clip-pal/styles.css"`.
3
+
4
+ Theming: set the --pal-* custom properties on any ancestor (or :root), or
5
+ pass the matching props — they set the same variables inline. Defaults are
6
+ the ClipSpace desk set: racing green, brass wire, cream face.
7
+
8
+ The morphing poses have no explicit start shape: they tween from whatever
9
+ `d` the body and leg paths currently carry, which is what lets shape
10
+ presets and custom shapes use every pose — as long as the paths keep the
11
+ M L C C L C C L (body) / M C C L (leg) command sequence. */
12
+ .pal,
13
+ .clip-pal-scroll,
14
+ .clip-pal-companion {
15
+ --_pal-wire: var(--pal-wire, #d9a441);
16
+ --_pal-face: var(--pal-face, #f2ede0);
17
+ --_pal-bubble-bg: var(--pal-bubble-bg, #1b2a23);
18
+ --_pal-bubble-border: var(--pal-bubble-border, rgba(242, 237, 224, 0.1));
19
+ --_pal-bubble-text: var(--pal-bubble-text, #f2ede0);
20
+ --_pal-bubble-dot: var(--pal-bubble-dot, #9daa9f);
21
+ --_pal-bubble-radius: var(--pal-bubble-radius, 1rem);
22
+ --_pal-bubble-shadow: var(--pal-bubble-shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -4px rgba(0, 0, 0, 0.3));
23
+ --_pal-bubble-padding: var(--pal-bubble-padding, 0.625rem 1rem);
24
+ --_pal-bubble-font-size: var(--pal-bubble-font-size, 0.875rem);
25
+ --_pal-font: var(--pal-font, inherit);
26
+ /* motion */
27
+ --_pal-tempo: var(--pal-tempo, 1); /* multiplies every emote's duration */
28
+ --_pal-step-ms: var(--pal-step, 0.36s); /* one stride while walking */
29
+ --_pal-sway-ms: var(--pal-sway, 5s); /* the idle sway period */
30
+ --_pal-sway-deg: var(--pal-sway-deg, 2.5deg);
31
+ --_pal-blink-ms: var(--pal-blink, 5s);
32
+ --_pal-float-ms: var(--pal-float, 6s); /* the companion's float */
33
+ --_pal-float-px: var(--pal-float-px, 8px);
34
+ }
35
+
36
+ /* ---------- the walking guide ---------- */
37
+ .clip-pal-scroll {
38
+ position: fixed;
39
+ left: 0;
40
+ top: 50%;
41
+ z-index: 40;
42
+ pointer-events: none;
43
+ display: none;
44
+ }
45
+ .clip-pal-scroll[data-on] {
46
+ display: block;
47
+ }
48
+ .clip-pal-anchor {
49
+ position: relative;
50
+ }
51
+ .clip-pal-grab {
52
+ pointer-events: auto;
53
+ touch-action: none;
54
+ user-select: none;
55
+ -webkit-user-select: none;
56
+ cursor: grab;
57
+ }
58
+ .clip-pal-grab.is-grabbed {
59
+ cursor: grabbing;
60
+ }
61
+
62
+ /* ---------- the standing companion ---------- */
63
+ .clip-pal-companion {
64
+ position: relative;
65
+ display: inline-block;
66
+ }
67
+ .clip-pal-companion-bubble {
68
+ pointer-events: none;
69
+ position: absolute;
70
+ bottom: 100%;
71
+ left: 50%;
72
+ margin-bottom: 0.75rem;
73
+ transform: translateX(-50%);
74
+ }
75
+ @keyframes pal-float {
76
+ 0%, 100% { transform: translate3d(0, 0, 0); }
77
+ 50% { transform: translate3d(0, calc(-1 * var(--_pal-float-px)), 0); }
78
+ }
79
+ .pal-float {
80
+ animation: pal-float var(--_pal-float-ms) ease-in-out infinite;
81
+ will-change: transform;
82
+ backface-visibility: hidden;
83
+ }
84
+
85
+ /* ---------- the speech bubble ---------- */
86
+ .clip-pal-bubble-wrap {
87
+ position: absolute;
88
+ bottom: 100%;
89
+ margin-bottom: 0.75rem;
90
+ }
91
+ .clip-pal-bubble {
92
+ position: relative;
93
+ font-family: var(--_pal-font);
94
+ }
95
+ .clip-pal-bubble.is-left { transform-origin: bottom right; }
96
+ .clip-pal-bubble.is-right { transform-origin: bottom left; }
97
+ .clip-pal-bubble.is-center { transform-origin: bottom center; }
98
+ /* the box animates its size, so it morphs smoothly from the little typing
99
+ bubble into the full line */
100
+ .clip-pal-bubble-box {
101
+ overflow: hidden;
102
+ border-radius: var(--_pal-bubble-radius);
103
+ border: 1px solid var(--_pal-bubble-border);
104
+ background: var(--_pal-bubble-bg);
105
+ box-shadow: var(--_pal-bubble-shadow);
106
+ transition: width 0.3s ease-out, height 0.3s ease-out;
107
+ }
108
+ .clip-pal-bubble.is-left .clip-pal-bubble-box {
109
+ margin-left: auto;
110
+ border-bottom-right-radius: 0.375rem;
111
+ }
112
+ .clip-pal-bubble.is-right .clip-pal-bubble-box {
113
+ border-bottom-left-radius: 0.375rem;
114
+ }
115
+ /* content at its natural size — the box above is measured against it and
116
+ clipped while growing */
117
+ .clip-pal-bubble-content {
118
+ width: max-content;
119
+ padding: var(--_pal-bubble-padding);
120
+ font-size: var(--_pal-bubble-font-size);
121
+ line-height: 1.625;
122
+ color: var(--_pal-bubble-text);
123
+ }
124
+ .clip-pal-dots {
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ gap: 0.25rem;
129
+ padding: 0.375rem 0;
130
+ }
131
+ .clip-pal-dot {
132
+ display: inline-block;
133
+ width: 0.375rem;
134
+ height: 0.375rem;
135
+ border-radius: 9999px;
136
+ background: var(--_pal-bubble-dot);
137
+ }
138
+ /* the tail: a rotated square poking out of the bottom edge, aimed at the
139
+ pal's head so the bubble clearly comes from him */
140
+ .clip-pal-tail {
141
+ position: absolute;
142
+ bottom: -7px;
143
+ width: 0.875rem;
144
+ height: 0.875rem;
145
+ transform: rotate(45deg);
146
+ border-right: 1px solid var(--_pal-bubble-border);
147
+ border-bottom: 1px solid var(--_pal-bubble-border);
148
+ background: var(--_pal-bubble-bg);
149
+ }
150
+ .clip-pal-bubble.is-center .clip-pal-tail {
151
+ left: 50%;
152
+ transform: translateX(-50%) rotate(45deg);
153
+ }
154
+
155
+ @keyframes pal-blink-dot {
156
+ 0%, 100% { opacity: 0.35; }
157
+ 50% { opacity: 1; }
158
+ }
159
+ .clip-pal-dot:nth-child(1) { animation: pal-blink-dot 1.4s ease-in-out infinite; }
160
+ .clip-pal-dot:nth-child(2) { animation: pal-blink-dot 1.4s ease-in-out 0.25s infinite; }
161
+ .clip-pal-dot:nth-child(3) { animation: pal-blink-dot 1.4s ease-in-out 0.5s infinite; }
162
+
163
+ /* the clip pal, alive: a gentle sway and an occasional blink */
164
+ @keyframes pal-wiggle {
165
+ 0%, 100% { transform: rotate(calc(-1 * var(--_pal-sway-deg))); }
166
+ 50% { transform: rotate(var(--_pal-sway-deg)); }
167
+ }
168
+ .pal-wiggle {
169
+ animation: pal-wiggle var(--_pal-sway-ms) ease-in-out infinite;
170
+ transform-origin: 50% 88%;
171
+ }
172
+ @keyframes pal-blink {
173
+ 0%, 88%, 100% { transform: scaleY(1); }
174
+ 92%, 96% { transform: scaleY(0.1); }
175
+ }
176
+ .pal-eye {
177
+ transform-box: fill-box;
178
+ transform-origin: center;
179
+ animation: pal-blink var(--_pal-blink-ms) ease-in-out infinite;
180
+ }
181
+
182
+ /* the leg itself steps: it pivots at the hip (15,63 in viewBox units),
183
+ swinging back and forth while the pal is on the move */
184
+ .pal-leg {
185
+ transform-box: view-box;
186
+ /* the hip; PalSvg sets it inline from the shape */
187
+ transform-origin: 15px 63px;
188
+ }
189
+ .pal-walking .pal-leg {
190
+ animation: pal-leg-step var(--_pal-step-ms) ease-in-out infinite;
191
+ }
192
+ @keyframes pal-leg-step {
193
+ 0%, 100% { transform: rotate(0deg); }
194
+ 30% { transform: rotate(-14deg); }
195
+ 70% { transform: rotate(11deg); }
196
+ }
197
+
198
+ /* standing still: a gentle sway so he never looks frozen */
199
+ .pal-idle {
200
+ animation: pal-wiggle var(--_pal-sway-ms) ease-in-out infinite;
201
+ transform-origin: 50% 90%;
202
+ }
203
+
204
+ /* ---------- the pal's emotes ----------
205
+ He acts out whatever he just said. Every target shape below keeps the exact
206
+ command sequence of the real paths in PalSvg (body: M L C C L C C L, leg:
207
+ M C C L) — that is what lets the browser interpolate instead of cutting. A
208
+ zero-length leg is a round dot thanks to the round linecap, which is how the
209
+ question mark gets its point. Browsers without CSS `d` ignore the morphs and
210
+ he stays a paperclip, which is a perfectly good thing to fall back to.
211
+
212
+ Two kinds of emote:
213
+
214
+ POSES (unbend, curl, heart, question) bend him into a shape and HOLD it —
215
+ `forwards`, no return leg — until something tells him to come out of it,
216
+ which is when the `-out` class takes over and unfolds him. That is why they
217
+ have no fixed duration: he stays a question mark until he sets off walking.
218
+
219
+ GESTURES (everything else) play once and land back where they started. */
220
+ @keyframes pal-unbend-in {
221
+ to { d: path("M25 78 L25 56.3 C25 53.9 25 51.5 25 49.1 C25 46.7 25 44.2 25 41.8 L25 25.6 C25 24 25 22.3 25 20.6 C25 18.9 25 17.3 25 15.6 L25 2.1"); }
222
+ }
223
+ @keyframes pal-unbend-out {
224
+ from { d: path("M25 78 L25 56.3 C25 53.9 25 51.5 25 49.1 C25 46.7 25 44.2 25 41.8 L25 25.6 C25 24 25 22.3 25 20.6 C25 18.9 25 17.3 25 15.6 L25 2.1"); }
225
+ }
226
+ @keyframes pal-unbend-leg-in {
227
+ to { d: path("M25 78 C25 78 25 78 25 78 C25 78 25 78 25 78 L25 78"); }
228
+ }
229
+ @keyframes pal-unbend-leg-out {
230
+ from { d: path("M25 78 C25 78 25 78 25 78 C25 78 25 78 25 78 L25 78"); }
231
+ }
232
+ @keyframes pal-curl-in {
233
+ to { d: path("M10 66 L10 58 C7 45 13 30 27 29 C40 28 47 40 43 49 L42 51 C38 60 26 61 22 53 C18 46 24 39 30 41 L32 44"); }
234
+ }
235
+ @keyframes pal-curl-out {
236
+ from { d: path("M10 66 L10 58 C7 45 13 30 27 29 C40 28 47 40 43 49 L42 51 C38 60 26 61 22 53 C18 46 24 39 30 41 L32 44"); }
237
+ }
238
+ @keyframes pal-curl-leg-in {
239
+ to { d: path("M32 44 C32 44 32 44 32 44 C32 44 32 44 32 44 L32 44"); }
240
+ }
241
+ @keyframes pal-curl-leg-out {
242
+ from { d: path("M32 44 C32 44 32 44 32 44 C32 44 32 44 32 44 L32 44"); }
243
+ }
244
+ @keyframes pal-heart-in {
245
+ to { d: path("M25 71 L9 50 C0 38 2 21 15 21 C22 21 25 27 25 34 L26 32 C28 21 40 18 46 29 C51 39 43 46 37 52 L27 67"); }
246
+ }
247
+ @keyframes pal-heart-out {
248
+ from { d: path("M25 71 L9 50 C0 38 2 21 15 21 C22 21 25 27 25 34 L26 32 C28 21 40 18 46 29 C51 39 43 46 37 52 L27 67"); }
249
+ }
250
+ @keyframes pal-heart-leg-in {
251
+ to { d: path("M25 71 C25 71 25 71 25 71 C25 71 25 71 25 71 L25 71"); }
252
+ }
253
+ @keyframes pal-heart-leg-out {
254
+ from { d: path("M25 71 C25 71 25 71 25 71 C25 71 25 71 25 71 L25 71"); }
255
+ }
256
+ @keyframes pal-question-in {
257
+ to { d: path("M12 22 L12 18 C12 10 18 5 26 5 C34 5 39 11 39 19 L38 25 C37 32 30 34 27 40 C25 44 25 47 25 50 L25 58"); }
258
+ }
259
+ @keyframes pal-question-out {
260
+ from { d: path("M12 22 L12 18 C12 10 18 5 26 5 C34 5 39 11 39 19 L38 25 C37 32 30 34 27 40 C25 44 25 47 25 50 L25 58"); }
261
+ }
262
+ @keyframes pal-question-leg-in {
263
+ to { d: path("M25 68 C25 68 25 68 25 68 C25 68 25 68 25 68 L25 68"); }
264
+ }
265
+ @keyframes pal-question-leg-out {
266
+ from { d: path("M25 68 C25 68 25 68 25 68 C25 68 25 68 25 68 L25 68"); }
267
+ }
268
+ /* the face has nowhere to sit on a bare wire, a spiral or a heart */
269
+ @keyframes pal-face-out { from { opacity: 1; } to { opacity: 0; } }
270
+ @keyframes pal-face-in { from { opacity: 0; } to { opacity: 1; } }
271
+
272
+ /* a stretch keeps reaching for it while he is straight */
273
+ @keyframes pal-unbend-reach {
274
+ 0%, 100% { transform: scaleY(1); }
275
+ 50% { transform: scaleY(1.05); }
276
+ }
277
+ /* Rolling up spins him; unrolling spins him back. Both land on a whole turn,
278
+ which matters: a continuous spin would have to be cut at an arbitrary angle
279
+ when the emote ends, and he would jerk upright. Ending on a multiple of
280
+ 360deg means the class can come off at any time without a snap. */
281
+ @keyframes pal-curl-roll {
282
+ from { transform: rotate(0deg); }
283
+ to { transform: rotate(360deg); }
284
+ }
285
+ @keyframes pal-curl-unroll {
286
+ from { transform: rotate(0deg); }
287
+ to { transform: rotate(-360deg); }
288
+ }
289
+
290
+ /* ---- poses: bend in, hold, unfold ---- */
291
+ .pal-em-unbend .pal-body { animation: pal-unbend-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
292
+ .pal-em-unbend .pal-leg { animation: pal-unbend-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
293
+ .pal-em-unbend .pal-eye { animation: pal-face-out calc(var(--_pal-tempo) * 0.5s) ease-in-out forwards; }
294
+ .pal-em-unbend-out .pal-body { animation: pal-unbend-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
295
+ .pal-em-unbend-out .pal-leg { animation: pal-unbend-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
296
+ .pal-em-unbend-out .pal-eye { animation: pal-face-in calc(var(--_pal-tempo) * 0.5s) ease-in-out calc(var(--_pal-tempo) * 0.3s) forwards; }
297
+
298
+ .pal-em-curl .pal-body { animation: pal-curl-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
299
+ .pal-em-curl .pal-leg { animation: pal-curl-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
300
+ .pal-em-curl .pal-eye { animation: pal-face-out calc(var(--_pal-tempo) * 0.5s) ease-in-out forwards; }
301
+ .pal-em-curl-out .pal-body { animation: pal-curl-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
302
+ .pal-em-curl-out .pal-leg { animation: pal-curl-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
303
+ .pal-em-curl-out .pal-eye { animation: pal-face-in calc(var(--_pal-tempo) * 0.5s) ease-in-out calc(var(--_pal-tempo) * 0.3s) forwards; }
304
+
305
+ .pal-em-heart .pal-body { animation: pal-heart-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
306
+ .pal-em-heart .pal-leg { animation: pal-heart-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
307
+ .pal-em-heart .pal-eye { animation: pal-face-out calc(var(--_pal-tempo) * 0.5s) ease-in-out forwards; }
308
+ .pal-em-heart-out .pal-body { animation: pal-heart-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
309
+ .pal-em-heart-out .pal-leg { animation: pal-heart-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
310
+ .pal-em-heart-out .pal-eye { animation: pal-face-in calc(var(--_pal-tempo) * 0.5s) ease-in-out calc(var(--_pal-tempo) * 0.3s) forwards; }
311
+
312
+ /* the question mark keeps its eyes — they land inside the hook */
313
+ .pal-em-question .pal-body { animation: pal-question-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
314
+ .pal-em-question .pal-leg { animation: pal-question-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
315
+ .pal-em-question-out .pal-body { animation: pal-question-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
316
+ .pal-em-question-out .pal-leg { animation: pal-question-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
317
+
318
+ /* He keeps stretching for as long as he stays straight. */
319
+ .pal-em-unbend { animation: pal-unbend-reach calc(var(--_pal-tempo) * 2.6s) ease-in-out calc(var(--_pal-tempo) * 0.9s) infinite; }
320
+
321
+ .pal-em-curl {
322
+ animation: pal-curl-roll calc(var(--_pal-tempo) * 1.5s) cubic-bezier(0.34, 0, 0.2, 1) forwards;
323
+ transform-origin: 54% 53%;
324
+ }
325
+ .pal-em-curl-out {
326
+ animation: pal-curl-unroll calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards;
327
+ transform-origin: 54% 53%;
328
+ }
329
+
330
+ /* ---- gestures: play once, land back where they started ---- */
331
+ /* One clean turn. Squeezing scaleX through zero is the flat 2D fake and it
332
+ read as exactly that; rotateY behind a perspective() gives real
333
+ foreshortening — the near edge grows as the far edge shrinks. */
334
+ @keyframes pal-spin {
335
+ 0% { transform: perspective(190px) rotateY(0deg); }
336
+ 10% { transform: perspective(190px) rotateY(-28deg); }
337
+ 100% { transform: perspective(190px) rotateY(360deg); }
338
+ }
339
+
340
+ /* a good long look left, a beat, then right */
341
+ @keyframes pal-look-eyes {
342
+ 0% { transform: translateX(0); }
343
+ 14%, 34% { transform: translateX(-2.4px); }
344
+ 46% { transform: translateX(0); }
345
+ 58%, 80% { transform: translateX(2.4px); }
346
+ 92%, 100% { transform: translateX(0); }
347
+ }
348
+ @keyframes pal-look-tilt {
349
+ 0% { transform: rotate(0deg); }
350
+ 20%, 34% { transform: rotate(-5deg); }
351
+ 46% { transform: rotate(0deg); }
352
+ 64%, 80% { transform: rotate(5deg); }
353
+ 92%, 100% { transform: rotate(0deg); }
354
+ }
355
+
356
+ /* Steps up closer to say something and stays there while the line is read.
357
+ Capped at 1.16: he stands in a gutter with GAP px of clearance to the text
358
+ and EDGE px to the screen, and anything larger eats into both. */
359
+ @keyframes pal-lean {
360
+ 0%, 100% { transform: scale(1) translateY(0); }
361
+ 16%, 84% { transform: scale(1.16) translateY(-3px); }
362
+ }
363
+
364
+ /* A decaying bounce: hops, each lower than the last, then he stands still.
365
+ The squash and stretch are inverse on the two axes so his volume looks
366
+ right. */
367
+ @keyframes pal-bounce {
368
+ 0% { transform: translateY(0) scale(1, 1); }
369
+ 9% { transform: translateY(0) scale(1.1, 0.88); }
370
+ 25% { transform: translateY(-16px) scale(0.94, 1.08); }
371
+ 40% { transform: translateY(0) scale(1.08, 0.92); }
372
+ 52% { transform: translateY(-10px) scale(0.96, 1.05); }
373
+ 64% { transform: translateY(0) scale(1.06, 0.94); }
374
+ 73% { transform: translateY(-5px) scale(0.98, 1.03); }
375
+ 82% { transform: translateY(0) scale(1.03, 0.97); }
376
+ 90%, 100% { transform: translateY(0) scale(1, 1); }
377
+ }
378
+
379
+ /* Knocks on the inside of the screen: he dips, walks up to the glass, puts
380
+ his leg out straight so it reads as a pointing finger, and raps three times.
381
+ The scale-up on each tap is him pressing toward you — there is no depth
382
+ here, so scale has to carry "into the display". */
383
+ @keyframes pal-knock {
384
+ 0% { transform: scale(1) rotate(0deg) translateY(0); }
385
+ 6% { transform: scale(0.95) rotate(0deg) translateY(2px); }
386
+ 20% { transform: scale(1.14) rotate(0deg) translateY(-2px); }
387
+ 32%, 40% { transform: scale(1.12) rotate(0deg) translateY(0); }
388
+ 44% { transform: scale(1.19) rotate(2deg) translateY(0); }
389
+ 52% { transform: scale(1.11) rotate(-2deg) translateY(0); }
390
+ 62% { transform: scale(1.19) rotate(2deg) translateY(0); }
391
+ 70% { transform: scale(1.11) rotate(-2deg) translateY(0); }
392
+ 80% { transform: scale(1.18) rotate(2deg) translateY(0); }
393
+ 88% { transform: scale(1.12) rotate(0deg) translateY(0); }
394
+ 100% { transform: scale(1) rotate(0deg) translateY(0); }
395
+ }
396
+ /* The leg straightens out and tips up at the end, so it reads as a forearm
397
+ with the fingertip raised rather than a prop holding him up. It has to stay
398
+ clear of the body silhouette — the hip pivot is at the bottom left, so
399
+ anything aimed upward just draws a bar straight through him. */
400
+ @keyframes pal-knock-finger {
401
+ 26%, 88% { d: path("M15 63 C15 71 23 73 31 71 C40 68 45 60 45 52 L45 46"); }
402
+ }
403
+ /* ...and jabs on each rap. Two animations on one element: this one drives
404
+ transform, the one above drives d, so they compose instead of clashing. */
405
+ @keyframes pal-knock-jab {
406
+ 0%, 34% { transform: rotate(0deg); }
407
+ 44% { transform: rotate(-8deg); }
408
+ 52% { transform: rotate(1deg); }
409
+ 62% { transform: rotate(-8deg); }
410
+ 70% { transform: rotate(1deg); }
411
+ 80% { transform: rotate(-7deg); }
412
+ 92%, 100% { transform: rotate(0deg); }
413
+ }
414
+ /* Rings spreading from the contact point, once per rap. */
415
+ @keyframes pal-tap-ring {
416
+ 0%, 41% { opacity: 0; transform: scale(0.25); }
417
+ 45% { opacity: 1; transform: scale(0.55); }
418
+ 56% { opacity: 0; transform: scale(1.3); }
419
+ 59% { opacity: 0; transform: scale(0.25); }
420
+ 63% { opacity: 1; transform: scale(0.55); }
421
+ 74% { opacity: 0; transform: scale(1.3); }
422
+ 77% { opacity: 0; transform: scale(0.25); }
423
+ 81% { opacity: 0.95; transform: scale(0.55); }
424
+ 93%, 100% { opacity: 0; transform: scale(1.3); }
425
+ }
426
+
427
+ /* A ball rolls in, he keeps it up twice, then boots it away.
428
+
429
+ The leg straightens into an actual foot for the duration: while it is curled
430
+ into its resting loop it covers the whole bottom-right of the frame, which
431
+ is where the ball has to be, and the two just overlapped into a mess.
432
+
433
+ The per-keyframe timing functions are what give the ball gravity — ease-out
434
+ on every rise, ease-in on every fall. One easing across the whole thing
435
+ floats. It also never fades in mid-air: it leaves through the edge of the
436
+ frame at speed, and only then drops to zero. */
437
+ @keyframes pal-kick-plant {
438
+ 12%, 76% { d: path("M15 63 C19 66 23 69 27 71 C29 72 31 73 32 74 L34 75"); }
439
+ }
440
+ @keyframes pal-kick-ball {
441
+ 0% {
442
+ opacity: 0;
443
+ transform: translate(16px, 0) rotate(0deg);
444
+ animation-timing-function: ease-out;
445
+ }
446
+ 12% {
447
+ opacity: 1;
448
+ transform: translate(3px, 0) rotate(-150deg);
449
+ animation-timing-function: linear;
450
+ }
451
+ 24% {
452
+ transform: translate(0, 0) rotate(-190deg);
453
+ animation-timing-function: ease-out;
454
+ }
455
+ 33% {
456
+ transform: translate(-2px, -23px) rotate(-70deg);
457
+ animation-timing-function: ease-in;
458
+ }
459
+ 44% {
460
+ transform: translate(0, 0) rotate(50deg);
461
+ animation-timing-function: ease-out;
462
+ }
463
+ 53% {
464
+ transform: translate(-2px, -31px) rotate(180deg);
465
+ animation-timing-function: ease-in;
466
+ }
467
+ 64% {
468
+ transform: translate(0, 0) rotate(300deg);
469
+ animation-timing-function: linear;
470
+ }
471
+ 70% {
472
+ opacity: 1;
473
+ transform: translate(0, 0) rotate(320deg);
474
+ animation-timing-function: ease-out;
475
+ }
476
+ 96%, 100% {
477
+ opacity: 1;
478
+ transform: translate(9px, -96px) rotate(760deg);
479
+ }
480
+ }
481
+ /* three swings: up, up, and away */
482
+ @keyframes pal-kick-leg {
483
+ 0%, 22% { transform: rotate(0deg); }
484
+ 28% { transform: rotate(-30deg); }
485
+ 38% { transform: rotate(6deg); }
486
+ 47% { transform: rotate(-34deg); }
487
+ 58% { transform: rotate(6deg); }
488
+ 64% { transform: rotate(26deg); }
489
+ 71% { transform: rotate(-44deg); }
490
+ 84% { transform: rotate(4deg); }
491
+ 94%, 100% { transform: rotate(0deg); }
492
+ }
493
+ @keyframes pal-kick-body {
494
+ 0%, 22% { transform: rotate(0deg) translateY(0); }
495
+ 29% { transform: rotate(3deg) translateY(-2px); }
496
+ 39% { transform: rotate(-1deg) translateY(0); }
497
+ 48% { transform: rotate(3deg) translateY(-2px); }
498
+ 59% { transform: rotate(-1deg) translateY(0); }
499
+ 65% { transform: rotate(5deg) translateY(0); }
500
+ 72% { transform: rotate(-7deg) translateY(-2px); }
501
+ 86% { transform: rotate(2deg) translateY(0); }
502
+ 94%, 100% { transform: rotate(0deg) translateY(0); }
503
+ }
504
+
505
+ /* He winds up and strikes the glass with his leg. The leg reaches up the
506
+ RIGHT side rather than straight up: the hip pivot is at the bottom left, so
507
+ anything aimed up the middle just draws a bar through his body. The crack
508
+ in PalSvg radiates from (42,30), which is where this puts his tip. */
509
+ @keyframes pal-crack-leg {
510
+ 30%, 72% { d: path("M15 63 C26 68 38 66 43 58 C46 52 45 45 45 40 L44 34"); }
511
+ }
512
+ @keyframes pal-crack-swing {
513
+ 0%, 14% { transform: rotate(0deg); }
514
+ 22% { transform: rotate(14deg); }
515
+ 30% { transform: rotate(-9deg); }
516
+ 40% { transform: rotate(-2deg); }
517
+ 72%, 100% { transform: rotate(0deg); }
518
+ }
519
+ @keyframes pal-crack-hit {
520
+ 0% { transform: scale(1) rotate(0deg) translateY(0); }
521
+ 18% { transform: scale(0.95) rotate(-6deg) translateY(2px); }
522
+ 30% { transform: scale(1.14) rotate(6deg) translateY(-2px); }
523
+ 38% { transform: scale(1.04) rotate(-3deg) translateY(1px); }
524
+ 50% { transform: scale(1.07) rotate(2deg) translateY(0); }
525
+ 66% { transform: scale(1.02) rotate(-1deg) translateY(0); }
526
+ 100% { transform: scale(1) rotate(0deg) translateY(0); }
527
+ }
528
+ /* the crack lands on the frame the leg arrives, then fades */
529
+ @keyframes pal-crack-show {
530
+ 0%, 28% { opacity: 0; transform: translate(2px, 1px) scale(0.25); }
531
+ 31% { opacity: 0.95; transform: translate(2px, 1px) scale(1.08); }
532
+ 37% { opacity: 0.8; transform: translate(2px, 1px) scale(1); }
533
+ 74% { opacity: 0.8; transform: translate(2px, 1px) scale(1); }
534
+ 100% { opacity: 0; transform: translate(2px, 1px) scale(1); }
535
+ }
536
+
537
+ /* Waves: the leg straightens into an arm raised up and out to the right,
538
+ clear of the body, and swings from the hip like a hand waving. Swinging
539
+ the curled leg itself sent the loop straight through his middle. */
540
+ @keyframes pal-wave-arm {
541
+ 14%, 86% { d: path("M15 63 C24 70 36 70 43 64 C47 60 48 52 48 44 L48 36"); }
542
+ }
543
+ @keyframes pal-wave-leg {
544
+ 0%, 14% { transform: rotate(0deg); }
545
+ 26% { transform: rotate(-8deg); }
546
+ 38% { transform: rotate(12deg); }
547
+ 50% { transform: rotate(-8deg); }
548
+ 62% { transform: rotate(12deg); }
549
+ 74% { transform: rotate(-6deg); }
550
+ 86%, 100% { transform: rotate(0deg); }
551
+ }
552
+ @keyframes pal-wave-body {
553
+ 0%, 100% { transform: rotate(0deg); }
554
+ 20%, 80% { transform: rotate(-4deg); }
555
+ }
556
+
557
+ /* yes */
558
+ @keyframes pal-nod {
559
+ 0% { transform: translateY(0) scaleY(1); }
560
+ 18% { transform: translateY(4px) scaleY(0.93); }
561
+ 36% { transform: translateY(0) scaleY(1); }
562
+ 54% { transform: translateY(4px) scaleY(0.93); }
563
+ 72% { transform: translateY(0) scaleY(1); }
564
+ 86% { transform: translateY(2px) scaleY(0.96); }
565
+ 100% { transform: translateY(0) scaleY(1); }
566
+ }
567
+
568
+ /* no */
569
+ @keyframes pal-shake {
570
+ 0% { transform: rotate(0deg); }
571
+ 14% { transform: rotate(-8deg); }
572
+ 30% { transform: rotate(8deg); }
573
+ 46% { transform: rotate(-7deg); }
574
+ 62% { transform: rotate(6deg); }
575
+ 78% { transform: rotate(-3deg); }
576
+ 100% { transform: rotate(0deg); }
577
+ }
578
+
579
+ /* Sways from side to side — hips, bob, squash and a glance on each beat, not
580
+ just a leg swinging under a rigid body. */
581
+ @keyframes pal-dance {
582
+ 0% { transform: rotate(0deg) translate(0, 0) scale(1, 1); }
583
+ 14% { transform: rotate(-12deg) translate(-5px, -5px) scale(0.96, 1.05); }
584
+ 28% { transform: rotate(10deg) translate(5px, 0) scale(1.05, 0.95); }
585
+ 42% { transform: rotate(-11deg) translate(-5px, -5px) scale(0.96, 1.05); }
586
+ 56% { transform: rotate(10deg) translate(5px, 0) scale(1.05, 0.95); }
587
+ 70% { transform: rotate(-8deg) translate(-3px, -4px) scale(0.98, 1.03); }
588
+ 84% { transform: rotate(6deg) translate(3px, 0) scale(1.03, 0.97); }
589
+ 100% { transform: rotate(0deg) translate(0, 0) scale(1, 1); }
590
+ }
591
+ @keyframes pal-dance-eyes {
592
+ 0%, 100% { transform: translateX(0); }
593
+ 14% { transform: translateX(-1.7px); }
594
+ 28% { transform: translateX(1.7px); }
595
+ 42% { transform: translateX(-1.7px); }
596
+ 56% { transform: translateX(1.7px); }
597
+ 70% { transform: translateX(-1.2px); }
598
+ 84% { transform: translateX(1px); }
599
+ }
600
+ @keyframes pal-dance-leg {
601
+ 0% { transform: rotate(0deg); }
602
+ 14% { transform: rotate(30deg); }
603
+ 28% { transform: rotate(-26deg); }
604
+ 42% { transform: rotate(28deg); }
605
+ 56% { transform: rotate(-26deg); }
606
+ 70% { transform: rotate(24deg); }
607
+ 84% { transform: rotate(-16deg); }
608
+ 100% { transform: rotate(0deg); }
609
+ }
610
+
611
+ /* The props sit in the markup on every pal but stay invisible until the emote
612
+ that uses them runs. */
613
+ .pal-ball,
614
+ .pal-crack,
615
+ .pal-tap {
616
+ opacity: 0;
617
+ transform-box: view-box;
618
+ }
619
+ .pal-ball { transform-origin: 42px 71px; }
620
+ .pal-crack { transform-origin: 42px 30px; }
621
+ .pal-tap { transform-origin: 45px 45px; }
622
+
623
+ /* The emotes that move his whole body pivot at his foot, so he pushes off the
624
+ ground rather than floating; the spin turns about his middle. */
625
+ .pal-em-lean,
626
+ .pal-em-hop,
627
+ .pal-em-knock,
628
+ .pal-em-wave,
629
+ .pal-em-nod,
630
+ .pal-em-shake,
631
+ .pal-em-dance,
632
+ .pal-em-kick,
633
+ .pal-em-crack,
634
+ .pal-em-unbend {
635
+ transform-origin: 50% 100%;
636
+ }
637
+
638
+ .pal-em-spin { animation: pal-spin calc(var(--_pal-tempo) * 1.4s) ease-in-out both; }
639
+
640
+ .pal-em-look { animation: pal-look-tilt calc(var(--_pal-tempo) * 3.2s) ease-in-out both; }
641
+ .pal-em-look .pal-eye { animation: pal-look-eyes calc(var(--_pal-tempo) * 3.2s) ease-in-out both; }
642
+
643
+ .pal-em-lean { animation: pal-lean calc(var(--_pal-tempo) * 4s) ease-in-out both; }
644
+ .pal-em-hop { animation: pal-bounce calc(var(--_pal-tempo) * 2.2s) ease-out both; }
645
+
646
+ .pal-em-knock { animation: pal-knock calc(var(--_pal-tempo) * 3.4s) ease-in-out both; }
647
+ .pal-em-knock .pal-tap { animation: pal-tap-ring calc(var(--_pal-tempo) * 3.4s) ease-out both; }
648
+ .pal-em-knock .pal-leg {
649
+ animation: pal-knock-finger calc(var(--_pal-tempo) * 3.4s) ease-in-out both,
650
+ pal-knock-jab calc(var(--_pal-tempo) * 3.4s) ease-in-out both;
651
+ }
652
+
653
+ .pal-em-kick { animation: pal-kick-body calc(var(--_pal-tempo) * 4.2s) ease-in-out both; }
654
+ .pal-em-kick .pal-leg {
655
+ animation: pal-kick-plant calc(var(--_pal-tempo) * 4.2s) ease-in-out both,
656
+ pal-kick-leg calc(var(--_pal-tempo) * 4.2s) ease-in-out both;
657
+ }
658
+ .pal-em-kick .pal-ball { animation: pal-kick-ball calc(var(--_pal-tempo) * 4.2s) linear both; }
659
+
660
+ .pal-em-crack { animation: pal-crack-hit calc(var(--_pal-tempo) * 3s) ease-in-out both; }
661
+ .pal-em-crack .pal-crack { animation: pal-crack-show calc(var(--_pal-tempo) * 3s) ease-out both; }
662
+ .pal-em-crack .pal-leg {
663
+ animation: pal-crack-leg calc(var(--_pal-tempo) * 3s) ease-in-out both,
664
+ pal-crack-swing calc(var(--_pal-tempo) * 3s) ease-in-out both;
665
+ }
666
+
667
+ .pal-em-wave { animation: pal-wave-body calc(var(--_pal-tempo) * 2.4s) ease-in-out both; }
668
+ .pal-em-wave .pal-leg {
669
+ animation: pal-wave-arm calc(var(--_pal-tempo) * 2.4s) ease-in-out both,
670
+ pal-wave-leg calc(var(--_pal-tempo) * 2.4s) ease-in-out both;
671
+ }
672
+
673
+ .pal-em-nod { animation: pal-nod calc(var(--_pal-tempo) * 1.6s) ease-in-out both; }
674
+ .pal-em-shake { animation: pal-shake calc(var(--_pal-tempo) * 1.6s) ease-in-out both; }
675
+
676
+ .pal-em-dance { animation: pal-dance calc(var(--_pal-tempo) * 3s) ease-in-out both; }
677
+ .pal-em-dance .pal-leg { animation: pal-dance-leg calc(var(--_pal-tempo) * 3s) ease-in-out both; }
678
+ .pal-em-dance .pal-eye { animation: pal-dance-eyes calc(var(--_pal-tempo) * 3s) ease-in-out both; }
679
+
680
+
681
+
682
+ /* speech bubble pops in whenever he says something new */
683
+ @keyframes pal-say {
684
+ 0% { opacity: 0; transform: scale(0.55); }
685
+ 60% { opacity: 1; transform: scale(1.06); }
686
+ 100% { opacity: 1; transform: scale(1); }
687
+ }
688
+ .pal-say {
689
+ animation: pal-say 0.34s cubic-bezier(0.22, 0.85, 0.3, 1) both;
690
+ }
691
+ /* the line fades in while the bubble box grows around it */
692
+ @keyframes pal-line-in {
693
+ from { opacity: 0; }
694
+ to { opacity: 1; }
695
+ }
696
+ .pal-line-in {
697
+ display: inline-block;
698
+ animation: pal-line-in 0.3s ease 0.1s both;
699
+ }
700
+
701
+ /* …and quietly fades away once he's done talking or starts moving */
702
+ .pal-bubble-hide {
703
+ opacity: 0;
704
+ transform: scale(0.92);
705
+ transition: opacity 0.28s ease, transform 0.28s ease;
706
+ }
707
+
708
+ /* one-legged hop for the walking pal */
709
+ @keyframes pal-hop {
710
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
711
+ 20% { transform: translateY(-7px) rotate(-5deg); }
712
+ 45% { transform: translateY(0) rotate(0deg); }
713
+ 70% { transform: translateY(-5px) rotate(4deg); }
714
+ }
715
+ .pal-hop { animation: pal-hop calc(var(--_pal-step-ms) * 1.7) ease-in-out infinite; transform-origin: 50% 92%; }
716
+
717
+ /* leg-tapping walk for the scroll guide (bob while moving) */
718
+ @keyframes pal-walk {
719
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
720
+ 25% { transform: translateY(-4px) rotate(-3deg); }
721
+ 50% { transform: translateY(0) rotate(0deg); }
722
+ 75% { transform: translateY(-3px) rotate(3deg); }
723
+ }
724
+ .pal-walk { animation: pal-walk calc(var(--_pal-step-ms) * 1.1) ease-in-out infinite; transform-origin: 50% 92%; }
725
+
726
+
727
+ /* ---------- more poses ---------- */
728
+ /* An exclamation mark: the body straightens into a short bar that stops well
729
+ above the foot, and the leg shrinks to the dot underneath. */
730
+ @keyframes pal-exclaim-in {
731
+ to { d: path("M25 56 L25 46 C25 42 25 38 25 34 C25 30 25 26 25 22 L25 18 C25 16 25 14 25 12 C25 10.5 25 9 25 8 L25 6"); }
732
+ }
733
+ @keyframes pal-exclaim-out {
734
+ from { d: path("M25 56 L25 46 C25 42 25 38 25 34 C25 30 25 26 25 22 L25 18 C25 16 25 14 25 12 C25 10.5 25 9 25 8 L25 6"); }
735
+ }
736
+ @keyframes pal-exclaim-leg-in {
737
+ to { d: path("M25 70 C25 70 25 70 25 70 C25 70 25 70 25 70 L25 70"); }
738
+ }
739
+ @keyframes pal-exclaim-leg-out {
740
+ from { d: path("M25 70 C25 70 25 70 25 70 C25 70 25 70 25 70 L25 70"); }
741
+ }
742
+ /* A key: the top loop opens into a round bow, the two arms merge into one
743
+ shaft, and the leg becomes the bit at the bottom. The bow keeps his eyes. */
744
+ @keyframes pal-key-in {
745
+ to { d: path("M25 76 L25 32 C17 32 11 26 11 18 C11 10 17 4 25 4 L25 4 C33 4 39 10 39 18 C39 26 33 32 25 32 L25 34"); }
746
+ }
747
+ @keyframes pal-key-out {
748
+ from { d: path("M25 76 L25 32 C17 32 11 26 11 18 C11 10 17 4 25 4 L25 4 C33 4 39 10 39 18 C39 26 33 32 25 32 L25 34"); }
749
+ }
750
+ @keyframes pal-key-leg-in {
751
+ to { d: path("M25 60 C29 60 33 60 35 60 C35 63 35 66 35 68 C35 68 32 68 30 68 L25 68"); }
752
+ }
753
+ @keyframes pal-key-leg-out {
754
+ from { d: path("M25 60 C29 60 33 60 35 60 C35 63 35 66 35 68 C35 68 32 68 30 68 L25 68"); }
755
+ }
756
+ /* his eyes slide into the middle of the bow */
757
+ @keyframes pal-key-eyes-in {
758
+ from { transform: translate(0, 0); }
759
+ to { transform: translate(0, 6px); }
760
+ }
761
+ @keyframes pal-key-eyes-out {
762
+ from { transform: translate(0, 6px); }
763
+ to { transform: translate(0, 0); }
764
+ }
765
+
766
+ .pal-em-exclaim .pal-body { animation: pal-exclaim-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
767
+ .pal-em-exclaim .pal-leg { animation: pal-exclaim-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
768
+ .pal-em-exclaim .pal-eye { animation: pal-face-out calc(var(--_pal-tempo) * 0.5s) ease-in-out forwards; }
769
+ .pal-em-exclaim-out .pal-body { animation: pal-exclaim-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
770
+ .pal-em-exclaim-out .pal-leg { animation: pal-exclaim-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
771
+ .pal-em-exclaim-out .pal-eye { animation: pal-face-in calc(var(--_pal-tempo) * 0.5s) ease-in-out calc(var(--_pal-tempo) * 0.3s) forwards; }
772
+ /* the bar bounces once as it lands, like the mark was stamped */
773
+ @keyframes pal-exclaim-stamp {
774
+ 0%, 60% { transform: scaleY(1); }
775
+ 75% { transform: scaleY(1.08) translateY(-2px); }
776
+ 88% { transform: scaleY(0.97); }
777
+ 100% { transform: scaleY(1); }
778
+ }
779
+ .pal-em-exclaim {
780
+ animation: pal-exclaim-stamp calc(var(--_pal-tempo) * 1.3s) ease-in-out both;
781
+ transform-origin: 50% 100%;
782
+ }
783
+
784
+ .pal-em-key .pal-body { animation: pal-key-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
785
+ .pal-em-key .pal-leg { animation: pal-key-leg-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
786
+ .pal-em-key .pal-eye { animation: pal-key-eyes-in calc(var(--_pal-tempo) * 0.9s) ease-in-out forwards; }
787
+ .pal-em-key-out .pal-body { animation: pal-key-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
788
+ .pal-em-key-out .pal-leg { animation: pal-key-leg-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
789
+ .pal-em-key-out .pal-eye { animation: pal-key-eyes-out calc(var(--_pal-tempo) * 0.7s) ease-in-out forwards; }
790
+ /* a key turns: a quarter twist in and back, once he has finished bending */
791
+ @keyframes pal-key-turn {
792
+ 0%, 20% { transform: perspective(190px) rotateY(0deg); }
793
+ 50%, 65% { transform: perspective(190px) rotateY(-70deg); }
794
+ 100% { transform: perspective(190px) rotateY(0deg); }
795
+ }
796
+ .pal-em-key {
797
+ animation: pal-key-turn calc(var(--_pal-tempo) * 2.4s) ease-in-out calc(var(--_pal-tempo) * 0.9s) both;
798
+ transform-origin: 50% 50%;
799
+ }
800
+
801
+ /* ---------- more gestures ---------- */
802
+ /* A bow from the foot: he tips forward, holds it while the applause dies
803
+ down, and straightens back up. */
804
+ @keyframes pal-bow {
805
+ 0% { transform: rotate(0deg) scaleY(1); }
806
+ 22%, 66% { transform: rotate(14deg) scaleY(0.94); }
807
+ 84% { transform: rotate(-3deg) scaleY(1.02); }
808
+ 100% { transform: rotate(0deg) scaleY(1); }
809
+ }
810
+ /* ...and bends at the waist: the top loop folds forward while the base
811
+ stays planted, so it reads as a bow rather than a lean */
812
+ @keyframes pal-bow-body {
813
+ 22%, 66% { d: path("M15 62 L16 30 C18 17 27 9 35 12 C42 15 46 22 43 29 L37 50 C36 55 33 58 29 58 C25 58 22 55 22 50 L25 32"); }
814
+ }
815
+ @keyframes pal-bow-eyes {
816
+ 0%, 100% { transform: translate(0, 0); }
817
+ 22%, 66% { transform: translate(11px, 9px); }
818
+ }
819
+ .pal-em-bow {
820
+ animation: pal-bow calc(var(--_pal-tempo) * 2.2s) ease-in-out both;
821
+ transform-origin: 50% 100%;
822
+ }
823
+ .pal-em-bow .pal-body { animation: pal-bow-body calc(var(--_pal-tempo) * 2.2s) ease-in-out both; }
824
+ .pal-em-bow .pal-eye { animation: pal-bow-eyes calc(var(--_pal-tempo) * 2.2s) ease-in-out both; }
825
+
826
+ /* Crouch, one whole turn backwards in the air, land with a squash. Ends on a
827
+ full 360 so the class can drop off without a snap. */
828
+ @keyframes pal-backflip {
829
+ 0% { transform: translateY(0) rotate(0deg) scale(1, 1); }
830
+ 14% { transform: translateY(3px) rotate(0deg) scale(1.08, 0.88); }
831
+ 30% { transform: translateY(-28px) rotate(-100deg) scale(1, 1); }
832
+ 48% { transform: translateY(-38px) rotate(-200deg) scale(1, 1); }
833
+ 66% { transform: translateY(-24px) rotate(-300deg) scale(1, 1); }
834
+ 78% { transform: translateY(0) rotate(-360deg) scale(1.1, 0.9); }
835
+ 88% { transform: translateY(0) rotate(-360deg) scale(0.97, 1.03); }
836
+ 100% { transform: translateY(0) rotate(-360deg) scale(1, 1); }
837
+ }
838
+ .pal-em-backflip {
839
+ animation: pal-backflip calc(var(--_pal-tempo) * 1.6s) ease-in-out both;
840
+ transform-origin: 50% 55%;
841
+ }
842
+ /* the leg tucks in for the turn */
843
+ @keyframes pal-backflip-leg {
844
+ 0%, 14% { transform: rotate(0deg); }
845
+ 30%, 66% { transform: rotate(-38deg); }
846
+ 80%, 100% { transform: rotate(0deg); }
847
+ }
848
+ .pal-em-backflip .pal-leg { animation: pal-backflip-leg calc(var(--_pal-tempo) * 1.6s) ease-in-out both; }
849
+
850
+ /* A shudder: fast, small, from the foot, with the eyes squeezed shut. */
851
+ @keyframes pal-shiver {
852
+ 0%, 100% { transform: translateX(0) rotate(0deg); }
853
+ 8% { transform: translateX(-2.5px) rotate(-4deg); }
854
+ 16% { transform: translateX(2.5px) rotate(4deg); }
855
+ 24% { transform: translateX(-2.5px) rotate(-4deg); }
856
+ 32% { transform: translateX(2.5px) rotate(4deg); }
857
+ 40% { transform: translateX(-2.5px) rotate(-4deg); }
858
+ 48% { transform: translateX(2.5px) rotate(4deg); }
859
+ 56% { transform: translateX(-1.2px) rotate(-2deg); }
860
+ 64% { transform: translateX(1.2px) rotate(2deg); }
861
+ 72% { transform: translateX(-0.8px) rotate(-1deg); }
862
+ 80% { transform: translateX(0.8px) rotate(1deg); }
863
+ 90% { transform: translateX(0) rotate(0deg); }
864
+ }
865
+ @keyframes pal-shiver-eyes {
866
+ 0%, 100% { transform: scaleY(1); }
867
+ 8%, 80% { transform: scaleY(0.35); }
868
+ }
869
+ .pal-em-shiver {
870
+ animation: pal-shiver calc(var(--_pal-tempo) * 1.4s) linear both;
871
+ transform-origin: 50% 100%;
872
+ }
873
+ .pal-em-shiver .pal-eye { animation: pal-shiver-eyes calc(var(--_pal-tempo) * 1.4s) ease-in-out both; }
874
+
875
+ /* Dozing off: the eyes close, he slowly droops to one side and breathes,
876
+ sleep bubbles drift up — then he jolts awake and shakes it off. */
877
+ @keyframes pal-doze {
878
+ 0% { transform: rotate(0deg) scaleY(1); }
879
+ 20% { transform: rotate(2deg) scaleY(0.99); }
880
+ 45% { transform: rotate(10deg) scaleY(0.97); }
881
+ 60% { transform: rotate(9deg) scaleY(0.98); }
882
+ 76% { transform: rotate(11deg) scaleY(0.965); }
883
+ 82% { transform: rotate(-4deg) scaleY(1.03); }
884
+ 90% { transform: rotate(2deg) scaleY(1); }
885
+ 100% { transform: rotate(0deg) scaleY(1); }
886
+ }
887
+ @keyframes pal-doze-eyes {
888
+ 0% { transform: scaleY(1); }
889
+ 12%, 78% { transform: scaleY(0.08); }
890
+ 82% { transform: scaleY(1.25); }
891
+ 90%, 100% { transform: scaleY(1); }
892
+ }
893
+ @keyframes pal-zzz-rise {
894
+ 0%, 18% { opacity: 0; transform: translateY(4px); }
895
+ 30% { opacity: 1; }
896
+ 70% { opacity: 1; transform: translateY(-3px); }
897
+ 80%, 100% { opacity: 0; transform: translateY(-4px); }
898
+ }
899
+ .pal-em-doze {
900
+ animation: pal-doze calc(var(--_pal-tempo) * 3.2s) ease-in-out both;
901
+ transform-origin: 50% 100%;
902
+ }
903
+ .pal-em-doze .pal-eye { animation: pal-doze-eyes calc(var(--_pal-tempo) * 3.2s) ease-in-out both; }
904
+ .pal-em-doze .pal-zzz-1 { animation: pal-zzz-rise calc(var(--_pal-tempo) * 3.2s) ease-in-out both; }
905
+ .pal-em-doze .pal-zzz-2 { animation: pal-zzz-rise calc(var(--_pal-tempo) * 3.2s) ease-in-out calc(var(--_pal-tempo) * 0.35s) both; }
906
+ .pal-em-doze .pal-zzz-3 { animation: pal-zzz-rise calc(var(--_pal-tempo) * 3.2s) ease-in-out calc(var(--_pal-tempo) * 0.7s) both; }
907
+ .pal-zzz {
908
+ opacity: 0;
909
+ transform-box: view-box;
910
+ }
911
+ .pal-em-doze .pal-zzz { opacity: 1; }
912
+ .pal-zzz circle { opacity: 0; }
913
+
914
+ /* Under reduced motion the pal keeps still and the bubbles simply appear. */
915
+ @media (prefers-reduced-motion: reduce) {
916
+ .pal,
917
+ .pal *,
918
+ .pal-idle,
919
+ .pal-walk,
920
+ .pal-hop,
921
+ .pal-float,
922
+ .clip-pal-bubble,
923
+ .clip-pal-bubble *,
924
+ .pal-say,
925
+ .pal-line-in {
926
+ animation: none !important;
927
+ transition: none !important;
928
+ }
929
+ }