diff --git a/public/images/projets/amarea.png b/public/images/projets/amarea.png new file mode 100644 index 0000000..0149b83 Binary files /dev/null and b/public/images/projets/amarea.png differ diff --git a/public/images/projets/arboretum-dark.png b/public/images/projets/arboretum-dark.png new file mode 100644 index 0000000..141a5c7 Binary files /dev/null and b/public/images/projets/arboretum-dark.png differ diff --git a/public/images/projets/arboretum.png b/public/images/projets/arboretum.png new file mode 100644 index 0000000..9971eca Binary files /dev/null and b/public/images/projets/arboretum.png differ diff --git a/public/images/projets/ksauce-dark.png b/public/images/projets/ksauce-dark.png new file mode 100644 index 0000000..0096052 Binary files /dev/null and b/public/images/projets/ksauce-dark.png differ diff --git a/public/images/projets/ksauce.png b/public/images/projets/ksauce.png new file mode 100644 index 0000000..3ccc6be Binary files /dev/null and b/public/images/projets/ksauce.png differ diff --git a/public/images/projets/lidge.png b/public/images/projets/lidge.png new file mode 100644 index 0000000..b4b1dc5 Binary files /dev/null and b/public/images/projets/lidge.png differ diff --git a/public/images/projets/techos-dark.png b/public/images/projets/techos-dark.png new file mode 100644 index 0000000..6bdb56d Binary files /dev/null and b/public/images/projets/techos-dark.png differ diff --git a/public/images/projets/techos.png b/public/images/projets/techos.png index 3180d5e..5b180c7 100644 Binary files a/public/images/projets/techos.png and b/public/images/projets/techos.png differ diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro index 78660f4..edf4a17 100644 --- a/src/components/ProjectCard.astro +++ b/src/components/ProjectCard.astro @@ -12,11 +12,20 @@ interface Props { project: Project; } const { project: p } = Astro.props; const st = STATUS[p.status]; const mono = p.title.replace(/[^A-Za-z0-9]/g, "").slice(0, 2); + +// Toute la carte devient cliquable (nouvel onglet) dès qu'un lien est défini. +const Tag = p.link ? "a" : "article"; +const linkAttrs = p.link + ? { href: p.link, target: "_blank", rel: "noopener", "aria-label": `${p.title} — voir le projet (nouvel onglet)` } + : {}; --- -
-
+ +
{p.img ? ( - {p.title} + <> + {p.title} + {p.imgDark && {p.title}} + ) : (
{mono}
)} @@ -25,9 +34,9 @@ const mono = p.title.replace(/[^A-Za-z0-9]/g, "").slice(0, 2);
{st.label} {p.link && ( - + + )}

{p.title}

@@ -37,4 +46,4 @@ const mono = p.title.replace(/[^A-Za-z0-9]/g, "").slice(0, 2); {p.stack.map((s) => {s})}
-
+ diff --git a/src/data/content.ts b/src/data/content.ts index 43e7377..167fc2e 100644 --- a/src/data/content.ts +++ b/src/data/content.ts @@ -15,6 +15,8 @@ export interface Project { status: ProjectStatus; link?: string; img?: string; + /** Variante du logo pour le thème sombre (logo « blanc »). Si absent, `img` est utilisé partout. */ + imgDark?: string; } export interface Experience { @@ -86,6 +88,7 @@ export const projectsFeatured: Project[] = [ status: "live", link: "https://techos-asso.fr", img: "/images/projets/techos.png", + imgDark: "/images/projets/techos-dark.png", }, { id: "amarea", @@ -95,15 +98,18 @@ export const projectsFeatured: Project[] = [ "Site vitrine + gestion pour un studio de tatouage. Architecture découplée SPA + API REST avec gestion de médias.", stack: ["Vue 3", "Vite", "NestJS 11", "TypeORM", "MariaDB", "Tailwind 4"], status: "live", + img: "/images/projets/amarea.png", }, { id: "lidge", title: "Lidge", subtitle: "Portail d'abonnements aux services auto-hébergés", desc: - "Plateforme perso pour gérer les abonnements à mes services auto-hébergés (Jellyfin, Cloud, Vault, Mail, Git…) avec SSO, tickets et workflow d'accès.", + "Plateforme perso pour gérer les abonnements à mes services auto-hébergés (Cloud, Vault, Git) avec SSO, tickets et workflow d'accès.", stack: ["Nuxt 3", "NestJS 11", "TypeORM", "MariaDB", "JWT", "Nodemailer"], status: "live", + link: "https://lidge.fr", + img: "/images/projets/lidge.png", }, ]; @@ -117,6 +123,8 @@ export const projectsOther: Project[] = [ stack: ["Node.js", "Vue 3", "TypeScript", "npx"], status: "oss", link: "https://github.com/JohanLeroy", + img: "/images/projets/arboretum.png", + imgDark: "/images/projets/arboretum-dark.png", }, { id: "ksauce", @@ -126,14 +134,18 @@ export const projectsOther: Project[] = [ "Bot Discord pour la communauté KSauce : commandes, modération et fonctionnalités sur-mesure.", stack: ["Node.js", "TypeScript", "Discord.js"], status: "live", + link: "https://ksauce.lidge.fr/", + img: "/images/projets/ksauce.png", + imgDark: "/images/projets/ksauce-dark.png", }, { id: "lplv", title: "Liberty Pour La Vie", subtitle: "Site associatif", - desc: "Site web complet pour l'association Liberty Pour La Vie. Projet désormais fermé.", + desc: "Site web complet pour l'association Liberty Pour La Vie, toujours en ligne.", stack: ["Java", "Spring Boot", "Thymeleaf", "Bootstrap", "MariaDB"], - status: "archived", + status: "live", + link: "https://libertypourlavie.fr/", img: "/images/projets/lplv.png", }, { diff --git a/src/styles/global.css b/src/styles/global.css index f6549bf..da8bdd7 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -150,10 +150,16 @@ html[data-theme="light"] .icon-moon{display:block;} .grid-projects.is-other{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));} .card{display:flex;flex-direction:column;border:1px solid var(--border);border-radius:14px;background:var(--surface-2);overflow:hidden;transition:transform .25s ease,border-color .25s ease,box-shadow .25s ease;} .card:hover{transform:translateY(-4px);border-color:var(--accent);box-shadow:var(--shadow);} -.card-media{position:relative;height:168px;overflow:hidden;} +.card--link{cursor:pointer;text-decoration:none;color:inherit;} +.card--link:focus-visible{outline:2px solid var(--accent);outline-offset:3px;} +.card-media{position:relative;height:168px;overflow:hidden;background:var(--surface);padding:16px 20px;} .is-other .card-media{height:140px;} -.card-media img{width:100%;height:100%;object-fit:cover;display:block;} -.card-mono{height:100%;display:flex;align-items:center;justify-content:center;background:radial-gradient(120% 130% at 0% 0%,color-mix(in srgb,var(--accent) 18%,transparent),transparent 58%),var(--surface);} +.card-media img{width:100%;height:100%;object-fit:contain;display:block;} +/* Swap du logo selon le thème (:root = sombre → on montre la variante claire/blanche). */ +.card-media--themed .logo-light{display:none;} +[data-theme="light"] .card-media--themed .logo-light{display:block;} +[data-theme="light"] .card-media--themed .logo-dark{display:none;} +.card-mono{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:radial-gradient(120% 130% at 0% 0%,color-mix(in srgb,var(--accent) 18%,transparent),transparent 58%),var(--surface);} .card-mono span{font-family:var(--font-mono);font-size:44px;font-weight:700;color:var(--accent);opacity:.92;} .is-other .card-mono span{font-size:38px;} .card-body{padding:18px 18px 20px;display:flex;flex-direction:column;gap:11px;flex:1;} @@ -161,8 +167,8 @@ html[data-theme="light"] .icon-moon{display:block;} .card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;} .status{display:inline-flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em;} .status-dot{width:7px;height:7px;border-radius:50%;} -.ext-link{color:var(--text-muted);display:inline-flex;} -.ext-link:hover{color:var(--accent);} +.ext-link{color:var(--text-muted);display:inline-flex;transition:color .25s ease;} +.card--link:hover .ext-link{color:var(--accent);} .card-title{font-size:19px;font-weight:700;letter-spacing:-.02em;color:var(--text);} .is-other .card-title{font-size:17px;} .card-sub{font-family:var(--font-mono);font-size:12px;color:var(--accent);}