Delete diff.txt

This commit is contained in:
2026-04-14 05:40:18 +02:00
parent 198471bf95
commit 646850ee4a
-506
View File
@@ -1,506 +0,0 @@
diff --git a/src/App.tsx b/src/App.tsx
index 19c9751..78d9f0c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,7 +5,6 @@ import remarkGfm from "remark-gfm";
// Components
import {
- AnimatedBackground,
ProjectThumb,
CascadeItem,
FadeContainer,
@@ -14,7 +13,6 @@ import {
// Hooks
import {
- useSystemDarkMode,
useGitHubReadme,
useGitHubRepoImages,
} from "./hooks";
@@ -31,13 +29,27 @@ export default function PortfolioAboveTheFold() {
null,
);
const [isContentVisible, setIsContentVisible] = useState(true);
- const isDark = useSystemDarkMode();
// Handle project selection with fade transition
const handleProjectSelect = (projectId: number) => {
if (projectId === active) return;
- // Start fade out
+ // If on mobile/tablet, trigger the modal instantly without waiting for fade-out
+ if (window.innerWidth < 1024) {
+ setIsContentVisible(false); // Reset content visibility for the cascade
+ setActive(projectId);
+ setDisplayedProject(projectId);
+
+ // Wait for the modal wrapper to begin its CSS transition before starting the cascade
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ setIsContentVisible(true);
+ });
+ });
+ return;
+ }
+
+ // Start fade out for desktop
setIsContentVisible(false);
// After fade out completes, update the project and fade in
@@ -48,19 +60,31 @@ export default function PortfolioAboveTheFold() {
requestAnimationFrame(() => {
setIsContentVisible(true);
});
- }, 200); // Match the fade-out duration
+ }, 300); // Match the fade-out duration
};
// Handle closing with fade transition
const handleClose = () => {
setIsContentVisible(false);
+
+ if (window.innerWidth < 1024) {
+ // Instantly start scaling down the modal on mobile
+ setActive(null);
+ setDisplayedProject(null);
+ requestAnimationFrame(() => {
+ setIsContentVisible(true);
+ });
+ return;
+ }
+
+ // On desktop, wait for fade-out before resetting project
setTimeout(() => {
setActive(null);
setDisplayedProject(null);
requestAnimationFrame(() => {
setIsContentVisible(true);
});
- }, 200);
+ }, 300);
};
// Combine all projects for lookup
@@ -87,40 +111,36 @@ export default function PortfolioAboveTheFold() {
} = useGitHubReadme(activeRepo);
return (
- <main className="h-screen w-full flex items-center justify-center p-6 overflow-hidden relative">
- <AnimatedBackground
- palette={activeProject?.palette}
- isDark={isDark}
- />
- <section className="relative max-w-[1100px] w-full h-[88vh] bg-white/60 dark:bg-slate-900/60 backdrop-blur-sm border border-white/60 dark:border-slate-700/60 rounded-2xl shadow-2xl p-6 grid grid-cols-2 gap-6 items-stretch">
- <ScrollArea className="min-h-0" scrollbarOffset={12}>
+ <main className="h-screen w-full bg-white dark:bg-slate-950 overflow-hidden font-sans text-slate-900 dark:text-slate-100 transition-colors duration-300">
+ <section className="mx-auto max-w-[1400px] w-full h-full grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16 items-stretch px-6 py-6 md:px-12 md:py-12 lg:px-16 lg:py-16 relative">
+ <ScrollArea className="min-h-0 pr-4 lg:pr-6" scrollbarOffset={12}>
<div className="flex flex-col gap-4">
<div>
- <p className="text-sm uppercase tracking-wide text-gray-500 dark:text-gray-400">
+ <p className="text-sm font-medium tracking-wide text-gray-500 dark:text-gray-400">
Hello, I'm
</p>
- <h1 className="mt-2 text-[clamp(22px,3.6vw,40px)] leading-tight font-semibold text-gray-900 dark:text-white">
+ <h1 className="mt-2 text-[clamp(32px,5vw,56px)] leading-none font-bold tracking-tight text-gray-900 dark:text-white">
{profile.name}
</h1>
- <p className="mt-1 text-[clamp(14px,1.6vw,18px)] text-gray-600 dark:text-gray-300">
- {profile.role} • {profile.location}
+ <p className="mt-2 text-[clamp(16px,2vw,22px)] font-medium text-gray-600 dark:text-gray-300">
+ {profile.role} <span className="text-gray-300 dark:text-gray-600 mx-2">•</span> {profile.location}
</p>
- <p className="mt-6 text-[clamp(13px,1.2vw,16px)] text-gray-700 dark:text-gray-300 max-w-[38ch]">
+ <p className="mt-8 text-base lg:text-lg text-gray-600 dark:text-gray-400 max-w-[45ch] leading-relaxed">
{profile.blurb}
</p>
- <div className="mt-6 flex flex-wrap gap-3">
+ <div className="mt-8 flex flex-wrap gap-4">
<a
href={profile.cv}
- className="inline-flex items-center gap-2 rounded-md px-4 py-2 border border-gray-200 dark:border-slate-600 shadow-sm text-sm font-medium text-gray-900 dark:text-white hover:shadow hover:-translate-y-0.5 transition-transform"
+ className="inline-flex items-center gap-2 rounded-full px-6 py-2.5 border border-gray-200 dark:border-slate-800 bg-white dark:bg-slate-900 shadow-sm text-sm font-medium text-gray-900 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-800 transition-colors"
>
Download CV
</a>
<a
href={`mailto:${profile.email}`}
- className="inline-flex items-center gap-2 rounded-md px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 text-sm font-medium hover:opacity-95 transition-opacity"
+ className="inline-flex items-center gap-2 rounded-full px-6 py-2.5 bg-gray-900 dark:bg-white text-white dark:text-gray-900 text-sm font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors"
>
Contact
</a>
@@ -137,48 +157,24 @@ export default function PortfolioAboveTheFold() {
const isActive = active === p.id;
return (
- // Wrapper div creates the gradient border effect
<div
key={p.id}
- className="group relative rounded-lg p-[2px] transition-all duration-200 shadow-sm hover:shadow-md hover:-translate-y-0.5 bg-white dark:bg-slate-800"
+ className={`group relative rounded-xl transition-all duration-200 bg-gray-50 dark:bg-slate-900 border ${
+ isActive
+ ? "border-gray-900 dark:border-white shadow-sm"
+ : "border-transparent hover:border-gray-200 dark:hover:border-slate-700"
+ }`}
>
- {/* Gradient border layer - always present, opacity controlled */}
- <div
- aria-hidden
- className="absolute inset-0 rounded-lg transition-opacity duration-300 pointer-events-none"
- style={{
- backgroundImage: p.gradient,
- opacity: isActive ? 1 : 0,
- }}
- />
<button
onClick={() =>
handleProjectSelect(p.id)
}
- className={`relative flex flex-col w-full rounded-[6px] overflow-hidden
- bg-white dark:bg-slate-800
- focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-indigo-500`}
+ className={`relative flex flex-col w-full rounded-xl overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500`}
aria-expanded={isActive}
type="button"
>
- {/* Gradient overlay that covers the entire button; becomes visible on hover. */}
- <div
- aria-hidden
- className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none rounded-[6px]"
- style={{
- backgroundImage:
- p.gradient,
- }}
- />
-
- {/* Slight dark layer to insure text contrast when gradient is visible */}
- <div
- aria-hidden
- className="absolute inset-0 bg-black/0 group-hover:bg-black/25 transition-colors duration-300 pointer-events-none rounded-[6px]"
- />
-
{/* Image fills width */}
- <div className="relative z-10 w-full h-24 overflow-hidden">
+ <div className="relative z-10 w-full h-32 overflow-hidden bg-gray-200 dark:bg-slate-800">
<ProjectThumb
src={
(p.repo &&
@@ -192,11 +188,11 @@ export default function PortfolioAboveTheFold() {
</div>
{/* Text below image */}
- <div className="relative z-10 text-left p-2">
- <div className="text-sm font-medium transition-colors duration-200 text-gray-900 dark:text-white group-hover:text-white">
+ <div className="relative z-10 text-left p-3">
+ <div className="text-sm font-semibold transition-colors duration-200 text-gray-900 dark:text-white">
{p.title}
</div>
- <div className="text-xs transition-colors duration-200 text-gray-500 dark:text-gray-400 group-hover:text-gray-100 line-clamp-2">
+ <div className="text-xs transition-colors duration-200 text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
{p.desc}
</div>
</div>
@@ -215,48 +211,24 @@ export default function PortfolioAboveTheFold() {
const isActive = active === p.id;
return (
- // Wrapper div creates the gradient border effect
<div
key={p.id}
- className="group relative rounded-lg p-[2px] transition-all duration-200 shadow-sm hover:shadow-md hover:-translate-y-0.5 bg-white dark:bg-slate-800"
+ className={`group relative rounded-xl transition-all duration-200 bg-gray-50 dark:bg-slate-900 border ${
+ isActive
+ ? "border-gray-900 dark:border-white shadow-sm"
+ : "border-transparent hover:border-gray-200 dark:hover:border-slate-700"
+ }`}
>
- {/* Gradient border layer - always present, opacity controlled */}
- <div
- aria-hidden
- className="absolute inset-0 rounded-lg transition-opacity duration-300 pointer-events-none"
- style={{
- backgroundImage: p.gradient,
- opacity: isActive ? 1 : 0,
- }}
- />
<button
onClick={() =>
handleProjectSelect(p.id)
}
- className={`relative flex flex-col w-full rounded-[6px] overflow-hidden
- bg-white dark:bg-slate-800
- focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-indigo-500`}
+ className={`relative flex flex-col w-full rounded-xl overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500`}
aria-expanded={isActive}
type="button"
>
- {/* Gradient overlay that covers the entire button; becomes visible on hover. */}
- <div
- aria-hidden
- className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none rounded-[6px]"
- style={{
- backgroundImage:
- p.gradient,
- }}
- />
-
- {/* Slight dark layer to insure text contrast when gradient is visible */}
- <div
- aria-hidden
- className="absolute inset-0 bg-black/0 group-hover:bg-black/25 transition-colors duration-300 pointer-events-none rounded-[6px]"
- />
-
{/* Image fills width */}
- <div className="relative z-10 w-full h-24 overflow-hidden">
+ <div className="relative z-10 w-full h-32 overflow-hidden bg-gray-200 dark:bg-slate-800">
<ProjectThumb
src={
(p.repo &&
@@ -270,11 +242,11 @@ export default function PortfolioAboveTheFold() {
</div>
{/* Text below image */}
- <div className="relative z-10 text-left p-2">
- <div className="text-sm font-medium transition-colors duration-200 text-gray-900 dark:text-white group-hover:text-white">
+ <div className="relative z-10 text-left p-3">
+ <div className="text-sm font-semibold transition-colors duration-200 text-gray-900 dark:text-white">
{p.title}
</div>
- <div className="text-xs transition-colors duration-200 text-gray-500 dark:text-gray-400 group-hover:text-gray-100 line-clamp-2">
+ <div className="text-xs transition-colors duration-200 text-gray-600 dark:text-gray-400 mt-1 line-clamp-2">
{p.desc}
</div>
</div>
@@ -282,13 +254,59 @@ export default function PortfolioAboveTheFold() {
</div>
);
})}
+ </div>
+ </div>
+ </div>
+
+ <div className="mt-8 pt-8 border-t border-gray-200/60 dark:border-slate-800/60 flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6 px-2 pb-8">
+ <div>
+ <h4 className="text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-widest">
+ Skills
+ </h4>
+ <div className="mt-3 flex flex-wrap gap-2.5">
+ {skills.map((s, i) => (
+ <span
+ key={i}
+ className="text-[11px] font-medium tracking-wide px-3 py-1.5 bg-gray-100 dark:bg-slate-800/50 rounded-full text-gray-700 dark:text-gray-300"
+ >
+ {s}
+ </span>
+ ))}
+ </div>
+ </div>
+
+ <div className="text-left lg:text-right text-sm font-medium text-gray-500 dark:text-gray-400">
+ <div className="flex items-center gap-2 lg:justify-end mb-1">
+ <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
+ Available for freelance & contract
+ </div>
+ <a href={`mailto:${profile.email}`} className="hover:text-gray-900 dark:hover:text-white transition-colors">{profile.email}</a>
</div>
</div>
</div>
</ScrollArea>
- <div className="flex flex-col gap-4 min-h-0">
- <div className="flex-1 bg-white dark:bg-slate-800 rounded-lg border border-gray-100 dark:border-slate-700 overflow-hidden">
+ <div
+ className={`flex flex-col gap-6 min-h-0 h-full transition-all duration-300 fixed inset-0 z-50 p-4 sm:p-8 bg-slate-50/90 dark:bg-slate-950/90 backdrop-blur-md lg:static lg:p-0 lg:bg-transparent lg:dark:bg-transparent lg:backdrop-blur-none ${
+ displayedProject !== null
+ ? "opacity-100 pointer-events-auto"
+ : "opacity-0 pointer-events-none lg:opacity-100 lg:pointer-events-auto"
+ }`}
+ onClick={() => {
+ // Only close on click if it's acting as a modal (narrow viewports)
+ if (window.innerWidth < 1024 && displayedProject !== null) {
+ handleClose();
+ }
+ }}
+ >
+ <div
+ className={`flex-1 bg-white dark:bg-slate-900 lg:rounded-3xl border border-gray-200/60 dark:border-slate-800/60 shadow-xl overflow-hidden rounded-2xl transition-all duration-300 ease-out transform ${
+ displayedProject !== null
+ ? "translate-y-0 scale-100 opacity-100"
+ : "translate-y-8 scale-95 opacity-0 lg:translate-y-0 lg:scale-100 lg:opacity-100"
+ }`}
+ onClick={(e) => e.stopPropagation()}
+ >
<div className="w-full h-full flex items-center justify-center relative">
{/* Project detail view */}
<FadeContainer
@@ -302,12 +320,22 @@ export default function PortfolioAboveTheFold() {
className="w-full h-full"
scrollbarOffset={12}
>
- <article>
+ <article className="p-6 md:p-10">
+ <button
+ onClick={handleClose}
+ className="absolute z-50 top-4 right-4 md:top-6 md:right-6 p-2 rounded-full bg-white/80 dark:bg-slate-900/80 backdrop-blur text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:hover:text-white dark:hover:bg-slate-800 border border-gray-200/50 dark:border-slate-700/50 shadow-sm transition-all lg:hidden"
+ aria-label="Close project"
+ >
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+ <line x1="18" y1="6" x2="6" y2="18"></line>
+ <line x1="6" y1="6" x2="18" y2="18"></line>
+ </svg>
+ </button>
<CascadeItem
delay={0}
isVisible={isContentVisible}
>
- <div className="w-full aspect-[4/3] rounded-lg overflow-hidden bg-gray-100 dark:bg-slate-700">
+ <div className="w-full aspect-video rounded-xl overflow-hidden bg-gray-100 dark:bg-slate-800 border border-gray-200/50 dark:border-slate-700/50">
<ProjectThumb
src={
readmeImage ||
@@ -333,8 +361,8 @@ export default function PortfolioAboveTheFold() {
delay={75}
isVisible={isContentVisible}
>
- <div className="mt-4 flex items-start justify-between">
- <h2 className="text-lg font-semibold text-gray-900 dark:text-white">
+ <div className="mt-6 flex items-start justify-between">
+ <h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
{
allProjectsList.find(
(p) =>
@@ -345,9 +373,13 @@ export default function PortfolioAboveTheFold() {
</h2>
<button
onClick={handleClose}
- className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
+ className="hidden lg:block p-2 -mr-2 rounded-full text-gray-400 hover:text-gray-900 hover:bg-gray-100 dark:hover:text-white dark:hover:bg-slate-800 transition-colors"
+ aria-label="Close project"
>
- Close
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
+ <line x1="18" y1="6" x2="6" y2="18"></line>
+ <line x1="6" y1="6" x2="18" y2="18"></line>
+ </svg>
</button>
</div>
</CascadeItem>
@@ -366,7 +398,7 @@ export default function PortfolioAboveTheFold() {
?.tags.map((t, i) => (
<span
key={i}
- className="text-xs px-2 py-1 bg-gray-100 dark:bg-slate-700 rounded-md text-gray-700 dark:text-gray-300"
+ className="text-[11px] font-medium tracking-wide uppercase px-2.5 py-1 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-full text-gray-600 dark:text-gray-400"
>
{t}
</span>
@@ -380,13 +412,15 @@ export default function PortfolioAboveTheFold() {
>
<div className="mt-4">
{readmeLoading && (
- <div className="text-sm text-gray-500 dark:text-gray-400">
- Loading README...
+ <div className="flex flex-col gap-3 mt-8 opacity-50 animate-pulse w-full max-w-lg">
+ <div className="h-4 bg-gray-200 dark:bg-slate-800 rounded-full w-3/4"></div>
+ <div className="h-4 bg-gray-200 dark:bg-slate-800 rounded-full w-1/2"></div>
+ <div className="h-4 bg-gray-200 dark:bg-slate-800 rounded-full w-5/6"></div>
</div>
)}
{readmeError &&
!readmeLoading && (
- <p className="text-sm text-gray-700 dark:text-gray-300">
+ <p className="text-base text-gray-600 dark:text-gray-400 mt-6 leading-relaxed">
{
allProjectsList.find(
(p) =>
@@ -398,7 +432,7 @@ export default function PortfolioAboveTheFold() {
)}
{readmeContent &&
!readmeLoading && (
- <div className="prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300">
+ <div className="prose prose-base md:prose-lg dark:prose-invert max-w-none text-gray-600 dark:text-gray-300 mt-8 prose-headings:font-semibold prose-a:text-blue-600 dark:prose-a:text-blue-400">
<Markdown
remarkPlugins={[
remarkGfm,
@@ -468,7 +502,7 @@ export default function PortfolioAboveTheFold() {
)}
{!activeRepo &&
!readmeLoading && (
- <p className="text-sm text-gray-700 dark:text-gray-300">
+ <p className="text-base text-gray-600 dark:text-gray-400 leading-relaxed mt-6">
{
allProjectsList.find(
(p) =>
@@ -487,12 +521,13 @@ export default function PortfolioAboveTheFold() {
{/* Empty state view */}
<FadeContainer
+ className="hidden lg:flex"
isVisible={
displayedProject === null &&
isContentVisible
}
>
- <div className="text-center max-w-[44ch] flex flex-col items-center justify-center h-full">
+ <div className="text-center max-w-[44ch] flex flex-col items-center justify-center h-full p-6 md:p-10">
<CascadeItem
delay={0}
isVisible={
@@ -500,8 +535,8 @@ export default function PortfolioAboveTheFold() {
isContentVisible
}
>
- <h2 className="text-xl font-semibold text-gray-900 dark:text-white">
- Selected work
+ <h2 className="text-2xl font-semibold tracking-tight text-gray-900 dark:text-white">
+ Selected Work
</h2>
</CascadeItem>
@@ -512,9 +547,9 @@ export default function PortfolioAboveTheFold() {
isContentVisible
}
>
- <p className="mt-3 text-sm text-gray-600 dark:text-gray-400">
+ <p className="mt-4 text-base text-gray-500 dark:text-gray-400 max-w-sm mx-auto">
Click a project on the left to open
- a short preview.
+ a quick preview and read more.
</p>
</CascadeItem>
@@ -531,29 +566,6 @@ export default function PortfolioAboveTheFold() {
</FadeContainer>
</div>
</div>
-
- <div className="flex items-center justify-between gap-3">
- <div>
- <h4 className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">
- Skills
- </h4>
- <div className="mt-2 flex flex-wrap gap-2">
- {skills.map((s, i) => (
- <span
- key={i}
- className="text-xs px-2 py-1 border border-gray-200 dark:border-slate-600 rounded-md text-gray-700 dark:text-gray-300"
- >
- {s}
- </span>
- ))}
- </div>
- </div>
-
- <div className="text-right text-xs text-gray-500 dark:text-gray-400">
- <div>Available for freelance & contract</div>
- <div className="mt-2">{profile.email}</div>
- </div>
- </div>
</div>
<div className="pointer-events-none absolute bottom-6 right-6 text-[10px] text-gray-400 dark:text-gray-500">