feat: integrate custom brand logo, navbar updates, favicon, and cached image visibility fix
This commit is contained in:
1 parent
c20bf00236
commit
cfb5c0a68e
4 files changed
+86
-70
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 707 KiB |
+79
-66
@@ -12,11 +12,11 @@ interface InteractiveButtonProps {
|
||||
function InteractiveButton({ href, onClick, className, children, variant }: InteractiveButtonProps) {
|
||||
const x = useMotionValue(0);
|
||||
const y = useMotionValue(0);
|
||||
|
||||
|
||||
// Magnetic / Tilt effect relative to button bounds
|
||||
const rotateX = useTransform(y, [-40, 40], [8, -8]);
|
||||
const rotateY = useTransform(x, [-100, 100], [-8, 8]);
|
||||
|
||||
|
||||
// Radial glow coordinates inside the button
|
||||
const glowX = useMotionValue(0);
|
||||
const glowY = useMotionValue(0);
|
||||
@@ -26,14 +26,14 @@ function InteractiveButton({ href, onClick, className, children, variant }: Inte
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
|
||||
// Relative mouse position from center (-width/2 to width/2)
|
||||
const relX = event.clientX - rect.left - width / 2;
|
||||
const relY = event.clientY - rect.top - height / 2;
|
||||
|
||||
|
||||
x.set(relX);
|
||||
y.set(relY);
|
||||
|
||||
|
||||
// Pixel coordinates inside the button (for radial gradient)
|
||||
glowX.set(event.clientX - rect.left);
|
||||
glowY.set(event.clientY - rect.top);
|
||||
@@ -53,9 +53,9 @@ function InteractiveButton({ href, onClick, className, children, variant }: Inte
|
||||
onClick={onClick}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
style={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
transformStyle: 'preserve-3d',
|
||||
perspective: '600px'
|
||||
}}
|
||||
@@ -75,7 +75,7 @@ function InteractiveButton({ href, onClick, className, children, variant }: Inte
|
||||
opacity: glowOpacity
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
{/* Button Content */}
|
||||
<span className="relative z-10 flex items-center justify-center gap-2">
|
||||
{children}
|
||||
@@ -137,14 +137,15 @@ const socialLinks = [
|
||||
|
||||
export default function Hero({ fullName, title, valueProposition, heroImage }: HeroProps) {
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
const [imageLoaded, setImageLoaded] = React.useState(false);
|
||||
|
||||
// Profile card 3D tilt tracking
|
||||
const cardX = useMotionValue(0);
|
||||
const cardY = useMotionValue(0);
|
||||
const shineOpacity = useMotionValue(0);
|
||||
|
||||
const rotateX = useTransform(cardY, [-150, 150], [22, -22]);
|
||||
const rotateY = useTransform(cardX, [-150, 150], [-22, 22]);
|
||||
const rotateY = useTransform(cardX, [-150, 150], [22, -22]);
|
||||
|
||||
// Dynamic shine reflection offsets
|
||||
const shineX = useTransform(cardX, [-150, 150], [-120, 120]);
|
||||
@@ -154,25 +155,30 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
|
||||
// Relative coordinate from center
|
||||
const relX = event.clientX - rect.left - width / 2;
|
||||
const relY = event.clientY - rect.top - height / 2;
|
||||
|
||||
|
||||
cardX.set(relX);
|
||||
cardY.set(relY);
|
||||
animate(shineOpacity, 1, { duration: 0.25 });
|
||||
animate(shineOpacity, 1, { duration: 0.50 });
|
||||
};
|
||||
|
||||
const handleProfileMouseLeave = () => {
|
||||
// Return to absolute flat center smoothly
|
||||
animate(cardX, 0, { type: 'spring', stiffness: 150, damping: 20 });
|
||||
animate(cardY, 0, { type: 'spring', stiffness: 150, damping: 20 });
|
||||
animate(shineOpacity, 0, { duration: 0.35 });
|
||||
animate(shineOpacity, 0, { duration: 0.50 });
|
||||
};
|
||||
|
||||
const imgRef = React.useRef<HTMLImageElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
if (imgRef.current?.complete) {
|
||||
setImageLoaded(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSmoothScroll = (e: React.MouseEvent<HTMLAnchorElement>, targetId: string) => {
|
||||
@@ -207,9 +213,9 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
|
||||
const tiltVariants = {
|
||||
rest: { rotateX: 0, rotateY: 0, scale: 1 },
|
||||
hover: {
|
||||
rotateX: 8,
|
||||
rotateY: -12,
|
||||
hover: {
|
||||
rotateX: 8,
|
||||
rotateY: -12,
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.45, ease: "easeOut" }
|
||||
}
|
||||
@@ -218,7 +224,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
return (
|
||||
<section
|
||||
id="home"
|
||||
className="relative min-h-screen flex items-center justify-center pt-24 overflow-hidden"
|
||||
className="relative min-h-screen flex flex-col md:flex-row items-center justify-center pt-20 md:pt-24 pb-6 md:pb-0 overflow-hidden"
|
||||
>
|
||||
{/* Animated Aurora backgrounds */}
|
||||
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
|
||||
@@ -230,7 +236,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:14px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]" />
|
||||
</div>
|
||||
|
||||
<div className="container mx-auto px-6 max-w-5xl relative z-10 grid grid-cols-1 md:grid-cols-12 gap-12 items-center">
|
||||
<div className="container mx-auto px-6 max-w-5xl relative z-10 grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-12 items-center">
|
||||
{/* Left Column: Text & Actions */}
|
||||
<motion.div
|
||||
className="md:col-span-7 flex flex-col items-center md:items-start text-center md:text-left order-2 md:order-1"
|
||||
@@ -241,7 +247,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Profile/Badge */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full glass-card text-xs font-semibold text-primary-custom border border-primary-custom/10 mb-6 shadow-sm"
|
||||
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full glass-card text-[10px] sm:text-xs font-semibold text-primary-custom border border-primary-custom/10 mb-3 sm:mb-6 shadow-sm"
|
||||
>
|
||||
<span className="flex h-2 w-2 relative">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
@@ -253,7 +259,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Heading */}
|
||||
<motion.h1
|
||||
variants={itemVariants}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-6 bg-clip-text text-transparent bg-gradient-to-r from-foreground-custom via-foreground-custom to-primary-custom leading-tight"
|
||||
className="text-3xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-3 sm:mb-6 bg-clip-text text-transparent bg-gradient-to-r from-foreground-custom via-foreground-custom to-primary-custom leading-tight"
|
||||
>
|
||||
Hi, I'm <span className="text-glow text-primary-custom">{fullName}</span>
|
||||
</motion.h1>
|
||||
@@ -261,7 +267,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Title */}
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className="text-lg sm:text-xl md:text-2xl font-semibold text-muted-foreground-custom mb-4 font-heading"
|
||||
className="text-base sm:text-xl md:text-2xl font-semibold text-muted-foreground-custom mb-1.5 sm:mb-4 font-heading"
|
||||
>
|
||||
{title}
|
||||
</motion.p>
|
||||
@@ -269,7 +275,7 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Value Proposition */}
|
||||
<motion.p
|
||||
variants={itemVariants}
|
||||
className="text-sm sm:text-base md:text-lg text-muted-foreground-custom/80 mb-8 max-w-xl leading-relaxed"
|
||||
className="text-xs sm:text-base md:text-lg text-muted-foreground-custom/80 mb-4 sm:mb-8 max-w-xl leading-relaxed"
|
||||
>
|
||||
{valueProposition}
|
||||
</motion.p>
|
||||
@@ -277,45 +283,18 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Actions & Social Alignment Group */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="flex flex-col sm:flex-row items-center gap-6 mt-2 w-full justify-center md:justify-start"
|
||||
className="flex flex-col sm:flex-row items-center gap-4 sm:gap-6 mt-1 sm:mt-2 w-full justify-center md:justify-start"
|
||||
>
|
||||
{/* CTA Buttons */}
|
||||
<div className="flex flex-col sm:flex-row items-center gap-4 w-full sm:w-auto">
|
||||
<InteractiveButton
|
||||
href="#projects"
|
||||
onClick={(e) => handleSmoothScroll(e, 'projects')}
|
||||
variant="primary"
|
||||
className="w-full sm:w-auto px-6 py-3.5 rounded-xl bg-gradient-to-r from-primary-custom to-secondary-custom text-primary-foreground font-bold text-xs shadow-md hover:shadow-lg transition-all duration-200 text-center cursor-pointer uppercase tracking-wider group"
|
||||
>
|
||||
Explore Projects
|
||||
<svg className="w-3.5 h-3.5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</InteractiveButton>
|
||||
<InteractiveButton
|
||||
href="#contact"
|
||||
onClick={(e) => handleSmoothScroll(e, 'contact')}
|
||||
variant="secondary"
|
||||
className="w-full sm:w-auto px-6 py-3.5 rounded-xl glass-card text-foreground-custom font-bold text-xs border border-border/25 hover:bg-primary-custom/10 hover:text-primary-custom transition-all duration-200 text-center cursor-pointer uppercase tracking-wider"
|
||||
>
|
||||
Get In Touch
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
|
||||
</svg>
|
||||
</InteractiveButton>
|
||||
</div>
|
||||
|
||||
{/* Social Links & Divider */}
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="hidden sm:block w-[1px] h-8 bg-border/20" />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-4 sm:gap-6">
|
||||
<div className="flex items-center gap-3">
|
||||
{socialLinks.map((link) => (
|
||||
<div key={link.name} className="relative group flex flex-col items-center">
|
||||
<a
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-11 h-11 rounded-full glass-card hover:bg-primary-custom/10 hover:text-primary-custom transition-all duration-300 flex items-center justify-center"
|
||||
className="w-9 h-9 sm:w-11 sm:h-11 rounded-full glass-card hover:bg-primary-custom/10 hover:text-primary-custom transition-all duration-300 flex items-center justify-center"
|
||||
aria-label={`Visit Cris's ${link.name}`}
|
||||
>
|
||||
{link.icon}
|
||||
@@ -329,6 +308,33 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="hidden sm:block w-[1px] h-8 bg-border/20" />
|
||||
</div>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="flex flex-row items-center gap-3 w-full sm:w-auto justify-center">
|
||||
<InteractiveButton
|
||||
href="#projects"
|
||||
onClick={(e) => handleSmoothScroll(e, 'projects')}
|
||||
variant="primary"
|
||||
className="w-1/2 sm:w-auto px-4 py-2.5 rounded-lg bg-gradient-to-r from-primary-custom to-secondary-custom text-primary-foreground font-bold text-[10px] sm:text-xs shadow-md hover:shadow-lg transition-all duration-200 text-center cursor-pointer uppercase tracking-wider group flex items-center justify-center gap-1.5"
|
||||
>
|
||||
Projects
|
||||
<svg className="w-3 h-3 sm:w-3.5 sm:h-3.5 transform group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
</InteractiveButton>
|
||||
<InteractiveButton
|
||||
href="#contact"
|
||||
onClick={(e) => handleSmoothScroll(e, 'contact')}
|
||||
variant="secondary"
|
||||
className="w-1/2 sm:w-auto px-4 py-2.5 rounded-lg glass-card text-foreground-custom font-bold text-[10px] sm:text-xs border border-border/25 hover:bg-primary-custom/10 hover:text-primary-custom transition-all duration-200 text-center cursor-pointer uppercase tracking-wider flex items-center justify-center gap-1.5"
|
||||
>
|
||||
Contact
|
||||
<svg className="w-3 h-3 sm:w-3.5 sm:h-3.5" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
|
||||
</svg>
|
||||
</InteractiveButton>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
@@ -336,14 +342,15 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
{/* Right Column: Professional Profile Picture Frame */}
|
||||
<div className="md:col-span-5 flex justify-center order-1 md:order-2">
|
||||
<motion.div
|
||||
className="relative w-56 h-56 sm:w-64 sm:h-64 md:w-72 md:h-72 lg:w-80 lg:h-80 flex items-center justify-center cursor-pointer"
|
||||
className="relative w-40 h-40 sm:w-64 sm:h-64 md:w-72 md:h-72 lg:w-80 lg:h-80 flex items-center justify-center cursor-pointer"
|
||||
onMouseMove={handleProfileMouseMove}
|
||||
onMouseLeave={handleProfileMouseLeave}
|
||||
style={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
transformStyle: 'preserve-3d',
|
||||
perspective: '1000px'
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
style={{
|
||||
rotateX,
|
||||
rotateY,
|
||||
transformStyle: 'preserve-3d',
|
||||
perspective: '1000px'
|
||||
}}
|
||||
>
|
||||
{/* Glowing Backdrop Aura (Dynamic Shadow - translates deepest to Z: -40px) */}
|
||||
@@ -361,20 +368,26 @@ export default function Hero({ fullName, title, valueProposition, heroImage }: H
|
||||
|
||||
{/* The Transparent Glass Casing (translated forward to Z: 10px) */}
|
||||
<div
|
||||
className="relative w-full h-full rounded-[38px] p-4 glass-card border border-white/20 bg-white/5 backdrop-blur-xl shadow-2xl flex items-center justify-center overflow-hidden"
|
||||
className="relative w-full h-full rounded-[24px] sm:rounded-[38px] p-2.5 sm:p-4 border border-white/30 bg-gradient-to-br from-white/12 via-white/5 to-transparent backdrop-blur-2xl shadow-2xl flex items-center justify-center overflow-hidden"
|
||||
style={{ transformStyle: 'preserve-3d', transform: 'translateZ(10px)' }}
|
||||
>
|
||||
{/* Inner picture casing container (translated slightly in Z for depth) */}
|
||||
<div
|
||||
className="w-full h-full rounded-[28px] overflow-hidden bg-muted-custom/10 relative border border-white/10"
|
||||
<div
|
||||
className="w-full h-full rounded-[16px] sm:rounded-[28px] overflow-hidden bg-muted-custom/10 relative border border-white/10"
|
||||
style={{ transform: 'translateZ(15px)' }}
|
||||
>
|
||||
<img
|
||||
<motion.img
|
||||
ref={imgRef}
|
||||
src={heroImage || "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=350&h=350&q=80"}
|
||||
alt={fullName}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className="w-full h-full object-cover"
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
initial={{ scale: 1.15, opacity: 0 }}
|
||||
animate={imageLoaded ? { scale: 1, opacity: 1 } : { scale: 1.15, opacity: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
draggable={false}
|
||||
className="w-full h-full object-cover pointer-events-none select-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -86,9 +86,12 @@ export default function Navbar() {
|
||||
href="/"
|
||||
className="flex items-center gap-2 group font-semibold text-lg tracking-tight text-foreground-custom"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-primary-custom to-secondary-custom flex items-center justify-center text-white font-bold text-sm shadow-md group-hover:scale-105 transition-transform">
|
||||
CC
|
||||
</div>
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="Cris Charles Logo"
|
||||
draggable="false"
|
||||
className="w-8 h-8 rounded-full object-cover shadow-md group-hover:scale-105 transition-transform"
|
||||
/>
|
||||
<span className="hidden sm:inline font-heading font-bold text-glow">Cris Charles</span>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const {
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" href="/logo.png" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
|
||||
Reference in new issue
Block a user