diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40f4d1c..a01bf36 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,7 @@ deploy: - echo "🚀 Déploiement du Portfolio..." - kubectl apply -f k8s.yaml - kubectl rollout restart deployment/portfolio-app -n portfolio-ns - - echo "✅ Déploiement terminé sur http://portfolio.theval.ovh" + - echo "✅ Déploiement terminé sur https://portfolio.valentin-massonniere.ch" only: - main - master diff --git a/Dockerfile b/Dockerfile index 001a05d..7936e6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,5 @@ -# Dockerfile FROM node:18-alpine AS base -# Install dependencies only when needed FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app @@ -14,18 +12,15 @@ RUN \ else echo "Lockfile not found." && exit 1; \ fi -# Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . -# Next.js collects completely anonymous telemetry data about general usage. ENV NEXT_TELEMETRY_DISABLED 1 RUN npm run build -# Production image, copy all the files and run next FROM base AS runner WORKDIR /app @@ -37,11 +32,9 @@ RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public -# Set the correct permission for prerender cache RUN mkdir .next RUN chown nextjs:nodejs .next -# Automatically leverage output traces to reduce image size COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static diff --git a/README.md b/README.md index 93f80e6..6a68f64 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This repository is the home for the source code of my personal portfolio built w ## Live Demo 🌐 -Check out the live demo here: http://portfolio.theval.ovh +Check out the live demo here: https://portfolio.valentin-massonniere.ch ## Features 🚀 diff --git a/app/layout.js b/app/layout.js index 1f5471e..07177f7 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,8 +1,28 @@ import "./globals.css"; +const siteUrl = "https://portfolio.valentin-massonniere.ch"; +const title = "Valentin Massonniere — AI & Robotics Engineer"; +const description = + "Portfolio of Valentin Massonniere, AI & Robotics Engineer and Data Scientist, working on real-time robotics platforms, ESP32 firmware, SLAM, and applied machine learning."; + export const metadata = { - title: "Valentin Massonniere", - description: "Valentin Massonniere Portfolio", + metadataBase: new URL(siteUrl), + title, + description, + alternates: { canonical: siteUrl }, + openGraph: { + type: "website", + url: siteUrl, + title, + description, + siteName: "Valentin Massonniere", + locale: "en_US", + }, + twitter: { + card: "summary_large_image", + title, + description, + }, }; export default function RootLayout({ children }) { diff --git a/app/page.js b/app/page.js index ea7be7d..ab1316a 100644 --- a/app/page.js +++ b/app/page.js @@ -3,6 +3,7 @@ import Presentation from "../components/Presentation"; import About from "../components/About"; import Projects from "../components/Projects"; import Experiences from "@/components/Experiences"; +import Certifications from "@/components/Certifications"; import Educations from "@/components/Educations"; import Contact from "@/components/Contact"; @@ -12,6 +13,7 @@ export default function Home() {
+ diff --git a/components/About.jsx b/components/About.jsx index 8328989..7c7d11a 100644 --- a/components/About.jsx +++ b/components/About.jsx @@ -8,35 +8,40 @@ const About = () => { id="aboutScroll" className="max-w-6xl mx-auto my-20 px-4 md:px-8 grid grid-cols-1 md:grid-cols-2 gap-16 items-center min-h-[80vh]" > - {/* Left Image Column */}
about image
- {/* Right Content Column */}
- {/* Title */}

About me

- {/* Paragraph 1 */}

I'm currently registered at Efrei Paris specializing in Data - Engineering & AI 📊. I'm in apprenticeship at IceBerg Data Lab such as - AI Engineer & Data Scientist ✨. + Engineering & AI. I'm in apprenticeship at Iceberg Data Lab as AI + Engineer & Data Scientist.

- {/* Paragraph 2 */}

- These aspirations merge my deep interest in IT 🖥️ with the fascinating - fields of aerospace and aeronautics 🛰️. My strong passion lies in - computer science and artificial intelligence, and my ultimate aim is - to become a highly skilled engineer in the big data and AI domains. 📈 + My deep interest in IT merges with the fascinating field of aerospace. + My strong passion lies in computer science and artificial + intelligence, with a growing focus on robotics and its applications to + space. +

+

+ I am working toward a PhD in Robotics + AI, and I prepare for it + actively. With DeepSight-Nebula, I am turning an educational robot + into a real-time capable platform — writing ESP32 firmware from + scratch and documenting the work as a mini-thesis. In parallel, I run + a daily spaced-repetition review on MP-level mathematics, work through + the Rust book for low-level fluency, and read one robotics paper per + week. Next up: a SLAM-focused project paired with a ROS2 course, to + bridge the gap from firmware to full autonomous systems.

diff --git a/components/Certifications.jsx b/components/Certifications.jsx new file mode 100644 index 0000000..1cc894d --- /dev/null +++ b/components/Certifications.jsx @@ -0,0 +1,80 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import Image from "next/image"; + +const Certifications = () => { + const [certifications, setCertifications] = useState(null); + + useEffect(() => { + const fetchCertifications = async () => { + try { + const response = await fetch("/assets/data/data.json"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setCertifications(data.certifications || []); + } catch (error) { + console.error("Failed to fetch certifications data:", error); + } + }; + + fetchCertifications(); + }, []); + + if (!certifications) { + return ( +
+

+ Loading Certifications... +

+
+ ); + } + + return ( +
+

+ Certifications +

+ +
+ {certifications.map((cert) => ( + +
+ {`${cert.name} +
+
+

+ {cert.name} +

+

+ {cert.issuer} +

+
+
+ ))} +
+
+ ); +}; + +export default Certifications; diff --git a/components/Contact.jsx b/components/Contact.jsx index 2e60d5e..e3d9808 100644 --- a/components/Contact.jsx +++ b/components/Contact.jsx @@ -1,62 +1,71 @@ "use client"; import React from "react"; -import Image from "next/image"; -import { MailIcon, LinkedinIcon, GithubIcon } from "lucide-react"; +import { Mail, MailOpen, LinkedinIcon, GithubIcon } from "lucide-react"; const Contact = () => { + const itemClass = + "group relative flex items-center gap-3 text-gray-700 font-mulish text-lg rounded-lg px-3 py-2 -mx-3 transition-colors duration-300 hover:text-blue-600"; + return (
- {/* Contact section title */}

Contact

- Feel free to contact me! 😊 + Feel free to contact me!

-
-
-
- {/* Mail contact */} - -
- -
- Contact illustration + + + Valentin MASSONNIERE +
diff --git a/components/Educations.jsx b/components/Educations.jsx index 7a6db34..e110551 100644 --- a/components/Educations.jsx +++ b/components/Educations.jsx @@ -41,7 +41,6 @@ const Educations = () => { Educations - {/* Formations container */}
{formationsData.map((formation, index) => (
{ md:grid md:grid-cols-2 md:gap-8 p-6 `} > - {/* Conditional rendering for alternating layout */} {index % 2 === 0 ? ( <> - {/* Left/Image Column */}
{ className="w-full h-auto rounded-lg transition-transform duration-300 hover:scale-95 shadow-md" />
- {/* Right/Content Column */}

{formation.name}

- {/* Use whitespace-pre-wrap to respect newline characters in description */}

{formation.desc}

- {/* Link to formation (if available) */} {formation.link && (
{ ) : ( <> - {/* Right/Content Column (now on left visually) */} - {/* Left/Image Column (now on right visually) */}
{ Experiences - {/* Experiences container */}
{experiencesData.map((experience, index) => (
{ md:grid md:grid-cols-2 md:gap-8 p-6 `} > - {/* Conditional rendering for alternating layout */} {index % 2 === 0 ? ( <> - {/* Left/Image Column */}
{`${experience.name}
- {/* Right/Content Column */}

{experience.name} @@ -74,11 +70,15 @@ const Experiences = () => {

{experience.date}

+ {experience.about && ( +

+ {experience.about} +

+ )}

{experience.desc}

- {/* Link to experience (if available) */} {experience.link && (
{ ) : ( <> - {/* Right/Content Column (now on left visually) */} - {/* Left/Image Column (now on right visually) */}
{`${experience.name}
diff --git a/components/Header.jsx b/components/Header.jsx index c6b539f..2d46baa 100644 --- a/components/Header.jsx +++ b/components/Header.jsx @@ -26,6 +26,7 @@ const Header = () => { const navLinks = [ { name: "Home", href: "#home" }, { name: "About", href: "#aboutScroll" }, + { name: "Certifications", href: "#certificationsScroll" }, { name: "Projects", href: "#projectScroll" }, { name: "Experience", href: "#experiencesScroll" }, { name: "Education", href: "#educationScroll" }, diff --git a/components/Presentation.jsx b/components/Presentation.jsx index dd6415e..4693197 100644 --- a/components/Presentation.jsx +++ b/components/Presentation.jsx @@ -11,7 +11,6 @@ const Presentation = () => { id="home" className="flex flex-col md:flex-row items-center justify-center min-h-screen py-16 px-4 md:px-8 bg-gray-50 overflow-hidden" > - {/* Right Image Column (appears first on small screens due to flex-col order) */}
{ }} >
- {/* Left Content Column (appears second on small screens, first on medium screens) */}
- {/* Scroll Down Indicator */} { Projects

- {/* Main projects container (Top Projects) */}
{topProjects.map((project, index) => (
{ md:grid md:grid-cols-2 md:gap-8 p-6 `} > - {/* Conditional rendering for alternating layout */} {index % 2 === 0 ? ( <> - {/* Left/Image Column */}
{ className="w-full h-auto rounded-lg transition-transform duration-300 hover:scale-95 shadow-md" />
- {/* Right/Content Column */}

{project.name}

- {/* Applied font-poppins here */}

{project.desc}

- {/* Language Icons */}
{project.language.map((langIcon, iconIndex) => ( { /> ))}
- {/* Code/Demo Links */}
{project.link && ( { ) : ( <> - {/* Right/Content Column (now on left visually) */} - {/* Left/Image Column (now on right visually) */}
{ ))}
- {/* Toggle Button for All Projects */} - {/* All Projects Section (conditionally displayed) */}
{ {bottomProjects.map((project) => ( - - - - \ No newline at end of file diff --git a/public/assets/icon/Bash.svg b/public/assets/icon/Bash.svg deleted file mode 100644 index f00218c..0000000 --- a/public/assets/icon/Bash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Bots.svg b/public/assets/icon/Bots.svg deleted file mode 100644 index a891e4a..0000000 --- a/public/assets/icon/Bots.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/CPP.svg b/public/assets/icon/CPP.svg deleted file mode 100644 index a5072bf..0000000 --- a/public/assets/icon/CPP.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Discord.svg b/public/assets/icon/Discord.svg deleted file mode 100644 index d5df79b..0000000 --- a/public/assets/icon/Discord.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Docker.svg b/public/assets/icon/Docker.svg deleted file mode 100644 index 3d508c2..0000000 --- a/public/assets/icon/Docker.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/GCP.svg b/public/assets/icon/GCP.svg deleted file mode 100644 index 77877d1..0000000 --- a/public/assets/icon/GCP.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Git.svg b/public/assets/icon/Git.svg deleted file mode 100644 index 28e85bc..0000000 --- a/public/assets/icon/Git.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/GitHub.svg b/public/assets/icon/GitHub.svg deleted file mode 100644 index 9b71c45..0000000 --- a/public/assets/icon/GitHub.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/assets/icon/GitLab.svg b/public/assets/icon/GitLab.svg deleted file mode 100644 index 1b7e3c2..0000000 --- a/public/assets/icon/GitLab.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/assets/icon/JavaScript.svg b/public/assets/icon/JavaScript.svg deleted file mode 100644 index 991e506..0000000 --- a/public/assets/icon/JavaScript.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/public/assets/icon/Linux.svg b/public/assets/icon/Linux.svg deleted file mode 100644 index 608680d..0000000 --- a/public/assets/icon/Linux.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Ollama.svg b/public/assets/icon/Ollama.svg deleted file mode 100644 index cc887e3..0000000 --- a/public/assets/icon/Ollama.svg +++ /dev/null @@ -1 +0,0 @@ -Ollama \ No newline at end of file diff --git a/public/assets/icon/Python.svg b/public/assets/icon/Python.svg deleted file mode 100644 index dd0e485..0000000 --- a/public/assets/icon/Python.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/QT.svg b/public/assets/icon/QT.svg deleted file mode 100644 index dc2e270..0000000 --- a/public/assets/icon/QT.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/ROS-Dark.svg b/public/assets/icon/ROS-Dark.svg deleted file mode 100644 index 47a62ff..0000000 --- a/public/assets/icon/ROS-Dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/public/assets/icon/RaspberryPi.svg b/public/assets/icon/RaspberryPi.svg deleted file mode 100644 index 16ffb73..0000000 --- a/public/assets/icon/RaspberryPi.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/public/assets/icon/React.svg b/public/assets/icon/React.svg deleted file mode 100644 index 9ba319e..0000000 --- a/public/assets/icon/React.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/Sass.svg b/public/assets/icon/Sass.svg deleted file mode 100644 index 9e68548..0000000 --- a/public/assets/icon/Sass.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/assets/icon/VisualStudio.svg b/public/assets/icon/VisualStudio.svg deleted file mode 100644 index 8b7307d..0000000 --- a/public/assets/icon/VisualStudio.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/assets/img/about_me.jpg b/public/assets/img/about_me.jpg new file mode 100644 index 0000000..4366ea6 Binary files /dev/null and b/public/assets/img/about_me.jpg differ