mirror of
https://github.com/BreizhHardware/portfolio.git
synced 2026-01-18 16:37:22 +01:00
feat: Add GitHubStatsSection component for displaying GitHub statistics
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="/V5.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="generator" content="React" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
@@ -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)
|
||||
|
||||
BIN
public/V5.png
Normal file
BIN
public/V5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
<About />
|
||||
<Skills skills={data.skills} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="experience" />
|
||||
<TimelineSection experience={data.experience} />
|
||||
<TimelineSection experience={data.experience.map(item => ({
|
||||
...item,
|
||||
type: item.type as "work" | "education" | "achievement"
|
||||
}))} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="github" />
|
||||
<GitHubStatsSection />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="projects" />
|
||||
<Project projects={data.projects} />
|
||||
<hr className="text-gray-800 dark:text-gray-200 mt-4" id="contact" />
|
||||
@@ -99,8 +105,10 @@ function App() {
|
||||
}
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
if (container) {
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
}
|
||||
// ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
export default App;
|
||||
@@ -12,6 +12,7 @@ interface Social {
|
||||
|
||||
interface CardProps {
|
||||
name: string;
|
||||
title: string;
|
||||
social: Social;
|
||||
}
|
||||
|
||||
|
||||
234
src/components/GitHubStatsSection.tsx
Normal file
234
src/components/GitHubStatsSection.tsx
Normal file
@@ -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<GitHubStats | null>(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 (
|
||||
<div className="max-w-4xl mx-auto mt-16">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-lg">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-8 bg-gray-300 dark:bg-gray-600 rounded w-1/3 mx-auto mb-6"></div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="h-20 bg-gray-300 dark:bg-gray-600 rounded"></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto mt-16">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-800 dark:text-gray-200 mb-4 flex items-center justify-center gap-3">
|
||||
{t('github.title')}
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400">
|
||||
{t('github.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{stats && (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover text-center">
|
||||
<FaCode className="text-3xl text-blue-500 mx-auto mb-3" />
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">{stats.totalRepos}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{t('github.repositories')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover text-center">
|
||||
<FaStar className="text-3xl text-yellow-500 mx-auto mb-3" />
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">{stats.totalStars}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{t('github.stars')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover text-center">
|
||||
<FaCodeBranch className="text-3xl text-green-500 mx-auto mb-3" />
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">{stats.totalForks}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{t('github.forks')}</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover text-center">
|
||||
<FaCalendarAlt className="text-3xl text-purple-500 mx-auto mb-3" />
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">{stats.totalCommits !== -1 ? stats.totalCommits : "N/A"}</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{t('github.commits')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<h3 className="text-xl font-bold text-gray-800 dark:text-gray-200 mb-6 text-center">
|
||||
{t('github.languages')}
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{topLanguages.map(([language, percentage], index) => (
|
||||
<div key={language} className="animate-slideInLeft" style={{ animationDelay: `${index * 100}ms` }}>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">{language}</span>
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">{percentage}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3 overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-blue-500 to-purple-500 rounded-full progress-fill transition-all duration-1000 ease-out"
|
||||
style={{
|
||||
width: `${percentage}%`,
|
||||
animationDelay: `${index * 200}ms`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<h3 className="text-xl font-bold text-gray-800 dark:text-gray-200 mb-6 text-center">
|
||||
{t('github.activity')}
|
||||
</h3>
|
||||
{stats.contributions && (
|
||||
<div className="grid grid-cols-12 gap-1 max-w-3xl mx-auto">
|
||||
{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 (
|
||||
<div
|
||||
key={day.date}
|
||||
className={`w-3 h-3 rounded-sm ${color} animate-fadeInUp`}
|
||||
style={{ animationDelay: `${i * 2}ms` }}
|
||||
title={`${day.date}: ${day.count} commit${day.count > 1 ? 's' : ''}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center mt-4">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{t('github.legend')}<br/>
|
||||
{t('github.totalCommits')} : {stats.totalCommits}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GitHubStatsSection;
|
||||
@@ -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: <FaServer />, description: "Virtualisation et orchestration" },
|
||||
{ name: "Docker Swarm", icon: <FaDocker />, description: "Conteneurisation des services" },
|
||||
{ name: "Monitoring Stack", icon: <FaNetworkWired />, description: "Grafana + Prometheus + Alertmanager" },
|
||||
{ name: "Backup Solution", icon: <FaHdd />, description: "Sauvegarde automatisée 3-2-1" },
|
||||
{ name: "Development Lab", icon: <FaMicrochip />, description: "Environnements de développement isolés" },
|
||||
{ name: "Media Center", icon: <FaLinux />, 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 (
|
||||
<div className="max-w-6xl mx-auto mt-16">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-800 dark:text-gray-200 mb-4">
|
||||
🏠 Mon Homelab
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400">
|
||||
Infrastructure personnelle et laboratoire de développement
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Navigation tabs */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="bg-gray-200 dark:bg-gray-700 rounded-lg p-1">
|
||||
{['overview', 'servers', 'services'].map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab as any)}
|
||||
className={`px-6 py-2 rounded-md text-sm font-medium transition-all ${
|
||||
activeTab === tab
|
||||
? 'bg-white dark:bg-gray-600 text-gray-800 dark:text-gray-200 shadow-sm'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
{tab === 'overview' ? 'Vue d\'ensemble' : tab === 'servers' ? 'Serveurs' : 'Services'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content based on active tab */}
|
||||
<div className="animate-fadeInUp">
|
||||
{activeTab === 'overview' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">2</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Serveurs</p>
|
||||
</div>
|
||||
<FaServer className="text-blue-500 text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">96GB</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">RAM Total</p>
|
||||
</div>
|
||||
<FaMicrochip className="text-green-500 text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">16TB</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Stockage</p>
|
||||
</div>
|
||||
<FaHdd className="text-purple-500 text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-800 dark:text-gray-200">24/7</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">Uptime</p>
|
||||
</div>
|
||||
<FaNetworkWired className="text-orange-500 text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'servers' && (
|
||||
<div className="space-y-6">
|
||||
{servers.map((server, index) => (
|
||||
<div key={index} className="bg-white dark:bg-gray-800 rounded-lg shadow-lg card-hover overflow-hidden">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<FaServer className="text-2xl text-blue-500" />
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-gray-800 dark:text-gray-200">{server.name}</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{server.model}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(server.status)}`}></div>
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
{getStatusText(server.status)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">CPU</p>
|
||||
<p className="text-sm text-gray-800 dark:text-gray-200">{server.cpu}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">RAM</p>
|
||||
<p className="text-sm text-gray-800 dark:text-gray-200">{server.ram}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400">Stockage</p>
|
||||
<p className="text-sm text-gray-800 dark:text-gray-200">{server.storage}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">Services</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{server.services.map((service, idx) => (
|
||||
<span key={idx} className="px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full">
|
||||
{service}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'services' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{services.map((service, index) => (
|
||||
<div key={index} className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-lg card-hover">
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<div className="text-2xl text-blue-500">{service.icon}</div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200">{service.name}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{service.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomelabSection;
|
||||
@@ -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<LoadingScreenProps> = ({ 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<void>((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);
|
||||
};
|
||||
|
||||
return () => clearInterval(typingInterval);
|
||||
}, [fullText, onLoadingComplete]);
|
||||
typeNextChar();
|
||||
};
|
||||
|
||||
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<LoadingScreenProps> = ({ onLoadingComplete }) => {
|
||||
{displayText}
|
||||
{showCursor && !isComplete && <span className="animate-pulse">|</span>}
|
||||
</div>
|
||||
<div className="flex space-x-2 justify-center">
|
||||
<div className="flex space-x-2 justify-center mb-4">
|
||||
<div className="w-3 h-3 bg-white rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div>
|
||||
<div className="w-3 h-3 bg-white rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div>
|
||||
<div className="w-3 h-3 bg-white rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div>
|
||||
</div>
|
||||
<p className="text-white/80 mt-4 text-lg">Chargement du portfolio...</p>
|
||||
|
||||
<p className="text-white/80 text-lg">
|
||||
{assetsLoaded ? 'Prêt !' : currentAsset}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,9 +9,10 @@ function Menu() {
|
||||
<ul className="flex justify-around">
|
||||
<li><a href="#top" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">Félix MARQUET</a></li>
|
||||
<li><a href="#about" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.about')}</a></li>
|
||||
<li><a href="#experience" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">Expérience</a></li>
|
||||
<li><a href="#experience" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.experience')}</a></li>
|
||||
<li><a href="#github" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.github')}</a></li>
|
||||
<li><a href="#projects" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.projects')}</a></li>
|
||||
<li><a href="#contact" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">Contact</a></li>
|
||||
<li><a href="#contact" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.contact')}</a></li>
|
||||
<li><a href="#cv" className="text-gray-800 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 transition-colors">{t('nav.cv')}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
131
src/output.css
131
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;
|
||||
|
||||
Reference in New Issue
Block a user