first commit
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,16 @@
|
||||
# React + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
@@ -0,0 +1,29 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
},
|
||||
},
|
||||
])
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Portfolio</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2671
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "portfolio",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-router-dom": "^7.14.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.4.0",
|
||||
"vite": "^8.0.4"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
+184
@@ -0,0 +1,184 @@
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
||||
import { SiteLayout } from './layouts/SiteLayout.jsx'
|
||||
import { Home } from './pages/Home.jsx'
|
||||
import { Profil } from './pages/Profil.jsx'
|
||||
import { Formations } from './pages/Formations.jsx'
|
||||
import { Projets } from './pages/Projets.jsx'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<SiteLayout />}>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/profil" element={<Profil />} />
|
||||
<Route path="/formations" element={<Formations />} />
|
||||
<Route path="/projets" element={<Projets />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1,52 @@
|
||||
export function ContentStack({ panel1, panel1b, panel2, panel3 }) {
|
||||
return (
|
||||
<div className="content-stack" aria-label="Contenu principal">
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel1 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label="Bloc 1"
|
||||
>
|
||||
{panel1}
|
||||
</section>
|
||||
{panel1b != null ? (
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
'content-stack__panel--padded',
|
||||
].join(' ')}
|
||||
aria-label="Bloc 2"
|
||||
>
|
||||
{panel1b}
|
||||
</section>
|
||||
) : null}
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
panel2 ? 'content-stack__panel--padded' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label={
|
||||
panel3 != null || panel1b != null ? 'Bloc 3' : 'Bloc 2'
|
||||
}
|
||||
>
|
||||
{panel2}
|
||||
</section>
|
||||
{panel3 != null ? (
|
||||
<section
|
||||
className={[
|
||||
'content-stack__panel',
|
||||
'content-stack__panel--padded',
|
||||
].join(' ')}
|
||||
aria-label="Bloc 4"
|
||||
>
|
||||
{panel3}
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Frise centrée : un point par événement, ordre chronologique (plus ancien en haut).
|
||||
* Chaque entrée peut avoir une description optionnelle.
|
||||
*/
|
||||
const EVENTS = [
|
||||
{
|
||||
period: '2018',
|
||||
sequence: 0,
|
||||
side: 'left',
|
||||
title: 'Bac techno STI2D SIN',
|
||||
description: 'Don Bosco',
|
||||
},
|
||||
{
|
||||
period: '2019',
|
||||
sequence: 1,
|
||||
side: 'right',
|
||||
title: 'Formation à l’étranger en anglais',
|
||||
description: '1 mois — Canada',
|
||||
},
|
||||
{
|
||||
period: '2020-2021',
|
||||
sequence: 0,
|
||||
side: 'left',
|
||||
title: 'CAP Mécanique auto',
|
||||
description: 'AF13',
|
||||
},
|
||||
{
|
||||
period: '2021-2022',
|
||||
sequence: 0,
|
||||
side: 'right',
|
||||
title: 'CAP Plomberie chauffagerie',
|
||||
},
|
||||
{
|
||||
period: '2022-2025',
|
||||
sequence: 0,
|
||||
side: 'left',
|
||||
title: 'Administrateur système\nDéveloppeur application',
|
||||
description: 'OpenMotion',
|
||||
},
|
||||
{
|
||||
period: '2022-2026',
|
||||
sequence: 0,
|
||||
side: 'right',
|
||||
title: 'Installation et configuration de serveurs personnels',
|
||||
description: ' de projets personnels et professionnels.',
|
||||
},
|
||||
]
|
||||
|
||||
function parsePeriod(period) {
|
||||
const m = String(period).match(/^(\d{4})(?:-(\d{4}))?$/)
|
||||
if (!m) return { start: 0, end: 0 }
|
||||
const start = Number.parseInt(m[1], 10)
|
||||
const end = m[2] ? Number.parseInt(m[2], 10) : start
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
function compareChronological(a, b) {
|
||||
const pa = parsePeriod(a.period)
|
||||
const pb = parsePeriod(b.period)
|
||||
if (pa.start !== pb.start) return pa.start - pb.start
|
||||
if (pa.end !== pb.end) return pa.end - pb.end
|
||||
return (a.sequence ?? 0) - (b.sequence ?? 0)
|
||||
}
|
||||
|
||||
const SORTED_EVENTS = [...EVENTS].sort(compareChronological)
|
||||
|
||||
function TimelineCard({ period, title, description, side }) {
|
||||
return (
|
||||
<article
|
||||
className={`formations-timeline__card formations-timeline__card--${side}`}
|
||||
>
|
||||
<time className="formations-timeline__period" dateTime={period}>
|
||||
{period}
|
||||
</time>
|
||||
<h3 className="formations-timeline__card-title">{title}</h3>
|
||||
{description ? (
|
||||
<p className="formations-timeline__desc">{description}</p>
|
||||
) : null}
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export function FormationsTimeline() {
|
||||
return (
|
||||
<div className="formations-timeline">
|
||||
<div className="formations-timeline__rows">
|
||||
<div className="formations-timeline__line" aria-hidden />
|
||||
{SORTED_EVENTS.map((event) => (
|
||||
<div
|
||||
key={`${event.period}-${event.side}-${event.sequence}-${event.title}`}
|
||||
className="formations-timeline__row"
|
||||
>
|
||||
<div className="formations-timeline__cell formations-timeline__cell--left">
|
||||
{event.side === 'left' ? (
|
||||
<TimelineCard
|
||||
side="left"
|
||||
period={event.period}
|
||||
title={event.title}
|
||||
description={event.description}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="formations-timeline__node-wrap">
|
||||
<span className="formations-timeline__node" aria-hidden />
|
||||
</div>
|
||||
<div className="formations-timeline__cell formations-timeline__cell--right">
|
||||
{event.side === 'right' ? (
|
||||
<TimelineCard
|
||||
side="right"
|
||||
period={event.period}
|
||||
title={event.title}
|
||||
description={event.description}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { INTRO } from './introText.js'
|
||||
|
||||
export function HomeAutobiographiePanel() {
|
||||
return (
|
||||
<div className="home-intro home-intro--profil-box">
|
||||
<section className="home-intro__section" aria-labelledby="home-autobio-title">
|
||||
<h2 className="home-intro__heading" id="home-autobio-title">
|
||||
Autobiographie
|
||||
</h2>
|
||||
<p className="home-intro__text">{INTRO}</p>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const ENTRIES = [
|
||||
{ period: '2018', title: 'Bac techno STI2D SIN' },
|
||||
{ period: '2019-2020', title: 'CAP Mecanique Auto' },
|
||||
{ period: '2020-2021', title: 'CAP Plomberie Chauffagerie' },
|
||||
{
|
||||
period: '2022-2025',
|
||||
title: 'Administrateur système\nDéveloppeur application',
|
||||
description: 'OpenMotion',
|
||||
},
|
||||
]
|
||||
|
||||
export function HomeEducationTimeline() {
|
||||
return (
|
||||
<div className="home-timeline">
|
||||
<h2 className="home-timeline__heading">Parcours</h2>
|
||||
<ol className="home-timeline__list">
|
||||
{ENTRIES.map((entry) => (
|
||||
<li key={`${entry.period}-${entry.title}`} className="home-timeline__item">
|
||||
<span className="home-timeline__marker" aria-hidden />
|
||||
<div className="home-timeline__body">
|
||||
<span className="home-timeline__period">{entry.period}</span>
|
||||
<p className="home-timeline__title">{entry.title}</p>
|
||||
{entry.description ? (
|
||||
<p className="home-timeline__desc">{entry.description}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { INTRO } from './introText.js'
|
||||
|
||||
const TAGS = [
|
||||
{ label: 'Curieux', tone: 'pos' },
|
||||
{ label: 'Polyvalent', tone: 'pos' },
|
||||
{ label: 'Autodidacte', tone: 'pos' },
|
||||
{ label: 'Logique', tone: 'pos' },
|
||||
{ label: 'Persévérance', tone: 'pos' },
|
||||
{ label: "Esprit d'Analyse", tone: 'pos' },
|
||||
{ label: 'Autonomie', tone: 'pos' },
|
||||
{ label: "Capacité d'Adaptation", tone: 'pos' },
|
||||
{ label: 'Impatient', tone: 'neg' },
|
||||
{ label: 'Besoin de comprendre', tone: 'neg' },
|
||||
{ label: 'Pédagogie', tone: 'neg' },
|
||||
]
|
||||
|
||||
export function HomeIntroPanel() {
|
||||
return (
|
||||
<div className="home-intro">
|
||||
<p className="home-intro__text">{INTRO}</p>
|
||||
<ul className="home-intro__tags" aria-label="Traits">
|
||||
{TAGS.map(({ label, tone }) => (
|
||||
<li key={label}>
|
||||
<span className={`home-tag home-tag--${tone}`}>{label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { SKILLS } from '../data/skills.js'
|
||||
|
||||
export function HomeProfilCompetencesPanel() {
|
||||
return (
|
||||
<div className="home-intro home-intro--profil-box">
|
||||
<section
|
||||
className="home-profil-traits"
|
||||
aria-labelledby="home-profil-competences-title"
|
||||
>
|
||||
<h2 className="home-intro__heading" id="home-profil-competences-title">
|
||||
Compétence
|
||||
</h2>
|
||||
<ul className="home-profil-traits__list">
|
||||
{SKILLS.map(({ label, cat, desc }) => (
|
||||
<li key={label} className="home-profil-traits__row">
|
||||
<span
|
||||
className={`home-profil-traits__label home-skill home-skill--${cat}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<p className="home-profil-traits__desc">{desc}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { PROFIL_TRAITS } from '../data/profilTraits.js'
|
||||
|
||||
export function HomeProfilQualitesPanel() {
|
||||
return (
|
||||
<div className="home-intro home-intro--profil-box">
|
||||
<section
|
||||
className="home-profil-traits"
|
||||
aria-labelledby="home-profil-traits-title"
|
||||
>
|
||||
<h2 className="home-intro__heading" id="home-profil-traits-title">
|
||||
Qualité / Défauts
|
||||
</h2>
|
||||
<ul className="home-profil-traits__list">
|
||||
{PROFIL_TRAITS.map(({ label, tone, desc }) => (
|
||||
<li key={label} className="home-profil-traits__row">
|
||||
<span
|
||||
className={`home-profil-traits__label home-tag home-tag--${tone}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<p className="home-profil-traits__desc">{desc}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { createElement } from 'react'
|
||||
import { FaGithub, FaLinkedinIn } from 'react-icons/fa6'
|
||||
import { SiTryhackme } from 'react-icons/si'
|
||||
|
||||
const SOCIAL = [
|
||||
{
|
||||
label: 'LinkedIn',
|
||||
href: 'https://www.linkedin.com/',
|
||||
Icon: FaLinkedinIn,
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/',
|
||||
Icon: FaGithub,
|
||||
},
|
||||
{
|
||||
label: 'TryHackMe',
|
||||
href: 'https://tryhackme.com/',
|
||||
Icon: SiTryhackme,
|
||||
},
|
||||
]
|
||||
|
||||
export function HomeSidebar({ layout = 'vertical' }) {
|
||||
const isHorizontal = layout === 'horizontal'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'home-sidebar__inner',
|
||||
isHorizontal ? 'home-sidebar__inner--horizontal' : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<img
|
||||
className="home-sidebar__photo"
|
||||
src="https://www.thispersondoesnotexist.com"
|
||||
alt=""
|
||||
width={280}
|
||||
height={280}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<div className="home-sidebar__cluster">
|
||||
<p className="home-sidebar__name">Prénom Nom</p>
|
||||
<ul className="home-sidebar__social" aria-label="Liens sociaux">
|
||||
{SOCIAL.map(({ label, href, Icon }) => (
|
||||
<li key={label}>
|
||||
<a
|
||||
className="home-sidebar__social-link"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={label}
|
||||
>
|
||||
{createElement(Icon, {
|
||||
'aria-hidden': true,
|
||||
className: 'home-sidebar__social-icon',
|
||||
})}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="home-sidebar__extra">
|
||||
<ul className="home-sidebar__extra-list">
|
||||
<li>Marseille</li>
|
||||
<li>Voiture</li>
|
||||
<li>Francais</li>
|
||||
<li>Anglais B1</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { SKILLS } from '../data/skills.js'
|
||||
|
||||
export function HomeSkillsBlock() {
|
||||
return (
|
||||
<section className="home-skills" aria-labelledby="home-skills-title">
|
||||
<h2 className="home-skills__title" id="home-skills-title">
|
||||
Compétence
|
||||
</h2>
|
||||
<ul className="home-skills__list">
|
||||
{SKILLS.map(({ label, cat }) => (
|
||||
<li key={label}>
|
||||
<span className={`home-skill home-skill--${cat}`}>{label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { PROJETS } from '../data/projets.js'
|
||||
|
||||
const SKILL_CATS = ['green', 'blue', 'yellow', 'red']
|
||||
|
||||
export function ProjetsGrid() {
|
||||
return (
|
||||
<ul className="projets-grid">
|
||||
{PROJETS.map((projet, index) => (
|
||||
<li key={projet.id} className="projets-grid__cell">
|
||||
<article className="projet-card" aria-labelledby={`projet-title-${projet.id}`}>
|
||||
<div className="projet-card__media">
|
||||
<img
|
||||
src={projet.imageSrc}
|
||||
alt={projet.imageAlt}
|
||||
className="projet-card__img"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</div>
|
||||
<div className="projet-card__body">
|
||||
<h2 className="projet-card__title" id={`projet-title-${projet.id}`}>
|
||||
{projet.title}
|
||||
</h2>
|
||||
<ul className="projet-card__skills" aria-label="Compétences">
|
||||
{projet.skills.map((skill, i) => (
|
||||
<li key={skill}>
|
||||
<span
|
||||
className={`home-skill home-skill--${
|
||||
SKILL_CATS[(index + i) % SKILL_CATS.length]
|
||||
}`}
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="projet-card__desc">{projet.description}</p>
|
||||
<a
|
||||
href={projet.link}
|
||||
className="projet-card__link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{projet.linkLabel}
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { NavLink } from 'react-router-dom'
|
||||
|
||||
function getNavLinkClassName({ isActive }) {
|
||||
return ['topnav__link', isActive ? 'is-active' : null].filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export function TopNav() {
|
||||
return (
|
||||
<header className="topnav">
|
||||
<div className="topnav__inner">
|
||||
<nav className="topnav__nav" aria-label="Sommaire">
|
||||
<NavLink to="/" className={getNavLinkClassName} end>
|
||||
Accueil
|
||||
</NavLink>
|
||||
<NavLink to="/profil" className={getNavLinkClassName}>
|
||||
Profil
|
||||
</NavLink>
|
||||
<NavLink to="/formations" className={getNavLinkClassName}>
|
||||
Formations
|
||||
</NavLink>
|
||||
<NavLink to="/projets" className={getNavLinkClassName}>
|
||||
Projets
|
||||
</NavLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const INTRO =
|
||||
"Passionné d'informatique et d'électronique depuis petit et curieux de nature. J'ai toujours aimé apprendre en créant mes propres systèmes. J'accomplis des projets et des architectures informatiques pour me simplifier la vie. Polyvalent j'effectue également la réparation de divers équipements électroniques."
|
||||
@@ -0,0 +1,52 @@
|
||||
export const PROFIL_TRAITS = [
|
||||
{
|
||||
label: 'La Logique',
|
||||
tone: 'pos',
|
||||
desc: 'Capacité à décomposer un problème complexe en étapes simples.',
|
||||
},
|
||||
{
|
||||
label: 'La Curiosité',
|
||||
tone: 'pos',
|
||||
desc: "S'intéresser facilement, aimer apprendre de nouvelles choses.",
|
||||
},
|
||||
{
|
||||
label: "L'Autonomie",
|
||||
tone: 'pos',
|
||||
desc: 'Savoir trouver la solution dans une documentation ou sur un forum sans aide extérieure.',
|
||||
},
|
||||
{
|
||||
label: 'La Persévérance',
|
||||
tone: 'pos',
|
||||
desc: 'Ne pas abandonner facilement.',
|
||||
},
|
||||
{
|
||||
label: "L'Esprit d'Analyse",
|
||||
tone: 'pos',
|
||||
desc: "Savoir anticiper les failles ou les besoins futurs d'un système.",
|
||||
},
|
||||
{
|
||||
label: "La Capacité d'Adaptation",
|
||||
tone: 'pos',
|
||||
desc: "Changer de projet rapidement sans difficulté. s'adapter à de nouvelles technologies.",
|
||||
},
|
||||
{
|
||||
label: 'Polyvalent',
|
||||
tone: 'pos',
|
||||
desc: "Administration système, développement logiciel, réparation d'équipements électroniques, modification de hardware, Mécanique, Plomberie, Electricité.",
|
||||
},
|
||||
{
|
||||
label: "L'Impatience",
|
||||
tone: 'neg',
|
||||
desc: 'L\'envie que les choses avancent, que l\'attente est une perte de temps.',
|
||||
},
|
||||
{
|
||||
label: 'Pédagogie',
|
||||
tone: 'neg',
|
||||
desc: "Capacité à expliquer un problème technique complexe à un utilisateur qui n'y connaît rien.",
|
||||
},
|
||||
{
|
||||
label: 'Besoin de comprendre',
|
||||
tone: 'neg',
|
||||
desc: 'Ne pas se contenter de voir que quelque chose fonctionne, mais comprendre comment ça fonctionne.',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,114 @@
|
||||
/** Cartes projet (page Projets) : titre, compétences, visuel, texte, lien externe. */
|
||||
export const PROJETS = [
|
||||
{
|
||||
id: 'Self-Hosting',
|
||||
title: 'Self Hosting',
|
||||
skills: [ 'Linux', 'Bash', 'Dockers', 'Sécurité'],
|
||||
imageSrc: 'https://picsum.photos/seed/pdfredact/640/360',
|
||||
imageAlt: 'Home Lab, Serveurs personnels',
|
||||
description:
|
||||
'Installation et configuration de serveurs personnels pour des projets personnels et professionnels.',
|
||||
link: 'https://pdfbox.apache.org/',
|
||||
linkLabel: 'Apache PDFBox',
|
||||
},
|
||||
{
|
||||
id: 'androidpayload',
|
||||
title: 'androidpayload',
|
||||
skills: ['Linux', 'Bash', 'Java', 'Hardware'],
|
||||
imageSrc: 'https://www.thispersondoesnotexist.com/',
|
||||
imageAlt: 'Visuel du projet androidpayload',
|
||||
description:
|
||||
'Application pour récupérer tous les droits sur un appareil Android (ROOT).',
|
||||
link: 'https://github.com/topjohnwu/Magisk',
|
||||
linkLabel: 'Magisk sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'homelab-stack',
|
||||
title: 'HomelabStack',
|
||||
skills: ['Docker', 'Linux', 'Ansible', 'Nginx'],
|
||||
imageSrc: 'https://picsum.photos/seed/homelab/640/360',
|
||||
imageAlt: 'Schéma de services conteneurisés',
|
||||
description:
|
||||
'Automatisation du déploiement de services auto-hébergés, reverse proxy et sauvegardes planifiées.',
|
||||
link: 'https://github.com/ansible/ansible',
|
||||
linkLabel: 'Ansible sur GitHub',
|
||||
},
|
||||
{
|
||||
id: 'capteurs-maison',
|
||||
title: 'CapteursMaison',
|
||||
skills: ['C', 'MQTT', 'ESP32', 'Électronique'],
|
||||
imageSrc: 'https://picsum.photos/seed/iotcapteurs/640/360',
|
||||
imageAlt: 'Maquette capteurs connectés',
|
||||
description:
|
||||
'Firmware embarqué et passerelle MQTT pour relevés de température et humidité en temps réel.',
|
||||
link: 'https://mqtt.org/',
|
||||
linkLabel: 'Protocole MQTT',
|
||||
},
|
||||
{
|
||||
id: 'veille-marche',
|
||||
title: 'VeilleMarché',
|
||||
skills: ['Python', 'REST', 'SQLite', 'CLI'],
|
||||
imageSrc: 'https://picsum.photos/seed/chartscli/640/360',
|
||||
imageAlt: 'Courbes et terminal',
|
||||
description:
|
||||
'Outil en ligne de commande pour agréger des cours, historiser en local et déclencher des alertes.',
|
||||
link: 'https://docs.python.org/3/library/argparse.html',
|
||||
linkLabel: 'argparse (Python)',
|
||||
},
|
||||
{
|
||||
id: 'photo-archiver',
|
||||
title: 'PhotoArchiver',
|
||||
skills: ['Rust', 'CLI', 'Filesystem', 'EXIF'],
|
||||
imageSrc: 'https://picsum.photos/seed/photoarch/640/360',
|
||||
imageAlt: 'Dossiers et vignettes photo',
|
||||
description:
|
||||
'Indexation d’une photothèque locale, détection de doublons par empreinte et renommage par date EXIF.',
|
||||
link: 'https://exiftool.org/',
|
||||
linkLabel: 'ExifTool',
|
||||
},
|
||||
{
|
||||
id: 'net-pulse',
|
||||
title: 'NetPulse',
|
||||
skills: ['Go', 'Prometheus', 'Grafana', 'HTTP'],
|
||||
imageSrc: 'https://picsum.photos/seed/netpulse/640/360',
|
||||
imageAlt: 'Graphiques de métriques réseau',
|
||||
description:
|
||||
'Sonde légère et tableaux de bord pour suivre latence, codes HTTP et disponibilité des services du lab.',
|
||||
link: 'https://prometheus.io/',
|
||||
linkLabel: 'Prometheus',
|
||||
},
|
||||
{
|
||||
id: 'bench-auto',
|
||||
title: 'BenchAuto',
|
||||
skills: ['Bash', 'Jenkins', 'Git', 'curl'],
|
||||
imageSrc: 'https://picsum.photos/seed/benchauto/640/360',
|
||||
imageAlt: 'Pipeline et rapports de tests',
|
||||
description:
|
||||
'Jobs planifiés pour enchaîner scénarios de charge, collecter les métriques et publier un rapport HTML.',
|
||||
link: 'https://www.jenkins.io/',
|
||||
linkLabel: 'Jenkins',
|
||||
},
|
||||
{
|
||||
id: 'mesh-relay',
|
||||
title: 'MeshRelay',
|
||||
skills: ['Zigbee', 'Node', 'MQTT', 'Home Assistant'],
|
||||
imageSrc: 'https://picsum.photos/seed/meshrelay/640/360',
|
||||
imageAlt: 'Passerelle et objets connectés',
|
||||
description:
|
||||
'Automatisations domotiques : capteurs Zigbee, scénarios jour/nuit et notifications sur événements.',
|
||||
link: 'https://www.home-assistant.io/',
|
||||
linkLabel: 'Home Assistant',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'sync-vault',
|
||||
title: 'SyncVault',
|
||||
skills: ['rsync', 'S3', 'LUKS', 'Cron'],
|
||||
imageSrc: 'https://picsum.photos/seed/syncvault/640/360',
|
||||
imageAlt: 'Sauvegarde et chiffrement',
|
||||
description:
|
||||
'Volumes chiffrés, sauvegardes incrémentielles locales et miroir distant compatible stockage objet.',
|
||||
link: 'https://rclone.org/',
|
||||
linkLabel: 'rclone',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
/** Compétences : couleur + courte phrase (page Profil). */
|
||||
export const SKILLS = [
|
||||
{
|
||||
label: 'Linux',
|
||||
cat: 'green',
|
||||
desc: 'Utilisé en entreprise et quotidiennement. j\'utilise principalement ArchLinux ou Debian, j\'effectue installation et configuration complète d\'un OS, développement d\'applications Linux, etc.',
|
||||
},
|
||||
{
|
||||
label: 'Bash',
|
||||
cat: 'green',
|
||||
desc: 'Utilisé en entreprise et quotidiennement. Je conçois et modifie des scripts ainsi que des services système, ces outils me permettent l\'automatisation de tâches répétitives, la gestion des sauvegardes, l\'analyse de données et l\'administration de serveurs à distance.. ',
|
||||
},
|
||||
{
|
||||
label: 'Python',
|
||||
cat: 'green',
|
||||
desc: 'Utilisé en entreprise et quotidiennement. pour des projets nécessitant une grande flexibilité : reconnaissance d\'image,api, ia, trading cryptomonnaie, etc.',
|
||||
},
|
||||
{
|
||||
label: 'Réseau Système',
|
||||
cat: 'blue',
|
||||
desc: 'Utilisation au quotidien et en entreprise. Mise en place de serveur privé avec des services privé et publique (vpn, docker, etc), avec réseau de serveur de backup, etc.',
|
||||
},
|
||||
{
|
||||
label: 'PHP',
|
||||
cat: 'blue',
|
||||
desc: 'Utilisé en entreprise avec un framework privé, ce back-office fait office de centre de contrôle pour un système complet gérant les serveurs, les bases de données et les applications mobiles.',
|
||||
},
|
||||
{
|
||||
label: 'Hardware',
|
||||
cat: 'yellow',
|
||||
desc: 'Utilisé en entreprise et au domicile. Diagnostic, réparation et modification de fonctionnement.',
|
||||
},
|
||||
{
|
||||
label: 'JAVA',
|
||||
cat: 'yellow',
|
||||
desc: 'Utilisée en entreprise, cette application mobile remplace l\'interface utilisateur Android, Elle permet la gestion des droits d\'accès à l\'appareil ainsi que le suivi d\'activités pour faciliter la maintenance et l\'accompagnement de l\'intervenant. intègre également des fonctionnalité comme : trajets GPS, comptage de détections par reconnaissance d\'image, calendrier des missions...',
|
||||
},
|
||||
{
|
||||
label: 'Assembleur',
|
||||
cat: 'red',
|
||||
desc: 'Utilisé en entreprise et au domicile lecture de partitions et modification de cette derniere, afin de modifier le comportement d\'appareils android, modification de comportement de logicel : jeux video, etc.',
|
||||
},
|
||||
]
|
||||
+1021
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { TopNav } from '../components/TopNav.jsx'
|
||||
|
||||
export function SiteLayout() {
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<TopNav />
|
||||
<main className="app-main">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
import { FormationsTimeline } from '../components/FormationsTimeline.jsx'
|
||||
|
||||
export function Formations() {
|
||||
return (
|
||||
<div className="page page--stack-only page--formations">
|
||||
<div className="content-stack" aria-label="Contenu principal">
|
||||
<section
|
||||
className="content-stack__panel content-stack__panel--padded"
|
||||
aria-label="Parcours"
|
||||
>
|
||||
<FormationsTimeline />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { HomePageView } from './HomePageView.jsx'
|
||||
|
||||
export function Home() {
|
||||
return <HomePageView />
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ContentStack } from '../components/ContentStack.jsx'
|
||||
import { HomeAutobiographiePanel } from '../components/HomeAutobiographiePanel.jsx'
|
||||
import { HomeEducationTimeline } from '../components/HomeEducationTimeline.jsx'
|
||||
import { HomeIntroPanel } from '../components/HomeIntroPanel.jsx'
|
||||
import { HomeProfilCompetencesPanel } from '../components/HomeProfilCompetencesPanel.jsx'
|
||||
import { HomeProfilQualitesPanel } from '../components/HomeProfilQualitesPanel.jsx'
|
||||
import { HomeSkillsBlock } from '../components/HomeSkillsBlock.jsx'
|
||||
import { HomeSidebar } from '../components/HomeSidebar.jsx'
|
||||
|
||||
/** Accueil : sidebar à gauche. Profil (`variant="profil"`) : bandeau profil horizontal au-dessus du contenu. */
|
||||
export function HomePageView({ variant = 'home' }) {
|
||||
const isProfil = variant === 'profil'
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['page', 'page--home', isProfil ? 'page--home-profil' : null]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<aside
|
||||
className={['home-sidebar', isProfil ? 'home-sidebar--horizontal' : null]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-label="Profil"
|
||||
>
|
||||
<HomeSidebar layout={isProfil ? 'horizontal' : 'vertical'} />
|
||||
</aside>
|
||||
<div className="home-main">
|
||||
<ContentStack
|
||||
panel1={
|
||||
isProfil ? <HomeAutobiographiePanel /> : <HomeIntroPanel />
|
||||
}
|
||||
panel1b={
|
||||
isProfil ? <HomeProfilCompetencesPanel /> : <HomeSkillsBlock />
|
||||
}
|
||||
panel2={
|
||||
isProfil ? <HomeProfilQualitesPanel /> : <HomeEducationTimeline />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { HomePageView } from './HomePageView.jsx'
|
||||
|
||||
export function Profil() {
|
||||
return <HomePageView variant="profil" />
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ProjetsGrid } from '../components/ProjetsGrid.jsx'
|
||||
|
||||
export function Projets() {
|
||||
return (
|
||||
<div className="page page--stack-only page--projets">
|
||||
<div className="content-stack" aria-label="Contenu principal">
|
||||
<section
|
||||
className="content-stack__panel content-stack__panel--padded"
|
||||
aria-label="Liste des projets"
|
||||
>
|
||||
<ProjetsGrid />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: true,
|
||||
},
|
||||
preview: {
|
||||
host: true,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user