ChandimaPrabath
commited on
Commit
•
3b85fd7
1
Parent(s):
2e4a89e
0.0.0.2 Alpha
Browse files- frontend/src/components/HeroSection.css +17 -1
- frontend/src/components/HeroSection.js +51 -80
- frontend/src/components/Sidebar.css +0 -2
- frontend/src/components/Sidebar.js +26 -4
- frontend/src/lib/LoadBalancer.js +5 -1
- frontend/src/lib/config.js +1 -1
- frontend/src/skeletons/HeroSection.css +111 -0
- frontend/src/skeletons/HeroSection.js +16 -0
frontend/src/components/HeroSection.css
CHANGED
@@ -1,3 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
.hero-container {
|
2 |
position: relative;
|
3 |
width: 100%;
|
@@ -13,6 +23,7 @@
|
|
13 |
padding: 1rem;
|
14 |
padding-bottom: 2.5rem;
|
15 |
justify-content: flex-end;
|
|
|
16 |
}
|
17 |
|
18 |
.hero-image {
|
@@ -24,7 +35,7 @@
|
|
24 |
background-size: cover;
|
25 |
background-position: center;
|
26 |
background-repeat: no-repeat;
|
27 |
-
transition: opacity
|
28 |
opacity: 0;
|
29 |
}
|
30 |
|
@@ -54,6 +65,11 @@
|
|
54 |
font-size: 1rem;
|
55 |
font-weight: normal;
|
56 |
line-height: 1.4;
|
|
|
|
|
|
|
|
|
|
|
57 |
transition: font-size 0.3s ease-in-out;
|
58 |
}
|
59 |
|
|
|
1 |
+
/* Fade-in animation for the hero section */
|
2 |
+
@keyframes fadeIn {
|
3 |
+
0% {
|
4 |
+
opacity: 0;
|
5 |
+
}
|
6 |
+
100% {
|
7 |
+
opacity: 1;
|
8 |
+
}
|
9 |
+
}
|
10 |
+
|
11 |
.hero-container {
|
12 |
position: relative;
|
13 |
width: 100%;
|
|
|
23 |
padding: 1rem;
|
24 |
padding-bottom: 2.5rem;
|
25 |
justify-content: flex-end;
|
26 |
+
animation: fadeIn 1s ease-in-out; /* Add fade-in animation */
|
27 |
}
|
28 |
|
29 |
.hero-image {
|
|
|
35 |
background-size: cover;
|
36 |
background-position: center;
|
37 |
background-repeat: no-repeat;
|
38 |
+
transition: opacity 1s ease-in-out; /* Smooth transition */
|
39 |
opacity: 0;
|
40 |
}
|
41 |
|
|
|
65 |
font-size: 1rem;
|
66 |
font-weight: normal;
|
67 |
line-height: 1.4;
|
68 |
+
overflow: hidden; /* Hide text overflow */
|
69 |
+
display: -webkit-box; /* Use flexbox layout */
|
70 |
+
-webkit-line-clamp: 3; /* Limit text to 3 lines */
|
71 |
+
-webkit-box-orient: vertical; /* Set box orientation to vertical */
|
72 |
+
text-overflow: ellipsis; /* Add ellipsis (...) */
|
73 |
transition: font-size 0.3s ease-in-out;
|
74 |
}
|
75 |
|
frontend/src/components/HeroSection.js
CHANGED
@@ -1,113 +1,84 @@
|
|
1 |
'use client';
|
2 |
import { useState, useEffect, useRef } from 'react';
|
3 |
import './HeroSection.css';
|
4 |
-
|
5 |
-
|
6 |
-
{
|
7 |
-
title: "My Spy (2020)",
|
8 |
-
description: "An exciting new release!",
|
9 |
-
imageUrl: "https://i.ytimg.com/vi/pfAhQSz-j_o/maxresdefault.jpg"
|
10 |
-
},
|
11 |
-
{
|
12 |
-
title: "My Spy 2 (2024)",
|
13 |
-
description: "Don't miss this one!",
|
14 |
-
imageUrl: "https://m.media-amazon.com/images/M/MV5BNDA5MTlhMDMtN2QwOS00N2MwLTgyY2ItOTU3YjBkNjM4OTMwXkEyXkFqcGc@._V1_.jpg"
|
15 |
-
},
|
16 |
-
{
|
17 |
-
title: "The Matrix (1999)",
|
18 |
-
description: "A revolutionary sci-fi film.",
|
19 |
-
imageUrl: "https://www.visitindianacountypa.org/wp-content/uploads/2024/05/The-Matrix-1999-Movie-Poster.jpg"
|
20 |
-
},
|
21 |
-
{
|
22 |
-
title: "Inception (2010)",
|
23 |
-
description: "A mind-bending thriller.",
|
24 |
-
imageUrl: "https://i.ytimg.com/vi/T03XUKBn8UA/maxresdefault.jpg"
|
25 |
-
},
|
26 |
-
{
|
27 |
-
title: "Interstellar (2014)",
|
28 |
-
description: "A journey through space and time.",
|
29 |
-
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZgarY79EPQu_BBe86NdqmVxRhgH0N6AgLEA&s"
|
30 |
-
},
|
31 |
-
{
|
32 |
-
title: "Parasite (2019)",
|
33 |
-
description: "A gripping social thriller.",
|
34 |
-
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQxR9Ata15qwHHpAnmpQrxHjHc19Y2UwBaTYw&s"
|
35 |
-
},
|
36 |
-
{
|
37 |
-
title: "Spider-Man: Into the Spider-Verse (2018)",
|
38 |
-
description: "A fresh take on the Spider-Man story.",
|
39 |
-
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR_Xg7zSuKysUusyQFXFSB1oy82y9wjHPx_qQ&s"
|
40 |
-
},
|
41 |
-
{
|
42 |
-
title: "Knives Out (2019)",
|
43 |
-
description: "A clever whodunit mystery.",
|
44 |
-
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThiumkG9e13lc94b71SstKXbSiIrzcgZgBIA&s"
|
45 |
-
},
|
46 |
-
{
|
47 |
-
title: "Dune (2021)",
|
48 |
-
description: "An epic adaptation of the sci-fi classic.",
|
49 |
-
imageUrl: "https://spaceandsorcery.wordpress.com/wp-content/uploads/2021/10/dune-2021.jpg"
|
50 |
-
},
|
51 |
-
{
|
52 |
-
title: "Joker (2019)",
|
53 |
-
description: "A dark take on the iconic character.",
|
54 |
-
imageUrl: "https://miro.medium.com/v2/resize:fit:1400/1*WVZSodH2x8YwwjtotNbVxw.jpeg"
|
55 |
-
},
|
56 |
-
{
|
57 |
-
title: "Blade Runner 2049 (2017)",
|
58 |
-
description: "A visually stunning sci-fi sequel.",
|
59 |
-
imageUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSXIGK_OIyih2iwse36yeywRq4nYLocWtLjcQ&s"
|
60 |
-
}
|
61 |
-
];
|
62 |
-
|
63 |
|
64 |
const HeroSection = () => {
|
65 |
const [currentIndex, setCurrentIndex] = useState(0);
|
66 |
-
const intervalRef = useRef(null);
|
67 |
const [fadeOut, setFadeOut] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
const startAutoSwitch = () => {
|
70 |
if (intervalRef.current) clearInterval(intervalRef.current);
|
71 |
intervalRef.current = setInterval(() => {
|
72 |
-
setFadeOut(true);
|
73 |
setTimeout(() => {
|
74 |
-
setCurrentIndex(prevIndex => (prevIndex + 1) %
|
75 |
-
setFadeOut(false);
|
76 |
-
}, 200);
|
77 |
}, 5000);
|
78 |
};
|
79 |
|
80 |
useEffect(() => {
|
81 |
-
|
|
|
|
|
82 |
|
83 |
return () => {
|
84 |
-
clearInterval(intervalRef.current);
|
85 |
};
|
86 |
-
}, []);
|
87 |
|
88 |
const handleIndicatorClick = (index) => {
|
89 |
-
setFadeOut(true);
|
90 |
setTimeout(() => {
|
91 |
setCurrentIndex(index);
|
92 |
-
setFadeOut(false);
|
93 |
}, 100);
|
94 |
startAutoSwitch();
|
95 |
};
|
96 |
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
return () => {
|
101 |
-
clearInterval(intervalRef.current);
|
102 |
-
};
|
103 |
-
}, [currentIndex]);
|
104 |
|
105 |
-
const { title, description, imageUrl } =
|
106 |
|
107 |
return (
|
108 |
<div className="hero-container">
|
109 |
<div className="hero-section">
|
110 |
-
{
|
111 |
<div
|
112 |
key={index}
|
113 |
className={`hero-image ${index === currentIndex ? 'active' : ''} ${fadeOut ? 'fade-out' : ''}`}
|
@@ -116,12 +87,12 @@ const HeroSection = () => {
|
|
116 |
))}
|
117 |
<div className="hero-text">
|
118 |
<h1 className="hero-title">{title}</h1>
|
119 |
-
<
|
120 |
</div>
|
121 |
</div>
|
122 |
|
123 |
<div className="hero-indicators">
|
124 |
-
{
|
125 |
<div
|
126 |
key={index}
|
127 |
className={`indicator ${index === currentIndex ? 'active' : ''}`}
|
|
|
1 |
'use client';
|
2 |
import { useState, useEffect, useRef } from 'react';
|
3 |
import './HeroSection.css';
|
4 |
+
import apiClient from '@/api/apiClient';
|
5 |
+
import SkeletonLoader from '@/skeletons/HeroSection';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
const HeroSection = () => {
|
8 |
const [currentIndex, setCurrentIndex] = useState(0);
|
9 |
+
const intervalRef = useRef(null);
|
10 |
const [fadeOut, setFadeOut] = useState(false);
|
11 |
+
const [items, setItems] = useState([]);
|
12 |
+
const [loading, setLoading] = useState(true);
|
13 |
+
|
14 |
+
useEffect(() => {
|
15 |
+
const fetchRecentItems = async () => {
|
16 |
+
try {
|
17 |
+
const response = await apiClient.getRecent();
|
18 |
+
const films = response.films.map(film => ({
|
19 |
+
title: film[0],
|
20 |
+
description: film[2],
|
21 |
+
imageUrl: film[3],
|
22 |
+
type: 'Film'
|
23 |
+
}));
|
24 |
+
const series = response.series.map(serie => ({
|
25 |
+
title: serie[0],
|
26 |
+
description: serie[2],
|
27 |
+
imageUrl: serie[3],
|
28 |
+
type: 'Series'
|
29 |
+
}));
|
30 |
+
setItems([...films, ...series]);
|
31 |
+
} catch (error) {
|
32 |
+
console.error('Error fetching recent items:', error);
|
33 |
+
// Handle error (e.g., set a default list or show an error message)
|
34 |
+
} finally {
|
35 |
+
setLoading(false);
|
36 |
+
}
|
37 |
+
};
|
38 |
+
|
39 |
+
fetchRecentItems();
|
40 |
+
}, []);
|
41 |
|
42 |
const startAutoSwitch = () => {
|
43 |
if (intervalRef.current) clearInterval(intervalRef.current);
|
44 |
intervalRef.current = setInterval(() => {
|
45 |
+
setFadeOut(true);
|
46 |
setTimeout(() => {
|
47 |
+
setCurrentIndex(prevIndex => (prevIndex + 1) % items.length);
|
48 |
+
setFadeOut(false);
|
49 |
+
}, 200);
|
50 |
}, 5000);
|
51 |
};
|
52 |
|
53 |
useEffect(() => {
|
54 |
+
if (items.length > 0) {
|
55 |
+
startAutoSwitch();
|
56 |
+
}
|
57 |
|
58 |
return () => {
|
59 |
+
clearInterval(intervalRef.current);
|
60 |
};
|
61 |
+
}, [items]);
|
62 |
|
63 |
const handleIndicatorClick = (index) => {
|
64 |
+
setFadeOut(true);
|
65 |
setTimeout(() => {
|
66 |
setCurrentIndex(index);
|
67 |
+
setFadeOut(false);
|
68 |
}, 100);
|
69 |
startAutoSwitch();
|
70 |
};
|
71 |
|
72 |
+
if (loading) {
|
73 |
+
return <SkeletonLoader />;
|
74 |
+
}
|
|
|
|
|
|
|
|
|
75 |
|
76 |
+
const { title, description, imageUrl } = items[currentIndex];
|
77 |
|
78 |
return (
|
79 |
<div className="hero-container">
|
80 |
<div className="hero-section">
|
81 |
+
{items.map((item, index) => (
|
82 |
<div
|
83 |
key={index}
|
84 |
className={`hero-image ${index === currentIndex ? 'active' : ''} ${fadeOut ? 'fade-out' : ''}`}
|
|
|
87 |
))}
|
88 |
<div className="hero-text">
|
89 |
<h1 className="hero-title">{title}</h1>
|
90 |
+
<p className="hero-description">{description}</p>
|
91 |
</div>
|
92 |
</div>
|
93 |
|
94 |
<div className="hero-indicators">
|
95 |
+
{items.map((_, index) => (
|
96 |
<div
|
97 |
key={index}
|
98 |
className={`indicator ${index === currentIndex ? 'active' : ''}`}
|
frontend/src/components/Sidebar.css
CHANGED
@@ -87,11 +87,9 @@
|
|
87 |
@media only screen and (orientation: portrait) {
|
88 |
.sidebar-footer {
|
89 |
padding: 16px;
|
90 |
-
margin-bottom: 100px;
|
91 |
}
|
92 |
}
|
93 |
|
94 |
-
|
95 |
.sidebar-item {
|
96 |
display: flex;
|
97 |
align-items: center;
|
|
|
87 |
@media only screen and (orientation: portrait) {
|
88 |
.sidebar-footer {
|
89 |
padding: 16px;
|
|
|
90 |
}
|
91 |
}
|
92 |
|
|
|
93 |
.sidebar-item {
|
94 |
display: flex;
|
95 |
align-items: center;
|
frontend/src/components/Sidebar.js
CHANGED
@@ -12,7 +12,10 @@ import {
|
|
12 |
faTv,
|
13 |
faFilm,
|
14 |
faBookBookmark,
|
|
|
|
|
15 |
} from "@fortawesome/free-solid-svg-icons";
|
|
|
16 |
|
17 |
const Sidebar = () => {
|
18 |
const [isOpen, setIsOpen] = useState(false);
|
@@ -83,9 +86,7 @@ const Sidebar = () => {
|
|
83 |
</Link>
|
84 |
<Link
|
85 |
href="/new"
|
86 |
-
className={`sidebar-link ${
|
87 |
-
pathname === "/new" ? "active" : ""
|
88 |
-
}`}
|
89 |
onMouseEnter={handleMouseEnter}
|
90 |
onMouseLeave={handleMouseLeave}
|
91 |
>
|
@@ -105,7 +106,28 @@ const Sidebar = () => {
|
|
105 |
)}
|
106 |
{isOpen && (
|
107 |
<div className="sidebar-footer">
|
108 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
</div>
|
110 |
)}
|
111 |
</div>
|
|
|
12 |
faTv,
|
13 |
faFilm,
|
14 |
faBookBookmark,
|
15 |
+
faCodeCommit,
|
16 |
+
faBook,
|
17 |
} from "@fortawesome/free-solid-svg-icons";
|
18 |
+
import config from "@/lib/config";
|
19 |
|
20 |
const Sidebar = () => {
|
21 |
const [isOpen, setIsOpen] = useState(false);
|
|
|
86 |
</Link>
|
87 |
<Link
|
88 |
href="/new"
|
89 |
+
className={`sidebar-link ${pathname === "/new" ? "active" : ""}`}
|
|
|
|
|
90 |
onMouseEnter={handleMouseEnter}
|
91 |
onMouseLeave={handleMouseLeave}
|
92 |
>
|
|
|
106 |
)}
|
107 |
{isOpen && (
|
108 |
<div className="sidebar-footer">
|
109 |
+
<Link
|
110 |
+
href="/dashboard"
|
111 |
+
className={`sidebar-link ${
|
112 |
+
pathname === "/dashboard" ? "active" : ""
|
113 |
+
}`}
|
114 |
+
onMouseEnter={handleMouseEnter}
|
115 |
+
onMouseLeave={handleMouseLeave}
|
116 |
+
>
|
117 |
+
<SidebarItem icon={faBook} text="Dashboard" />
|
118 |
+
</Link>
|
119 |
+
<Link
|
120 |
+
href="/settings"
|
121 |
+
className={`sidebar-link ${
|
122 |
+
pathname === "/settings" ? "active" : ""
|
123 |
+
}`}
|
124 |
+
onMouseEnter={handleMouseEnter}
|
125 |
+
onMouseLeave={handleMouseLeave}
|
126 |
+
>
|
127 |
+
<SidebarItem icon={faCogs} text="Settings" />
|
128 |
+
</Link>
|
129 |
+
|
130 |
+
<SidebarItem icon={faCodeCommit} text={config.version} />
|
131 |
</div>
|
132 |
)}
|
133 |
</div>
|
frontend/src/lib/LoadBalancer.js
CHANGED
@@ -60,7 +60,11 @@ class LoadBalancerAPI {
|
|
60 |
async getAllTVShows() {
|
61 |
return this._getRequest('/api/get/tv/all');
|
62 |
}
|
63 |
-
|
|
|
|
|
|
|
|
|
64 |
async getDownloadProgress(url) {
|
65 |
return this._getRequestNoBase(url);
|
66 |
}
|
|
|
60 |
async getAllTVShows() {
|
61 |
return this._getRequest('/api/get/tv/all');
|
62 |
}
|
63 |
+
|
64 |
+
async getRecent() {
|
65 |
+
return this._getRequest('/api/get/recent');
|
66 |
+
}
|
67 |
+
|
68 |
async getDownloadProgress(url) {
|
69 |
return this._getRequestNoBase(url);
|
70 |
}
|
frontend/src/lib/config.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
const config = {
|
2 |
apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
|
3 |
searchUrl: 'https://unicone-studio-search.hf.space/api/search',
|
4 |
-
version: "0.0.0.
|
5 |
};
|
6 |
|
7 |
export default config;
|
|
|
1 |
const config = {
|
2 |
apiBaseUrl: 'https://hans-den-load-balancer.hf.space',
|
3 |
searchUrl: 'https://unicone-studio-search.hf.space/api/search',
|
4 |
+
version: "0.0.0.2 Alpha",
|
5 |
};
|
6 |
|
7 |
export default config;
|
frontend/src/skeletons/HeroSection.css
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* SkeletonLoader.css */
|
2 |
+
|
3 |
+
.skeleton-container {
|
4 |
+
position: relative;
|
5 |
+
width: 100%;
|
6 |
+
height: 480px; /* Set height to auto for responsiveness */
|
7 |
+
display: flex;
|
8 |
+
flex-direction: column;
|
9 |
+
justify-content: flex-end;
|
10 |
+
background-color: #1b1d2b; /* Dark purple background */
|
11 |
+
padding: 1rem;
|
12 |
+
padding-bottom: 2rem;
|
13 |
+
overflow: hidden;
|
14 |
+
}
|
15 |
+
|
16 |
+
.skeleton-image {
|
17 |
+
width: 100%;
|
18 |
+
height: 100%; /* Adjust height to fit container */
|
19 |
+
max-height: 400px; /* Maximum height to maintain aspect ratio */
|
20 |
+
border-radius: 4px;
|
21 |
+
background: linear-gradient(90deg, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
|
22 |
+
background-size: 200% 100%;
|
23 |
+
animation: shimmer 1.5s infinite;
|
24 |
+
}
|
25 |
+
|
26 |
+
.skeleton-text {
|
27 |
+
display: flex;
|
28 |
+
flex-direction: column;
|
29 |
+
gap: 0.4rem;
|
30 |
+
position: relative;
|
31 |
+
z-index: 1;
|
32 |
+
padding-top: 10px;
|
33 |
+
}
|
34 |
+
|
35 |
+
.skeleton-title, .skeleton-description {
|
36 |
+
background: linear-gradient(90deg, #3b3f5c 25%, #2b2d43 50%, #3b3f5c 75%);
|
37 |
+
background-size: 200% 100%;
|
38 |
+
border-radius: 4px;
|
39 |
+
animation: shimmer 1.5s infinite;
|
40 |
+
}
|
41 |
+
|
42 |
+
.skeleton-title {
|
43 |
+
width: 50%;
|
44 |
+
height: 2rem;
|
45 |
+
}
|
46 |
+
|
47 |
+
.skeleton-description {
|
48 |
+
width: 70%;
|
49 |
+
height: 1.2rem;
|
50 |
+
}
|
51 |
+
|
52 |
+
/* Responsive Styles */
|
53 |
+
@media (max-width: 1200px) {
|
54 |
+
.skeleton-title {
|
55 |
+
width: 60%;
|
56 |
+
}
|
57 |
+
.skeleton-description {
|
58 |
+
width: 80%;
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
@media (max-width: 992px) {
|
63 |
+
.skeleton-container {
|
64 |
+
height: 400px;
|
65 |
+
}
|
66 |
+
.skeleton-title {
|
67 |
+
width: 70%;
|
68 |
+
height: 1.8rem;
|
69 |
+
}
|
70 |
+
.skeleton-description {
|
71 |
+
width: 85%;
|
72 |
+
height: 1.1rem;
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
@media (max-width: 768px) {
|
77 |
+
.skeleton-container {
|
78 |
+
height: 300px;
|
79 |
+
}
|
80 |
+
.skeleton-title {
|
81 |
+
width: 80%;
|
82 |
+
height: 1.6rem;
|
83 |
+
}
|
84 |
+
.skeleton-description {
|
85 |
+
width: 90%;
|
86 |
+
height: 1rem;
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
@media (max-width: 576px) {
|
91 |
+
.skeleton-container {
|
92 |
+
height: 200px;
|
93 |
+
}
|
94 |
+
.skeleton-title {
|
95 |
+
width: 90%;
|
96 |
+
height: 1.4rem;
|
97 |
+
}
|
98 |
+
.skeleton-description {
|
99 |
+
width: 95%;
|
100 |
+
height: 0.9rem;
|
101 |
+
}
|
102 |
+
}
|
103 |
+
|
104 |
+
@keyframes shimmer {
|
105 |
+
0% {
|
106 |
+
background-position: -200% 0;
|
107 |
+
}
|
108 |
+
100% {
|
109 |
+
background-position: 200% 0;
|
110 |
+
}
|
111 |
+
}
|
frontend/src/skeletons/HeroSection.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// SkeletonLoader.js
|
2 |
+
import './HeroSection.css';
|
3 |
+
|
4 |
+
const SkeletonLoader = () => {
|
5 |
+
return (
|
6 |
+
<div className="skeleton-container">
|
7 |
+
<div className="skeleton-image"></div>
|
8 |
+
<div className="skeleton-text">
|
9 |
+
<div className="skeleton-title"></div>
|
10 |
+
<div className="skeleton-description"></div>
|
11 |
+
</div>
|
12 |
+
</div>
|
13 |
+
);
|
14 |
+
};
|
15 |
+
|
16 |
+
export default SkeletonLoader;
|