@leftium/logo 0.0.1

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 ADDED
File without changes
@@ -0,0 +1,502 @@
1
+ <script module>
2
+ import { dev } from '$app/environment';
3
+
4
+ // Simple shared variable - no store needed!
5
+ let globalAnimated = !dev;
6
+
7
+ // Export control functions instead of the variable
8
+ export function toggleAnimation() {
9
+ globalAnimated = !globalAnimated;
10
+ updateAllInstances();
11
+ }
12
+
13
+ export function setAnimated(value: boolean) {
14
+ globalAnimated = value;
15
+ updateAllInstances();
16
+ }
17
+
18
+ // Keep track of all instances for updates
19
+ let instances = new Set<() => void>();
20
+
21
+ function updateAllInstances() {
22
+ instances.forEach((update) => update());
23
+ }
24
+
25
+ export function registerInstance(updateFn: () => void) {
26
+ instances.add(updateFn);
27
+ return () => instances.delete(updateFn);
28
+ }
29
+ </script>
30
+
31
+ <script lang="ts">
32
+ import { onDestroy } from 'svelte';
33
+ import type { Attachment } from 'svelte/attachments';
34
+ import { Ripples, type RipplesOptions } from './webgl-ripples/webgl-ripples.js';
35
+
36
+ import logoGlow from './assets/logo-parts/glow.svg';
37
+ import logoLigature from './assets/logo-parts/ligature.svg';
38
+ import logoShadow from './assets/logo-parts/shadow.svg';
39
+ import logoSquare from './assets/logo-parts/square.svg?inline';
40
+
41
+ interface Props {
42
+ size?: string;
43
+ toggleAnimationWithShift?: boolean;
44
+ ripplesOptions?: RipplesOptions;
45
+ boundingBox?: 'square' | 'default' | 'cropped' | 'encircled';
46
+ class?: string;
47
+ onClick?: (event: MouseEvent | KeyboardEvent) => void;
48
+ [key: string]: unknown; // Allow any additional props
49
+ }
50
+
51
+ let {
52
+ size = '100%',
53
+ toggleAnimationWithShift = false,
54
+ ripplesOptions: ripplesOptionsProp = {},
55
+ boundingBox = 'default',
56
+ class: className = '',
57
+ onClick = undefined,
58
+ ...restProps
59
+ }: Props = $props();
60
+
61
+ // Use global animation state shared across ALL instances
62
+ let animated = $state(globalAnimated);
63
+
64
+ // Register for updates from other instances
65
+ let unregister = registerInstance(() => {
66
+ animated = globalAnimated;
67
+ });
68
+
69
+ // Clean up on destroy
70
+ onDestroy(unregister);
71
+
72
+ let ripples: Ripples | null;
73
+ let animatedElements: Element[];
74
+ let animate: (time: number) => void;
75
+
76
+ // Reactive effect to handle animation state changes from global store
77
+ $effect(() => {
78
+ if (animated) {
79
+ // Start animation - only if elements are available
80
+ if (animatedElements && animate) {
81
+ for (const el of animatedElements) {
82
+ (el as HTMLElement).style.transition = '';
83
+ }
84
+ requestAnimationFrame(animate);
85
+ }
86
+ } else {
87
+ // Stop animation - only if elements are available
88
+ if (animatedElements) {
89
+ if (ripples) {
90
+ ripples.destroy();
91
+ ripples = null;
92
+ }
93
+
94
+ // Reset transforms with smooth transition
95
+ for (const el of animatedElements) {
96
+ (el as HTMLElement).style.transition = 'transform 0.8s cubic-bezier(0.4, 0, 0.2, 1)';
97
+ (el as HTMLElement).style.transform = 'translate(0%, 0%)';
98
+ }
99
+ }
100
+ }
101
+ });
102
+
103
+ const logoAnimation: Attachment = (element) => {
104
+ animatedElements = [...element.children].filter((child) => child.classList.contains('animate'));
105
+ const ripplesElement = element.getElementsByClassName('ripples')[0] as HTMLElement | undefined;
106
+
107
+ // Higher resolution for smaller sizes to avoid blurriness
108
+ // For small logos, use closer to 1:1 ratio, for large logos cap at 512
109
+ const elementSize = ripplesElement?.offsetWidth || 100;
110
+ const resolution = !ripplesElement ? 512 : Math.min(512, Math.max(128, elementSize * 0.8));
111
+
112
+ // Scale drop radius based on element size: 15px at 500px down to 2px at 62px
113
+ // Linear interpolation from 62px:2px to 800px:23.36px, capped at 20px
114
+ // Formula: 2 + ((elementSize - 62) / (800 - 62)) * (23.36 - 2)
115
+ const scaledDropRadius = Math.min(
116
+ 20,
117
+ Math.max(2, 2 + ((elementSize - 62) / (800 - 62)) * 21.36)
118
+ );
119
+
120
+ // Scale wave propagation based on element size: 2.0 at 500px down to 0.2 at 125px
121
+ // Linear interpolation for sizes 500px down to 125px, then clamp at 0.2 for smaller
122
+ const scaledWavePropagation = Math.max(
123
+ 0.2,
124
+ Math.min(2.0, 2.0 - ((500 - elementSize) / (500 - 125)) * 1.5)
125
+ );
126
+
127
+ const DEFAULT_RIPPLES_OPTIONS = {
128
+ resolution,
129
+ dropRadius: scaledDropRadius,
130
+ perturbance: 0.04,
131
+ // Ripple tuning parameters
132
+ wavePropagation: scaledWavePropagation,
133
+ dampening: 0.997 // Ripple dampening (0.999 = very long lasting, 0.995 = normal, 0.99 = quick fade)
134
+ };
135
+ const rippleOptions = { ...DEFAULT_RIPPLES_OPTIONS, ...ripplesOptionsProp };
136
+
137
+ // Set up ResizeObserver to handle component resizing
138
+ let resizeObserver: ResizeObserver | null = null;
139
+ let lastWidth = ripplesElement?.offsetWidth;
140
+ let lastHeight = ripplesElement?.offsetHeight;
141
+ let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
142
+
143
+ if (ripplesElement && typeof ResizeObserver !== 'undefined') {
144
+ resizeObserver = new ResizeObserver(() => {
145
+ const currentWidth = ripplesElement.offsetWidth;
146
+ const currentHeight = ripplesElement.offsetHeight;
147
+
148
+ // Only recreate if size actually changed significantly (more than 5px)
149
+ const widthChanged = Math.abs(currentWidth - (lastWidth || 0)) > 5;
150
+ const heightChanged = Math.abs(currentHeight - (lastHeight || 0)) > 5;
151
+
152
+ if (widthChanged || heightChanged) {
153
+ lastWidth = currentWidth;
154
+ lastHeight = currentHeight;
155
+
156
+ // Debounce the recreation to avoid too many WebGL contexts
157
+ if (resizeTimeout) {
158
+ clearTimeout(resizeTimeout);
159
+ }
160
+
161
+ resizeTimeout = setTimeout(() => {
162
+ if (animated && ripplesElement) {
163
+ // Destroy old instance first
164
+ if (ripples) {
165
+ try {
166
+ ripples.destroy();
167
+ } catch (e) {
168
+ console.error('Error destroying ripples:', e);
169
+ }
170
+ ripples = null;
171
+ }
172
+
173
+ // Wait a frame before creating new instance to ensure cleanup
174
+ requestAnimationFrame(() => {
175
+ if (!ripples && animated && ripplesElement) {
176
+ // Higher resolution for smaller sizes to avoid blurriness
177
+ const newResolution = Math.min(512, Math.max(128, currentWidth * 0.8));
178
+ // Scale drop radius and wave propagation for new size
179
+ // Linear interpolation from 62px:2px to 800px:23.36px, capped at 20px
180
+ const newScaledDropRadius = Math.min(
181
+ 20,
182
+ Math.max(2, 2 + ((currentWidth - 62) / (800 - 62)) * 21.36)
183
+ );
184
+ const newScaledWavePropagation = Math.max(
185
+ 0.2,
186
+ Math.min(2.0, 2.0 - ((500 - currentWidth) / (500 - 125)) * 1.5)
187
+ );
188
+ const newRippleOptions = {
189
+ ...rippleOptions,
190
+ resolution: newResolution,
191
+ dropRadius: newScaledDropRadius,
192
+ wavePropagation: newScaledWavePropagation
193
+ };
194
+
195
+ try {
196
+ ripples = new Ripples(ripplesElement, newRippleOptions);
197
+ } catch (e) {
198
+ console.error('Error creating ripples:', e);
199
+ }
200
+ }
201
+ });
202
+ }
203
+ }, 100); // 100ms debounce
204
+ }
205
+ });
206
+ resizeObserver.observe(ripplesElement);
207
+ }
208
+
209
+ let angle = $state(0);
210
+ let lastDropTime = $state(0);
211
+ let lastTime = 0;
212
+
213
+ animate = (time: number) => {
214
+ // Exit if we shouldn't be running
215
+ if (!animated) {
216
+ return;
217
+ }
218
+ const deltaTime = lastTime ? time - lastTime : 0;
219
+ lastTime = time;
220
+
221
+ // Create ripples if needed (and not already being created by resize)
222
+ if (animated && !ripples && ripplesElement && !resizeTimeout) {
223
+ try {
224
+ ripples = new Ripples(ripplesElement, rippleOptions);
225
+ } catch (e) {
226
+ console.error('Error creating ripples in animation loop:', e);
227
+ }
228
+ }
229
+
230
+ // Automatic drops (only when animated)
231
+ if (animated && lastDropTime !== null && ripples && ripplesElement) {
232
+ if (time - lastDropTime > 3000) {
233
+ lastDropTime = time;
234
+ const x = Math.random() * ripplesElement.offsetWidth;
235
+ const y = Math.random() * ripplesElement.offsetHeight;
236
+ // Use scaled drop radius for automatic drops
237
+ const currentSize = ripplesElement.offsetWidth;
238
+ const autoDropRadius = Math.min(
239
+ 20,
240
+ Math.max(2, 2 + ((currentSize - 62) / (800 - 62)) * 21.36)
241
+ );
242
+ // Scale strength based on size - ensure minimum visibility for small sizes
243
+ const sizeFactor = Math.max(0.7, Math.min(1, currentSize / 200)); // Never below 70%
244
+ const baseStrength = 0.1 + Math.random() * 0.04;
245
+ const strength = Math.max(0.08, baseStrength * sizeFactor); // Minimum 0.08 for visibility
246
+ ripples.drop(x, y, autoDropRadius, strength);
247
+ }
248
+ }
249
+
250
+ // Animate ligature
251
+ angle += deltaTime;
252
+
253
+ // Original movement in 0-4 range
254
+ const origX = 2 + 2 * Math.cos(angle / 971 - Math.PI);
255
+ const origY = 2 + 2 * Math.sin(angle / 601 - Math.PI / 2);
256
+
257
+ // Rotate -45 degrees: transform the 4x4 square into a diamond
258
+ const dx = animated ? ((origX + origY) * Math.sqrt(2)) / 2 : 0;
259
+ const dy = animated ? ((-origX + origY) * Math.sqrt(2)) / 2 : 0;
260
+
261
+ // The animation percentages were designed for the full 800px canvas
262
+ // Now we need to scale them for each element's actual size
263
+ for (const el of animatedElements) {
264
+ if (el.classList.contains('shadow')) {
265
+ // Shadow is 540x766, need to scale movement to match original animation
266
+ // Original: 4% of 800px = 32px. For shadow: 32px / 540px = 5.93%
267
+ const scaleX = 800 / 540;
268
+ const scaleY = 800 / 766;
269
+ (el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
270
+ } else if (el.classList.contains('ligature')) {
271
+ // Ligature is 440x666, need to scale movement to match original animation
272
+ // Original: 4% of 800px = 32px. For ligature: 32px / 440px = 7.27%
273
+ const scaleX = 800 / 440;
274
+ const scaleY = 800 / 666;
275
+ (el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
276
+ } else {
277
+ // Default for any other animated elements
278
+ (el as HTMLElement).style.transform = `translate(${dx}%, ${dy}%)`;
279
+ }
280
+ }
281
+
282
+ // Only continue animation if animated
283
+ if (animated) {
284
+ requestAnimationFrame(animate);
285
+ }
286
+ };
287
+
288
+ // Start animation if initially animated
289
+ if (animated) {
290
+ requestAnimationFrame(animate);
291
+ }
292
+
293
+ return function () {
294
+ if (resizeTimeout) {
295
+ clearTimeout(resizeTimeout);
296
+ }
297
+ if (ripples) {
298
+ try {
299
+ ripples.destroy();
300
+ } catch (e) {
301
+ console.error('Error destroying ripples on cleanup:', e);
302
+ }
303
+ }
304
+ if (resizeObserver) {
305
+ resizeObserver.disconnect();
306
+ }
307
+ };
308
+ };
309
+
310
+ function handleClick(event: MouseEvent | KeyboardEvent) {
311
+ // Call external callback first if provided
312
+ if (onClick) {
313
+ onClick(event);
314
+ }
315
+
316
+ // For keyboard events, only respond to Enter key
317
+ if ('key' in event && event.key !== 'Enter') {
318
+ return;
319
+ }
320
+
321
+ // Shift key controls whether click drops or toggles animation.
322
+ if (toggleAnimationWithShift !== event.shiftKey) {
323
+ return;
324
+ }
325
+
326
+ // Update global state - affects ALL LeftiumLogo instances immediately
327
+ // The reactive effect above will handle the animation start/stop logic
328
+ toggleAnimation();
329
+ }
330
+ </script>
331
+
332
+ <logo-container style:--size={size} class="{boundingBox} {className}" role="none" {...restProps}>
333
+ <grid-logo {@attach logoAnimation}>
334
+ <img class="animate shadow" alt="" src={logoShadow} />
335
+ <img class="glow" alt="" src={logoGlow} />
336
+ <div
337
+ class="ripples square"
338
+ style:background-image={`url("${logoSquare}")`}
339
+ onclick={handleClick}
340
+ onkeydown={handleClick}
341
+ role="button"
342
+ tabindex="0"
343
+ aria-label="Toggle logo animation"
344
+ ></div>
345
+ <img class="animate ligature" alt="" src={logoLigature} />
346
+ </grid-logo>
347
+ </logo-container>
348
+
349
+ <style>
350
+ /* Container that defines the bounding box */
351
+ logo-container {
352
+ display: inline-block;
353
+ position: relative;
354
+ width: var(--size);
355
+ aspect-ratio: 1;
356
+ user-select: none;
357
+ -webkit-user-select: none;
358
+ -moz-user-select: none;
359
+ -ms-user-select: none;
360
+ overflow: visible;
361
+ }
362
+
363
+ /* Square mode - square container */
364
+ logo-container.square {
365
+ aspect-ratio: 1;
366
+ }
367
+
368
+ /* Default mode - square container */
369
+ logo-container.default {
370
+ aspect-ratio: 1;
371
+ }
372
+
373
+ /* Encircled mode - square container */
374
+ logo-container.encircled {
375
+ aspect-ratio: 1;
376
+ }
377
+
378
+ /* Cropped mode - match ellipse aspect ratio: 723.08875/812.58868 ≈ 0.8906 */
379
+ logo-container.cropped {
380
+ aspect-ratio: 0.8906;
381
+ }
382
+
383
+ /* Grid that holds all the logo elements */
384
+ grid-logo {
385
+ position: absolute;
386
+ display: grid;
387
+ place-items: center;
388
+ width: 100%; /* Default width, will be overridden for default/encircled modes */
389
+ aspect-ratio: 1;
390
+
391
+ img {
392
+ /* Prevent PicoCSS styles. */
393
+ max-width: unset !important;
394
+ }
395
+ }
396
+
397
+ /* Square bounding box mode - grid fills the container */
398
+ logo-container.square grid-logo {
399
+ width: 100%;
400
+ left: 0;
401
+ top: 0;
402
+ }
403
+
404
+ /* Default bounding box mode - grid is scaled down to leave some padding */
405
+ logo-container.default grid-logo {
406
+ /* Grid is 1/1.2519 = 79.88% of container to account for padding */
407
+ width: calc(100% / 1.2519);
408
+ /* Center within container */
409
+ left: calc((100% - 100% / 1.2519) / 2);
410
+ top: calc((100% - 100% / 1.2519) / 2);
411
+ }
412
+
413
+ /* Encircled bounding box mode - grid is scaled down more to leave full padding */
414
+ logo-container.encircled grid-logo {
415
+ /* Grid is 1/1.5037 = 66.5% of container (532/800) */
416
+ width: calc(100% / 1.5037);
417
+ /* Center within container */
418
+ left: calc((100% - 100% / 1.5037) / 2);
419
+ top: calc((100% - 100% / 1.5037) / 2);
420
+ }
421
+
422
+ /* Cropped bounding box mode - grid scaled and positioned to match reference SVG */
423
+ logo-container.cropped grid-logo {
424
+ /* Square is 1131.371px in a 1447px wide container = 78.2% */
425
+ width: 78.2%;
426
+ /* Position calculations from SVG transforms */
427
+ /* Final square position after transforms: x=122.18, y=247.73 */
428
+ /* Left offset: 122.18/1447 = 8.44% */
429
+ left: 8.44%;
430
+ /* Top offset: 247.73/1626.9 = 15.23% */
431
+ top: 15.23%;
432
+ }
433
+
434
+ /* Individual logo elements positioned relative to the square */
435
+ grid-logo > * {
436
+ position: absolute;
437
+ grid-column: 1 / 2;
438
+ grid-row: 1 / 2;
439
+ will-change: auto;
440
+ }
441
+
442
+ /* Square image - base element at 532x532 */
443
+ .ripples.square {
444
+ width: 100%;
445
+ aspect-ratio: 1;
446
+ background-size: contain;
447
+ background-position: center;
448
+ background-repeat: no-repeat;
449
+ z-index: 2;
450
+ cursor: pointer;
451
+ outline: none;
452
+
453
+ /* Prevent PicoCSS [role=button] styles. */
454
+ padding: 0 !important;
455
+ border: none !important;
456
+ border-radius: 0 !important;
457
+ outline: none !important;
458
+ box-shadow: none !important;
459
+ }
460
+
461
+ .ripples.square:focus {
462
+ outline: none;
463
+ }
464
+
465
+ /* Glow - 647x647, centered on square */
466
+ .glow {
467
+ width: calc(100% * 647 / 532);
468
+ height: calc(100% * 647 / 532);
469
+ /* Center it: (647-532)/2 = 57.5px offset at original scale */
470
+ left: calc(100% * -57.5 / 532);
471
+ top: calc(100% * -57.5 / 532);
472
+ z-index: 1;
473
+ /* Glow should not capture clicks */
474
+ pointer-events: none;
475
+ }
476
+
477
+ /* Shadow - 540x766, positioned to outline the ligature */
478
+ .shadow {
479
+ width: calc(100% * 540 / 532);
480
+ height: calc(100% * 766 / 532);
481
+ /* Shadow should be centered on ligature */
482
+ /* Ligature is 440x666, shadow is 540x766 */
483
+ /* Shadow is (540-440)/2 = 50px wider on each side, (766-666)/2 = 50px taller on each side */
484
+ left: calc(100% * (133 - 50) / 532); /* Center shadow on ligature */
485
+ top: calc(100% * (-66 - 50) / 532);
486
+ z-index: 0;
487
+ /* Shadow should not capture clicks */
488
+ pointer-events: none;
489
+ }
490
+
491
+ /* Ligature - 440x666, positioned based on SVG */
492
+ .ligature {
493
+ width: calc(100% * 440 / 532);
494
+ height: calc(100% * 666 / 532);
495
+ /* Ligature extends to the right and below the square */
496
+ /* Looking at the SVG path, the ligature starts at top-left and extends right and down */
497
+ left: calc(100% * 133.5 / 532);
498
+ top: calc(100% * -65.75 / 532);
499
+ z-index: 3;
500
+ pointer-events: none;
501
+ }
502
+ </style>
@@ -0,0 +1,16 @@
1
+ export declare function toggleAnimation(): void;
2
+ export declare function setAnimated(value: boolean): void;
3
+ export declare function registerInstance(updateFn: () => void): () => boolean;
4
+ import { type RipplesOptions } from './webgl-ripples/webgl-ripples.js';
5
+ interface Props {
6
+ size?: string;
7
+ toggleAnimationWithShift?: boolean;
8
+ ripplesOptions?: RipplesOptions;
9
+ boundingBox?: 'square' | 'default' | 'cropped' | 'encircled';
10
+ class?: string;
11
+ onClick?: (event: MouseEvent | KeyboardEvent) => void;
12
+ [key: string]: unknown;
13
+ }
14
+ declare const LeftiumLogo: import("svelte").Component<Props, {}, "">;
15
+ type LeftiumLogo = ReturnType<typeof LeftiumLogo>;
16
+ export default LeftiumLogo;
@@ -0,0 +1,99 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="128"
6
+ height="128"
7
+ viewBox="0 0 128 128"
8
+ version="1.1"
9
+ id="svg1"
10
+ xmlns:xlink="http://www.w3.org/1999/xlink"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ xmlns:svg="http://www.w3.org/2000/svg">
13
+ <defs
14
+ id="defs1">
15
+ <linearGradient
16
+ xlink:href="#linearGradient1"
17
+ id="linearGradient2"
18
+ x1="-13953.743"
19
+ y1="20745.297"
20
+ x2="-13153.738"
21
+ y2="19945.293"
22
+ gradientUnits="userSpaceOnUse"
23
+ spreadMethod="pad"
24
+ gradientTransform="matrix(1.4378303,0,0,1.4378303,20080.608,-28687.206)" />
25
+ <linearGradient
26
+ id="linearGradient1">
27
+ <stop
28
+ style="stop-color:#0029c1;stop-opacity:1;"
29
+ offset="0"
30
+ id="stop1" />
31
+ <stop
32
+ style="stop-color:#3973ff;stop-opacity:1;"
33
+ offset="0.28999999"
34
+ id="stop3" />
35
+ <stop
36
+ style="stop-color:#0029c1;stop-opacity:1;"
37
+ offset="1"
38
+ id="stop2" />
39
+ </linearGradient>
40
+ <filter
41
+ style="color-interpolation-filters:sRGB"
42
+ id="filter2"
43
+ x="-0.35644"
44
+ y="-0.13903579"
45
+ width="1.6523672"
46
+ height="1.2780716">
47
+ <feGaussianBlur
48
+ stdDeviation="44.27207"
49
+ id="feGaussianBlur2" />
50
+ </filter>
51
+ <filter
52
+ style="color-interpolation-filters:sRGB"
53
+ id="filter3"
54
+ x="-0.10799999"
55
+ y="-0.108"
56
+ width="1.216"
57
+ height="1.216">
58
+ <feGaussianBlur
59
+ stdDeviation="50.911691"
60
+ id="feGaussianBlur3" />
61
+ </filter>
62
+ </defs>
63
+ <g
64
+ id="layer1">
65
+ <g
66
+ id="g1"
67
+ transform="translate(253.40376,284.58137)"
68
+ style="display:inline">
69
+ <g
70
+ id="g8"
71
+ style="display:inline"
72
+ transform="matrix(0.07868598,0,0,0.07868598,-236.035,-265.10778)">
73
+ <path
74
+ id="path2"
75
+ d="M 1052.0206,-70.521304 415.62473,565.8747 l 636.39587,636.396 m 0,-141.421 A 200.0002,200.0002 0 0 0 769.1778,778.00671 m -141.42104,141.421 a 200,200 0 0 0 0,282.84299"
76
+ style="display:inline;opacity:0.7;mix-blend-mode:normal;fill:none;stroke:#000000;stroke-width:200;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2)" />
77
+ <rect
78
+ style="opacity:0.7;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3)"
79
+ id="rect2"
80
+ width="1131.371"
81
+ height="1131.3708"
82
+ x="-8.6700182"
83
+ y="0.25409326" />
84
+ <rect
85
+ style="fill:url(#linearGradient2);stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
86
+ id="rect5345-1-4"
87
+ width="1626.7195"
88
+ height="1626.7192"
89
+ x="-220.73523"
90
+ y="-247.48486"
91
+ rx="222.70804" />
92
+ <path
93
+ id="path1"
94
+ d="M 949.57704,-166.14889 217.55333,565.87478 949.57704,1297.8984 m -0.8134,-161.8586 A 230.05284,230.05284 0 0 0 623.41984,810.69599 M 462.37465,971.74111 a 230.05284,230.05284 0 0 0 0,325.34389"
95
+ style="display:inline;fill:none;stroke:#ffffff;stroke-width:230.053;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
96
+ </g>
97
+ </g>
98
+ </g>
99
+ </svg>
@@ -0,0 +1,46 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="1375.7469"
6
+ height="1375.7469"
7
+ viewBox="0 0 1375.7469 1375.7469"
8
+ version="1.1"
9
+ id="svg1"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlns:svg="http://www.w3.org/2000/svg">
12
+ <defs
13
+ id="defs1">
14
+ <filter
15
+ style="color-interpolation-filters:sRGB"
16
+ id="filter3"
17
+ x="-0.10799999"
18
+ y="-0.108"
19
+ width="1.216"
20
+ height="1.216">
21
+ <feGaussianBlur
22
+ stdDeviation="50.911691"
23
+ id="feGaussianBlur3" />
24
+ </filter>
25
+ </defs>
26
+ <g
27
+ id="layer1"
28
+ transform="translate(-122.54569,-162.64743)">
29
+ <g
30
+ id="g1"
31
+ transform="translate(253.40376,284.58137)"
32
+ style="display:inline">
33
+ <g
34
+ id="g8"
35
+ style="display:inline">
36
+ <rect
37
+ style="opacity:0.7;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3)"
38
+ id="rect2"
39
+ width="1131.371"
40
+ height="1131.3708"
41
+ x="-8.6700182"
42
+ y="0.25409326" />
43
+ </g>
44
+ </g>
45
+ </g>
46
+ </svg>
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="935.01953"
6
+ height="1414.2136"
7
+ viewBox="0 0 935.01953 1414.2136"
8
+ version="1.1"
9
+ id="svg1"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlns:svg="http://www.w3.org/2000/svg">
12
+ <defs
13
+ id="defs1" />
14
+ <g
15
+ id="layer1"
16
+ transform="translate(-527.92273,-143.3493)">
17
+ <g
18
+ id="g1"
19
+ transform="translate(253.40376,284.58137)"
20
+ style="display:inline">
21
+ <g
22
+ id="g8"
23
+ style="display:inline">
24
+ <path
25
+ id="path1"
26
+ d="m 1305.7402,214.05999 -636.39613,636.3961 636.39613,636.39611 m -0.7071,-140.7143 a 200,200 0 0 0 -282.8427,-282.8427 m -140.00713,140.0071 a 200,200 0 0 0 0,282.8427"
27
+ style="display:inline;fill:none;stroke:#ffffff;stroke-width:200;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
28
+ transform="translate(-253.40376,-284.58137)" />
29
+ </g>
30
+ </g>
31
+ </g>
32
+ </svg>
@@ -0,0 +1,43 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="1148.1566"
6
+ height="1626.7194"
7
+ viewBox="0 0 1148.1566 1626.7194"
8
+ version="1.1"
9
+ id="svg1"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlns:svg="http://www.w3.org/2000/svg">
12
+ <defs
13
+ id="defs1">
14
+ <filter
15
+ style="color-interpolation-filters:sRGB"
16
+ id="filter2"
17
+ x="-0.35644"
18
+ y="-0.13903579"
19
+ width="1.6523672"
20
+ height="1.2780716">
21
+ <feGaussianBlur
22
+ stdDeviation="44.27207"
23
+ id="feGaussianBlur2" />
24
+ </filter>
25
+ </defs>
26
+ <g
27
+ id="layer1"
28
+ transform="translate(-421.35417,-37.096423)">
29
+ <g
30
+ id="g1"
31
+ transform="translate(253.40376,284.58137)"
32
+ style="display:inline">
33
+ <g
34
+ id="g8"
35
+ style="display:inline">
36
+ <path
37
+ id="path2"
38
+ d="M 1052.0206,-70.521304 415.62473,565.8747 l 636.39587,636.396 m 0,-141.421 A 200.0002,200.0002 0 0 0 769.1778,778.00671 m -141.42104,141.421 a 200,200 0 0 0 0,282.84299"
39
+ style="display:inline;opacity:0.7;mix-blend-mode:normal;fill:none;stroke:#000000;stroke-width:200;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2)" />
40
+ </g>
41
+ </g>
42
+ </g>
43
+ </svg>
@@ -0,0 +1,58 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="1131.371"
6
+ height="1131.3708"
7
+ viewBox="0 0 1131.371 1131.3708"
8
+ version="1.1"
9
+ id="svg1"
10
+ xmlns:xlink="http://www.w3.org/1999/xlink"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ xmlns:svg="http://www.w3.org/2000/svg">
13
+ <defs
14
+ id="defs1">
15
+ <linearGradient
16
+ id="linearGradient2"
17
+ x1="-13953.743"
18
+ y1="20745.297"
19
+ x2="-13153.738"
20
+ y2="19945.293"
21
+ gradientUnits="userSpaceOnUse"
22
+ spreadMethod="pad"
23
+ gradientTransform="translate(14110.759,-19779.355)">
24
+ <stop
25
+ style="stop-color:#0029c1;stop-opacity:1;"
26
+ offset="0"
27
+ id="stop1" />
28
+ <stop
29
+ style="stop-color:#3973ff;stop-opacity:1;"
30
+ offset="0.28999999"
31
+ id="stop3" />
32
+ <stop
33
+ style="stop-color:#0029c1;stop-opacity:1;"
34
+ offset="1"
35
+ id="stop2" />
36
+ </linearGradient>
37
+ </defs>
38
+ <g
39
+ id="layer1"
40
+ transform="translate(-244.73374,-284.83546)">
41
+ <g
42
+ id="g1"
43
+ transform="translate(253.40376,284.58137)"
44
+ style="display:inline">
45
+ <g
46
+ id="g8"
47
+ style="display:inline">
48
+ <rect
49
+ style="fill:url(#linearGradient2);stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
50
+ id="rect5345-1-4"
51
+ width="1131.371"
52
+ height="1131.3708"
53
+ x="-8.6700182"
54
+ y="0.25409326" />
55
+ </g>
56
+ </g>
57
+ </g>
58
+ </svg>
@@ -0,0 +1,5 @@
1
+ import LeftiumLogo from './LeftiumLogo.svelte';
2
+ import toggleAnimation from './LeftiumLogo.svelte';
3
+ import setAnimated from './LeftiumLogo.svelte';
4
+ import favicon from './assets/favicon.svg';
5
+ export { LeftiumLogo, toggleAnimation, setAnimated, favicon };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Reexport your entry components here
2
+ import LeftiumLogo from './LeftiumLogo.svelte';
3
+ import toggleAnimation from './LeftiumLogo.svelte';
4
+ import setAnimated from './LeftiumLogo.svelte';
5
+ import favicon from './assets/favicon.svg';
6
+ export { LeftiumLogo, toggleAnimation, setAnimated, favicon };
@@ -0,0 +1,22 @@
1
+ export interface RipplesOptions {
2
+ imageUrl?: string | null;
3
+ resolution?: number;
4
+ dropRadius?: number;
5
+ perturbance?: number;
6
+ interactive?: boolean;
7
+ crossOrigin?: string;
8
+ }
9
+
10
+ export declare class Ripples {
11
+ constructor(el: string | HTMLElement, options?: RipplesOptions);
12
+ drop(x: number, y: number, radius: number, strength: number): void;
13
+ destroy(): void;
14
+ pause(): void;
15
+ play(): void;
16
+ set(property: string, value: any): void;
17
+ updateSize(): void;
18
+ }
19
+
20
+ export default Ripples;
21
+
22
+ export declare function isWebGLSupported(): boolean;
@@ -0,0 +1 @@
1
+ const isBrowser="undefined"!=typeof window&&"undefined"!=typeof document;let gl=null,config=null,transparentPixels=null;function isPercentage(e){return"%"===e[e.length-1]}function loadConfig(){if(!isBrowser)return null;const e=document.createElement("canvas");if(gl=e.getContext("webgl")||e.getContext("experimental-webgl"),!gl)return null;const t={};if(["OES_texture_float","OES_texture_half_float","OES_texture_float_linear","OES_texture_half_float_linear"].forEach(e=>{const i=gl.getExtension(e);i&&(t[e]=i)}),!t.OES_texture_float)return null;const i=[];function r(e,i,r){const n="OES_texture_"+e,s=n+"_linear",o=s in t,a=[n];return o&&a.push(s),{type:i,arrayType:r,linearSupport:o,extensions:a}}i.push(r("float",gl.FLOAT,Float32Array)),t.OES_texture_half_float&&i.push(r("half_float",t.OES_texture_half_float.HALF_FLOAT_OES,null));const n=gl.createTexture(),s=gl.createFramebuffer();gl.bindFramebuffer(gl.FRAMEBUFFER,s),gl.bindTexture(gl.TEXTURE_2D,n),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);let o=null;for(let e=0;e<i.length;e++)if(gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,32,32,0,gl.RGBA,i[e].type,null),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,n,0),gl.checkFramebufferStatus(gl.FRAMEBUFFER)===gl.FRAMEBUFFER_COMPLETE){o=i[e];break}return o}function createImageData(e,t){if(!isBrowser)return null;try{return new ImageData(e,t)}catch{return document.createElement("canvas").getContext("2d").createImageData(e,t)}}function translateBackgroundPosition(e){const t=e.split(" ");if(1!==t.length)return t.map(t=>{switch(e){case"center":return"50%";case"top":case"left":return"0";case"right":case"bottom":return"100%";default:return t}});switch(e){case"center":return["50%","50%"];case"top":return["50%","0"];case"bottom":return["50%","100%"];case"left":return["0","50%"];case"right":return["100%","50%"];default:return[e,"50%"]}}function createProgram(e,t){function i(e,t){const i=gl.createShader(e);if(gl.shaderSource(i,t),gl.compileShader(i),!gl.getShaderParameter(i,gl.COMPILE_STATUS))throw new Error("compile error: "+gl.getShaderInfoLog(i));return i}const r={};if(r.id=gl.createProgram(),gl.attachShader(r.id,i(gl.VERTEX_SHADER,e)),gl.attachShader(r.id,i(gl.FRAGMENT_SHADER,t)),gl.linkProgram(r.id),!gl.getProgramParameter(r.id,gl.LINK_STATUS))throw new Error("link error: "+gl.getProgramInfoLog(r.id));r.uniforms={},r.locations={},gl.useProgram(r.id),gl.enableVertexAttribArray(0);let n,s,o=/uniform (\w+) (\w+)/g,a=e+t;for(;null!=(n=o.exec(a));)s=n[2],r.locations[s]=gl.getUniformLocation(r.id,s);return r}function bindTexture(e,t){gl.activeTexture(gl.TEXTURE0+(t||0)),gl.bindTexture(gl.TEXTURE_2D,e)}function extractUrl(e){const t=/url\(["']?([^"']*)["']?\)/.exec(e);return null==t?null:t[1]}function isDataUri(e){return e.match(/^data:/)}isBrowser&&(config=loadConfig(),transparentPixels=createImageData(32,32));const DEFAULTS={imageUrl:null,dropRadius:15,perturbance:.04,resolution:512,interactive:!0,crossOrigin:"",wavePropagation:2,dampening:.997};class Ripples{constructor(e,t){if(!isBrowser)return console.warn("Ripples: Cannot initialize in non-browser environment"),void(this.destroyed=!0);if(this.el="string"==typeof e?document.querySelector(e):e,!this.el)return void console.error("Ripples: Element not found.",e);this.options={...DEFAULTS,...t},this.interactive=this.options.interactive,this.resolution=this.options.resolution,this.textureDelta=new Float32Array([1/this.resolution,1/this.resolution]),this.perturbance=this.options.perturbance,this.dropRadius=this.options.dropRadius,this.crossOrigin=this.options.crossOrigin,this.imageUrl=this.options.imageUrl;const i=this.el.clientWidth,r=this.el.clientHeight;if(0===i||0===r)return console.warn("Ripples: Element has zero dimensions. Deferring initialization."),this.pendingInitialization=!0,this.destroyed=!1,this.visible=!1,this.running=!1,this.inited=!1,void this.observeVisibility();const n=document.createElement("canvas");if(n.width=i,n.height=r,this.canvas=n,this.canvas.style.position="absolute",this.canvas.style.inset=0,0===n.width||0===n.height)return console.warn("Ripples: Canvas has zero dimensions. Deferring initialization."),this.pendingInitialization=!0,this.destroyed=!1,this.visible=!1,this.running=!1,this.inited=!1,void this.observeVisibility();if(this.el.append(n),this.context=gl=n.getContext("webgl")||n.getContext("experimental-webgl"),!this.context)return console.error("Ripples: WebGL is not supported by your browser or is disabled."),void(this.destroyed=!0);config&&config.extensions&&config.extensions.forEach(e=>{gl.getExtension(e)}),this.abortController=new AbortController;const s=this.signal=this.abortController.signal;this.updateSize=this.updateSize.bind(this),window.addEventListener("resize",this.updateSize,{signal:s}),this.textures=[],this.framebuffers=[],this.bufferWriteIndex=0,this.bufferReadIndex=1;const o=config?config.arrayType:null,a=o?new o(this.resolution*this.resolution*4):null;for(let e=0;e<2;e++){const e=gl.createTexture(),t=gl.createFramebuffer();gl.bindFramebuffer(gl.FRAMEBUFFER,t),gl.bindTexture(gl.TEXTURE_2D,e),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,config&&config.linearSupport?gl.LINEAR:gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,config&&config.linearSupport?gl.LINEAR:gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,this.resolution,this.resolution,0,gl.RGBA,config&&config.type||gl.UNSIGNED_BYTE,a),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,e,0),this.textures.push(e),this.framebuffers.push(t)}this.quad=gl.createBuffer(),gl.bindBuffer(gl.ARRAY_BUFFER,this.quad),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,1,1,-1,1]),gl.STATIC_DRAW),this.initShaders(),this.initTexture(),this.setTransparentTexture(),this.originalCssBackgroundImage=getComputedStyle(this.el).backgroundImage,this.originalInlineCss=this.el.style.backgroundImage,this.loadImage(),gl.clearColor(0,0,0,0),gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA),this.visible=!0,this.running=!0,this.inited=!0,this.destroyed=!1,this.setupPointerEvents(),this.animationId=null;const l=()=>{this.destroyed||(this.step(),this.running||this.needsRender?this.animationId=requestAnimationFrame(l):this.animationId=null)};return this.animationId=requestAnimationFrame(l),this}observeVisibility(){isBrowser&&this.el&&("undefined"!=typeof ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>{if(this.pendingInitialization){const e=this.el.clientWidth,t=this.el.clientHeight;e>0&&t>0&&this.completeInitialization()}else this.inited&&this.canvas&&this.updateSize()}),this.resizeObserver.observe(this.el)),"undefined"!=typeof IntersectionObserver&&(this.intersectionObserver=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting&&this.pendingInitialization){const e=this.el.clientWidth,t=this.el.clientHeight;e>0&&t>0&&this.completeInitialization()}})}),this.intersectionObserver.observe(this.el)),this.resizeObserver||this.intersectionObserver||(this.visibilityCheckInterval=setInterval(()=>{if(this.pendingInitialization){const e=this.el.clientWidth,t=this.el.clientHeight;e>0&&t>0&&this.completeInitialization()}},500)))}completeInitialization(){if(!this.pendingInitialization||this.inited)return;console.log("Ripples: Element now visible, completing initialization."),this.pendingInitialization=!1,this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.intersectionObserver&&(this.intersectionObserver.disconnect(),this.intersectionObserver=null),this.visibilityCheckInterval&&(clearInterval(this.visibilityCheckInterval),this.visibilityCheckInterval=null);const e=this.options,t=this.el;Object.assign(this,new Ripples(t,e))}setupPointerEvents(){const e=()=>{const e=Math.max(this.el.clientWidth,this.el.clientHeight),t=Math.max(1,150/e);return Math.min(.04,.01*t)},t=t=>{if(this.visible&&this.running&&this.interactive){const i=t.changedTouches,r=e();for(let e=0;e<i.length;e++)this.dropAtPointer(i[e],1*this.dropRadius,r)}},i=this.signal;this.el.addEventListener("mousemove",t=>{this.visible&&this.running&&this.interactive&&this.dropAtPointer(t,1*this.dropRadius,e())},{signal:i}),this.el.addEventListener("touchmove",t,{signal:i,passive:!1}),this.el.addEventListener("touchstart",t,{signal:i,passive:!0}),this.el.addEventListener("mousedown",e=>{this.visible&&this.running&&this.interactive&&this.dropAtPointer(e,1.5*this.dropRadius,.14)},{signal:i})}loadImage(){gl=this.context;const e=this.imageUrl||extractUrl(this.originalCssBackgroundImage)||extractUrl(getComputedStyle(this.el).backgroundImage);if(e==this.imageSource)return;if(this.imageSource=e,!this.imageSource)return void this.setTransparentTexture();const t=new Image;t.onload=()=>{function e(e){return!(e&e-1)}gl=this.context;const i=e(t.width)&&e(t.height)?gl.REPEAT:gl.CLAMP_TO_EDGE;gl.bindTexture(gl.TEXTURE_2D,this.backgroundTexture),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,i),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,i),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,t),this.backgroundWidth=t.width,this.backgroundHeight=t.height},t.onerror=()=>{gl=this.context,this.setTransparentTexture()},t.crossOrigin=isDataUri(this.imageSource)?null:this.crossOrigin,t.src=this.imageSource}step(){this.pendingInitialization||this.destroyed||(gl=this.context,gl&&this.canvas&&0!==this.canvas.width&&0!==this.canvas.height&&this.visible&&(this.computeTextureBoundaries(),this.running&&(this.update(),this.render())))}drawQuad(){gl.bindBuffer(gl.ARRAY_BUFFER,this.quad),gl.vertexAttribPointer(0,2,gl.FLOAT,!1,0,0),gl.drawArrays(gl.TRIANGLE_FAN,0,4)}render(){gl&&this.canvas&&0!==this.canvas.width&&0!==this.canvas.height&&(gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,this.canvas.width,this.canvas.height),gl.enable(gl.BLEND),gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT),gl.useProgram(this.renderProgram.id),bindTexture(this.backgroundTexture,0),bindTexture(this.textures[0],1),gl.uniform1f(this.renderProgram.locations.perturbance,this.perturbance),gl.uniform2fv(this.renderProgram.locations.topLeft,this.renderProgram.uniforms.topLeft),gl.uniform2fv(this.renderProgram.locations.bottomRight,this.renderProgram.uniforms.bottomRight),gl.uniform2fv(this.renderProgram.locations.containerRatio,this.renderProgram.uniforms.containerRatio),gl.uniform1i(this.renderProgram.locations.samplerBackground,0),gl.uniform1i(this.renderProgram.locations.samplerRipples,1),this.drawQuad(),gl.disable(gl.BLEND))}update(){gl&&0!==this.resolution&&(gl.viewport(0,0,this.resolution,this.resolution),gl.bindFramebuffer(gl.FRAMEBUFFER,this.framebuffers[this.bufferWriteIndex]),bindTexture(this.textures[this.bufferReadIndex]),gl.useProgram(this.updateProgram.id),gl.uniform1f(this.updateProgram.locations.wavePropagation,this.options.wavePropagation),gl.uniform1f(this.updateProgram.locations.dampening,this.options.dampening),this.drawQuad(),this.swapBufferIndices())}swapBufferIndices(){this.bufferWriteIndex=1-this.bufferWriteIndex,this.bufferReadIndex=1-this.bufferReadIndex}computeTextureBoundaries(){const e=getComputedStyle(this.el),t=e.backgroundSize,i=e.backgroundAttachment,r=translateBackgroundPosition(e.backgroundPosition);let n,s,o;if("fixed"==i)n={left:window.pageXOffset,top:window.pageYOffset},n.width=window.innerWidth,n.height=window.innerHeight;else{const e=this.el.getBoundingClientRect();n={left:e.left+window.pageXOffset,top:e.top+window.pageYOffset},n.width=this.el.clientWidth,n.height=this.el.clientHeight}if("cover"==t){const e=Math.max(n.width/this.backgroundWidth,n.height/this.backgroundHeight);s=this.backgroundWidth*e,o=this.backgroundHeight*e}else if("contain"==t){const e=Math.min(n.width/this.backgroundWidth,n.height/this.backgroundHeight);s=this.backgroundWidth*e,o=this.backgroundHeight*e}else{const e=t.split(" ");s=e[0]||"",o=e[1]||s,isPercentage(s)?s=n.width*parseFloat(s)/100:"auto"!=s&&(s=parseFloat(s)),isPercentage(o)?o=n.height*parseFloat(o)/100:"auto"!=o&&(o=parseFloat(o)),"auto"==s&&"auto"==o?(s=this.backgroundWidth,o=this.backgroundHeight):("auto"==s&&(s=this.backgroundWidth*(o/this.backgroundHeight)),"auto"==o&&(o=this.backgroundHeight*(s/this.backgroundWidth)))}let a=r[0],l=r[1];a=isPercentage(a)?n.left+(n.width-s)*parseFloat(a)/100:n.left+parseFloat(a),l=isPercentage(l)?n.top+(n.height-o)*parseFloat(l)/100:n.top+parseFloat(l);const g=this.el.getBoundingClientRect(),h=g.left+window.pageXOffset,d=g.top+window.pageYOffset;this.renderProgram.uniforms.topLeft=new Float32Array([(h-a)/s,(d-l)/o]),this.renderProgram.uniforms.bottomRight=new Float32Array([this.renderProgram.uniforms.topLeft[0]+this.el.clientWidth/s,this.renderProgram.uniforms.topLeft[1]+this.el.clientHeight/o]);const c=Math.max(this.canvas.width,this.canvas.height);this.renderProgram.uniforms.containerRatio=new Float32Array([this.canvas.width/c,this.canvas.height/c])}initShaders(){if(!gl)return;const e=["attribute vec2 vertex;","varying vec2 coord;","void main() {","coord = vertex * 0.5 + 0.5;","gl_Position = vec4(vertex, 0.0, 1.0);","}"].join("\n");this.dropProgram=createProgram(e,["precision highp float;","const float PI = 3.141592653589793;","uniform sampler2D texture;","uniform vec2 center;","uniform float radius;","uniform float strength;","varying vec2 coord;","void main() {","vec4 info = texture2D(texture, coord);","float drop = max(0.0, 1.0 - length(center * 0.5 + 0.5 - coord) / radius);","drop = 0.5 - cos(drop * PI) * 0.5;","info.r += drop * strength;","gl_FragColor = info;","}"].join("\n")),this.updateProgram=createProgram(e,["precision highp float;","uniform sampler2D texture;","uniform vec2 delta;","uniform float wavePropagation;","uniform float dampening;","varying vec2 coord;","void main() {","vec4 info = texture2D(texture, coord);","vec2 dx = vec2(delta.x, 0.0);","vec2 dy = vec2(0.0, delta.y);","// Detect edges and apply boundary conditions","float edgeFactor = 1.0;","if (coord.x <= delta.x || coord.x >= 1.0 - delta.x ||"," coord.y <= delta.y || coord.y >= 1.0 - delta.y) {"," edgeFactor = 0.95; // Dampen waves near edges more aggressively","}","float average = (","texture2D(texture, coord - dx).r +","texture2D(texture, coord - dy).r +","texture2D(texture, coord + dx).r +","texture2D(texture, coord + dy).r",") * 0.25;","// Wave propagation and dampening with configurable parameters","info.g += (average - info.r) * wavePropagation;","info.g *= dampening * edgeFactor;","info.r += info.g;","// Clamp values to prevent overflow artifacts","info.r = clamp(info.r, -1.0, 1.0);","info.g = clamp(info.g, -1.0, 1.0);","gl_FragColor = info;","}"].join("\n")),gl.uniform2fv(this.updateProgram.locations.delta,this.textureDelta),gl.uniform1f(this.updateProgram.locations.wavePropagation,this.options.wavePropagation),gl.uniform1f(this.updateProgram.locations.dampening,this.options.dampening),this.renderProgram=createProgram(["precision highp float;","attribute vec2 vertex;","uniform vec2 topLeft;","uniform vec2 bottomRight;","uniform vec2 containerRatio;","varying vec2 ripplesCoord;","varying vec2 backgroundCoord;","void main() {","backgroundCoord = mix(topLeft, bottomRight, vertex * 0.5 + 0.5);","backgroundCoord.y = 1.0 - backgroundCoord.y;","ripplesCoord = vec2(vertex.x, -vertex.y) * containerRatio * 0.5 + 0.5;","gl_Position = vec4(vertex.x, -vertex.y, 0.0, 1.0);","}"].join("\n"),["precision highp float;","uniform sampler2D samplerBackground;","uniform sampler2D samplerRipples;","uniform vec2 delta;","uniform float perturbance;","varying vec2 ripplesCoord;","varying vec2 backgroundCoord;","void main() {","float height = texture2D(samplerRipples, ripplesCoord).r;","// Sample neighboring pixels for calculating normals","vec2 texelSize = delta;","float heightX = texture2D(samplerRipples, clamp(ripplesCoord + vec2(texelSize.x, 0.0), 0.0, 1.0)).r;","float heightY = texture2D(samplerRipples, clamp(ripplesCoord + vec2(0.0, texelSize.y), 0.0, 1.0)).r;","vec3 dx = vec3(texelSize.x, heightX - height, 0.0);","vec3 dy = vec3(0.0, heightY - height, texelSize.y);","vec2 offset = -normalize(cross(dy, dx)).xz;","// Apply edge fading to reduce artifacts at boundaries","float edgeFade = 1.0;","float edgeDistance = 0.05;","edgeFade *= smoothstep(0.0, edgeDistance, ripplesCoord.x);","edgeFade *= smoothstep(0.0, edgeDistance, ripplesCoord.y);","edgeFade *= smoothstep(0.0, edgeDistance, 1.0 - ripplesCoord.x);","edgeFade *= smoothstep(0.0, edgeDistance, 1.0 - ripplesCoord.y);","float specular = pow(max(0.0, dot(offset, normalize(vec2(-0.6, 1.0)))), 4.0) * edgeFade;","gl_FragColor = texture2D(samplerBackground, backgroundCoord + offset * perturbance * edgeFade) + specular;","}"].join("\n")),gl.uniform2fv(this.renderProgram.locations.delta,this.textureDelta)}initTexture(){gl&&(this.backgroundTexture=gl.createTexture(),gl.bindTexture(gl.TEXTURE_2D,this.backgroundTexture),gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR))}setTransparentTexture(){gl&&(gl.bindTexture(gl.TEXTURE_2D,this.backgroundTexture),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,transparentPixels))}dropAtPointer(e,t,i){if(!this.canvas||this.pendingInitialization)return;const r=this.canvas.getBoundingClientRect();let n,s;void 0!==e.clientX?(n=e.clientX-r.left,s=e.clientY-r.top):(n=e.pageX-(r.left+window.pageXOffset),s=e.pageY-(r.top+window.pageYOffset)),n<0||n>r.width||s<0||s>r.height||(this.canvas.width===r.width&&this.canvas.height===r.height||(n*=this.canvas.width/r.width,s*=this.canvas.height/r.height),this.drop(n,s,t,i))}drop(e,t,i,r){if(gl=this.context,!gl)return;const n=this.el.clientWidth,s=this.el.clientHeight,o=Math.max(n,s);i/=o;const a=new Float32Array([(2*e-n)/o,(s-2*t)/o]);gl.viewport(0,0,this.resolution,this.resolution),gl.bindFramebuffer(gl.FRAMEBUFFER,this.framebuffers[this.bufferWriteIndex]),bindTexture(this.textures[this.bufferReadIndex]),gl.useProgram(this.dropProgram.id),gl.uniform2fv(this.dropProgram.locations.center,a),gl.uniform1f(this.dropProgram.locations.radius,i),gl.uniform1f(this.dropProgram.locations.strength,r),this.drawQuad(),this.swapBufferIndices()}updateSize(){const e=this.el.clientWidth,t=this.el.clientHeight;e===this.canvas.width&&t===this.canvas.height||(this.canvas.width=e,this.canvas.height=t)}destroy(){this.destroyed=!0,this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.intersectionObserver&&(this.intersectionObserver.disconnect(),this.intersectionObserver=null),this.visibilityCheckInterval&&(clearInterval(this.visibilityCheckInterval),this.visibilityCheckInterval=null),this.abortController&&this.abortController.abort(),this.canvas&&this.canvas.remove();const e=this.context;e&&(this.quad&&e.deleteBuffer(this.quad),this.dropProgram&&e.deleteProgram(this.dropProgram.id),this.updateProgram&&e.deleteProgram(this.updateProgram.id),this.renderProgram&&e.deleteProgram(this.renderProgram.id),this.backgroundTexture&&e.deleteTexture(this.backgroundTexture),this.textures&&this.textures.forEach(t=>e.deleteTexture(t)),this.framebuffers&&this.framebuffers.forEach(t=>e.deleteFramebuffer(t)),gl===e&&(gl=null),this.context=null)}pause(){this.destroyed||(this.running=!1,this.needsRender=!0,setTimeout(()=>{this.needsRender=!1},50))}play(){if(!this.destroyed&&(this.running=!0,!this.animationId)){const e=()=>{this.destroyed||(this.step(),this.running||this.needsRender?this.animationId=requestAnimationFrame(e):this.animationId=null)};this.animationId=requestAnimationFrame(e)}}reset(){if(this.destroyed||!gl)return;gl=this.context;const e=config?config.arrayType:null,t=e?new e(this.resolution*this.resolution*4):null;for(let e=0;e<2;e++)gl.bindTexture(gl.TEXTURE_2D,this.textures[e]),gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,this.resolution,this.resolution,0,gl.RGBA,config&&config.type||gl.UNSIGNED_BYTE,t)}set(e,t){if(!this.destroyed)switch(e){case"dropRadius":case"perturbance":case"interactive":case"crossOrigin":this[e]=t,this.options[e]=t;break;case"imageUrl":this.imageUrl=t,this.options.imageUrl=t,this.loadImage();break;case"resolution":console.warn("Ripples: Changing 'resolution' dynamically is not supported. Please re-initialize the Ripples instance.");break;default:console.warn(`Ripples: Property "${e}" is not a settable option.`)}}}export{Ripples};export default Ripples;export const isWebGLSupported=()=>!!isBrowser&&null!==config;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@leftium/logo",
3
+ "version": "0.0.1",
4
+ "files": [
5
+ "dist",
6
+ "!dist/**/*.test.*",
7
+ "!dist/**/*.spec.*",
8
+ "!dist/webgl-ripples/webgl-ripples-original.*"
9
+ ],
10
+ "sideEffects": [
11
+ "**/*.css"
12
+ ],
13
+ "svelte": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "svelte": "./dist/index.js"
20
+ }
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "peerDependencies": {
26
+ "svelte": "^5.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@eslint/compat": "^1.2.5",
30
+ "@eslint/js": "^9.18.0",
31
+ "@sveltejs/adapter-auto": "^6.0.0",
32
+ "@sveltejs/kit": "^2.22.0",
33
+ "@sveltejs/package": "^2.0.0",
34
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
35
+ "eslint": "^9.18.0",
36
+ "eslint-config-prettier": "^10.0.1",
37
+ "eslint-plugin-svelte": "^3.0.0",
38
+ "globals": "^16.0.0",
39
+ "prettier": "^3.4.2",
40
+ "prettier-plugin-svelte": "^3.3.3",
41
+ "publint": "^0.3.2",
42
+ "svelte": "^5.0.0",
43
+ "svelte-check": "^4.0.0",
44
+ "typescript": "^5.0.0",
45
+ "typescript-eslint": "^8.20.0",
46
+ "vite": "^7.0.4"
47
+ },
48
+ "keywords": [
49
+ "svelte"
50
+ ],
51
+ "scripts": {
52
+ "dev": "vite dev",
53
+ "build": "vite build && npm run prepack",
54
+ "preview": "vite preview",
55
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
56
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
57
+ "format": "prettier --write .",
58
+ "lint": "prettier --check . && eslint ."
59
+ }
60
+ }