1
- // src/components/sections/Hero.jsx
2
- import React from 'react' ;
1
+ import React , { useState , useEffect } from 'react' ;
3
2
import { Link } from 'react-router-dom' ;
4
3
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
5
4
import { faEye , faPaperPlane } from '@fortawesome/free-solid-svg-icons' ;
6
5
import { faGithub , faLinkedinIn , faXTwitter } from '@fortawesome/free-brands-svg-icons' ;
7
6
8
7
const Hero = ( { content, loading } ) => {
9
- // Render loading state with a layout matching the final content structure
10
- if ( loading ) {
11
- return (
12
- < section className = "hero py-8 md:py-12" >
13
- < div className = "container px-4 mx-auto" >
14
- < div className = "hero-content flex flex-col md:flex-row items-center" >
15
- < div className = "hero-text w-full md:w-3/5 animate-pulse mb-8 md:mb-0" >
16
- < div className = "h-6 bg-gray-200 rounded w-1/3 mb-4" > </ div >
17
- < div className = "h-12 bg-gray-200 rounded w-3/4 mb-4" > </ div >
18
- < div className = "h-4 bg-gray-200 rounded w-2/3 mb-2" > </ div >
19
- < div className = "h-4 bg-gray-200 rounded w-5/6 mb-2" > </ div >
20
- < div className = "h-4 bg-gray-200 rounded w-4/6 mb-6" > </ div >
21
- < div className = "flex flex-col sm:flex-row gap-4 mb-8" >
22
- < div className = "h-10 bg-gray-200 rounded-full w-full sm:w-32" > </ div >
23
- < div className = "h-10 bg-gray-200 rounded-full w-full sm:w-32" > </ div >
24
- </ div >
25
- < div className = "flex gap-4" >
26
- < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
27
- < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
28
- < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
29
- </ div >
30
- </ div >
31
-
32
- < div className = "hero-image w-full md:w-2/5 animate-pulse" >
33
- < div className = "avatar mx-auto md:ml-auto md:mr-0 max-w-xs" >
34
- < div className = "w-full h-full bg-gray-200 rounded-lg aspect-square" > </ div >
35
- </ div >
36
- </ div >
37
- </ div >
38
- </ div >
39
- </ section >
40
- ) ;
41
- }
8
+ const [ isReady , setIsReady ] = useState ( false ) ; // Single state to control rendering
42
9
43
- // Default metadata for static demo
10
+ // Default metadata
44
11
const defaultMetadata = {
45
12
name : 'Digin Dominic' ,
46
13
title : 'Software Engineer | Research Toolsmith | Data Workflow Architect' ,
47
- subtitle : 'I build powerful tools that bridge science and software. From high-performance image segmentation apps to intuitive 3D data visualizations, I create solutions that turn complex research into accessible, interactive, and scalable applications. Whether it’ s automating microscopy workflows or designing end-to-end pipelines for brain mapping, my work empowers scientists with the right technology—precise, efficient, and beautiful.' ,
14
+ subtitle : 'I build powerful tools that bridge science and software. From high-performance image segmentation apps to intuitive 3D data visualizations, I create solutions that turn complex research into accessible, interactive, and scalable applications. Whether it\' s automating microscopy workflows or designing end-to-end pipelines for brain mapping, my work empowers scientists with the right technology—precise, efficient, and beautiful.' ,
48
15
profileImage : 'https://raw.githubusercontent.com/digin1/web-images/refs/heads/main/digin.png' ,
49
16
primaryCta : 'View My Work' ,
50
17
primaryCtaLink : '/projects' ,
51
18
secondaryCta : 'Contact Me' ,
52
19
secondaryCtaLink : '/about'
53
20
} ;
54
21
55
- // Use content from props or fall back to default
56
22
const metadata = ( content && content . metadata ) ? content . metadata : defaultMetadata ;
57
-
58
- // Extract values from metadata
59
23
const {
60
24
name = defaultMetadata . name ,
61
25
title = defaultMetadata . title ,
@@ -66,28 +30,72 @@ const Hero = ({ content, loading }) => {
66
30
secondaryCta = defaultMetadata . secondaryCta ,
67
31
secondaryCtaLink = defaultMetadata . secondaryCtaLink ,
68
32
} = metadata ;
69
-
70
- // Process subtitle for multiline support
33
+
71
34
const processedSubtitle = subtitle . replace ( / \\ n / g, '\n' ) ;
72
35
const paragraphs = processedSubtitle . split ( '\n' ) . filter ( p => p . trim ( ) !== '' ) ;
73
36
74
- return (
37
+ // Preload image and control rendering
38
+ useEffect ( ( ) => {
39
+ if ( ! loading ) {
40
+ const img = new Image ( ) ;
41
+ img . onload = ( ) => setIsReady ( true ) ;
42
+ img . onerror = ( ) => setIsReady ( true ) ; // Fallback to render content even on error
43
+ img . src = profileImage ;
44
+ } else {
45
+ setIsReady ( false ) ; // Reset when loading starts
46
+ }
47
+ } , [ loading , profileImage ] ) ;
48
+
49
+ const renderLoadingSkeleton = ( ) => (
75
50
< section className = "hero py-8 md:py-16" >
76
51
< div className = "container px-4 mx-auto" >
77
52
< div className = "hero-content flex flex-col md:flex-row items-center" >
78
53
< div className = "hero-text w-full md:w-3/4 lg:w-3/5 order-2 md:order-1 mt-8 md:mt-0" >
79
- < p className = "subtitle text-base md:text-base lg:text-lg animate-fadeInUp whitespace-normal break-words leading-normal" > { title } </ p >
80
- < h1 className = "title text-3xl md:text-4xl lg:text-5xl font-bold mt-1 mb-3 animate-fadeInUp delay-100" >
54
+ < div className = "h-6 bg-gray-200 rounded w-1/3 mb-4" > </ div >
55
+ < div className = "h-12 bg-gray-200 rounded w-3/4 mb-4" > </ div >
56
+ < div className = "space-y-2 mb-6" >
57
+ < div className = "h-4 bg-gray-200 rounded w-full" > </ div >
58
+ < div className = "h-4 bg-gray-200 rounded w-5/6" > </ div >
59
+ < div className = "h-4 bg-gray-200 rounded w-full" > </ div >
60
+ < div className = "h-4 bg-gray-200 rounded w-4/5" > </ div >
61
+ </ div >
62
+ < div className = "flex flex-col sm:flex-row gap-4 mb-8" >
63
+ < div className = "h-10 bg-gray-200 rounded-full w-full sm:w-32" > </ div >
64
+ < div className = "h-10 bg-gray-200 rounded-full w-full sm:w-32" > </ div >
65
+ </ div >
66
+ < div className = "flex gap-4" >
67
+ < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
68
+ < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
69
+ < div className = "h-8 w-8 bg-gray-200 rounded-full" > </ div >
70
+ </ div >
71
+ </ div >
72
+ < div className = "hero-image w-full md:w-2/5 order-1 md:order-2 mt-8 md:mt-0" >
73
+ < div className = "avatar mx-auto md:ml-auto md:mr-0 max-w-xs" >
74
+ < div className = "w-full h-auto aspect-square bg-gray-200 rounded-lg" > </ div >
75
+ </ div >
76
+ </ div >
77
+ </ div >
78
+ </ div >
79
+ </ section >
80
+ ) ;
81
+
82
+ const renderContent = ( ) => (
83
+ < section className = "hero py-8 md:py-16" >
84
+ < div className = "container px-4 mx-auto" >
85
+ < div className = "hero-content flex flex-col md:flex-row items-center" >
86
+ < div className = "hero-text w-full md:w-3/4 lg:w-3/5 order-2 md:order-1 mt-8 md:mt-0" >
87
+ < p className = "subtitle text-base md:text-base lg:text-lg whitespace-normal break-words leading-normal" > { title } </ p >
88
+ < h1 className = "title text-3xl md:text-4xl lg:text-5xl font-bold mt-1 mb-3" >
81
89
Building < span className = "text-primary" > impactful</ span > digital solutions
82
90
</ h1 >
83
- < div className = "description text-sm md:text-base animate-fadeInUp delay-200 " >
91
+ < div className = "description text-sm md:text-base" >
84
92
{ paragraphs . map ( ( paragraph , index ) => (
85
93
< p key = { index } className = { index > 0 ? 'mt-4' : '' } >
86
94
{ paragraph }
87
95
</ p >
88
96
) ) }
89
97
</ div >
90
- < div className = "flex flex-col sm:flex-row gap-4 mt-6 mb-8 animate-fadeInUp delay-300 " >
98
+ < div className = "flex flex-col sm:flex-row gap-4 mt-6 mb-8" >
91
99
< Link to = { primaryCtaLink } className = "cta-primary py-2 px-6 rounded-full bg-primary text-white text-center hover:bg-primary-dark transition" >
92
100
< FontAwesomeIcon icon = { faEye } size = "sm" className = "mr-2" /> { primaryCta }
93
101
</ Link >
@@ -97,7 +105,7 @@ const Hero = ({ content, loading }) => {
97
105
</ Link >
98
106
) }
99
107
</ div >
100
- < div className = "social-links flex gap-4 animate-fadeInUp delay-400 " >
108
+ < div className = "social-links flex gap-4" >
101
109
< a href = "https://github.com/digin1" className = "social-link p-2 text-gray-600 hover:text-primary transition" >
102
110
< FontAwesomeIcon icon = { faGithub } size = "lg" />
103
111
</ a >
@@ -109,7 +117,7 @@ const Hero = ({ content, loading }) => {
109
117
</ a >
110
118
</ div >
111
119
</ div >
112
- < div className = "hero-image w-full md:w-2/5 order-1 md:order-2 animate-fadeInUp delay-200 mt-8 md:mt-0" >
120
+ < div className = "hero-image w-full md:w-2/5 order-1 md:order-2 mt-8 md:mt-0" >
113
121
< div className = "avatar mx-auto md:ml-auto md:mr-0 max-w-xs" >
114
122
< img
115
123
src = { profileImage }
@@ -126,6 +134,8 @@ const Hero = ({ content, loading }) => {
126
134
</ div >
127
135
</ section >
128
136
) ;
137
+
138
+ return loading || ! isReady ? renderLoadingSkeleton ( ) : renderContent ( ) ;
129
139
} ;
130
140
131
141
export default Hero ;
0 commit comments