@lavalogic/scoria 0.40.9 → 0.40.11
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/dist/Components/LoadingAnimation.svelte +1145 -87
- package/dist/Components/LoadingAnimation.svelte.d.ts +6 -0
- package/dist/Components/LoadingAnimationProps.d.ts +40 -6
- package/dist/Components/LoadingModal.svelte +6 -2
- package/dist/Components/LoadingModalProps.d.ts +14 -2
- package/dist/Components/LoadingOverlay.svelte +7 -2
- package/dist/Components/LoadingOverlayProps.d.ts +13 -1
- package/package.json +1 -1
|
@@ -1,131 +1,1189 @@
|
|
|
1
1
|
<svelte:options runes />
|
|
2
2
|
|
|
3
|
+
<script
|
|
4
|
+
lang="ts"
|
|
5
|
+
module
|
|
6
|
+
>
|
|
7
|
+
/**
|
|
8
|
+
* Warm, on-brand phrases printed inside the box's rear flap. One is chosen at
|
|
9
|
+
* random when the loader mounts and a fresh one is shown on each loop. Pass
|
|
10
|
+
* the `messages` prop to override this set.
|
|
11
|
+
*/
|
|
12
|
+
export const defaultLoaderMessages: string[] = [
|
|
13
|
+
'You rock',
|
|
14
|
+
'Have a great day',
|
|
15
|
+
'Thanks for waiting',
|
|
16
|
+
'Almost there',
|
|
17
|
+
'Hang tight',
|
|
18
|
+
'Worth the wait',
|
|
19
|
+
"You're a star",
|
|
20
|
+
'Nearly done',
|
|
21
|
+
'Sit back and relax',
|
|
22
|
+
'Good things come to those who wait',
|
|
23
|
+
'On its way',
|
|
24
|
+
'Making it happen',
|
|
25
|
+
'Fresh out the box',
|
|
26
|
+
'Hold tight, nearly there',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// ---- geometry constants for the thrown-letter extrusion ----
|
|
30
|
+
const MAXLW = 200; // max combined letter width, box-local px (long words shrink to fit)
|
|
31
|
+
const DEPTH = 19; // extrusion layers behind each letter face
|
|
32
|
+
const EVX = 0.22; // extrusion direction: across the box's resting angle
|
|
33
|
+
const EVY = -0.24;
|
|
34
|
+
const EVZ = -1.0;
|
|
35
|
+
const NFLUTES = 4; // corrugation waves shaded across the thickness
|
|
36
|
+
|
|
37
|
+
function shuffle(a: number[]): number[] {
|
|
38
|
+
for (let i = a.length - 1; i > 0; i--) {
|
|
39
|
+
const j = (Math.random() * (i + 1)) | 0;
|
|
40
|
+
const t = a[i];
|
|
41
|
+
a[i] = a[j];
|
|
42
|
+
a[j] = t;
|
|
43
|
+
}
|
|
44
|
+
return a;
|
|
45
|
+
}
|
|
46
|
+
function rand(min: number, max: number): number {
|
|
47
|
+
return min + Math.random() * (max - min);
|
|
48
|
+
}
|
|
49
|
+
function n1(v: number): number {
|
|
50
|
+
return Math.round(v * 10) / 10;
|
|
51
|
+
}
|
|
52
|
+
function parseColor(c: string): [number, number, number] {
|
|
53
|
+
c = (c || '').trim();
|
|
54
|
+
if (c.charAt(0) === '#') {
|
|
55
|
+
if (c.length === 4) {
|
|
56
|
+
c = '#' + c[1] + c[1] + c[2] + c[2] + c[3] + c[3];
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
parseInt(c.substr(1, 2), 16),
|
|
60
|
+
parseInt(c.substr(3, 2), 16),
|
|
61
|
+
parseInt(c.substr(5, 2), 16),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const m = c.match(/(\d+)[,\s]+(\d+)[,\s]+(\d+)/);
|
|
65
|
+
if (m) {
|
|
66
|
+
return [+m[1], +m[2], +m[3]];
|
|
67
|
+
}
|
|
68
|
+
return [154, 160, 166];
|
|
69
|
+
}
|
|
70
|
+
function mixRGB(
|
|
71
|
+
a: [number, number, number],
|
|
72
|
+
b: [number, number, number],
|
|
73
|
+
t: number
|
|
74
|
+
): [number, number, number] {
|
|
75
|
+
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
76
|
+
return [
|
|
77
|
+
Math.round(a[0] + (b[0] - a[0]) * t),
|
|
78
|
+
Math.round(a[1] + (b[1] - a[1]) * t),
|
|
79
|
+
Math.round(a[2] + (b[2] - a[2]) * t),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
function rgbStr(c: [number, number, number]): string {
|
|
83
|
+
return 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')';
|
|
84
|
+
}
|
|
85
|
+
// Shade a single extrusion layer like stacked fluted cardboard: it darkens
|
|
86
|
+
// toward the back, with flute banding so the thickness reads as corrugated
|
|
87
|
+
// sheets — matching the box stylistically.
|
|
88
|
+
function fluteCol(
|
|
89
|
+
k: number,
|
|
90
|
+
faceRGB: [number, number, number],
|
|
91
|
+
deepRGB: [number, number, number]
|
|
92
|
+
): string {
|
|
93
|
+
const t = k / DEPTH; // 0 = just behind the face, 1 = deepest
|
|
94
|
+
const wave = 0.5 - 0.5 * Math.cos(t * Math.PI * 2 * NFLUTES); // 0 = ridge, 1 = valley
|
|
95
|
+
const ridge = mixRGB(faceRGB, deepRGB, 0.46 + 0.42 * t);
|
|
96
|
+
const valley = mixRGB(faceRGB, deepRGB, 0.74 + 0.26 * t);
|
|
97
|
+
return rgbStr(mixRGB(ridge, valley, wave));
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
3
101
|
<script lang="ts">
|
|
4
|
-
import { loadingText } from '../Helpers/Helpers.svelte.js';
|
|
5
102
|
import type { LoadingAnimationProps } from './LoadingAnimationProps.js';
|
|
6
103
|
|
|
7
|
-
const {
|
|
104
|
+
const {
|
|
105
|
+
name = '',
|
|
106
|
+
showText = true,
|
|
107
|
+
label = 'Loading',
|
|
108
|
+
caption = 'Loading',
|
|
109
|
+
messages = defaultLoaderMessages,
|
|
110
|
+
letterFace = '#cdab73',
|
|
111
|
+
letterDeep = '#2a1d0d',
|
|
112
|
+
}: LoadingAnimationProps = $props();
|
|
113
|
+
|
|
114
|
+
// The word the box spells: the caller's first name, or the product name as a
|
|
115
|
+
// friendly fallback. Block capitals tumble cleanly and dodge lowercase
|
|
116
|
+
// descenders clipping the baseline on landing.
|
|
117
|
+
const word = $derived((name.trim() || 'FloWMS').toUpperCase());
|
|
118
|
+
|
|
119
|
+
// Caption text with any trailing dots/ellipsis stripped, since the pill
|
|
120
|
+
// appends its own animated "..." — avoids "moment……" style doubling.
|
|
121
|
+
const captionText = $derived(caption.replace(/[\s.…]+$/, ''));
|
|
122
|
+
|
|
123
|
+
// Per-letter render data: each letter is a stack of DEPTH layers (painted
|
|
124
|
+
// back-to-front) topped by the lit face, so it has genuine 3D thickness.
|
|
125
|
+
const geom = $derived.by(() => {
|
|
126
|
+
const chars = word.split('');
|
|
127
|
+
const N = Math.max(chars.length, 1);
|
|
128
|
+
const spacing = Math.min(40, MAXLW / N);
|
|
129
|
+
const fontSize = Math.max(13, Math.min(34, spacing * 0.92));
|
|
130
|
+
const perStep = Math.max(0.6, fontSize * 0.046);
|
|
131
|
+
const faceRGB = parseColor(letterFace);
|
|
132
|
+
const deepRGB = parseColor(letterDeep);
|
|
133
|
+
const letters = chars.map((ch) => {
|
|
134
|
+
const glyph = ch === ' ' ? ' ' : ch;
|
|
135
|
+
const layers: { transform: string; color: string }[] = [];
|
|
136
|
+
for (let k = DEPTH; k >= 1; k--) {
|
|
137
|
+
layers.push({
|
|
138
|
+
transform:
|
|
139
|
+
'translate(-50%,-50%) translate3d(' +
|
|
140
|
+
n1(EVX * perStep * k) +
|
|
141
|
+
'px,' +
|
|
142
|
+
n1(EVY * perStep * k) +
|
|
143
|
+
'px,' +
|
|
144
|
+
n1(EVZ * perStep * k) +
|
|
145
|
+
'px)',
|
|
146
|
+
color: fluteCol(k, faceRGB, deepRGB),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return { glyph, layers };
|
|
150
|
+
});
|
|
151
|
+
return { N, spacing, fontSize, letters };
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Confetti launch vectors — fixed once per instance so the burst is stable.
|
|
155
|
+
const fills = Array.from({ length: 16 }, () => ({
|
|
156
|
+
dx: rand(-50, 56),
|
|
157
|
+
dz: rand(-40, 40),
|
|
158
|
+
rise: rand(48, 90),
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
let rootRef: HTMLDivElement | undefined = $state();
|
|
162
|
+
let boxRef: HTMLDivElement | undefined = $state();
|
|
163
|
+
let glowRef: HTMLDivElement | undefined = $state();
|
|
164
|
+
let s0pivot: HTMLDivElement | undefined = $state();
|
|
165
|
+
let s1pivot: HTMLDivElement | undefined = $state();
|
|
166
|
+
let s2pivot: HTMLDivElement | undefined = $state();
|
|
167
|
+
let s3pivot: HTMLDivElement | undefined = $state();
|
|
168
|
+
let letterRefs: (HTMLSpanElement | undefined)[] = $state([]);
|
|
169
|
+
let fillRefs: (HTMLSpanElement | undefined)[] = $state([]);
|
|
170
|
+
|
|
171
|
+
let msgIndex = $state(0);
|
|
172
|
+
|
|
173
|
+
$effect(() => {
|
|
174
|
+
const rootEl = rootRef;
|
|
175
|
+
const boxEl = boxRef;
|
|
176
|
+
if (!rootEl || !boxEl) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Read reactive inputs so the timeline rebuilds when the word changes.
|
|
181
|
+
const { N, spacing } = geom;
|
|
182
|
+
const letterEls = letterRefs
|
|
183
|
+
.slice(0, geom.letters.length)
|
|
184
|
+
.filter((x): x is HTMLSpanElement => !!x);
|
|
185
|
+
|
|
186
|
+
const reduce =
|
|
187
|
+
window.matchMedia &&
|
|
188
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
189
|
+
if (reduce) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cs = getComputedStyle(rootEl);
|
|
194
|
+
const hh = parseFloat(cs.getPropertyValue('--hh')) || 55;
|
|
195
|
+
const openDeg = parseFloat(cs.getPropertyValue('--open')) || 130;
|
|
196
|
+
const landY = 30;
|
|
197
|
+
const landZ = 112; // box-local: on the floor plane, out in front of the box
|
|
198
|
+
|
|
199
|
+
const anims: Animation[] = [];
|
|
200
|
+
const track = (a: Animation | null | undefined): void => {
|
|
201
|
+
if (a) {
|
|
202
|
+
anims.push(a);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ---- timeline (ms), scaled to the word length ----
|
|
207
|
+
const D_rotin = 700;
|
|
208
|
+
const outerOpen: [number, number] = [600, 1080];
|
|
209
|
+
const innerOpen: [number, number] = [1020, 1520];
|
|
210
|
+
const outStagger = 205;
|
|
211
|
+
const outFlight = 860;
|
|
212
|
+
const outStart = 1560;
|
|
213
|
+
const outEnd = outStart + (N - 1) * outStagger + outFlight;
|
|
214
|
+
const confStart = outEnd + 240;
|
|
215
|
+
const confDur = 1300;
|
|
216
|
+
const confEnd = confStart + confDur;
|
|
217
|
+
const backStagger = 120;
|
|
218
|
+
const backFlight = 520;
|
|
219
|
+
const backStart = confEnd + 200;
|
|
220
|
+
const backEnd = backStart + (N - 1) * backStagger + backFlight;
|
|
221
|
+
const closeStart = backEnd + 180;
|
|
222
|
+
const innerClose: [number, number] = [closeStart, closeStart + 460];
|
|
223
|
+
const outerClose: [number, number] = [closeStart + 360, closeStart + 880];
|
|
224
|
+
const spinStart = outerClose[1] + 60;
|
|
225
|
+
const spinEnd = spinStart + 980;
|
|
226
|
+
const T = spinEnd + 460;
|
|
227
|
+
|
|
228
|
+
const o = (t: number): number => Math.max(0, Math.min(1, t / T));
|
|
229
|
+
const loop: KeyframeAnimationOptions = {
|
|
230
|
+
duration: T,
|
|
231
|
+
iterations: Infinity,
|
|
232
|
+
easing: 'linear',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// box: flatten -> tip to resting angle -> hold -> spin shut
|
|
236
|
+
const boxKF: Keyframe[] = [
|
|
237
|
+
{
|
|
238
|
+
offset: 0,
|
|
239
|
+
transform: 'rotateX(0deg) rotateY(0deg)',
|
|
240
|
+
easing: 'cubic-bezier(.5,0,.3,1)',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
offset: o(D_rotin),
|
|
244
|
+
transform: 'rotateX(-26deg) rotateY(-32deg)',
|
|
245
|
+
easing: 'linear',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
offset: o(spinStart),
|
|
249
|
+
transform: 'rotateX(-26deg) rotateY(-32deg)',
|
|
250
|
+
easing: 'cubic-bezier(.6,0,.3,1)',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
offset: o(spinEnd),
|
|
254
|
+
transform: 'rotateX(0deg) rotateY(-360deg)',
|
|
255
|
+
easing: 'linear',
|
|
256
|
+
},
|
|
257
|
+
{ offset: 1, transform: 'rotateX(0deg) rotateY(-360deg)' },
|
|
258
|
+
];
|
|
259
|
+
track(boxEl.animate(boxKF, loop));
|
|
260
|
+
|
|
261
|
+
// flaps: open, hold, close
|
|
262
|
+
function flapKF(open: [number, number], close: [number, number]): Keyframe[] {
|
|
263
|
+
const ov = 'rotateX(' + -openDeg + 'deg)';
|
|
264
|
+
return [
|
|
265
|
+
{ offset: 0, transform: 'rotateX(0deg)' },
|
|
266
|
+
{
|
|
267
|
+
offset: o(open[0]),
|
|
268
|
+
transform: 'rotateX(0deg)',
|
|
269
|
+
easing: 'cubic-bezier(.4,0,.2,1)',
|
|
270
|
+
},
|
|
271
|
+
{ offset: o(open[1]), transform: ov, easing: 'ease-in-out' },
|
|
272
|
+
{
|
|
273
|
+
offset: o(close[0]),
|
|
274
|
+
transform: ov,
|
|
275
|
+
easing: 'cubic-bezier(.4,0,.2,1)',
|
|
276
|
+
},
|
|
277
|
+
{ offset: o(close[1]), transform: 'rotateX(0deg)' },
|
|
278
|
+
{ offset: 1, transform: 'rotateX(0deg)' },
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
[s0pivot, s2pivot].forEach((p) => {
|
|
282
|
+
if (p) {
|
|
283
|
+
track(p.animate(flapKF(outerOpen, outerClose), loop));
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
[s1pivot, s3pivot].forEach((p) => {
|
|
287
|
+
if (p) {
|
|
288
|
+
track(p.animate(flapKF(innerOpen, innerClose), loop));
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// letters: random pop order out, eruption pause, random pop order back in.
|
|
293
|
+
// They live in their own stage drawn ABOVE the box, so they sail up out of
|
|
294
|
+
// the opening and land in front of everything — never behind or through the
|
|
295
|
+
// front flap.
|
|
296
|
+
const outOrder = shuffle(letterEls.map((_, i) => i));
|
|
297
|
+
const backOrder = shuffle(letterEls.map((_, i) => i));
|
|
298
|
+
const outSlot: number[] = [];
|
|
299
|
+
outOrder.forEach((idx, k) => (outSlot[idx] = k));
|
|
300
|
+
const backSlot: number[] = [];
|
|
301
|
+
backOrder.forEach((idx, k) => (backSlot[idx] = k));
|
|
302
|
+
|
|
303
|
+
letterEls.forEach((el, i) => {
|
|
304
|
+
const lx = (i - (N - 1) / 2) * spacing;
|
|
305
|
+
const ot = outStart + outSlot[i] * outStagger;
|
|
306
|
+
const bt = backStart + backSlot[i] * backStagger;
|
|
307
|
+
const f = outFlight;
|
|
308
|
+
const bf = backFlight;
|
|
309
|
+
const sgn = Math.random() < 0.5 ? -1 : 1;
|
|
310
|
+
const spin = sgn * rand(55, 120); // tumble through the air
|
|
311
|
+
const wonky = sgn * rand(12, 28); // askew angle on landing, tips upright on settle
|
|
312
|
+
const base = 'translate(-50%,-50%) ';
|
|
313
|
+
function pose(
|
|
314
|
+
x: number,
|
|
315
|
+
y: number,
|
|
316
|
+
z: number,
|
|
317
|
+
rz: number,
|
|
318
|
+
sx: number,
|
|
319
|
+
sy: number
|
|
320
|
+
): string {
|
|
321
|
+
return (
|
|
322
|
+
base +
|
|
323
|
+
'translate3d(' +
|
|
324
|
+
n1(x) +
|
|
325
|
+
'px,' +
|
|
326
|
+
n1(y) +
|
|
327
|
+
'px,' +
|
|
328
|
+
n1(z) +
|
|
329
|
+
'px) rotateZ(' +
|
|
330
|
+
n1(rz) +
|
|
331
|
+
'deg) scale(' +
|
|
332
|
+
n1(sx) +
|
|
333
|
+
',' +
|
|
334
|
+
n1(sy) +
|
|
335
|
+
')'
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
function smooth(a: number): number {
|
|
339
|
+
a = a < 0 ? 0 : a > 1 ? 1 : a;
|
|
340
|
+
return a * a * (3 - 2 * a);
|
|
341
|
+
}
|
|
342
|
+
const pHide = pose(rand(-8, 8), -hh * 0.22, 0, 0, 0.12, 0.12);
|
|
343
|
+
const SAMP = 15;
|
|
344
|
+
|
|
345
|
+
// THROW OUT: a real projectile sampled at even time steps — fast launch
|
|
346
|
+
// out of the opening, a hang at the apex, then a gravity-driven drop that
|
|
347
|
+
// lands it on the floor in front of the box.
|
|
348
|
+
const oy0 = -hh - 4;
|
|
349
|
+
const oyA = -hh - 80;
|
|
350
|
+
const oy1 = landY;
|
|
351
|
+
const oRise = oy0 - oyA;
|
|
352
|
+
const oFall = oy1 - oyA;
|
|
353
|
+
const otA = Math.sqrt(oRise) / (Math.sqrt(oRise) + Math.sqrt(oFall));
|
|
354
|
+
const oC = oRise / (otA * otA);
|
|
355
|
+
const oZlo = 6;
|
|
356
|
+
const oZ0 = 0.3;
|
|
357
|
+
const oZ1 = 0.6;
|
|
358
|
+
const FLYo = 0.84;
|
|
359
|
+
|
|
360
|
+
const kf: Keyframe[] = [
|
|
361
|
+
{ offset: 0, transform: pHide, opacity: 0 },
|
|
362
|
+
{
|
|
363
|
+
offset: o(ot),
|
|
364
|
+
transform: pose(lx * 0.03, oy0, oZlo, 0, 0.45, 0.45),
|
|
365
|
+
opacity: 0,
|
|
366
|
+
easing: 'linear',
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
for (let sI = 1; sI <= SAMP; sI++) {
|
|
370
|
+
const s = sI / SAMP;
|
|
371
|
+
const oy = oyA + oC * (s - otA) * (s - otA);
|
|
372
|
+
const ox = lx * (0.03 + 0.97 * s);
|
|
373
|
+
const oz = oZlo + (landZ - oZlo) * smooth((s - oZ0) / (oZ1 - oZ0));
|
|
374
|
+
const orz = wonky * s + spin * Math.sin(Math.PI * s) * 0.4;
|
|
375
|
+
const osc = s < 0.1 ? 0.45 + 5.5 * s : 1;
|
|
376
|
+
kf.push({
|
|
377
|
+
offset: o(ot + f * FLYo * s),
|
|
378
|
+
transform: pose(ox, oy, oz, orz, osc, osc),
|
|
379
|
+
opacity: Math.min(1, s / 0.05),
|
|
380
|
+
easing: 'linear',
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
kf.push({
|
|
384
|
+
offset: o(ot + f * 0.88),
|
|
385
|
+
transform: pose(lx, landY, landZ, wonky, 1.08, 0.84),
|
|
386
|
+
opacity: 1,
|
|
387
|
+
easing: 'cubic-bezier(.2,.7,.4,1)',
|
|
388
|
+
}); // contact + squash
|
|
389
|
+
kf.push({
|
|
390
|
+
offset: o(ot + f * 0.94),
|
|
391
|
+
transform: pose(lx, landY - 5, landZ, wonky * 0.35, 0.98, 1.05),
|
|
392
|
+
opacity: 1,
|
|
393
|
+
easing: 'ease-out',
|
|
394
|
+
}); // rebound
|
|
395
|
+
kf.push({
|
|
396
|
+
offset: o(ot + f),
|
|
397
|
+
transform: pose(lx, landY, landZ, 0, 1, 1),
|
|
398
|
+
opacity: 1,
|
|
399
|
+
easing: 'linear',
|
|
400
|
+
}); // settle
|
|
401
|
+
|
|
402
|
+
// PULL BACK IN: the same physics reversed — up in front, hang, then drop
|
|
403
|
+
// back down into the box.
|
|
404
|
+
const by0 = landY;
|
|
405
|
+
const byA = -hh - 70;
|
|
406
|
+
const by1 = -hh * 0.18;
|
|
407
|
+
const bRise = by0 - byA;
|
|
408
|
+
const bFall = by1 - byA;
|
|
409
|
+
const btA = Math.sqrt(bRise) / (Math.sqrt(bRise) + Math.sqrt(bFall));
|
|
410
|
+
const bC = bRise / (btA * btA);
|
|
411
|
+
const bZ0 = 0.4;
|
|
412
|
+
const bZ1 = 0.72;
|
|
413
|
+
kf.push({
|
|
414
|
+
offset: o(bt),
|
|
415
|
+
transform: pose(lx, landY, landZ, 0, 1, 1),
|
|
416
|
+
opacity: 1,
|
|
417
|
+
easing: 'linear',
|
|
418
|
+
});
|
|
419
|
+
for (let bI = 1; bI <= SAMP; bI++) {
|
|
420
|
+
const bs = bI / SAMP;
|
|
421
|
+
const by = byA + bC * (bs - btA) * (bs - btA);
|
|
422
|
+
const bx = lx * (1 - bs);
|
|
423
|
+
const bz = landZ - (landZ - 8) * smooth((bs - bZ0) / (bZ1 - bZ0));
|
|
424
|
+
const brz = -spin * Math.sin(Math.PI * bs) * 0.4;
|
|
425
|
+
const bsc = bs > 0.84 ? Math.max(0.12, 1 - 5.5 * (bs - 0.84)) : 1;
|
|
426
|
+
const bop = Math.max(0, Math.min(1, 1 - (bs - 0.52) / 0.2));
|
|
427
|
+
kf.push({
|
|
428
|
+
offset: o(bt + bf * bs),
|
|
429
|
+
transform: pose(bx, by, bz, brz, bsc, bsc),
|
|
430
|
+
opacity: bop,
|
|
431
|
+
easing: 'linear',
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
kf.push({ offset: o(bt + bf), transform: pHide, opacity: 0 });
|
|
435
|
+
kf.push({ offset: 1, transform: pHide, opacity: 0 });
|
|
436
|
+
track(el.animate(kf, loop));
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// confetti eruption
|
|
440
|
+
fills.forEach((p, i) => {
|
|
441
|
+
const elf = fillRefs[i];
|
|
442
|
+
if (!elf) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const { dx, dz, rise } = p;
|
|
446
|
+
track(
|
|
447
|
+
elf.animate(
|
|
448
|
+
[
|
|
449
|
+
{
|
|
450
|
+
offset: 0,
|
|
451
|
+
transform: 'translate3d(0,0,0) scale(.3)',
|
|
452
|
+
opacity: 0,
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
offset: o(confStart),
|
|
456
|
+
transform: 'translate3d(0,0,0) scale(.3)',
|
|
457
|
+
opacity: 0,
|
|
458
|
+
easing: 'cubic-bezier(.2,.6,.3,1)',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
offset: o(confStart + confDur * 0.16),
|
|
462
|
+
transform:
|
|
463
|
+
'translate3d(' +
|
|
464
|
+
n1(dx * 0.3) +
|
|
465
|
+
'px,-8px,' +
|
|
466
|
+
n1(dz * 0.3) +
|
|
467
|
+
'px) scale(1)',
|
|
468
|
+
opacity: 1,
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
offset: o(confStart + confDur * 0.46),
|
|
472
|
+
transform:
|
|
473
|
+
'translate3d(' +
|
|
474
|
+
n1(dx) +
|
|
475
|
+
'px,' +
|
|
476
|
+
n1(-rise) +
|
|
477
|
+
'px,' +
|
|
478
|
+
n1(dz) +
|
|
479
|
+
'px) scale(1)',
|
|
480
|
+
opacity: 1,
|
|
481
|
+
easing: 'ease-in',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
offset: o(confStart + confDur * 0.95),
|
|
485
|
+
transform:
|
|
486
|
+
'translate3d(' +
|
|
487
|
+
n1(dx * 1.2) +
|
|
488
|
+
'px,26px,' +
|
|
489
|
+
n1(dz * 1.2) +
|
|
490
|
+
'px) scale(.5)',
|
|
491
|
+
opacity: 0,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
offset: 1,
|
|
495
|
+
transform:
|
|
496
|
+
'translate3d(' +
|
|
497
|
+
n1(dx * 1.2) +
|
|
498
|
+
'px,26px,' +
|
|
499
|
+
n1(dz * 1.2) +
|
|
500
|
+
'px) scale(.5)',
|
|
501
|
+
opacity: 0,
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
loop
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
if (glowRef) {
|
|
510
|
+
track(
|
|
511
|
+
glowRef.animate(
|
|
512
|
+
[
|
|
513
|
+
{ offset: 0, transform: 'scale(.4)', opacity: 0 },
|
|
514
|
+
{
|
|
515
|
+
offset: o(confStart),
|
|
516
|
+
transform: 'scale(.4)',
|
|
517
|
+
opacity: 0,
|
|
518
|
+
easing: 'ease-out',
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
offset: o(confStart + confDur * 0.2),
|
|
522
|
+
transform: 'scale(1)',
|
|
523
|
+
opacity: 0.9,
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
offset: o(confStart + confDur * 0.6),
|
|
527
|
+
transform: 'scale(1.15)',
|
|
528
|
+
opacity: 0.5,
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
offset: o(confStart + confDur * 0.92),
|
|
532
|
+
transform: 'scale(.6)',
|
|
533
|
+
opacity: 0,
|
|
534
|
+
},
|
|
535
|
+
{ offset: 1, transform: 'scale(.6)', opacity: 0 },
|
|
536
|
+
],
|
|
537
|
+
loop
|
|
538
|
+
)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// rotate the rear-flap message once per loop
|
|
543
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
544
|
+
if (messages.length > 1) {
|
|
545
|
+
msgIndex = (Math.random() * messages.length) | 0;
|
|
546
|
+
timer = setInterval(() => {
|
|
547
|
+
msgIndex = (msgIndex + 1) % messages.length;
|
|
548
|
+
}, T);
|
|
549
|
+
} else {
|
|
550
|
+
msgIndex = 0;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return () => {
|
|
554
|
+
anims.forEach((a) => a.cancel());
|
|
555
|
+
if (timer) {
|
|
556
|
+
clearInterval(timer);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
});
|
|
8
560
|
</script>
|
|
9
561
|
|
|
10
562
|
<div
|
|
11
|
-
|
|
563
|
+
bind:this={rootRef}
|
|
564
|
+
class="flowms-loader"
|
|
12
565
|
role="status"
|
|
13
566
|
aria-live="polite"
|
|
14
567
|
aria-label={label}
|
|
15
568
|
>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
569
|
+
<svg
|
|
570
|
+
width="0"
|
|
571
|
+
height="0"
|
|
572
|
+
style="position:absolute"
|
|
573
|
+
aria-hidden="true"
|
|
574
|
+
>
|
|
575
|
+
<filter
|
|
576
|
+
id="flowms-stamp"
|
|
577
|
+
x="-15%"
|
|
578
|
+
y="-15%"
|
|
579
|
+
width="130%"
|
|
580
|
+
height="130%"
|
|
581
|
+
>
|
|
582
|
+
<feTurbulence
|
|
583
|
+
type="fractalNoise"
|
|
584
|
+
baseFrequency="0.03 0.045"
|
|
585
|
+
numOctaves="3"
|
|
586
|
+
seed="6"
|
|
587
|
+
result="warp"
|
|
588
|
+
/>
|
|
589
|
+
<feDisplacementMap
|
|
590
|
+
in="SourceGraphic"
|
|
591
|
+
in2="warp"
|
|
592
|
+
scale="3.4"
|
|
593
|
+
xChannelSelector="R"
|
|
594
|
+
yChannelSelector="G"
|
|
595
|
+
result="rough"
|
|
596
|
+
/>
|
|
597
|
+
<feTurbulence
|
|
598
|
+
type="fractalNoise"
|
|
599
|
+
baseFrequency="0.5 0.55"
|
|
600
|
+
numOctaves="2"
|
|
601
|
+
seed="11"
|
|
602
|
+
result="grain"
|
|
603
|
+
/>
|
|
604
|
+
<feColorMatrix
|
|
605
|
+
in="grain"
|
|
606
|
+
type="matrix"
|
|
607
|
+
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.3 0 0 0 0.05"
|
|
608
|
+
result="grainA"
|
|
609
|
+
/>
|
|
610
|
+
<feComposite
|
|
611
|
+
in="rough"
|
|
612
|
+
in2="grainA"
|
|
613
|
+
operator="in"
|
|
614
|
+
/>
|
|
615
|
+
</filter>
|
|
616
|
+
</svg>
|
|
617
|
+
|
|
618
|
+
<div class="scene">
|
|
619
|
+
<div class="shadow"></div>
|
|
620
|
+
<div
|
|
621
|
+
bind:this={boxRef}
|
|
622
|
+
class="box"
|
|
623
|
+
>
|
|
624
|
+
<div class="face back"></div>
|
|
625
|
+
<div class="face left">
|
|
626
|
+
<svg
|
|
627
|
+
class="flo-logo"
|
|
628
|
+
viewBox="0 0 172 300"
|
|
629
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
630
|
+
aria-hidden="true"
|
|
631
|
+
>
|
|
632
|
+
<path
|
|
633
|
+
d="M1.54715 179.524C4.94675 176.764 11.6045 178.966 13.4335 180.132L13.44 180.125L29.0209 188.966L25.7231 191.604C13.44 200.508 18.7976 216.338 37.1425 217.942L54.0844 219.353L56.3929 219.471V268.062C56.3929 269.123 55.7069 270.574 54.5791 271.201L54.2886 271.366L5.44142 299.569C2.62507 300.967 0 298.711 0 296.429V185.886C0 183.535 0.318398 180.522 1.54715 179.524Z"
|
|
634
|
+
/>
|
|
635
|
+
<path
|
|
636
|
+
d="M62.3754 133.49C66.3328 131.155 71.4576 130.799 76.0086 133.424L111.784 154.082L128.952 144.169C130.963 143.008 133.536 144.01 134.228 146.226L153.092 206.478V206.498L153.099 206.517C153.547 208.845 151.674 210.976 149.306 210.818L56.3865 204.855V204.861L39.0593 203.747C35.4781 203.516 34.3703 198.78 37.4768 196.986L55.4036 186.632L19.0811 165.703L19.0746 165.71C17.3158 164.58 12.8616 162.913 7.58502 165.222L62.3754 133.477V133.49Z"
|
|
637
|
+
/>
|
|
638
|
+
<path
|
|
639
|
+
d="M19.2923 34.575C10.3024 29.1402 0 35.2873 0 45.7151V145.85C0 148.133 2.98124 150.626 5.44142 148.99L54.5725 120.622C55.4893 120.094 56.3863 118.544 56.3863 117.483V55.6152L19.2923 34.5816V34.575Z"
|
|
640
|
+
/>
|
|
641
|
+
<path
|
|
642
|
+
d="M61.9664 2.15088C65.9765 -0.408228 71.2992 -0.890029 76.0085 1.83397L170.036 52.5942C173.393 54.5333 172.147 59.361 167.992 59.3618L167.813 59.3483L132.375 60.3248C130.264 60.3248 129.605 62.329 129.229 64.2422L125.357 84.5503C124.658 88.3819 123.135 90.4663 120.252 88.956L56.3864 55.6215V55.615L19.2923 34.5817C17.5446 33.3614 12.7433 31.4565 7.51929 33.5988L61.9664 2.15088Z"
|
|
643
|
+
/>
|
|
644
|
+
</svg>
|
|
645
|
+
</div>
|
|
646
|
+
<div class="face right">
|
|
647
|
+
<svg
|
|
648
|
+
class="flo-logo"
|
|
649
|
+
viewBox="0 0 172 300"
|
|
650
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
651
|
+
aria-hidden="true"
|
|
652
|
+
>
|
|
653
|
+
<path
|
|
654
|
+
d="M1.54715 179.524C4.94675 176.764 11.6045 178.966 13.4335 180.132L13.44 180.125L29.0209 188.966L25.7231 191.604C13.44 200.508 18.7976 216.338 37.1425 217.942L54.0844 219.353L56.3929 219.471V268.062C56.3929 269.123 55.7069 270.574 54.5791 271.201L54.2886 271.366L5.44142 299.569C2.62507 300.967 0 298.711 0 296.429V185.886C0 183.535 0.318398 180.522 1.54715 179.524Z"
|
|
655
|
+
/>
|
|
656
|
+
<path
|
|
657
|
+
d="M62.3754 133.49C66.3328 131.155 71.4576 130.799 76.0086 133.424L111.784 154.082L128.952 144.169C130.963 143.008 133.536 144.01 134.228 146.226L153.092 206.478V206.498L153.099 206.517C153.547 208.845 151.674 210.976 149.306 210.818L56.3865 204.855V204.861L39.0593 203.747C35.4781 203.516 34.3703 198.78 37.4768 196.986L55.4036 186.632L19.0811 165.703L19.0746 165.71C17.3158 164.58 12.8616 162.913 7.58502 165.222L62.3754 133.477V133.49Z"
|
|
658
|
+
/>
|
|
659
|
+
<path
|
|
660
|
+
d="M19.2923 34.575C10.3024 29.1402 0 35.2873 0 45.7151V145.85C0 148.133 2.98124 150.626 5.44142 148.99L54.5725 120.622C55.4893 120.094 56.3863 118.544 56.3863 117.483V55.6152L19.2923 34.5816V34.575Z"
|
|
661
|
+
/>
|
|
662
|
+
<path
|
|
663
|
+
d="M61.9664 2.15088C65.9765 -0.408228 71.2992 -0.890029 76.0085 1.83397L170.036 52.5942C173.393 54.5333 172.147 59.361 167.992 59.3618L167.813 59.3483L132.375 60.3248C130.264 60.3248 129.605 62.329 129.229 64.2422L125.357 84.5503C124.658 88.3819 123.135 90.4663 120.252 88.956L56.3864 55.6215V55.615L19.2923 34.5817C17.5446 33.3614 12.7433 31.4565 7.51929 33.5988L61.9664 2.15088Z"
|
|
664
|
+
/>
|
|
665
|
+
</svg>
|
|
666
|
+
</div>
|
|
667
|
+
<div class="face floor"></div>
|
|
668
|
+
<div class="face front">
|
|
669
|
+
<div class="label-wrap">
|
|
670
|
+
<div class="label">Goods<br />Loading</div>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
674
|
+
<div class="vent">
|
|
675
|
+
<div
|
|
676
|
+
bind:this={glowRef}
|
|
677
|
+
class="glow"
|
|
678
|
+
></div>
|
|
679
|
+
{#each fills as _fill, i (i)}
|
|
680
|
+
<span
|
|
681
|
+
bind:this={fillRefs[i]}
|
|
682
|
+
class="fill"
|
|
683
|
+
></span>
|
|
684
|
+
{/each}
|
|
685
|
+
</div>
|
|
686
|
+
|
|
687
|
+
<div class="flap-unit s0 outer">
|
|
688
|
+
<div
|
|
689
|
+
bind:this={s0pivot}
|
|
690
|
+
class="flap-pivot"
|
|
691
|
+
>
|
|
692
|
+
<div class="flap-panel">
|
|
693
|
+
<div class="flap-face out"></div>
|
|
694
|
+
<div class="flap-face in"></div>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
<div class="flap-unit s2 outer">
|
|
699
|
+
<div
|
|
700
|
+
bind:this={s2pivot}
|
|
701
|
+
class="flap-pivot"
|
|
702
|
+
>
|
|
703
|
+
<div class="flap-panel">
|
|
704
|
+
<div class="flap-face out"></div>
|
|
705
|
+
<div class="flap-face in">
|
|
706
|
+
{#if messages.length}
|
|
707
|
+
<div class="flap-msg">{messages[msgIndex] ?? ''}</div>
|
|
708
|
+
{/if}
|
|
709
|
+
</div>
|
|
710
|
+
</div>
|
|
711
|
+
</div>
|
|
712
|
+
</div>
|
|
713
|
+
<div class="flap-unit s1 inner">
|
|
714
|
+
<div
|
|
715
|
+
bind:this={s1pivot}
|
|
716
|
+
class="flap-pivot"
|
|
717
|
+
>
|
|
718
|
+
<div class="flap-panel">
|
|
719
|
+
<div class="flap-face out"></div>
|
|
720
|
+
<div class="flap-face in"></div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="flap-unit s3 inner">
|
|
725
|
+
<div
|
|
726
|
+
bind:this={s3pivot}
|
|
727
|
+
class="flap-pivot"
|
|
728
|
+
>
|
|
729
|
+
<div class="flap-panel">
|
|
730
|
+
<div class="flap-face out"></div>
|
|
731
|
+
<div class="flap-face in"></div>
|
|
732
|
+
</div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
20
735
|
</div>
|
|
21
|
-
|
|
22
|
-
<div class="right"></div>
|
|
23
|
-
<div class="left"></div>
|
|
24
|
-
<div class="top"></div>
|
|
25
|
-
<div class="bottom"></div>
|
|
736
|
+
|
|
26
737
|
</div>
|
|
738
|
+
|
|
739
|
+
<!-- Letters live in their own perspective stage, drawn after (above) the box,
|
|
740
|
+
so they erupt from the opening and land in front of everything. -->
|
|
741
|
+
<div class="letterstage">
|
|
742
|
+
<div class="l3d">
|
|
743
|
+
{#each geom.letters as letter, i (i)}
|
|
744
|
+
<span
|
|
745
|
+
bind:this={letterRefs[i]}
|
|
746
|
+
class="letter"
|
|
747
|
+
style="font-size:{geom.fontSize}px"
|
|
748
|
+
>
|
|
749
|
+
{#each letter.layers as layer, k (k)}
|
|
750
|
+
<span
|
|
751
|
+
class="lyr"
|
|
752
|
+
style="transform:{layer.transform};color:{layer.color}"
|
|
753
|
+
>{letter.glyph}</span
|
|
754
|
+
>
|
|
755
|
+
{/each}
|
|
756
|
+
<span
|
|
757
|
+
class="lface"
|
|
758
|
+
style="transform:translate(-50%,-50%);color:{letterFace}"
|
|
759
|
+
>{letter.glyph}</span
|
|
760
|
+
>
|
|
761
|
+
</span>
|
|
762
|
+
{/each}
|
|
763
|
+
</div>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
<!-- Caption sits above the box and is the loader's last child, so it is never
|
|
767
|
+
hidden by the erupting letters. The dots are three always-present spans
|
|
768
|
+
whose opacity cycles, so the pill width stays fixed as they animate. -->
|
|
769
|
+
{#if showText}
|
|
770
|
+
<div class="caption">
|
|
771
|
+
<span class="caption-label">{captionText}</span><span
|
|
772
|
+
class="caption-dots"
|
|
773
|
+
aria-hidden="true"><span>.</span><span>.</span><span>.</span></span
|
|
774
|
+
>
|
|
775
|
+
</div>
|
|
776
|
+
{/if}
|
|
27
777
|
</div>
|
|
28
778
|
|
|
29
|
-
<style
|
|
30
|
-
|
|
31
|
-
|
|
779
|
+
<style>@charset "UTF-8";
|
|
780
|
+
.flowms-loader {
|
|
781
|
+
--w: 120px; /* footprint width / depth (square) */
|
|
782
|
+
--d: 120px;
|
|
783
|
+
--h: 110px; /* box height */
|
|
784
|
+
--open: 130deg; /* how far the flaps splay open */
|
|
785
|
+
--kraft-front: #cda770;
|
|
786
|
+
--kraft-side: #b8915a;
|
|
787
|
+
--kraft-back: #ab8650;
|
|
788
|
+
--kraft-flap: #d4af77;
|
|
789
|
+
--kraft-flap2: #c6a065;
|
|
790
|
+
--kraft-inner: #8a6b43;
|
|
791
|
+
--seam: rgba(0, 0, 0, 0.14);
|
|
792
|
+
--ink: #3c4045;
|
|
793
|
+
/* subtle white confetti — shades of white/off-white */
|
|
794
|
+
--fill-1: #ffffff;
|
|
795
|
+
--fill-2: #eef1f4;
|
|
796
|
+
--fill-3: #f8fafb;
|
|
797
|
+
--fill-4: #e2e7ec;
|
|
798
|
+
--fill-5: #fbfcfd;
|
|
799
|
+
--hw: calc(var(--w) / 2);
|
|
800
|
+
--hd: calc(var(--d) / 2);
|
|
801
|
+
--hh: calc(var(--h) / 2);
|
|
802
|
+
--y-outer: calc(var(--hh) * -1 + 0.5px);
|
|
803
|
+
--y-inner: calc(var(--hh) * -1 + 1.5px);
|
|
804
|
+
/* Push the box (and its letters) down within the footprint so the caption
|
|
805
|
+
pill has clear headroom above the opened flaps. */
|
|
806
|
+
--stage-shift: 40px;
|
|
807
|
+
display: grid;
|
|
808
|
+
place-items: center;
|
|
32
809
|
position: relative;
|
|
33
|
-
|
|
810
|
+
width: 300px;
|
|
811
|
+
height: 300px;
|
|
34
812
|
}
|
|
35
|
-
.
|
|
36
|
-
width:
|
|
37
|
-
height:
|
|
38
|
-
|
|
813
|
+
.flowms-loader .scene {
|
|
814
|
+
width: var(--w);
|
|
815
|
+
height: var(--h);
|
|
816
|
+
translate: 0 var(--stage-shift);
|
|
817
|
+
perspective: 820px;
|
|
818
|
+
perspective-origin: 50% 40%;
|
|
39
819
|
transform-style: preserve-3d;
|
|
820
|
+
position: relative;
|
|
821
|
+
}
|
|
822
|
+
.flowms-loader .shadow {
|
|
823
|
+
position: absolute;
|
|
824
|
+
left: 50%;
|
|
825
|
+
top: calc(50% + var(--hh) + 24px);
|
|
826
|
+
width: calc(var(--w) * 1.1);
|
|
827
|
+
height: 22px;
|
|
828
|
+
transform: translate(-50%, -50%);
|
|
829
|
+
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 70%);
|
|
830
|
+
border-radius: 50%;
|
|
40
831
|
}
|
|
41
832
|
@media (prefers-reduced-motion: no-preference) {
|
|
42
|
-
.
|
|
43
|
-
animation:
|
|
833
|
+
.flowms-loader .shadow {
|
|
834
|
+
animation: flowms-shadow 4.2s ease-in-out infinite;
|
|
44
835
|
}
|
|
45
836
|
}
|
|
46
|
-
.
|
|
47
|
-
width: var(--loading-animation-size, 100px);
|
|
48
|
-
height: var(--loading-animation-size, 100px);
|
|
49
|
-
display: block;
|
|
837
|
+
.flowms-loader .box {
|
|
50
838
|
position: absolute;
|
|
839
|
+
inset: 0;
|
|
840
|
+
transform-style: preserve-3d;
|
|
841
|
+
}
|
|
842
|
+
.flowms-loader .face {
|
|
843
|
+
position: absolute;
|
|
844
|
+
top: 50%;
|
|
845
|
+
left: 50%;
|
|
51
846
|
box-sizing: border-box;
|
|
52
|
-
|
|
847
|
+
backface-visibility: visible;
|
|
848
|
+
transform-style: preserve-3d;
|
|
849
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), inset 0 0 18px rgba(0, 0, 0, 0.14);
|
|
850
|
+
}
|
|
851
|
+
.flowms-loader .front {
|
|
852
|
+
width: var(--w);
|
|
853
|
+
height: var(--h);
|
|
854
|
+
margin-left: calc(var(--w) / -2);
|
|
855
|
+
margin-top: calc(var(--h) / -2);
|
|
856
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.1333333333), rgba(0, 0, 0, 0.0941176471)), var(--kraft-front);
|
|
857
|
+
transform: translateZ(var(--hd));
|
|
858
|
+
}
|
|
859
|
+
.flowms-loader .back {
|
|
860
|
+
width: var(--w);
|
|
861
|
+
height: var(--h);
|
|
862
|
+
margin-left: calc(var(--w) / -2);
|
|
863
|
+
margin-top: calc(var(--h) / -2);
|
|
864
|
+
background: var(--kraft-back);
|
|
865
|
+
transform: rotateY(180deg) translateZ(var(--hd));
|
|
866
|
+
}
|
|
867
|
+
.flowms-loader .right {
|
|
868
|
+
width: var(--d);
|
|
869
|
+
height: var(--h);
|
|
870
|
+
margin-left: calc(var(--d) / -2);
|
|
871
|
+
margin-top: calc(var(--h) / -2);
|
|
872
|
+
background: linear-gradient(160deg, rgba(0, 0, 0, 0.062745098), rgba(0, 0, 0, 0.1882352941)), var(--kraft-side);
|
|
873
|
+
transform: rotateY(90deg) translateZ(var(--hw));
|
|
874
|
+
}
|
|
875
|
+
.flowms-loader .left {
|
|
876
|
+
width: var(--d);
|
|
877
|
+
height: var(--h);
|
|
878
|
+
margin-left: calc(var(--d) / -2);
|
|
879
|
+
margin-top: calc(var(--h) / -2);
|
|
880
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.0941176471), rgba(0, 0, 0, 0.1333333333)), var(--kraft-side);
|
|
881
|
+
transform: rotateY(-90deg) translateZ(var(--hw));
|
|
882
|
+
}
|
|
883
|
+
.flowms-loader .floor {
|
|
884
|
+
width: var(--w);
|
|
885
|
+
height: var(--d);
|
|
886
|
+
margin-left: calc(var(--w) / -2);
|
|
887
|
+
margin-top: calc(var(--d) / -2);
|
|
888
|
+
background: var(--kraft-inner);
|
|
889
|
+
transform: rotateX(-90deg) translateZ(var(--hh));
|
|
890
|
+
}
|
|
891
|
+
.flowms-loader {
|
|
892
|
+
/* stamped front label */
|
|
893
|
+
}
|
|
894
|
+
.flowms-loader .label-wrap {
|
|
895
|
+
position: absolute;
|
|
896
|
+
left: 50%;
|
|
897
|
+
top: 45%;
|
|
898
|
+
transform: translate(-50%, -50%) translateZ(0.1px);
|
|
53
899
|
backface-visibility: hidden;
|
|
54
|
-
|
|
900
|
+
pointer-events: none;
|
|
901
|
+
}
|
|
902
|
+
.flowms-loader .label {
|
|
903
|
+
display: inline-block;
|
|
904
|
+
padding: 6px 12px 5px;
|
|
905
|
+
border: 3px solid var(--ink);
|
|
906
|
+
border-radius: 3px;
|
|
907
|
+
font: 800 15px/1.05 "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
|
|
908
|
+
letter-spacing: 1.5px;
|
|
909
|
+
text-transform: uppercase;
|
|
910
|
+
text-align: center;
|
|
911
|
+
white-space: nowrap;
|
|
912
|
+
color: var(--ink);
|
|
913
|
+
opacity: 0.72;
|
|
914
|
+
mix-blend-mode: multiply;
|
|
915
|
+
transform: rotate(-2.6deg);
|
|
916
|
+
filter: url(#flowms-stamp);
|
|
55
917
|
}
|
|
56
|
-
.
|
|
918
|
+
.flowms-loader {
|
|
919
|
+
/* positive message printed inside the rear flap */
|
|
920
|
+
}
|
|
921
|
+
.flowms-loader .flap-msg {
|
|
922
|
+
position: absolute;
|
|
923
|
+
inset: 0;
|
|
57
924
|
display: flex;
|
|
58
|
-
height: 100%;
|
|
59
|
-
padding: 10px;
|
|
60
925
|
align-items: center;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
926
|
+
justify-content: center;
|
|
927
|
+
text-align: center;
|
|
928
|
+
padding: 4px 9px;
|
|
929
|
+
box-sizing: border-box;
|
|
930
|
+
font: 800 13px/1.14 "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
|
|
931
|
+
letter-spacing: 0.8px;
|
|
932
|
+
text-transform: uppercase;
|
|
933
|
+
color: var(--ink);
|
|
934
|
+
opacity: 0.8;
|
|
935
|
+
transform: rotate(180deg); /* reads upright on the inner face when open */
|
|
936
|
+
pointer-events: none;
|
|
66
937
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
0%, 10% {
|
|
70
|
-
transform: rotateX(0deg) rotateY(360deg) rotateZ(0deg);
|
|
71
|
-
}
|
|
72
|
-
10%, 100% {
|
|
73
|
-
transform: rotateX(0deg) rotateY(360deg) rotateZ(0deg);
|
|
74
|
-
}
|
|
75
|
-
35% {
|
|
76
|
-
transform: rotateX(90deg) rotateY(270deg) rotateZ(180deg);
|
|
77
|
-
}
|
|
78
|
-
60% {
|
|
79
|
-
transform: rotateX(180deg) rotateY(180deg) rotateZ(0deg);
|
|
80
|
-
}
|
|
81
|
-
85% {
|
|
82
|
-
transform: rotateX(-90deg) rotateY(90deg) rotateZ(0deg);
|
|
83
|
-
}
|
|
938
|
+
.flowms-loader {
|
|
939
|
+
/* Flo logo embossed on the side faces */
|
|
84
940
|
}
|
|
85
|
-
.
|
|
86
|
-
|
|
941
|
+
.flowms-loader .flo-logo {
|
|
942
|
+
position: absolute;
|
|
943
|
+
left: 50%;
|
|
944
|
+
top: 50%;
|
|
945
|
+
height: 56%;
|
|
946
|
+
transform: translate(-50%, -50%) translateZ(0.1px);
|
|
947
|
+
backface-visibility: hidden;
|
|
948
|
+
pointer-events: none;
|
|
949
|
+
filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.42)) drop-shadow(0 -1px 0 rgba(0, 0, 0, 0.38));
|
|
87
950
|
}
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
background: rgba(216, 208, 199, 0.5);
|
|
951
|
+
.flowms-loader .flo-logo :global(path) {
|
|
952
|
+
fill: rgba(64, 45, 24, 0.16);
|
|
91
953
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
background: rgba(216, 208, 199, 0.35);
|
|
954
|
+
.flowms-loader {
|
|
955
|
+
/* ---- flaps ---- */
|
|
95
956
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
957
|
+
.flowms-loader .flap-unit {
|
|
958
|
+
position: absolute;
|
|
959
|
+
top: 50%;
|
|
960
|
+
left: 50%;
|
|
961
|
+
transform-style: preserve-3d;
|
|
99
962
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
background: rgba(216, 208, 199, 0.25);
|
|
963
|
+
.flowms-loader .flap-unit.s0 {
|
|
964
|
+
transform: rotateY(0deg) translateY(var(--y-outer)) translateZ(var(--hw));
|
|
103
965
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
background: rgba(216, 208, 199, 0.75);
|
|
966
|
+
.flowms-loader .flap-unit.s2 {
|
|
967
|
+
transform: rotateY(180deg) translateY(var(--y-outer)) translateZ(var(--hw));
|
|
107
968
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
transform: rotateY(0deg) translateZ(50px);
|
|
969
|
+
.flowms-loader .flap-unit.s1 {
|
|
970
|
+
transform: rotateY(90deg) translateY(var(--y-inner)) translateZ(var(--hw));
|
|
111
971
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
transform: rotateX(180deg) translateZ(50px);
|
|
972
|
+
.flowms-loader .flap-unit.s3 {
|
|
973
|
+
transform: rotateY(270deg) translateY(var(--y-inner)) translateZ(var(--hw));
|
|
115
974
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
transform:
|
|
975
|
+
.flowms-loader .flap-pivot {
|
|
976
|
+
transform-style: preserve-3d;
|
|
977
|
+
transform-origin: 0 0;
|
|
119
978
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
979
|
+
.flowms-loader .flap-panel {
|
|
980
|
+
position: absolute;
|
|
981
|
+
width: var(--w);
|
|
982
|
+
height: var(--hw);
|
|
983
|
+
margin-left: calc(var(--w) / -2);
|
|
984
|
+
transform-origin: 50% 0;
|
|
985
|
+
transform: rotateX(-90deg);
|
|
986
|
+
transform-style: preserve-3d;
|
|
123
987
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
988
|
+
.flowms-loader .flap-face {
|
|
989
|
+
position: absolute;
|
|
990
|
+
inset: 0;
|
|
991
|
+
box-sizing: border-box;
|
|
992
|
+
backface-visibility: hidden;
|
|
993
|
+
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
|
|
994
|
+
}
|
|
995
|
+
.flowms-loader .flap-face.out {
|
|
996
|
+
transform: rotateY(180deg) translateZ(0.4px);
|
|
997
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.1490196078), rgba(0, 0, 0, 0.1098039216)), var(--kraft-flap);
|
|
998
|
+
}
|
|
999
|
+
.flowms-loader .flap-face.in {
|
|
1000
|
+
transform: translateZ(0.4px);
|
|
1001
|
+
background: linear-gradient(160deg, rgba(0, 0, 0, 0.0862745098), rgba(0, 0, 0, 0.1568627451)), var(--kraft-flap2);
|
|
1002
|
+
}
|
|
1003
|
+
.flowms-loader .flap-unit.outer .flap-face.out::after {
|
|
1004
|
+
content: "";
|
|
1005
|
+
position: absolute;
|
|
1006
|
+
left: 0;
|
|
1007
|
+
right: 0;
|
|
1008
|
+
bottom: 0;
|
|
1009
|
+
height: 8px;
|
|
1010
|
+
background: var(--seam);
|
|
1011
|
+
}
|
|
1012
|
+
.flowms-loader {
|
|
1013
|
+
/* eruption vent */
|
|
1014
|
+
}
|
|
1015
|
+
.flowms-loader .vent {
|
|
1016
|
+
position: absolute;
|
|
1017
|
+
top: 50%;
|
|
1018
|
+
left: 50%;
|
|
1019
|
+
transform-style: preserve-3d;
|
|
1020
|
+
transform: translateY(calc(var(--hh) * -1));
|
|
1021
|
+
}
|
|
1022
|
+
.flowms-loader .glow {
|
|
1023
|
+
position: absolute;
|
|
1024
|
+
left: 0;
|
|
1025
|
+
top: 0;
|
|
1026
|
+
width: 96px;
|
|
1027
|
+
height: 96px;
|
|
1028
|
+
margin-left: -48px;
|
|
1029
|
+
margin-top: -48px;
|
|
1030
|
+
border-radius: 50%;
|
|
1031
|
+
opacity: 0;
|
|
1032
|
+
background: radial-gradient(circle, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0.3) 35%, rgba(255, 255, 255, 0) 70%);
|
|
1033
|
+
}
|
|
1034
|
+
.flowms-loader .fill {
|
|
1035
|
+
position: absolute;
|
|
1036
|
+
left: 0;
|
|
1037
|
+
top: 0;
|
|
1038
|
+
width: 13px;
|
|
1039
|
+
height: 13px;
|
|
1040
|
+
margin-left: -6.5px;
|
|
1041
|
+
margin-top: -6.5px;
|
|
1042
|
+
border-radius: 3px;
|
|
1043
|
+
opacity: 0;
|
|
1044
|
+
/* faint edge so the white pieces stay visible on light backgrounds */
|
|
1045
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
|
|
1046
|
+
}
|
|
1047
|
+
.flowms-loader .fill:nth-child(5n+1) {
|
|
1048
|
+
background: var(--fill-1);
|
|
1049
|
+
}
|
|
1050
|
+
.flowms-loader .fill:nth-child(5n+2) {
|
|
1051
|
+
background: var(--fill-2);
|
|
1052
|
+
}
|
|
1053
|
+
.flowms-loader .fill:nth-child(5n+3) {
|
|
1054
|
+
background: var(--fill-3);
|
|
1055
|
+
}
|
|
1056
|
+
.flowms-loader .fill:nth-child(5n+4) {
|
|
1057
|
+
background: var(--fill-4);
|
|
1058
|
+
}
|
|
1059
|
+
.flowms-loader .fill:nth-child(5n+5) {
|
|
1060
|
+
background: var(--fill-5);
|
|
1061
|
+
}
|
|
1062
|
+
.flowms-loader .fill:nth-child(3n) {
|
|
1063
|
+
width: 9px;
|
|
1064
|
+
height: 9px;
|
|
1065
|
+
margin-left: -4.5px;
|
|
1066
|
+
margin-top: -4.5px;
|
|
1067
|
+
border-radius: 50%;
|
|
1068
|
+
}
|
|
1069
|
+
.flowms-loader {
|
|
1070
|
+
/* 3D word letters: own perspective stage, sits in front of the box and
|
|
1071
|
+
matches its resting angle. Drawn above the box, so letters land in front
|
|
1072
|
+
of everything — never behind or through the front flap. */
|
|
1073
|
+
}
|
|
1074
|
+
.flowms-loader .letterstage {
|
|
1075
|
+
position: absolute;
|
|
1076
|
+
left: 50%;
|
|
1077
|
+
top: calc(50% + var(--stage-shift)); /* track the shifted box */
|
|
1078
|
+
width: var(--w);
|
|
1079
|
+
height: var(--h);
|
|
1080
|
+
transform: translate(-50%, -50%);
|
|
1081
|
+
perspective: 820px;
|
|
1082
|
+
perspective-origin: 50% 40%;
|
|
1083
|
+
pointer-events: none;
|
|
1084
|
+
}
|
|
1085
|
+
.flowms-loader .l3d {
|
|
1086
|
+
position: absolute;
|
|
1087
|
+
inset: 0;
|
|
1088
|
+
transform-style: preserve-3d;
|
|
1089
|
+
transform: rotateX(-26deg) rotateY(-32deg); /* the box's resting angle */
|
|
1090
|
+
}
|
|
1091
|
+
.flowms-loader .letter {
|
|
1092
|
+
position: absolute;
|
|
1093
|
+
left: 50%;
|
|
1094
|
+
top: 50%;
|
|
1095
|
+
transform: translate(-50%, -50%) scale(0);
|
|
1096
|
+
transform-origin: 50% 50%;
|
|
1097
|
+
transform-style: preserve-3d;
|
|
1098
|
+
font-family: "Arial Black", "Helvetica Neue", system-ui, sans-serif;
|
|
1099
|
+
font-weight: 900;
|
|
1100
|
+
line-height: 1;
|
|
1101
|
+
opacity: 0;
|
|
1102
|
+
}
|
|
1103
|
+
.flowms-loader .letter .lface,
|
|
1104
|
+
.flowms-loader .letter .lyr {
|
|
1105
|
+
position: absolute;
|
|
1106
|
+
left: 0;
|
|
1107
|
+
top: 0;
|
|
1108
|
+
}
|
|
1109
|
+
.flowms-loader {
|
|
1110
|
+
/* Caption pill: sits above the box and on top of every other layer. */
|
|
1111
|
+
}
|
|
1112
|
+
.flowms-loader .caption {
|
|
1113
|
+
position: absolute;
|
|
1114
|
+
left: 50%;
|
|
1115
|
+
top: 10px; /* sits in the headroom above the opened flaps */
|
|
1116
|
+
transform: translateX(-50%);
|
|
1117
|
+
white-space: nowrap;
|
|
1118
|
+
padding: 7px 16px;
|
|
1119
|
+
border-radius: 999px;
|
|
1120
|
+
background: rgba(255, 255, 255, 0.82);
|
|
1121
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
1122
|
+
-webkit-backdrop-filter: blur(3px);
|
|
1123
|
+
backdrop-filter: blur(3px);
|
|
1124
|
+
font: 600 14px/1.2 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1125
|
+
color: #44515a;
|
|
1126
|
+
letter-spacing: 0.2px;
|
|
1127
|
+
}
|
|
1128
|
+
.flowms-loader {
|
|
1129
|
+
/* Three always-present dots; only their opacity animates, so the pill never
|
|
1130
|
+
reflows or changes width as they cycle. */
|
|
1131
|
+
}
|
|
1132
|
+
.flowms-loader .caption-dots {
|
|
1133
|
+
display: inline-block;
|
|
1134
|
+
text-align: left;
|
|
1135
|
+
}
|
|
1136
|
+
.flowms-loader .caption-dots span:nth-child(2),
|
|
1137
|
+
.flowms-loader .caption-dots span:nth-child(3) {
|
|
1138
|
+
opacity: 1; /* shown when motion is reduced (static "...") */
|
|
1139
|
+
}
|
|
1140
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
1141
|
+
.flowms-loader .caption-dots span:nth-child(2) {
|
|
1142
|
+
animation: flowms-dot2 1.5s infinite;
|
|
1143
|
+
}
|
|
1144
|
+
.flowms-loader .caption-dots span:nth-child(3) {
|
|
1145
|
+
animation: flowms-dot3 1.5s infinite;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1149
|
+
.flowms-loader .box {
|
|
1150
|
+
animation: flowms-rest 2.4s ease-in-out infinite;
|
|
1151
|
+
}
|
|
127
1152
|
}
|
|
128
1153
|
|
|
129
|
-
|
|
130
|
-
|
|
1154
|
+
@keyframes flowms-shadow {
|
|
1155
|
+
0%, 100% {
|
|
1156
|
+
transform: translate(-50%, -50%) scale(1, 1);
|
|
1157
|
+
opacity: 0.5;
|
|
1158
|
+
}
|
|
1159
|
+
50% {
|
|
1160
|
+
transform: translate(-50%, -50%) scale(0.93, 1);
|
|
1161
|
+
opacity: 0.45;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
@keyframes flowms-rest {
|
|
1165
|
+
0%, 100% {
|
|
1166
|
+
transform: rotateX(-22deg) rotateY(-28deg);
|
|
1167
|
+
}
|
|
1168
|
+
50% {
|
|
1169
|
+
transform: rotateX(-22deg) rotateY(-22deg);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
/* Dots reveal in sequence (".", "..", "...") then reset — width never changes
|
|
1173
|
+
because all three glyphs always occupy space; only opacity toggles. */
|
|
1174
|
+
@keyframes flowms-dot2 {
|
|
1175
|
+
0%, 32.9% {
|
|
1176
|
+
opacity: 0;
|
|
1177
|
+
}
|
|
1178
|
+
33%, 100% {
|
|
1179
|
+
opacity: 1;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
@keyframes flowms-dot3 {
|
|
1183
|
+
0%, 65.9% {
|
|
1184
|
+
opacity: 0;
|
|
1185
|
+
}
|
|
1186
|
+
66%, 100% {
|
|
1187
|
+
opacity: 1;
|
|
1188
|
+
}
|
|
131
1189
|
}</style>
|