@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.
Files changed (34) hide show
  1. package/dist/AppLogo.svelte +146 -0
  2. package/dist/AppLogo.svelte.d.ts +7 -0
  3. package/dist/LeftiumLogo.svelte +267 -186
  4. package/dist/LeftiumLogo.svelte.d.ts +1 -0
  5. package/dist/app-logo/color-transform.d.ts +40 -0
  6. package/dist/app-logo/color-transform.js +142 -0
  7. package/dist/app-logo/config-serialization.d.ts +80 -0
  8. package/dist/app-logo/config-serialization.js +489 -0
  9. package/dist/app-logo/defaults.d.ts +60 -0
  10. package/dist/app-logo/defaults.js +55 -0
  11. package/dist/app-logo/generate-favicon-set.d.ts +44 -0
  12. package/dist/app-logo/generate-favicon-set.js +97 -0
  13. package/dist/app-logo/generate-ico.d.ts +18 -0
  14. package/dist/app-logo/generate-ico.js +63 -0
  15. package/dist/app-logo/generate-png.d.ts +16 -0
  16. package/dist/app-logo/generate-png.js +60 -0
  17. package/dist/app-logo/generate-svg.d.ts +9 -0
  18. package/dist/app-logo/generate-svg.js +160 -0
  19. package/dist/app-logo/iconify.d.ts +35 -0
  20. package/dist/app-logo/iconify.js +223 -0
  21. package/dist/app-logo/squircle.d.ts +43 -0
  22. package/dist/app-logo/squircle.js +213 -0
  23. package/dist/app-logo/types.d.ts +39 -0
  24. package/dist/app-logo/types.js +1 -0
  25. package/dist/assets/logo-parts/glow-squircle.svg +44 -0
  26. package/dist/index.d.ts +8 -3
  27. package/dist/index.js +9 -3
  28. package/dist/leftium-logo/generate-svg.d.ts +29 -0
  29. package/dist/leftium-logo/generate-svg.js +470 -0
  30. package/dist/tooltip.d.ts +18 -0
  31. package/dist/tooltip.js +38 -0
  32. package/dist/webgl-ripples/webgl-ripples.d.ts +0 -4
  33. package/dist/webgl-ripples/webgl-ripples.js +1 -1
  34. package/package.json +35 -20
@@ -0,0 +1,223 @@
1
+ import { detectIconSource, DEFAULT_EMOJI_STYLE } from './defaults.js';
2
+ /** Cached Iconify SVG strings keyed by icon ID */
3
+ const iconCache = new Map();
4
+ /** Cached emoji slug resolution keyed by `${prefix}:${emoji}` */
5
+ const emojiSlugCache = new Map();
6
+ /**
7
+ * Fetch an Iconify icon SVG by its ID (e.g. "mdi:rocket-launch").
8
+ * Results are cached in memory for the session.
9
+ */
10
+ async function fetchIconifySvg(iconId) {
11
+ const cached = iconCache.get(iconId);
12
+ if (cached)
13
+ return cached;
14
+ const [prefix, name] = iconId.split(':');
15
+ if (!prefix || !name) {
16
+ throw new Error(`Invalid Iconify icon ID: "${iconId}". Expected format: "prefix:name"`);
17
+ }
18
+ const url = `https://api.iconify.design/${prefix}/${name}.svg`;
19
+ const response = await fetch(url);
20
+ if (!response.ok) {
21
+ throw new Error(`Failed to fetch icon "${iconId}": ${response.status} ${response.statusText}`);
22
+ }
23
+ const svg = await response.text();
24
+ iconCache.set(iconId, svg);
25
+ return svg;
26
+ }
27
+ /**
28
+ * Decode a data URL to an SVG string.
29
+ * Supports both base64 and plain-text encoded data URLs.
30
+ */
31
+ function decodeDataUrl(dataUrl) {
32
+ // data:image/svg+xml;base64,PHN2Zy...
33
+ // data:image/svg+xml,%3Csvg...
34
+ // data:image/svg+xml;utf8,<svg...
35
+ const commaIndex = dataUrl.indexOf(',');
36
+ if (commaIndex === -1) {
37
+ throw new Error('Invalid data URL: no comma separator found');
38
+ }
39
+ const meta = dataUrl.substring(0, commaIndex);
40
+ const data = dataUrl.substring(commaIndex + 1);
41
+ if (meta.includes(';base64')) {
42
+ return atob(data);
43
+ }
44
+ // URL-encoded or UTF-8
45
+ return decodeURIComponent(data);
46
+ }
47
+ /**
48
+ * Parse viewBox from an SVG string. Falls back to "0 0 24 24" (Iconify default).
49
+ */
50
+ function parseViewBox(svg) {
51
+ const match = svg.match(/viewBox=["']([^"']+)["']/);
52
+ return match?.[1] ?? '0 0 24 24';
53
+ }
54
+ /**
55
+ * Extract the inner content of an SVG element (everything between <svg> and </svg>).
56
+ */
57
+ function extractSvgContent(svg) {
58
+ // Remove the outer <svg ...> and </svg> tags
59
+ const openTagEnd = svg.indexOf('>');
60
+ const closeTagStart = svg.lastIndexOf('</svg>');
61
+ if (openTagEnd === -1 || closeTagStart === -1) {
62
+ return svg; // Return as-is if we can't parse
63
+ }
64
+ return svg.substring(openTagEnd + 1, closeTagStart).trim();
65
+ }
66
+ /**
67
+ * Detect whether an SVG is monochrome (uses currentColor) or multicolor (hardcoded fills).
68
+ *
69
+ * Heuristic: if the SVG contains "currentColor" it's monochrome.
70
+ * If it contains hardcoded color values (hex, rgb, named colors) in fill/stroke attributes,
71
+ * it's multicolor.
72
+ */
73
+ function detectMonochrome(svg) {
74
+ // If it explicitly uses currentColor, it's monochrome
75
+ if (svg.includes('currentColor'))
76
+ return true;
77
+ // Check for hardcoded fill/stroke colors (hex, rgb, named colors)
78
+ // If we find fill or stroke with a value that's not "none" or "currentColor", it's multicolor
79
+ const colorAttrPattern = /(?:fill|stroke)=["'](?!none|currentColor)([^"']+)["']/gi;
80
+ const hasHardcodedColors = colorAttrPattern.test(svg);
81
+ // Also check for fill/stroke in style attributes
82
+ const styleColorPattern = /(?:fill|stroke)\s*:\s*(?!none|currentColor)([^;"']+)/gi;
83
+ const hasStyleColors = styleColorPattern.test(svg);
84
+ if (hasHardcodedColors || hasStyleColors)
85
+ return false;
86
+ // No colors found at all -- treat as monochrome (will use currentColor behavior)
87
+ return true;
88
+ }
89
+ // ─── Emoji → Iconify slug resolution ────────────────────────────────────
90
+ /**
91
+ * Convert an emoji string to its Unicode codepoint sequence (e.g. "🚀" → "1f680").
92
+ * Strips variation selectors (U+FE0E, U+FE0F).
93
+ */
94
+ function emojiToCodepoints(emoji) {
95
+ const codepoints = [];
96
+ for (const codePoint of emoji) {
97
+ const cp = codePoint.codePointAt(0);
98
+ if (cp === undefined)
99
+ continue;
100
+ // Skip variation selectors
101
+ if (cp === 0xfe0e || cp === 0xfe0f)
102
+ continue;
103
+ codepoints.push(cp.toString(16));
104
+ }
105
+ return codepoints.join('-');
106
+ }
107
+ /**
108
+ * Resolve an emoji to its Iconify icon slug within a given set.
109
+ *
110
+ * Strategy A: Use the Iconify search API, which accepts emoji characters
111
+ * directly and returns matching icon names.
112
+ *
113
+ * Returns the icon name (slug) or null if not found.
114
+ * Results are cached by `${prefix}:${emoji}`.
115
+ */
116
+ export async function resolveEmojiSlug(emoji, prefix) {
117
+ const cacheKey = `${prefix}:${emoji}`;
118
+ const cached = emojiSlugCache.get(cacheKey);
119
+ if (cached !== undefined)
120
+ return cached;
121
+ try {
122
+ // Strategy A: Iconify search API
123
+ const encoded = encodeURIComponent(emoji);
124
+ const url = `https://api.iconify.design/search?query=${encoded}&prefix=${prefix}&limit=1`;
125
+ const response = await fetch(url);
126
+ if (response.ok) {
127
+ const data = await response.json();
128
+ // Response format: { icons: ["prefix:name", ...] }
129
+ if (data.icons && data.icons.length > 0) {
130
+ const fullId = data.icons[0];
131
+ // Extract just the name part (after the prefix:)
132
+ const colonIndex = fullId.indexOf(':');
133
+ const slug = colonIndex >= 0 ? fullId.substring(colonIndex + 1) : fullId;
134
+ emojiSlugCache.set(cacheKey, slug);
135
+ return slug;
136
+ }
137
+ }
138
+ // Strategy B fallback: Try codepoint-based name (many sets use this)
139
+ const codepoints = emojiToCodepoints(emoji);
140
+ if (codepoints) {
141
+ // Try fetching directly — if it exists, the codepoint IS the name
142
+ const directUrl = `https://api.iconify.design/${prefix}/${codepoints}.svg`;
143
+ const directResponse = await fetch(directUrl, { method: 'HEAD' });
144
+ if (directResponse.ok) {
145
+ emojiSlugCache.set(cacheKey, codepoints);
146
+ return codepoints;
147
+ }
148
+ }
149
+ // Not found
150
+ emojiSlugCache.set(cacheKey, null);
151
+ return null;
152
+ }
153
+ catch {
154
+ // Network error — don't cache failures
155
+ return null;
156
+ }
157
+ }
158
+ // ─── Main resolution ────────────────────────────────────────────────────
159
+ /**
160
+ * Resolve an icon prop value to SVG data ready for rendering.
161
+ *
162
+ * Handles all 4 source types: Iconify ID, emoji, inline SVG, data URL.
163
+ * For emoji, auto-maps to an Iconify emoji set unless emojiStyle is 'native'.
164
+ *
165
+ * @param icon - The icon prop value
166
+ * @param emojiStyle - Iconify emoji set prefix (default: DEFAULT_EMOJI_STYLE).
167
+ * Use 'native' to disable auto-mapping and render as <text>.
168
+ */
169
+ export async function resolveIcon(icon, emojiStyle = DEFAULT_EMOJI_STYLE) {
170
+ const sourceType = detectIconSource(icon);
171
+ switch (sourceType) {
172
+ case 'iconify': {
173
+ const svg = await fetchIconifySvg(icon);
174
+ return {
175
+ svgContent: extractSvgContent(svg),
176
+ viewBox: parseViewBox(svg),
177
+ isMonochrome: detectMonochrome(svg),
178
+ sourceType
179
+ };
180
+ }
181
+ case 'data-url': {
182
+ const svg = decodeDataUrl(icon);
183
+ return {
184
+ svgContent: extractSvgContent(svg),
185
+ viewBox: parseViewBox(svg),
186
+ isMonochrome: detectMonochrome(svg),
187
+ sourceType
188
+ };
189
+ }
190
+ case 'svg': {
191
+ const svg = icon.trim();
192
+ return {
193
+ svgContent: extractSvgContent(svg),
194
+ viewBox: parseViewBox(svg),
195
+ isMonochrome: detectMonochrome(svg),
196
+ sourceType
197
+ };
198
+ }
199
+ case 'emoji': {
200
+ // Auto-map emoji to Iconify SVG icon
201
+ if (emojiStyle !== 'native') {
202
+ const slug = await resolveEmojiSlug(icon, emojiStyle);
203
+ if (slug) {
204
+ const iconId = `${emojiStyle}:${slug}`;
205
+ const svg = await fetchIconifySvg(iconId);
206
+ return {
207
+ svgContent: extractSvgContent(svg),
208
+ viewBox: parseViewBox(svg),
209
+ isMonochrome: detectMonochrome(svg),
210
+ sourceType: 'iconify' // Resolved as Iconify, even though input was emoji
211
+ };
212
+ }
213
+ }
214
+ // Fallback: platform-native <text> rendering
215
+ return {
216
+ svgContent: `<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-size="80" font-family="'Apple Color Emoji','Segoe UI Emoji','Noto Color Emoji',sans-serif">${icon}</text>`,
217
+ viewBox: '0 0 100 100',
218
+ isMonochrome: false,
219
+ sourceType
220
+ };
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Superellipse / corner-shape path generation.
3
+ *
4
+ * Follows the CSS Borders Level 4 specification for corner-shape rendering.
5
+ * Maps CornerShape keywords to superellipse parameters and generates SVG paths.
6
+ *
7
+ * The CSS spec defines the curve parametrically:
8
+ * K = 2^abs(curvature)
9
+ * For T in [0, 1]: point = mapPointToCorner(T^K, (1-T)^K)
10
+ *
11
+ * @see https://drafts.csswg.org/css-borders-4/#corner-shape-rendering
12
+ */
13
+ import type { CornerShape } from './types.js';
14
+ /**
15
+ * Map a CornerShape keyword or superellipse(n) to its numeric curvature parameter.
16
+ *
17
+ * CSS spec keyword -> superellipse parameter:
18
+ * round -> 1 (standard elliptical arc)
19
+ * squircle -> 2 (iOS-style continuous curvature)
20
+ * square -> Infinity (sharp 90 deg corner)
21
+ * bevel -> 0 (straight diagonal cut)
22
+ * scoop -> -1 (inward concave curve)
23
+ * notch -> -Infinity (inward right-angle cut)
24
+ */
25
+ export declare function cornerShapeToK(shape: CornerShape): number;
26
+ /**
27
+ * Generate an SVG path `d` attribute for a rounded rectangle with a
28
+ * superellipse corner shape.
29
+ *
30
+ * The curve for each corner is sampled parametrically per the CSS Borders L4 spec:
31
+ * K = 2^abs(curvature)
32
+ * point(T) = mapPointToCorner(T^K, (1-T)^K) for T in [0, 1]
33
+ *
34
+ * For convex shapes (curvature > 0), the curve bulges outward.
35
+ * For concave shapes (curvature < 0), the center reference flips to the
36
+ * outer corner, creating inward "scoop" curves.
37
+ *
38
+ * @param size - Square side length in pixels
39
+ * @param cornerRadius - Corner radius as percentage (0-50)
40
+ * @param cornerShape - CornerShape keyword or superellipse(n)
41
+ * @returns SVG path `d` string
42
+ */
43
+ export declare function generateCornerPath(size: number, cornerRadius: number, cornerShape: CornerShape): string;
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Superellipse / corner-shape path generation.
3
+ *
4
+ * Follows the CSS Borders Level 4 specification for corner-shape rendering.
5
+ * Maps CornerShape keywords to superellipse parameters and generates SVG paths.
6
+ *
7
+ * The CSS spec defines the curve parametrically:
8
+ * K = 2^abs(curvature)
9
+ * For T in [0, 1]: point = mapPointToCorner(T^K, (1-T)^K)
10
+ *
11
+ * @see https://drafts.csswg.org/css-borders-4/#corner-shape-rendering
12
+ */
13
+ /** Number of line segments to sample per corner arc. */
14
+ const SEGMENTS = 64;
15
+ /**
16
+ * Map a CornerShape keyword or superellipse(n) to its numeric curvature parameter.
17
+ *
18
+ * CSS spec keyword -> superellipse parameter:
19
+ * round -> 1 (standard elliptical arc)
20
+ * squircle -> 2 (iOS-style continuous curvature)
21
+ * square -> Infinity (sharp 90 deg corner)
22
+ * bevel -> 0 (straight diagonal cut)
23
+ * scoop -> -1 (inward concave curve)
24
+ * notch -> -Infinity (inward right-angle cut)
25
+ */
26
+ export function cornerShapeToK(shape) {
27
+ switch (shape) {
28
+ case 'round':
29
+ return 1;
30
+ case 'squircle':
31
+ return 2;
32
+ case 'square':
33
+ return Infinity;
34
+ case 'bevel':
35
+ return 0;
36
+ case 'scoop':
37
+ return -1;
38
+ case 'notch':
39
+ return -Infinity;
40
+ default: {
41
+ // superellipse(n) -- parse the numeric value
42
+ const match = shape.match(/^superellipse\((.+)\)$/);
43
+ if (match) {
44
+ const val = match[1].trim();
45
+ if (val === 'infinity')
46
+ return Infinity;
47
+ if (val === '-infinity')
48
+ return -Infinity;
49
+ const n = Number(val);
50
+ if (!isNaN(n))
51
+ return n;
52
+ }
53
+ return 1; // fallback to round
54
+ }
55
+ }
56
+ }
57
+ /**
58
+ * Generate an SVG path `d` attribute for a rounded rectangle with a
59
+ * superellipse corner shape.
60
+ *
61
+ * The curve for each corner is sampled parametrically per the CSS Borders L4 spec:
62
+ * K = 2^abs(curvature)
63
+ * point(T) = mapPointToCorner(T^K, (1-T)^K) for T in [0, 1]
64
+ *
65
+ * For convex shapes (curvature > 0), the curve bulges outward.
66
+ * For concave shapes (curvature < 0), the center reference flips to the
67
+ * outer corner, creating inward "scoop" curves.
68
+ *
69
+ * @param size - Square side length in pixels
70
+ * @param cornerRadius - Corner radius as percentage (0-50)
71
+ * @param cornerShape - CornerShape keyword or superellipse(n)
72
+ * @returns SVG path `d` string
73
+ */
74
+ export function generateCornerPath(size, cornerRadius, cornerShape) {
75
+ // No rounding needed
76
+ if (cornerRadius <= 0) {
77
+ return `M0,0 H${size} V${size} H0 Z`;
78
+ }
79
+ const curvature = cornerShapeToK(cornerShape);
80
+ // square (Infinity): sharp corners, radius is ignored
81
+ if (curvature === Infinity) {
82
+ return `M0,0 H${size} V${size} H0 Z`;
83
+ }
84
+ // Convert percentage to pixels, clamped to half the side
85
+ const r = Math.min((cornerRadius / 100) * size, size / 2);
86
+ // notch (-Infinity): inward right-angle cut at each corner.
87
+ // Goes from tangent point on one edge INWARD to the corner-region center,
88
+ // then back out to the tangent point on the other edge (concave square corner).
89
+ if (curvature === -Infinity) {
90
+ return [
91
+ `M${rd(r)},0`,
92
+ `H${rd(size - r)}`,
93
+ // top-right: inward to center of corner region, then out
94
+ `L${rd(size - r)},${rd(r)}`,
95
+ `L${rd(size)},${rd(r)}`,
96
+ `V${rd(size - r)}`,
97
+ // bottom-right: inward to center, then out
98
+ `L${rd(size - r)},${rd(size - r)}`,
99
+ `L${rd(size - r)},${rd(size)}`,
100
+ `H${rd(r)}`,
101
+ // bottom-left: inward to center, then out
102
+ `L${rd(r)},${rd(size - r)}`,
103
+ `L0,${rd(size - r)}`,
104
+ `V${rd(r)}`,
105
+ // top-left: inward to center, then out
106
+ `L${rd(r)},${rd(r)}`,
107
+ `L${rd(r)},0`,
108
+ `Z`
109
+ ].join(' ');
110
+ }
111
+ // bevel (0): straight diagonal cut from tangent to tangent
112
+ if (curvature === 0) {
113
+ return [
114
+ `M${rd(r)},0`,
115
+ `H${rd(size - r)}`,
116
+ `L${rd(size)},${rd(r)}`,
117
+ `V${rd(size - r)}`,
118
+ `L${rd(size - r)},${rd(size)}`,
119
+ `H${rd(r)}`,
120
+ `L0,${rd(size - r)}`,
121
+ `V${rd(r)}`,
122
+ `Z`
123
+ ].join(' ');
124
+ }
125
+ // round (1): use SVG arc commands for a perfect quarter-ellipse.
126
+ // This matches what browsers render for border-radius and is exact,
127
+ // unlike the parametric approximation.
128
+ if (curvature === 1) {
129
+ return [
130
+ `M${rd(r)},0`,
131
+ `H${rd(size - r)}`,
132
+ `A${rd(r)},${rd(r)} 0 0 1 ${rd(size)},${rd(r)}`,
133
+ `V${rd(size - r)}`,
134
+ `A${rd(r)},${rd(r)} 0 0 1 ${rd(size - r)},${rd(size)}`,
135
+ `H${rd(r)}`,
136
+ `A${rd(r)},${rd(r)} 0 0 1 0,${rd(size - r)}`,
137
+ `V${rd(r)}`,
138
+ `A${rd(r)},${rd(r)} 0 0 1 ${rd(r)},0`,
139
+ `Z`
140
+ ].join(' ');
141
+ }
142
+ // Parametric superellipse curve.
143
+ // K = 2^abs(curvature) per the CSS spec: "Let K be 0.5^(-abs(curvature))"
144
+ const K = Math.pow(2, Math.abs(curvature));
145
+ // Invert: for the outline path (not clip-out), convex shapes use outer corner as ref
146
+ const useOuterAsRef = curvature > 0;
147
+ // Sample the corner arc and return SVG line-to commands.
148
+ //
149
+ // The CSS spec generates a "clip-out" path (what to REMOVE from the rect).
150
+ // Since we generate the OUTLINE path (what to KEEP), we invert the reference:
151
+ // - Convex (squircle, round): curveCenter = outerCorner (curve bows outward)
152
+ // - Concave (scoop): curveCenter = cornerCenter (curve bows inward)
153
+ //
154
+ // The parametric curve maps (T^K, (1-T)^K) through mapPointToCorner,
155
+ // which scales by vectors from curveCenter to the two tangent points.
156
+ const parts = [`M${rd(size - r)},0`];
157
+ // Top-right corner: start=(size-r, 0), end=(size, r), outer=(size, 0), center=(size-r, r)
158
+ parts.push(sampleCorner(size - r, 0, size, r, size, 0, size - r, r, K, useOuterAsRef));
159
+ // Right edge
160
+ parts.push(`L${rd(size)},${rd(size - r)}`);
161
+ // Bottom-right corner: start=(size, size-r), end=(size-r, size), outer=(size, size), center=(size-r, size-r)
162
+ parts.push(sampleCorner(size, size - r, size - r, size, size, size, size - r, size - r, K, useOuterAsRef));
163
+ // Bottom edge
164
+ parts.push(`L${rd(r)},${rd(size)}`);
165
+ // Bottom-left corner: start=(r, size), end=(0, size-r), outer=(0, size), center=(r, size-r)
166
+ parts.push(sampleCorner(r, size, 0, size - r, 0, size, r, size - r, K, useOuterAsRef));
167
+ // Left edge
168
+ parts.push(`L0,${rd(r)}`);
169
+ // Top-left corner: start=(0, r), end=(r, 0), outer=(0, 0), center=(r, r)
170
+ parts.push(sampleCorner(0, r, r, 0, 0, 0, r, r, K, useOuterAsRef));
171
+ parts.push('Z');
172
+ return parts.join(' ');
173
+ }
174
+ /**
175
+ * Sample a superellipse corner arc as SVG line-to commands.
176
+ *
177
+ * @param sx, sy - Start point (tangent point on the "start" edge)
178
+ * @param ex, ey - End point (tangent point on the "end" edge)
179
+ * @param ox, oy - Outer corner point (the actual rectangle corner)
180
+ * @param cx, cy - Center point (diagonally opposite the outer corner within the corner region)
181
+ * @param K - Exponent: 2^abs(curvature)
182
+ * @param useOuterAsRef - Whether to use outer corner as the curve reference point
183
+ */
184
+ function sampleCorner(sx, sy, ex, ey, ox, oy, cx, cy, K, useOuterAsRef) {
185
+ // For convex shapes (outline path), use outer corner so curve bows outward.
186
+ // For concave shapes (outline path), use inner center so curve bows inward.
187
+ const refX = useOuterAsRef ? ox : cx;
188
+ const refY = useOuterAsRef ? oy : cy;
189
+ // Vectors from curveCenter to the tangent points
190
+ const toEndX = ex - refX;
191
+ const toEndY = ey - refY;
192
+ const toStartX = sx - refX;
193
+ const toStartY = sy - refY;
194
+ const cmds = [];
195
+ // Sample T from 0 to 1 (exclusive of 0 since we're already at start)
196
+ for (let i = 1; i <= SEGMENTS; i++) {
197
+ const t = i / SEGMENTS;
198
+ const xParam = Math.pow(t, K);
199
+ const yParam = Math.pow(1 - t, K);
200
+ // mapPointToCorner(xParam, yParam):
201
+ // refPoint + toEnd * xParam + toStart * yParam
202
+ const px = refX + toEndX * xParam + toStartX * yParam;
203
+ const py = refY + toEndY * xParam + toStartY * yParam;
204
+ cmds.push(`L${rd(px)},${rd(py)}`);
205
+ }
206
+ return cmds.join(' ');
207
+ }
208
+ /**
209
+ * Round a number to 2 decimal places for clean SVG output.
210
+ */
211
+ function rd(n) {
212
+ return Math.round(n * 100) / 100;
213
+ }
@@ -0,0 +1,39 @@
1
+ export type IconColorMode = 'auto' | 'original' | 'monochrome' | 'grayscale' | 'grayscale-tint' | {
2
+ hue: number;
3
+ saturation?: number;
4
+ };
5
+ export type CornerShape = 'round' | 'squircle' | 'square' | 'bevel' | 'scoop' | 'notch' | `superellipse(${number})`;
6
+ export interface GradientConfig {
7
+ colors: string[];
8
+ stops?: number[];
9
+ angle?: number;
10
+ position?: number;
11
+ scale?: number;
12
+ }
13
+ export interface AppLogoProps {
14
+ icon?: string;
15
+ iconColor?: string;
16
+ iconColorMode?: IconColorMode;
17
+ iconSize?: number;
18
+ iconOffsetX?: number;
19
+ iconOffsetY?: number;
20
+ iconRotation?: number;
21
+ grayscaleLightness?: number;
22
+ cornerRadius?: number;
23
+ cornerShape?: CornerShape;
24
+ background?: string | GradientConfig;
25
+ size?: number;
26
+ }
27
+ export interface AppLogoConfig {
28
+ icon: string;
29
+ iconColor?: string;
30
+ iconColorMode?: IconColorMode;
31
+ background?: AppLogoProps['background'];
32
+ cornerRadius?: number;
33
+ cornerShape?: CornerShape;
34
+ emojiStyle?: string;
35
+ logo?: Partial<AppLogoProps>;
36
+ favicon?: Partial<AppLogoProps>;
37
+ }
38
+ /** Detected source type of the icon prop value */
39
+ export type IconSourceType = 'iconify' | 'emoji' | 'svg' | 'data-url';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Squircle variant of glow (50% radius, K=2 superellipse) -->
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
+ <path
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="path2"
39
+ transform="translate(-8.6700182, 0.25409326)"
40
+ d="M565.69,0 L600.22,0 L633.15,0 L664.52,0 L694.39,0.01 L722.8,0.02 L749.81,0.04 L775.45,0.08 L799.78,0.14 L822.83,0.22 L844.67,0.34 L865.32,0.49 L884.84,0.7 L903.27,0.96 L920.64,1.3 L937,1.71 L952.38,2.21 L966.84,2.82 L980.4,3.54 L993.11,4.39 L1004.99,5.39 L1016.1,6.56 L1026.45,7.9 L1036.09,9.44 L1045.05,11.19 L1053.37,13.17 L1061.07,15.41 L1068.18,17.92 L1074.74,20.72 L1080.77,23.85 L1086.31,27.31 L1091.38,31.14 L1096.02,35.36 L1100.23,39.99 L1104.06,45.06 L1107.52,50.6 L1110.65,56.63 L1113.45,63.19 L1115.96,70.31 L1118.2,78 L1120.18,86.32 L1121.94,95.28 L1123.47,104.92 L1124.81,115.27 L1125.98,126.38 L1126.98,138.26 L1127.83,150.97 L1128.55,164.53 L1129.16,178.99 L1129.66,194.37 L1130.08,210.73 L1130.41,228.11 L1130.67,246.53 L1130.88,266.05 L1131.03,286.7 L1131.15,308.54 L1131.23,331.59 L1131.29,355.92 L1131.33,381.56 L1131.35,408.57 L1131.36,436.98 L1131.37,466.85 L1131.37,498.22 L1131.37,531.15 L1131.37,565.69 L1131.37,565.69 L1131.37,600.22 L1131.37,633.15 L1131.37,664.52 L1131.36,694.39 L1131.35,722.8 L1131.33,749.81 L1131.29,775.45 L1131.23,799.78 L1131.15,822.83 L1131.03,844.67 L1130.88,865.32 L1130.67,884.84 L1130.41,903.27 L1130.08,920.64 L1129.66,937 L1129.16,952.38 L1128.55,966.84 L1127.83,980.4 L1126.98,993.11 L1125.98,1004.99 L1124.81,1016.1 L1123.47,1026.45 L1121.94,1036.09 L1120.18,1045.05 L1118.2,1053.37 L1115.96,1061.07 L1113.45,1068.18 L1110.65,1074.74 L1107.52,1080.77 L1104.06,1086.31 L1100.23,1091.38 L1096.02,1096.02 L1091.38,1100.23 L1086.31,1104.06 L1080.77,1107.52 L1074.74,1110.65 L1068.18,1113.45 L1061.07,1115.96 L1053.37,1118.2 L1045.05,1120.18 L1036.09,1121.94 L1026.45,1123.47 L1016.1,1124.81 L1004.99,1125.98 L993.11,1126.98 L980.4,1127.83 L966.84,1128.55 L952.38,1129.16 L937,1129.66 L920.64,1130.08 L903.27,1130.41 L884.84,1130.67 L865.32,1130.88 L844.67,1131.03 L822.83,1131.15 L799.78,1131.23 L775.45,1131.29 L749.81,1131.33 L722.8,1131.35 L694.39,1131.36 L664.52,1131.37 L633.15,1131.37 L600.22,1131.37 L565.69,1131.37 L565.69,1131.37 L531.15,1131.37 L498.22,1131.37 L466.85,1131.37 L436.98,1131.36 L408.57,1131.35 L381.56,1131.33 L355.92,1131.29 L331.59,1131.23 L308.54,1131.15 L286.7,1131.03 L266.05,1130.88 L246.53,1130.67 L228.11,1130.41 L210.73,1130.08 L194.37,1129.66 L178.99,1129.16 L164.53,1128.55 L150.97,1127.83 L138.26,1126.98 L126.38,1125.98 L115.27,1124.81 L104.92,1123.47 L95.28,1121.94 L86.32,1120.18 L78,1118.2 L70.31,1115.96 L63.19,1113.45 L56.63,1110.65 L50.6,1107.52 L45.06,1104.06 L39.99,1100.23 L35.36,1096.02 L31.14,1091.38 L27.31,1086.31 L23.85,1080.77 L20.72,1074.74 L17.92,1068.18 L15.41,1061.07 L13.17,1053.37 L11.19,1045.05 L9.44,1036.09 L7.9,1026.45 L6.56,1016.1 L5.39,1004.99 L4.39,993.11 L3.54,980.4 L2.82,966.84 L2.21,952.38 L1.71,937 L1.3,920.64 L0.96,903.27 L0.7,884.84 L0.49,865.32 L0.34,844.67 L0.22,822.83 L0.14,799.78 L0.08,775.45 L0.04,749.81 L0.02,722.8 L0.01,694.39 L0,664.52 L0,633.15 L0,600.22 L0,565.69 L0,565.69 L0,531.15 L0,498.22 L0,466.85 L0.01,436.98 L0.02,408.57 L0.04,381.56 L0.08,355.92 L0.14,331.59 L0.22,308.54 L0.34,286.7 L0.49,266.05 L0.7,246.53 L0.96,228.11 L1.3,210.73 L1.71,194.37 L2.21,178.99 L2.82,164.53 L3.54,150.97 L4.39,138.26 L5.39,126.38 L6.56,115.27 L7.9,104.92 L9.44,95.28 L11.19,86.32 L13.17,78 L15.41,70.31 L17.92,63.19 L20.72,56.63 L23.85,50.6 L27.31,45.06 L31.14,39.99 L35.36,35.36 L39.99,31.14 L45.06,27.31 L50.6,23.85 L56.63,20.72 L63.19,17.92 L70.31,15.41 L78,13.17 L86.32,11.19 L95.28,9.44 L104.92,7.9 L115.27,6.56 L126.38,5.39 L138.26,4.39 L150.97,3.54 L164.53,2.82 L178.99,2.21 L194.37,1.71 L210.73,1.3 L228.11,0.96 L246.53,0.7 L266.05,0.49 L286.7,0.34 L308.54,0.22 L331.59,0.14 L355.92,0.08 L381.56,0.04 L408.57,0.02 L436.98,0.01 L466.85,0 L498.22,0 L531.15,0 L565.69,0 Z" />
41
+ </g>
42
+ </g>
43
+ </g>
44
+ </svg>
package/dist/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
- import LeftiumLogo from './LeftiumLogo.svelte';
2
- import toggleAnimation from './LeftiumLogo.svelte';
3
- import setAnimated from './LeftiumLogo.svelte';
1
+ import LeftiumLogo, { toggleAnimation, setAnimated } from './LeftiumLogo.svelte';
4
2
  import favicon from './assets/favicon.svg';
5
3
  export { LeftiumLogo, toggleAnimation, setAnimated, favicon };
4
+ export { default as AppLogo } from './AppLogo.svelte';
5
+ export type { AppLogoProps, AppLogoConfig, GradientConfig, IconColorMode, CornerShape } from './app-logo/types.js';
6
+ export { generateAppLogoSvg } from './app-logo/generate-svg.js';
7
+ export { generateAppLogoPng } from './app-logo/generate-png.js';
8
+ export { LEFTIUM_GRADIENT } from './app-logo/defaults.js';
9
+ export { generateCornerPath, cornerShapeToK } from './app-logo/squircle.js';
10
+ export { applyColorMode } from './app-logo/color-transform.js';
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  // Reexport your entry components here
2
- import LeftiumLogo from './LeftiumLogo.svelte';
3
- import toggleAnimation from './LeftiumLogo.svelte';
4
- import setAnimated from './LeftiumLogo.svelte';
2
+ import LeftiumLogo, { toggleAnimation, setAnimated } from './LeftiumLogo.svelte';
5
3
  import favicon from './assets/favicon.svg';
6
4
  export { LeftiumLogo, toggleAnimation, setAnimated, favicon };
5
+ // Phase 1: AppLogo component and generation utilities
6
+ export { default as AppLogo } from './AppLogo.svelte';
7
+ export { generateAppLogoSvg } from './app-logo/generate-svg.js';
8
+ export { generateAppLogoPng } from './app-logo/generate-png.js';
9
+ export { LEFTIUM_GRADIENT } from './app-logo/defaults.js';
10
+ // Phase 2: Advanced styling utilities
11
+ export { generateCornerPath, cornerShapeToK } from './app-logo/squircle.js';
12
+ export { applyColorMode } from './app-logo/color-transform.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Generate a self-contained static SVG for the Leftium logo.
3
+ *
4
+ * Replicates the LeftiumLogo.svelte layout geometry as pure SVG,
5
+ * with all assets inlined as base64 data URIs.
6
+ */
7
+ export type BoundingBox = 'square' | 'default' | 'encircled' | 'cropped';
8
+ export interface LeftiumLogoConfig {
9
+ /** Output canvas width in pixels. For 'cropped' mode the height will differ. Default: 800 */
10
+ size?: number;
11
+ /** Corner shape of the blue square. Default: true (squircle) */
12
+ squircle?: boolean;
13
+ /** Bounding box mode. Default: 'default' */
14
+ boundingBox?: BoundingBox;
15
+ /** Background fill. 'transparent' or a CSS color string e.g. '#ffffff'. Default: 'transparent' */
16
+ background?: string;
17
+ }
18
+ /**
19
+ * Generate a complete, self-contained SVG string for the static Leftium logo.
20
+ *
21
+ * All asset SVGs are inlined as base64 data URIs so the output is a single
22
+ * portable file with no external dependencies.
23
+ */
24
+ export declare function generateLeftiumLogoSvg(config?: LeftiumLogoConfig): string;
25
+ /**
26
+ * Rasterise the Leftium logo to a PNG or WebP Blob.
27
+ * Browser-only (requires canvas + Image).
28
+ */
29
+ export declare function generateLeftiumLogoPng(config: LeftiumLogoConfig, format?: 'png' | 'webp'): Promise<Blob>;