feat: integrate custom brand logo, navbar updates, favicon, and cached image visibility fix

This commit is contained in:
ssbg04 committed 2026-07-07 21:10:58 +08:00
1 parent c20bf00236
commit cfb5c0a68e
4 files changed
+86 -70

No files matched your search

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

+79 -66
View File
@@ -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>
+6 -3
View File
@@ -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>
+1 -1
View File
@@ -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 -->