diff options
Diffstat (limited to 'frontend/src/components/HeartLogo.tsx')
| -rw-r--r-- | frontend/src/components/HeartLogo.tsx | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/frontend/src/components/HeartLogo.tsx b/frontend/src/components/HeartLogo.tsx new file mode 100644 index 0000000..2d407f7 --- /dev/null +++ b/frontend/src/components/HeartLogo.tsx @@ -0,0 +1,270 @@ +import React, { useState, useEffect } from 'react' + +interface HeartLogoProps { + size?: 'small' | 'medium' | 'large' | 'header' | 'header-no-rays' + className?: string + onClick?: () => void +} + +export function HeartLogo({ size = 'small', className = '', onClick }: HeartLogoProps) { + const [animationOffset, setAnimationOffset] = useState(0) + const [isAnimating, setIsAnimating] = useState(false) + + // Click handler to toggle animation + const handleClick = () => { + setIsAnimating(!isAnimating) + if (onClick) { + onClick() + } + } + + // Animation for header beams + useEffect(() => { + if (size !== 'header-no-rays' || !isAnimating) return + + let animationId: number + let startTime = Date.now() + + const animate = () => { + const elapsed = Date.now() - startTime + const rotationSpeed = 0.02 // Slow rotation speed + const offset = (elapsed * rotationSpeed) % 360 + setAnimationOffset(offset) + animationId = requestAnimationFrame(animate) + } + + animationId = requestAnimationFrame(animate) + + return () => { + if (animationId) { + cancelAnimationFrame(animationId) + } + } + }, [size, isAnimating]) + if (size === 'header') { + // Header version with rays filling the entire header rectangle + return ( + <div className={`heart-logo ${size} ${className}`}> + <div className="heart-logo-rectangle"> + <div className="heart-logo-content"> + <div className="heart-logo-rays"> + <svg viewBox="0 0 100 100" className="heart-logo-rays-svg" preserveAspectRatio="none"> + {/* Header rays matching loading screen exactly */} + <g transform="translate(50, 50)"> + {[...Array(16)].map((_, i) => { + const angle = (i * 22.5); + const rayWidth = 2; // Much thinner rays + + // Create triangular ray path - thin rays extending to header edges + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from center (heart center) + const outerRadius = 200; // Extend far beyond to reach all header edges + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + const x3 = Math.cos(startAngle * Math.PI / 180) * outerRadius; + const y3 = Math.sin(startAngle * Math.PI / 180) * outerRadius; + const x4 = Math.cos(endAngle * Math.PI / 180) * outerRadius; + const y4 = Math.sin(endAngle * Math.PI / 180) * outerRadius; + + return ( + <polygon + key={i} + points={`${x1},${y1} ${x2},${y2} ${x4},${y4} ${x3},${y3}`} + fill={i % 2 === 0 ? "#8B0000" : "#000000"} + opacity="0.9" + /> + ); + })} + </g> + </svg> + </div> + <div className="heart-logo-outline"> + <svg viewBox="0 0 100 100" className="heart-logo-svg"> + <path d="M50,85 C50,85 10,60 10,35 C10,18 20,8 35,8 C42,8 50,13 50,25 C50,13 58,8 65,8 C80,8 90,18 90,35 C90,60 50,85 50,85 Z" + fill="#8B0000" + stroke="#8B0000" + strokeWidth="2"/> + </svg> + </div> + </div> + </div> + </div> + ) + } + + if (size === 'header-no-rays') { + // Header version with small red beams - compact sunlight effect + return ( + <div className={`heart-logo ${size} ${className}`} onClick={handleClick} style={{ cursor: 'pointer' }}> + <div className="heart-logo-content"> + <div className="heart-logo-rays"> + <svg viewBox="-200 -67 400 134" className="heart-logo-rays-svg" preserveAspectRatio="xMidYMid slice"> + {/* Red beams extending across entire header rectangle - optimized for wide screens */} + <g> + {[...Array(32)].map((_, i) => { + const angle = (i * 11.25) + animationOffset; // 32 rays, 11.25 degrees apart, animated + const rayWidth = 2; // Much narrower rays to show background between them + + // Create triangular ray path + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from heart center + + // Calculate intersection with rectangle bounds for wide header + const getIntersectionWithRect = (angleInDegrees: number) => { + const rad = angleInDegrees * Math.PI / 180; + const dx = Math.cos(rad); + const dy = Math.sin(rad); + + // Header rectangle bounds (wide format ~3:1 ratio) + const rectBoundsX = 200; // Full width + const rectBoundsY = 67; // Height to match header proportions + + // Calculate intersection with each edge + let t = Infinity; + + // Right edge (x = rectBoundsX) + if (dx > 0) { + t = Math.min(t, rectBoundsX / dx); + } + // Left edge (x = -rectBoundsX) + if (dx < 0) { + t = Math.min(t, -rectBoundsX / dx); + } + // Top edge (y = -rectBoundsY) + if (dy < 0) { + t = Math.min(t, -rectBoundsY / dy); + } + // Bottom edge (y = rectBoundsY) + if (dy > 0) { + t = Math.min(t, rectBoundsY / dy); + } + + return { x: dx * t, y: dy * t }; + }; + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + + const edge1 = getIntersectionWithRect(startAngle); + const edge2 = getIntersectionWithRect(endAngle); + + return ( + <polygon + key={i} + points={`${x1},${y1} ${x2},${y2} ${edge2.x},${edge2.y} ${edge1.x},${edge1.y}`} + fill={i % 2 === 0 ? "#DC2626" : "#B91C1C"} + opacity="0.6" + /> + ); + })} + </g> + </svg> + </div> + <div className="heart-logo-outline"> + <svg viewBox="0 0 100 100" className="heart-logo-svg"> + <path d="M50,85 C50,85 10,60 10,35 C10,18 20,8 35,8 C42,8 50,13 50,25 C50,13 58,8 65,8 C80,8 90,18 90,35 C90,60 50,85 50,85 Z" + fill="#8B0000" + stroke="#8B0000" + strokeWidth="2"/> + </svg> + </div> + </div> + </div> + ) + } + + // Original square version for other sizes + return ( + <div className={`heart-logo ${size} ${className}`}> + <div className="heart-logo-rectangle"> + <div className="heart-logo-content"> + <div className="heart-logo-rays"> + <svg viewBox="-100 -67 200 134" className="heart-logo-rays-svg"> + {/* Rising Sun flag style rays - triangular rays extending to rectangle edges */} + <g> + {[...Array(16)].map((_, i) => { + const angle = (i * 22.5); + const rayWidth = 11.25; // Half the angle between rays for triangular shape + + // Create triangular ray path + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from center (heart center) + + // Calculate intersection with rectangle bounds + // Rectangle aspect ratio 120:80 = 3:2, so use different bounds for x and y + const getIntersectionWithRect = (angleInDegrees: number) => { + const rad = angleInDegrees * Math.PI / 180; + const dx = Math.cos(rad); + const dy = Math.sin(rad); + + // Rectangle bounds matching the 120x80 aspect ratio (3:2) + const rectBoundsX = 100; // Full width + const rectBoundsY = 67; // 2/3 of width to maintain 3:2 ratio + + // Calculate intersection with each edge + let t = Infinity; + + // Right edge (x = rectBoundsX) + if (dx > 0) { + t = Math.min(t, rectBoundsX / dx); + } + // Left edge (x = -rectBoundsX) + if (dx < 0) { + t = Math.min(t, -rectBoundsX / dx); + } + // Top edge (y = -rectBoundsY) + if (dy < 0) { + t = Math.min(t, -rectBoundsY / dy); + } + // Bottom edge (y = rectBoundsY) + if (dy > 0) { + t = Math.min(t, rectBoundsY / dy); + } + + return { x: dx * t, y: dy * t }; + }; + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + + const edge1 = getIntersectionWithRect(startAngle); + const edge2 = getIntersectionWithRect(endAngle); + + return ( + <polygon + key={i} + points={`${x1},${y1} ${x2},${y2} ${edge2.x},${edge2.y} ${edge1.x},${edge1.y}`} + fill={i % 2 === 0 ? "#8B0000" : "#000000"} + opacity="0.9" + /> + ); + })} + </g> + </svg> + </div> + <div className="heart-logo-outline"> + <svg viewBox="0 0 100 100" className="heart-logo-svg"> + <path d="M50,85 C50,85 10,60 10,35 C10,18 20,8 35,8 C42,8 50,13 50,25 C50,13 58,8 65,8 C80,8 90,18 90,35 C90,60 50,85 50,85 Z" + fill="#8B0000" + stroke="#8B0000" + strokeWidth="2"/> + </svg> + </div> + </div> + </div> + </div> + ) +}
\ No newline at end of file |
