feat: Update About page content with new media, refactor BentoGrid layout, and optimize particle background performance and styling.

This commit is contained in:
2026-02-03 01:29:11 -06:00
parent c2f3b57837
commit 2f8b4e690e
25 changed files with 128 additions and 49 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 825 KiB

View File

@@ -8,39 +8,42 @@ interface BentoItem {
description: string;
className?: string; // For sizing: bento-large, bento-wide, bento-tall
bgImage: string | string[];
videoSrc?: string; // Optional video source
}
const items: BentoItem[] = [
{
id: 'gaming',
title: "Gaming & Tech",
description: "Wind down with some PC gaming or just appreciating good new gear.",
title: "Gaming",
description: "From Old Classic Fighting Game Tournaments to New PC Gaming Experiences. I enjoy it all",
className: "bento-large",
bgImage: "https://placehold.co/800x800/2a1a4a/FFF?text=Gaming+Setup"
bgImage: "/images/about/slab.jpg"
},
{
id: 'snowboarding',
title: "Snowboarding",
description: "The rush of going down the mountains are unmatched",
description: "Here is me teaching my girlfriend😅",
className: "bento-tall",
bgImage: "https://placehold.co/400x800/a7ffeb/004d40?text=Snowboarding"
bgImage: "https://placehold.co/400x800/a7ffeb/004d40?text=Snowboarding",
videoSrc: "/images/about/snowboard2.mp4"
},
{
id: 'cooking',
title: "Culinary Arts",
description: "Experimenting with new recipes and flavors in the kitchen.",
className: "", // Standard 1x1
bgImage: "https://placehold.co/400x400/3e2723/d7ccc8?text=Cooking"
title: "Cooking",
description: "Everyone cooks sure, I'll give you that. But I want to master the art of cooking a excellent steak.",
className: "",
bgImage: "/images/about/steak.jpg"
},
{
id: 'social',
title: "Family & Friends",
description: "Good times with my favorite people. Including my Girlfriend, Friends and Family",
className: "", // Standard 1x1
className: "bento-tall",
bgImage: [
"https://placehold.co/400x400/f8bbd0/880e4f?text=Social+1",
"https://placehold.co/400x400/d81b60/ffffff?text=Social+2",
"https://placehold.co/400x400/880e4f/f8bbd0?text=Social+3"
"/images/about/girl1.jpg",
"/images/about/family1.jpg",
"/images/about/family2.jpg",
"/images/about/friend2.jpg",
]
},
{
@@ -49,6 +52,59 @@ const items: BentoItem[] = [
description: "I enjoy the challenge of self-hosting my own digital services at home. I also love tinkering with new hardware and software setups.",
className: "bento-wide",
bgImage: "/images/projects/homelabber.png"
},
{
id: 'controller-modding',
title: "Controller Modding",
description: "Restoring and modifying retro game controllers. Giving new life to old hardware.",
className: "", // Standard 1x1
bgImage: "/images/about/gamecube.jpg"
},
{
id: 'outdoor-exploring',
title: "Outdoor Exploring",
description: "Hiking mountains, exploring caves, beaches and more.",
className: "bento-tall",
bgImage: [
"/images/about/outdoor1.jpg",
"/images/about/outdoor2.jpg",
"/images/about/beach1.jpg",
]
},
{
id: 'smart-home',
title: "Smart Home",
description: "Automating my living space with smart devices and custom integrations.",
className: "", // Standard 1x1
bgImage: "https://placehold.co/400x400/0288d1/e1f5fe?text=Smart+Home"
},
{
id: 'pc-building',
title: "Computer Building",
description: "Designing and assembling custom PC builds for performance and aesthetics.",
className: "bento-wide",
bgImage: "/images/about/pcBuild.jpg"
},
{
id: 'small-town',
title: "Small Town Origin",
description: "I'm from Melfort, Saskatchewan. A small town with a big heart.",
className: "", // Standard 1x1
bgImage: [
"/images/about/melfort1.jpg",
"/images/about/melfort2.jpg",
"/images/about/melfort3.png",
"/images/about/melfort4.jpg",
"/images/about/melfort5.jpg",
]
},
{
id: 'cats',
title: "Cats",
description: "I see a cat, I pet a cat. Simple as that.",
className: "bento-large",
bgImage: "https://placehold.co/800x800/ffccbc/bf360c?text=Cats", // Fallback
videoSrc: "/images/about/kitty.mp4"
}
];
@@ -71,6 +127,17 @@ const BentoCard = ({ item, index, delay }: { item: BentoItem; index: number; del
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: delay + index * 0.1 }}
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
loop
muted
playsInline
className="bento-bg"
style={{ objectFit: 'cover', opacity: 0.6 }}
/>
) : (
<AnimatePresence mode="popLayout" initial={false}>
<motion.img
key={currentIndex}
@@ -86,6 +153,7 @@ const BentoCard = ({ item, index, delay }: { item: BentoItem; index: number; del
}}
/>
</AnimatePresence>
)}
<div className="bento-overlay" />
<div className="bento-content">
<h3 className="bento-title">{item.title}</h3>

View File

@@ -79,6 +79,7 @@ const ParticlesBackground: React.FC = () => {
// Mobile (390x844) -> ~48 particles
// Mobile Landscape (844x390) -> ~48 particles (Same as portrait!)
const area = canvas.width * canvas.height;
// Reduce density further to specificially help the heavy About page
const particleCount = Math.floor(Math.sqrt(area) / 12);
particles = [];
@@ -90,17 +91,7 @@ const ParticlesBackground: React.FC = () => {
const animate = () => {
if (!ctx || !canvas) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw a subtle gradient background for the canvas itself if needed, or keep transparent
// For now, let's keep it transparent so we can style the container behind it if we want,
// OR we can make this the definitive background.
// Let's add a deep gradient here to match the user's previous aesthetic but cooler.
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#0f0c29');
gradient.addColorStop(0.5, '#302b63');
gradient.addColorStop(1, '#24243e');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Gradient is now handled by CSS to avoid repainting every frame
particles.forEach((particle) => {
particle.update();
@@ -119,13 +110,32 @@ const ParticlesBackground: React.FC = () => {
for (let b = a; b < particles.length; b++) {
const dx = particles[a].x - particles[b].x;
const dy = particles[a].y - particles[b].y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Optimization 1: Strictly check max possible visible distance
// The opacity formula becomes <= 0 when distance > (2/3 * distanceThreshold).
// Max distanceThreshold is 300, so max visible distance is 200.
if (Math.abs(dx) > 200 || Math.abs(dy) > 200) continue;
const distSq = dx * dx + dy * dy;
const sizeAverage = (particles[a].size + particles[b].size) / 2;
// Scale from 100 to 300 based on size average (2 to 6)
const distanceThreshold = 100 + (sizeAverage - 2) * 50;
if (distance < distanceThreshold) {
ctx.strokeStyle = `rgba(255, 255, 255, ${(distanceThreshold * (2 / 3) * .001) - distance / 1000})`;
// Optimization 2: Only calculate if we are within the VISIBLE threshold (2/3 of logical threshold)
// Opacity = (Threshold * 2/3 * 0.001) - (dist / 1000)
// 0 = (2/3 T)/1000 - d/1000 => d = 2/3 T
const distinctThreshold = distanceThreshold * (2 / 3);
const distinctThresholdSq = distinctThreshold * distinctThreshold;
if (distSq < distinctThresholdSq) {
const distance = Math.sqrt(distSq);
// Calculate opacity
const opacity = (distanceThreshold * (2 / 3) * .001) - distance / 1000;
if (opacity > 0) {
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.lineWidth = sizeAverage;
ctx.beginPath();
ctx.moveTo(particles[a].x, particles[a].y);
@@ -135,6 +145,7 @@ const ParticlesBackground: React.FC = () => {
}
}
}
}
init();
animate();
@@ -166,6 +177,7 @@ const ParticlesBackground: React.FC = () => {
width: '100%',
height: '120vh', // Extend well below viewport to cover mobile browser bar retraction
zIndex: -1, // Behind everything
background: 'linear-gradient(to bottom, #0f0c29 0%, #302b63 50%, #24243e 100%)'
}}
/>
);

View File

@@ -34,7 +34,7 @@ export default function About() {
transition={{ duration: 0.8 }}
>
<div style={{ width: "100%", maxWidth: "1200px" }}>
<motion.h1
{/* <motion.h1
className="about-title"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
@@ -42,7 +42,7 @@ export default function About() {
style={{ textAlign: "center" }}
>
About Me
</motion.h1>
</motion.h1> */}
<BioSection
imageSrc="/images/about/dapperSasha.jpg"
@@ -57,7 +57,6 @@ export default function About() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.8 }}
>
<h2 style={{ textAlign: "center", fontSize: "1.5rem", marginBottom: "30px", color: "rgba(255,255,255,0.9)" }}>Personal Interests</h2>
<BentoGrid delay={1.0} />
</motion.div>
<motion.div

View File

@@ -1,7 +1,7 @@
.bento-grid-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(2, 250px);
grid-auto-rows: 250px;
gap: 20px;
width: 100%;
max-width: 1200px;