From cc52ccab0a1684fe362061e1e4d2d6d5576393f4 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:23:18 +0200 Subject: [PATCH 01/42] refactor: cards horizontales pleine largeur en mode client --- components/portfolio-content.tsx | 4 +- components/project-card.tsx | 374 +++++++++++++++---------------- 2 files changed, 188 insertions(+), 190 deletions(-) diff --git a/components/portfolio-content.tsx b/components/portfolio-content.tsx index 5ee9cda..3891ec4 100644 --- a/components/portfolio-content.tsx +++ b/components/portfolio-content.tsx @@ -10,12 +10,12 @@ interface PortfolioContentProps { export function PortfolioContent({ projects }: PortfolioContentProps) { return (
-
+
{projects.map((project, index) => (
diff --git a/components/project-card.tsx b/components/project-card.tsx index 19daa8c..7239233 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -16,235 +16,233 @@ export function ProjectCard({ project }: ProjectCardProps) { const isPaused = project.development_status === 'paused'; return ( -
-
-
- {/* Glow Effect Overlay */} -
+ {/* Glow Effect Overlay */} +
- {project.image_url ? ( -
- {`${project.title} + {`${project.title} -
+ /> +
- {isInDev && ( - <> -
-
-
- {isPaused ? ( - <> - - Development paused - - ) : ( - <> - - Development in progress - - )} -
-
- - )} - - {isFinished && ( -
-
- + {isInDev && ( + <> +
+
+
+ {isPaused ? ( + <> + + Development paused + + ) : ( + <> + + Development in progress + + )}
- )} + + )} - {isArchived && !isFinished && ( -
-
- -
-
- )} -
- ) : ( -
-
-
+
+ +
+
+ )} + + {isArchived && !isFinished && ( +
+
+ +
+
+ )} +
+ ) : ( +
+
+
- -
- - No preview available -
+ + No preview available +
- )} +
+ )} -
-
-
-
-

+
+
+
+

- {project.title} -

- {isInDev && ( -
- {isPaused ? ( - - ) : ( - - )} -
- )} - {isFinished && ( -
- -
- )} - {isArchived && ( -
- -
- )} -
-
- -
-
-

- {project.description} -

-
- -
-
- {project.tags.map((tag) => ( - - {tag} - - ))} + {project.title} +

{isInDev && ( - +
{isPaused ? ( - <> - - On pause - + ) : ( - <> - WIP - + )} - +
)} {isFinished && ( - - Finished - +
+ +
)} {isArchived && ( - - Archived - +
+ +
)}
+
+ +
+
+

+ {project.description} +

+
+
+
+ {project.tags.map((tag) => ( + + {tag} + + ))} {isInDev && ( -
-
-
+ + {isPaused ? ( + <> + + On pause + + ) : ( + <> + WIP + + )} + + )} + {isFinished && ( + + Finished + + )} + {isArchived && ( + + Archived + )}
-
-
- {isInDev ? ( - - ) : ( - project.project_url && ( - - ) - )} + {isInDev && ( +
+
+
+ )} +
- {project.github_url && ( - + ) : ( + project.project_url && ( + - )} -
-
+ ) + )} + + {project.github_url && ( + + )}
From 9b14932a52241d7e32530f7b40f8288944d20fbf Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:28:24 +0200 Subject: [PATCH 02/42] =?UTF-8?q?fix:=20ajout=20du=20d=C3=A9grad=C3=A9=20s?= =?UTF-8?q?mooth=20entre=20l'image=20et=20le=20texte=20sur=20les=20cards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 7239233..71690c7 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -29,12 +29,12 @@ export function ProjectCard({ project }: ProjectCardProps) { {/* Image Section */} {project.image_url ? ( -
+
{`${project.title} -
+
{isInDev && ( <> @@ -91,7 +91,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )}
) : ( -
+
Date: Mon, 20 Jul 2026 15:30:20 +0200 Subject: [PATCH 03/42] =?UTF-8?q?fix:=20remplace=20le=20d=C3=A9grad=C3=A9?= =?UTF-8?q?=20couleur=20par=20un=20blur=20d=C3=A9grad=C3=A9=20sur=20l'imag?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 71690c7..834f8f9 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -42,7 +42,13 @@ export function ProjectCard({ project }: ProjectCardProps) { ${isFinished ? "brightness-110 contrast-110" : isArchived ? "brightness-100" : ""} `} /> -
+
{isInDev && ( <> From 42cea0dfee7b597ae5741a7b7241a13f9fce5ad8 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:33:10 +0200 Subject: [PATCH 04/42] =?UTF-8?q?fix:=20image=20en=20fond=20=C3=A9tendu=20?= =?UTF-8?q?avec=20blur=20d=C3=A9grad=C3=A9=20pour=20transition=20smooth=20?= =?UTF-8?q?vers=20le=20texte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 143 ++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 57 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 834f8f9..b13f634 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -15,29 +15,79 @@ export function ProjectCard({ project }: ProjectCardProps) { const isInDev = project.in_development; const isPaused = project.development_status === 'paused'; + const statusBadges = ( + <> + {isInDev && ( + <> +
+
+
+ {isPaused ? ( + <> + + Development paused + + ) : ( + <> + + Development in progress + + )} +
+
+ + )} + + {isFinished && ( +
+
+ +
+
+ )} + + {isArchived && !isFinished && ( +
+
+ +
+
+ )} + + ); + return (
-
+
{/* Glow Effect Overlay */}
- {/* Image Section */} - {project.image_url ? ( -
+ {/* Desktop: Image as extended background behind content */} + {project.image_url && ( +
{`${project.title} + {statusBadges} +
+ )} - {isInDev && ( - <> -
-
-
- {isPaused ? ( - <> - - Development paused - - ) : ( - <> - - Development in progress - - )} -
-
- - )} - - {isFinished && ( -
-
- -
-
- )} - - {isArchived && !isFinished && ( -
-
- -
-
- )} + {/* Mobile: Image in normal flow */} + {project.image_url && ( +
+ {`${project.title} +
+
+ {statusBadges}
- ) : ( -
+ )} + + {/* No image placeholder */} + {!project.image_url && ( +
+
From 03183f6214cbeecf9406c81e654c999813935aa3 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:36:13 +0200 Subject: [PATCH 05/42] =?UTF-8?q?fix:=20image=20s'=C3=A9tend=20sur=20toute?= =?UTF-8?q?=20la=20card=20avec=20blur=20gradient=20de=2038%=20=C3=A0=2055%?= =?UTF-8?q?,=20contenu=20=C3=A0=2042%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 324 +++++++++++++++++------------------- 1 file changed, 153 insertions(+), 171 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index b13f634..9ef069d 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -48,7 +48,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {isFinished && ( -
+
@@ -56,7 +56,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {isArchived && !isFinished && ( -
+
@@ -66,25 +66,25 @@ export function ProjectCard({ project }: ProjectCardProps) { ); return ( -
-
- {/* Glow Effect Overlay */} -
+ {/* Glow Effect Overlay */} +
- {/* Desktop: Image as extended background behind content */} - {project.image_url && ( -
+ {project.image_url ? ( + <> + {/* Image - fills full card on desktop, stacked on mobile */} +
{`${project.title} + + {/* Desktop blur gradient - starts at 35%, fully blurred by 55%, covers the rest of the card */}
- {statusBadges} -
- )} - {/* Mobile: Image in normal flow */} - {project.image_url && ( -
- {`${project.title} -
-
- {statusBadges} -
- )} + {/* Mobile gradient */} +
- {/* No image placeholder */} - {!project.image_url && ( -
-
-
- -
- - No preview available - -
+ {statusBadges}
- )} - - {/* Content Section */} -
-
-
-
-

- {project.title} -

- {isInDev && ( -
- {isPaused ? ( - - ) : ( - - )} -
- )} - {isFinished && ( -
- -
- )} - {isArchived && ( -
- -
- )} -
-
- -
+ + ) : ( +
+
+
+
-

- {project.description} -

+ No preview available +
+
+ )} -
-
- {project.tags.map((tag) => ( - - {tag} - - ))} + {/* Content Section */} +
+
+
+
+

+ {project.title} +

{isInDev && ( - {isPaused ? ( - <> - - On pause - + ) : ( - <> - WIP - + )} - +
)} {isFinished && ( - - Finished - +
+ +
)} {isArchived && ( - - Archived - +
+ +
)}
+
+ +
+
+

+ {project.description} +

+
+
+
+ {project.tags.map((tag) => ( + + {tag} + + ))} {isInDev && ( -
-
-
+ + {isPaused ? ( + <> + + On pause + + ) : ( + <> + WIP + + )} + )} -
- -
- {isInDev ? ( - - ) : ( - project.project_url && ( - - ) + {isFinished && ( + + Finished + )} - - {project.github_url && ( - + {isArchived && ( + + Archived + )}
+ + {isInDev && ( +
+
+
+ )} +
+ +
+ {isInDev ? ( + + ) : ( + project.project_url && ( + + ) + )} + + {project.github_url && ( + + )}
From 19c806046aec6d4b158dde5e39d12c346e50520b Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:36:59 +0200 Subject: [PATCH 06/42] =?UTF-8?q?fix:=20ajout=20d'un=20d=C3=A9grad=C3=A9?= =?UTF-8?q?=20de=20transparence=20en=20plus=20du=20blur=20pour=20fondre=20?= =?UTF-8?q?l'image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 9ef069d..1ce3c9d 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -97,11 +97,14 @@ export function ProjectCard({ project }: ProjectCardProps) {
+ {/* Desktop fade - image devient transparente progressivement vers la droite */} +
+ {/* Mobile gradient */}
From 6df11fc55a12d14f604469f6a073bd386bfd23a0 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:37:59 +0200 Subject: [PATCH 07/42] =?UTF-8?q?Revert=20"fix:=20ajout=20d'un=20d=C3=A9gr?= =?UTF-8?q?ad=C3=A9=20de=20transparence=20en=20plus=20du=20blur=20pour=20f?= =?UTF-8?q?ondre=20l'image"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 19c806046aec6d4b158dde5e39d12c346e50520b. --- components/project-card.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 1ce3c9d..9ef069d 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -97,14 +97,11 @@ export function ProjectCard({ project }: ProjectCardProps) {
- {/* Desktop fade - image devient transparente progressivement vers la droite */} -
- {/* Mobile gradient */}
From e5493e53a4311b88f592bcc712a76a2d891f8630 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:39:57 +0200 Subject: [PATCH 08/42] =?UTF-8?q?fix:=20texte=20=C3=A0=20gauche,=20image?= =?UTF-8?q?=20=C3=A0=20droite,=20blur=20invers=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 9ef069d..4ebdc49 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -87,7 +87,7 @@ export function ProjectCard({ project }: ProjectCardProps) { sizes="(max-width: 768px) 100vw, 100vw" loading="lazy" decoding="async" - className={`object-cover object-left group-hover:scale-110 transition-transform duration-1000 ease-out + className={`object-cover object-right group-hover:scale-110 transition-transform duration-1000 ease-out ${isInDev ? "grayscale brightness-50" : ""} ${isFinished ? "brightness-110 contrast-110" : isArchived ? "brightness-100" : ""} `} @@ -97,8 +97,8 @@ export function ProjectCard({ project }: ProjectCardProps) {
@@ -109,7 +109,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
) : ( -
+
+
From 95b0ee505c552591e2a96209beb2e13b524cc68e Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:42:08 +0200 Subject: [PATCH 09/42] fix: limite la largeur du texte avec max-w-prose --- components/project-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 4ebdc49..d2ccc32 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -128,7 +128,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {/* Content Section */} -
+
From 8808022fa672818920f7c114d9d5002b813eb1ce Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:43:47 +0200 Subject: [PATCH 10/42] fix: limite la description avec max-w-sm --- components/project-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index d2ccc32..9440e8e 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -128,7 +128,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {/* Content Section */} -
+
@@ -166,7 +166,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
-

From ab3e366d15cb84c19001ce9d409650c47a1474fe Mon Sep 17 00:00:00 2001 From: ddrayko Date: Mon, 20 Jul 2026 15:44:54 +0200 Subject: [PATCH 11/42] fix: limite aussi la largeur des tags --- components/project-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 9440e8e..9269786 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -175,7 +175,7 @@ export function ProjectCard({ project }: ProjectCardProps) {

-
+
{project.tags.map((tag) => ( Date: Mon, 20 Jul 2026 15:45:24 +0200 Subject: [PATCH 12/42] =?UTF-8?q?fix:=20=C3=A9largit=20de=20max-w-sm=20?= =?UTF-8?q?=C3=A0=20max-w-md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 9269786..04f3063 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -166,7 +166,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
-

@@ -175,7 +175,7 @@ export function ProjectCard({ project }: ProjectCardProps) {

-
+
{project.tags.map((tag) => ( Date: Tue, 21 Jul 2026 12:04:48 +0200 Subject: [PATCH 13/42] =?UTF-8?q?feat:=20d=C3=A9tection=20automatique=20de?= =?UTF-8?q?=20la=20luminosit=C3=A9=20du=20fond=20pour=20adapter=20la=20cou?= =?UTF-8?q?leur=20du=20texte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 11 +++++--- hooks/use-image-brightness.ts | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 hooks/use-image-brightness.ts diff --git a/components/project-card.tsx b/components/project-card.tsx index 04f3063..cf96691 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button" import { ExternalLink, Github, ArrowUpRight, Wrench, Construction, CheckCircle2, Archive, PackageCheck, ImageOff, Trophy, Play, Pause } from "lucide-react" import Image from "next/image" import Link from "next/link" +import { useImageBrightness } from "@/hooks/use-image-brightness" interface ProjectCardProps { project: Project @@ -14,6 +15,8 @@ export function ProjectCard({ project }: ProjectCardProps) { const isArchived = project.is_archived; const isInDev = project.in_development; const isPaused = project.development_status === 'paused'; + const brightness = useImageBrightness(project.image_url) + const isDarkBg = brightness === 'dark' const statusBadges = ( <> @@ -128,7 +131,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {/* Content Section */} -
+
@@ -166,9 +169,9 @@ export function ProjectCard({ project }: ProjectCardProps) {
-

{project.description}

@@ -231,7 +234,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
{isInDev ? ( - diff --git a/hooks/use-image-brightness.ts b/hooks/use-image-brightness.ts new file mode 100644 index 0000000..95caa9e --- /dev/null +++ b/hooks/use-image-brightness.ts @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react' + +type Brightness = 'light' | 'dark' | 'loading' + +export function useImageBrightness(imageUrl: string | null): Brightness { + const [brightness, setBrightness] = useState('loading') + + useEffect(() => { + if (!imageUrl) { + setBrightness('dark') + return + } + + let cancelled = false + const img = new Image() + img.crossOrigin = 'anonymous' + + img.onload = () => { + if (cancelled) return + try { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + if (!ctx) { setBrightness('dark'); return } + + const sw = Math.floor(img.naturalWidth * 0.4) + const sh = img.naturalHeight + canvas.width = sw + canvas.height = sh + ctx.drawImage(img, img.naturalWidth - sw, 0, sw, sh, 0, 0, sw, sh) + + const { data } = ctx.getImageData(0, 0, sw, sh) + let total = 0 + for (let i = 0; i < data.length; i += 4) { + total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2] + } + setBrightness(total / (data.length / 4) > 128 ? 'light' : 'dark') + } catch { + setBrightness('dark') + } + } + + img.onerror = () => { + if (!cancelled) setBrightness('dark') + } + + img.src = imageUrl + + return () => { cancelled = true } + }, [imageUrl]) + + return brightness +} From eaec60aa4b31ae925404a397e06639302e2e9aad Mon Sep 17 00:00:00 2001 From: ddrayko Date: Tue, 21 Jul 2026 12:08:58 +0200 Subject: [PATCH 14/42] =?UTF-8?q?fix:=20remplace=20la=20d=C3=A9tection=20c?= =?UTF-8?q?anvas=20par=20un=20overlay=20sombre=20pour=20la=20lisibilit?= =?UTF-8?q?=C3=A9=20du=20texte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 18 ++++++++---- hooks/use-image-brightness.ts | 52 ----------------------------------- 2 files changed, 12 insertions(+), 58 deletions(-) delete mode 100644 hooks/use-image-brightness.ts diff --git a/components/project-card.tsx b/components/project-card.tsx index cf96691..5bfd28f 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button" import { ExternalLink, Github, ArrowUpRight, Wrench, Construction, CheckCircle2, Archive, PackageCheck, ImageOff, Trophy, Play, Pause } from "lucide-react" import Image from "next/image" import Link from "next/link" -import { useImageBrightness } from "@/hooks/use-image-brightness" interface ProjectCardProps { project: Project @@ -15,8 +14,6 @@ export function ProjectCard({ project }: ProjectCardProps) { const isArchived = project.is_archived; const isInDev = project.in_development; const isPaused = project.development_status === 'paused'; - const brightness = useImageBrightness(project.image_url) - const isDarkBg = brightness === 'dark' const statusBadges = ( <> @@ -105,6 +102,15 @@ export function ProjectCard({ project }: ProjectCardProps) { }} /> + {/* Desktop dark overlay - darkens the text area for readability */} +
+ {/* Mobile gradient */}
@@ -131,7 +137,7 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {/* Content Section */} -
+
@@ -171,7 +177,7 @@ export function ProjectCard({ project }: ProjectCardProps) {

{project.description}

@@ -234,7 +240,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
{isInDev ? ( - diff --git a/hooks/use-image-brightness.ts b/hooks/use-image-brightness.ts deleted file mode 100644 index 95caa9e..0000000 --- a/hooks/use-image-brightness.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useState, useEffect } from 'react' - -type Brightness = 'light' | 'dark' | 'loading' - -export function useImageBrightness(imageUrl: string | null): Brightness { - const [brightness, setBrightness] = useState('loading') - - useEffect(() => { - if (!imageUrl) { - setBrightness('dark') - return - } - - let cancelled = false - const img = new Image() - img.crossOrigin = 'anonymous' - - img.onload = () => { - if (cancelled) return - try { - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - if (!ctx) { setBrightness('dark'); return } - - const sw = Math.floor(img.naturalWidth * 0.4) - const sh = img.naturalHeight - canvas.width = sw - canvas.height = sh - ctx.drawImage(img, img.naturalWidth - sw, 0, sw, sh, 0, 0, sw, sh) - - const { data } = ctx.getImageData(0, 0, sw, sh) - let total = 0 - for (let i = 0; i < data.length; i += 4) { - total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2] - } - setBrightness(total / (data.length / 4) > 128 ? 'light' : 'dark') - } catch { - setBrightness('dark') - } - } - - img.onerror = () => { - if (!cancelled) setBrightness('dark') - } - - img.src = imageUrl - - return () => { cancelled = true } - }, [imageUrl]) - - return brightness -} From aca9385ab772f5293a40098812449b65e40b75bf Mon Sep 17 00:00:00 2001 From: ddrayko Date: Tue, 21 Jul 2026 12:12:27 +0200 Subject: [PATCH 15/42] =?UTF-8?q?fix:=20d=C3=A9tection=20de=20luminosit?= =?UTF-8?q?=C3=A9=20canvas=20avec=20gestion=20CORS=20+=20texte=20adaptatif?= =?UTF-8?q?=20(titre,=20description,=20tags)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 22 +++++-------- hooks/use-image-brightness.ts | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 hooks/use-image-brightness.ts diff --git a/components/project-card.tsx b/components/project-card.tsx index 5bfd28f..13fc883 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button" import { ExternalLink, Github, ArrowUpRight, Wrench, Construction, CheckCircle2, Archive, PackageCheck, ImageOff, Trophy, Play, Pause } from "lucide-react" import Image from "next/image" import Link from "next/link" +import { useImageBrightness } from "@/hooks/use-image-brightness" interface ProjectCardProps { project: Project @@ -14,6 +15,8 @@ export function ProjectCard({ project }: ProjectCardProps) { const isArchived = project.is_archived; const isInDev = project.in_development; const isPaused = project.development_status === 'paused'; + const brightness = useImageBrightness(project.image_url) + const isDarkBg = brightness === 'dark' const statusBadges = ( <> @@ -102,15 +105,6 @@ export function ProjectCard({ project }: ProjectCardProps) { }} /> - {/* Desktop dark overlay - darkens the text area for readability */} -
- {/* Mobile gradient */}
@@ -137,12 +131,12 @@ export function ProjectCard({ project }: ProjectCardProps) { )} {/* Content Section */} -
+

{project.title}

@@ -172,12 +166,12 @@ export function ProjectCard({ project }: ProjectCardProps) {
- +

{project.description}

@@ -240,7 +234,7 @@ export function ProjectCard({ project }: ProjectCardProps) {
{isInDev ? ( - diff --git a/hooks/use-image-brightness.ts b/hooks/use-image-brightness.ts new file mode 100644 index 0000000..1b01a85 --- /dev/null +++ b/hooks/use-image-brightness.ts @@ -0,0 +1,60 @@ +import { useState, useEffect } from 'react' + +type Brightness = 'light' | 'dark' | 'loading' + +export function useImageBrightness(imageUrl: string | null): Brightness { + const [brightness, setBrightness] = useState('loading') + + useEffect(() => { + if (!imageUrl) { + setBrightness('dark') + return + } + + let cancelled = false + const img = new Image() + + img.onload = () => { + if (cancelled) return + try { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + if (!ctx) { setBrightness('dark'); return } + + const sw = Math.floor(img.naturalWidth * 0.4) + const sh = img.naturalHeight + canvas.width = sw + canvas.height = sh + ctx.drawImage(img, img.naturalWidth - sw, 0, sw, sh, 0, 0, sw, sh) + + const { data } = ctx.getImageData(0, 0, sw, sh) + let total = 0 + for (let i = 0; i < data.length; i += 4) { + total += 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2] + } + setBrightness(total / (data.length / 4) > 128 ? 'light' : 'dark') + } catch { + setBrightness('dark') + } + } + + img.onerror = () => { + if (!cancelled) setBrightness('dark') + } + + try { + const url = new URL(imageUrl, window.location.origin) + if (url.origin !== window.location.origin) { + img.crossOrigin = 'anonymous' + } + } catch { + img.crossOrigin = 'anonymous' + } + + img.src = imageUrl + + return () => { cancelled = true } + }, [imageUrl]) + + return brightness +} From fbd35e3ebec22f1deb3a7758995d5a4e25e72d70 Mon Sep 17 00:00:00 2001 From: ddrayko Date: Tue, 21 Jul 2026 12:23:53 +0200 Subject: [PATCH 16/42] =?UTF-8?q?fix:=20utilise=20l'URL=20optimis=C3=A9e?= =?UTF-8?q?=20Next.js=20(=5Fnext/image)=20pour=20contourner=20CORS=20sur?= =?UTF-8?q?=20img.drayko.xyz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/project-card.tsx | 8 ++++---- hooks/use-image-brightness.ts | 23 +++++++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/components/project-card.tsx b/components/project-card.tsx index 13fc883..e0cc5b7 100644 --- a/components/project-card.tsx +++ b/components/project-card.tsx @@ -183,8 +183,8 @@ export function ProjectCard({ project }: ProjectCardProps) { {tag} @@ -241,7 +241,7 @@ export function ProjectCard({ project }: ProjectCardProps) { ) : ( project.project_url && ( ) : ( project.project_url && ( @@ -253,7 +253,7 @@ export function ProjectCard({ project }: ProjectCardProps) { asChild variant="ghost" size="sm" - className="rounded-full border border-white/10 glass hover:bg-white/10 hover:text-foreground font-bold tracking-tight transition-all duration-500 hover:scale-110 active:scale-95 text-xs h-8 px-4 text-white mix-blend-difference" + className="rounded-full border border-white/10 glass hover:bg-white/10 hover:text-foreground font-bold tracking-tight transition-all duration-500 hover:scale-110 active:scale-95 text-xs h-8 px-4 text-contrast text-white" >