From 3e7b2beca1136a42700a7e1aebfe4c0fb2861a00 Mon Sep 17 00:00:00 2001
From: soryu <soryu@soryu.co>
Date: Sat, 15 Nov 2025 18:00:09 +0000
Subject: Initial commit

---
 frontend/src/components/LoadingScreen.tsx | 129 ++++++++++++++++++++++++++++++
 1 file changed, 129 insertions(+)
 create mode 100644 frontend/src/components/LoadingScreen.tsx

(limited to 'frontend/src/components/LoadingScreen.tsx')

diff --git a/frontend/src/components/LoadingScreen.tsx b/frontend/src/components/LoadingScreen.tsx
new file mode 100644
index 0000000..5f33d00
--- /dev/null
+++ b/frontend/src/components/LoadingScreen.tsx
@@ -0,0 +1,129 @@
+import React, { useEffect, useState, useRef } from 'react'
+
+type Props = {
+  onComplete: () => void
+}
+
+export const LoadingScreen: React.FC<Props> = ({ onComplete }) => {
+  const [fadeOut, setFadeOut] = useState(false)
+  const [mounted, setMounted] = useState(true)
+  const [subtitle, setSubtitle] = useState('')
+  const [animatedText, setAnimatedText] = useState('')
+  const subtitleLoaded = useRef(false)
+
+  useEffect(() => {
+    console.log('Loading screen mounted - starting timer...')
+    
+    // Set fixed subtitle and animate it
+    if (!subtitleLoaded.current) {
+      subtitleLoaded.current = true
+      
+      const fixedSubtitle = 'Whisper of the Heart'
+      setSubtitle(fixedSubtitle)
+      
+      // Animate text character by character
+      let currentIndex = 0
+      const animateText = () => {
+        if (currentIndex <= fixedSubtitle.length) {
+          setAnimatedText(fixedSubtitle.slice(0, currentIndex))
+          currentIndex++
+          setTimeout(animateText, 50) // 50ms delay between characters
+        }
+      }
+      
+      // Start animation after heart appears (1000ms = 0.2s heart delay + 0.8s heart animation)
+      setTimeout(animateText, 1000)
+    }
+    
+    // Start fade after 4 seconds (longer to show ray spinning)
+    const fadeTimer = setTimeout(() => {
+      console.log('4 seconds passed - Starting fade out...')
+      setFadeOut(true)
+    }, 4000)
+
+    // Complete after fade finishes (4s show + 1s fade = 5s total)
+    const completeTimer = setTimeout(() => {
+      console.log('5 seconds passed - Loading screen completing...')
+      setMounted(false)
+      onComplete()
+    }, 5000)
+
+    return () => {
+      console.log('Cleaning up timers...')
+      clearTimeout(fadeTimer)
+      clearTimeout(completeTimer)
+    }
+  }, []) // Empty dependency array - run once on mount
+
+  const handleClick = () => {
+    console.log('Loading screen clicked, completing early...')
+    setFadeOut(true)
+    setTimeout(() => {
+      setMounted(false)
+      onComplete()
+    }, 1000)
+  }
+
+  console.log('LoadingScreen render - fadeOut:', fadeOut, 'mounted:', mounted)
+
+  if (!mounted) return null
+
+  return (
+    <div className={`loading-screen ${fadeOut ? 'fade-out' : ''}`} onClick={handleClick}>
+      <div className="loading-logo">
+        <div className="heart-container">
+          <div className="sun-rays loading-animate-rays">
+            <svg viewBox="0 0 100 100" className="rays-svg">
+              {/* Rising Sun flag style rays - triangular rays extending to edges */}
+              <g transform="translate(50, 50)">
+                {[...Array(16)].map((_, i) => {
+                  const angle = (i * 22.5);
+                  const nextAngle = ((i + 1) * 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)
+                  const outerRadius = 70; // Extend to screen edge
+
+                  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-outline loading-animate-heart">
+            <svg viewBox="0 0 100 100" className="heart-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 className="logo-text loading-animate-text">soryu</div>
+          <div className="loading-subtitle loading-animate-subtitle">{animatedText}</div>
+          <div className="loading-dots loading-animate-text">
+            <span>.</span><span>.</span><span>.</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

