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
at main 646 lines 25 kB view raw
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;