Skip to content

Commit 6d2bd79

Browse files
committed
Add page transitions with Cozify loader, enhance image quality CSS
1 parent 14a9ff1 commit 6d2bd79

16 files changed

Lines changed: 692 additions & 15 deletions

src/components/AnimeCard.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@
1919
width: 100%;
2020
height: 100%;
2121
object-fit: cover;
22-
transition: transform 0.4s ease;
22+
transition: transform 0.4s ease, filter 0.3s ease;
23+
/* High quality image rendering */
24+
will-change: transform;
25+
-webkit-backface-visibility: hidden;
26+
backface-visibility: hidden;
2327
}
2428

2529
.card:hover .card-image img {
2630
transform: scale(1.08);
31+
filter: contrast(1.05) saturate(1.1) brightness(1.02);
2732
}
2833

2934
.card-overlay {

src/components/AnimeCard.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState, useRef } from 'react'
22
import { Link } from 'react-router-dom'
3+
import { getHighQualityImage } from '../utils/imageUtils'
34
import './AnimeCard.css'
45

56
export default function AnimeCard({ anime, index = 0 }) {
67
const { id, title, image, type, episodes, subEpisodes, dubEpisodes, duration, description, genres, status, rating, japaneseTitle, aired } = anime
8+
const hqImage = getHighQualityImage(image)
79
const [showPopup, setShowPopup] = useState(false)
810
const [popupSide, setPopupSide] = useState('right')
911
const cardRef = useRef(null)
@@ -36,7 +38,7 @@ export default function AnimeCard({ anime, index = 0 }) {
3638
>
3739
<Link to={`/anime/${id}`} className="card">
3840
<div className="card-image">
39-
<img src={image} alt={title} loading="lazy" />
41+
<img src={hqImage} alt={title} loading="lazy" />
4042

4143
<div className="card-overlay">
4244
<div className="card-play">

src/components/Layout.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { Outlet } from 'react-router-dom'
22
import Header from './Header'
33
import Footer from './Footer'
4+
import PageTransition from './PageTransition'
45
import './Layout.css'
56

67
export default function Layout() {
78
return (
89
<div className="layout">
910
<Header />
1011
<main className="main-content">
11-
<Outlet />
12+
<PageTransition>
13+
<Outlet />
14+
</PageTransition>
1215
</main>
1316
<Footer />
1417
</div>

src/components/PageTransition.css

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* Page Transition Animations */
2+
.page-content {
3+
min-height: 100vh;
4+
}
5+
6+
/* Page Loader with Cozify branding */
7+
.page-loader {
8+
position: fixed;
9+
inset: 0;
10+
z-index: 9999;
11+
display: flex;
12+
flex-direction: column;
13+
align-items: center;
14+
justify-content: center;
15+
gap: 24px;
16+
background: rgba(22, 21, 29, 0.7);
17+
backdrop-filter: blur(20px);
18+
-webkit-backdrop-filter: blur(20px);
19+
animation: loaderIn 0.3s ease-out;
20+
}
21+
22+
.page-loader.exit {
23+
animation: loaderOut 0.4s ease-out forwards;
24+
}
25+
26+
@keyframes loaderIn {
27+
0% {
28+
opacity: 0;
29+
}
30+
100% {
31+
opacity: 1;
32+
}
33+
}
34+
35+
@keyframes loaderOut {
36+
0% {
37+
opacity: 1;
38+
}
39+
100% {
40+
opacity: 0;
41+
}
42+
}
43+
44+
/* Cozify Logo */
45+
.loader-logo {
46+
display: flex;
47+
gap: 2px;
48+
}
49+
50+
.loader-logo span {
51+
font-size: 2rem;
52+
font-weight: 800;
53+
color: #ffbade;
54+
animation: letterPulse 0.8s ease-in-out infinite alternate;
55+
text-shadow: 0 0 20px rgba(255, 186, 222, 0.6);
56+
}
57+
58+
@keyframes letterPulse {
59+
0% {
60+
transform: translateY(0);
61+
text-shadow: 0 0 10px rgba(255, 186, 222, 0.4);
62+
}
63+
100% {
64+
transform: translateY(-4px);
65+
text-shadow: 0 0 25px rgba(255, 186, 222, 0.8);
66+
}
67+
}
68+
69+
/* Spinner */
70+
.loader-spinner {
71+
width: 40px;
72+
height: 40px;
73+
border: 3px solid rgba(255, 186, 222, 0.2);
74+
border-top-color: #ffbade;
75+
border-radius: 50%;
76+
animation: spin 0.8s linear infinite;
77+
}
78+
79+
@keyframes spin {
80+
to { transform: rotate(360deg); }
81+
}
82+
83+
/* Smooth content reveal for staggered elements */
84+
.page-transition.enter .anime-card,
85+
.page-transition.enter .browse-card,
86+
.page-transition.enter .schedule-card,
87+
.page-transition.enter .trending-item,
88+
.page-transition.enter .top-item {
89+
animation: staggerReveal 0.5s ease-out backwards;
90+
}
91+
92+
.page-transition.enter .anime-card:nth-child(1) { animation-delay: 0.05s; }
93+
.page-transition.enter .anime-card:nth-child(2) { animation-delay: 0.1s; }
94+
.page-transition.enter .anime-card:nth-child(3) { animation-delay: 0.15s; }
95+
.page-transition.enter .anime-card:nth-child(4) { animation-delay: 0.2s; }
96+
.page-transition.enter .anime-card:nth-child(5) { animation-delay: 0.25s; }
97+
.page-transition.enter .anime-card:nth-child(6) { animation-delay: 0.3s; }
98+
99+
@keyframes staggerReveal {
100+
0% {
101+
opacity: 0;
102+
transform: translateY(20px);
103+
}
104+
100% {
105+
opacity: 1;
106+
transform: translateY(0);
107+
}
108+
}

src/components/PageTransition.jsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useState, useEffect, useRef } from 'react'
2+
import { useLocation } from 'react-router-dom'
3+
import './PageTransition.css'
4+
5+
export default function PageTransition({ children }) {
6+
const location = useLocation()
7+
const [showLoader, setShowLoader] = useState(true)
8+
const [loaderExiting, setLoaderExiting] = useState(false)
9+
const lastPathRef = useRef('')
10+
const timer1Ref = useRef(null)
11+
const timer2Ref = useRef(null)
12+
13+
useEffect(() => {
14+
const currentPath = location.pathname
15+
const isInitialLoad = lastPathRef.current === ''
16+
const isPathChange = lastPathRef.current !== currentPath
17+
18+
if (!isInitialLoad && !isPathChange) return
19+
20+
lastPathRef.current = currentPath
21+
22+
// Clear any existing timers
23+
if (timer1Ref.current) clearTimeout(timer1Ref.current)
24+
if (timer2Ref.current) clearTimeout(timer2Ref.current)
25+
26+
// Show loader
27+
setShowLoader(true)
28+
setLoaderExiting(false)
29+
30+
// Start exit after delay
31+
timer1Ref.current = setTimeout(() => {
32+
if (isPathChange && !isInitialLoad) {
33+
window.scrollTo(0, 0)
34+
}
35+
setLoaderExiting(true)
36+
37+
// Hide completely
38+
timer2Ref.current = setTimeout(() => {
39+
setShowLoader(false)
40+
setLoaderExiting(false)
41+
}, 400)
42+
}, 600)
43+
44+
return () => {
45+
if (timer1Ref.current) clearTimeout(timer1Ref.current)
46+
if (timer2Ref.current) clearTimeout(timer2Ref.current)
47+
}
48+
}, [location.pathname])
49+
50+
return (
51+
<>
52+
{showLoader && (
53+
<div className={`page-loader ${loaderExiting ? 'exit' : ''}`}>
54+
<div className="loader-logo">
55+
{'Cozify'.split('').map((letter, i) => (
56+
<span key={i} style={{ animationDelay: `${i * 0.05}s` }}>
57+
{letter}
58+
</span>
59+
))}
60+
</div>
61+
<div className="loader-spinner"></div>
62+
</div>
63+
)}
64+
<div className="page-content">
65+
{children}
66+
</div>
67+
</>
68+
)
69+
}

0 commit comments

Comments
 (0)