diff --git a/index.html b/index.html index 16835c5..852d5a2 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + diff --git a/package.json b/package.json index fb2ce8a..6ecf9bc 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@types/react": "19.1.11", - "@types/react-dom": "19.1.8", + "@types/react": "19.1.12", + "@types/react-dom": "19.1.9", "aos": "^2.3.4", "browserslist": "^4.25.3", "browserslist-useragent": "^4.0.0", @@ -60,7 +60,7 @@ "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@simonsmith/cypress-image-snapshot": "^10.0.2", - "@vitejs/plugin-react": "^5.0.1", + "@vitejs/plugin-react": "^5.0.2", "prettier": "^3.6.2", "vite": "^7.1.3", "vite-plugin-svgr": "^4.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b331990..dcd9e86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,16 +22,16 @@ importers: version: 6.8.0 '@testing-library/react': specifier: ^16.3.0 - version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) '@types/react': - specifier: 19.1.11 - version: 19.1.11 + specifier: 19.1.12 + version: 19.1.12 '@types/react-dom': - specifier: 19.1.8 - version: 19.1.8(@types/react@19.1.11) + specifier: 19.1.9 + version: 19.1.9(@types/react@19.1.12) aos: specifier: ^2.3.4 version: 2.3.4 @@ -94,8 +94,8 @@ importers: specifier: ^10.0.2 version: 10.0.2(cypress@15.0.0) '@vitejs/plugin-react': - specifier: ^5.0.1 - version: 5.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.91.0)) + specifier: ^5.0.2 + version: 5.0.2(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.91.0)) prettier: specifier: ^3.6.2 version: 3.6.2 @@ -539,8 +539,8 @@ packages: resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} engines: {node: '>= 10.0.0'} - '@rolldown/pluginutils@1.0.0-beta.32': - resolution: {integrity: sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==} + '@rolldown/pluginutils@1.0.0-beta.34': + resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} '@rollup/pluginutils@5.2.0': resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} @@ -889,13 +889,13 @@ packages: '@types/pixelmatch@5.2.6': resolution: {integrity: sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==} - '@types/react-dom@19.1.8': - resolution: {integrity: sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==} + '@types/react-dom@19.1.9': + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.11': - resolution: {integrity: sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==} + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} '@types/sinonjs__fake-timers@8.1.1': resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} @@ -915,8 +915,8 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@vitejs/plugin-react@5.0.1': - resolution: {integrity: sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==} + '@vitejs/plugin-react@5.0.2': + resolution: {integrity: sha512-tmyFgixPZCx2+e6VO9TNITWcCQl8+Nl/E8YbAyPVv85QCc7/A3JrdfG2A8gIzvVhWuzMOVrFW1aReaNxrI6tbw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -2773,7 +2773,7 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.1 '@parcel/watcher-win32-x64': 2.5.1 - '@rolldown/pluginutils@1.0.0-beta.32': {} + '@rolldown/pluginutils@1.0.0-beta.34': {} '@rollup/pluginutils@5.2.0(rollup@4.48.1)': dependencies: @@ -3025,15 +3025,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.8(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@babel/runtime': 7.28.3 '@testing-library/dom': 10.4.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optionalDependencies: - '@types/react': 19.1.11 - '@types/react-dom': 19.1.8(@types/react@19.1.11) + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -3093,11 +3093,11 @@ snapshots: dependencies: '@types/node': 24.3.0 - '@types/react-dom@19.1.8(@types/react@19.1.11)': + '@types/react-dom@19.1.9(@types/react@19.1.12)': dependencies: - '@types/react': 19.1.11 + '@types/react': 19.1.12 - '@types/react@19.1.11': + '@types/react@19.1.12': dependencies: csstype: 3.1.3 @@ -3118,12 +3118,12 @@ snapshots: '@types/node': 24.3.0 optional: true - '@vitejs/plugin-react@5.0.1(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.91.0))': + '@vitejs/plugin-react@5.0.2(vite@7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.91.0))': dependencies: '@babel/core': 7.28.3 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) - '@rolldown/pluginutils': 1.0.0-beta.32 + '@rolldown/pluginutils': 1.0.0-beta.34 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 vite: 7.1.3(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.91.0) diff --git a/public/V5.png b/public/V5.png new file mode 100644 index 0000000..08d6169 Binary files /dev/null and b/public/V5.png differ diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 17b9290..d3321d3 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,59 +1,102 @@ { - "about.title": "About me", - "about.description": "I am a fourth-year student at ISEN Nantes, currently in a work-study program at Horoquartz. I am passionate about computer science. I learned to code on my own and am currently learning Rust and Go. I am also passionate about electronics and hardware. I have a homelab consisting of 3 servers: a DELL T320, a DELL T330, and a Dell Precision T3610, all running Proxmox.", - "card.title": "Fourth year student at ISEN Nantes in work-study program at Horoquartz", - "projects.title": "My projects", - "projects.Front end starter.description": "My personal starter for front end projects", - "projects.Project C - ISEN CIR 1.description": "End of 1st year project at ISEN Nantes", - "projects.Projet robot.description": "Robot project with the Modelec ISEN club for the French robotics cup (Development and deployment on Raspberry Pi)", - "projects.MercuryCloud.description": "Game server and VPS hosting project. Technical support position, administrator of VPS, Game and web services.", - "projects.Projet C++ - ISEN CIR 2.description": "End of 4th semester project at ISEN Nantes. Creation of a Tower Defense type game in C++ with the QT6 library.", - "projects.Github NTFY.description": "Notification project for github and dockerhub releases that sends notifications on ntfy, gotify and discord.", - "projects.Projet C++ - ISEN CIPA 3.description": "Fish school behavior simulation game project in C++ with the SLD2 library, with multiplayer support.", - "projects.Alternance Horoquartz.description": "Development of an update system for Horoquartz products.", - "cv.title": "My resume", - "cv.path": "/CV-Felix-MARQUET-English.pdf", - "nav.about": "About me", - "nav.projects": "My projects", - "nav.cv": "My resume", - "skills.beginner": "Beginner", - "skills.intermediate": "Intermediate", - "skills.expert": "Expert", - "skills.examples.C.title": "C Projects", - "skills.examples.C.description": "End-of-first-year project at ISEN, basic algorithms, creation of a REST API.", - "skills.examples.C++.title": "C++ Projects", - "skills.examples.C++.description": "Tower Defense in Qt6, fish school simulation with SDL2 featuring multiplayer support, basic algorithms.", - "skills.examples.Admin Système.title": "System Administration", - "skills.examples.Admin Système.description": "Configuration of 3 DELL servers under Proxmox, virtualization, maintenance of virtual machines, deployment of applications in Azure.", - "skills.examples.Python.title": "Python Projects", - "skills.examples.Python.description": "GitHub NTFY for GitHub and DockerHub release notifications, course projects.", - "skills.examples.PHP.title": "PHP Projects", - "skills.examples.PHP.description": "Web development with PHP, AJAX, PostgreSQL.", - "skills.examples.HTML/CSS.title": "Front-end development", - "skills.examples.HTML/CSS.description": "Custom front-end starter, base project, various web projects.", - "skills.examples.JS/TS.title": "JavaScript/TypeScript", - "skills.examples.JS/TS.description": "React development, this portfolio, NodeJS API with Express, creation of the Studysen mobile application with React Native, professional projects.", - "skills.examples.Linux.title": "Linux Administration", - "skills.examples.Linux.description": "Server configuration, system administration, Ansible playbooks.", - "skills.examples.Go.title": "Go Projects", - "skills.examples.Go.description": "Introduction to the language, creation of a REST API with Fiber, Go Gin, and the standard library, professional projects.", - "skills.examples.Docker.title": "Containerization", - "skills.examples.Docker.description": "Container deployment, Docker Compose configuration, image creation, high-availability service deployment via Swarm and Kubernetes.", - "skills.examples.Rust.title": "Rust Projects", - "skills.examples.Rust.description": "Learning the language, creation of a REST API with Ntex, rewrite of the GitHub NTFY API in Rust.", - "skills.examples.React.title": "React Development", - "skills.examples.React.description": "This portfolio, Modelec website, Studysen mobile application, professional projects.", - - "contact.title": "Contact", - "contact.subtitle": "A question? A project? Feel free to contact me!", - "contact.info.title": "Contact information", - "contact.info.email": "Email", - "contact.info.status": "Status", - "contact.info.statusValue": "ISEN student - Work-study at Horoquartz", - "contact.info.response": "Response", - "contact.info.responseValue": "Usually within 24h", - "contact.subjects.title": "Topics of interest", - "contact.subjects.list": [ + "about": { + "title": "About me", + "description": "I am a fourth-year student at ISEN Nantes, currently in a work-study program at Horoquartz. I am passionate about computer science. I learned to code on my own and am currently learning Rust and Go. I am also passionate about electronics and hardware. I have a homelab consisting of 3 servers: a DELL T320, a DELL T330, and a Dell Precision T3610, all running Proxmox." + }, + "card": { + "title": "Fourth year student at ISEN Nantes in work-study program at Horoquartz" + }, + "projects": { + "title": "My projects", + "Front end starter.description": "My personal starter for front end projects", + "Project C - ISEN CIR 1.description": "End of 1st year project at ISEN Nantes", + "Projet robot.description": "Robot project with the Modelec ISEN club for the French robotics cup (Development and deployment on Raspberry Pi)", + "MercuryCloud.description": "Game server and VPS hosting project. Technical support position, administrator of VPS, Game and web services.", + "Projet C++ - ISEN CIR 2.description": "End of 4th semester project at ISEN Nantes. Creation of a Tower Defense type game in C++ with the QT6 library.", + "Github NTFY.description": "Notification project for github and dockerhub releases that sends notifications on ntfy, gotify and discord.", + "Projet C++ - ISEN CIPA 3.description": "Fish school behavior simulation game project in C++ with the SLD2 library, with multiplayer support.", + "Alternance Horoquartz.description": "Development of an update system for Horoquartz products." + }, + "cv": { + "title": "My resume", + "path": "/CV-Felix-MARQUET-English.pdf" + }, + "nav": { + "about": "About me", + "projects": "My projects", + "cv": "My resume", + "contact": "Contact", + "experience": "Experience", + "github": "GitHub" + }, + "skills": { + "beginner": "Beginner", + "intermediate": "Intermediate", + "expert": "Expert", + "examples": { + "C": { + "title": "C Projects", + "description": "End-of-first-year project at ISEN, basic algorithms, creation of a REST API." + }, + "C++": { + "title": "C++ Projects", + "description": "Tower Defense in Qt6, fish school simulation with SDL2 featuring multiplayer support, basic algorithms." + }, + "Admin Système": { + "title": "System Administration", + "description": "Configuration of 3 DELL servers under Proxmox, virtualization, maintenance of virtual machines, deployment of applications in Azure." + }, + "Python": { + "title": "Python Projects", + "description": "GitHub NTFY for GitHub and DockerHub release notifications, course projects." + }, + "PHP": { + "title": "PHP Projects", + "description": "Web development with PHP, AJAX, PostgreSQL." + }, + "HTML/CSS": { + "title": "Front-end development", + "description": "Custom front-end starter, base project, various web projects." + }, + "JS/TS": { + "title": "JavaScript/TypeScript", + "description": "React development, this portfolio, NodeJS API with Express, creation of the Studysen mobile application with React Native, professional projects." + }, + "Linux": { + "title": "Linux Administration", + "description": "Server configuration, system administration, Ansible playbooks." + }, + "Go": { + "title": "Go Projects", + "description": "Introduction to the language, creation of a REST API with Fiber, Go Gin, and the standard library, professional projects." + }, + "Docker": { + "title": "Containerization", + "description": "Container deployment, Docker Compose configuration, image creation, high-availability service deployment via Swarm and Kubernetes." + }, + "Rust": { + "title": "Rust Projects", + "description": "Learning the language, creation of a REST API with Ntex, rewrite of the GitHub NTFY API in Rust." + }, + "React": { + "title": "React Development", + "description": "This portfolio, Modelec website, Studysen mobile application, professional projects." + } + } + }, + "contact": { + "title": "Contact", + "subtitle": "A question? A project? Feel free to contact me!", + "info": { + "title": "Contact information", + "email": "Email", + "status": "Status", + "statusValue": "ISEN student - Work-study at Horoquartz", + "response": "Response", + "responseValue": "Usually within 24h" + }, + "subjects.title": "Topics of interest", + "subjects.list": [ "Web Development", "System Administration", "DevOps", @@ -63,19 +106,20 @@ "Collaboration", "Internship/Job" ], - "contact.form.title": "Send me a message", - "contact.form.name": "Full name", - "contact.form.namePlaceholder": "Your name", - "contact.form.email": "Email", - "contact.form.emailPlaceholder": "your.email@example.com", - "contact.form.subject": "Subject", - "contact.form.subjectPlaceholder": "Subject of your message", - "contact.form.message": "Message", - "contact.form.messagePlaceholder": "Your message...", - "contact.form.send": "Send message", - "contact.form.sending": "Sending...", - "contact.form.success": "Message sent successfully! I will reply as soon as possible.", - "contact.form.error": "An error occurred. Please try again later.", + "form.title": "Send me a message", + "form.name": "Full name", + "form.namePlaceholder": "Your name", + "form.email": "Email", + "form.emailPlaceholder": "your.email@example.com", + "form.subject": "Subject", + "form.subjectPlaceholder": "Subject of your message", + "form.message": "Message", + "form.messagePlaceholder": "Your message...", + "form.send": "Send message", + "form.sending": "Sending...", + "form.success": "Message sent successfully! I will reply as soon as possible.", + "form.error": "An error occurred. Please try again later." + }, "experience": { "title": "Career & Experience", "description": "My professional and academic journey", @@ -144,5 +188,16 @@ "technologies": ["C++", "QT", "Raspberry Pi", "Linux"] } ] + }, + "github": { + "title": "GitHub Stats", + "description": "An overview of my activity and contributions on GitHub", + "stars": "Stars", + "forks": "Forks", + "commits": "Commits", + "languages": "Most used languages", + "legend": "Contribution calendar", + "totalCommits": "Total commits", + "activity": "Recent activity" } } \ No newline at end of file diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 9210890..069a8db 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -1,80 +1,125 @@ { - "about.title": "A propos de moi", - "about.description": "Je suis étudiant en 4e année à l'ISEN Nantes en alternance chez Horoquartz. Je suis passionné par l'informatique. J'ai appris à coder en autodidacte et je suis actuellement en train d'apprendre le Rust et le Go. Je suis également passionné par l'électronique et le hardware. Je possède un homelab composé de 3 serveur, un DELL T320, un DELL T330 et un Dell Precision T3610 les 3 sous proxmox.", - "card.title": "Etudiant en 4e année a l'ISEN Nantes en alternance chez Horoquartz", - "projects.title": "Mes projets", - "projects.Front end starter.description": "Mon starter personnel pour projet front end", - "projects.Project C - ISEN CIR 1.description": "Projet de fin de 1ere année à l'ISEN Nantes", - "projects.Projet robot.description": "Projet de robot avec le club Modelec ISEN pour la coupe de france de robotique (Developpement et déploiment sur Raspberry Pi)", - "projects.MercuryCloud.description": "Projet d'herbergeur de serveur de jeu et VPS. Poste de support technique, administrateur des service VPS, Game et web.", - "projects.Projet C++ - ISEN CIR 2.description": "Projet de fin de 4e semestre à l'ISEN Nantes. Création d'un jeu de type Tower Defense en C++ avec la librairie QT6.", - "projects.Github NTFY.description": "Projet de notification pour les releases github et dockerhub qui envoie des notifications sur ntfy, gotify et discord.", - "projects.Projet C++ - ISEN CIPA 3.description": "Projet de jeu de simulation de comportement de banc de poisson en C++ avec la librairie SLD2, avec support du multijoueur.", - "projects.Alternance Horoquartz.description": "Développement d'un système de mise à jour pour les produits Horoquartz.", - "cv.title": "Mon CV", - "cv.path": "/CV-Felix-MARQUET.pdf", - "nav.about": "A propos de moi", - "nav.projects": "Mes projets", - "nav.cv": "Mon CV", - "skills.beginner": "Débutant", - "skills.intermediate": "Intermédiaire", - "skills.expert": "Expert", - "skills.examples.C.title": "Projets C", - "skills.examples.C.description": "Projet de fin de 1ère année à l'ISEN, algorithmes de base, création d'une api REST", - "skills.examples.C++.title": "Projets C++", - "skills.examples.C++.description": "Tower Defense en Qt6, simulation de banc de poissons avec SDL2 avec support du multijoueur, algorithmes de base", - "skills.examples.Admin Système.title": "Administration Système", - "skills.examples.Admin Système.description": "Configuration de 3 serveurs DELL sous Proxmox, virtualisation, maintenance des machines virtuelles, deploiement d'applications dans Azure", - "skills.examples.Python.title": "Projets Python", - "skills.examples.Python.description": "Github NTFY pour les notifications des releases github et dockerhub, projets de cours", - "skills.examples.PHP.title": "Projets PHP", - "skills.examples.PHP.description": "Développements web avec PHP, AJAX, postgreSQL", - "skills.examples.HTML/CSS.title": "Développement Front-end", - "skills.examples.HTML/CSS.description": "Front-end starter personnalisé, projet de base, divers projets web", - "skills.examples.JS/TS.title": "JavaScript/TypeScript", - "skills.examples.JS/TS.description": "Développement React, ce portfolio, api NodeJS avec Express, Création d'une application mobile Studysen avec React Native, projets professionnels", - "skills.examples.Linux.title": "Administration Linux", - "skills.examples.Linux.description": "Configuration de serveurs, administration système, Ansible playbooks", - "skills.examples.Go.title": "Projets Go", - "skills.examples.Go.description": "Initiation au langage, Création d'une api REST avec Fiber, Go Gin et la librairie standard, projets professionnels", - "skills.examples.Docker.title": "Containerisation", - "skills.examples.Docker.description": "Déploiement de conteneurs, configuration Docker Compose, création d'images, déploiement de services en haute disponibilité via Swarm et Kubernetes", - "contact.title": "Contact", - "contact.subtitle": "Une question ? Un projet ? N'hésitez pas à me contacter !", - "contact.info.title": "Informations de contact", - "contact.info.email": "Email", - "contact.info.status": "Statut", - "contact.info.statusValue": "Étudiant ISEN - Alternant chez Horoquartz", - "contact.info.response": "Réponse", - "contact.info.responseValue": "Généralement sous 24h", - "contact.subjects.title": "Sujets d'intérêt", - "contact.subjects.list": [ - "Développement Web", - "Administration Système", - "DevOps", - "Alternance", - "Projets Open Source", - "Homelab", - "Collaboration", - "Stage/Emploi" - ], - "contact.form.title": "Envoyez-moi un message", - "contact.form.name": "Nom complet", - "contact.form.namePlaceholder": "Votre nom", - "contact.form.email": "Email", - "contact.form.emailPlaceholder": "votre.email@exemple.com", - "contact.form.subject": "Sujet", - "contact.form.subjectPlaceholder": "Objet de votre message", - "contact.form.message": "Message", - "contact.form.messagePlaceholder": "Votre message...", - "contact.form.send": "Envoyer le message", - "contact.form.sending": "Envoi en cours...", - "contact.form.success": "Message envoyé avec succès ! Je vous répondrai dans les plus brefs délais.", - "contact.form.error": "Une erreur est survenue. Veuillez réessayer plus tard.", - "skills.examples.Rust.title": "Projets Rust", - "skills.examples.Rust.description": "Apprentissage du langage, création d'une api REST avec Ntex, récriture de l'api github NTFY en Rust", - "skills.examples.React.title": "Développement React", - "skills.examples.React.description": "Ce portfolio, site web de Modelec, application mobile Studysen, projets professionnels", + "about": { + "title": "A propos de moi", + "description": "Je suis étudiant en 4e année à l'ISEN Nantes en alternance chez Horoquartz. Je suis passionné par l'informatique. J'ai appris à coder en autodidacte et je suis actuellement en train d'apprendre le Rust et le Go. Je suis également passionné par l'électronique et le hardware. Je possède un homelab composé de 3 serveur, un DELL T320, un DELL T330 et un Dell Precision T3610 les 3 sous proxmox." + }, + "card": { + "title": "Etudiant en 4e année a l'ISEN Nantes en alternance chez Horoquartz" + }, + "projects": { + "title": "Mes projets", + "Front end starter.description": "Mon starter personnel pour projet front end", + "Project C - ISEN CIR 1.description": "Projet de fin de 1ere année à l'ISEN Nantes", + "Projet robot.description": "Projet de robot avec le club Modelec ISEN pour la coupe de france de robotique (Developpement et déploiment sur Raspberry Pi)", + "MercuryCloud.description": "Projet d'herbergeur de serveur de jeu et VPS. Poste de support technique, administrateur des service VPS, Game et web.", + "Projet C++ - ISEN CIR 2.description": "Projet de fin de 4e semestre à l'ISEN Nantes. Création d'un jeu de type Tower Defense en C++ avec la librairie QT6.", + "Github NTFY.description": "Projet de notification pour les releases github et dockerhub qui envoie des notifications sur ntfy, gotify et discord.", + "Projet C++ - ISEN CIPA 3.description": "Projet de jeu de simulation de comportement de banc de poisson en C++ avec la librairie SLD2, avec support du multijoueur.", + "Alternance Horoquartz.description": "Développement d'un système de mise à jour pour les produits Horoquartz." + }, + "cv": { + "title": "Mon CV", + "path": "/CV-Felix-MARQUET.pdf" + }, + "nav": { + "about": "A propos de moi", + "projects": "Mes projets", + "cv": "Mon CV", + "contact": "Contact", + "experience": "Expérience", + "github": "GitHub" + }, + "skills": { + "beginner": "Débutant", + "intermediate": "Intermédiaire", + "expert": "Expert", + "examples": { + "C": { + "title": "Projets C", + "description": "Projet de fin de 1ère année à l'ISEN, algorithmes de base, création d'une api REST" + }, + "C++": { + "title": "Projets C++", + "description": "Tower Defense en Qt6, simulation de banc de poissons avec SDL2 avec support du multijoueur, algorithmes de base" + }, + "Admin Système": { + "title": "Administration Système", + "description": "Configuration de 3 serveurs DELL sous Proxmox, virtualisation, maintenance des machines virtuelles, deploiement d'applications dans Azure" + }, + "Python": { + "title": "Projets Python", + "description": "Github NTFY pour les notifications des releases github et dockerhub, projets de cours" + }, + "PHP": { + "title": "Projets PHP", + "description": "Développements web avec PHP, AJAX, postgreSQL" + }, + "HTML/CSS": { + "title": "Développement Front-end", + "description": "Front-end starter personnalisé, projet de base, divers projets web" + }, + "JS/TS": { + "title": "JavaScript/TypeScript", + "description": "Développement React, ce portfolio, api NodeJS avec Express, Création d'une application mobile Studysen avec React Native, projets professionnels" + }, + "Linux": { + "title": "Administration Linux", + "description": "Configuration de serveurs, administration système, Ansible playbooks" + }, + "Go": { + "title": "Projets Go", + "description": "Initiation au langage, Création d'une api REST avec Fiber, Go Gin et la librairie standard, projets professionnels" + }, + "Docker": { + "title": "Containerisation", + "description": "Déploiement de conteneurs, configuration Docker Compose, création d'images, déploiement de services en haute disponibilité via Swarm et Kubernetes" + }, + "Rust": { + "title": "Projets Rust", + "description": "Apprentissage du langage, création d'une api REST avec Ntex, récriture de l'api github NTFY en Rust" + }, + "React": { + "title": "Développement React", + "description": "Ce portfolio, site web de Modelec, application mobile Studysen, projets professionnels" + } + } + }, + "contact": { + "title": "Contact", + "subtitle": "Une question ? Un projet ? N'hésitez pas à me contacter !", + "info": { + "title": "Informations de contact", + "email": "Email", + "status": "Statut", + "statusValue": "Étudiant ISEN - Alternant chez Horoquartz", + "response": "Réponse", + "responseValue": "Généralement sous 24h" + }, + "subjects.title": "Sujets d'intérêt", + "subjects.list": [ + "Développement Web", + "Administration Système", + "DevOps", + "Alternance", + "Projets Open Source", + "Homelab", + "Collaboration", + "Stage/Emploi" + ], + "form.title": "Envoyez-moi un message", + "form.name": "Nom complet", + "form.namePlaceholder": "Votre nom", + "form.email": "Email", + "form.emailPlaceholder": "votre.email@exemple.com", + "form.subject": "Sujet", + "form.subjectPlaceholder": "Objet de votre message", + "form.message": "Message", + "form.messagePlaceholder": "Votre message...", + "form.send": "Envoyer le message", + "form.sending": "Envoi en cours...", + "form.success": "Message envoyé avec succès ! Je vous répondrai dans les plus brefs délais.", + "form.error": "Une erreur est survenue. Veuillez réessayer plus tard." + }, "experience": { "title": "Parcours & Expérience", "description": "Mon évolution professionnelle et académique", @@ -143,5 +188,16 @@ "technologies": ["C++", "QT", "Raspberry Pi", "Linux"] } ] + }, + "github": { + "title": "Statistiques GitHub", + "description": "Un aperçu de mon activité et de mes contributions sur GitHub", + "stars": "Étoiles", + "forks": "Forks", + "commits": "Commits", + "languages": "Langages les plus utilisés", + "legend": "Calendrier des contributions", + "totalCommits": "Total des commits", + "activity": "Activité récente" } } \ No newline at end of file diff --git a/src/App.jsx b/src/App.tsx similarity index 81% rename from src/App.jsx rename to src/App.tsx index 32a22e4..a895702 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -3,21 +3,22 @@ import { FaSun, FaMoon} from "react-icons/fa"; import ReactDOM from "react-dom"; import './output.css'; import './other.css'; -import Card from "./components/Card.tsx"; -import About from "./components/About.tsx"; -import Skills from "./components/Skills.tsx"; -import Project from "./components/Project.tsx"; -import Footer from "./components/Footer.tsx"; -import CV from "./components/CV.tsx"; -import Menu from "./components/Menu.tsx"; -import LoadingScreen from "./components/LoadingScreen.tsx"; -import ParticlesBackground from "./components/ParticlesBackground.tsx"; -import ContactSection from "./components/ContactSection.tsx"; -import TimelineSection from "./components/TimelineSection.tsx"; -import data from "./assets/DATA.ts"; +import Card from "./components/Card"; +import About from "./components/About"; +import Skills from "./components/Skills"; +import Project from "./components/Project"; +import Footer from "./components/Footer"; +import CV from "./components/CV"; +import Menu from "./components/Menu"; +import LoadingScreen from "./components/LoadingScreen"; +import ParticlesBackground from "./components/ParticlesBackground"; +import ContactSection from "./components/ContactSection"; +import TimelineSection from "./components/TimelineSection"; +import data from "./assets/DATA"; import { useTranslation } from "react-i18next"; import i18n from './i18n.js'; import {createRoot} from "react-dom/client"; +import GitHubStatsSection from 'components/GitHubStatsSection'; function App() { // Initialise le thème depuis localStorage ou détecte le thème système @@ -78,7 +79,12 @@ function App() {
- + ({ + ...item, + type: item.type as "work" | "education" | "achievement" + }))} /> +
+

@@ -99,8 +105,10 @@ function App() { } const container = document.getElementById('root'); -const root = createRoot(container); -root.render(); +if (container) { + const root = createRoot(container); + root.render(); +} // ReactDOM.render(, document.getElementById('root')); export default App; \ No newline at end of file diff --git a/src/components/Card.tsx b/src/components/Card.tsx index f884a62..a71aed8 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -12,6 +12,7 @@ interface Social { interface CardProps { name: string; + title: string; social: Social; } diff --git a/src/components/GitHubStatsSection.tsx b/src/components/GitHubStatsSection.tsx new file mode 100644 index 0000000..cf866aa --- /dev/null +++ b/src/components/GitHubStatsSection.tsx @@ -0,0 +1,234 @@ +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaGithub, FaStar, FaCodeBranch, FaCode, FaCalendarAlt } from 'react-icons/fa'; + +interface GitHubStats { + totalRepos: number; + totalStars: number; + totalForks: number; + totalCommits: number; + languages: { [key: string]: number }; + contributions: Array<{ date: string; count: number }>; +} + +const GitHubStatsSection: React.FC = () => { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const { t } = useTranslation(); + + + useEffect(() => { + const fetchStats = async () => { + try { + const username = "BreizhHardware"; // à adapter si besoin + const token = import.meta.env.VITE_GITHUB_TOKEN; + // Infos utilisateur REST + const userRes = await fetch(`https://api.github.com/users/${username}`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github.v3+json", + }, + }); + const userData = await userRes.json(); + + // Repos pour stars/forks REST + const reposRes = await fetch(`https://api.github.com/users/${username}/repos?per_page=100`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: "application/vnd.github.v3+json", + }, + }); + const reposData = await reposRes.json(); + + // Calculs REST + const totalStars = reposData.reduce((acc: number, repo: any) => acc + repo.stargazers_count, 0); + const totalForks = reposData.reduce((acc: number, repo: any) => acc + repo.forks_count, 0); + // Langages + const langCount: { [key: string]: number } = {}; + reposData.forEach((repo: any) => { + if (repo.language) { + langCount[repo.language] = (langCount[repo.language] || 0) + 1; + } + }); + const totalRepos = userData.public_repos; + + // GraphQL pour contributions + const today = new Date(); + const lastYear = new Date(today); + lastYear.setFullYear(today.getFullYear() - 1); + const fromDate = lastYear.toISOString(); // format complet ISO 8601 + const toDate = today.toISOString(); + const graphQLQuery = { + query: ` + query { + user(login: "${username}") { + contributionsCollection(from: \"${fromDate}\", to: \"${toDate}\") { + contributionCalendar { + totalContributions + weeks { + contributionDays { + date + contributionCount + } + } + } + } + } + } + ` + }; + const graphQLRes = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(graphQLQuery), + }); + const graphQLData = await graphQLRes.json(); + const calendar = graphQLData.data.user.contributionsCollection.contributionCalendar; + const totalCommits = calendar.totalContributions; + // Flatten days + const contributions: Array<{ date: string; count: number }> = []; + calendar.weeks.forEach((week: any) => { + week.contributionDays.forEach((day: any) => { + contributions.push({ date: day.date, count: day.contributionCount }); + }); + }); + + setStats({ + totalRepos, + totalStars, + totalForks, + totalCommits, + languages: langCount, + contributions, + }); + } catch (e) { + setStats(null); + } + setLoading(false); + }; + fetchStats(); + }, []); + + const topLanguages = stats ? Object.entries(stats.languages).sort((a, b) => b[1] - a[1]).slice(0, 5) : []; + + if (loading) { + return ( +
+
+
+
+
+ {[1, 2, 3, 4].map((i) => ( +
+ ))} +
+
+
+
+ ); + } + + return ( +
+
+

+ {t('github.title')} +

+

+ {t('github.description')} +

+
+ + {stats && ( +
+
+
+ +

{stats.totalRepos}

+

{t('github.repositories')}

+
+ +
+ +

{stats.totalStars}

+

{t('github.stars')}

+
+ +
+ +

{stats.totalForks}

+

{t('github.forks')}

+
+ +
+ +

{stats.totalCommits !== -1 ? stats.totalCommits : "N/A"}

+

{t('github.commits')}

+
+
+ +
+

+ {t('github.languages')} +

+
+ {topLanguages.map(([language, percentage], index) => ( +
+
+ {language} + {percentage}% +
+
+
+
+
+ ))} +
+
+ +
+

+ {t('github.activity')} +

+ {stats.contributions && ( +
+ {stats.contributions.map((day, i) => { + let color = 'bg-gray-200 dark:bg-gray-700'; + if (day.count > 10) color = 'bg-green-500'; + else if (day.count > 5) color = 'bg-green-400'; + else if (day.count > 2) color = 'bg-green-300'; + else if (day.count > 0) color = 'bg-green-200'; + return ( +
1 ? 's' : ''}`} + /> + ); + })} +
+ )} +
+ + {t('github.legend')}
+ {t('github.totalCommits')} : {stats.totalCommits} +
+
+
+
+ )} +
+ ); +}; + +export default GitHubStatsSection; \ No newline at end of file diff --git a/src/components/HomelabSection.tsx b/src/components/HomelabSection.tsx deleted file mode 100644 index 5a3267e..0000000 --- a/src/components/HomelabSection.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import React, { useState } from 'react'; -import { FaServer, FaDocker, FaLinux, FaNetworkWired, FaHdd, FaMicrochip } from 'react-icons/fa'; -import { useTranslation } from 'react-i18next'; - -interface ServerSpec { - name: string; - model: string; - cpu: string; - ram: string; - storage: string; - os: string; - services: string[]; - status: 'online' | 'offline' | 'maintenance'; -} - -const HomelabSection: React.FC = () => { - const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState<'overview' | 'servers' | 'services'>('overview'); - - const servers: ServerSpec[] = [ - { - name: "DELL T320", - model: "PowerEdge T320", - cpu: "Intel Xeon E5-2400 series", - ram: "32GB DDR3", - storage: "4x 1TB HDD RAID 10", - os: "Proxmox VE", - services: ["Proxmox", "Docker", "VM Management", "Backup Server"], - status: 'online' - }, - { - name: "DELL T330", - model: "PowerEdge T330", - cpu: "Intel Xeon E3-1200 series", - ram: "64GB DDR4", - storage: "6x 2TB HDD + 2x 500GB SSD", - os: "Proxmox VE", - services: ["Media Server", "Development Environment", "CI/CD", "Monitoring"], - status: 'online' - } - ]; - - const services = [ - { name: "Proxmox Cluster", icon: , description: "Virtualisation et orchestration" }, - { name: "Docker Swarm", icon: , description: "Conteneurisation des services" }, - { name: "Monitoring Stack", icon: , description: "Grafana + Prometheus + Alertmanager" }, - { name: "Backup Solution", icon: , description: "Sauvegarde automatisée 3-2-1" }, - { name: "Development Lab", icon: , description: "Environnements de développement isolés" }, - { name: "Media Center", icon: , description: "Streaming et gestion multimédia" } - ]; - - const getStatusColor = (status: string) => { - switch (status) { - case 'online': return 'bg-green-500'; - case 'offline': return 'bg-red-500'; - case 'maintenance': return 'bg-yellow-500'; - default: return 'bg-gray-500'; - } - }; - - const getStatusText = (status: string) => { - switch (status) { - case 'online': return 'En ligne'; - case 'offline': return 'Hors ligne'; - case 'maintenance': return 'Maintenance'; - default: return 'Inconnu'; - } - }; - - return ( -
-
-

- 🏠 Mon Homelab -

-

- Infrastructure personnelle et laboratoire de développement -

-
- - {/* Navigation tabs */} -
-
- {['overview', 'servers', 'services'].map((tab) => ( - - ))} -
-
- - {/* Content based on active tab */} -
- {activeTab === 'overview' && ( -
-
-
-
-

2

-

Serveurs

-
- -
-
- -
-
-
-

96GB

-

RAM Total

-
- -
-
- -
-
-
-

16TB

-

Stockage

-
- -
-
- -
-
-
-

24/7

-

Uptime

-
- -
-
-
- )} - - {activeTab === 'servers' && ( -
- {servers.map((server, index) => ( -
-
-
-
- -
-

{server.name}

-

{server.model}

-
-
-
-
- - {getStatusText(server.status)} - -
-
- -
-
-

CPU

-

{server.cpu}

-
-
-

RAM

-

{server.ram}

-
-
-

Stockage

-

{server.storage}

-
-
- -
-

Services

-
- {server.services.map((service, idx) => ( - - {service} - - ))} -
-
-
-
- ))} -
- )} - - {activeTab === 'services' && ( -
- {services.map((service, index) => ( -
-
-
{service.icon}
-

{service.name}

-
-

{service.description}

-
- ))} -
- )} -
-
- ); -}; - -export default HomelabSection; diff --git a/src/components/LoadingScreen.tsx b/src/components/LoadingScreen.tsx index 066de3b..bd48eb0 100644 --- a/src/components/LoadingScreen.tsx +++ b/src/components/LoadingScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; interface LoadingScreenProps { onLoadingComplete: () => void; @@ -9,24 +9,162 @@ const LoadingScreen: React.FC = ({ onLoadingComplete }) => { const [showCursor, setShowCursor] = useState(true); const fullText = "Félix MARQUET"; const [isComplete, setIsComplete] = useState(false); + const [assetsLoaded, setAssetsLoaded] = useState(false); + const [loadingProgress, setLoadingProgress] = useState(0); + const [currentAsset, setCurrentAsset] = useState('Initialisation...'); + + const typingRef = useRef<{ + currentIndex: number; + timeout: NodeJS.Timeout | null; + isTyping: boolean; + }>({ + currentIndex: 0, + timeout: null, + isTyping: false + }); useEffect(() => { - let currentIndex = 0; - const typingInterval = setInterval(() => { - if (currentIndex <= fullText.length) { - setDisplayText(fullText.substring(0, currentIndex)); - currentIndex++; + let loadedCount = 0; + let totalCount = 0; + + const updateProgress = () => { + const progress = totalCount > 0 ? (loadedCount / totalCount) * 100 : 0; + setLoadingProgress(progress); + + if (loadedCount === totalCount && totalCount > 0) { + setAssetsLoaded(true); + setCurrentAsset('Prêt !'); + } + }; + + const incrementLoaded = (assetName?: string) => { + loadedCount++; + if (assetName) { + setCurrentAsset(`Chargé: ${assetName}`); + } + updateProgress(); + }; + + const checkAssets = async () => { + const assetsToCheck = [ + { url: 'https://1.gravatar.com/avatar/4d43af207280d1d23e2a2905577c7b6167723fec2d33f946cc86f114c1a85b8d?size=256', name: 'Photo de profil' }, + { url: '/V5.png', name: 'Favicon' }, + { url: '/CV-Felix-MARQUET.pdf', name: 'CV Français' }, + { url: '/CV-Felix-MARQUET-English.pdf', name: 'CV Anglais' }, + { url: '/locales/fr/translation.json', name: 'Traductions FR' }, + { url: '/locales/en/translation.json', name: 'Traductions EN' } + ]; + + totalCount = assetsToCheck.length + 1; + + setCurrentAsset('Chargement des polices...'); + try { + await document.fonts.ready; + incrementLoaded('Polices'); + } catch (error) { + incrementLoaded('Polices'); + } + + for (const asset of assetsToCheck) { + setCurrentAsset(`Chargement: ${asset.name}...`); + + if (asset.url.endsWith('.json')) { + try { + const response = await fetch(asset.url); + if (response.ok) { + incrementLoaded(asset.name); + } else { + incrementLoaded(asset.name); + } + } catch (error) { + incrementLoaded(asset.name); + } + } else if (asset.url.endsWith('.pdf')) { + try { + const response = await fetch(asset.url, { method: 'HEAD' }); + incrementLoaded(asset.name); + } catch (error) { + incrementLoaded(asset.name); + } + } else { + await new Promise((resolve) => { + const img = new Image(); + img.onload = () => { + incrementLoaded(asset.name); + resolve(); + }; + img.onerror = () => { + incrementLoaded(asset.name); + resolve(); + }; + img.src = asset.url; + }); + } + } + + setTimeout(() => { + if (!assetsLoaded) { + loadedCount = totalCount; + setAssetsLoaded(true); + setLoadingProgress(100); + setCurrentAsset('Prêt !'); + } + }, 5000); + }; + + const timeout = setTimeout(() => { + checkAssets(); + }, 100); + + return () => clearTimeout(timeout); + }, [assetsLoaded]); + + const startTyping = () => { + if (typingRef.current.isTyping) return; + + typingRef.current.isTyping = true; + + const typeNextChar = () => { + if (typingRef.current.currentIndex <= fullText.length) { + setDisplayText(fullText.substring(0, typingRef.current.currentIndex)); + typingRef.current.currentIndex++; + + const delay = assetsLoaded ? 30 : 150; + typingRef.current.timeout = setTimeout(typeNextChar, delay); } else { - clearInterval(typingInterval); setIsComplete(true); + typingRef.current.isTyping = false; + + const finalDelay = assetsLoaded ? 200 : 800; setTimeout(() => { onLoadingComplete(); - }, 1000); + }, finalDelay); } - }, 150); + }; + + typeNextChar(); + }; - return () => clearInterval(typingInterval); - }, [fullText, onLoadingComplete]); + useEffect(() => { + startTyping(); + + return () => { + if (typingRef.current.timeout) { + clearTimeout(typingRef.current.timeout); + } + }; + }, []); + + useEffect(() => { + if (assetsLoaded && typingRef.current.isTyping && !isComplete) { + if (typingRef.current.timeout) { + clearTimeout(typingRef.current.timeout); + } + + typingRef.current.isTyping = false; + setTimeout(() => startTyping(), 10); + } + }, [assetsLoaded, isComplete]); useEffect(() => { const cursorInterval = setInterval(() => { @@ -43,12 +181,15 @@ const LoadingScreen: React.FC = ({ onLoadingComplete }) => { {displayText} {showCursor && !isComplete && |}
-
+
-

Chargement du portfolio...

+ +

+ {assetsLoaded ? 'Prêt !' : currentAsset} +

); diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index 66134d7..3c0567d 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -9,9 +9,10 @@ function Menu() { diff --git a/src/output.css b/src/output.css index fec83dd..320a62a 100644 --- a/src/output.css +++ b/src/output.css @@ -17,6 +17,7 @@ --color-green-50: oklch(98.2% 0.018 155.826); --color-green-100: oklch(96.2% 0.044 156.743); --color-green-200: oklch(92.5% 0.084 155.995); + --color-green-300: oklch(87.1% 0.15 154.449); --color-green-500: oklch(72.3% 0.219 149.579); --color-green-800: oklch(44.8% 0.119 151.328); --color-green-900: oklch(39.3% 0.095 152.535); @@ -46,6 +47,7 @@ --color-white: #fff; --spacing: 0.25rem; --container-xs: 20rem; + --container-3xl: 48rem; --container-4xl: 56rem; --container-6xl: 72rem; --text-xs: 0.75rem; @@ -71,9 +73,11 @@ --font-weight-bold: 700; --font-weight-extrabold: 800; --leading-relaxed: 1.625; + --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; + --ease-out: cubic-bezier(0, 0, 0.2, 1); --animate-spin: spin 1s linear infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-bounce: bounce 1s infinite; @@ -397,9 +401,18 @@ .h-6 { height: calc(var(--spacing) * 6); } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-20 { + height: calc(var(--spacing) * 20); + } .h-64 { height: calc(var(--spacing) * 64); } + .h-full { + height: 100%; + } .min-h-screen { min-height: 100vh; } @@ -409,6 +422,9 @@ .w-1 { width: calc(var(--spacing) * 1); } + .w-1\/3 { + width: calc(1/3 * 100%); + } .w-3 { width: calc(var(--spacing) * 3); } @@ -430,12 +446,12 @@ .w-full { width: 100%; } + .max-w-3xl { + max-width: var(--container-3xl); + } .max-w-4xl { max-width: var(--container-4xl); } - .max-w-6xl { - max-width: var(--container-6xl); - } .max-w-xs { max-width: var(--container-xs); } @@ -470,6 +486,12 @@ .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } + .grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } .flex-col { flex-direction: column; } @@ -500,6 +522,9 @@ .gap-2 { gap: calc(var(--spacing) * 2); } + .gap-3 { + gap: calc(var(--spacing) * 3); + } .gap-4 { gap: calc(var(--spacing) * 4); } @@ -526,13 +551,6 @@ margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); } } - .space-y-6 { - :where(& > :not(:last-child)) { - --tw-space-y-reverse: 0; - margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse)); - margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse))); - } - } .space-y-8 { :where(& > :not(:last-child)) { --tw-space-y-reverse: 0; @@ -571,6 +589,9 @@ .overflow-hidden { overflow: hidden; } + .rounded { + border-radius: 0.25rem; + } .rounded-full { border-radius: calc(infinity * 1px); } @@ -580,6 +601,9 @@ .rounded-md { border-radius: var(--radius-md); } + .rounded-sm { + border-radius: var(--radius-sm); + } .rounded-xl { border-radius: var(--radius-xl); } @@ -642,9 +666,6 @@ .bg-gray-400 { background-color: var(--color-gray-400); } - .bg-gray-500 { - background-color: var(--color-gray-500); - } .bg-gray-600 { background-color: var(--color-gray-600); } @@ -654,6 +675,12 @@ .bg-green-100 { background-color: var(--color-green-100); } + .bg-green-200 { + background-color: var(--color-green-200); + } + .bg-green-300 { + background-color: var(--color-green-300); + } .bg-green-500 { background-color: var(--color-green-500); } @@ -663,19 +690,21 @@ .bg-red-100 { background-color: var(--color-red-100); } - .bg-red-500 { - background-color: var(--color-red-500); - } .bg-white { background-color: var(--color-white); } - .bg-yellow-500 { - background-color: var(--color-yellow-500); - } .bg-gradient-to-br { --tw-gradient-position: to bottom right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } + .bg-gradient-to-r { + --tw-gradient-position: to right in oklab; + background-image: linear-gradient(var(--tw-gradient-stops)); + } + .from-blue-500 { + --tw-gradient-from: var(--color-blue-500); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } .from-blue-900 { --tw-gradient-from: var(--color-blue-900); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); @@ -689,6 +718,10 @@ --tw-gradient-to: var(--color-indigo-900); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } + .to-purple-500 { + --tw-gradient-to: var(--color-purple-500); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } .p-1 { padding: calc(var(--spacing) * 1); } @@ -707,6 +740,9 @@ .p-6 { padding: calc(var(--spacing) * 6); } + .p-8 { + padding: calc(var(--spacing) * 8); + } .px-2 { padding-inline: calc(var(--spacing) * 2); } @@ -719,9 +755,6 @@ .px-5 { padding-inline: calc(var(--spacing) * 5); } - .px-6 { - padding-inline: calc(var(--spacing) * 6); - } .py-1 { padding-block: calc(var(--spacing) * 1); } @@ -834,9 +867,6 @@ .text-green-800 { color: var(--color-green-800); } - .text-orange-500 { - color: var(--color-orange-500); - } .text-purple-500 { color: var(--color-purple-500); } @@ -855,6 +885,9 @@ color: color-mix(in oklab, var(--color-white) 80%, transparent); } } + .text-yellow-500 { + color: var(--color-yellow-500); + } .opacity-0 { opacity: 0%; } @@ -866,10 +899,6 @@ --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .shadow-sm { - --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } .shadow-xl { --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -908,6 +937,10 @@ --tw-duration: 1000ms; transition-duration: 1000ms; } + .ease-out { + --tw-ease: var(--ease-out); + transition-timing-function: var(--ease-out); + } .hover\:bg-blue-700 { &:hover { @media (hover: hover) { @@ -929,13 +962,6 @@ } } } - .hover\:text-gray-800 { - &:hover { - @media (hover: hover) { - color: var(--color-gray-800); - } - } - } .hover\:text-white { &:hover { @media (hover: hover) { @@ -1033,14 +1059,9 @@ width: calc(3/4 * 100%); } } - .md\:grid-cols-2 { + .md\:grid-cols-4 { @media (width >= 48rem) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - } - .md\:grid-cols-3 { - @media (width >= 48rem) { - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); } } .md\:flex-row { @@ -1090,16 +1111,6 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } } - .lg\:grid-cols-3 { - @media (width >= 64rem) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - } - .lg\:grid-cols-4 { - @media (width >= 64rem) { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - } .dark\:border-gray-600 { &:where(.dark, .dark *) { border-color: var(--color-gray-600); @@ -1238,15 +1249,6 @@ } } } - .dark\:hover\:text-gray-200 { - &:where(.dark, .dark *) { - &:hover { - @media (hover: hover) { - color: var(--color-gray-200); - } - } - } - } .dark\:hover\:text-gray-800 { &:where(.dark, .dark *) { &:hover { @@ -1442,6 +1444,10 @@ syntax: "*"; inherits: false; } +@property --tw-ease { + syntax: "*"; + inherits: false; +} @property --tw-scale-x { syntax: "*"; inherits: false; @@ -1541,6 +1547,7 @@ --tw-backdrop-saturate: initial; --tw-backdrop-sepia: initial; --tw-duration: initial; + --tw-ease: initial; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scale-z: 1;