@leftium/logo 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/AppLogo.svelte +137 -0
- package/dist/AppLogo.svelte.d.ts +4 -0
- package/dist/LeftiumLogo.svelte +269 -187
- package/dist/LeftiumLogo.svelte.d.ts +1 -0
- package/dist/app-logo/color-transform.d.ts +36 -0
- package/dist/app-logo/color-transform.js +136 -0
- package/dist/app-logo/defaults.d.ts +16 -0
- package/dist/app-logo/defaults.js +38 -0
- package/dist/app-logo/generate-favicon-set.d.ts +44 -0
- package/dist/app-logo/generate-favicon-set.js +97 -0
- package/dist/app-logo/generate-ico.d.ts +18 -0
- package/dist/app-logo/generate-ico.js +63 -0
- package/dist/app-logo/generate-png.d.ts +16 -0
- package/dist/app-logo/generate-png.js +60 -0
- package/dist/app-logo/generate-svg.d.ts +9 -0
- package/dist/app-logo/generate-svg.js +158 -0
- package/dist/app-logo/iconify.d.ts +21 -0
- package/dist/app-logo/iconify.js +134 -0
- package/dist/app-logo/squircle.d.ts +43 -0
- package/dist/app-logo/squircle.js +213 -0
- package/dist/app-logo/types.d.ts +37 -0
- package/dist/app-logo/types.js +1 -0
- package/dist/assets/logo-parts/glow-squircle.svg +44 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +9 -3
- package/dist/webgl-ripples/webgl-ripples.d.ts +1 -5
- package/dist/webgl-ripples/webgl-ripples.js +1 -1
- package/package.json +39 -20
package/dist/LeftiumLogo.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script module>
|
|
2
2
|
import { dev } from '$app/environment';
|
|
3
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
3
4
|
|
|
4
5
|
// Simple shared variable - no store needed!
|
|
5
6
|
let globalAnimated = !dev;
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
// Keep track of all instances for updates
|
|
19
|
-
let instances = new
|
|
20
|
+
let instances = new SvelteSet<() => void>();
|
|
20
21
|
|
|
21
22
|
function updateAllInstances() {
|
|
22
23
|
instances.forEach((update) => update());
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
import { Ripples, type RipplesOptions } from './webgl-ripples/webgl-ripples.js';
|
|
35
36
|
|
|
36
37
|
import logoGlow from './assets/logo-parts/glow.svg';
|
|
38
|
+
import logoGlowSquircle from './assets/logo-parts/glow-squircle.svg';
|
|
37
39
|
import logoLigature from './assets/logo-parts/ligature.svg';
|
|
38
40
|
import logoShadow from './assets/logo-parts/shadow.svg';
|
|
39
41
|
import logoSquare from './assets/logo-parts/square.svg?inline';
|
|
@@ -43,21 +45,56 @@
|
|
|
43
45
|
toggleAnimationWithShift?: boolean;
|
|
44
46
|
ripplesOptions?: RipplesOptions;
|
|
45
47
|
boundingBox?: 'square' | 'default' | 'cropped' | 'encircled';
|
|
48
|
+
squircle?: boolean;
|
|
46
49
|
class?: string;
|
|
47
50
|
onClick?: (event: MouseEvent | KeyboardEvent) => void;
|
|
48
51
|
[key: string]: unknown; // Allow any additional props
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
// Squircle clip-path (50% radius, K=2 superellipse, CSS polygon with percentages)
|
|
55
|
+
const SQUIRCLE_CLIP =
|
|
56
|
+
'polygon(50% 0%, 53.05% 0%, 55.96% 0%, 58.74% 0%, 61.38% 0%, 63.89% 0%, 66.27% 0%, 68.54% 0.01%, 70.69% 0.01%, 72.73% 0.02%, 74.66% 0.03%, 76.48% 0.04%, 78.21% 0.06%, 79.84% 0.09%, 81.37% 0.11%, 82.82% 0.15%, 84.18% 0.2%, 85.46% 0.25%, 86.66% 0.31%, 87.78% 0.39%, 88.83% 0.48%, 89.81% 0.58%, 90.73% 0.7%, 91.58% 0.83%, 92.37% 0.99%, 93.11% 1.16%, 93.79% 1.36%, 94.41% 1.58%, 94.99% 1.83%, 95.53% 2.11%, 96.02% 2.41%, 96.47% 2.75%, 96.88% 3.13%, 97.25% 3.53%, 97.59% 3.98%, 97.89% 4.47%, 98.17% 5.01%, 98.42% 5.59%, 98.64% 6.21%, 98.84% 6.89%, 99.01% 7.63%, 99.17% 8.42%, 99.3% 9.27%, 99.42% 10.19%, 99.52% 11.17%, 99.61% 12.22%, 99.69% 13.34%, 99.75% 14.54%, 99.8% 15.82%, 99.85% 17.18%, 99.89% 18.63%, 99.91% 20.16%, 99.94% 21.79%, 99.96% 23.52%, 99.97% 25.34%, 99.98% 27.27%, 99.99% 29.31%, 99.99% 31.46%, 100% 33.73%, 100% 36.11%, 100% 38.62%, 100% 41.26%, 100% 44.04%, 100% 46.95%, 100% 50%, 100% 50%, 100% 53.05%, 100% 55.96%, 100% 58.74%, 100% 61.38%, 100% 63.89%, 100% 66.27%, 99.99% 68.54%, 99.99% 70.69%, 99.98% 72.73%, 99.97% 74.66%, 99.96% 76.48%, 99.94% 78.21%, 99.91% 79.84%, 99.89% 81.37%, 99.85% 82.82%, 99.8% 84.18%, 99.75% 85.46%, 99.69% 86.66%, 99.61% 87.78%, 99.52% 88.83%, 99.42% 89.81%, 99.3% 90.73%, 99.17% 91.58%, 99.01% 92.37%, 98.84% 93.11%, 98.64% 93.79%, 98.42% 94.41%, 98.17% 94.99%, 97.89% 95.53%, 97.59% 96.02%, 97.25% 96.47%, 96.88% 96.88%, 96.47% 97.25%, 96.02% 97.59%, 95.53% 97.89%, 94.99% 98.17%, 94.41% 98.42%, 93.79% 98.64%, 93.11% 98.84%, 92.37% 99.01%, 91.58% 99.17%, 90.73% 99.3%, 89.81% 99.42%, 88.83% 99.52%, 87.78% 99.61%, 86.66% 99.69%, 85.46% 99.75%, 84.18% 99.8%, 82.82% 99.85%, 81.37% 99.89%, 79.84% 99.91%, 78.21% 99.94%, 76.48% 99.96%, 74.66% 99.97%, 72.73% 99.98%, 70.69% 99.99%, 68.54% 99.99%, 66.27% 100%, 63.89% 100%, 61.38% 100%, 58.74% 100%, 55.96% 100%, 53.05% 100%, 50% 100%, 50% 100%, 46.95% 100%, 44.04% 100%, 41.26% 100%, 38.62% 100%, 36.11% 100%, 33.73% 100%, 31.46% 99.99%, 29.31% 99.99%, 27.27% 99.98%, 25.34% 99.97%, 23.52% 99.96%, 21.79% 99.94%, 20.16% 99.91%, 18.63% 99.89%, 17.18% 99.85%, 15.82% 99.8%, 14.54% 99.75%, 13.34% 99.69%, 12.22% 99.61%, 11.17% 99.52%, 10.19% 99.42%, 9.27% 99.3%, 8.42% 99.17%, 7.63% 99.01%, 6.89% 98.84%, 6.21% 98.64%, 5.59% 98.42%, 5.01% 98.17%, 4.47% 97.89%, 3.98% 97.59%, 3.53% 97.25%, 3.13% 96.88%, 2.75% 96.47%, 2.41% 96.02%, 2.11% 95.53%, 1.83% 94.99%, 1.58% 94.41%, 1.36% 93.79%, 1.16% 93.11%, 0.99% 92.37%, 0.83% 91.58%, 0.7% 90.73%, 0.58% 89.81%, 0.48% 88.83%, 0.39% 87.78%, 0.31% 86.66%, 0.25% 85.46%, 0.2% 84.18%, 0.15% 82.82%, 0.11% 81.37%, 0.09% 79.84%, 0.06% 78.21%, 0.04% 76.48%, 0.03% 74.66%, 0.02% 72.73%, 0.01% 70.69%, 0.01% 68.54%, 0% 66.27%, 0% 63.89%, 0% 61.38%, 0% 58.74%, 0% 55.96%, 0% 53.05%, 0% 50%, 0% 50%, 0% 46.95%, 0% 44.04%, 0% 41.26%, 0% 38.62%, 0% 36.11%, 0% 33.73%, 0.01% 31.46%, 0.01% 29.31%, 0.02% 27.27%, 0.03% 25.34%, 0.04% 23.52%, 0.06% 21.79%, 0.09% 20.16%, 0.11% 18.63%, 0.15% 17.18%, 0.2% 15.82%, 0.25% 14.54%, 0.31% 13.34%, 0.39% 12.22%, 0.48% 11.17%, 0.58% 10.19%, 0.7% 9.27%, 0.83% 8.42%, 0.99% 7.63%, 1.16% 6.89%, 1.36% 6.21%, 1.58% 5.59%, 1.83% 5.01%, 2.11% 4.47%, 2.41% 3.98%, 2.75% 3.53%, 3.13% 3.13%, 3.53% 2.75%, 3.98% 2.41%, 4.47% 2.11%, 5.01% 1.83%, 5.59% 1.58%, 6.21% 1.36%, 6.89% 1.16%, 7.63% 0.99%, 8.42% 0.83%, 9.27% 0.7%, 10.19% 0.58%, 11.17% 0.48%, 12.22% 0.39%, 13.34% 0.31%, 14.54% 0.25%, 15.82% 0.2%, 17.18% 0.15%, 18.63% 0.11%, 20.16% 0.09%, 21.79% 0.06%, 23.52% 0.04%, 25.34% 0.03%, 27.27% 0.02%, 29.31% 0.01%, 31.46% 0.01%, 33.73% 0%, 36.11% 0%, 38.62% 0%, 41.26% 0%, 44.04% 0%, 46.95% 0%, 50% 0%)';
|
|
57
|
+
|
|
51
58
|
let {
|
|
52
59
|
size = '100%',
|
|
53
60
|
toggleAnimationWithShift = false,
|
|
54
61
|
ripplesOptions: ripplesOptionsProp = {},
|
|
55
62
|
boundingBox = 'default',
|
|
63
|
+
squircle = false,
|
|
56
64
|
class: className = '',
|
|
57
65
|
onClick = undefined,
|
|
58
66
|
...restProps
|
|
59
67
|
}: Props = $props();
|
|
60
68
|
|
|
69
|
+
// Ligature positioning: original (square) vs squircle-adjusted base values
|
|
70
|
+
// Square: original positioning where ligature inner corners touch the square corners
|
|
71
|
+
const LIG_ORIG_W = 440;
|
|
72
|
+
const LIG_ORIG_H = 666;
|
|
73
|
+
const LIG_ORIG_L = 133.5;
|
|
74
|
+
const LIG_ORIG_T = -65.75;
|
|
75
|
+
const BLUR_PAD_ORIG = 50; // shadow extends 50px beyond ligature on each side
|
|
76
|
+
|
|
77
|
+
// Squircle: scaled to align inner corners with squircle boundary
|
|
78
|
+
// (base 94.46% * 1.023 scale, offset x=-6.5, y=7)
|
|
79
|
+
const LIG_SQRC_W = 425.2;
|
|
80
|
+
const LIG_SQRC_H = 643.6;
|
|
81
|
+
const LIG_SQRC_L = 129.5;
|
|
82
|
+
const LIG_SQRC_T = -47.6;
|
|
83
|
+
const BLUR_PAD_SQRC = 48.3;
|
|
84
|
+
|
|
85
|
+
// Select positioning values depending on squircle mode
|
|
86
|
+
let ligW = $derived(squircle ? LIG_SQRC_W : LIG_ORIG_W);
|
|
87
|
+
let ligH = $derived(squircle ? LIG_SQRC_H : LIG_ORIG_H);
|
|
88
|
+
let ligL = $derived(squircle ? LIG_SQRC_L : LIG_ORIG_L);
|
|
89
|
+
let ligT = $derived(squircle ? LIG_SQRC_T : LIG_ORIG_T);
|
|
90
|
+
|
|
91
|
+
// Shadow tracks ligature center + blur padding
|
|
92
|
+
let blurPad = $derived(squircle ? BLUR_PAD_SQRC : BLUR_PAD_ORIG);
|
|
93
|
+
let shadW = $derived(ligW + blurPad * 2);
|
|
94
|
+
let shadH = $derived(ligH + blurPad * 2);
|
|
95
|
+
let shadL = $derived(ligL - blurPad);
|
|
96
|
+
let shadT = $derived(ligT - blurPad);
|
|
97
|
+
|
|
61
98
|
// Use global animation state shared across ALL instances
|
|
62
99
|
let animated = $state(globalAnimated);
|
|
63
100
|
|
|
@@ -73,6 +110,9 @@
|
|
|
73
110
|
let animatedElements: Element[];
|
|
74
111
|
let animate: (time: number) => void;
|
|
75
112
|
|
|
113
|
+
// State for dimension bindings
|
|
114
|
+
let ripplesWidth = $state(0);
|
|
115
|
+
|
|
76
116
|
// Reactive effect to handle animation state changes from global store
|
|
77
117
|
$effect(() => {
|
|
78
118
|
if (animated) {
|
|
@@ -102,27 +142,71 @@
|
|
|
102
142
|
|
|
103
143
|
const logoAnimation: Attachment = (element) => {
|
|
104
144
|
animatedElements = [...element.children].filter((child) => child.classList.contains('animate'));
|
|
105
|
-
const ripplesElement = element.
|
|
145
|
+
const ripplesElement = element.getElementsByTagName('d-ripple')[0] as HTMLElement | undefined;
|
|
146
|
+
|
|
147
|
+
// Scaling constants for ripple calculations
|
|
148
|
+
const SCALING_CONSTANTS = {
|
|
149
|
+
// Resolution scaling
|
|
150
|
+
MIN_RESOLUTION: 128,
|
|
151
|
+
MAX_RESOLUTION: 512,
|
|
152
|
+
RESOLUTION_FACTOR: 0.8,
|
|
153
|
+
|
|
154
|
+
// Drop radius scaling (linear interpolation from 62px:2px to 800px:23.36px, capped at 20px)
|
|
155
|
+
MIN_DROP_RADIUS: 2,
|
|
156
|
+
MAX_DROP_RADIUS: 20,
|
|
157
|
+
DROP_RADIUS_MIN_SIZE: 62,
|
|
158
|
+
DROP_RADIUS_MAX_SIZE: 800,
|
|
159
|
+
DROP_RADIUS_RANGE: 21.36,
|
|
160
|
+
|
|
161
|
+
// Wave propagation scaling (2.0 at 500px down to 0.2 at 125px)
|
|
162
|
+
MIN_WAVE_PROPAGATION: 0.2,
|
|
163
|
+
MAX_WAVE_PROPAGATION: 2.0,
|
|
164
|
+
WAVE_PROP_REFERENCE_SIZE: 500,
|
|
165
|
+
WAVE_PROP_MIN_SIZE: 125,
|
|
166
|
+
WAVE_PROP_FACTOR: 1.5
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Scaling utility functions
|
|
170
|
+
function calculateResolution(width: number): number {
|
|
171
|
+
return Math.min(
|
|
172
|
+
SCALING_CONSTANTS.MAX_RESOLUTION,
|
|
173
|
+
Math.max(SCALING_CONSTANTS.MIN_RESOLUTION, width * SCALING_CONSTANTS.RESOLUTION_FACTOR)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function calculateDropRadius(width: number): number {
|
|
178
|
+
return Math.min(
|
|
179
|
+
SCALING_CONSTANTS.MAX_DROP_RADIUS,
|
|
180
|
+
Math.max(
|
|
181
|
+
SCALING_CONSTANTS.MIN_DROP_RADIUS,
|
|
182
|
+
SCALING_CONSTANTS.MIN_DROP_RADIUS +
|
|
183
|
+
((width - SCALING_CONSTANTS.DROP_RADIUS_MIN_SIZE) /
|
|
184
|
+
(SCALING_CONSTANTS.DROP_RADIUS_MAX_SIZE - SCALING_CONSTANTS.DROP_RADIUS_MIN_SIZE)) *
|
|
185
|
+
SCALING_CONSTANTS.DROP_RADIUS_RANGE
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
106
189
|
|
|
107
|
-
|
|
108
|
-
|
|
190
|
+
function calculateWavePropagation(width: number): number {
|
|
191
|
+
return Math.max(
|
|
192
|
+
SCALING_CONSTANTS.MIN_WAVE_PROPAGATION,
|
|
193
|
+
Math.min(
|
|
194
|
+
SCALING_CONSTANTS.MAX_WAVE_PROPAGATION,
|
|
195
|
+
SCALING_CONSTANTS.MAX_WAVE_PROPAGATION -
|
|
196
|
+
((SCALING_CONSTANTS.WAVE_PROP_REFERENCE_SIZE - width) /
|
|
197
|
+
(SCALING_CONSTANTS.WAVE_PROP_REFERENCE_SIZE - SCALING_CONSTANTS.WAVE_PROP_MIN_SIZE)) *
|
|
198
|
+
SCALING_CONSTANTS.WAVE_PROP_FACTOR
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Calculate initial scaling values
|
|
109
204
|
const elementSize = ripplesElement?.offsetWidth || 100;
|
|
110
|
-
const resolution = !ripplesElement
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
);
|
|
205
|
+
const resolution = !ripplesElement
|
|
206
|
+
? SCALING_CONSTANTS.MAX_RESOLUTION
|
|
207
|
+
: calculateResolution(elementSize);
|
|
208
|
+
const scaledDropRadius = calculateDropRadius(elementSize);
|
|
209
|
+
const scaledWavePropagation = calculateWavePropagation(elementSize);
|
|
126
210
|
|
|
127
211
|
const DEFAULT_RIPPLES_OPTIONS = {
|
|
128
212
|
resolution,
|
|
@@ -134,77 +218,77 @@
|
|
|
134
218
|
};
|
|
135
219
|
const rippleOptions = { ...DEFAULT_RIPPLES_OPTIONS, ...ripplesOptionsProp };
|
|
136
220
|
|
|
137
|
-
//
|
|
138
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
221
|
+
// Use Svelte dimension bindings for resize handling
|
|
139
222
|
let lastWidth = ripplesElement?.offsetWidth;
|
|
140
|
-
let lastHeight = ripplesElement?.offsetHeight;
|
|
141
223
|
let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
224
|
+
let hasExecutedLeading = $state(false);
|
|
142
225
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
226
|
+
// Extract ripples recreation logic for reuse
|
|
227
|
+
function recreateRipples(currentWidth: number) {
|
|
228
|
+
if (animated && ripplesElement) {
|
|
229
|
+
// Destroy old instance first
|
|
230
|
+
if (ripples) {
|
|
231
|
+
try {
|
|
232
|
+
ripples.destroy();
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.error('Error destroying ripples:', e);
|
|
235
|
+
}
|
|
236
|
+
ripples = null;
|
|
237
|
+
}
|
|
147
238
|
|
|
148
|
-
//
|
|
239
|
+
// Wait a frame before creating new instance to ensure cleanup
|
|
240
|
+
requestAnimationFrame(() => {
|
|
241
|
+
if (!ripples && animated && ripplesElement) {
|
|
242
|
+
// Calculate scaled values using utility functions
|
|
243
|
+
const newResolution = calculateResolution(currentWidth);
|
|
244
|
+
const newScaledDropRadius = calculateDropRadius(currentWidth);
|
|
245
|
+
const newScaledWavePropagation = calculateWavePropagation(currentWidth);
|
|
246
|
+
const newRippleOptions = {
|
|
247
|
+
...rippleOptions,
|
|
248
|
+
resolution: newResolution,
|
|
249
|
+
dropRadius: newScaledDropRadius,
|
|
250
|
+
wavePropagation: newScaledWavePropagation
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
ripples = new Ripples(ripplesElement, newRippleOptions);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
console.error('Error creating ripples:', e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Reactive effect to handle dimension changes with leading + trailing edge debouncing
|
|
264
|
+
$effect(() => {
|
|
265
|
+
if (ripplesWidth && animated && ripplesElement) {
|
|
266
|
+
const currentWidth = ripplesWidth;
|
|
267
|
+
|
|
268
|
+
// Only recreate if width actually changed significantly (more than 5px)
|
|
149
269
|
const widthChanged = Math.abs(currentWidth - (lastWidth || 0)) > 5;
|
|
150
|
-
const heightChanged = Math.abs(currentHeight - (lastHeight || 0)) > 5;
|
|
151
270
|
|
|
152
|
-
if (widthChanged
|
|
271
|
+
if (widthChanged) {
|
|
153
272
|
lastWidth = currentWidth;
|
|
154
|
-
lastHeight = currentHeight;
|
|
155
273
|
|
|
156
|
-
//
|
|
274
|
+
// LEADING EDGE: Execute immediately on first change in sequence
|
|
275
|
+
if (!hasExecutedLeading) {
|
|
276
|
+
hasExecutedLeading = true;
|
|
277
|
+
recreateRipples(currentWidth);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// TRAILING EDGE: Debounced execution to handle rapid changes
|
|
157
281
|
if (resizeTimeout) {
|
|
158
282
|
clearTimeout(resizeTimeout);
|
|
159
283
|
}
|
|
160
284
|
|
|
161
285
|
resizeTimeout = setTimeout(() => {
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
}
|
|
286
|
+
recreateRipples(currentWidth);
|
|
287
|
+
hasExecutedLeading = false; // Reset for next change sequence
|
|
203
288
|
}, 100); // 100ms debounce
|
|
204
289
|
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
208
292
|
|
|
209
293
|
let angle = $state(0);
|
|
210
294
|
let lastDropTime = $state(0);
|
|
@@ -235,10 +319,7 @@
|
|
|
235
319
|
const y = Math.random() * ripplesElement.offsetHeight;
|
|
236
320
|
// Use scaled drop radius for automatic drops
|
|
237
321
|
const currentSize = ripplesElement.offsetWidth;
|
|
238
|
-
const autoDropRadius =
|
|
239
|
-
20,
|
|
240
|
-
Math.max(2, 2 + ((currentSize - 62) / (800 - 62)) * 21.36)
|
|
241
|
-
);
|
|
322
|
+
const autoDropRadius = calculateDropRadius(currentSize);
|
|
242
323
|
// Scale strength based on size - ensure minimum visibility for small sizes
|
|
243
324
|
const sizeFactor = Math.max(0.7, Math.min(1, currentSize / 200)); // Never below 70%
|
|
244
325
|
const baseStrength = 0.1 + Math.random() * 0.04;
|
|
@@ -262,16 +343,14 @@
|
|
|
262
343
|
// Now we need to scale them for each element's actual size
|
|
263
344
|
for (const el of animatedElements) {
|
|
264
345
|
if (el.classList.contains('shadow')) {
|
|
265
|
-
// Shadow is
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
const scaleY = 800 / 766;
|
|
346
|
+
// Shadow is 510.1x723.6 (scaled 94.46% for squircle)
|
|
347
|
+
const scaleX = 800 / 510.1;
|
|
348
|
+
const scaleY = 800 / 723.6;
|
|
269
349
|
(el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
|
|
270
350
|
} else if (el.classList.contains('ligature')) {
|
|
271
|
-
// Ligature is
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
const scaleY = 800 / 666;
|
|
351
|
+
// Ligature is 415.6x629.1 (scaled 94.46% for squircle)
|
|
352
|
+
const scaleX = 800 / 415.6;
|
|
353
|
+
const scaleY = 800 / 629.1;
|
|
275
354
|
(el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
|
|
276
355
|
} else {
|
|
277
356
|
// Default for any other animated elements
|
|
@@ -301,9 +380,7 @@
|
|
|
301
380
|
console.error('Error destroying ripples on cleanup:', e);
|
|
302
381
|
}
|
|
303
382
|
}
|
|
304
|
-
|
|
305
|
-
resizeObserver.disconnect();
|
|
306
|
-
}
|
|
383
|
+
hasExecutedLeading = false; // Reset leading edge state
|
|
307
384
|
};
|
|
308
385
|
};
|
|
309
386
|
|
|
@@ -329,20 +406,38 @@
|
|
|
329
406
|
}
|
|
330
407
|
</script>
|
|
331
408
|
|
|
332
|
-
<logo-container
|
|
409
|
+
<logo-container
|
|
410
|
+
style:--size={size}
|
|
411
|
+
class="{boundingBox} {className}"
|
|
412
|
+
class:squircle
|
|
413
|
+
role="none"
|
|
414
|
+
{...restProps}
|
|
415
|
+
>
|
|
333
416
|
<grid-logo {@attach logoAnimation}>
|
|
334
|
-
<img
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
417
|
+
<img
|
|
418
|
+
class="animate shadow"
|
|
419
|
+
alt=""
|
|
420
|
+
src={logoShadow}
|
|
421
|
+
style="width:calc(100% * {shadW} / 532);height:calc(100% * {shadH} / 532);left:calc(100% * {shadL} / 532);top:calc(100% * {shadT} / 532)"
|
|
422
|
+
/>
|
|
423
|
+
<img class="glow" alt="" src={squircle ? logoGlowSquircle : logoGlow} />
|
|
424
|
+
<d-ripple
|
|
425
|
+
class="square"
|
|
338
426
|
style:background-image={`url("${logoSquare}")`}
|
|
427
|
+
style:clip-path={squircle ? SQUIRCLE_CLIP : 'none'}
|
|
428
|
+
bind:offsetWidth={ripplesWidth}
|
|
339
429
|
onclick={handleClick}
|
|
340
430
|
onkeydown={handleClick}
|
|
341
431
|
role="button"
|
|
342
432
|
tabindex="0"
|
|
343
433
|
aria-label="Toggle logo animation"
|
|
344
|
-
></
|
|
345
|
-
<img
|
|
434
|
+
></d-ripple>
|
|
435
|
+
<img
|
|
436
|
+
class="animate ligature"
|
|
437
|
+
alt=""
|
|
438
|
+
src={logoLigature}
|
|
439
|
+
style="width:calc(100% * {ligW} / 532);height:calc(100% * {ligH} / 532);left:calc(100% * {ligL} / 532);top:calc(100% * {ligT} / 532)"
|
|
440
|
+
/>
|
|
346
441
|
</grid-logo>
|
|
347
442
|
</logo-container>
|
|
348
443
|
|
|
@@ -358,26 +453,55 @@
|
|
|
358
453
|
-moz-user-select: none;
|
|
359
454
|
-ms-user-select: none;
|
|
360
455
|
overflow: visible;
|
|
361
|
-
}
|
|
362
456
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
457
|
+
/* Cropped mode - match ellipse aspect ratio: 723.08875/812.58868 ≈ 0.8906 */
|
|
458
|
+
&.cropped {
|
|
459
|
+
aspect-ratio: 0.8906;
|
|
460
|
+
}
|
|
367
461
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
462
|
+
/* Square bounding box mode - grid fills the container */
|
|
463
|
+
&.square grid-logo {
|
|
464
|
+
width: 100%;
|
|
465
|
+
left: 0;
|
|
466
|
+
top: 0;
|
|
467
|
+
}
|
|
372
468
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
469
|
+
/* Default bounding box mode - grid is scaled down to leave some padding */
|
|
470
|
+
&.default grid-logo {
|
|
471
|
+
/* Grid is 1/1.2519 = 79.88% of container to account for padding */
|
|
472
|
+
width: calc(100% / 1.2519);
|
|
473
|
+
/* Center within container */
|
|
474
|
+
left: calc((100% - 100% / 1.2519) / 2);
|
|
475
|
+
top: calc((100% - 100% / 1.2519) / 2);
|
|
476
|
+
}
|
|
377
477
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
478
|
+
/* Encircled bounding box mode - grid is scaled down more to leave full padding */
|
|
479
|
+
&.encircled grid-logo {
|
|
480
|
+
/* Grid is 1/1.5037 = 66.5% of container (532/800) */
|
|
481
|
+
width: calc(100% / 1.5037);
|
|
482
|
+
/* Center within container */
|
|
483
|
+
left: calc((100% - 100% / 1.5037) / 2);
|
|
484
|
+
top: calc((100% - 100% / 1.5037) / 2);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Squircle + encircled: scale up 1.04x to compensate for smaller ligature */
|
|
488
|
+
&.encircled.squircle grid-logo {
|
|
489
|
+
width: calc(100% / 1.5037 * 1.04);
|
|
490
|
+
left: calc((100% - 100% / 1.5037 * 1.04) / 2);
|
|
491
|
+
top: calc((100% - 100% / 1.5037 * 1.04) / 2);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* Cropped bounding box mode - grid scaled and positioned to match reference SVG */
|
|
495
|
+
&.cropped grid-logo {
|
|
496
|
+
/* Square is 1131.371px in a 1447px wide container = 78.2% */
|
|
497
|
+
width: 78.2%;
|
|
498
|
+
/* Position calculations from SVG transforms */
|
|
499
|
+
/* Final square position after transforms: x=122.18, y=247.73 */
|
|
500
|
+
/* Left offset: 122.18/1447 = 8.44% */
|
|
501
|
+
left: 8.44%;
|
|
502
|
+
/* Top offset: 247.73/1626.9 = 15.23% */
|
|
503
|
+
top: 15.23%;
|
|
504
|
+
}
|
|
381
505
|
}
|
|
382
506
|
|
|
383
507
|
/* Grid that holds all the logo elements */
|
|
@@ -389,58 +513,23 @@
|
|
|
389
513
|
aspect-ratio: 1;
|
|
390
514
|
|
|
391
515
|
img {
|
|
392
|
-
|
|
393
|
-
max-width: unset !important;
|
|
516
|
+
max-width: unset;
|
|
394
517
|
}
|
|
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
518
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
519
|
+
/* Individual logo elements positioned relative to the square */
|
|
520
|
+
> * {
|
|
521
|
+
position: absolute;
|
|
522
|
+
grid-column: 1 / 2;
|
|
523
|
+
grid-row: 1 / 2;
|
|
524
|
+
will-change: auto;
|
|
525
|
+
}
|
|
440
526
|
}
|
|
441
527
|
|
|
442
528
|
/* Square image - base element at 532x532 */
|
|
443
|
-
|
|
529
|
+
/* Uses <d-ripple> custom element to avoid classless CSS library interference */
|
|
530
|
+
/* Still needs resets for [role="button"] targeting by classless CSS libraries */
|
|
531
|
+
d-ripple.square {
|
|
532
|
+
display: block;
|
|
444
533
|
width: 100%;
|
|
445
534
|
aspect-ratio: 1;
|
|
446
535
|
background-size: contain;
|
|
@@ -450,16 +539,23 @@
|
|
|
450
539
|
cursor: pointer;
|
|
451
540
|
outline: none;
|
|
452
541
|
|
|
453
|
-
/*
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
border
|
|
457
|
-
|
|
458
|
-
box-shadow: none
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
542
|
+
/* Reset [role="button"] styles from classless CSS libraries */
|
|
543
|
+
margin: 0;
|
|
544
|
+
padding: 0;
|
|
545
|
+
border: none;
|
|
546
|
+
border-radius: 0;
|
|
547
|
+
box-shadow: none;
|
|
548
|
+
background-color: transparent;
|
|
549
|
+
color: inherit;
|
|
550
|
+
font: inherit;
|
|
551
|
+
text-align: inherit;
|
|
552
|
+
text-decoration: none;
|
|
553
|
+
line-height: inherit;
|
|
554
|
+
|
|
555
|
+
&:focus {
|
|
556
|
+
outline: none;
|
|
557
|
+
box-shadow: none;
|
|
558
|
+
}
|
|
463
559
|
}
|
|
464
560
|
|
|
465
561
|
/* Glow - 647x647, centered on square */
|
|
@@ -474,28 +570,14 @@
|
|
|
474
570
|
pointer-events: none;
|
|
475
571
|
}
|
|
476
572
|
|
|
477
|
-
/* Shadow -
|
|
573
|
+
/* Shadow - dimensions/position set via inline style for dynamic ligature adjustment */
|
|
478
574
|
.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
575
|
z-index: 0;
|
|
487
|
-
/* Shadow should not capture clicks */
|
|
488
576
|
pointer-events: none;
|
|
489
577
|
}
|
|
490
578
|
|
|
491
|
-
/* Ligature -
|
|
579
|
+
/* Ligature - dimensions/position set via inline style */
|
|
492
580
|
.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
581
|
z-index: 3;
|
|
500
582
|
pointer-events: none;
|
|
501
583
|
}
|
|
@@ -7,6 +7,7 @@ interface Props {
|
|
|
7
7
|
toggleAnimationWithShift?: boolean;
|
|
8
8
|
ripplesOptions?: RipplesOptions;
|
|
9
9
|
boundingBox?: 'square' | 'default' | 'cropped' | 'encircled';
|
|
10
|
+
squircle?: boolean;
|
|
10
11
|
class?: string;
|
|
11
12
|
onClick?: (event: MouseEvent | KeyboardEvent) => void;
|
|
12
13
|
[key: string]: unknown;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKLCH-based color transforms for icon color modes.
|
|
3
|
+
*
|
|
4
|
+
* Uses culori for perceptually uniform color manipulation.
|
|
5
|
+
* All transforms operate on individual CSS color strings.
|
|
6
|
+
*/
|
|
7
|
+
import type { IconColorMode } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Transform a single CSS color to grayscale using OKLCH.
|
|
10
|
+
* Sets chroma to 0, preserving perceptual lightness.
|
|
11
|
+
*/
|
|
12
|
+
export declare function toGrayscale(color: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Transform a single CSS color to grayscale, then tint toward a target color.
|
|
15
|
+
*
|
|
16
|
+
* The tint color's hue and chroma influence the result, while the original
|
|
17
|
+
* color's lightness is preserved. This unifies a palette while keeping contrast.
|
|
18
|
+
*/
|
|
19
|
+
export declare function toGrayscaleTint(color: string, tintColor: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Remap a single CSS color to a target hue, optionally overriding saturation.
|
|
22
|
+
*
|
|
23
|
+
* Preserves the original lightness. If saturation is provided, it's used as
|
|
24
|
+
* a percentage of the maximum chroma; otherwise, the original chroma is preserved.
|
|
25
|
+
*/
|
|
26
|
+
export declare function remapHue(color: string, targetHue: number, targetSaturation?: number): string;
|
|
27
|
+
/**
|
|
28
|
+
* Apply a full IconColorMode transformation to SVG content.
|
|
29
|
+
*
|
|
30
|
+
* Handles all modes: 'auto', 'original', 'monochrome', 'grayscale',
|
|
31
|
+
* 'grayscale-tint', and { hue, saturation? }.
|
|
32
|
+
*
|
|
33
|
+
* Color transforms operate on fill/stroke attribute values and style properties,
|
|
34
|
+
* replacing each color with its transformed equivalent.
|
|
35
|
+
*/
|
|
36
|
+
export declare function applyColorMode(svgContent: string, isMonochrome: boolean, colorMode: IconColorMode, iconColor: string): string;
|