1
1
mirror of https://github.com/theoludwig/theoludwig.git synced 2025-05-29 22:37:44 +02:00

Compare commits

...

11 Commits

Author SHA1 Message Date
8a59e9034f chore(release): 3.2.6 [skip ci] 2024-05-21 18:23:28 +00:00
d7121ea833 style: fix tailwindcss linting 2024-05-21 20:18:05 +02:00
c10f690622 build(deps): update dependencies to latest 2024-05-21 20:15:57 +02:00
6915072ab9 chore: delete unused config 2024-05-21 19:31:45 +02:00
dd803bcc51 test: fix should display hello-world blog post 2024-05-21 19:17:29 +02:00
efa33f26ec fix(blog): headings should be aligned with the text, not shifted 2024-05-21 19:06:12 +02:00
5f3dfad988 chore(release): 3.2.5 [skip ci] 2024-05-16 08:09:25 +00:00
b231381cb3 fix: client-side age calculation, more glanular check for isMounted
Allows to render as much as possible on the server side.
While keeping the calculation of the age on the client side to avoid hydratation mismatch.
2024-05-16 10:06:43 +02:00
bbb2e56512 fix: usage of correct heading levels and html tags 2024-05-16 09:56:19 +02:00
66cf6d7438 fix: add scroll behavior: smooth 2024-05-16 09:32:20 +02:00
2a635bf3ba fix: add hover effects 2024-05-16 09:26:05 +02:00
27 changed files with 2946 additions and 1440 deletions

View File

@ -1 +0,0 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:20

View File

@ -1,9 +0,0 @@
services:
workspace:
build:
context: "./"
dockerfile: "./Dockerfile"
volumes:
- "..:/workspace:cached"
command: "sleep infinity"
network_mode: "host"

View File

@ -1,24 +0,0 @@
{
"name": "theoludwig",
"dockerComposeFile": "./compose.yaml",
"service": "workspace",
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"settings": {
"remote.autoForwardPorts": false,
"remote.localPortHost": "allInterfaces"
}
},
"extensions": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"ms-azuretools.vscode-docker"
]
},
"remoteUser": "node"
}

View File

@ -3,13 +3,9 @@
"extends": [ "extends": [
"conventions", "conventions",
"next/core-web-vitals", "next/core-web-vitals",
"plugin:tailwindcss/recommended", "plugin:tailwindcss/recommended"
"prettier"
], ],
"plugins": ["prettier"], "plugins": ["import", "promise", "unicorn"],
"parserOptions": {
"project": "./tsconfig.json"
},
"settings": { "settings": {
"tailwindcss": { "tailwindcss": {
"callees": ["classNames"] "callees": ["classNames"]
@ -19,7 +15,6 @@
} }
}, },
"rules": { "rules": {
"prettier/prettier": "error",
"react/self-closing-comp": [ "react/self-closing-comp": [
"error", "error",
{ {
@ -33,7 +28,11 @@
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],
"parser": "@typescript-eslint/parser" "parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"parserOptions": {
"project": "./tsconfig.json"
}
} }
] ]
} }

View File

@ -1,10 +0,0 @@
image: "gitpod/workspace-full"
tasks:
- before: "cp .env.example .env"
init: "npm clean-install"
command: "npm run dev"
ports:
- port: 3000
onOpen: "open-preview"

View File

@ -29,8 +29,6 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv
## Getting Started ## Getting Started
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/theoludwig/theoludwig)
### Prerequisites ### Prerequisites
- [Node.js](https://nodejs.org/) >= 20.0.0 - [Node.js](https://nodejs.org/) >= 20.0.0
@ -66,6 +64,6 @@ npm run dev
docker compose up --build docker compose up --build
``` ```
### Services started ### Service started
- `website`: <http://127.0.0.1:3000> `website`: <http://127.0.0.1:3000>

View File

@ -57,10 +57,13 @@ const RootLayout = (props: RootLayoutProps): JSX.Element => {
return ( return (
<html <html
lang={i18n.locale} lang={i18n.locale}
className={classNames({ className={classNames(
dark: theme === "dark", {
light: theme === "light", dark: theme === "dark",
})} light: theme === "light",
},
"scroll-smooth",
)}
style={{ style={{
colorScheme: theme, colorScheme: theme,
}} }}

View File

@ -26,14 +26,14 @@ const Heading = (
): JSX.Element => { ): JSX.Element => {
const { children, id = "" } = props const { children, id = "" } = props
return ( return (
<h2 {...props} className="group"> <h2 {...props}>
<Link <Link href={`#${id}`} className="group relative hover:no-underline">
href={`#${id}`} <FontAwesomeIcon
className="invisible !text-black group-hover:visible dark:!text-white" className="absolute bottom-2 left-[-26px] mr-2 hidden size-4 !text-black group-hover:inline dark:!text-white"
> icon={faLink}
<FontAwesomeIcon className="mr-2 inline size-4" icon={faLink} /> />
{children}
</Link> </Link>
{children}
</h2> </h2>
) )
} }

View File

@ -9,37 +9,34 @@ export const BlogPosts = async (): Promise<JSX.Element> => {
return ( return (
<div className="flex w-full items-center justify-center p-8"> <div className="flex w-full items-center justify-center p-8">
<div className="w-[1600px]" data-cy="blog-posts"> <ul className="w-[1600px]" data-cy="blog-posts">
{posts.map((post, index) => { {posts.map((post) => {
const postPublishedOn = date.format( const postPublishedOn = date.format(
new Date(post.frontmatter.publishedOn), new Date(post.frontmatter.publishedOn),
"DD/MM/YYYY", "DD/MM/YYYY",
) )
return ( return (
<Link <li key={post.slug}>
href={`/blog/${post.slug}`} <Link href={`/blog/${post.slug}`} locale="en" data-cy={post.slug}>
key={index} <ShadowContainer className="cursor-pointer p-6 transition-all duration-300 ease-in-out hover:scale-[1.02]">
locale="en" <h2
data-cy={post.slug} data-cy="blog-post-title"
> className="text-xl font-semibold text-primary dark:text-primary-dark"
<ShadowContainer className="cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2"> >
<h2 {post.frontmatter.title}
data-cy="blog-post-title" </h2>
className="text-xl font-semibold text-primary dark:text-primary-dark" <p data-cy="blog-post-date" className="mt-2">
> {postPublishedOn}
{post.frontmatter.title} </p>
</h2> <p data-cy="blog-post-description" className="mt-3">
<p data-cy="blog-post-date" className="mt-2"> {post.frontmatter.description}
{postPublishedOn} </p>
</p> </ShadowContainer>
<p data-cy="blog-post-description" className="mt-3"> </Link>
{post.frontmatter.description} </li>
</p>
</ShadowContainer>
</Link>
) )
})} })}
</div> </ul>
</div> </div>
) )
} }

View File

@ -58,7 +58,7 @@ export const SwitchTheme = (props: SwitchThemeProps): JSX.Element => {
</div> </div>
<div <div
className={classNames( className={classNames(
"absolute top-[1px] box-border size-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out", "absolute top-px box-border size-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out",
{ {
"left-[27px]": theme === "dark", "left-[27px]": theme === "dark",
"left-0": theme === "light", "left-0": theme === "light",
@ -70,7 +70,7 @@ export const SwitchTheme = (props: SwitchThemeProps): JSX.Element => {
data-cy="switch-theme-input" data-cy="switch-theme-input"
type="checkbox" type="checkbox"
aria-label="Dark mode toggle" aria-label="Dark mode toggle"
className="absolute m-[-1px] hidden size-[1px] overflow-hidden border-0 p-0" className="absolute -m-px hidden size-px overflow-hidden border-0 p-0"
defaultChecked defaultChecked
/> />
</div> </div>

View File

@ -14,8 +14,11 @@ export const Header = (): JSX.Element => {
return ( return (
<header className="sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black"> <header className="sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black">
<Link href="/"> <h1>
<h1 className="flex items-center justify-center"> <Link
href="/"
className="flex items-center justify-center transition-all duration-300 ease-in-out hover:scale-[1.03]"
>
<Image <Image
quality={100} quality={100}
className="size-16" className="size-16"
@ -26,8 +29,8 @@ export const Header = (): JSX.Element => {
<strong className="ml-1 hidden font-headline font-semibold text-primary dark:text-primary-dark sm:block sm:text-xl"> <strong className="ml-1 hidden font-headline font-semibold text-primary dark:text-primary-dark sm:block sm:text-xl">
Théo LUDWIG Théo LUDWIG
</strong> </strong>
</h1> </Link>
</Link> </h1>
<div className="flex justify-between"> <div className="flex justify-between">
<div className="flex flex-col items-center justify-center px-6"> <div className="flex flex-col items-center justify-center px-6">
<Link <Link

View File

@ -20,14 +20,11 @@ export const InterestParagraph = (
const { title, description } = props const { title, description } = props
return ( return (
<> <div className="my-6 text-center text-gray dark:text-gray-dark">
<p className="my-6 text-center text-gray dark:text-gray-dark"> <h3 className="text-lg font-semibold text-primary dark:text-primary-dark">
<strong className="text-lg font-semibold text-primary dark:text-primary-dark"> {title}
{title} </h3>
</strong> <p className="my-2">{htmlParser(description)}</p>
<br /> </div>
<span>{htmlParser(description)}</span>
</p>
</>
) )
} }

View File

@ -16,8 +16,8 @@ export const Interests = (): JSX.Element => {
return ( return (
<div className="max-w-full"> <div className="max-w-full">
{paragraphs.map((paragraph, index) => { {paragraphs.map((paragraph) => {
return <InterestParagraph key={index} {...paragraph} /> return <InterestParagraph key={paragraph.id} {...paragraph} />
})} })}
<InterestsList /> <InterestsList />
</div> </div>

View File

@ -11,16 +11,18 @@ export const Repository = (props: RepositoryProps): JSX.Element => {
const { name, description, href } = props const { name, description, href } = props
return ( return (
<ShadowContainer className="relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2"> <li>
<a href={href} target="_blank" rel="noopener noreferrer"> <a href={href} target="_blank" rel="noopener noreferrer">
<div className="flex"> <ShadowContainer className="relative !mb-4 max-h-32 cursor-pointer p-6 transition-all duration-300 ease-in-out hover:scale-[1.03]">
<GitHubIcon className="mr-2 h-6" /> <h3 className="flex">
<span className="font-semibold text-primary dark:text-primary-dark"> <GitHubIcon className="mr-2 h-6" />
{name} <span className="font-semibold text-primary dark:text-primary-dark">
</span> {name}
</div> </span>
<p className="my-4">{description}</p> </h3>
<p className="my-4">{description}</p>
</ShadowContainer>
</a> </a>
</ShadowContainer> </li>
) )
} }

View File

@ -10,7 +10,7 @@ export const OpenSource = (): JSX.Element => {
<p className="text-center"> <p className="text-center">
{i18n.translate("home.open-source.description")} {i18n.translate("home.open-source.description")}
</p> </p>
<div className="my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2"> <ul className="my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2">
<Repository <Repository
name="nodejs/node" name="nodejs/node"
description="Node.js JavaScript runtime ✨🐢🚀✨" description="Node.js JavaScript runtime ✨🐢🚀✨"
@ -31,7 +31,7 @@ export const OpenSource = (): JSX.Element => {
description="The React Framework" description="The React Framework"
href="https://github.com/vercel/next.js/commits?author=theoludwig" href="https://github.com/vercel/next.js/commits?author=theoludwig"
/> />
</div> </ul>
</div> </div>
) )
} }

View File

@ -13,7 +13,7 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
const { title, description, link, image } = props const { title, description, link, image } = props
return ( return (
<ShadowContainer className="relative cursor-pointer items-center sm:ml-10"> <li>
<a <a
className="group inline-flex justify-center" className="group inline-flex justify-center"
target="_blank" target="_blank"
@ -21,23 +21,25 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
href={link} href={link}
aria-label={title} aria-label={title}
> >
<div className="flex justify-center"> <ShadowContainer className="relative cursor-pointer items-center sm:ml-10">
<Image <div className="flex justify-center">
quality={100} <Image
className="size-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5" quality={100}
width={300} className="size-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5"
height={300} width={300}
src={image} height={300}
alt={title} src={image}
/> alt={title}
</div> />
<div className="absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100"> </div>
<h3 className="my-6 text-2xl font-semibold text-primary dark:text-primary-dark"> <div className="absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100">
{title} <h3 className="my-6 text-2xl font-semibold text-primary dark:text-primary-dark">
</h3> {title}
<p className="mx-4 my-6 font-semibold">{description}</p> </h3>
</div> <p className="mx-4 my-6 font-semibold">{description}</p>
</div>
</ShadowContainer>
</a> </a>
</ShadowContainer> </li>
) )
} }

View File

@ -12,10 +12,10 @@ export const Portfolio = (): JSX.Element => {
} }
return ( return (
<div className="flex w-full flex-wrap justify-center px-3"> <ul className="flex w-full flex-wrap justify-center px-3">
{items.map((item, index) => { {items.map((item) => {
return <PortfolioItem key={index} {...item} /> return <PortfolioItem key={item.title} {...item} />
})} })}
</div> </ul>
) )
} }

View File

@ -30,14 +30,17 @@ export const ProfileList = (props: ProfileListProps): JSX.Element => {
title={i18n.translate("home.about.pronouns")} title={i18n.translate("home.about.pronouns")}
value={i18n.translate("home.about.pronouns-value")} value={i18n.translate("home.about.pronouns-value")}
/> />
{isMounted ? ( <ProfileItem
<ProfileItem title={i18n.translate("home.about.birth-date")}
title={i18n.translate("home.about.birth-date")} value={
value={`${BIRTH_DATE_STRING} (${age} ${i18n.translate( isMounted
"home.about.years-old", ? `${BIRTH_DATE_STRING} (${age} ${i18n.translate(
)})`} "home.about.years-old",
/> )})`
) : null} : BIRTH_DATE_STRING
}
/>
<ProfileItem <ProfileItem
title={i18n.translate("home.about.nationality")} title={i18n.translate("home.about.nationality")}
value="Alsace, France" value="Alsace, France"

View File

@ -13,7 +13,7 @@ export const SocialMediaItem = (props: SocialMediaItemProps): JSX.Element => {
aria-label={ariaLabel} aria-label={ariaLabel}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="relative inline-block bg-transparent" className="relative inline-block bg-transparent transition-all duration-300 ease-in-out hover:scale-110"
> >
{children} {children}
</a> </a>

View File

@ -27,13 +27,13 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
} }
return ( return (
<a <li>
href={skillProperties.link} <a
className="mx-2 max-w-xl text-primary hover:underline dark:text-primary-dark" href={skillProperties.link}
target="_blank" className="mx-2 flex max-w-xl flex-col items-center justify-center text-center text-primary hover:underline dark:text-primary-dark"
rel="noopener noreferrer" target="_blank"
> rel="noopener noreferrer"
<div className="text-center"> >
<Image <Image
className="inline size-16" className="inline size-16"
quality={100} quality={100}
@ -43,7 +43,7 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
src={getImage()} src={getImage()}
/> />
<p className="mt-1 font-semibold">{skill}</p> <p className="mt-1 font-semibold">{skill}</p>
</div> </a>
</a> </li>
) )
} }

View File

@ -18,7 +18,7 @@ export const SkillsSection = (props: SkillsSectionProps): JSX.Element => {
{title} {title}
</h3> </h3>
</div> </div>
<div className="flex flex-wrap justify-around">{children}</div> <ul className="flex flex-wrap justify-around">{children}</ul>
</div> </div>
</div> </div>
</div> </div>

View File

@ -57,8 +57,8 @@ export const Section = (props: SectionProps): JSX.Element => {
</p> </p>
) : null} ) : null}
<div className="w-full px-3"> <div className="w-full px-3">
<ShadowContainer> <ShadowContainer className="w-full px-2 py-4 leading-8 sm:px-16">
<div className="w-full px-16 py-4 leading-8">{children}</div> {children}
</ShadowContainer> </ShadowContainer>
</div> </div>
</section> </section>

View File

@ -12,9 +12,9 @@
"modern-normalize": "2.0.0" "modern-normalize": "2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.12.7", "@types/node": "20.12.12",
"date-and-time": "3.1.1", "date-and-time": "3.3.0",
"vite": "5.2.8", "vite": "5.2.11",
"vite-plugin-html": "3.2.2" "vite-plugin-html": "3.2.2"
} }
}, },
@ -694,10 +694,11 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.12.7", "version": "20.12.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@ -896,10 +897,11 @@
} }
}, },
"node_modules/date-and-time": { "node_modules/date-and-time": {
"version": "3.1.1", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.1.1.tgz", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.3.0.tgz",
"integrity": "sha512-N9kstidT3P0VUk1iKOFilOZ6251r6iTUNx9M9kvgL2jqOk9mljWZUq5CjAtYwCnppWHbERk5YFQUrSbY7FQOpA==", "integrity": "sha512-UguWfh9LkUecVrGSE0B7SpAnGRMPATmpwSoSij24/lDnwET3A641abfDBD/TdL0T+E04f8NWlbMkD9BscVvIZg==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "1.4.1", "version": "1.4.1",
@ -1642,10 +1644,11 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.2.8", "version": "5.2.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.20.1", "esbuild": "^0.20.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",

View File

@ -13,9 +13,9 @@
"modern-normalize": "2.0.0" "modern-normalize": "2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "20.12.7", "@types/node": "20.12.12",
"date-and-time": "3.1.1", "date-and-time": "3.3.0",
"vite": "5.2.8", "vite": "5.2.11",
"vite-plugin-html": "3.2.2" "vite-plugin-html": "3.2.2"
} }
} }

View File

@ -3,7 +3,6 @@ describe("Page /blog/[slug]", () => {
cy.visit("/blog/hello-world") cy.visit("/blog/hello-world")
cy.get("[data-cy=locale-flag-text]").should("not.exist") cy.get("[data-cy=locale-flag-text]").should("not.exist")
cy.get("main h1").should("have.text", "👋 Hello, world!") cy.get("main h1").should("have.text", "👋 Hello, world!")
cy.get(".prose a:visible").should("have.attr", "target", "_blank")
}) })
it("should redirect to /404 if the blog post doesn't exist", () => { it("should redirect to /404 if the blog post doesn't exist", () => {

4018
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "theoludwig", "name": "theoludwig",
"version": "3.2.4", "version": "3.2.6",
"private": true, "private": true,
"repository": { "repository": {
"type": "git", "type": "git",
@ -29,63 +29,61 @@
"postinstall": "husky" "postinstall": "husky"
}, },
"dependencies": { "dependencies": {
"@fontsource/montserrat": "5.0.17", "@fontsource/montserrat": "5.0.18",
"@formatjs/intl-localematcher": "0.5.4", "@formatjs/intl-localematcher": "0.5.4",
"@fortawesome/fontawesome-svg-core": "6.5.2", "@fortawesome/fontawesome-svg-core": "6.5.2",
"@fortawesome/free-brands-svg-icons": "6.5.2", "@fortawesome/free-brands-svg-icons": "6.5.2",
"@fortawesome/free-solid-svg-icons": "6.5.2", "@fortawesome/free-solid-svg-icons": "6.5.2",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.1",
"@giscus/react": "3.0.0", "@giscus/react": "3.0.0",
"clsx": "2.1.0", "clsx": "2.1.1",
"date-and-time": "3.1.1", "date-and-time": "3.3.0",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"html-react-parser": "5.1.10", "html-react-parser": "5.1.10",
"i18n-js": "4.3.2", "i18n-js": "4.4.3",
"katex": "0.16.10", "katex": "0.16.10",
"negotiator": "0.6.3", "negotiator": "0.6.3",
"next": "14.1.0", "next": "14.1.0",
"next-mdx-remote": "4.4.1", "next-mdx-remote": "4.4.1",
"react": "18.2.0", "react": "18.3.1",
"react-dom": "18.2.0", "react-dom": "18.3.1",
"read-pkg": "9.0.1", "read-pkg": "9.0.1",
"rehype-katex": "6.0.3", "rehype-katex": "6.0.3",
"rehype-raw": "6.1.1", "rehype-raw": "6.1.1",
"rehype-slug": "5.1.0", "rehype-slug": "5.1.0",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"remark-math": "5.1.1", "remark-math": "5.1.1",
"sharp": "0.33.3", "sharp": "0.33.4",
"shiki": "0.14.7", "shiki": "0.14.7",
"unified": "10.1.2", "unified": "10.1.2",
"unist-util-visit": "5.0.0", "unist-util-visit": "5.0.0",
"universal-cookie": "7.1.4" "universal-cookie": "7.1.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "19.1.0", "@commitlint/cli": "19.2.2",
"@commitlint/config-conventional": "19.1.0", "@commitlint/config-conventional": "19.2.2",
"@saithodev/semantic-release-backmerge": "4.0.1", "@saithodev/semantic-release-backmerge": "4.0.1",
"@semantic-release/git": "10.0.1", "@semantic-release/git": "10.0.1",
"@tailwindcss/typography": "0.5.12", "@tailwindcss/typography": "0.5.13",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
"@tsconfig/strictest": "2.0.5", "@tsconfig/strictest": "2.0.5",
"@types/negotiator": "0.6.3", "@types/negotiator": "0.6.3",
"@types/node": "20.12.7", "@types/node": "20.12.12",
"@types/react": "18.2.78", "@types/react": "18.3.2",
"@types/unist": "3.0.2", "@types/unist": "3.0.2",
"@typescript-eslint/eslint-plugin": "7.6.0", "@typescript-eslint/eslint-plugin": "7.10.0",
"@typescript-eslint/parser": "7.6.0", "@typescript-eslint/parser": "7.10.0",
"autoprefixer": "10.4.19", "autoprefixer": "10.4.19",
"curriculum-vitae": "file:./curriculum-vitae", "curriculum-vitae": "file:./curriculum-vitae",
"cypress": "13.7.3", "cypress": "13.9.0",
"editorconfig-checker": "5.1.5", "editorconfig-checker": "5.1.5",
"eslint": "8.56.0", "eslint": "8.57.0",
"eslint-config-conventions": "14.1.0", "eslint-config-conventions": "14.2.0",
"eslint-config-next": "14.1.0", "eslint-config-next": "14.1.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-tailwindcss": "3.14.2", "eslint-plugin-tailwindcss": "3.15.2",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "53.0.0",
"html-w3c-validator": "1.6.1", "html-w3c-validator": "1.6.1",
"husky": "9.0.11", "husky": "9.0.11",
"lint-staged": "15.2.2", "lint-staged": "15.2.2",
@ -93,8 +91,8 @@
"markdownlint-rule-relative-links": "2.3.2", "markdownlint-rule-relative-links": "2.3.2",
"postcss": "8.4.38", "postcss": "8.4.38",
"prettier": "3.2.5", "prettier": "3.2.5",
"prettier-plugin-tailwindcss": "0.5.13", "prettier-plugin-tailwindcss": "0.5.14",
"semantic-release": "23.0.8", "semantic-release": "23.1.1",
"start-server-and-test": "2.0.3", "start-server-and-test": "2.0.3",
"tailwindcss": "3.4.3", "tailwindcss": "3.4.3",
"typescript": "5.4.5" "typescript": "5.4.5"