Tend your corner of the atmosphere. spores.garden turns your AT Protocol records into a personal site with unique themes. Your data never leaves your PDS. Grow something that's truly yours.
spores.garden
1import { generateFlowerSVGString } from '../utils/flower-svg';
2
3/**
4 * Seeded random number generator for deterministic randomness
5 */
6function seededRandom(seed: string): () => number {
7 let hash = 0;
8 for (let i = 0; i < seed.length; i++) {
9 hash = ((hash << 5) - hash) + seed.charCodeAt(i);
10 hash = hash & hash; // Convert to 32bit integer
11 }
12 let state = Math.abs(hash);
13
14 return function () {
15 state = (state * 1103515245 + 12345) & 0x7fffffff;
16 return state / 0x7fffffff;
17 };
18}
19
20/**
21 * Lighten or darken a hex color
22 */
23function adjustColor(hex: string, amount: number): string {
24 // Remove # if present
25 hex = hex.replace(/^#/, '');
26
27 // Parse RGB
28 let r = parseInt(hex.slice(0, 2), 16);
29 let g = parseInt(hex.slice(2, 4), 16);
30 let b = parseInt(hex.slice(4, 6), 16);
31
32 // Adjust
33 r = Math.max(0, Math.min(255, r + amount));
34 g = Math.max(0, Math.min(255, g + amount));
35 b = Math.max(0, Math.min(255, b + amount));
36
37 // Convert back to hex
38 return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
39}
40
41/**
42 * Generate flower parameters from DID
43 */
44interface FlowerParams {
45 // Petal configuration
46 petalCount: number;
47 petalShape: 'round' | 'pointed' | 'wavy' | 'heart' | 'tulip';
48 petalSize: number;
49 petalRotation: number;
50
51 // Layering (Proposal 1)
52 layerCount: number;
53 layerRotationOffset: number;
54 layerSizeDecay: number;
55
56 // Per-petal variation (Proposal 3)
57 petalSizeJitter: number;
58 petalAngleJitter: number;
59 petalCurveJitter: number;
60
61 // Center style (Proposal 4)
62 centerStyle: 'simple' | 'stamen' | 'spiral' | 'dots' | 'ring';
63 centerSize: number;
64 stamenCount: number;
65
66 // Stem and leaves
67 hasStem: boolean;
68 hasLeaves: boolean;
69 leafStyle: 'ellipse' | 'pointed' | 'serrated';
70
71 // Colors
72 primaryColor: string;
73 secondaryColor: string;
74 centerColor: string;
75 stamenTipColor: string;
76}
77
78function generateFlowerParams(did: string, colors: any): FlowerParams {
79 const rng = seededRandom(did);
80
81 // Number of petals: 4-10 (increased range)
82 const petalCount = Math.floor(rng() * 7) + 4;
83
84 // Petal shape: expanded options
85 const shapeIndex = Math.floor(rng() * 5);
86 const petalShape = ['round', 'pointed', 'wavy', 'heart', 'tulip'][shapeIndex] as FlowerParams['petalShape'];
87
88 // Petal size: 0.5 to 0.9 (relative to flower size)
89 const petalSize = 0.5 + rng() * 0.4;
90
91 // Initial rotation offset
92 const petalRotation = rng() * 360;
93
94 // Layer configuration (Proposal 1)
95 const layerCount = Math.floor(rng() * 3) + 1; // 1-3 layers
96 const layerRotationOffset = 15 + rng() * 30; // 15-45 degrees offset between layers
97 const layerSizeDecay = 0.6 + rng() * 0.2; // Each inner layer is 60-80% of outer
98
99 // Per-petal variation (Proposal 3)
100 const petalSizeJitter = 0.1 + rng() * 0.15; // 10-25% size variation
101 const petalAngleJitter = 3 + rng() * 7; // 3-10 degrees angle jitter
102 const petalCurveJitter = 0.1 + rng() * 0.2; // 10-30% curve variation
103
104 // Center style (Proposal 4)
105 const centerStyleIndex = Math.floor(rng() * 5);
106 const centerStyle = ['simple', 'stamen', 'spiral', 'dots', 'ring'][centerStyleIndex] as FlowerParams['centerStyle'];
107 const centerSize = 0.12 + rng() * 0.12; // 12-24% of flower size
108 const stamenCount = Math.floor(rng() * 8) + 5; // 5-12 stamens
109
110 // 60% chance of stem
111 const hasStem = rng() > 0.4;
112
113 // 50% chance of leaves (only if stem exists)
114 const hasLeaves = hasStem && rng() > 0.5;
115
116 // Leaf style
117 const leafStyleIndex = Math.floor(rng() * 3);
118 const leafStyle = ['ellipse', 'pointed', 'serrated'][leafStyleIndex] as FlowerParams['leafStyle'];
119
120 // Color variations
121 const primaryColor = colors.primary || '#ff6b9d';
122 const secondaryColor = colors.accent || colors.primary || '#ff9ecd';
123 const centerColor = colors.text || colors.primary || '#4a4a4a';
124 const stamenTipColor = colors.accent || adjustColor(primaryColor, 60);
125
126 return {
127 petalCount,
128 petalShape,
129 petalSize,
130 petalRotation,
131 layerCount,
132 layerRotationOffset,
133 layerSizeDecay,
134 petalSizeJitter,
135 petalAngleJitter,
136 petalCurveJitter,
137 centerStyle,
138 centerSize,
139 stamenCount,
140 hasStem,
141 hasLeaves,
142 leafStyle,
143 primaryColor,
144 secondaryColor,
145 centerColor,
146 stamenTipColor
147 };
148}
149
150/**
151 * Generate SVG path for a petal based on shape with organic variation
152 */
153function generatePetalPath(
154 shape: FlowerParams['petalShape'],
155 baseSize: number,
156 centerX: number,
157 centerY: number,
158 angle: number,
159 sizeMultiplier: number,
160 curveVariation: number
161): string {
162 const rad = (angle * Math.PI) / 180;
163 const size = baseSize * sizeMultiplier;
164 const petalLength = size * 40;
165 const petalWidth = size * 20 * (1 + curveVariation * 0.3);
166
167 // Calculate petal tip position
168 const tipX = centerX + Math.cos(rad) * petalLength;
169 const tipY = centerY + Math.sin(rad) * petalLength;
170
171 // Calculate control points for curves
172 const perpAngle = rad + Math.PI / 2;
173 const controlOffset = petalWidth * 0.5 * (1 + curveVariation * 0.2);
174
175 const leftControlX = centerX + Math.cos(perpAngle) * controlOffset;
176 const leftControlY = centerY + Math.sin(perpAngle) * controlOffset;
177 const rightControlX = centerX - Math.cos(perpAngle) * controlOffset;
178 const rightControlY = centerY - Math.sin(perpAngle) * controlOffset;
179
180 switch (shape) {
181 case 'round': {
182 // Rounded petal using quadratic curves
183 const leftCurveX = tipX + Math.cos(perpAngle) * petalWidth * 0.3;
184 const leftCurveY = tipY + Math.sin(perpAngle) * petalWidth * 0.3;
185 const rightCurveX = tipX - Math.cos(perpAngle) * petalWidth * 0.3;
186 const rightCurveY = tipY - Math.sin(perpAngle) * petalWidth * 0.3;
187
188 return `M ${centerX} ${centerY} Q ${leftControlX} ${leftControlY} ${leftCurveX} ${leftCurveY} Q ${tipX} ${tipY} ${rightCurveX} ${rightCurveY} Q ${rightControlX} ${rightControlY} ${centerX} ${centerY} Z`;
189 }
190
191 case 'pointed': {
192 // Pointed petal with elegant curves leading to sharp tip
193 // Control points for smooth S-curve edges
194 const bulgeFactor = petalWidth * 0.45;
195 const bulgePoint = 0.4; // Where the petal is widest
196
197 // Left edge curve points
198 const leftBulgeX = centerX + Math.cos(rad) * petalLength * bulgePoint + Math.cos(perpAngle) * bulgeFactor;
199 const leftBulgeY = centerY + Math.sin(rad) * petalLength * bulgePoint + Math.sin(perpAngle) * bulgeFactor;
200 const leftNarrowX = centerX + Math.cos(rad) * petalLength * 0.75 + Math.cos(perpAngle) * bulgeFactor * 0.4;
201 const leftNarrowY = centerY + Math.sin(rad) * petalLength * 0.75 + Math.sin(perpAngle) * bulgeFactor * 0.4;
202
203 // Right edge curve points
204 const rightBulgeX = centerX + Math.cos(rad) * petalLength * bulgePoint - Math.cos(perpAngle) * bulgeFactor;
205 const rightBulgeY = centerY + Math.sin(rad) * petalLength * bulgePoint - Math.sin(perpAngle) * bulgeFactor;
206 const rightNarrowX = centerX + Math.cos(rad) * petalLength * 0.75 - Math.cos(perpAngle) * bulgeFactor * 0.4;
207 const rightNarrowY = centerY + Math.sin(rad) * petalLength * 0.75 - Math.sin(perpAngle) * bulgeFactor * 0.4;
208
209 // Use cubic bezier for smooth organic curves
210 return `M ${centerX} ${centerY} C ${leftControlX} ${leftControlY} ${leftBulgeX} ${leftBulgeY} ${leftNarrowX} ${leftNarrowY} Q ${tipX} ${tipY} ${rightNarrowX} ${rightNarrowY} C ${rightBulgeX} ${rightBulgeY} ${rightControlX} ${rightControlY} ${centerX} ${centerY} Z`;
211 }
212
213 case 'wavy': {
214 // Wavy petal with curved edges
215 const wave1X = centerX + Math.cos(rad) * petalLength * 0.3 + Math.cos(perpAngle) * petalWidth * 0.5;
216 const wave1Y = centerY + Math.sin(rad) * petalLength * 0.3 + Math.sin(perpAngle) * petalWidth * 0.5;
217 const wave2X = centerX + Math.cos(rad) * petalLength * 0.6 + Math.cos(perpAngle) * petalWidth * 0.3;
218 const wave2Y = centerY + Math.sin(rad) * petalLength * 0.6 + Math.sin(perpAngle) * petalWidth * 0.3;
219 const wave3X = centerX + Math.cos(rad) * petalLength * 0.6 - Math.cos(perpAngle) * petalWidth * 0.3;
220 const wave3Y = centerY + Math.sin(rad) * petalLength * 0.6 - Math.sin(perpAngle) * petalWidth * 0.3;
221 const wave4X = centerX + Math.cos(rad) * petalLength * 0.3 - Math.cos(perpAngle) * petalWidth * 0.5;
222 const wave4Y = centerY + Math.sin(rad) * petalLength * 0.3 - Math.sin(perpAngle) * petalWidth * 0.5;
223
224 return `M ${centerX} ${centerY} Q ${leftControlX} ${leftControlY} ${wave1X} ${wave1Y} Q ${wave2X} ${wave2Y} ${tipX} ${tipY} Q ${wave3X} ${wave3Y} ${wave4X} ${wave4Y} Q ${rightControlX} ${rightControlY} ${centerX} ${centerY} Z`;
225 }
226
227 case 'heart': {
228 // Heart-shaped petal with notch at tip
229 const notchDepth = petalLength * 0.15;
230 const lobeWidth = petalWidth * 0.6;
231
232 // Left lobe
233 const leftLobeX = tipX + Math.cos(perpAngle) * lobeWidth - Math.cos(rad) * notchDepth * 0.5;
234 const leftLobeY = tipY + Math.sin(perpAngle) * lobeWidth - Math.sin(rad) * notchDepth * 0.5;
235 // Right lobe
236 const rightLobeX = tipX - Math.cos(perpAngle) * lobeWidth - Math.cos(rad) * notchDepth * 0.5;
237 const rightLobeY = tipY - Math.sin(perpAngle) * lobeWidth - Math.sin(rad) * notchDepth * 0.5;
238 // Notch point
239 const notchX = tipX - Math.cos(rad) * notchDepth;
240 const notchY = tipY - Math.sin(rad) * notchDepth;
241
242 return `M ${centerX} ${centerY} Q ${leftControlX} ${leftControlY} ${leftLobeX} ${leftLobeY} Q ${tipX} ${tipY} ${notchX} ${notchY} Q ${tipX} ${tipY} ${rightLobeX} ${rightLobeY} Q ${rightControlX} ${rightControlY} ${centerX} ${centerY} Z`;
243 }
244
245 case 'tulip': {
246 // Tulip-shaped petal - cupped shape, narrow base widening to rounded top
247 const cupWidth = petalWidth * 0.7;
248 const cupPoint = petalLength * 0.6;
249
250 // Control points for the cup shape - smooth S-curves
251 const leftCupX = centerX + Math.cos(rad) * cupPoint + Math.cos(perpAngle) * cupWidth;
252 const leftCupY = centerY + Math.sin(rad) * cupPoint + Math.sin(perpAngle) * cupWidth;
253 const rightCupX = centerX + Math.cos(rad) * cupPoint - Math.cos(perpAngle) * cupWidth;
254 const rightCupY = centerY + Math.sin(rad) * cupPoint - Math.sin(perpAngle) * cupWidth;
255
256 // Rounded top edge
257 const tipLeftX = tipX + Math.cos(perpAngle) * cupWidth * 0.5;
258 const tipLeftY = tipY + Math.sin(perpAngle) * cupWidth * 0.5;
259 const tipRightX = tipX - Math.cos(perpAngle) * cupWidth * 0.5;
260 const tipRightY = tipY - Math.sin(perpAngle) * cupWidth * 0.5;
261
262 // Smooth cubic bezier for elegant tulip shape
263 return `M ${centerX} ${centerY} C ${leftControlX * 0.2 + centerX * 0.8} ${leftControlY * 0.2 + centerY * 0.8} ${leftCupX} ${leftCupY} ${tipLeftX} ${tipLeftY} Q ${tipX} ${tipY} ${tipRightX} ${tipRightY} C ${rightCupX} ${rightCupY} ${rightControlX * 0.2 + centerX * 0.8} ${rightControlY * 0.2 + centerY * 0.8} ${centerX} ${centerY} Z`;
264 }
265 }
266}
267
268/**
269 * Generate detailed center based on style (Proposal 4)
270 */
271function generateCenter(
272 style: FlowerParams['centerStyle'],
273 centerX: number,
274 centerY: number,
275 size: number,
276 stamenCount: number,
277 centerColor: string,
278 stamenTipColor: string,
279 rng: () => number
280): string[] {
281 const elements: string[] = [];
282 const radius = size * 0.4;
283
284 switch (style) {
285 case 'simple': {
286 // Basic circle
287 elements.push(`<circle cx="${centerX}" cy="${centerY}" r="${radius * 0.6}" fill="${centerColor}" />`);
288 break;
289 }
290
291 case 'stamen': {
292 // Radiating stamens with dots at tips
293 const innerRadius = radius * 0.3;
294 const outerRadius = radius * 1.2;
295
296 // Central disc
297 elements.push(`<circle cx="${centerX}" cy="${centerY}" r="${innerRadius}" fill="${centerColor}" />`);
298
299 // Stamens
300 for (let i = 0; i < stamenCount; i++) {
301 const angle = (i / stamenCount) * Math.PI * 2 + rng() * 0.2;
302 const stamenLength = outerRadius * (0.7 + rng() * 0.3);
303 const tipX = centerX + Math.cos(angle) * stamenLength;
304 const tipY = centerY + Math.sin(angle) * stamenLength;
305 const startX = centerX + Math.cos(angle) * innerRadius;
306 const startY = centerY + Math.sin(angle) * innerRadius;
307
308 // Stamen line
309 elements.push(`<line x1="${startX}" y1="${startY}" x2="${tipX}" y2="${tipY}" stroke="${centerColor}" stroke-width="0.8" />`);
310 // Anther (tip)
311 const antherSize = 1.5 + rng() * 1;
312 elements.push(`<circle cx="${tipX}" cy="${tipY}" r="${antherSize}" fill="${stamenTipColor}" />`);
313 }
314 break;
315 }
316
317 case 'spiral': {
318 // Fibonacci spiral pattern (sunflower-like)
319 const goldenAngle = Math.PI * (3 - Math.sqrt(5)); // ~137.5 degrees
320 const dotCount = Math.floor(20 + rng() * 15);
321
322 for (let i = 0; i < dotCount; i++) {
323 const angle = i * goldenAngle;
324 const r = radius * 0.9 * Math.sqrt(i / dotCount);
325 const x = centerX + Math.cos(angle) * r;
326 const y = centerY + Math.sin(angle) * r;
327 const dotSize = 0.8 + (1 - i / dotCount) * 1.5;
328 const color = i % 3 === 0 ? stamenTipColor : centerColor;
329 elements.push(`<circle cx="${x}" cy="${y}" r="${dotSize}" fill="${color}" />`);
330 }
331 break;
332 }
333
334 case 'dots': {
335 // Scattered dots with size variation
336 const dotCount = Math.floor(8 + rng() * 10);
337
338 // Background disc
339 elements.push(`<circle cx="${centerX}" cy="${centerY}" r="${radius * 0.8}" fill="${adjustColor(centerColor, 40)}" />`);
340
341 for (let i = 0; i < dotCount; i++) {
342 const angle = rng() * Math.PI * 2;
343 const distance = rng() * radius * 0.6;
344 const x = centerX + Math.cos(angle) * distance;
345 const y = centerY + Math.sin(angle) * distance;
346 const dotSize = 1 + rng() * 2;
347 const color = rng() > 0.5 ? centerColor : stamenTipColor;
348 elements.push(`<circle cx="${x}" cy="${y}" r="${dotSize}" fill="${color}" />`);
349 }
350 break;
351 }
352
353 case 'ring': {
354 // Concentric rings
355 const ringCount = 2 + Math.floor(rng() * 2);
356 for (let i = ringCount; i >= 0; i--) {
357 const ringRadius = radius * 0.9 * ((i + 1) / (ringCount + 1));
358 const color = i % 2 === 0 ? centerColor : stamenTipColor;
359 elements.push(`<circle cx="${centerX}" cy="${centerY}" r="${ringRadius}" fill="${color}" />`);
360 }
361 break;
362 }
363 }
364
365 return elements;
366}
367
368/**
369 * Generate leaf SVG based on style
370 * Leaves grow FROM the attachment point (x,y) OUTWARD in the rotation direction
371 */
372function generateLeaf(
373 style: FlowerParams['leafStyle'],
374 x: number,
375 y: number,
376 size: number,
377 rotation: number,
378 color: string
379): string {
380 const rad = (rotation * Math.PI) / 180;
381 const perpRad = rad + Math.PI / 2;
382
383 // Tip of the leaf (grows outward from attachment point)
384 const tipX = x + Math.cos(rad) * size;
385 const tipY = y + Math.sin(rad) * size;
386
387 switch (style) {
388 case 'ellipse': {
389 // Teardrop shape - wider near base, pointed at tip
390 const width = size * 0.4;
391 const bulgePoint = 0.35; // Where leaf is widest
392
393 const bulgeX = x + Math.cos(rad) * size * bulgePoint;
394 const bulgeY = y + Math.sin(rad) * size * bulgePoint;
395 const leftBulgeX = bulgeX + Math.cos(perpRad) * width;
396 const leftBulgeY = bulgeY + Math.sin(perpRad) * width;
397 const rightBulgeX = bulgeX - Math.cos(perpRad) * width;
398 const rightBulgeY = bulgeY - Math.sin(perpRad) * width;
399
400 return `<path d="M ${x} ${y} Q ${leftBulgeX} ${leftBulgeY} ${tipX} ${tipY} Q ${rightBulgeX} ${rightBulgeY} ${x} ${y} Z" fill="none" stroke="${color}" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />`;
401 }
402
403 case 'pointed': {
404 // More elongated pointed leaf with smooth curves
405 const width = size * 0.3;
406 const bulgePoint = 0.3;
407
408 const bulgeX = x + Math.cos(rad) * size * bulgePoint;
409 const bulgeY = y + Math.sin(rad) * size * bulgePoint;
410 const leftCtrlX = bulgeX + Math.cos(perpRad) * width;
411 const leftCtrlY = bulgeY + Math.sin(perpRad) * width;
412 const rightCtrlX = bulgeX - Math.cos(perpRad) * width;
413 const rightCtrlY = bulgeY - Math.sin(perpRad) * width;
414
415 // Midpoint controls for S-curve
416 const midX = x + Math.cos(rad) * size * 0.65;
417 const midY = y + Math.sin(rad) * size * 0.65;
418 const leftMidX = midX + Math.cos(perpRad) * width * 0.5;
419 const leftMidY = midY + Math.sin(perpRad) * width * 0.5;
420 const rightMidX = midX - Math.cos(perpRad) * width * 0.5;
421 const rightMidY = midY - Math.sin(perpRad) * width * 0.5;
422
423 return `<path d="M ${x} ${y} C ${leftCtrlX} ${leftCtrlY} ${leftMidX} ${leftMidY} ${tipX} ${tipY} C ${rightMidX} ${rightMidY} ${rightCtrlX} ${rightCtrlY} ${x} ${y} Z" fill="none" stroke="${color}" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />`;
424 }
425
426 case 'serrated': {
427 // Serrated leaf with curved base and wavy edges using smooth curves
428 const width = size * 0.35;
429
430 // Build path with smooth curves - simpler approach
431 const bulgePoint = 0.35;
432 const bulgeX = x + Math.cos(rad) * size * bulgePoint;
433 const bulgeY = y + Math.sin(rad) * size * bulgePoint;
434
435 // Wider at bulge, narrower toward tip
436 const leftBulgeX = bulgeX + Math.cos(perpRad) * width;
437 const leftBulgeY = bulgeY + Math.sin(perpRad) * width;
438 const rightBulgeX = bulgeX - Math.cos(perpRad) * width;
439 const rightBulgeY = bulgeY - Math.sin(perpRad) * width;
440
441 // Mid-leaf points with slight wave
442 const midPoint = 0.65;
443 const midX = x + Math.cos(rad) * size * midPoint;
444 const midY = y + Math.sin(rad) * size * midPoint;
445 const leftMidX = midX + Math.cos(perpRad) * width * 0.6;
446 const leftMidY = midY + Math.sin(perpRad) * width * 0.6;
447 const rightMidX = midX - Math.cos(perpRad) * width * 0.6;
448 const rightMidY = midY - Math.sin(perpRad) * width * 0.6;
449
450 return `<path d="M ${x} ${y} Q ${leftBulgeX} ${leftBulgeY} ${leftMidX} ${leftMidY} Q ${tipX + Math.cos(perpRad) * width * 0.2} ${tipY + Math.sin(perpRad) * width * 0.2} ${tipX} ${tipY} Q ${tipX - Math.cos(perpRad) * width * 0.2} ${tipY - Math.sin(perpRad) * width * 0.2} ${rightMidX} ${rightMidY} Q ${rightBulgeX} ${rightBulgeY} ${x} ${y} Z" fill="none" stroke="${color}" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" />`;
451 }
452 }
453}
454
455/**
456 * Generate flower SVG from parameters with layering and variation
457 */
458function generateFlowerSVG(params: FlowerParams, size: number = 100, rng: () => number): string {
459 const centerX = size / 2;
460 const centerY = size / 2;
461 const flowerRadius = size * 0.4;
462
463 let svgElements: string[] = [];
464
465 // Generate layered petals (Proposal 1) - outer layers first
466 for (let layer = 0; layer < params.layerCount; layer++) {
467 const layerScale = Math.pow(params.layerSizeDecay, layer);
468 const layerRotation = params.petalRotation + layer * params.layerRotationOffset;
469 const layerPetalCount = params.petalCount - layer; // Fewer petals in inner layers
470
471 // Color variation per layer - inner layers slightly lighter
472 const layerColorAdjust = layer * 25;
473 const layerPrimaryColor = adjustColor(params.primaryColor, layerColorAdjust);
474 const layerSecondaryColor = adjustColor(params.secondaryColor, layerColorAdjust);
475
476 const angleStep = 360 / Math.max(layerPetalCount, 3);
477
478 for (let i = 0; i < layerPetalCount; i++) {
479 // Apply per-petal variation (Proposal 3)
480 const sizeMultiplier = 1 + (rng() - 0.5) * 2 * params.petalSizeJitter;
481 const angleJitter = (rng() - 0.5) * 2 * params.petalAngleJitter;
482 const curveVariation = (rng() - 0.5) * 2 * params.petalCurveJitter;
483
484 const angle = layerRotation + i * angleStep + angleJitter;
485 const petalPath = generatePetalPath(
486 params.petalShape,
487 params.petalSize * layerScale,
488 centerX,
489 centerY,
490 angle,
491 sizeMultiplier,
492 curveVariation
493 );
494
495 // Alternate colors for visual interest
496 const strokeColor = i % 2 === 0 ? layerPrimaryColor : layerSecondaryColor;
497 svgElements.push(`<path d="${petalPath}" fill="none" stroke="${strokeColor}" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round" opacity="${1 - layer * 0.1}" />`);
498 }
499 }
500
501 // Add detailed center (Proposal 4)
502 const centerElements = generateCenter(
503 params.centerStyle,
504 centerX,
505 centerY,
506 flowerRadius * params.centerSize * 5,
507 params.stamenCount,
508 params.centerColor,
509 params.stamenTipColor,
510 rng
511 );
512 svgElements.push(...centerElements);
513
514 // Add stem if needed - stem connects directly to flower center where petals radiate from
515 if (params.hasStem) {
516 const stemHeight = size * 0.35; // Longer stem to extend from center to bottom
517 const stemWidth = size * 0.025;
518 const stemColor = adjustColor(params.secondaryColor, -40);
519
520 // Stem starts at flower center and extends downward
521 const stemStartY = centerY;
522 const stemEndY = centerY + stemHeight;
523
524 // Slightly curved stem
525 const stemCurve = (rng() - 0.5) * 4;
526 svgElements.unshift(`<path d="M ${centerX} ${stemStartY} Q ${centerX + stemCurve} ${stemStartY + stemHeight * 0.5} ${centerX} ${stemEndY}" stroke="${stemColor}" stroke-width="${stemWidth}" fill="none" stroke-linecap="round" />`);
527
528 // Add leaves if needed - positioned along the stem
529 if (params.hasLeaves) {
530 const leafSize = size * 0.1;
531 const leafColor = adjustColor(params.secondaryColor, -30);
532
533 // Left leaf - attached at stem, grows outward
534 const leftLeafY = stemStartY + stemHeight * 0.4;
535 svgElements.unshift(generateLeaf(params.leafStyle, centerX, leftLeafY, leafSize, -50, leafColor));
536
537 // Right leaf (sometimes) - attached at stem, grows outward
538 if (rng() > 0.3) {
539 const rightLeafY = stemStartY + stemHeight * 0.6;
540 svgElements.unshift(generateLeaf(params.leafStyle, centerX, rightLeafY, leafSize * 0.85, 50, leafColor));
541 }
542 }
543 }
544
545 return svgElements.join('\n ');
546}
547
548class DidVisualization extends HTMLElement {
549 private did: string | null = null;
550 private displaySize: number = 100;
551
552 static get observedAttributes() {
553 return ['did', 'show-info', 'size'];
554 }
555
556 attributeChangedCallback(name: string, oldValue: string, newValue: string) {
557 if (oldValue !== newValue) {
558 if (name === 'did') {
559 this.did = newValue;
560 } else if (name === 'size') {
561 this.displaySize = parseInt(newValue, 10) || 100;
562 }
563 this.render();
564 }
565 }
566
567 connectedCallback() {
568 const sizeAttr = this.getAttribute('size');
569 if (sizeAttr) {
570 this.displaySize = parseInt(sizeAttr, 10) || 100;
571 }
572 this.render();
573 }
574
575 render() {
576 if (!this.did) {
577 this.innerHTML = '<p>No DID provided</p>';
578 return;
579 }
580
581 // Use the shared flower generation function
582 const svgString = generateFlowerSVGString(this.did, this.displaySize);
583
584 const showInfo = this.hasAttribute('show-info');
585
586 this.innerHTML = `
587 <div style="display: flex; flex-direction: column; align-items: center; gap: 0.5rem;">
588 <div style="display: flex; align-items: center; gap: 0.5rem; position: relative;">
589 ${svgString}
590 ${showInfo ? `
591 <button class="did-info-button" aria-label="Information about DID visualization" title="Information about this flower">
592 ?
593 </button>
594 <div class="did-info-tooltip" role="tooltip">
595 This flower is generated from your DID—a unique cryptographic identifier. It's your visual signature in the garden network.
596 </div>
597 ` : ''}
598 </div>
599 </div>
600 `;
601
602 if (showInfo) {
603 this.attachTooltipListeners();
604 }
605 }
606
607 private attachTooltipListeners() {
608 const infoButton = this.querySelector('.did-info-button') as HTMLElement;
609 const tooltip = this.querySelector('.did-info-tooltip') as HTMLElement;
610
611 if (!infoButton || !tooltip) return;
612
613 // Show tooltip on hover or click
614 const showTooltip = () => {
615 tooltip.style.display = 'block';
616 };
617
618 const hideTooltip = () => {
619 tooltip.style.display = 'none';
620 };
621
622 infoButton.addEventListener('mouseenter', showTooltip);
623 infoButton.addEventListener('mouseleave', hideTooltip);
624 infoButton.addEventListener('click', (e) => {
625 e.stopPropagation();
626 if (tooltip.style.display === 'block') {
627 hideTooltip();
628 } else {
629 showTooltip();
630 }
631 });
632
633 // Hide tooltip when clicking outside
634 document.addEventListener('click', (e) => {
635 if (!this.contains(e.target as Node)) {
636 hideTooltip();
637 }
638 });
639 }
640}
641
642customElements.define('did-visualization', DidVisualization);
643
644// Keep legacy generators discoverable for future visual variants.
645void generateFlowerParams;
646void generateFlowerSVG;