feat: add CV
All checks were successful
Build, check & Test / run (push) Successful in 4m13s

Signed-off-by: Avior <git@avior.me>
This commit is contained in:
Florian Bouillon 2024-10-18 11:58:57 +02:00
parent f671a21d2f
commit 851ae7a528
Signed by: Florian Bouillon
GPG Key ID: 7676FF78F3BC40EC
10 changed files with 204 additions and 14 deletions

View File

@ -9,6 +9,16 @@ http {
listen 3000; listen 3000;
listen [::]:3000; listen [::]:3000;
# Redirect from non-www to www
# if ($host ~ ^(?!www\.)(?<domain>.+)$) {
# return 301 $scheme://www.$domain$request_uri;
# }
# Redirect from www to non-www
if ($host ~ ^www\.(?<domain>.+)$) {
return 301 $scheme://$domain$request_uri;
}
server_name _; server_name _;
root /usr/share/nginx/html; root /usr/share/nginx/html;
@ -18,13 +28,23 @@ http {
gzip on; gzip on;
gzip_min_length 1000; gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth; gzip_proxied expired no-cache no-store private auth;
gzip_types text/html text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /404.html; error_page 404 /404.html;
error_page 500 502 503 504 /500.html; error_page 500 502 503 504 /50x.html;
# Security headers (note: temporaly unvailable "prefetch-src 'self'; ")
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; form-action 'self'; manifest-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src data: 'self' https://img.shields.io; font-src 'self'; connect-src 'self'; base-uri 'self';";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "geolocation=(), microphone=(), interest-cohort=()";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Download-Options "noopen";
location / { location / {
try_files $uri $uri.html $uri/index.html /$uri /$uri/index.html /index.html; try_files $uri $uri.html $uri/index.html /$uri /$uri/index.html =404;
} }
# Plausible script # Plausible script

8
package-lock.json generated
View File

@ -11,7 +11,7 @@
"@dzeio/logger": "^3", "@dzeio/logger": "^3",
"@dzeio/object-util": "^1", "@dzeio/object-util": "^1",
"@dzeio/url-manager": "^1", "@dzeio/url-manager": "^1",
"@fontsource-variable/lexend": "^5.0.12", "@fontsource-variable/lexend": "^5.1.1",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"astro": "^3", "astro": "^3",
"astro-seo": "^0.8.0", "astro-seo": "^0.8.0",
@ -947,9 +947,9 @@
} }
}, },
"node_modules/@fontsource-variable/lexend": { "node_modules/@fontsource-variable/lexend": {
"version": "5.0.12", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@fontsource-variable/lexend/-/lexend-5.0.12.tgz", "resolved": "https://registry.npmjs.org/@fontsource-variable/lexend/-/lexend-5.1.1.tgz",
"integrity": "sha512-ib0Fvf4MK+Cpjq3xp9F8Xf/uigo08lPqPK/dU6s2RQk2U+IVuKB2Fbhceql7LFxC80OYlEjN+CyOZbSzqsirRQ==" "integrity": "sha512-ixzYkzPiJRBAxYPsjIUBLBjS1zXBMF75qQwoVyEPWOtt5uyEHdT8jTpbh/b4ibsnQvqEVb/CToifmEXJfvShag=="
}, },
"node_modules/@istanbuljs/schema": { "node_modules/@istanbuljs/schema": {
"version": "0.1.3", "version": "0.1.3",

View File

@ -20,7 +20,7 @@
"@dzeio/logger": "^3", "@dzeio/logger": "^3",
"@dzeio/object-util": "^1", "@dzeio/object-util": "^1",
"@dzeio/url-manager": "^1", "@dzeio/url-manager": "^1",
"@fontsource-variable/lexend": "^5.0.12", "@fontsource-variable/lexend": "^5.1.1",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"astro": "^3", "astro": "^3",
"astro-seo": "^0.8.0", "astro-seo": "^0.8.0",

5
public/robots.txt Normal file
View File

@ -0,0 +1,5 @@
User-Agent: *
Disallow:
Disallow: /cv
Sitemap: https://avior.me/sitemap.xml

BIN
src/assets/pages/cv/me.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -13,6 +13,7 @@ export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
srcDark?: ImageMetadata | string srcDark?: ImageMetadata | string
width?: number width?: number
height?: number height?: number
innerClass?: string
} }
type PictureResult = { type PictureResult = {
@ -70,7 +71,7 @@ const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
{res.light.formats.map((it) => ( {res.light.formats.map((it) => (
<source srcset={it.img.src} type={`image/${it.format}`} /> <source srcset={it.img.src} type={`image/${it.format}`} />
))} ))}
<img src={res.light.src.src} /> <img src={res.light.src.src} class={Astro.props.innerClass} />
</picture> </picture>
) || ( ) || (
<img {...props} class:list={[Astro.props.class, {'dark:hidden': res.dark}]} src={res.light.src as string} /> <img {...props} class:list={[Astro.props.class, {'dark:hidden': res.dark}]} src={res.light.src as string} />
@ -81,7 +82,7 @@ const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
{res.dark.formats.map((it) => ( {res.dark.formats.map((it) => (
<source srcset={it.img.src} type={`image/${it.format}`} /> <source srcset={it.img.src} type={`image/${it.format}`} />
))} ))}
<img src={res.dark.src.src} /> <img src={res.dark.src.src} class={Astro.props.innerClass} />
</picture> </picture>
) || (res.dark && ( ) || (res.dark && (
<img {...props} class:list={[Astro.props.class, 'hidden', 'dark:block']} src={res.dark.src as string} /> <img {...props} class:list={[Astro.props.class, 'hidden', 'dark:block']} src={res.dark.src as string} />

View File

@ -1,6 +1,8 @@
--- ---
import Head, { type Props as HeadProps } from './Head.astro' import Head, { type Props as HeadProps } from './Head.astro'
import '@fontsource-variable/lexend'
export interface Props extends HeadProps {} export interface Props extends HeadProps {}
--- ---
@ -13,3 +15,9 @@ export interface Props extends HeadProps {}
<slot /> <slot />
</body> </body>
</html> </html>
<style is:global>
html {
font-weight: 350;
}
</style>

158
src/pages/cv.astro Normal file
View File

@ -0,0 +1,158 @@
---
import Base from 'layouts/Base.astro'
import Picture from 'components/Picture.astro'
import Me from 'assets/pages/cv/me.jpg'
import { objectMap } from '@dzeio/object-util'
const skills = {
'Langages': 'Typescript/JavaScript, Web (HTML/CSS), Kotlin, C++',
'Frameworks': 'Astro, NextJS, Android (MVVM), PlatformIO',
'Softwares': 'Git, VSCode, Zed',
'Tertiaire': 'Gestion de projet, Encadrement de collaborateurs'
} as const
const skillColor: Record<keyof typeof skills, string> = {
'Langages': 'amber',
'Frameworks': 'sky',
'Softwares': 'teal',
'Tertiaire': 'gray'
}
const passions: Array<string> = [
'Pokémon',
'Tennis',
'Programation'
]
const jobs: Array<{
company: string
start: string
end?: string
projects: Array<{
name: string
client?: string
description: string
technics?: Array<string>
}>
}> = [{
company: 'Aptatio',
start: '2019-09-30',
projects: [{
name: 'Ifremer LOOP',
client: 'Ifremer',
description: 'Développement dune application de gestion des réponses des appels à données et participations aux groupes internationaux',
technics: [
'Gestion de projet',
'Conception de logiciel',
'Développement de logiciel Web (Framework Astro & Typescript)'
]
}, {
name: 'Gwaleen',
client: 'Ifremer & Aptatio',
description: 'Développement de plusieurs modules logiciel des Ichtyomètre éléctronique Gwaleen',
technics: [
'Création et maintenance d\'un Logiciel Android de Gestion de donnée provenant d\'Ichtyomètres Connecté (Kotlin)',
'Conception et Création d\'un protocol de communication entre 2 appareil Android en bluetooth pour de la donnée Vidéo (Kotlin)',
'Gestion et création du code embarqué dans les Ichtyomètres Gwaleen (C++)'
]
}, {
name: 'ImmerCité',
client: 'La cité des congrès de Nantes',
description: 'Création d\'ImmerCité, un logiciel immersif pour la présentation d\'événements, primé au concours international des Cités des Congrès et lauréat du 1er prix Innovation Award à la conférence annuelle de l\'AIPC au Costa Rica.',
technics: [
'Conception de logiciel',
'Développement de logiciel Web (Framework NextJS & Typescript)',
'Création d\'un engine de gestion 2D pour le placement et l\'automatisation des objets dans les salles de la cité',
'Gestion de la communication et synchronisation entre plusieurs ordinateurs (WebSocket & ExpressJS)',
'Programation de la 3D dans Unity puis Godot (C#, GDScript)'
]
}, {
name: 'Gestion de l\'infrastructure',
description: 'Gestion de l\'infrastructure Réseau de l\'entreprise',
technics: [
'mise en place d\'un SSO (Single sign-on)',
'Gestion de la donnée utilisateur/client',
'Mise en ligne des logiciels Client',
'Maintenance Préventive des serveurs'
]
}]
}, {
company: 'Auto-Entreprise',
start: '2019-04-01',
projects: [{
name: 'TCGdex',
description: 'Développement de TCGdex, une API open source multilingue (14 langues) pour le jeu de cartes Pokémon JCC, offrant des fonctionnalités avancées aux développeurs et amateurs du jeu.',
technics: [
'Récupération des données depuis une multitude de sources',
'Création et maintenance d\'une API avec + de 4 Millions de requettes par mois',
'Mise en place d\'outillages pour simplifier l\'usage de l\'API aux développeurs',
'Maintenance et gestion d\'une communauté autour du jeux de carte Pokémon'
]
}]
}]
---
<Base>
<main class="container py-8">
<div class="flex gap-4">
<div class="w-1/4 flex flex-col gap-4">
<Picture src={Me} innerClass="rounded-xl" />
<h1 class="font-bold text-5xl text-center">Florian Bouillon</h1>
<div>
<p>Compétences :</p>
<div class="hidden bg-amber-300 bg-sky-300 bg-teal-300"></div>
<ul class="pl-6 list-disc">
{objectMap(skills, (it, key) => (
<li>{key}: {it.split(',').map((it) => <span class:list={[`bg-${skillColor[key]}-300`, "inline-block px-2 py-1 m-1 text-black rounded-xl"]}>{it}</span>)}</li>
))}
</ul>
</div>
<div>
<p>Passions :</p>
<ul class="pl-2 list-disc">
{passions.map((it) => (
<li>{it}</li>
))}
</ul>
</div>
</div>
<div class="w-3/4">
<div class="">
<h2 class="text-4xl font-bold">Expérience professionnel</h2>
{jobs.map((company) => (
<div class="mt-6">
<div class="flex justify-between items-center">
<h2 class="text-2xl font-semibold">{company.company}</h2>
<div class="italic">{company.start} - {company.end ?? 'Aujourd\'hui'}</div>
</div>
<div class="pl-2">
{company.projects.map((it, idx) => (
<div>
{idx > 0 && (
<hr class="my-2 border-t-2 border-gray-500" />
)}
<div class="flex justify-between items-center">
<p class="text-xl font-[550]">- {it.name}</p>
{it.client && (
<p>{it.client}</p>
)}
</div>
<p class="mt-2 pl-4">{it.description}</p>
{it.technics && (
<div class="pl-4 mt-2">
<p>Tâches:</p>
<ul class="list-disc pl-6">
{it.technics?.map((tech) => <li>{tech}</li>)}
</ul>
</div>
)}
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
</main>
</Layout>

View File

@ -29,7 +29,7 @@ const clients = await Promise.all((await getCollection('clients')).map(async (it
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<div></div> <div></div>
<div class="prose dark:prose-invert max-w-none"> <div class="prose dark:prose-invert max-w-none">
<p class="font-semibold">Tâches :O :</p> <p class="font-semibold">Tâches :</p>
</div> </div>
</div> </div>
{clients.map((client, index) => { {clients.map((client, index) => {

View File

@ -14,11 +14,9 @@ module.exports = {
'2xl': '6rem', '2xl': '6rem',
} }
}, },
fontFamily: {
fontFamily: { fontFamily: {
sans: ["Lexend Variable", "Lexend", ...defaultTheme.fontFamily.sans], sans: ["Lexend Variable", "Lexend", ...defaultTheme.fontFamily.sans],
}, },
},
extend: { extend: {
}, },
}, },