|
| 1 | +import React, { useMemo } from 'react'; |
| 2 | + |
| 3 | +const LuxeArt = ({ seed = 'luxe', className }) => { |
| 4 | + // LCG Random Generator |
| 5 | + const rng = useMemo(() => { |
| 6 | + let h = 0xdeadbeef; |
| 7 | + for (let i = 0; i < seed.length; i++) { |
| 8 | + h = Math.imul(h ^ seed.charCodeAt(i), 2654435761); |
| 9 | + } |
| 10 | + return () => { |
| 11 | + h = Math.imul(h ^ (h >>> 16), 2246822507); |
| 12 | + h = Math.imul(h ^ (h >>> 13), 3266489909); |
| 13 | + return ((h ^= h >>> 16) >>> 0) / 4294967296; |
| 14 | + }; |
| 15 | + }, [seed]); |
| 16 | + |
| 17 | + const shapes = useMemo(() => { |
| 18 | + const r = rng; // concise alias |
| 19 | + const items = []; |
| 20 | + |
| 21 | + // Palette Generation |
| 22 | + const baseHue = Math.floor(r() * 360); |
| 23 | + // const saturation = 20 + r() * 20; // Low saturation for Luxe feel |
| 24 | + // const lightness = 80 + r() * 10; // High lightness |
| 25 | + |
| 26 | + // Generate organic curves (Silk/Marble effect) |
| 27 | + const curveCount = 8 + Math.floor(r() * 5); |
| 28 | + |
| 29 | + for (let i = 0; i < curveCount; i++) { |
| 30 | + const points = []; |
| 31 | + const segments = 4; |
| 32 | + const startY = r() * 100; |
| 33 | + |
| 34 | + points.push({ x: 0, y: startY }); |
| 35 | + |
| 36 | + for (let j = 1; j <= segments; j++) { |
| 37 | + points.push({ |
| 38 | + x: (j / segments) * 100, |
| 39 | + y: startY + (r() - 0.5) * 50 // Variation |
| 40 | + }); |
| 41 | + } |
| 42 | + |
| 43 | + // Create smooth bezier path |
| 44 | + let d = `M ${points[0].x} ${points[0].y}`; |
| 45 | + for (let j = 0; j < points.length - 1; j++) { |
| 46 | + const p0 = points[j]; |
| 47 | + const p1 = points[j + 1]; |
| 48 | + // Simple catmull-rom or quadratic approx? |
| 49 | + // Let's use simple cubic bezier for smoothness |
| 50 | + const cp1x = p0.x + (p1.x - p0.x) / 2; |
| 51 | + const cp1y = p0.y; |
| 52 | + const cp2x = p0.x + (p1.x - p0.x) / 2; |
| 53 | + const cp2y = p1.y; |
| 54 | + d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p1.x} ${p1.y}`; |
| 55 | + } |
| 56 | + |
| 57 | + // Close the shape to bottom/corners to form a fillable area |
| 58 | + d += ` L 100 100 L 0 100 Z`; |
| 59 | + |
| 60 | + const opacity = 0.05 + r() * 0.15; |
| 61 | + // const color = `hsla(${baseHue + (r() - 0.5) * 40}, ${saturation}%, ${lightness - i * 5}%, ${opacity})`; |
| 62 | + // Force grayscale/gold/bronze tones for "Luxe" |
| 63 | + const isGold = r() > 0.8; |
| 64 | + const hue = isGold ? 45 : baseHue; // 45 is roughly gold |
| 65 | + const sat = isGold ? 60 : 0; // Grayscale or Gold |
| 66 | + const lit = isGold ? 60 : 90 - i * 5; |
| 67 | + |
| 68 | + items.push({ |
| 69 | + d, |
| 70 | + fill: `hsla(${hue}, ${sat}%, ${lit}%, ${opacity})`, |
| 71 | + stroke: `hsla(${hue}, ${sat}%, ${lit - 20}%, ${opacity * 2})` |
| 72 | + }); |
| 73 | + } |
| 74 | + |
| 75 | + // Add some noise texture specks |
| 76 | + const specks = []; |
| 77 | + for(let k=0; k<50; k++) { |
| 78 | + specks.push({ |
| 79 | + cx: r() * 100, |
| 80 | + cy: r() * 100, |
| 81 | + r: r() * 0.3, |
| 82 | + fill: 'rgba(0,0,0,0.1)' |
| 83 | + }); |
| 84 | + } |
| 85 | + |
| 86 | + return { curves: items, specks }; |
| 87 | + }, [rng]); |
| 88 | + |
| 89 | + return ( |
| 90 | + <div className={`w-full h-full bg-[#EBEBEB] overflow-hidden relative ${className}`}> |
| 91 | + <svg |
| 92 | + viewBox="0 0 100 100" |
| 93 | + preserveAspectRatio="none" |
| 94 | + className="w-full h-full" |
| 95 | + > |
| 96 | + {shapes.curves.map((shape, i) => ( |
| 97 | + <path |
| 98 | + key={`curve-${i}`} |
| 99 | + d={shape.d} |
| 100 | + fill={shape.fill} |
| 101 | + stroke={shape.stroke} |
| 102 | + strokeWidth="0.1" |
| 103 | + style={{ mixBlendMode: 'multiply' }} |
| 104 | + /> |
| 105 | + ))} |
| 106 | + {shapes.specks.map((s, i) => ( |
| 107 | + <circle key={`speck-${i}`} cx={s.cx} cy={s.cy} r={s.r} fill={s.fill} /> |
| 108 | + ))} |
| 109 | + </svg> |
| 110 | + </div> |
| 111 | + ); |
| 112 | +}; |
| 113 | + |
| 114 | +export default LuxeArt; |
0 commit comments