1
1
mirror of https://github.com/theoludwig/theoludwig.git synced 2024-11-08 22:31:30 +01:00

feat: add Curriculum vitae

This commit is contained in:
Divlo 2022-02-22 21:19:42 +01:00
parent 729e540d04
commit 0e0036b737
No known key found for this signature in database
GPG Key ID: 8F9478F220CE65E9
35 changed files with 16881 additions and 1899 deletions

View File

@ -72,6 +72,9 @@ jobs:
- name: 'lint:prettier' - name: 'lint:prettier'
run: 'npm run lint:prettier' run: 'npm run lint:prettier'
- name: 'resume:validate'
run: 'npm run resume:validate'
- name: 'lint:dotenv' - name: 'lint:dotenv'
uses: 'dotenv-linter/action-dotenv-linter@v2' uses: 'dotenv-linter/action-dotenv-linter@v2'
with: with:

8
.gitignore vendored
View File

@ -11,6 +11,10 @@ out
# production # production
build build
dist dist
public/*.html
# PWA
public/workbox-*.js
public/sw.js
# testing # testing
coverage coverage
@ -18,10 +22,6 @@ cypress/screenshots
cypress/videos cypress/videos
cypress/downloads cypress/downloads
# PWA
**/workbox-*.js
**/sw.js
# envs # envs
.env .env
.env.production .env.production

View File

@ -6,5 +6,6 @@
"jest --findRelatedTests" "jest --findRelatedTests"
], ],
"*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"], "*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"],
"*.{md,mdx}": ["prettier --write", "markdownlint --dot --fix"] "*.{md,mdx}": ["prettier --write", "markdownlint --dot --fix"],
"resume.json": ["resume validate"]
} }

View File

@ -10,6 +10,7 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
return ( return (
<> <>
<Image <Image
quality={100}
width={35} width={35}
height={35} height={35}
src={`/images/languages/${language}.svg`} src={`/images/languages/${language}.svg`}

View File

@ -47,7 +47,7 @@ export const Language: React.FC = () => {
<ul <ul
data-cy='languages-list' data-cy='languages-list'
className={classNames( className={classNames(
'absolute top-14 z-10 mt-3 mr-4 flex w-24 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-light dark:bg-black dark:shadow-dark', 'absolute top-14 z-10 mt-3 mr-4 flex w-24 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-lightFlag dark:bg-black dark:shadow-darkFlag',
{ hidden: hiddenMenu } { hidden: hiddenMenu }
)} )}
> >

View File

@ -17,6 +17,7 @@ export const Header: React.FC<HeaderProps> = (props) => {
<a> <a>
<div className='flex items-center justify-center'> <div className='flex items-center justify-center'>
<Image <Image
quality={100}
width={60} width={60}
height={60} height={60}
src='/images/divlo_icon_small.png' src='/images/divlo_icon_small.png'

View File

@ -23,6 +23,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
> >
<div className='flex justify-center'> <div className='flex justify-center'>
<Image <Image
quality={100}
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5' className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
width={300} width={300}
height={300} height={300}

View File

@ -1,12 +1,23 @@
import Translation from 'next-translate/Trans' import useTranslation from 'next-translate/useTranslation'
export const ProfileDescriptionBottom: React.FC = () => { export const ProfileDescriptionBottom: React.FC = () => {
const { t, lang } = useTranslation()
return ( return (
<p className='mt-8 mb-8 text-base font-normal text-gray dark:text-gray-dark'> <p className='mt-8 mb-8 text-base font-normal text-gray dark:text-gray-dark'>
<Translation {t('home:about.description-bottom')}
i18nKey='home:about.description-bottom' {lang === 'fr' && (
components={[<br key='break' />]} <>
/> <br />
<br />
<a
href='/curriculum-vitae.html'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Mon Curriculum vitæ
</a>
</>
)}
</p> </p>
) )
} }

View File

@ -9,10 +9,10 @@ export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
return ( return (
<li className='mb-3 before:table after:clear-both after:table'> <li className='mb-3 before:table after:clear-both after:table'>
<strong className='float-left block w-28 text-xs font-bold uppercase text-black dark:text-white'> <strong className='float-left block w-28 text-sm font-bold text-black dark:text-white'>
{title} {title}
</strong> </strong>
<span className='profile-list__item-info ml-0 mb-4 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'> <span className='ml-0 mb-4 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
{link != null ? ( {link != null ? (
<a <a
className='text-gray hover:underline dark:text-gray-dark' className='text-gray hover:underline dark:text-gray-dark'

View File

@ -7,6 +7,7 @@ export const ProfileList: React.FC = () => {
return ( return (
<ul className='m-0 list-none p-0'> <ul className='m-0 list-none p-0'>
<ProfileItem title={t('home:about.full-name')} value='Théo LUDWIG' />
<ProfileItem title={t('home:about.birth-date')} value='31/03/2003' /> <ProfileItem title={t('home:about.birth-date')} value='31/03/2003' />
<ProfileItem title={t('home:about.nationality')} value='Alsace, France' /> <ProfileItem title={t('home:about.nationality')} value='Alsace, France' />
<ProfileItem <ProfileItem

View File

@ -5,7 +5,7 @@ import DivloLogo from 'public/images/divlo_logo.png'
export const ProfileLogo: React.FC = () => { export const ProfileLogo: React.FC = () => {
return ( return (
<div className='max-h-[370px] max-w-[370px] px-2 py-6'> <div className='max-h-[370px] max-w-[370px] px-2 py-6'>
<Image src={DivloLogo} alt='Divlo' /> <Image quality={100} src={DivloLogo} alt='Divlo' />
</div> </div>
) )
} }

View File

@ -28,7 +28,7 @@ export const SkillComponent: React.FC<SkillComponentProps> = (props) => {
rel='noopener noreferrer' rel='noopener noreferrer'
> >
<div className='text-center'> <div className='text-center'>
<Image width={60} height={60} alt={skill} src={image} /> <Image quality={100} width={60} height={60} alt={skill} src={image} />
<p className='mt-1'>{skill}</p> <p className='mt-1'>{skill}</p>
</div> </div>
</a> </a>

View File

@ -6,26 +6,14 @@ export const ShadowContainer: React.FC<ShadowContainerProps> = (props) => {
const { children, className, ...rest } = props const { children, className, ...rest } = props
return ( return (
<>
<div <div
className={classNames( className={classNames(
'shadow-container mb-12 h-full max-w-full break-words', 'mb-12 h-full max-w-full break-words rounded-2xl border border-solid border-[#000] shadow-light dark:shadow-dark ',
className className
)} )}
{...rest} {...rest}
> >
{children} {children}
</div> </div>
<style jsx>
{`
.shadow-container {
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
}
`}
</style>
</>
) )
} }

4
jsonresume-theme-custom/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
theme/index.html
dist
.parcel-cache

View File

@ -0,0 +1,32 @@
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
const date = require('date-and-time')
const { Parcel } = require('@parcel/core')
const render = async (resume) => {
const themeIndexPath = path.join(__dirname, 'theme', 'index.ejs')
const themeBuildPath = path.join(__dirname, 'theme', 'index.html')
const indexHTMLPath = path.join(__dirname, 'dist', 'index.html')
const html = await ejs.renderFile(themeIndexPath, {
date,
locals: {
...resume
}
})
await fs.promises.writeFile(themeBuildPath, html, { encoding: 'utf-8' })
const bundler = new Parcel({
entries: themeBuildPath,
source: themeBuildPath,
mode: 'production',
defaultConfig: '@parcel/config-default'
})
await bundler.run()
return await fs.promises.readFile(indexHTMLPath, { encoding: 'utf-8' })
}
module.exports = {
render
}

4953
jsonresume-theme-custom/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
{
"name": "jsonresume-theme-custom",
"private": true,
"version": "1.0.0",
"scripts": {},
"dependencies": {
"date-and-time": "2.1.2",
"ejs": "3.1.6",
"modern-normalize": "1.1.0"
},
"devDependencies": {
"@parcel/config-default": "2.3.2",
"@parcel/core": "2.3.2",
"@parcel/optimizer-data-url": "^2.3.2",
"@parcel/transformer-inline-string": "^2.3.2",
"parcel": "2.3.2"
}
}

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M243.4 2.587C251.4-.8625 260.6-.8625 268.6 2.587L492.6 98.59C506.6 104.6 514.4 119.6 511.3 134.4C508.3 149.3 495.2 159.1 479.1 160V168C479.1 181.3 469.3 192 455.1 192H55.1C42.74 192 31.1 181.3 31.1 168V160C16.81 159.1 3.708 149.3 .6528 134.4C-2.402 119.6 5.429 104.6 19.39 98.59L243.4 2.587zM256 128C273.7 128 288 113.7 288 96C288 78.33 273.7 64 256 64C238.3 64 224 78.33 224 96C224 113.7 238.3 128 256 128zM127.1 416H167.1V224H231.1V416H280V224H344V416H384V224H448V420.3C448.6 420.6 449.2 420.1 449.8 421.4L497.8 453.4C509.5 461.2 514.7 475.8 510.6 489.3C506.5 502.8 494.1 512 480 512H31.1C17.9 512 5.458 502.8 1.372 489.3C-2.715 475.8 2.515 461.2 14.25 453.4L62.25 421.4C62.82 420.1 63.41 420.6 63.1 420.3V224H127.1V416z"/></svg>

After

Width:  |  Height:  |  Size: 1015 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M623.1 136.9l-282.7-101.2c-13.73-4.91-28.7-4.91-42.43 0L16.05 136.9C6.438 140.4 0 149.6 0 160s6.438 19.65 16.05 23.09L76.07 204.6c-11.89 15.8-20.26 34.16-24.55 53.95C40.05 263.4 32 274.8 32 288c0 9.953 4.814 18.49 11.94 24.36l-24.83 149C17.48 471.1 25 480 34.89 480H93.11c9.887 0 17.41-8.879 15.78-18.63l-24.83-149C91.19 306.5 96 297.1 96 288c0-10.29-5.174-19.03-12.72-24.89c4.252-17.76 12.88-33.82 24.94-47.03l190.6 68.23c13.73 4.91 28.7 4.91 42.43 0l282.7-101.2C633.6 179.6 640 170.4 640 160S633.6 140.4 623.1 136.9zM351.1 314.4C341.7 318.1 330.9 320 320 320c-10.92 0-21.69-1.867-32-5.555L142.8 262.5L128 405.3C128 446.6 213.1 480 320 480c105.1 0 192-33.4 192-74.67l-14.78-142.9L351.1 314.4z"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M502.6 182.6l-45.25-45.25C451.4 131.4 443.3 128 434.8 128H384V80C384 53.5 362.5 32 336 32h-160C149.5 32 128 53.5 128 80V128H77.25c-8.5 0-16.62 3.375-22.62 9.375L9.375 182.6C3.375 188.6 0 196.8 0 205.3V304h128v-32C128 263.1 135.1 256 144 256h32C184.9 256 192 263.1 192 272v32h128v-32C320 263.1 327.1 256 336 256h32C376.9 256 384 263.1 384 272v32h128V205.3C512 196.8 508.6 188.6 502.6 182.6zM336 128h-160V80h160V128zM384 368c0 8.875-7.125 16-16 16h-32c-8.875 0-16-7.125-16-16v-32H192v32C192 376.9 184.9 384 176 384h-32C135.1 384 128 376.9 128 368v-32H0V448c0 17.62 14.38 32 32 32h448c17.62 0 32-14.38 32-32v-112h-128V368z"/></svg>

After

Width:  |  Height:  |  Size: 912 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM274.7 304H173.3C77.61 304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304z"/></svg>

After

Width:  |  Height:  |  Size: 528 B

View File

@ -0,0 +1,206 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= locals.basics.name %></title>
<link rel="icon" type="image/png" href="<%= locals.basics.image %>" />
<style>
@import './styles/global.css';
</style>
</head>
<body>
<div class="container-fluid">
<div class="row main clearfix">
<section class="col-md-3 card-wrapper profile-card-wrapper affix">
<div class="card profile-card">
<div class="profile-pic-container">
<div class="profile-pic">
<img
class="media-object img-circle center-block"
data-src="holder.js/100x100"
alt="<%= locals.basics.name %>"
src="<%= locals.basics.image %>"
/>
</div>
<div class="name-and-profession text-center">
<h3>
<strong><%= locals.basics.name %></strong>
</h3>
<h5 class="text-muted"><%= locals.basics.label %></h5>
</div>
</div>
<div class="contact-details clearfix">
<div class="detail">
<span class="info"><%= locals.basics.phone %></span>
</div>
<div class="detail">
<span class="info">
<a
class="link-disguise"
href="mailto:<%= locals.basics.email %>"
>
<%= locals.basics.email %>
</a>
</span>
</div>
<div class="detail">
<span class="info">
<a class="link-disguise" href="<%= locals.basics.url %>">
<%= locals.basics.url %>
</a>
</span>
</div>
</div>
<hr />
</div>
<div class="card background-card">
<div class="background-details">
<div class="detail" id="about">
<div class="icon">
<img src="data-url:./images/user.svg" alt="user" />
</div>
<div class="info">
<h4 class="title text-uppercase">À propos</h4>
<div class="card card-nested">
<div class="content mop-wrapper">
<p><%- locals.basics.summary %></p>
</div>
</div>
</div>
</div>
<hr />
<div class="detail" id="work-experience">
<div class="icon">
<img
src="data-url:./images/building-columns.svg"
alt="work"
/>
</div>
<div class="info">
<h4 class="title text-uppercase">Expériences</h4>
<ul class="list-unstyled clear-margin">
<% locals.work.forEach((experience) => { %>
<li class="card card-nested clearfix">
<div class="content">
<p class="clear-margin relative">
<a href="<%= experience.url %>">
<strong><%= experience.name %></strong>
</a>
</p>
<p class="clear-margin relative">
<strong><%- experience.position %></strong>
</p>
<p class="text-muted">
<small>
<span class="space-right">
<%= date.format(new Date(experience.startDate),
'DD/MM/YYYY') %> - <%= date.format(new
Date(experience.endDate), 'DD/MM/YYYY') %>
</span>
</small>
</p>
<div class="experience-description">
<p><%- experience.summary %></p>
</div>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
<hr />
<div class="detail" id="skills">
<div class="icon">
<img src="data-url:./images/toolbox.svg" alt="toolbox" />
</div>
<div class="info">
<h4 class="title text-uppercase">Compétences</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.skills.forEach((skill) => { %>
<li class="card card-nested card-skills">
<div class="skill-info">
<strong><%= skill.name %></strong>
<div class="space-top labels">
<% skill.keywords.forEach((keyword) => { %>
<p class="label label-keyword"><%= keyword %></p>
<% }) %>
</div>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
<hr />
<div class="detail" id="education">
<div class="icon">
<img
src="data-url:./images/graduation-cap.svg"
alt="graduation"
/>
</div>
<div class="info">
<h4 class="title text-uppercase">Éducation</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.education.forEach((degree) => { %>
<li class="card card-nested">
<div class="content">
<p class="clear-margin relative">
<strong><%= degree.studyType %></strong>
</p>
<p class="clear-margin relative">
<strong><%= degree.score %></strong>
</p>
<p class="text-muted clear-margin">
<%= degree.institution %>
</p>
<p class="text-muted clear-margin">
<small>
<%= degree.startDate %> - <%= degree.endDate %>
</small>
</p>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
<hr />
<div class="detail" id="interests">
<div class="icon">
<img src="data-url:./images/heart.svg" alt="heart" />
</div>
<div class="info">
<h4 class="title text-uppercase">Intérets</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.interests.forEach((interest) => { %>
<li class="card card-nested">
<p><strong><%= interest.name %></strong></p>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,229 @@
@import 'npm:modern-normalize/modern-normalize.css';
body {
font-family: 'Montserrat', 'Arial', 'sans-serif';
background: #f0f0f0;
color: #333;
line-height: 1.42857143;
font-size: 14px;
}
hr {
margin-top: 15px;
margin-bottom: 15px;
border: 0;
border-top: 1px solid #eee;
}
p {
margin: 0;
}
strong {
font-weight: 600;
}
a {
color: #337ab7;
text-decoration: none;
}
a:focus,
a:hover {
color: #23527c;
text-decoration: underline;
}
.link-disguise {
color: inherit;
}
.link-disguise:hover {
color: inherit;
}
.h1,
.h2,
.h3,
h1,
h2,
h3 {
margin-top: 20px;
margin-bottom: 10px;
}
.h4,
.h5,
.h6,
h4,
h5,
h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.h3,
h3 {
font-size: 24px;
}
.h4,
h4 {
font-size: 18px;
}
.h5,
h5 {
font-size: 14px;
}
.container-fluid {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.clear-margin {
margin: 0;
}
.relative {
position: relative;
}
.center-block {
display: block;
margin-right: auto;
margin-left: auto;
}
.text-center {
text-align: center;
}
.text-muted {
color: #777;
}
.text-uppercase {
text-transform: uppercase;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
.main {
padding: 5px;
}
.title {
font-weight: 600;
}
.profile-card-wrapper {
position: relative;
}
.card-wrapper {
float: none !important;
padding: 5px;
}
.profile-card-wrapper .profile-card {
padding: 10px;
}
.card {
background: white;
border-radius: 3px;
padding: 10px 0;
}
.profile-pic {
padding: 10px 0;
}
.profile-pic img {
width: 100px;
height: 100px;
border-radius: 50%;
vertical-align: middle;
border: 0;
}
.contact-details {
display: flex;
justify-content: center;
}
.contact-details .detail {
position: relative;
min-height: 1px;
padding: 10px;
}
.social-links {
line-height: 2.5;
}
.experience-description {
margin-top: 10px;
}
.background-details .detail {
display: table;
}
.background-details .detail .icon,
.background-details .detail .info {
display: table-cell;
}
.background-details .detail .icon {
color: #707070;
}
.background-details .detail .icon {
min-width: 45px;
max-width: 45px;
text-align: center;
}
.icon img {
width: 20px;
height: 20px;
}
.background-details .detail .mobile-title {
display: none;
}
.card-nested {
min-height: 0;
border-width: 1px 0 0 0;
}
.card-skills {
position: relative;
}
.labels {
line-height: 2;
}
.space-top {
margin-top: 10px;
}
.label {
display: inline;
padding: 0.2em 0.6em 0.3em;
font-size: 75%;
font-weight: 600;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25em;
}
.label-keyword {
display: inline-block;
background: #7eb0db;
color: white;
font-size: 0.9em;
padding: 5px;
border: 1px solid #357ebd;
margin-right: 5px;
}
.label-keyword p {
margin: 0;
}

View File

@ -2,9 +2,10 @@
"about": { "about": {
"i-am": "I am", "i-am": "I am",
"description": "Developer Full Stack Junior • Passionate about High-Tech", "description": "Developer Full Stack Junior • Passionate about High-Tech",
"full-name": "Full name",
"birth-date": "Birth date", "birth-date": "Birth date",
"nationality": "Nationality", "nationality": "Nationality",
"description-bottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (first year). <0/> <0/> I put into practice everything I learn and make many projects." "description-bottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (first year)."
}, },
"interests": { "interests": {
"title": "Interests", "title": "Interests",

View File

@ -2,9 +2,10 @@
"about": { "about": {
"i-am": "Je suis", "i-am": "Je suis",
"description": "Développeur Full Stack Junior • Passionné de High-Tech", "description": "Développeur Full Stack Junior • Passionné de High-Tech",
"full-name": "Prénom NOM",
"birth-date": "Date de naissance", "birth-date": "Date de naissance",
"nationality": "Nationalité", "nationality": "Nationalité",
"description-bottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (première année). <0/> <0/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets." "description-bottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (première année)."
}, },
"interests": { "interests": {
"title": "Intérêts", "title": "Intérêts",

View File

@ -18,7 +18,12 @@ module.exports = nextTranslate(
contentSecurityPolicy: { contentSecurityPolicy: {
directives: { directives: {
defaultSrc: ["'self'"], defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"], scriptSrc: [
"'self'",
'data:',
"'unsafe-eval'",
"'unsafe-inline'"
],
styleSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ['*', 'data:', 'blob:'], imgSrc: ['*', 'data:', 'blob:'],
mediaSrc: "'none'", mediaSrc: "'none'",

13055
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"start": "next start", "start": "next start",
"build": "next build", "build": "npm run resume:export && next build",
"export": "next export", "export": "next export",
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
@ -26,6 +26,9 @@
"test:lighthouse": "lhci autorun", "test:lighthouse": "lhci autorun",
"test:e2e": "start-server-and-test \"start\" \"http://localhost:3000\" \"cypress run\"", "test:e2e": "start-server-and-test \"start\" \"http://localhost:3000\" \"cypress run\"",
"test:e2e:dev": "start-server-and-test \"dev\" \"http://localhost:3000\" \"cypress open\"", "test:e2e:dev": "start-server-and-test \"dev\" \"http://localhost:3000\" \"cypress open\"",
"resume:validate": "resume validate",
"resume:serve": "resume serve --theme \"custom\"",
"resume:export": "resume export \"./public/curriculum-vitae.html\" --format \"html\" --theme \"custom\"",
"release": "semantic-release", "release": "semantic-release",
"deploy": "vercel", "deploy": "vercel",
"postinstall": "husky install" "postinstall": "husky install"
@ -39,19 +42,19 @@
"classnames": "2.3.1", "classnames": "2.3.1",
"date-and-time": "2.1.2", "date-and-time": "2.1.2",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"highlight.js": "11.4.0",
"html-react-parser": "1.4.8", "html-react-parser": "1.4.8",
"next": "12.1.0", "next": "12.1.0",
"next-mdx-remote": "4.0.0", "next-mdx-remote": "4.0.0",
"next-pwa": "5.4.4", "next-pwa": "5.4.4",
"next-themes": "0.0.15", "next-themes": "0.1.1",
"next-translate": "1.3.4", "next-translate": "1.3.4",
"prism-themes": "1.9.0",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"read-pkg": "7.1.0", "read-pkg": "7.1.0",
"rehype-highlight": "5.0.2",
"rehype-slug": "5.0.1", "rehype-slug": "5.0.1",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"remark-prism": "1.3.6",
"sharp": "0.30.1", "sharp": "0.30.1",
"universal-cookie": "4.0.4" "universal-cookie": "4.0.4"
}, },
@ -66,31 +69,32 @@
"@testing-library/react": "12.1.3", "@testing-library/react": "12.1.3",
"@types/date-and-time": "0.13.0", "@types/date-and-time": "0.13.0",
"@types/jest": "27.4.0", "@types/jest": "27.4.0",
"@types/node": "17.0.18", "@types/node": "17.0.19",
"@types/react": "17.0.39", "@types/react": "17.0.39",
"@types/remark-prism": "1.3.0", "@typescript-eslint/eslint-plugin": "5.12.1",
"@typescript-eslint/eslint-plugin": "5.12.0",
"autoprefixer": "10.4.2", "autoprefixer": "10.4.2",
"cypress": "9.5.0", "cypress": "9.5.0",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"eslint": "8.9.0", "eslint": "8.9.0",
"eslint-config-conventions": "1.1.0",
"eslint-config-next": "12.1.0", "eslint-config-next": "12.1.0",
"eslint-config-prettier": "8.4.0", "eslint-config-prettier": "8.4.0",
"eslint-config-conventions": "1.1.0",
"eslint-plugin-import": "2.25.4", "eslint-plugin-import": "2.25.4",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "4.0.0",
"eslint-plugin-promise": "6.0.0", "eslint-plugin-promise": "6.0.0",
"eslint-plugin-unicorn": "41.0.0", "eslint-plugin-unicorn": "41.0.0",
"html-w3c-validator": "1.0.0",
"husky": "7.0.4", "husky": "7.0.4",
"jest": "27.5.1", "jest": "27.5.1",
"jsonresume-theme-custom": "file:./jsonresume-theme-custom",
"lint-staged": "12.3.4", "lint-staged": "12.3.4",
"markdownlint-cli": "0.31.1", "markdownlint-cli": "0.31.1",
"next-secure-headers": "2.2.0", "next-secure-headers": "2.2.0",
"postcss": "8.4.6", "postcss": "8.4.6",
"prettier": "2.5.1", "prettier": "2.5.1",
"prettier-plugin-tailwindcss": "0.1.7", "prettier-plugin-tailwindcss": "0.1.7",
"resume-cli": "3.0.6",
"semantic-release": "19.0.2", "semantic-release": "19.0.2",
"html-w3c-validator": "1.0.0",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"tailwindcss": "3.0.23", "tailwindcss": "3.0.23",
"typescript": "4.5.5", "typescript": "4.5.5",

View File

@ -1,7 +1,7 @@
import { GetStaticProps, GetStaticPaths, NextPage } from 'next' import { GetStaticProps, GetStaticPaths, NextPage } from 'next'
import { MDXRemote } from 'next-mdx-remote' import { MDXRemote } from 'next-mdx-remote'
import date from 'date-and-time' import date from 'date-and-time'
import 'prism-themes/themes/prism-one-dark.css' import 'highlight.js/styles/github-dark.css'
import { Head } from 'components/Head' import { Head } from 'components/Head'
import { Header } from 'components/Header' import { Header } from 'components/Header'

View File

@ -38,7 +38,7 @@ const BlogPage: NextPage<BlogPageProps> = (props) => {
'DD/MM/YYYY' 'DD/MM/YYYY'
) )
return ( return (
<Link href={`/blog/${post.slug}`} key={index}> <Link href={`/blog/${post.slug}`} key={index} locale='en'>
<a data-cy='blog-post'> <a data-cy='blog-post'>
<ShadowContainer className='cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2'> <ShadowContainer className='cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2'>
<h2 <h2

119
resume.json Normal file
View File

@ -0,0 +1,119 @@
{
"$schema": "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json",
"meta": {
"theme": "custom"
},
"basics": {
"name": "Théo LUDWIG",
"label": "Développeur Full Stack Junior • Passionné de High-Tech",
"image": "https://s.gravatar.com/avatar/ebd6e0bf679562c20e28b5ffd02bf3e5?s=100&amp;r=pg&amp;d=mm",
"email": "contact@divlo.fr",
"location": {},
"url": "https://divlo.fr",
"summary": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (première année). <br/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets."
},
"education": [
{
"startDate": "2022",
"endDate": "2024",
"studyType": "Diplôme du Bachelor Universitaire de Technologie (BUT) Informatique",
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
"score": "En cours"
},
{
"startDate": "2019",
"endDate": "2021",
"studyType": "Diplôme du Baccalauréat Général (Mathématiques et Numériques Sciences Informatiques)",
"institution": "Lycée Heinrich Nessel à Haguenau",
"score": "Mention Assez Bien"
},
{
"startDate": "2014",
"endDate": "2018",
"studyType": "Diplôme national du brevet",
"institution": "Collège Gustave Doré à Hochfelden",
"score": "Mention Bien"
}
],
"work": [
{
"summary": "Développement site web en React.js et Strapi afin de répondre <a href=\"https://www.nuitdelinfo.com/nuitinfo/_media/infos:la_nuit_de_l_info_2021_-_sujet.pdf\">au sujet de la Nuit de l'Info 2021</a>.<br /> TOP 1 France: Défi de l'entreprise <a href=\"https://www.nuitdelinfo.com/inscription/defis/300\">ToolPad</a>.",
"website": "https://www.nuitdelinfo.com/",
"name": "La Nuit de l'info 2021",
"position": "Participation avec l'équipe <a href=\"https://www.nuitdelinfo.com/inscription/equipes/46\">Who are We</a>",
"startDate": "2021-07-07",
"endDate": "2021-07-30"
},
{
"summary": "Agent administratif en vue de faire face au sucroît temporaire d'activités liés à la numérisation des plans des postes sources <br /> actuellement sous format papier calque suite à la libération des locaux des archives.",
"website": "https://www.es.fr/",
"name": "ÉS (Électricité de Strasbourg)",
"location": "5 Rue André Marie Ampère, 67450 Mundolsheim",
"position": "Emploi d'été en qualité d'agent administratif",
"startDate": "2021-07-07",
"endDate": "2021-07-30"
},
{
"summary": "Hackathon développement d'une landing page et web scraping.",
"website": "https://www.wildcodeschool.fr/",
"name": "Wild Code School",
"location": "32 Rue du Bass. d'Austerlitz, 67100 Strasbourg",
"position": "Stage initiation métier développeur web",
"startDate": "2019-06-24",
"endDate": "2019-06-28"
},
{
"summary": "Développement d'un site web pour trouver un restaurant à la pause repas.",
"website": "https://www.itpartners.fr/",
"name": "Tribe | IT Partners",
"location": "16 Rue du Parc, 67205 Oberhausbergen",
"position": "Stage initiation métier développeur web",
"startDate": "2019-06-17",
"endDate": "2019-06-21"
},
{
"summary": "Apprentissage du métier \"Chargé de communication\" et des logiciels de graphisme tels que \"Adobe Photoshop\".",
"website": "https://www.es.fr/",
"name": "ÉS (Électricité de Strasbourg)",
"location": "26 Bd du Président-Wilson, 67000 Strasbourg",
"position": "Stage de découverte (3ème)",
"startDate": "2018-02-19",
"endDate": "2018-02-23"
}
],
"interests": [
{
"name": "Développeur Full Stack Junior"
},
{
"name": "Passionné de High-Tech"
},
{
"name": "Enthousiaste de l'Open-Source"
}
],
"skills": [
{
"keywords": ["JavaScript", "TypeScript", "Python", "C/C++"],
"name": "Langages de programmation"
},
{
"keywords": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
"name": "Front-end"
},
{
"keywords": ["Node.js", "Fastify", "PostgreSQL", "MySQL"],
"name": "Back-end"
},
{
"keywords": [
"GNU/Linux",
"Ubuntu",
"Visual Studio Code",
"git",
"Docker"
],
"name": "Logiciels et outils"
}
]
}

View File

@ -23,12 +23,16 @@
@apply mt-1 text-gray dark:text-gray-dark; @apply mt-1 text-gray dark:text-gray-dark;
} }
.prose code { .prose code:not(.hljs) {
color: hsl(286, 60%, 67%); color: hsl(286, 60%, 67%);
} }
.prose pre {
code[class*='language-'], background-color: transparent !important;
pre[class*='language-'] { }
.prose pre code {
border-radius: 10px;
}
.hljs {
white-space: pre-wrap !important; white-space: pre-wrap !important;
word-break: break-word !important; word-break: break-word !important;
word-wrap: normal; word-wrap: normal;

View File

@ -21,8 +21,10 @@ module.exports = {
} }
}, },
boxShadow: { boxShadow: {
dark: '0px 1px 10px hsla(0, 0%, 100%, 0.2)', dark: '0px 0px 6px 6px rgba(0, 0, 0, 0.25)',
light: '0px 1px 10px rgba(0, 0, 0, 0.25)' light: '0px 0px 6px 6px rgba(0, 0, 0, 0.10)',
darkFlag: '0px 1px 10px hsla(0, 0%, 100%, 0.2)',
lightFlag: '0px 1px 10px rgba(0, 0, 0, 0.25)'
}, },
fontFamily: { fontFamily: {
headline: "'Montserrat', 'Arial', 'sans-serif'" headline: "'Montserrat', 'Arial', 'sans-serif'"

View File

@ -4,8 +4,8 @@ import path from 'node:path'
import type { MDXRemoteSerializeResult } from 'next-mdx-remote' import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
import { serialize } from 'next-mdx-remote/serialize' import { serialize } from 'next-mdx-remote/serialize'
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import remarkPrism from 'remark-prism'
import rehypeSlug from 'rehype-slug' import rehypeSlug from 'rehype-slug'
import rehypeHighlight from 'rehype-highlight'
import matter from 'gray-matter' import matter from 'gray-matter'
export const postsPath = path.join(process.cwd(), 'posts') export const postsPath = path.join(process.cwd(), 'posts')
@ -64,8 +64,8 @@ export const getPostBySlug = async (
} }
const source = await serialize(post.content, { const source = await serialize(post.content, {
mdxOptions: { mdxOptions: {
remarkPlugins: [remarkGfm as any, remarkPrism], remarkPlugins: [remarkGfm as any],
rehypePlugins: [rehypeSlug as any] rehypePlugins: [rehypeSlug as any, rehypeHighlight]
} }
}) })
return { ...post, source } return { ...post, source }