summaryrefslogtreecommitdiff
path: root/frontend/src/components/HeartLogo.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/HeartLogo.tsx')
-rw-r--r--frontend/src/components/HeartLogo.tsx270
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