Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 2 additions & 26 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { ConfigProvider, theme as antdTheme } from "antd";
import "./App.css";
import Header from "./components/header/Header";
import Home from "./components/home/Home";
import About from "./components/about/About";
import GitHubActivity from "./components/github/GitHubActivity";
import Skills from "./components/skills/Skills";
import Services from "./components/services/Services";
import Qualification from "./components/qualification/Qualification";
import Certificate from "./components/certificate/Certificate";
import Contact from "./components/contact/Contact";
import BentoGrid from "./components/bento/BentoGrid";
import Footer from "./components/footer/Footer";
import CursorGlow from "./components/effects/CursorGlow";
import ScrollReveal from "./components/effects/ScrollReveal";
Expand Down Expand Up @@ -152,26 +146,8 @@ function App(): React.ReactElement {
<Header />
<main className="main">
<Home />
<ScrollReveal>
<About />
</ScrollReveal>
<ScrollReveal direction="up" delay={0.1}>
<GitHubActivity />
</ScrollReveal>
<ScrollReveal direction="up" delay={0.1}>
<Skills />
</ScrollReveal>
<ScrollReveal direction="left">
<Services />
</ScrollReveal>
<ScrollReveal direction="up">
<Qualification />
</ScrollReveal>
<ScrollReveal direction="right">
<Certificate />
</ScrollReveal>
<ScrollReveal direction="up">
<Contact />
<BentoGrid />
</ScrollReveal>
<Footer />
</main>
Expand Down
131 changes: 131 additions & 0 deletions src/components/bento/BentoCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from "react";
import { Box, Typography } from "@mui/material";
import { ArrowOutward } from "@mui/icons-material";

export interface BentoCardProps {
title: string;
subtitle?: string;
accent?: string;
preview: React.ReactNode;
onClick: () => void;
gridColumn?: string | { xs?: string; md?: string };
gridRow?: string | { xs?: string; md?: string };
}

const BentoCard: React.FC<BentoCardProps> = ({
title,
subtitle,
accent = "#0eaddf",
preview,
onClick,
gridColumn,
gridRow
}) => {
return (
<Box
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick();
}
}}
sx={{
gridColumn,
gridRow,
position: "relative",
cursor: "pointer",
background: "rgba(22, 22, 22, 0.85)",
border: "1px solid rgba(255, 255, 255, 0.06)",
borderRadius: 4,
p: { xs: 2.5, md: 3 },
display: "flex",
flexDirection: "column",
gap: 2,
overflow: "hidden",
transition: "all 0.35s cubic-bezier(0.4, 0, 0.2, 1)",
"&:hover, &:focus-visible": {
transform: "translateY(-4px)",
borderColor: `${accent}55`,
boxShadow: `0 16px 40px ${accent}20`,
"& .bento-arrow": { opacity: 1, transform: "translate(2px, -2px)" },
"& .bento-glow": { opacity: 0.5 }
},
"&:focus-visible": { outline: `2px solid ${accent}` }
}}
>
<Box
className="bento-glow"
aria-hidden
sx={{
position: "absolute",
top: -40,
right: -40,
width: 160,
height: 160,
borderRadius: "50%",
background: accent,
opacity: 0.15,
filter: "blur(60px)",
transition: "opacity 0.4s ease",
pointerEvents: "none"
}}
/>

<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 2, position: "relative", zIndex: 1 }}>
<Box sx={{ minWidth: 0 }}>
<Typography
variant="caption"
sx={{
color: accent,
fontWeight: 600,
fontSize: "0.7rem",
letterSpacing: 1.5,
textTransform: "uppercase"
}}
>
{subtitle}
</Typography>
<Typography
variant="h6"
sx={{
color: "#e6edf3",
fontWeight: 700,
fontSize: { xs: "1rem", md: "1.15rem" },
lineHeight: 1.3,
mt: 0.25
}}
>
{title}
</Typography>
</Box>
<Box
className="bento-arrow"
sx={{
flexShrink: 0,
width: 32,
height: 32,
borderRadius: "50%",
background: `${accent}15`,
color: accent,
display: "flex",
alignItems: "center",
justifyContent: "center",
opacity: 0.5,
transition: "all 0.3s ease"
}}
>
<ArrowOutward sx={{ fontSize: 16 }} />
</Box>
</Box>

<Box sx={{ flex: 1, position: "relative", zIndex: 1, display: "flex", flexDirection: "column", minHeight: 0, overflow: "hidden" }}>
{preview}
</Box>
</Box>
);
};

export default BentoCard;
194 changes: 194 additions & 0 deletions src/components/bento/BentoGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import React, { useState, lazy, Suspense, useCallback, useEffect } from "react";
import { Box, Container, Typography, CircularProgress } from "@mui/material";
import BentoCard from "./BentoCard";
import SectionDrawer from "./SectionDrawer";
import {
AboutPreview,
SkillsPreview,
GitHubPreview,
ExperiencePreview,
CertsPreview,
ServicesPreview,
ContactPreview
} from "./previews";

const About = lazy(() => import("../about/About"));
const GitHubActivity = lazy(() => import("../github/GitHubActivity"));
const Skills = lazy(() => import("../skills/Skills"));
const Services = lazy(() => import("../services/Services"));
const Qualification = lazy(() => import("../qualification/Qualification"));
const Certificate = lazy(() => import("../certificate/Certificate"));
const Contact = lazy(() => import("../contact/Contact"));

type SectionKey =
| "about"
| "github"
| "skills"
| "experience"
| "certificates"
| "services"
| "contact";

const sectionContent: Record<SectionKey, React.LazyExoticComponent<React.FC>> = {
about: About,
github: GitHubActivity,
skills: Skills,
experience: Qualification,
certificates: Certificate,
services: Services,
contact: Contact
};

const DrawerLoader: React.FC = () => (
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", minHeight: "60vh" }}>
<CircularProgress sx={{ color: "#0eaddf" }} />
</Box>
);

const validKeys = new Set<SectionKey>([
"about",
"github",
"skills",
"experience",
"certificates",
"services",
"contact"
]);

const BentoGrid: React.FC = () => {
const [openSection, setOpenSection] = useState<SectionKey | null>(null);

const open = useCallback((key: SectionKey) => {
setOpenSection(key);
if (window.location.hash !== `#${key}`) {
window.history.pushState(null, "", `#${key}`);
}
}, []);

const close = useCallback(() => {
setOpenSection(null);
if (window.location.hash) {
window.history.pushState(null, "", window.location.pathname + window.location.search);
window.dispatchEvent(new HashChangeEvent("hashchange"));
}
}, []);

useEffect(() => {
const sync = () => {
const hash = window.location.hash.replace("#", "") as SectionKey;
if (validKeys.has(hash)) {
setOpenSection(hash);
} else {
setOpenSection(null);
}
};
sync();
window.addEventListener("hashchange", sync);
return () => window.removeEventListener("hashchange", sync);
}, []);

const ActiveSection = openSection ? sectionContent[openSection] : null;

return (
<Box
component="section"
id="explore"
sx={{
py: { xs: 6, md: 10 },
background: "#0a0a0a",
position: "relative"
}}
>
<Container maxWidth="lg">
<Box sx={{ textAlign: "center", mb: { xs: 5, md: 7 } }}>
<Typography
variant="h2"
sx={{
fontSize: { xs: "2rem", sm: "2.5rem", md: "3rem" },
fontWeight: 700,
mb: 2,
color: "#0eaddf"
}}
>
Explore
</Typography>
<Typography variant="h6" sx={{ color: "#8b949e", fontWeight: 400 }}>
Click any card to dive deeper
</Typography>
</Box>

<Box
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", sm: "repeat(2, 1fr)", md: "repeat(6, 1fr)" },
gridAutoRows: { xs: "180px", md: "200px" },
gap: { xs: 2, md: 2.5 }
}}
>
<BentoCard
title="About Me"
subtitle="Profile"
preview={<AboutPreview />}
onClick={() => open("about")}
gridColumn={{ xs: "1 / -1", md: "1 / span 4" }}
/>
<BentoCard
title="Tech Stack"
subtitle="Skills"
accent="#a855f7"
preview={<SkillsPreview />}
onClick={() => open("skills")}
gridColumn={{ md: "5 / span 2" }}
gridRow={{ md: "span 2" }}
/>
<BentoCard
title="GitHub Activity"
subtitle="Open Source"
preview={<GitHubPreview />}
onClick={() => open("github")}
gridColumn={{ md: "1 / span 4" }}
/>
<BentoCard
title="Experience"
subtitle="Career"
accent="#22c55e"
preview={<ExperiencePreview />}
onClick={() => open("experience")}
gridColumn={{ md: "1 / span 2" }}
/>
<BentoCard
title="Certificates"
subtitle="Achievements"
accent="#FFD700"
preview={<CertsPreview />}
onClick={() => open("certificates")}
gridColumn={{ md: "3 / span 2" }}
/>
<BentoCard
title="Services"
subtitle="What I do"
accent="#f5576c"
preview={<ServicesPreview />}
onClick={() => open("services")}
gridColumn={{ md: "5 / span 2" }}
/>
<BentoCard
title="Let's Connect"
subtitle="Get in touch"
preview={<ContactPreview />}
onClick={() => open("contact")}
gridColumn={{ xs: "1 / -1", md: "1 / -1" }}
/>
</Box>
</Container>

<SectionDrawer open={openSection !== null} onClose={close}>
<Suspense fallback={<DrawerLoader />}>
{ActiveSection ? <ActiveSection /> : null}
</Suspense>
</SectionDrawer>
</Box>
);
};

export default BentoGrid;
Loading
Loading