@leftium/logo 0.0.2 → 0.2.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/dist/AppLogo.svelte +146 -0
- package/dist/AppLogo.svelte.d.ts +7 -0
- package/dist/LeftiumLogo.svelte +267 -186
- package/dist/LeftiumLogo.svelte.d.ts +1 -0
- package/dist/app-logo/color-transform.d.ts +40 -0
- package/dist/app-logo/color-transform.js +142 -0
- package/dist/app-logo/config-serialization.d.ts +80 -0
- package/dist/app-logo/config-serialization.js +489 -0
- package/dist/app-logo/defaults.d.ts +60 -0
- package/dist/app-logo/defaults.js +55 -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 +160 -0
- package/dist/app-logo/iconify.d.ts +35 -0
- package/dist/app-logo/iconify.js +223 -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 +39 -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/leftium-logo/generate-svg.d.ts +29 -0
- package/dist/leftium-logo/generate-svg.js +470 -0
- package/dist/tooltip.d.ts +18 -0
- package/dist/tooltip.js +38 -0
- package/dist/webgl-ripples/webgl-ripples.d.ts +0 -4
- package/dist/webgl-ripples/webgl-ripples.js +1 -1
- package/package.json +35 -20
package/dist/LeftiumLogo.svelte
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import { Ripples, type RipplesOptions } from './webgl-ripples/webgl-ripples.js';
|
|
36
36
|
|
|
37
37
|
import logoGlow from './assets/logo-parts/glow.svg';
|
|
38
|
+
import logoGlowSquircle from './assets/logo-parts/glow-squircle.svg';
|
|
38
39
|
import logoLigature from './assets/logo-parts/ligature.svg';
|
|
39
40
|
import logoShadow from './assets/logo-parts/shadow.svg';
|
|
40
41
|
import logoSquare from './assets/logo-parts/square.svg?inline';
|
|
@@ -44,21 +45,56 @@
|
|
|
44
45
|
toggleAnimationWithShift?: boolean;
|
|
45
46
|
ripplesOptions?: RipplesOptions;
|
|
46
47
|
boundingBox?: 'square' | 'default' | 'cropped' | 'encircled';
|
|
48
|
+
squircle?: boolean;
|
|
47
49
|
class?: string;
|
|
48
50
|
onClick?: (event: MouseEvent | KeyboardEvent) => void;
|
|
49
51
|
[key: string]: unknown; // Allow any additional props
|
|
50
52
|
}
|
|
51
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
|
+
|
|
52
58
|
let {
|
|
53
59
|
size = '100%',
|
|
54
60
|
toggleAnimationWithShift = false,
|
|
55
61
|
ripplesOptions: ripplesOptionsProp = {},
|
|
56
62
|
boundingBox = 'default',
|
|
63
|
+
squircle = false,
|
|
57
64
|
class: className = '',
|
|
58
65
|
onClick = undefined,
|
|
59
66
|
...restProps
|
|
60
67
|
}: Props = $props();
|
|
61
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
|
+
|
|
62
98
|
// Use global animation state shared across ALL instances
|
|
63
99
|
let animated = $state(globalAnimated);
|
|
64
100
|
|
|
@@ -74,6 +110,9 @@
|
|
|
74
110
|
let animatedElements: Element[];
|
|
75
111
|
let animate: (time: number) => void;
|
|
76
112
|
|
|
113
|
+
// State for dimension bindings
|
|
114
|
+
let ripplesWidth = $state(0);
|
|
115
|
+
|
|
77
116
|
// Reactive effect to handle animation state changes from global store
|
|
78
117
|
$effect(() => {
|
|
79
118
|
if (animated) {
|
|
@@ -103,27 +142,71 @@
|
|
|
103
142
|
|
|
104
143
|
const logoAnimation: Attachment = (element) => {
|
|
105
144
|
animatedElements = [...element.children].filter((child) => child.classList.contains('animate'));
|
|
106
|
-
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
|
+
}
|
|
107
189
|
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
110
204
|
const elementSize = ripplesElement?.offsetWidth || 100;
|
|
111
|
-
const resolution = !ripplesElement
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const scaledDropRadius = Math.min(
|
|
117
|
-
20,
|
|
118
|
-
Math.max(2, 2 + ((elementSize - 62) / (800 - 62)) * 21.36)
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// Scale wave propagation based on element size: 2.0 at 500px down to 0.2 at 125px
|
|
122
|
-
// Linear interpolation for sizes 500px down to 125px, then clamp at 0.2 for smaller
|
|
123
|
-
const scaledWavePropagation = Math.max(
|
|
124
|
-
0.2,
|
|
125
|
-
Math.min(2.0, 2.0 - ((500 - elementSize) / (500 - 125)) * 1.5)
|
|
126
|
-
);
|
|
205
|
+
const resolution = !ripplesElement
|
|
206
|
+
? SCALING_CONSTANTS.MAX_RESOLUTION
|
|
207
|
+
: calculateResolution(elementSize);
|
|
208
|
+
const scaledDropRadius = calculateDropRadius(elementSize);
|
|
209
|
+
const scaledWavePropagation = calculateWavePropagation(elementSize);
|
|
127
210
|
|
|
128
211
|
const DEFAULT_RIPPLES_OPTIONS = {
|
|
129
212
|
resolution,
|
|
@@ -135,77 +218,77 @@
|
|
|
135
218
|
};
|
|
136
219
|
const rippleOptions = { ...DEFAULT_RIPPLES_OPTIONS, ...ripplesOptionsProp };
|
|
137
220
|
|
|
138
|
-
//
|
|
139
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
221
|
+
// Use Svelte dimension bindings for resize handling
|
|
140
222
|
let lastWidth = ripplesElement?.offsetWidth;
|
|
141
|
-
let lastHeight = ripplesElement?.offsetHeight;
|
|
142
223
|
let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
224
|
+
let hasExecutedLeading = $state(false);
|
|
143
225
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
}
|
|
148
238
|
|
|
149
|
-
//
|
|
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)
|
|
150
269
|
const widthChanged = Math.abs(currentWidth - (lastWidth || 0)) > 5;
|
|
151
|
-
const heightChanged = Math.abs(currentHeight - (lastHeight || 0)) > 5;
|
|
152
270
|
|
|
153
|
-
if (widthChanged
|
|
271
|
+
if (widthChanged) {
|
|
154
272
|
lastWidth = currentWidth;
|
|
155
|
-
lastHeight = currentHeight;
|
|
156
273
|
|
|
157
|
-
//
|
|
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
|
|
158
281
|
if (resizeTimeout) {
|
|
159
282
|
clearTimeout(resizeTimeout);
|
|
160
283
|
}
|
|
161
284
|
|
|
162
285
|
resizeTimeout = setTimeout(() => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (ripples) {
|
|
166
|
-
try {
|
|
167
|
-
ripples.destroy();
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.error('Error destroying ripples:', e);
|
|
170
|
-
}
|
|
171
|
-
ripples = null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Wait a frame before creating new instance to ensure cleanup
|
|
175
|
-
requestAnimationFrame(() => {
|
|
176
|
-
if (!ripples && animated && ripplesElement) {
|
|
177
|
-
// Higher resolution for smaller sizes to avoid blurriness
|
|
178
|
-
const newResolution = Math.min(512, Math.max(128, currentWidth * 0.8));
|
|
179
|
-
// Scale drop radius and wave propagation for new size
|
|
180
|
-
// Linear interpolation from 62px:2px to 800px:23.36px, capped at 20px
|
|
181
|
-
const newScaledDropRadius = Math.min(
|
|
182
|
-
20,
|
|
183
|
-
Math.max(2, 2 + ((currentWidth - 62) / (800 - 62)) * 21.36)
|
|
184
|
-
);
|
|
185
|
-
const newScaledWavePropagation = Math.max(
|
|
186
|
-
0.2,
|
|
187
|
-
Math.min(2.0, 2.0 - ((500 - currentWidth) / (500 - 125)) * 1.5)
|
|
188
|
-
);
|
|
189
|
-
const newRippleOptions = {
|
|
190
|
-
...rippleOptions,
|
|
191
|
-
resolution: newResolution,
|
|
192
|
-
dropRadius: newScaledDropRadius,
|
|
193
|
-
wavePropagation: newScaledWavePropagation
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
ripples = new Ripples(ripplesElement, newRippleOptions);
|
|
198
|
-
} catch (e) {
|
|
199
|
-
console.error('Error creating ripples:', e);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
}
|
|
286
|
+
recreateRipples(currentWidth);
|
|
287
|
+
hasExecutedLeading = false; // Reset for next change sequence
|
|
204
288
|
}, 100); // 100ms debounce
|
|
205
289
|
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
209
292
|
|
|
210
293
|
let angle = $state(0);
|
|
211
294
|
let lastDropTime = $state(0);
|
|
@@ -236,10 +319,7 @@
|
|
|
236
319
|
const y = Math.random() * ripplesElement.offsetHeight;
|
|
237
320
|
// Use scaled drop radius for automatic drops
|
|
238
321
|
const currentSize = ripplesElement.offsetWidth;
|
|
239
|
-
const autoDropRadius =
|
|
240
|
-
20,
|
|
241
|
-
Math.max(2, 2 + ((currentSize - 62) / (800 - 62)) * 21.36)
|
|
242
|
-
);
|
|
322
|
+
const autoDropRadius = calculateDropRadius(currentSize);
|
|
243
323
|
// Scale strength based on size - ensure minimum visibility for small sizes
|
|
244
324
|
const sizeFactor = Math.max(0.7, Math.min(1, currentSize / 200)); // Never below 70%
|
|
245
325
|
const baseStrength = 0.1 + Math.random() * 0.04;
|
|
@@ -263,16 +343,14 @@
|
|
|
263
343
|
// Now we need to scale them for each element's actual size
|
|
264
344
|
for (const el of animatedElements) {
|
|
265
345
|
if (el.classList.contains('shadow')) {
|
|
266
|
-
// Shadow is
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
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;
|
|
270
349
|
(el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
|
|
271
350
|
} else if (el.classList.contains('ligature')) {
|
|
272
|
-
// Ligature is
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
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;
|
|
276
354
|
(el as HTMLElement).style.transform = `translate(${dx * scaleX}%, ${dy * scaleY}%)`;
|
|
277
355
|
} else {
|
|
278
356
|
// Default for any other animated elements
|
|
@@ -302,9 +380,7 @@
|
|
|
302
380
|
console.error('Error destroying ripples on cleanup:', e);
|
|
303
381
|
}
|
|
304
382
|
}
|
|
305
|
-
|
|
306
|
-
resizeObserver.disconnect();
|
|
307
|
-
}
|
|
383
|
+
hasExecutedLeading = false; // Reset leading edge state
|
|
308
384
|
};
|
|
309
385
|
};
|
|
310
386
|
|
|
@@ -330,20 +406,38 @@
|
|
|
330
406
|
}
|
|
331
407
|
</script>
|
|
332
408
|
|
|
333
|
-
<logo-container
|
|
409
|
+
<logo-container
|
|
410
|
+
style:--size={size}
|
|
411
|
+
class="{boundingBox} {className}"
|
|
412
|
+
class:squircle
|
|
413
|
+
role="none"
|
|
414
|
+
{...restProps}
|
|
415
|
+
>
|
|
334
416
|
<grid-logo {@attach logoAnimation}>
|
|
335
|
-
<img
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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"
|
|
339
426
|
style:background-image={`url("${logoSquare}")`}
|
|
427
|
+
style:clip-path={squircle ? SQUIRCLE_CLIP : 'none'}
|
|
428
|
+
bind:offsetWidth={ripplesWidth}
|
|
340
429
|
onclick={handleClick}
|
|
341
430
|
onkeydown={handleClick}
|
|
342
431
|
role="button"
|
|
343
432
|
tabindex="0"
|
|
344
433
|
aria-label="Toggle logo animation"
|
|
345
|
-
></
|
|
346
|
-
<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
|
+
/>
|
|
347
441
|
</grid-logo>
|
|
348
442
|
</logo-container>
|
|
349
443
|
|
|
@@ -359,26 +453,55 @@
|
|
|
359
453
|
-moz-user-select: none;
|
|
360
454
|
-ms-user-select: none;
|
|
361
455
|
overflow: visible;
|
|
362
|
-
}
|
|
363
456
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
457
|
+
/* Cropped mode - match ellipse aspect ratio: 723.08875/812.58868 ≈ 0.8906 */
|
|
458
|
+
&.cropped {
|
|
459
|
+
aspect-ratio: 0.8906;
|
|
460
|
+
}
|
|
368
461
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
462
|
+
/* Square bounding box mode - grid fills the container */
|
|
463
|
+
&.square grid-logo {
|
|
464
|
+
width: 100%;
|
|
465
|
+
left: 0;
|
|
466
|
+
top: 0;
|
|
467
|
+
}
|
|
373
468
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
+
}
|
|
378
477
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
505
|
}
|
|
383
506
|
|
|
384
507
|
/* Grid that holds all the logo elements */
|
|
@@ -390,58 +513,23 @@
|
|
|
390
513
|
aspect-ratio: 1;
|
|
391
514
|
|
|
392
515
|
img {
|
|
393
|
-
|
|
394
|
-
max-width: unset !important;
|
|
516
|
+
max-width: unset;
|
|
395
517
|
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/* Square bounding box mode - grid fills the container */
|
|
399
|
-
logo-container.square grid-logo {
|
|
400
|
-
width: 100%;
|
|
401
|
-
left: 0;
|
|
402
|
-
top: 0;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/* Default bounding box mode - grid is scaled down to leave some padding */
|
|
406
|
-
logo-container.default grid-logo {
|
|
407
|
-
/* Grid is 1/1.2519 = 79.88% of container to account for padding */
|
|
408
|
-
width: calc(100% / 1.2519);
|
|
409
|
-
/* Center within container */
|
|
410
|
-
left: calc((100% - 100% / 1.2519) / 2);
|
|
411
|
-
top: calc((100% - 100% / 1.2519) / 2);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/* Encircled bounding box mode - grid is scaled down more to leave full padding */
|
|
415
|
-
logo-container.encircled grid-logo {
|
|
416
|
-
/* Grid is 1/1.5037 = 66.5% of container (532/800) */
|
|
417
|
-
width: calc(100% / 1.5037);
|
|
418
|
-
/* Center within container */
|
|
419
|
-
left: calc((100% - 100% / 1.5037) / 2);
|
|
420
|
-
top: calc((100% - 100% / 1.5037) / 2);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/* Cropped bounding box mode - grid scaled and positioned to match reference SVG */
|
|
424
|
-
logo-container.cropped grid-logo {
|
|
425
|
-
/* Square is 1131.371px in a 1447px wide container = 78.2% */
|
|
426
|
-
width: 78.2%;
|
|
427
|
-
/* Position calculations from SVG transforms */
|
|
428
|
-
/* Final square position after transforms: x=122.18, y=247.73 */
|
|
429
|
-
/* Left offset: 122.18/1447 = 8.44% */
|
|
430
|
-
left: 8.44%;
|
|
431
|
-
/* Top offset: 247.73/1626.9 = 15.23% */
|
|
432
|
-
top: 15.23%;
|
|
433
|
-
}
|
|
434
518
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
}
|
|
441
526
|
}
|
|
442
527
|
|
|
443
528
|
/* Square image - base element at 532x532 */
|
|
444
|
-
|
|
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;
|
|
445
533
|
width: 100%;
|
|
446
534
|
aspect-ratio: 1;
|
|
447
535
|
background-size: contain;
|
|
@@ -451,16 +539,23 @@
|
|
|
451
539
|
cursor: pointer;
|
|
452
540
|
outline: none;
|
|
453
541
|
|
|
454
|
-
/*
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
border
|
|
458
|
-
|
|
459
|
-
box-shadow: none
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
}
|
|
464
559
|
}
|
|
465
560
|
|
|
466
561
|
/* Glow - 647x647, centered on square */
|
|
@@ -475,28 +570,14 @@
|
|
|
475
570
|
pointer-events: none;
|
|
476
571
|
}
|
|
477
572
|
|
|
478
|
-
/* Shadow -
|
|
573
|
+
/* Shadow - dimensions/position set via inline style for dynamic ligature adjustment */
|
|
479
574
|
.shadow {
|
|
480
|
-
width: calc(100% * 540 / 532);
|
|
481
|
-
height: calc(100% * 766 / 532);
|
|
482
|
-
/* Shadow should be centered on ligature */
|
|
483
|
-
/* Ligature is 440x666, shadow is 540x766 */
|
|
484
|
-
/* Shadow is (540-440)/2 = 50px wider on each side, (766-666)/2 = 50px taller on each side */
|
|
485
|
-
left: calc(100% * (133 - 50) / 532); /* Center shadow on ligature */
|
|
486
|
-
top: calc(100% * (-66 - 50) / 532);
|
|
487
575
|
z-index: 0;
|
|
488
|
-
/* Shadow should not capture clicks */
|
|
489
576
|
pointer-events: none;
|
|
490
577
|
}
|
|
491
578
|
|
|
492
|
-
/* Ligature -
|
|
579
|
+
/* Ligature - dimensions/position set via inline style */
|
|
493
580
|
.ligature {
|
|
494
|
-
width: calc(100% * 440 / 532);
|
|
495
|
-
height: calc(100% * 666 / 532);
|
|
496
|
-
/* Ligature extends to the right and below the square */
|
|
497
|
-
/* Looking at the SVG path, the ligature starts at top-left and extends right and down */
|
|
498
|
-
left: calc(100% * 133.5 / 532);
|
|
499
|
-
top: calc(100% * -65.75 / 532);
|
|
500
581
|
z-index: 3;
|
|
501
582
|
pointer-events: none;
|
|
502
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,40 @@
|
|
|
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
|
+
* @param lightness Optional lightness multiplier (0-200), default 100 = no change
|
|
12
|
+
*/
|
|
13
|
+
export declare function toGrayscale(color: string, lightness?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Transform a single CSS color to grayscale, then tint toward a target color.
|
|
16
|
+
*
|
|
17
|
+
* The tint color's hue and chroma influence the result, while the original
|
|
18
|
+
* color's lightness is preserved. This unifies a palette while keeping contrast.
|
|
19
|
+
* @param lightness Optional lightness multiplier (0-200), default 100 = no change
|
|
20
|
+
*/
|
|
21
|
+
export declare function toGrayscaleTint(color: string, tintColor: string, lightness?: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* Remap a single CSS color to a target hue, optionally overriding saturation.
|
|
24
|
+
*
|
|
25
|
+
* Preserves the original lightness. If saturation is provided, it's used as
|
|
26
|
+
* a percentage of the maximum chroma; otherwise, the original chroma is preserved.
|
|
27
|
+
*/
|
|
28
|
+
export declare function remapHue(color: string, targetHue: number, targetSaturation?: number): string;
|
|
29
|
+
/**
|
|
30
|
+
* Apply a full IconColorMode transformation to SVG content.
|
|
31
|
+
*
|
|
32
|
+
* Handles all modes: 'auto', 'original', 'monochrome', 'grayscale',
|
|
33
|
+
* 'grayscale-tint', and { hue, saturation? }.
|
|
34
|
+
*
|
|
35
|
+
* Color transforms operate on fill/stroke attribute values and style properties,
|
|
36
|
+
* replacing each color with its transformed equivalent.
|
|
37
|
+
*
|
|
38
|
+
* @param grayscaleLightness Lightness multiplier for grayscale modes (0-200), default 100
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyColorMode(svgContent: string, isMonochrome: boolean, colorMode: IconColorMode, iconColor: string, grayscaleLightness?: number): string;
|