diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..66e2009c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+
+[*.{md,mdx}]
+indent_size = 2
+insert_final_newline = true
+
+[*.{js,jsx,mjs,ts,tsx,mts,json}]
+indent_size = 4
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index a56b5862..00000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,108 +0,0 @@
-module.exports = {
- root: true,
- parserOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module',
- },
- 'env': {
- browser: true,
- es2021: true,
- node: true,
- },
- ignorePatterns: [
- 'node_modules/',
- '.next/',
- '.vscode/',
- 'public/',
- 'LICENSE',
- // by default we always ignore our tests folder
- // to ensure the tests dont trigger errors in
- // staging / production deployments
- // comment out the next line to have eslint check
- // the test files (in development)
- 'tests/eslint/',
- ],
- reportUnusedDisableDirectives: true,
- overrides: [
- {
- files: ['**/*.ts?(x)', '**/*.md?(x)'],
- extends: [
- 'next/core-web-vitals',
- ],
- },
- {
- files: ['**/*.ts?(x)'],
- extends: [
- 'plugin:@react-three/recommended',
- // https://typescript-eslint.io/users/configs#recommended-configurations
- 'plugin:@typescript-eslint/recommended-type-checked',
- 'plugin:@typescript-eslint/stylistic-type-checked',
- ],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- sourceType: 'module',
- ecmaFeatures: {
- jsx: true,
- },
- warnOnUnsupportedTypeScriptVersion: true,
- project: './tsconfig.json',
- },
- plugins: [
- '@typescript-eslint',
- ],
- rules: {
- quotes: [
- 'error',
- 'single',
- { "allowTemplateLiterals": true },
- ],
- semi: [
- 'error',
- 'never',
- ],
- '@typescript-eslint/naming-convention': [
- 'error',
- {
- 'selector': 'interface',
- 'format': [
- 'PascalCase',
- ],
- 'custom': {
- 'regex': '^I[A-Z]',
- 'match': true,
- },
- }
- ],
- '@typescript-eslint/consistent-indexed-object-style': 'off',
- '@typescript-eslint/ban-ts-comment': [
- 'error',
- {
- 'ts-expect-error': 'allow-with-description',
- 'ts-ignore': 'allow-with-description',
- 'ts-nocheck': false,
- 'ts-check': false,
- minimumDescriptionLength: 3,
- },
- ],
- },
- },
- {
- files: ['**/*.md?(x)'],
- extends: [
- 'plugin:mdx/recommended',
- ],
- parser: 'eslint-mdx',
- parserOptions: {
- markdownExtensions: ['*.md, *.mdx'],
- },
- settings: {
- 'mdx/code-blocks': false,
- 'mdx/remark': true,
- },
- rules: {
- 'react/no-unescaped-entities': 0,
- }
- // markdown rules get configured in remarkrc.mjs
- },
- ],
-}
\ No newline at end of file
diff --git a/.no-dead-urls.remarkrc.mjs b/.no-dead-urls.remarkrc.mjs
index 4f9e073e..2c63f088 100644
--- a/.no-dead-urls.remarkrc.mjs
+++ b/.no-dead-urls.remarkrc.mjs
@@ -1,38 +1,52 @@
import remarkLintNoDeadUrls from 'remark-lint-no-dead-urls'
+/** @type {import('remark-lint-no-dead-urls').Options} */
+const remarkLintNoDeadUrlsOptions = {
+ from: 'http://localhost:3000',
+ skipUrlPatterns: [
+ // can't find the anchor element for the hash
+ // document is very big and loads slowly
+ // might be due to a timeout
+ '/*w3c.github.io*/',
+ // pexels blocks all requests with a 403 (forbidden) response
+ '/*pexels.com*/',
+ // github urls with a hash for the line numbers
+ // produce the error that the hash can not be found as anchor element
+ // I do NOT want to disable all of github.com
+ // disabling all the urls with a line number hash would be ideal but not sure how to do that
+ // for now I search for /blob/ or /blame/ in the URL and exclude those
+ '/*/blob/*/',
+ '/*/blame/*/',
+ // this page wants to redirect to a url with the language set
+ // I prefer to use the URL with no language
+ // so that it adapts to the user's language that will visit
+ '/*azure.microsoft.com*/',
+ // npmjs has low rate limit, often returns 429 (too many requests)
+ '/*npmjs.com*/',
+ // these domains often produce fetch errors
+ '/*https://developer.apple.com/*/',
+ '/*archive.org*/',
+ // images have /public in their path
+ // next.js however uses a cached version /_next/image?url=
+ // for now I exclude them all, future we need something to convert URLs
+ '/*/public/assets/images*/',
+ // discord always returns 403 (forbidden)
+ '/*discord.com*/',
+ ],
+ deadOrAliveOptions: {
+ // I will wait 60 seconds for the request to complete
+ timeout: 60000,
+ // I will retry 1 times
+ retries: 1,
+ // I will wait 60 seconds between retries
+ retryDelay: 60000,
+ },
+}
+
const config = {
plugins: [
//[remarkLintNoDeadUrls, { from: 'https://example.com' }]
- [remarkLintNoDeadUrls, {
- from: 'http://localhost:3000',
- skipUrlPatterns: [
- // can't find the anchor element for the hash
- // document is very big and loads slowly
- // might be due to a timeout
- '/*w3c.github.io*/',
- // pexels blocks all requests with a 403 (forbidden) response
- '/*pexels.com*/',
- // github urls with a hash for the line numbers
- // produce the error that the hash can not be found as anchor element
- // I do NOT want to disable all of github.com
- // disabling all the urls with a line number hash would be ideal but not sure how to do that
- // for now I search for /blob/ in the URL and exclude those
- '/*/blob/*/',
- // this page wants to redirect to a url with the language set
- // I prefer to use the URL with no language
- // so that it adapts to the user's language that will visit
- '/*azure.microsoft.com*/',
- // npmjs has low rate limit, often returns 429 (too many requests)
- '/*npmjs.com*/',
- // these domains often produce fetch errors
- '/*https://developer.apple.com/*/',
- '/*archive.org*/',
- // images have /public in their path
- // next.js however uses a cached version /_next/image?url=
- // for now I eclude them all, future we need something to convert URLs
- '/*/public/assets/images*/',
- ]
- }]
+ [remarkLintNoDeadUrls, remarkLintNoDeadUrlsOptions]
]
}
diff --git a/.remarkrc.mjs b/.remarkrc.mjs
index 464c05a7..7d734eda 100644
--- a/.remarkrc.mjs
+++ b/.remarkrc.mjs
@@ -1,6 +1,6 @@
// presets imports
-import remarkPresetLintRecommended from 'remark-preset-lint-consistent'
-import remarkPresetLintConsistent from 'remark-preset-lint-recommended'
+import remarkPresetLintRecommended from 'remark-preset-lint-recommended'
+import remarkPresetLintConsistent from 'remark-preset-lint-consistent'
import remarkPresetLintMarkdownStyleGuide from 'remark-preset-lint-markdown-style-guide'
// rules imports
@@ -10,15 +10,21 @@ import remarkLintNoUndefinedReferences from 'remark-lint-no-undefined-references
import remarkLintLinkTitleStyle from 'remark-lint-link-title-style'
import remarkLintMaximumLineLength from 'remark-lint-maximum-line-length'
import remarkLintListItemSpacing from 'remark-lint-list-item-spacing'
+
+// remark plugins
+import remarkGfm from 'remark-gfm'
import remarkFrontmatter from 'remark-frontmatter'
const config = {
plugins: [
- // presets
+ // first the plugins
+ remarkGfm,
+ remarkFrontmatter,
+ // then the presets
remarkPresetLintRecommended,
remarkPresetLintConsistent,
remarkPresetLintMarkdownStyleGuide,
- // rules
+ // and finally the rules customizations
// https://www.npmjs.com/package/remark-lint-maximum-heading-length
[remarkLintMaximumHeadingLength, [1, 100]],
// https://www.npmjs.com/package/remark-lint-unordered-list-marker-style
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4b541bcb..b2f405a3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,14 +6,7 @@
},
"eslint.debug": true,
"eslint.options": {
- "extensions": [
- ".js",
- ".jsx",
- ".md",
- ".mdx",
- ".ts",
- ".tsx"
- ]
+ "flags": ["unstable_ts_config"]
},
"eslint.validate": [
"markdown",
diff --git a/README.md b/README.md
index ce548428..86581677 100644
--- a/README.md
+++ b/README.md
@@ -18,17 +18,15 @@ On [chris.lu](https://chris.lu), you will find my tutorials and can learn more a
## Technologies used
-During the development of the blog, I wrote a ["Next.js static MDX blog" tutorial](https://chris.lu/web_development/tutorials/next-js-static-mdx-blog) that showcases most of the technologies that I used
+During the development of the blog, I wrote a ["Next.js static MDX blog" tutorial](https://chris.lu/web-development/tutorials/next-js-static-mdx-blog) that showcases most of the technologies that I used
-The framework I used is [Next.js 14](https://github.com/vercel/next.js) with [React 18](https://github.com/facebook/react) (I plan on upgrading to Next.js 15 and React 19 as soon as the first stable versions get released and will update my tutorial accordingly)
+The framework I used is [Next.js 15.x](https://github.com/vercel/next.js) with [React 19.x](https://github.com/facebook/react)
-I added [MDX](https://mdxjs.com/) support to be able to create content using next/mdx. I then also used several remark and rehype plugins and even built two myself, [rehype-github-alerts
-](https://github.com/chrisweb/rehype-github-alerts) and [remark-table-of-contents
-](https://github.com/chrisweb/remark-table-of-contents)
+I added [MDX](https://mdxjs.com/) support to be able to create content using **@next/mdx**. I then also used several MDX (remark and rehype) plugins and even built two myself, [rehype-github-alerts](https://github.com/chrisweb/rehype-github-alerts) and [remark-table-of-contents](https://github.com/chrisweb/remark-table-of-contents)
I had a lot of fun doing my [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) header animation using [react-three-fiber](https://github.com/pmndrs/react-three-fiber) (a React renderer for [three.js](https://github.com/mrdoob/three.js))
-I also added a jukebox on top using my [web-audio-api-player](https://github.com/chrisweb/web-audio-api-player) and added a dynamic waveform using my [waveform-visualizer](https://github.com/chrisweb/waveform-visualizer) and [waveform-data-generator](https://github.com/chrisweb/waveform-data-generator) packages
+I also added a jukebox like music player (on the top right) using my [web-audio-api-player](https://github.com/chrisweb/web-audio-api-player) and added a dynamic waveform using my [waveform-visualizer](https://github.com/chrisweb/waveform-visualizer) and [waveform-data-generator](https://github.com/chrisweb/waveform-data-generator) packages
## Feedback & bug reports
@@ -47,7 +45,11 @@ If you have feedback or want to discuss something, please use the [chris.lu gith
`npm run lint-debug`: linting command but more verbose output
`npm run lint-fix`: linting command that also attempts to automatically fix problems
`npm run info`: the default next.js script to get some info about the project
-`npm run check-urls`: check if URLs in documents are alive or not, this linting is seperate from the main linting script so that it can be used sporadically, as it makes lots of calls to 3rd party URLs to check if they are alive, it does not run during the build process so that a unreachable URL of a third party won't break the build
+`npm run check-urls`: check if URLs in documents are alive or not, this linting is separate from the main linting script so that it can be used sporadically, as it makes lots of calls to 3rd party URLs to check if they are alive, it does not run during the build process so that a unreachable URL of a third party won't break the build, it is separate from eslint process and uses remark-cli
+
+## Node.js version
+
+Next.js [requires >=18.18.0](https://github.com/vercel/next.js/commit/ecd2be6d3b74d7af2513a8b355408a8f88ec6b25) (same as ESLint v9), Typescript ESLint [requires Node.js >=20.11.0](https://typescript-eslint.io/getting-started/typed-linting) (for import.meta.dirname in ESM files), this projects [package.json](./package.json) has the engines node set to 20.11.0, the latest Node.js LTS is 22.11.0 (Nov. 2024)
## License
diff --git a/app/about_me/opengraph-image.tsx b/app/about_me/opengraph-image.tsx
index 215d9585..71cc1a05 100644
--- a/app/about_me/opengraph-image.tsx
+++ b/app/about_me/opengraph-image.tsx
@@ -15,26 +15,16 @@ const title = 'About me'
export const alt = `Chris.lu ${title} banner`
-/*interface IImageProps {
- params: {
- slug: string
- }
- id: number
-}*/
-
// Image generation
-export default async function OGImage(/*props: IImageProps*/) {
-
- //console.log(props)
+export default async function Image() {
- // Font
const permanentMarkerRegular = fetch(
new URL('/public/assets/fonts/PermanentMarker-Regular.ttf', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
const imageData = await fetch(
new URL('/public/assets/images/og_image_background_1200x630.jpg', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
return new ImageResponse(
// ImageResponse JSX element
@@ -48,17 +38,15 @@ export default async function OGImage(/*props: IImageProps*/) {
justifyContent: 'center',
}}
>
- {
- // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
-
- }
+ {/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */}
+
{title}
-
+
),
// ImageResponse options
{
diff --git a/app/about_me/page.mdx b/app/about_me/page.mdx
index d7828c15..f19626c9 100644
--- a/app/about_me/page.mdx
+++ b/app/about_me/page.mdx
@@ -1,4 +1,4 @@
-import webpMindBlowingStaticImport from '/public/assets/images/animated/mind_blowing.webp'
+import webpMindBlowingStaticImport from '@/public/assets/images/animated/mind_blowing.webp'
import ImageAnimatedPicture from '@/components/base/image/AnimatedPicture'
import { sharedMetaData } from '@/shared/metadata'
@@ -8,7 +8,7 @@ export const metadata = {
canonical: 'https://chris.lu/about_me',
},
openGraph: {
- ...sharedMetaData,
+ ...sharedMetaData.openGraph,
url: 'https://chris.lu/about_me',
},
}
@@ -51,6 +51,7 @@ On tombraider.net, I had a "music box" (similar to the jukebox I now have on thi
Tombraider 1 inspired midi song:
@@ -61,6 +62,7 @@ On tombraider.net, I had a "music box" (similar to the jukebox I now have on thi
Tombraider 2 inspired midi song:
@@ -76,6 +78,7 @@ I was also able to retrieve a third song. It is only now that I realize how mela
Evening Falls (by Etherea):
@@ -135,9 +138,9 @@ When the COVID-19 pandemic started to impact us all, it created new challenges w
## Sabbatical and current projects
-I have taken a [sabbatical](https://en.wikipedia.org/wiki/Sabbatical) to update my skills once again. I also developed an Android and iOS app called [Beavo](https://beavoapp.com/), it is a web app for Beach Volleyball players using [React](https://react.dev/) and [Capacitor.js](https://capacitorjs.com/), that I did for a friend. I used Xcode Cloud to create a CI/CD pipeline to automatically build the app and distribute it to testers whenever a new pull request is entered into the GitHub repository.
+I have taken a [sabbatical](https://en.wikipedia.org/wiki/Sabbatical) to update my skills once again. I also developed an Android and iOS app called [Beavo](https://beavo.com), it is a web app for Beach Volleyball players using [React](https://react.dev/) and [Capacitor.js](https://capacitorjs.com/), that I did for a friend. I used Xcode Cloud to create a CI/CD pipeline to automatically build the app and distribute it to testers whenever a new pull request is entered into the GitHub repository.
-I rebuilt my personal blog using [Next.js](https://nextjs.org/) and focused on [MDX](https://mdxjs.com/) (markdown) content formatting to create a static blog, which led to the creation of 2 new open source plugins [remark-table-of-contents](https://github.com/chrisweb/remark-table-of-contents) and [rehype-github-alerts](https://github.com/chrisweb/rehype-github-alerts). I also updated my [web-audio-api-player](https://github.com/chrisweb/web-audio-api-player) project which is powering the jukebox on top of this blog and had a lot of fun working on the header ("Press Start" in the top header to see the animation) using [React-three-fiber](https://r3f.docs.pmnd.rs/getting-started/introduction) a React renderer for three.js (WebGL).
+I rebuilt my personal blog using [Next.js](https://nextjs.org/) and focused on [MDX](https://mdxjs.com/) (markdown) content formatting to create a static blog, which led to the creation of 2 new open source plugins [remark-table-of-contents](https://github.com/chrisweb/remark-table-of-contents) and [rehype-github-alerts](https://github.com/chrisweb/rehype-github-alerts). I also updated my [web-audio-api-player](https://github.com/chrisweb/web-audio-api-player) project which is powering the jukebox on top of this blog and had a lot of fun working on the header ("Press Start" in the top header to see the animation) using [React-three-fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) a React renderer for three.js (WebGL).
I read countless articles and watched educational videos on YouTube about web development and game development (using the [Godot Engine](https://godotengine.org/)) and contributed to open-source projects on GitHub, fixing bugs and helping improve documentation, for example, my latest PR for Next.js [Next.js PR #61412](https://github.com/vercel/next.js/pull/61412) just got accepted recently
diff --git a/app/error.tsx b/app/error.tsx
index 58b221d5..1a594946 100644
--- a/app/error.tsx
+++ b/app/error.tsx
@@ -19,9 +19,16 @@ export default function Error({
return (
-
Sorry, something went wrong 😞
+
+ Sorry, something went wrong
+
+ 😞
+
reset()} // attempt to recover by trying to re-render the segment
+ clickCallback={() => {
+ // attempt to recover by trying to re-render the segment
+ reset()
+ }}
>
Try again
diff --git a/app/games/opengraph-image.tsx b/app/games/opengraph-image.tsx
index c4b5e3ff..a0c9f1e7 100644
--- a/app/games/opengraph-image.tsx
+++ b/app/games/opengraph-image.tsx
@@ -15,26 +15,16 @@ const title = 'Games'
export const alt = `Chris.lu ${title} banner`
-/*interface IImageProps {
- params: {
- slug: string
- }
- id: number
-}*/
-
// Image generation
-export default async function OGImage(/*props: IImageProps*/) {
-
- //console.log(props)
+export default async function Image() {
- // Font
const permanentMarkerRegular = fetch(
new URL('/public/assets/fonts/PermanentMarker-Regular.ttf', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
const imageData = await fetch(
new URL('/public/assets/images/og_image_background_1200x630.jpg', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
return new ImageResponse(
// ImageResponse JSX element
@@ -48,17 +38,15 @@ export default async function OGImage(/*props: IImageProps*/) {
justifyContent: 'center',
}}
>
- {
- // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
-
- }
+ {/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */}
+
{title}
-
+
),
// ImageResponse options
{
diff --git a/app/games/page.mdx b/app/games/page.mdx
index d6c3c94f..b71ffb65 100644
--- a/app/games/page.mdx
+++ b/app/games/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
canonical: 'https://chris.lu/games',
},
openGraph: {
- ...sharedMetaData,
+ ...sharedMetaData.openGraph,
url: 'https://chris.lu/games',
},
}
@@ -128,7 +128,7 @@ My first PC ever had an [Intel 486DX2-66](https://en.wikipedia.org/wiki/I486) pr
![MS-DOS The Clou! game](../../public/assets/images/app/games/msdos_the_clou.png '{ screenshot }')
- [The Clue!](https://en.wikipedia.org/wiki/The_Clue!) (German: Der Clou!) The mix of genres in this game was what I liked most. The first part was like a classical adventure game in which you were wandering through the city of London trying to recruit members for your crew, but there was also a second part that was a top-down view of the interior of the buildings, in which you had to execute the actual burglaries
+ [The Clou!](https://en.wikipedia.org/wiki/The_Clou!) (German: Der Clou!) The mix of genres in this game was what I liked most. The first part was like a classical adventure game in which you were wandering through the city of London trying to recruit members for your crew, but there was also a second part that was a top-down view of the interior of the buildings, in which you had to execute the actual burglaries
![MS-DOS The Settlers II: Veni, Vidi, Vici game](../../public/assets/images/app/games/msdos_the_settlers_ii.png '{ screenshot }')
@@ -147,9 +147,10 @@ My first PC ever had an [Intel 486DX2-66](https://en.wikipedia.org/wiki/I486) pr
Video (with sound) of the intro of X-COM: UFO Defense:
## Nintendo 64
diff --git a/app/global-error.tsx b/app/global-error.tsx
index d1ae5dd5..44d06cb6 100644
--- a/app/global-error.tsx
+++ b/app/global-error.tsx
@@ -18,13 +18,20 @@ export default function GlobalError({
}, [error])
return (
-
+
-
Sorry, something went wrong 😞
+
+ Sorry, something went wrong
+
+ 😞
+
reset()} // attempt to recover by trying to re-render the segment
+ clickCallback={() => {
+ // attempt to recover by trying to re-render the segment
+ reset()
+ }}
>
Try again
diff --git a/app/global.css b/app/global.css
index a12648c9..e66d5a5b 100644
--- a/app/global.css
+++ b/app/global.css
@@ -49,7 +49,7 @@
--tertiary-light-color: rgb(var(--tertiary-light-value));
--quaternary-light-value: 215 110 255;
--quaternary-light-color: rgb(var(--quaternary-light-value));
- --codebloc-font-family: var(--font-sourceCodePro), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --codeblock-font-family: var(--font-sourceCodePro), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--header-navbar-height: 50px;
--cursor-default: url(/assets/cursors/secondary_mouse_default.cur), default;
--cursor-pointer: url(/assets/cursors/secondary_mouse_pointer.cur), pointer;
@@ -91,7 +91,7 @@ html {
scroll-behavior: smooth;
/* https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth */
-webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale; /* only macos */
+ -moz-osx-font-smoothing: grayscale;
/* https://developer.mozilla.org/en-US/docs/Web/CSS/text-rendering */
/*text-rendering: optimizeLegibility;*/
/* https://developer.mozilla.org/en-US/docs/Web/CSS/text-size-adjust */
@@ -575,7 +575,7 @@ progress {
}
code {
- font-family: var(--codebloc-font-family);
+ font-family: var(--codeblock-font-family);
font-size: var(--main-fontSize-small);
counter-reset: line;
}
@@ -606,6 +606,16 @@ code[data-line-numbers-max-digits="3"]>[data-line]::before {
border-left-color: transparent;
}
+[data-line].remove {
+ background-color: #34151b;
+ border-left-color: #ff0035;
+}
+
+[data-line].add {
+ background-color: #2a4827;
+ border-left-color: #28ff00;
+}
+
[data-highlighted-line] {
background-color: #3f2046;
border-left-color: var(--primary-light-color);
@@ -682,7 +692,7 @@ article,
padding-left: var(--main-spacing);
padding-right: var(--main-spacing);
margin-bottom: calc(var(--main-spacing) * 2);
- /* for mobile when a chain of characters is extremly long */
+ /* for mobile, when a chain of characters is extremely long */
overflow-x: clip;
}
@@ -795,10 +805,15 @@ aside {
text-decoration: none;
}
-/* "external link" icon styling */
-.externalLinkIcon {
+/* svg icons (font awesome) */
+.inlineIcon {
color: var(--primary-light-color);
- margin-left: calc(var(--main-spacing) / 5);
+ margin: 0 calc(var(--main-spacing) / 5);
+}
+
+.startInlineIcon {
+ color: var(--primary-light-color);
+ margin-right: calc(var(--main-spacing) / 5);
}
/* github like heading anchor */
@@ -939,7 +954,7 @@ make the text color a bit darker */
0 100%);
}
-.makrdown-alert-fake-border {
+.markdown-alert-fake-border {
width: 100%;
height: 100%;
position: relative;
@@ -1041,3 +1056,21 @@ make the text color a bit darker */
height: 30px;
padding-left: calc(var(--main-spacing) / 2);
}
+
+.neonGreenHighlightedText {
+ font-weight: var(--main-fontWeight-bolder);
+ color: rgb(57, 255, 20);
+ text-shadow: 0 0 5px rgba(57, 255, 20, 0.5);
+}
+
+.neonRedHighlightedText {
+ font-weight: var(--main-fontWeight-bolder);
+ color: rgb(255, 0, 0);
+ text-shadow: 0 0 5px rgba(255, 20, 20, 0.5);
+}
+
+.neonBlueHighlightedText {
+ font-weight: var(--main-fontWeight-bolder);
+ color: rgb(0, 157, 255);
+ text-shadow: 0 0 5px rgba(20, 149, 255, 0.5);
+}
\ No newline at end of file
diff --git a/app/layout.tsx b/app/layout.tsx
index 73369d0c..dbe352ca 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -3,8 +3,8 @@ import './global.css'
import styles from './layout.module.css'
import { Permanent_Marker, VT323, Architects_Daughter, Source_Code_Pro, Anta } from 'next/font/google'
import HeaderNavigation from '@/components/header/Navigation'
-import BaseLink from '@/components/base/Link'
-import type { Metadata } from 'next'
+import Disclaimer from '@/components/footer/Disclaimer'
+import type { Metadata, Viewport } from 'next'
import { sharedMetaData } from '@/shared/metadata'
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
@@ -12,9 +12,9 @@ import { SpeedInsights } from '@vercel/speed-insights/next'
export const metadata: Metadata = {
// default next.js value
// added this just to make the console message go away
- metadataBase: process.env.VERCEL_URL
- ? new URL(`https://${process.env.VERCEL_URL}`)
- : new URL(`http://localhost:${process.env.PORT ?? 3000}`),
+ metadataBase: process.env.VERCEL_URL ?
+ new URL(`https://${process.env.VERCEL_URL}`) :
+ new URL(`http://localhost:${process.env.PORT ?? '3000'}`),
title: {
template: '%s | chris.lu',
default: 'Home | chris.lu',
@@ -30,8 +30,6 @@ export const metadata: Metadata = {
},
}
-import type { Viewport } from 'next'
-
export const viewport: Viewport = {
/* on older safari this is used as background color
for the top safari UI, use dark color instead of primary */
@@ -95,8 +93,7 @@ export default function RootLayout({ children }: {
{children}
diff --git a/app/lego/opengraph-image.tsx b/app/lego/opengraph-image.tsx
index a5a8fe77..2e788afd 100644
--- a/app/lego/opengraph-image.tsx
+++ b/app/lego/opengraph-image.tsx
@@ -15,26 +15,16 @@ const title = 'Lego'
export const alt = `Chris.lu ${title} banner`
-/*interface IImageProps {
- params: {
- slug: string
- }
- id: number
-}*/
-
// Image generation
-export default async function OGImage(/*props: IImageProps*/) {
-
- //console.log(props)
+export default async function Image() {
- // Font
const permanentMarkerRegular = fetch(
new URL('/public/assets/fonts/PermanentMarker-Regular.ttf', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
const imageData = await fetch(
new URL('/public/assets/images/og_image_background_1200x630.jpg', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
return new ImageResponse(
// ImageResponse JSX element
@@ -48,17 +38,15 @@ export default async function OGImage(/*props: IImageProps*/) {
justifyContent: 'center',
}}
>
- {
- // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
-
- }
+ {/* eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element */}
+
{title}
-
@@ -58,7 +58,7 @@ export const metadata = {
Indie pop 🎉
- ![indie chill sunset playlist showing a sun gowing down in front of a red and purple sky](../../public/assets/images/app/music/indie_chill_sunset.jpg '{ card }')
+ ![indie chill sunset playlist showing a sun going down in front of a red and purple sky](../../public/assets/images/app/music/indie_chill_sunset.jpg '{ card }')
Indie chill sunset 🌇
diff --git a/app/not-found.tsx b/app/not-found.tsx
index 012ccc2d..06b74333 100644
--- a/app/not-found.tsx
+++ b/app/not-found.tsx
@@ -1,5 +1,5 @@
import BaseLink from '@/components/base/Link'
-import webpNotFoundStaticImport from '/public/assets/images/animated/404.webp'
+import webpNotFoundStaticImport from '@/public/assets/images/animated/404.webp'
import ImageAnimatedPicture from '@/components/base/image/AnimatedPicture'
export default function NotFound() {
@@ -8,7 +8,7 @@ export default function NotFound() {
404 Page not found
Sorry, I looked everywhere but somehow I can't find this page.
+
),
// ImageResponse options
{
diff --git a/app/page.tsx b/app/page.tsx
index 3358d78d..0a0faf4f 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -2,25 +2,35 @@ import Typing from '@/components/animated/Typing'
import styles from './page.module.css'
import Link from 'next/link'
import ImageAnimatedEmoji from '@/components/base/image/AnimatedEmoji'
-import gifWaveStaticImport from '/public/assets/images/noto_emoji_animated/48/waving.gif'
-import webpWaveStaticImport from '/public/assets/images/noto_emoji_animated/48/waving.webp'
+import gifWaveStaticImport from '@/public/assets/images/noto_emoji_animated/48/waving.gif'
+import webpWaveStaticImport from '@/public/assets/images/noto_emoji_animated/48/waving.webp'
export default function Homepage() {
return (
<>
-
Hello, World!
+
+ Hello, World!
+
Welcome to my blog, my name is Chris Weber (aka chrisweb)
-
I like Web development, Lego bricks, Music, Games, Cooking, Movies & TV shows, Memes
+
+ I like
+
+ Web development, Lego bricks, Music, Games, Cooking, Movies & TV shows, Memes
+
-
Web Development: In this portal you will find my articles about all things web development, so mostly about Javascript (Typescript), React, Next.js, APIs, CI/CD deployment, capacitor (web apps), WebGL, but probably also some posts about Cloud (serverless, edge, CDNs, ...), AI, IoT and maybe some more
+
+ Web Development:
+
+ In this portal you will find my articles about all things web development, so mostly about Javascript (Typescript), React, Next.js, APIs, CI/CD deployment, capacitor (web apps), WebGL, but probably also some posts about Cloud (serverless, edge, CDNs, ...), AI, IoT and maybe some more
+
@@ -29,7 +39,11 @@ export default function Homepage() {
-
Games: I have always liked playing video games, be it on consoles, PC and yes even mobile (I know shame on me 😉). Playing games is my oldest hobby I still enjoy these days. I like playing games and also like watching people play on Twitch and sometimes I even do both at the same time.
+
+ Games:
+
+ I have always liked playing video games, be it on consoles, PC and yes even mobile (I know shame on me 😉). Playing games is my oldest hobby I still enjoy these days. I like playing games and also like watching people play on Twitch and sometimes I even do both at the same time.
+
@@ -38,7 +52,11 @@ export default function Homepage() {
-
Lego: If there is one hobby that helps me chill after a busy day it for sure is building things using bricks. I like building a lot as I can do it in the evening while also bing watching my favorite TV series or just listening to music. I like being creative and like watching Videos or Streams from other AFOLs.
+
+ Lego:
+
+ If there is one hobby that helps me chill after a busy day it for sure is building things using bricks. I like building a lot as I can do it in the evening while also bing watching my favorite TV series or just listening to music. I like being creative and like watching Videos or Streams from other AFOLs.
+
@@ -47,7 +65,11 @@ export default function Homepage() {
-
Music: There are a lot of different activities during which I like listening to music, hence the music genres I listen to vary depending on what I do. I have my road trip playlists for when I'm in my car, my work playlist I listen to while coding, my chill playlists when building with bricks, ...
+
+ Music:
+
+ There are a lot of different activities during which I like listening to music, hence the music genres I listen to vary depending on what I do. I have my road trip playlists for when I'm in my car, my work playlist I listen to while coding, my chill playlists when building with bricks, ...
+
@@ -56,7 +78,11 @@ export default function Homepage() {
-
Memes: A growing collection of memes that make me laugh. The most important ingredient for a good meme is humor, without it a meme is just a quote on a picture. A good meme can be used as a source of light relief during a tense situation.
+
+ Memes:
+
+ A growing collection of memes that make me laugh. The most important ingredient for a good meme is humor, without it a meme is just a quote on a picture. A good meme can be used as a source of light relief during a tense situation.
+
@@ -65,7 +91,11 @@ export default function Homepage() {
-
About me: Everything on this blog is already about me, but I also wanted to have a more personal area where I can write about things that are not related to web development, playing games and building with bricks.
+
+ About me:
+
+ Everything on this blog is already about me, but I also wanted to have a more personal area where I can write about things that are not related to web development, playing games and building with bricks.
+
diff --git a/app/sitemap.ts b/app/sitemap.ts
index fa356cfc..22e00f8c 100644
--- a/app/sitemap.ts
+++ b/app/sitemap.ts
@@ -1,4 +1,4 @@
-import { MetadataRoute } from 'next'
+import type { MetadataRoute } from 'next'
import path from 'node:path'
import fs from 'node:fs'
import { glob } from 'glob'
diff --git a/app/web_development/og/[key]/opengraph-image.tsx b/app/web_development/og/[key]/opengraph-image.tsx
index c6d51a66..7978340e 100644
--- a/app/web_development/og/[key]/opengraph-image.tsx
+++ b/app/web_development/og/[key]/opengraph-image.tsx
@@ -12,7 +12,7 @@ export const size = {
export const contentType = 'image/png'
-export const alt = `Chris.lu article banner`
+export const alt = 'Chris.lu article banner'
interface IImageProps {
params: {
@@ -21,32 +21,31 @@ interface IImageProps {
}
// Image generation
-export default async function OGImage(props: IImageProps) {
+export default async function Image(props: IImageProps) {
if (!props.params.key) {
return
}
- if (!imageInfo[props.params.key]) {
- return
- }
-
const imageTitle = imageInfo[props.params.key][0]
const imagePath = imageInfo[props.params.key][1]
- const baseUrl = process.env.VERCEL_URL
- ? `https://${process.env.VERCEL_URL}`
- : `http://localhost:${process.env.PORT ?? 3000}`
+ const baseUrl = process.env.VERCEL_URL ?
+ `https://${process.env.VERCEL_URL}` :
+ `http://localhost:${process.env.PORT ?? '3000'}`
- // Font
- const permanentMarkerRegular = fetch(
- new URL('/public/assets/fonts/PermanentMarker-Regular.ttf', import.meta.url)
- ).then((res) => res.arrayBuffer())
+ const antaRegular = await fetch(
+ new URL('/public/assets/fonts/Anta-Regular.ttf', import.meta.url)
+ ).then(res => res.arrayBuffer())
- // using new URL(myPath, import.meta.url) did not work
const imageData = await fetch(
+ // relative does NOT work (for me)
+ //new URL('../../../../public/assets/images/app/web_development/' + imagePath + '/opengraph.jpg', import.meta.url)
+ // this works for font but not images
+ //new URL('/public/assets/images/app/web_development/' + imagePath + '/opengraph.jpg', import.meta.url)
+ // using this instead
baseUrl + '/assets/images/app/web_development/' + imagePath + '/opengraph.jpg'
- ).then((res) => res.arrayBuffer())
+ ).then(res => res.arrayBuffer())
return new ImageResponse(
// ImageResponse JSX element
@@ -59,7 +58,7 @@ export default async function OGImage(props: IImageProps) {
}}
>
{
- // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
+ // eslint-disable-next-line jsx-a11y/alt-text, @next/next/no-img-element
{imageTitle} | Chris.lu
-
- ![voodoo lady mixing potions in a big cauldron, it represents a dev using different packages to build a project using an IDE](../../public/assets/images/app/web_development/tutorials/next-js-static-mdx-blog/banner.png '{ card }')
- Next.js / React static MDX Blog
+
+ ![voodoo lady mixing potions in a big cauldron, it represents a dev using different packages like Next.js 15 and React 19 to build an MDX static first starterkit](../../public/assets/images/app/web_development/tutorials/next-js-static-first-mdx-starterkit/banner.png '{ card }')
+ Next.js 15 + React 19 (ESLint 9) static first MDX starterkit
## Posts
![a futuristic city with two signs "react" and "next.js"](../../public/assets/images/app/web_development/posts/road-to-react-19-next-js-15/banner.png '{ card }')
- The road to React 19 and Next.js 15
+ The road to React 19 and Next.js 14
![a female robocop, in front of a police car with the text CSP on the side door, in a futuristic city](../../public/assets/images/app/web_development/posts/csp/banner.png '{ card }')
@@ -82,4 +82,11 @@ export const metadata = {
MDX introduction
+ ### Archived tutorials
+
+
+ ![voodoo lady mixing potions in a big cauldron, it represents a dev using different packages to build a project using an IDE](../../public/assets/images/app/web_development/tutorials/next-js-static-mdx-blog/banner.png '{ card }')
+ Next.js 14 / React 18 static MDX Blog
+
+