Compare commits

...

10 Commits

152 changed files with 14822 additions and 6911 deletions

1
.devenv-root Normal file
View File

@@ -0,0 +1 @@
/home/phundrak/code/web/phundrak.com-frontend

View File

@@ -7,6 +7,10 @@ insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{rs, toml}]
indent_style = space
indent_size = 4
[*.{json,ts,css}]
indent_style = space
indent_size = 2

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
NUXT_PUBLIC_BACKEND_URL=http://localhost:3100
NUXT_PUBLIC_TURNSTILE_SITE_KEY="changeme"
NUXT_TURNSTILE_SECRET_KEY="changeme"

25
.envrc
View File

@@ -1 +1,24 @@
use nix
#!/usr/bin/env bash
if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-yMJ2OVMzrFaDPn7q8nCBZFRYpL/f0RcHzhmw/i6btJM="
fi
export DEVENV_IN_DIRENV_SHELL=true
# Load .env file if present
dotenv_if_exists
watch_file flake.nix
watch_file flake.lock
watch_file .envrc.local
watch_file nix/shell.nix
# Check if .envrc.local exists and contains a shell preference
if [[ -f .envrc.local ]]; then
source .envrc.local
fi
if ! use flake . --no-pure-eval; then
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to flake.nix and hit enter to try again." >&2
fi

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
*.org linguist-detectable=true

View File

@@ -1,41 +0,0 @@
name: deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- uses: purcell/setup-emacs@master
with:
version: 29.1
- name: "Export org to md"
run: emacs -Q --script export.el
- run: npm run build
- name: "Deploy on the Web"
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: content/.vuepress/dist/*
target: ${{ secrets.DESTPATH }}
strip_components: 3
- name: "Deploy on Gemini"
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
port: ${{ secrets.PORT }}
source: gemini/*
target: ${{ secrets.DESTPATH_GMI }}
strip_components: 1

35
.gitignore vendored
View File

@@ -1,6 +1,33 @@
node_modules
.temp
.cache
/content/.vuepress/dist/*
*.md
/.yarn/
.devenv
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example
# Frontend
## Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
## Node dependencies
node_modules
# Nix
result
.data/

17
.prettierrc Normal file
View File

@@ -0,0 +1,17 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"experimentalOperatorPosition": "start",
"experimentalTernaries": true,
"jsxSingleQuote": true,
"printWidth": 120,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false
}

View File

@@ -1,3 +0,0 @@
enableMessageNames: false
nodeLinker: node-modules

View File

@@ -1,51 +1,134 @@
#+title: phundrak.com
#+title: phundrak.com frontend
#+author: Lucien Cartier-Tilet
#+email: lucien@phundrak.com
#+html: <a href="https://www.gnu.org/software/emacs/"><img src="https://img.shields.io/badge/Emacs-29.1-blueviolet.svg?style=flat-square&logo=GNU%20Emacs&logoColor=white" /></a>
#+html: <a href="https://orgmode.org/"><img src="https://img.shields.io/badge/Written%20with-Org%20mode-success?logo=Org&logoColor=white&style=flat-square"/></a>
#+html: <a href="https://v2.vuepress.vuejs.org/"><img src="https://img.shields.io/badge/Framework-Vuepress-42D392?logo=Vue.js&logoColor=white&style=flat-square"/></a>
#+html: <a href="https://phundrak.com"><img src="https://img.shields.io/badge/dynamic/json?label=Website&query=%24%5B%3A1%5D.status&url=https%3A%2F%2Fdrone.phundrak.com%2Fapi%2Frepos%2Fphundrak%2Fphundrak.com%2Fbuilds&style=flat-square&logo=buffer" /></a>
This is the frontend of =phundrak.com=, written with Nuxt.
* Introduction
This is the repository for my website [[https://phundrak.com][phundrak.com]] which contains the
code available on the =main= branch. Code available on the =develop=
branch is available at [[https://beta.phundrak.com][beta.phundrak.com]].
* Setup
* Structure of the project
This website is made with [[https://v2.vuepress.vuejs.org/][VuePress]], a Vue-powered static site
generator. You can find its Node.JS configuration in the [[file:package.json][package.json]]
file as well as its content and general configuration in the directory
[[file:content/][content]].
** Environment
*** Nix Environment
If you use Nix, you can set up your environment using the [[file:flake.nix][=flake.nix=]]
file, which will give you the exact same development environment as I
use.
** Installing and running
In order to run the website, you firts need to export all the orgmode
files to Markdown files. I recommend using =ox-gfm= to do so. If you
dont mind =package.el= installing it as well as =f.el=, you can run the
following command:
#+begin_src shell
emacs -Q --script export.el
#+begin_src bash
nix develop
#+end_src
To install the NPM dependencies for the project, run one of the
following commands:
#+begin_src shell
yarn
# or
npm install # delete the yarn.lock file before
If you have [[https://direnv.net/][=direnv=]] installed, you can simply use it to automatically
enable this environment. However, I *strongly* recommend you to read the
content of the =flake.nix= file before doing so, as you should with any
Nix-defined environment you did not create.
#+begin_src bash
direnv allow .
#+end_src
To run the project, run one of the following commands using the same
package manager as above:
#+begin_src shell
yarn dev
# or
*** Required Tools
To be able to work on this project, you need a Javascript package
manager, such as:
- =npm=
- =pnpm= (recommended)
- =yarn=
- =bun=
In my case, I use pnpm.
You can skip this if you are already using my Nix environment.
** Dependencies
Once you have your environment ready, you can now install the
projects dependencies.
#+begin_src bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
#+end_src
* Running the Project
You are now ready to start the development server on
=http://localhost:3000=.
#+begin_src bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
#+end_src
You can compile the website to a static website by running
#+begin_src shell
yarn build
# or
* Production
Once you are satisfied with the project, you can build the application in production mode.
#+begin_src bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
#+end_src
The compiled version of the website can then be found in =content/.vuepress/dist=.
You can preview locally the production build too.
#+begin_src bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
#+end_src
Check out the [[https://nuxt.com/docs/getting-started/deployment][deployment documentation]] for more information.
* Known Issues
** =better-sqlite3= self-registration error
If you encounter an error stating that =better-sqlite3= does not
self-register when running =pnpm run dev=, this is typically caused by
the native module being compiled for a different Node.js version.
*Solution:* Rebuild the native module for your current Node.js version:
#+begin_src bash
# Rebuild just better-sqlite3
pnpm rebuild better-sqlite3
# Or rebuild all native modules
pnpm rebuild
# Or reinstall everything (nuclear option)
rm -rf node_modules
pnpm install
#+end_src
*Why this happens:* =better-sqlite3= contains native C++ code that
needs to be compiled for each specific Node.js version. When you
update Node.js or switch between versions, native modules need to be
rebuilt.

23
app/app.vue Normal file
View File

@@ -0,0 +1,23 @@
<template>
<UApp :locale="locales[locale]">
<AppNavbar />
<UMain>
<NuxtPage />
</UMain>
<AppFooter />
</UApp>
</template>
<script setup lang="ts">
import * as locales from '@nuxt/ui/locale';
const { locale } = useI18n();
const lang = computed(() => locales[locale.value].code);
const dir = computed(() => locales[locale.value].dir);
useHead({
htmlAttrs: {
dir,
lang,
},
});
</script>

132
app/assets/css/colors.css Normal file
View File

@@ -0,0 +1,132 @@
:root {
--text-50: oklch(96.68% 0.005 95.1);
--text-100: oklch(93.31% 0.012 96.43);
--text-200: oklch(86.46% 0.023 98.68);
--text-300: oklch(79.55% 0.036 98.17);
--text-400: oklch(72.45% 0.047 99.12);
--text-500: oklch(65.27% 0.06 98.88);
--text-600: oklch(55.54% 0.05 99.33);
--text-700: oklch(45.43% 0.04 98.55);
--text-800: oklch(34.63% 0.028 99.26);
--text-900: oklch(22.99% 0.017 97.01);
--text: oklch(17.69% 0.01 97.92);
--text-950: oklch(16.34% 0.008 95.54);
--background: oklch(97.33% 0.007 88.64);
--background-50: oklch(96.7% 0.008 91.48);
--background-100: oklch(93.46% 0.017 88);
--background-200: oklch(86.85% 0.034 88.07);
--background-300: oklch(80.17% 0.051 88.07);
--background-400: oklch(73.62% 0.069 89.26);
--background-500: oklch(66.8% 0.085 88.59);
--background-600: oklch(56.88% 0.071 88.9);
--background-700: oklch(46.26% 0.056 87.6);
--background-800: oklch(35.24% 0.04 87.71);
--background-900: oklch(23.27% 0.023 87.9);
--background-950: oklch(16.86% 0.012 91.89);
--primary-50: oklch(97.22% 0.012 96.42);
--primary-100: oklch(94.41% 0.025 97.12);
--primary-200: oklch(88.75% 0.05 98.42);
--primary-300: oklch(83.15% 0.074 98.36);
--primary-400: oklch(77.55% 0.097 98.29);
--primary: oklch(74.12% 0.109 98.34);
--primary-500: oklch(72% 0.116 97.93);
--primary-600: oklch(61.14% 0.097 98.09);
--primary-700: oklch(49.77% 0.077 98.34);
--primary-800: oklch(37.71% 0.055 98.79);
--primary-900: oklch(24.68% 0.033 97.74);
--primary-950: oklch(17.23% 0.018 97.53);
--secondary-50: oklch(97.69% 0.019 100.12);
--secondary-100: oklch(95.28% 0.036 96.71);
--secondary-200: oklch(90.57% 0.07 97.74);
--secondary-300: oklch(86.23% 0.103 98.42);
--secondary: oklch(83.86% 0.116 98.04);
--secondary-400: oklch(81.72% 0.129 98.31);
--secondary-500: oklch(77.44% 0.146 97.07);
--secondary-600: oklch(65.69% 0.123 97.5);
--secondary-700: oklch(53.48% 0.099 97.52);
--secondary-800: oklch(40.18% 0.072 97.19);
--secondary-900: oklch(26.04% 0.043 96.76);
--secondary-950: oklch(18.17% 0.026 97.52);
--accent-50: oklch(97.77% 0.019 96.86);
--accent-100: oklch(95.53% 0.039 97.44);
--accent-200: oklch(91.16% 0.076 97.81);
--accent-300: oklch(86.92% 0.11 97.94);
--accent: oklch(82.74% 0.136 98);
--accent-400: oklch(82.74% 0.136 98);
--accent-500: oklch(78.81% 0.152 96.76);
--accent-600: oklch(66.8% 0.128 96.97);
--accent-700: oklch(54.33% 0.103 96.65);
--accent-800: oklch(40.98% 0.076 96.95);
--accent-900: oklch(26.42% 0.045 97.53);
--accent-950: oklch(18.44% 0.029 102.49);
}
.dark {
--text-50: oklch(16.34% 0.008 95.54);
--text: oklch(96.05% 0.007 97.35);
--text-100: oklch(22.99% 0.017 97.01);
--text-200: oklch(34.63% 0.028 99.26);
--text-300: oklch(45.43% 0.04 98.55);
--text-400: oklch(55.54% 0.05 99.33);
--text-500: oklch(65.27% 0.06 98.88);
--text-600: oklch(72.45% 0.047 99.12);
--text-700: oklch(79.55% 0.036 98.17);
--text-800: oklch(86.46% 0.023 98.68);
--text-900: oklch(93.31% 0.012 96.43);
--text-950: oklch(96.68% 0.005 95.1);
--background-50: oklch(16.86% 0.012 91.89);
--background-100: oklch(23.27% 0.023 87.9);
--background-200: oklch(35.24% 0.04 87.71);
--background-300: oklch(46.26% 0.056 87.6);
--background-400: oklch(56.88% 0.071 88.9);
--background-500: oklch(66.8% 0.085 88.59);
--background-600: oklch(73.62% 0.069 89.26);
--background-700: oklch(80.17% 0.051 88.07);
--background-800: oklch(86.85% 0.034 88.07);
--background-900: oklch(93.46% 0.017 88);
--background-950: oklch(96.7% 0.008 91.48);
--background: oklch(15.48% 0.011 89.86);
--primary-50: oklch(17.23% 0.018 97.53);
--primary-100: oklch(24.68% 0.033 97.74);
--primary-200: oklch(37.71% 0.055 98.79);
--primary-300: oklch(49.77% 0.077 98.34);
--primary-400: oklch(61.14% 0.097 98.09);
--primary: oklch(67.74% 0.108 98.2);
--primary-500: oklch(72% 0.116 97.93);
--primary-600: oklch(77.55% 0.097 98.29);
--primary-700: oklch(83.15% 0.074 98.36);
--primary-800: oklch(88.75% 0.05 98.42);
--primary-900: oklch(94.41% 0.025 97.12);
--primary-950: oklch(97.22% 0.012 96.42);
--secondary-50: oklch(18.17% 0.026 97.52);
--secondary-100: oklch(26.04% 0.043 96.76);
--secondary-200: oklch(40.18% 0.072 97.19);
--secondary-300: oklch(53.48% 0.099 97.52);
--secondary: oklch(59.61% 0.111 97.84);
--secondary-400: oklch(65.69% 0.123 97.5);
--secondary-500: oklch(77.44% 0.146 97.07);
--secondary-600: oklch(81.72% 0.129 98.31);
--secondary-700: oklch(86.23% 0.103 98.42);
--secondary-800: oklch(90.57% 0.07 97.74);
--secondary-900: oklch(95.28% 0.036 96.71);
--secondary-950: oklch(97.69% 0.019 100.12);
--accent-50: oklch(18.44% 0.029 102.49);
--accent-100: oklch(26.42% 0.045 97.53);
--accent-200: oklch(40.98% 0.076 96.95);
--accent-300: oklch(54.33% 0.103 96.65);
--accent: oklch(66.8% 0.128 96.97);
--accent-400: oklch(66.8% 0.128 96.97);
--accent-500: oklch(78.81% 0.152 96.76);
--accent-600: oklch(82.74% 0.136 98);
--accent-700: oklch(86.92% 0.11 97.94);
--accent-800: oklch(91.16% 0.076 97.81);
--accent-900: oklch(95.53% 0.039 97.44);
--accent-950: oklch(97.77% 0.019 96.86);
}

6
app/assets/css/main.css Normal file
View File

@@ -0,0 +1,6 @@
@import '@nuxt/ui';
@import './colors.css';
@import './ui/index.css';
@import './tailwind.css';
@source "../../../content/**/*";

View File

@@ -0,0 +1,85 @@
@import 'tailwindcss';
@theme {
--color-text-50: var(--text-50);
--color-text: var(--text);
--color-text-100: var(--text-100);
--color-text-200: var(--text-200);
--color-text-300: var(--text-300);
--color-text-400: var(--text-400);
--color-text-500: var(--text-500);
--color-text-600: var(--text-600);
--color-text-700: var(--text-700);
--color-text-800: var(--text-800);
--color-text-900: var(--text-900);
--color-text-950: var(--text-950);
--color-background-50: var(--background-50);
--color-background-100: var(--background-100);
--color-background-200: var(--background-200);
--color-background-300: var(--background-300);
--color-background-400: var(--background-400);
--color-background-500: var(--background-500);
--color-background-600: var(--background-600);
--color-background-700: var(--background-700);
--color-background-800: var(--background-800);
--color-background-900: var(--background-900);
--color-background-950: var(--background-950);
--color-background: var(--background);
--color-primary-50: var(--primary-50);
--color-primary-100: var(--primary-100);
--color-primary-200: var(--primary-200);
--color-primary-300: var(--primary-300);
--color-primary-400: var(--primary-400);
--color-primary: var(--primary);
--color-primary-500: var(--primary-500);
--color-primary-600: var(--primary-600);
--color-primary-700: var(--primary-700);
--color-primary-800: var(--primary-800);
--color-primary-900: var(--primary-900);
--color-primary-950: var(--primary-950);
--color-secondary-50: var(--secondary-50);
--color-secondary-100: var(--secondary-100);
--color-secondary-200: var(--secondary-200);
--color-secondary-300: var(--secondary-300);
--color-secondary: var(--secondary);
--color-secondary-400: var(--secondary-400);
--color-secondary-500: var(--secondary-500);
--color-secondary-600: var(--secondary-600);
--color-secondary-700: var(--secondary-700);
--color-secondary-800: var(--secondary-800);
--color-secondary-900: var(--secondary-900);
--color-secondary-950: var(--secondary-950);
--color-accent-50: var(--accent-50);
--color-accent-100: var(--accent-100);
--color-accent-200: var(--accent-200);
--color-accent-300: var(--accent-300);
--color-accent: var(--accent);
--color-accent-400: var(--accent-400);
--color-accent-500: var(--accent-500);
--color-accent-600: var(--accent-600);
--color-accent-700: var(--accent-700);
--color-accent-800: var(--accent-800);
--color-accent-900: var(--accent-900);
--color-accent-950: var(--accent-950);
--text-sm: 0.75rem;
--text-base: 1rem;
--text-xl: 1.333rem;
--text-2xl: 1.777rem;
--text-3xl: 2.369rem;
--text-4xl: 3.158rem;
--text-5xl: 4.21rem;
--text-weight-normal: 400;
--text-weight-bold: 700;
--font-sans:
Noto Sans, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-title:
Wittgenstein, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}

View File

@@ -0,0 +1,14 @@
:root {
--ui-bg: var(--background);
--ui-bg-muted: var(--background-300);
--ui-bg-elevated: var(--background-100);
--ui-bg-accented: var(--backgsound-200);
--ui-bg-inverted: var(--background-900);
}
.dark {
--ui-bg: var(--background);
--ui-bg-muted: var(--background-100);
--ui-bg-elevated: var(--background-200);
--ui-bg-accented: var(--background-300);
--ui-bg-inverted: var(--background-900);
}

View File

@@ -0,0 +1,12 @@
:root {
--ui-border: var(--background-200);
--ui-border-muted: var(--background-200);
--ui-border-accented: var(--background-300);
--ui-border-inverted: var(--background-900);
}
.dark {
--ui-border: var(--background-100);
--ui-border-muted: var(--background-200);
--ui-border-accented: var(--background-200);
--ui-border-inverted: var(--background-900);
}

View File

@@ -0,0 +1,16 @@
:root {
--ui-primary: var(--primary);
--ui-secondary: var(--secondary);
--ui-success: var(--accent);
--ui-info: var(--ui-color-info-500);
--ui-warning: var(--ui-color-warning-500);
--ui-error: var(--ui-color-error-500);
}
.dark {
--ui-primary: var(--primary);
--ui-secondary: var(--secondary);
--ui-success: var(--accent);
--ui-info: var(--ui-color-info-400);
--ui-warning: var(--ui-color-warning-400);
--ui-error: var(--ui-color-error-400);
}

View File

@@ -0,0 +1,4 @@
@import './colors.css';
@import './text.css';
@import './background.css';
@import './border.css';

View File

@@ -0,0 +1,16 @@
:root {
--ui-text-dimmed: var(--text-800);
--ui-text-muted: var(--text-700);
--ui-text-toned: var(--text-600);
--ui-text: var(--text);
--ui-text-highlighted: var(--text-900);
--ui-text-inverted: var(--text-50);
}
.dark {
--ui-text-dimmed: var(--text-800);
--ui-text-muted: var(--text-700);
--ui-text-toned: var(--text-600);
--ui-text: var(--text);
--ui-text-highlighted: var(--text);
--ui-text-inverted: var(--text-50);
}

View File

@@ -0,0 +1,48 @@
<template>
<UFooter class="bg-background-200">
<template #left>
<div class="flex flex-col gap-2">
<p class="text-text-800 text-sm">Copyright &copy; {{ new Date().getFullYear() }}</p>
<p class="text-text-800 text-sm">{{ $t('footer.versions.frontend') }}: {{ version }}</p>
<p class="text-text-800 text-sm">{{ $t('footer.versions.backend') }}: {{ meta?.version }}</p>
</div>
</template>
<UNavigationMenu :items="items" variant="link" :orientation="orientation" />
<template #right>
<UButton
icon="i-simple-icons-github"
color="neutral"
variant="ghost"
to="https://github.com/Phundrak"
target="_blank"
aria-label="GitHub"
/>
</template>
</UFooter>
</template>
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui';
import { version } from '../../package.json';
const { isMobile } = useDevice();
const orientation = computed(() => (isMobile ? 'vertical' : 'horizontal'));
const { getMeta } = useBackend();
const meta = await getMeta();
const items = computed<NavigationMenuItem[]>(() => [
{
label: $t('footer.links.source'),
to: 'https://labs.phundrak.com/phundrak/phundrak.com',
},
{
label: $t('footer.links.nuxt'),
to: 'https://nuxt.com/',
},
{
label: $t('footer.links.rust'),
to: 'https://rust-lang.org/',
},
]);
</script>

View File

@@ -0,0 +1,29 @@
<template>
<UHeader toggle-side="right" mode="drawer">
<template #title> Phundrak </template>
<UNavigationMenu :items="items" />
<template #right>
<NavbarLanguageSwitcher />
<NavbarThemeSwitcher />
</template>
<template #body>
<UNavigationMenu :items="items" orientation="vertical" class="-mx-2.5" />
</template>
</UHeader>
</template>
<script setup lang="ts">
const route = useRoute();
const items = computed<NavigationMenuItem[]>(() => [
{
label: $t('pages.home.name'),
to: '/',
active: route.path == '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: $t(`pages.${page}.name`),
to: `/${page}`,
active: route.path.startsWith(`/${page}`),
})),
]);
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div v-if="tools" class="flex flex-row gap-1 flex-wrap">
<UBadge v-for="tool in tools" :key="tool" size="md" variant="solid">
{{ tool }}
</UBadge>
</div>
</template>
<script setup lang="ts">
const { tools } = defineProps<{
tools: string[];
}>();
</script>

View File

@@ -0,0 +1,14 @@
<template>
<UPageCard class="bg-background-100 my-10">
<p class="text-xl">
<slot />
</p>
<UiBadgeList :tools="tools" />
</UPageCard>
</template>
<script setup lang="ts">
const { tools } = defineProps<{
tools: string[];
}>();
</script>

View File

@@ -0,0 +1,35 @@
<template>
<UPageCard class="bg-background-100 my-10">
<p class="text-xl">
{{ $t('pages.vocal-synthesis.projects') }}
</p>
<div class="flex flex-col max-w gap-10">
<div v-for="project in data?.projects" :key="project.title" class="flex flex-row max-w gap-5">
<div>
<div
class="bg-primary text-text-50 dark:bg-primary p-1 rounded-md min-w-13 w-13 h-13 min-h-13 flex justify-center my-2"
>
<UIcon :name="project.icon" class="size-11" />
</div>
</div>
<div class="flex flex-col">
<div class="flex flex-row gap-2 items-baseline">
<ULink :to="project.link" class="text-2xl">
{{ project.title }}
</ULink>
<UIcon v-if="external(project.link)" name="mdi:link" class="size-5" />
</div>
<div>
{{ project.description }}
</div>
</div>
</div>
</div>
</UPageCard>
</template>
<script setup lang="ts">
// Inject data provided by the page to avoid hydration issues with MDC components
const data = inject('pageData');
const external = (url: string) => url.startsWith('http');
</script>

View File

@@ -0,0 +1,8 @@
<template>
<UiBadgeListCard v-if="data" :tools="data.tools">{{ $t('pages.vocal-synthesis.tools') }}</UiBadgeListCard>
</template>
<script setup lang="ts">
// Inject data provided by the page to avoid hydration issues with MDC components
const data = inject('pageData');
</script>

View File

@@ -0,0 +1,26 @@
<template>
<UDropdownMenu :key="locale" :items="availableLocales" :content="{ align: 'start' }">
<UButton color="neutral" variant="outline" icon="material-symbols:globe" :aria-label="$t('menu.language')" />
</UDropdownMenu>
</template>
<script setup lang="ts">
import type { DropdownMenuItem } from '@nuxt/ui';
const { locale, locales, setLocale } = useI18n();
const availableLocales = computed(() => {
return locales.value.map(
(optionLocale) =>
({
label: optionLocale.name,
code: optionLocale.code,
type: 'checkbox' as const,
checked: optionLocale.code === locale.value,
onUpdateChecked: () => switchLocale(optionLocale.code),
}) as DropdownMenuItem,
);
});
const switchLocale = (newLocale: string) => {
setLocale(newLocale);
};
</script>

View File

@@ -0,0 +1,27 @@
<template>
<UDropdownMenu :key="colorMode.preference" :items="themes" :content="{ align: 'start' }">
<UButton color="neutral" variant="outline" :icon="icons[currentColor]" :aria-label="$t('menu.theme')" />
</UDropdownMenu>
</template>
<script setup lang="ts">
type Theme = 'light' | 'dark' | 'system';
const icons: Dictionary<Theme, string> = {
light: 'material-symbols:light-mode',
dark: 'material-symbols:dark-mode',
system: 'material-symbols:computer-outline',
};
const colorMode = useColorMode();
const currentColor = computed<Theme>(() => colorMode.preference ?? 'system');
const themes = computed<DropdownValue[]>(() =>
['light', 'dark', 'system'].map((theme) => ({
code: theme,
label: $t(`theme.${theme}`),
icon: icons[theme],
type: 'checkbox' as const,
checked: currentColor.value === theme,
onUpdateChecked: () => switchColor(theme),
})),
);
const switchColor = (theme: Theme) => (colorMode.preference = theme);
</script>

32
app/composables/useApi.ts Normal file
View File

@@ -0,0 +1,32 @@
import type { FetchOptions } from 'ofetch';
export const useApi = () => {
const config = useRuntimeConfig();
const apiFetch = $fetch.create({
baseURL: config.public.apiBase,
});
const get = <T>(url: string, options?: FetchOptions) => apiFetch<T>(url, { method: 'GET', ...options });
const post = <ResultT, PayloadT = Record<string, string | number | boolean>>(
url: string,
body?: PayloadT,
options?: FetchOptions,
) => apiFetch<ResultT>(url, { method: 'POST', body, ...options });
const put = <ResultT, PayloadT = Record<string, string | number | boolean>>(
url: string,
body?: PayloadT,
options?: FetchOptions,
) => apiFetch<ResultT>(url, { method: 'PUT', body, ...options });
const patch = <ResultT, PayloadT = Record<string, string | number | boolean>>(
url: string,
body?: PayloadT,
options?: FetchOptions,
) => apiFetch<ResultT>(url, { method: 'PATCH', body, ...options });
const del = <T>(url: string, options?: FetchOptions) => apiFetch<T>(url, { method: 'DELETE', ...options });
return { get, post, put, patch, del };
};

View File

@@ -0,0 +1,8 @@
export const useBackend = () => {
const api = useApi();
const getMeta = () => api.get<MetaResponse>('/meta');
const postContact = (contact: ContactRequest) => api.post<ContactRequest, ContactResponse>('/contact', contact);
return { getMeta, postContact };
};

View File

@@ -0,0 +1,64 @@
import { withLeadingSlash } from 'ufo';
import type { Collections } from '@nuxt/content';
export const useDataJson = (prefix: string) => {
const route = useRoute();
const { locale } = useI18n();
const slug = computed(() => {
// Use route.params.slug for dynamic routes, or route.path for static routes
const slugValue = route.params.slug || route.path;
return withLeadingSlash(String(slugValue));
});
const key = computed(() => prefix + '-' + slug.value);
const getData = async <T>(
collectionPrefix: string,
options: {
useFilter?: boolean;
fallbackToEnglish?: boolean;
extractMeta?: boolean;
} = {},
) => {
const { useFilter = false, fallbackToEnglish = false, extractMeta = false } = options;
const { data } = await useAsyncData(
key.value,
async () => {
const collection = (collectionPrefix + locale.value) as keyof Collections;
let content;
if (useFilter) {
// For data collections, use .all() and filter
const allData = await queryCollection(collection).all();
content = allData.filter((source) => source.meta.path == slug.value)[0];
} else {
// For page collections, use .path().first()
content = await queryCollection(collection).path(slug.value).first();
if (!content && fallbackToEnglish && locale.value !== 'en') {
content = await queryCollection('content_en').path(slug.value).first();
}
}
return extractMeta ? content?.meta : content;
},
{
watch: [locale], // Automatically refresh when locale changes
},
);
return data as Ref<T | null>;
};
const getJsonData = async (collectionPrefix: string = 'content_data_') => {
return getData(collectionPrefix, { useFilter: true, extractMeta: true });
};
const getPageContent = async (collectionPrefix: string = 'content_', fallbackToEnglish: boolean = true) => {
return getData(collectionPrefix, { fallbackToEnglish });
};
const getCachedData = () => {
const { data } = useNuxtData(key.value);
return data;
};
return { getJsonData, getPageContent, getCachedData };
};

View File

@@ -0,0 +1,27 @@
export interface MetaImageOptions {
url: string;
alt: string;
}
export interface MetaOptions {
title: string;
description: string;
image?: MetaImageOptions;
}
export const useMeta = (options: MetaOptions) => {
const titleSuffix = ' Lucien Cartier-Tilet';
useSeoMeta({
title: () => options.title + titleSuffix,
ogTitle: () => options.title + titleSuffix,
twitterTitle: () => options.title + titleSuffix,
description: () => options.description,
ogDescription: () => options.description,
twitterDescription: () => options.description,
twitterCard: options.image ? 'summary_large_image' : 'summary',
ogImage: () => options.image?.url,
ogImageAlt: () => options.image?.alt,
twitterImage: () => options.image?.url,
twitterImageAlt: () => options.image?.alt,
});
};

5
app/layouts/centered.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div class="text-center prose prose-lg mx-auto max-w-prose">
<slot />
</div>
</template>

5
app/layouts/default.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div class="min-h-screen mx-auto px-4 py-8 max-w-6xl">
<slot />
</div>
</template>

22
app/pages/[...slug].vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<NuxtLayout v-if="page" :name="page.meta?.layout ?? 'default'">
<ContentRenderer :value="page" />
</NuxtLayout>
<div v-else>
<h1>Page not found</h1>
<p>This page doesn&apos;t exist in {{ locale }} language.</p>
</div>
</template>
<script setup lang="ts">
const { getPageContent } = useDataJson('page');
const page = await getPageContent();
// Pre-fetch JSON data for MDC components to avoid hydration issues
const { getJsonData } = useDataJson('page-data');
const pageData = await getJsonData();
// Provide data to child MDC components
provide('pageData', pageData);
useMeta({ title: page.value?.title, description: page.value?.description });
</script>

47
app/pages/resume.vue Normal file
View File

@@ -0,0 +1,47 @@
<template>
<NuxtLayout name="default">
<h1 class="text-4xl text-highlighted font-bold mb-8">
{{ $t('pages.resume.name') }}
</h1>
<UPageCard class="bg-background-100 my-10">
<p>
{{ $t('pages.resume.experience') }}
</p>
<UTimeline v-model="valueExp" reverse :items="resumeContent?.experience" class="w-full">
<template #description="{ item }">
<div class="flex flex-col gap-2">
<p>
{{ item.description }}
</p>
<UiBadgeList :tools="item.tools" />
</div>
</template>
</UTimeline>
</UPageCard>
<UPageCard class="bg-background-100 my-10">
<p>
{{ $t('pages.resume.education') }}
</p>
<UTimeline v-model="valueEd" reverse :items="resumeContent?.education" class="w-full" />
</UPageCard>
<UiBadgeListCard :tools="resumeContent?.otherTools">{{ $t('pages.resume.tools') }}</UiBadgeListCard>
<UiBadgeListCard :tools="resumeContent?.devops">{{ $t('pages.resume.devops') }}</UiBadgeListCard>
<UiBadgeListCard :tools="resumeContent?.os">{{ $t('pages.resume.os') }}</UiBadgeListCard>
<UiBadgeListCard :tools="resumeContent?.programmingLanguages">{{
$t('pages.resume.programmingLanguages')
}}</UiBadgeListCard>
<UiBadgeListCard :tools="resumeContent?.frameworks">{{ $t('pages.resume.frameworks') }}</UiBadgeListCard>
</NuxtLayout>
</template>
<script setup lang="ts">
useMeta({
title: $t('pages.resume.name'),
description: $t('pages.resume.description'),
});
const { getJsonData } = useDataJson('resume');
const resumeContent = await getJsonData();
const arrLength = (array?: T[]) => (array ? array.length - 1 : 0);
const valueExp = computed(() => arrLength(resumeContent.value?.experience));
const valueEd = computed(() => arrLength(resumeContent.value?.education));
</script>

11
app/types/api/contact.ts Normal file
View File

@@ -0,0 +1,11 @@
export interface ContactRequest {
name: string;
email: string;
message: string;
website?: string | null;
}
export interface ContactResponse {
success: boolean;
message: string;
}

4
app/types/api/error.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface ApiError {
message: string;
success: boolean;
}

4
app/types/api/meta.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface MetaResponse {
version: string;
name: string;
}

3
app/types/dictionary.ts Normal file
View File

@@ -0,0 +1,3 @@
export interface Dictionary<K, T> {
[key: K]: T;
}

13
app/types/resume.ts Normal file
View File

@@ -0,0 +1,13 @@
export interface ResumeExperience extends TimelineItem {
tools: string[];
}
export interface ResumeContent {
experience: ResumeExperience[];
education: TimelineItem[];
otherTools: string[];
devops: string[];
os: string[];
programmingLanguages: string[];
frameworks: string[];
}

0
app/types/tool.ts Normal file
View File

42
content.config.ts Normal file
View File

@@ -0,0 +1,42 @@
import { defineCollection, defineContentConfig } from '@nuxt/content';
import { z } from 'zod';
const commonSchema = z.object({
title: z.string(),
description: z.string()
});
export default defineContentConfig({
collections: {
content_en: defineCollection({
type: 'page',
source: {
include: 'en/**/*.md',
prefix: '',
},
schema: commonSchema,
}),
content_fr: defineCollection({
type: 'page',
source: {
include: 'fr/**/*.md',
prefix: '',
},
schema: commonSchema,
}),
content_data_en: defineCollection({
type: 'data',
source: {
include: 'en/**/*.json',
prefix: ''
},
}),
content_data_fr: defineCollection({
type: 'data',
source: {
include: 'fr/**/*.json',
prefix: ''
},
}),
},
});

View File

@@ -1,4 +0,0 @@
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((typescript-mode . ((typescript-indent-level . 2))))

View File

@@ -1,25 +0,0 @@
import { defineClientConfig } from '@vuepress/client';
import ResponsiveImage from './components/ResponsiveImage.vue';
import ListRepositories from './components/GitHub/ListRepositories.vue';
import FetchRepositories from './components/GitHub/FetchRepositories.vue';
import GithubRepository from './components/GitHub/GithubRepository.vue';
import ApiLoader from './components/ApiLoader.vue';
import LoaderAnimation from './components/LoaderAnimation.vue';
import FetchError from './components/FetchError.vue';
import Icon from './components/Icon.vue';
export default defineClientConfig({
enhance({ app }) {
app.component('ResponsiveImage', ResponsiveImage);
app.component('ListRepositories', ListRepositories);
app.component('FetchRepositories', FetchRepositories);
app.component('GithubRepository', GithubRepository);
app.component('ApiLoader', ApiLoader);
app.component('LoaderAnimation', LoaderAnimation);
app.component('FetchError', FetchError);
app.component('Icon', Icon);
},
setup() {},
layouts: {},
rootComponents: [],
});

View File

@@ -1,36 +0,0 @@
<template>
<slot v-if="loading" name="loader">
<LoaderAnimation />
</slot>
<slot v-else-if="error" name="error">
<FetchError :url="props.url" />
</slot>
<slot v-else> </slot>
</template>
<script setup lang="ts">
import LoaderAnimation from './LoaderAnimation.vue';
import FetchError from './FetchError.vue';
import { useFetchAndCache } from '../composables/fetchAndCache';
const props = defineProps({
url: {
default: '',
required: true,
type: String,
},
cacheName: {
required: true,
type: String,
},
alreadyKnownData: Object,
});
const emits = defineEmits(['loaded', 'error', 'loading']);
const { loading, error } = useFetchAndCache(props.url, {
emits: emits,
cacheName: props.cacheName,
});
</script>

View File

@@ -1,26 +0,0 @@
<template>
<div class="error rounded-corners card-width">
<p>API call to {{ props.url }} failed</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
url: {
required: true,
type: String,
},
});
</script>
<style lang="less">
@import 'node_modules/nord/src/lesscss/nord.less';
@import '../styles/classes.less';
.error {
display: inline-block;
padding: 2rem;
text-align: center;
background: @nord11;
}
</style>

View File

@@ -1,46 +0,0 @@
<template>
<ApiLoader :url="fetchUrl" @loaded="filterRepos" cache-name="repos" />
<slot />
</template>
<script setup lang="ts">
import { PropType, ref } from 'vue';
import { GithubRepo } from '../../types/github';
const props = defineProps({
sortBy: {
default: 'none',
required: false,
type: String as PropType<'stars' | 'forks' | 'pushed_at'>,
},
user: {
default: '',
required: true,
type: String,
},
limit: {
default: 5,
required: false,
type: Number,
},
});
const emits = defineEmits(['loaded']);
const fetchUrl = `https://api.github.com/users/${props.user}/repos?per_page=100`;
const repos = ref<GithubRepo[]>([]);
const filterRepos = (response: GithubRepo[]) => {
repos.value = response
.sort((a, b) => {
if (props.sortBy === 'stars') {
return b.stargazers_count - a.stargazers_count;
}
if (props.sortBy === 'pushed_at') {
const dateA = new Date(a.pushed_at);
const dateB = new Date(b.pushed_at);
return dateB.getTime() - dateA.getTime();
}
return b.forks_count - a.forks_count;
})
.slice(0, +props.limit);
emits('loaded', repos.value);
};
</script>

View File

@@ -1,89 +0,0 @@
<template>
<div
class="githubRepo flex-col flex-space-between gap-1rem rounded-corners card-width"
>
<ApiLoader
:cache-name="repoName()"
:url="fetchUrl"
:already-known-data="props.data"
@loaded="(repo: GithubRepo) => (repository = repo)"
>
<h3>{{ repository?.name }}</h3>
<div>
<p>
{{ repository?.description }}
</p>
</div>
<div class="flex-row flex-start gap-1rem stats">
<div class="stars">
<Icon name="star" /> {{ repository?.stargazers_count }}
</div>
<div class="forks">
<Icon name="fork" /> {{ repository?.forks_count }}
</div>
<div class="link">
<a :href="repository?.html_url"><i class="icon phunic-link" /></a>
</div>
</div>
</ApiLoader>
</div>
</template>
<script setup lang="ts">
import ApiLoader from '../ApiLoader.vue';
import { GithubRepo } from '../../types/github';
import { PropType, Ref, ref } from 'vue';
const props = defineProps({
data: Object as PropType<GithubRepo>,
repoName: String,
});
const repoName = (): string => {
return props.data ? props.data.full_name : props.repoName;
};
const fetchUrl = `https://api.github.com/repos/${repoName()}`;
const repository: Ref<GithubRepo | null> = ref(null);
</script>
<style lang="less">
@import 'node_modules/nord/src/lesscss/nord.less';
@import '../../styles/classes.less';
.githubRepo {
padding: 2rem;
background-color: @nord4;
align-self: auto;
h3,
h3:first-child {
margin: 0;
padding: 0;
}
html.dark & {
background-color: @nord3;
}
.info {
max-width: 30rem;
}
.stats {
width: 4rem;
div {
.flex-row();
gap: 0.3rem;
}
}
.link {
a {
display: flex;
align-items: center;
}
}
}
</style>

View File

@@ -1,54 +0,0 @@
<template>
<div class="list-repos flex-col gap-1rem">
<FetchRepositories
v-if="props.user !== ''"
:sort-by="props.sortBy"
:user="props.user"
:limit="props.limit"
@loaded="(response: GithubRepo[]) => (repos = response)"
>
<GithubRepository
:data="repo"
type="repositories"
v-for="repo in repos"
/>
</FetchRepositories>
<slot v-else />
</div>
</template>
<script setup lang="ts">
import FetchRepositories from './FetchRepositories.vue';
import GithubRepository from './GithubRepository.vue';
import { PropType, Ref, ref } from 'vue';
import { GithubRepo } from '../../types/github';
const props = defineProps({
sortBy: {
default: 'none',
required: false,
type: String as PropType<'stars' | 'forks' | 'pushed_at'>,
},
user: {
default: '',
required: false,
type: String,
},
limit: {
default: 5,
required: false,
type: Number,
},
});
const repos: Ref<GithubRepo[]> = ref(null);
</script>
<style lang="less">
@import '../../styles/classes.less';
.list-repos {
margin: 2rem auto;
}
</style>

View File

@@ -1,17 +0,0 @@
<template>
<i :class="`icon phunic-${props.name}`" />
</template>
<script setup lang="ts">
const props = defineProps({
name: {
default: '',
required: true,
type: String,
},
});
</script>
<style lang="less">
@import '../styles/fonts.less';
</style>

View File

@@ -1,47 +0,0 @@
<template>
<svg
class="circle-loader"
width="40"
height="40"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="20" cy="20" r="15" />
</svg>
</template>
<style lang="less" scoped>
@import 'node_modules/nord/src/lesscss/nord.less';
.circle-loader {
margin-left: 48%;
fill: transparent;
stroke: @nord7;
stroke-width: 5;
animation: dash 1.5s ease infinite, rotate 2s linear infinite;
}
@keyframes dash {
0% {
stroke-dasharray: 1, 95;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 85, 95;
stroke-dashoffset: -25;
}
100% {
stroke-dasharray: 85, 95;
stroke-dashoffset: -90;
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,25 +0,0 @@
<template>
<img :srcset="srcset" :sizes="sizes" :alt="props.alt" :src="props.src" />
</template>
<script setup lang="ts">
const props = defineProps<{
src: string;
width: number;
preview: string;
previewWidth: number;
previewThreshold?: number;
alt?: string;
}>();
const srcset = [
`${props.preview} ${props.previewWidth}w`,
`${props.src} ${props.width}w`,
].join(', ');
const sizes = [
`(max-width: ${props.previewThreshold || props.previewWidth}px) ${
props.previewWidth
}px`,
`${props.width}px`,
].join(', ');
</script>

View File

@@ -1,62 +0,0 @@
import { Ref, computed, ref, watchEffect } from 'vue';
interface CacheOptions {
lifetime?: number;
timestampSuffix?: string;
forceUpdate?: boolean;
}
/**
* Cache data in local storage.
*
* The cache is updated if:
* - cache data does not exist
* - cached data is outdated and `data` is not null
* - or `options.forceUpdate` is true, regardless of the value of `data`
*
* Otherwise, data is retrieved from cache.
*
* @param {string} name Name of the cached value in local storage
* @param {Ref<T>} data Data to cache
* @param {CacheOptions} options Tweaks to the behaviour of the function
*/
export const useCache = <T>(
name: string,
data: Ref<T>,
options: CacheOptions,
) => {
const error = ref<string>(null);
const timestampName = name + (options?.timestampSuffix || '-timestamp');
const lifetime = options?.lifetime || 1000 * 60 * 60; // one hour in milliseconds
const lastUpdated: number = +localStorage.getItem(timestampName);
const cacheAge: number = Date.now() - lastUpdated;
const isDataOutdated = computed(() => {
return cacheAge > lifetime;
});
const shouldUpdate = computed(
() => options?.forceUpdate || (isDataOutdated.value && data.value != null),
);
const setData = () => {
console.log('Setting data in cache with name', name);
localStorage.setItem(name, JSON.stringify(data.value));
localStorage.setItem(timestampName, `${Date.now()}`);
};
const getData = () => {
console.log('Getting data from cache with name', name);
const cached = localStorage.getItem(name);
console.log('Value from storage:', cached);
try {
data.value = JSON.parse(cached);
} catch (err) {
console.error('Failed to parse cached data:', err);
data.value = null;
error.value = err;
}
};
getData();
watchEffect(() => (shouldUpdate.value ? setData() : getData()));
return { error, isDataOutdated };
};

View File

@@ -1,72 +0,0 @@
import { ref, Ref } from 'vue';
import { useCache } from './cache';
type FetchAndCacheEmitter = (
event: 'loaded' | 'error' | 'loading',
...args: any[]
) => void;
interface UseFetchAndCacheOptions {
cacheLifetime?: number;
cacheName?: string;
emits?: FetchAndCacheEmitter;
}
const dummyEmits = (
_event: 'loaded' | 'error' | 'loading',
..._args: any[]
) => {};
export const useFetchAndCache = <T, E>(
url: string,
options?: UseFetchAndCacheOptions,
) => {
const data = ref<T | null>(null) as Ref<T>;
const error = ref<E | null>(null) as Ref<E>;
const loading = ref<boolean>(true);
const cacheLifetime: number = options?.cacheLifetime || 1000 * 60 * 60; // one hour
const cacheName: string = options?.cacheName || url;
const { isDataOutdated: isCacheOutdated, error: cacheError } = useCache(
cacheName,
data,
{
lifetime: cacheLifetime,
},
);
const emits: FetchAndCacheEmitter = options?.emits || dummyEmits;
const loaded = () => {
loading.value = false;
emits('loaded', data.value);
};
const fetchData = () => {
loading.value = true;
emits('loading');
console.log('Fetching from URL', url);
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<T>;
})
.then((responseData) => {
data.value = responseData;
loaded();
})
.catch((e) => {
console.warn('Caught error!', e);
error.value = e;
emits('error', e);
})
.finally(() => (loading.value = false));
};
if (isCacheOutdated.value || cacheError.value != null) {
fetchData();
} else {
loaded();
}
return { data, loading, error };
};

View File

@@ -1,46 +0,0 @@
import { defaultTheme } from '@vuepress/theme-default';
import { viteBundler } from '@vuepress/bundler-vite';
import { defineUserConfig } from 'vuepress';
import { searchProPlugin } from 'vuepress-plugin-search-pro';
import { umamiAnalyticsPlugin } from '@vuepress/plugin-umami-analytics';
import { head } from './head';
import { locales, searchLocales } from './locales';
import { themeLocales } from './themeLocales';
const isProd = process.env.NODE_ENV === 'production';
export default defineUserConfig({
lang: 'fr-FR',
title: 'Lucien Cartier-Tilet',
description: 'Site web personnel de Lucien Cartier-Tilet',
head: head,
bundler: viteBundler({}),
markdown: {
html: true,
linkify: true,
typographer: true,
},
plugins: [
searchProPlugin({
indexContent: true,
locales: searchLocales,
}),
isProd
? umamiAnalyticsPlugin({
id: '67166941-8c83-4a19-bc8c-139e44b7f7aa',
link: 'https://umami.phundrak.com/script.js',
})
: [],
],
locales: locales,
theme: defaultTheme({
contributors: false,
locales: themeLocales,
repo: 'https://labs.phundrak.com/phundrak/phundrak.com',
themePlugins: {
copyCode: false,
prismjs: false,
},
}),
});

View File

@@ -1,142 +0,0 @@
import { HeadAttrsConfig } from 'vuepress';
interface SimplifiedHeader {
tag: string;
content: HeadAttrsConfig[];
}
const simplifiedHead: SimplifiedHeader[] = [
{
tag: 'meta',
content: [
{
name: 'author',
content: 'Lucien Cartier-Tilet',
},
{
name: 'fediverse:creator',
content: '@phundrak@mastodon.phundrak.com',
},
{
property: 'og:image',
content: 'https://cdn.phundrak.com/img/rich_preview.png',
},
{
property: 'org:title',
content: 'Lucien Cartier-Tilet',
},
{
property: 'og:description',
content: 'Site web personnel de Lucien Cartier-Tilet',
},
{
name: 'twitter:card',
content: 'summary',
},
{
name: 'twitter:site',
content: '@phundrak',
},
{
name: 'twitter:creator',
content: '@phundrak',
},
{
name: 'build-status',
content: `value: ${process.env.NODE_ENV}`,
},
{ name: 'msapplication-TileColor', content: '#3b4252' },
{ name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' },
{ name: 'theme-color', content: '#3b4252' },
],
},
{
tag: 'link',
content: [
{
rel: 'apple-touch-icon',
sizes: '57x57',
href: '/apple-icon-57x57.png',
},
{
rel: 'apple-touch-icon',
sizes: '60x60',
href: '/apple-icon-60x60.png',
},
{
rel: 'apple-touch-icon',
sizes: '72x72',
href: '/apple-icon-72x72.png',
},
{
rel: 'apple-touch-icon',
sizes: '76x76',
href: '/apple-icon-76x76.png',
},
{
rel: 'apple-touch-icon',
sizes: '114x114',
href: '/apple-icon-114x114.png',
},
{
rel: 'apple-touch-icon',
sizes: '120x120',
href: '/apple-icon-120x120.png',
},
{
rel: 'apple-touch-icon',
sizes: '144x144',
href: '/apple-icon-144x144.png',
},
{
rel: 'apple-touch-icon',
sizes: '152x152',
href: '/apple-icon-152x152.png',
},
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-icon-180x180.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '192x192',
href: '/android-icon-192x192.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
href: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '96x96',
href: '/favicon-96x96.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
href: '/favicon-16x16.png',
},
{ rel: 'manifest', href: '/manifest.json' },
],
},
];
const headBuilder = [];
simplifiedHead.forEach((tag) => {
tag.content.forEach((element) => {
headBuilder.push([tag.tag, element]);
});
});
headBuilder.push([
'a',
{ rel: 'me', href: 'https://mastodon.phundrak.com/@phundrak' },
'Mastodon',
]);
export const head = headBuilder;

View File

@@ -1,54 +0,0 @@
import { SearchProLocaleConfig } from 'vuepress-plugin-search-pro';
export const locales = {
'/': {
lang: 'fr-FR',
title: 'Lucien Cartier-Tilet',
description: 'Site web personnel de Lucien Cartier-Tilet',
},
'/en/': {
lang: 'en-US',
title: 'Lucien Cartier-Tilet',
description: 'Personal website of Lucien Cartier-Tilet',
},
'/lfn/': {
lang: 'lfn',
title: 'Lucien Cartier-Tilet',
description: 'loca ueb de Lucien Cartier-Tilet',
},
};
export const searchLocales: SearchProLocaleConfig = {
'/fr/': {
cancel: 'Annuler',
placeholder: 'Rechercher',
search: 'Rechercher',
searching: 'Recherche',
defaultTitle: 'Documentation',
select: 'sélectionner',
navigate: 'naviguer',
autocomplete: 'auto-complétion',
exit: 'fermer',
queryHistory: 'Historique de recherche',
resultHistory: 'Historique des résultats',
emptyHistory: "Vider l'historique de recherche",
emptyResult: 'Aucun résultat trouvé',
loading: 'Chargement des index de recherche...',
},
'/lfn/': {
cancel: 'Cansela',
placeholder: 'Xerca',
search: 'Xerca',
searching: 'Xercante',
defaultTitle: 'Documentos',
select: 'eleje',
navigate: 'naviga',
autocomplete: 'auto-completi',
exit: 'sorti',
queryHistory: 'Historia de xerca',
resultHistory: 'Historia de resultas',
emptyHistory: 'Historia vacua',
emptyResult: 'Resultas vacua',
loading: 'Cargante la indise de xerca...',
},
};

View File

@@ -1,24 +0,0 @@
{
"subject": "acct:phundrak@emacs.ch",
"aliases": ["https://emacs.ch/@phundrak", "https://emacs.ch/users/phundrak"],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://emacs.ch/@phundrak"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://emacs.ch/users/phundrak"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://emacs.ch/authorize_interaction?uri={uri}"
},
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": "https://auth.phundrak.com"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#eceff4</TileColor></tile></msapplication></browserconfig>

View File

@@ -1,18 +0,0 @@
body {
margin: 3em;
}
body, code,p {
background: #e5e9f0 !important;
line-height: 1.4 !important;
color: #2E3440;
font-size: 16px !important;
}
blockquote, blockquote p {
border-left-color: #3b4252;
}
pre {
padding: 10px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="phunic" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="star" horiz-adv-x="1152" d="M575.8 960c18.4 0 35.2-10.4 43.2-27l137.2-282.6 306.4-45.2c18-2.6 33-15.2 38.6-32.6s1-36.2-11.8-49l-222.2-220.4 52.4-311.2c3-18-4.4-36.2-19.2-47s-34.6-12-50.6-3.4l-274 146.4-273.8-146.2c-16.2-8.6-35.8-7.4-50.6 3.4s-22.4 29-19.4 47l52.4 311.2-222.2 220.2c-13 12.8-17.4 31.8-11.8 49s20.6 29.8 38.6 32.6l306.4 45.2 137.2 282.6c8.2 16.6 24.8 27 43.2 27zM575.8 802l-105-216.4c-7-14.2-20.4-24.2-36.2-26.6l-236.6-34.8 171.8-170.2c11-11 16.2-26.6 13.6-42l-40.6-239.4 210.4 112.4c14.2 7.6 31.2 7.6 45.2 0l210.4-112.4-40.4 239.2c-2.6 15.4 2.4 31 13.6 42l171.8 170.2-236.6 35c-15.6 2.4-29.2 12.2-36.2 26.6l-105.2 216.4z" />
<glyph unicode="&#xe901;" glyph-name="envelope" d="M96 832c-53 0-96-43-96-96 0-30.2 14.2-58.6 38.4-76.8l435.2-326.4c22.8-17 54-17 76.8 0l435.2 326.4c24.2 18.2 38.4 46.6 38.4 76.8 0 53-43 96-96 96h-832zM0 608v-416c0-70.6 57.4-128 128-128h768c70.6 0 128 57.4 128 128v416l-435.2-326.4c-45.6-34.2-108-34.2-153.6 0l-435.2 326.4z" />
<glyph unicode="&#xe902;" glyph-name="emacs" horiz-adv-x="953" d="M783.986 963.865h-275.721c83.997-19.531 275.721-63.47 275.721-98.442 0-74.39-354.471 19.638-433.184 19.638-39.366 0-118.026-6.581-118.115-59.058-0.338-137.717 185.123-255.869 314.998-334.671-90.454 26.238-173.296 39.348-252.045 39.348-102.407 0-299.236-39.348-299.236-177.28 0-121.709 246.779-233.919 314.998-255.834 118.507-38.156 314.998-58.844 314.998-78.749 0-19.798-157.464-39.348-354.471-39.348h-118.098c78.821-39.561 196.918-39.561 315.104-39.561l118.098 0.089c78.732 0.089 275.561 2.935 275.561 70.905 0 59.253-236.194 109.524-354.381 133.912-164.739 33.975-284.367 100.096-275.561 149.475 20.955 118.667 314.998 118.187 551.299 118.187-103.047 82.876-354.471 236.285-354.471 303.31 0 28.551 39.455 35.238 78.732 35.346 157.553 0.907 275.721-33.318 315.158-33.318 78.66 0 118.045 49.095 118.045 108.172 0.035 98.53-117.99 137.877-157.427 137.877z" />
<glyph unicode="&#xe903;" glyph-name="gitea" d="M179.534 736.179c-10.527 0-22.386-0.823-35.824-3.754-14.172-2.932-54.552-12.077-87.618-43.842-73.302-65.32-54.584-169.208-52.304-184.844 2.769-19.058 11.207-71.999 51.604-118.097 74.605-91.383 235.239-89.273 235.239-89.273s19.688-47.089 49.823-90.418c40.723-53.917 82.592-95.932 123.315-100.981 102.622 0 307.714 0.127 307.714 0.127s19.581-0.143 46.131 16.798c22.805 13.846 43.141 38.115 43.141 38.114s21.011 22.5 50.332 73.811c8.959 15.801 16.455 31.062 22.97 45.559 0 0 89.91 190.801 89.91 376.498-1.792 56.198-15.641 66.099-18.899 69.357-6.679 6.679-15.653 6.554-15.653 6.554s-190.895-10.763-289.771-13.044c-21.601-0.488-43.054-0.981-64.33-1.145l0.127-190.572c0 0 93.541-39.449 135.404-65.347 6.027-3.747 16.6-11.1 20.998-23.479 3.421-9.936 3.232-21.334-1.654-31.433l-99.326-206.67c-10.099-20.687-34.869-29.534-55.231-19.598l-206.733 99.39c-20.362 9.774-29.153 34.516-19.216 55.039l99.39 206.733c9.774 20.362 34.516 29.153 55.040 19.216 27.903-13.465 43.837-21.063 43.969-21.125 0 59.356-0.127 177.655-0.127 177.655-47.239-0.652-145.331 3.627-145.331 3.627s-230.325 11.527-255.41 13.807c-7.982 0.489-17.153 1.336-27.679 1.336zM199.387 657.979c0 0 11.57-96.789 25.579-153.475 11.728-47.565 40.406-126.559 40.406-126.559s-42.528 5.053-70.056 14.825c-42.189 13.846-60.067 30.479-60.067 30.479s-31.131 21.836-46.768 64.839c-26.877 71.999-2.291 115.934-2.291 115.934s13.708 36.651 62.739 48.868c22.479 6.027 50.459 5.090 50.459 5.090zM523.077 350.102c-13.357-0.163-25.085-9.448-28.18-22.479s3.258-26.551 14.823-32.579c12.543-6.516 28.506-2.932 36.977 8.796 8.308 11.565 7.004 27.529-2.932 37.628l39.094 79.98c2.443-0.163 6.027-0.326 10.099 0.814 6.679 1.466 11.565 5.864 11.565 5.864 6.841-2.932 14.009-6.19 21.502-9.936 7.819-3.909 15.149-7.982 21.828-11.891 1.466-0.814 2.932-1.792 4.561-3.095 2.606-2.118 5.538-5.050 7.656-8.959 3.095-8.959-3.095-24.271-3.095-24.271-3.747-12.38-29.972-66.134-29.972-66.134-13.194 0.326-24.923-8.145-28.832-20.362-4.235-13.194 1.792-28.18 14.497-34.696s28.343-2.769 36.651 8.633c8.145 11.077 7.493 26.551-1.792 36.814 3.095 6.027 6.027 12.054 9.122 18.407 8.145 16.941 21.99 49.519 21.99 49.519 1.466 2.769 9.285 16.778 4.398 34.696-4.072 18.57-20.524 27.203-20.524 27.203-19.873 12.869-47.565 24.76-47.565 24.76s0 6.679-1.792 11.565c-1.792 5.050-4.561 8.308-6.353 10.262 7.656 15.801 15.312 31.438 22.968 47.239-6.679 3.258-13.194 6.516-19.873 9.936-7.819-15.963-15.801-32.090-23.619-48.053-10.914 0.163-21.013-5.701-26.226-15.312-5.538-10.262-4.398-22.968 3.095-32.253l-40.072-82.098z" />
<glyph unicode="&#xe904;" glyph-name="share" horiz-adv-x="896" d="M570.8 565.8l-188.2-94c1-7.8-0.4-14-0.4-23.8 0-8 1.4-14.2 0.4-23.8l188.2-94c34.4 33.4 81.4 53.8 133.2 53.8 106 0 192-84.2 192-192 0-106-86-192-192-192-107.8 0-192 86-192 192 0 9.8 0.4 16 1.4 23.8l-188.2 94c-34.4-33.4-81.4-53.8-133.2-53.8-106.040 0-192 86-192 192 0 107.8 85.96 192 192 192 51.8 0 98.8-20.4 133.2-53.8l188.2 94c-1 9.6-1.4 15.8-1.4 23.8 0 106.040 84.2 192 192 192 106 0 192-85.96 192-192 0-106-86-192-192-192-51.8 0-98.8 20.4-133.2 53.8v0z" />
<glyph unicode="&#xe905;" glyph-name="terminal" horiz-adv-x="1152" d="M18.744 786.74c-24.992 25-24.992 65.52 0 90.52 24.996 24.98 65.516 24.98 90.516 0l383.94-384.060c25-25 25-65.4 0-90.4l-383.94-384c-25-25-65.52-25-90.516 0-24.992 25-24.992 65.4 0 90.4l338.656 338.8-338.656 338.74zM1088 128c35.4 0 64-28.6 64-64s-28.6-64-64-64h-576c-35.4 0-64 28.6-64 64s28.6 64 64 64h576z" />
<glyph unicode="&#xe906;" glyph-name="at" d="M415.6 918.54c-186.9-36.64-337.4-187.32-374-374.2-55.28-281.8 137.3-532.4 398.2-570.2 38.020-5.776 72.34 24.52 72.34 62.98v1.326c0 31.48-22.88 57.76-53.68 62.48-168.7 25.96-298.4 172.26-298.4 348.4 0 205.8 177.22 371 386.8 350.8 183.080-17.738 317.2-182.5 317.2-366.4v-32.32c0-44.18-35.88-80.1-80-80.1s-80.020 35.92-80.020 80.1v240.2c0 17.694-14.322 32.040-32.020 32.040l-63.96-0.007c-14.598 0-26.4-9.984-30.24-23.36-49.7 24.3-108.48 32.76-172.12 10.212-77.5-27.46-136.24-97.82-147.44-179.28-18.966-138.020 87.62-256 221.8-256 52.88 0 100.86 19.088 139.18 49.76 48-62.6 130.46-97.38 218.8-74.98 92.36 21.409 153.96 111.809 152.16 205.609v41.8c0 298.4-267.8 531.264-574.6 471.14zM478.2 351.4c-52.94 0-96 43.12-96 96.1s43.060 96.1 96 96.1 96-43.12 96-96.1-41.2-96.1-96-96.1z" />
<glyph unicode="&#xe907;" glyph-name="mastodon" horiz-adv-x="896" d="M866 601.78c0 194.4-127.42 251.4-127.42 251.4-125.040 57.4-457.12 56.8-580.96 0 0 0-127.44-57-127.44-251.4 0-231.4-13.2-518.8 211.26-578.2 81.020-21.4 150.64-26 206.66-22.8 101.62 5.6 158.64 36.2 158.64 36.2l-3.4 73.8s-72.62-22.8-154.24-20.2c-80.82 2.8-166 8.8-179.26 108-1.147 8.146-1.801 17.557-1.801 27.12 0 0.239 0 0.478 0.001 0.717v-0.037c171.26-41.8 317.3-18.2 357.5-13.4 112.24 13.4 210 82.6 222.46 145.8 19.6 99.6 18 243 18 243zM715.76 351.38h-93.26v228.4c0 99.4-128 103.2-128-13.8v-125h-92.66v125.020c0 117-128 113.2-128 13.8v-228.4h-93.46c0 244.2-10.4 295.8 36.82 350 51.8 57.8 159.64 61.6 207.66-12.2l23.2-39 23.2 39c48.22 74.2 156.24 69.6 207.66 12.2 47.42-54.6 36.8-106 36.8-350z" />
<glyph unicode="&#xe908;" glyph-name="conlang" d="M512 800c-385.364 0-621.988-380.55-460.664-693.875l106.664 131.875 654 66 84-130-842.73-71.551c2.159-4.106 4.349-8.208 6.645-12.289l892.086 73.84 20.246-53.512c157.457 279.979-52.304 689.512-460.246 689.512zM410 490h204l54-66-328-30 70 96zM694 412l74-98-560-56 90 118 396 36z" />
<glyph unicode="&#xe909;" glyph-name="link" horiz-adv-x="1280" d="M1159.6 424.6c113 113 113 296 0 409-100 100-257.6 113-372.6 30.8l-3.2-2.2c-28.8-20.6-35.4-60.6-14.8-89.2s60.6-35.4 89.2-14.8l3.2 2.2c64.2 45.8 152 38.6 207.6-17.2 63-63 63-165 0-228l-224.4-224.8c-63-63-165-63-228 0-55.8 55.8-63 143.6-17.2 207.6l2.2 3.2c20.6 28.8 13.8 68.8-14.8 89.2s-68.8 13.8-89.2-14.8l-2.2-3.2c-82.4-114.8-69.4-272.4 30.6-372.4 113-113 296-113 409 0l224.6 224.6zM120.4 471.4c-113-113-113-296 0-409 100-100 257.6-113 372.6-30.8l3.2 2.2c28.8 20.6 35.4 60.6 14.8 89.2s-60.6 35.4-89.2 14.8l-3.2-2.2c-64.2-45.8-152-38.6-207.6 17.2-63 63.2-63 165.2 0 228.2l224.4 224.6c63 63 165 63 228 0 55.8-55.8 63-143.6 17.2-207.8l-2.2-3.2c-20.6-28.8-13.8-68.8 14.8-89.2s68.8-13.8 89.2 14.8l2.2 3.2c82.4 115 69.4 272.6-30.6 372.6-113 113-296 113-409 0l-224.6-224.6z" />
<glyph unicode="&#xe90a;" glyph-name="code" horiz-adv-x="1280" d="M829.6 878.42l-256-896.020c-9.8-34-45.2-53.6-79.2-44-34 9.8-53.6 45.2-44 79.2l256 895.98c9.8 33.988 45.2 53.668 79.2 43.956 34-9.71 53.6-45.136 44-79.116v0zM1037.2 717.2l224-224c25-25 25-65.4 0-90.4l-224-224c-25-25-65.4-25-90.4 0s-25 65.4 0 90.4l178.6 178.8-178.6 178.8c-25 25-25 65.4 0 90.4s65.4 25 90.4 0v0zM333.2 626.8l-178.7-178.8 178.7-178.8c25-25 25-65.4 0-90.4s-65.4-25-90.4 0l-224.056 224c-24.992 25-24.992 65.4 0 90.4l224.056 224c25 25 65.4 25 90.4 0s25-65.4 0-90.4v0z" />
<glyph unicode="&#xe90b;" glyph-name="fork" horiz-adv-x="896" d="M320 800c0-65.6-39.4-120.2-96-146.6v-175.6c37.6 21.8 81.4 34.2 128 34.2h192c70.6 0 128 57.4 128 128v13.4c-56.6 26.4-96 81-96 146.6 0 88.36 71.6 160 160 160s160-71.64 160-160c0-65.6-39.4-120.2-96-146.6v-13.4c0-141.4-114.6-256-256-256h-192c-70.6 0-128-57.4-128-128v-13.4c56.6-24.6 96-81 96-146.6 0-88.4-71.6-160-160-160-88.36 0-160 71.6-160 160 0 65.6 39.5 122 96 146.6v410.8c-56.5 26.4-96 81-96 146.6 0 88.36 71.64 160 160 160 88.4 0 160-71.64 160-160v0zM160 752c26.5 0 48 21.5 48 48s-21.5 48-48 48c-26.5 0-48-21.5-48-48s21.5-48 48-48zM736 848c-26.6 0-48-21.5-48-48s21.4-48 48-48c26.6 0 48 21.5 48 48s-21.4 48-48 48zM160 48c26.5 0 48 21.4 48 48s-21.5 48-48 48c-26.5 0-48-21.4-48-48s21.5-48 48-48z" />
<glyph unicode="&#xe90c;" glyph-name="house" horiz-adv-x="1152" d="M1151.6 449c0-36-30-64.2-64-64.2h-64l1.4-320.2c0-5.6-0.4-10.8-1-16.2v-32.4c0-44.2-35.8-80-80-80h-32c-2.2 0-4.4 1.8-6.6 0.2-2.8 1.6-5.6-0.2-8.4-0.2h-113c-44.2 0-80 35.8-80 80v176c0 35.4-28.6 64-64 64h-128c-35.4 0-64-28.6-64-64v-176c0-44.2-35.8-80-80-80h-111.8c-3 0-6 0.2-9 0.4-2.4-0.2-4.8-0.4-7.2-0.4h-32c-44.18 0-80 35.8-80 80v224c0 1.8 0.060 3.8 0.18 5.6v139.2h-64.080c-36.060 0-64.1 28.2-64.1 64.2 0 18 6.008 34 20.020 48l512.78 446.968c14 14.028 30 16.032 44 16.032s30-4.008 42.2-14.028l510.6-448.972c16-14 24.2-30 22-48v0z" />
<glyph unicode="&#xe90d;" glyph-name="language" horiz-adv-x="1280" d="M896 632c22 0 40-16.2 40-40v-8h120c22 0 40-16.2 40-40 0-22-18-40-40-40h-4l-3.2-9c-17.8-47.2-45-93.2-79.4-130.8 1.8-1 3.6-0.4 5.4-3.2l37.8-22.6c19-11.4 25-36 13.6-55-11.2-19-35.8-25-54.8-13.6l-37.8 22.6c-8.8 5.4-19.4 11-26.2 17-21-15-43.8-28-67.8-38.8l-7.4-3.2c-20.2-9-43.8 0.2-52.8 20.4s0.2 43.8 20.4 52.8l7.2 3.2c12.8 5.8 25.2 14 37 19.6l-24.2 24.4c-15.8 15.6-15.8 40.8 0 56.4 15.6 15.8 40.8 15.8 56.4 0l29.2-29 1.2 0.6c24.8 24.4 45 54.8 59.6 90h-214.2c-23.8 0-40 16.2-40 40 0 22 16.2 40 40 40h104v8c0 22 16.2 40 40 40v-1.8zM320 493.6l38-85.6h-77.8l39.8 85.6zM0 704c0 70.7 57.3 128 128 128h1024c70.6 0 128-57.3 128-128v-512c0-70.6-57.4-128-128-128h-1024c-70.7 0-128 57.4-128 128v512zM640 192h512v512h-512v-512zM356.6 608.2c-6.4 14.4-20.8 23.8-36.6 23.8s-30.2-9.4-36.6-23.8l-127.96-288c-8.96-18.4 0.12-43.8 20.32-52.8 20.18-9 43.84 0.2 52.84 20.4l17.8 42h147.2l17.8-42c9-20.2 32.6-29.4 52.8-20.4s29.4 34.4 20.4 52.8l-128 288z" />
<glyph unicode="&#xe90e;" glyph-name="mic-lines" horiz-adv-x="768" d="M384 256c106.060 0 192 85.94 192 192h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v64h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v65.8h-160c-17.672 0-32 14.328-32 32s14.328 32 32 32l160-1.8c0 106.060-85.94 192-192 192s-192-85.94-192-192v-320c0-106 84.2-192 192-192zM688 576c-26.6 0-48-21.4-48-46.2v-81.8c0-146.66-123.94-264.8-272.6-255.4-132.16 8.338-239.4 133.18-239.4 265.6v71.6c0 24.8-21.5 46.2-48 46.2s-48-21.4-48-46.2v-64.3c0-179.32 127.94-339.2 304-363.4v-70.1h-80c-36.38 0-65.68-30.36-63.92-67.14 0.78-16.46 15.52-28.86 31.92-28.86h320c16.444 0 31.14 12.432 31.92 28.86 1.68 36.74-27.52 67.14-63.92 67.14h-80v67.54c171.4 23.46 304 170.66 304 348.46v81.8c0 24.8-21.4 46.2-48 46.2z" />
<glyph unicode="&#xe90f;" glyph-name="question" horiz-adv-x="640" d="M408.6 895.98h-216.6c-105.88 0-192-86.12-192-192 0-35.34 28.62-62.2 64-62.2s64 28.64 64 62.2c0 35.28 28.68 64 64 64h216.6c57 0 103.4-46.38 103.4-103.58 0-39.44-21.94-74.94-61-94.66l-195.4-114.54c-21.4-11.6-31.6-32.6-31.6-55.2v-80c0-35.34 28.62-63.98 64-63.98s64 28.64 64 63.98v43.4l160 94c78.94 39.5 128 118.84 128 207 0 127.7-103.8 231.58-231.4 231.58zM288 160c-44.18 0-80-35.82-80-80s35.82-78.2 80-78.2 80 35.8 80 78.2-35.8 80-80 80z" />
<glyph unicode="&#xe910;" glyph-name="discord" horiz-adv-x="1280" d="M1049.062 820.328c-0.331 0.632-0.862 1.122-1.508 1.393l-0.020 0.007c-69.126 32.613-149.446 58.394-233.51 73.348l-5.862 0.864c-0.2 0.039-0.429 0.061-0.664 0.061-1.364 0-2.552-0.751-3.173-1.863l-0.009-0.018c-9.162-16.095-19.138-36.2-28.112-56.841l-1.688-4.359c-40.401 6.456-86.983 10.145-134.426 10.145s-94.023-3.689-139.472-10.794l5.046 0.65c-10.583 24.679-20.712 44.78-31.866 64.218l1.596-3.018c-0.669 1.124-1.878 1.866-3.26 1.866-0.208 0-0.412-0.017-0.61-0.049l0.022 0.003c-89.917-15.782-170.24-41.566-245.309-76.709l5.933 2.495c-0.662-0.286-1.201-0.752-1.568-1.338l-0.008-0.014c-152.458-227.676-194.222-449.754-173.734-669.082 0.124-1.122 0.692-2.092 1.521-2.743l0.009-0.007c83.919-62.742 181.476-113.306 286.742-146.499l6.908-1.879c0.327-0.102 0.702-0.16 1.092-0.16 1.236 0 2.333 0.59 3.027 1.503l0.007 0.009c20.992 28.236 40.943 60.215 58.246 93.782l1.828 3.902c0.254 0.49 0.402 1.069 0.402 1.683 0 1.595-1.004 2.956-2.415 3.485l-0.026 0.008c-35.78 13.792-65.987 28.482-94.765 45.347l3.029-1.641c-1.12 0.667-1.859 1.872-1.859 3.25 0 1.221 0.58 2.306 1.48 2.995l0.009 0.007c6.164 4.618 12.332 9.422 18.218 14.274 0.623 0.516 1.432 0.83 2.313 0.83 0.539 0 1.050-0.117 1.51-0.327l-0.023 0.009c192.458-87.834 400.82-87.834 591 0 0.455 0.221 0.99 0.351 1.556 0.351 0.873 0 1.673-0.308 2.298-0.822l-0.006 0.005c5.888-4.852 12.054-9.702 18.264-14.32 0.922-0.695 1.511-1.787 1.511-3.017 0-1.367-0.728-2.565-1.818-3.225l-0.017-0.009c-25.909-15.466-56.144-30.151-87.654-42.266l-4.126-1.394c-1.425-0.553-2.416-1.913-2.416-3.505 0-0.627 0.154-1.218 0.426-1.738l-0.010 0.021c19.628-37.639 39.545-69.579 61.585-99.876l-1.557 2.246c0.684-0.951 1.788-1.563 3.035-1.563 0.389 0 0.765 0.060 1.117 0.17l-0.026-0.007c112.357 34.95 210.088 85.528 296.679 150.197l-2.555-1.825c0.853 0.627 1.427 1.59 1.529 2.689l0.001 0.015c24.528 253.566-41.064 473.824-173.868 669.082zM444.982 284.84c-57.944 0-105.688 53.174-105.688 118.478s46.818 118.482 105.688 118.482c59.33 0 106.612-53.64 105.686-118.478 0-65.308-46.82-118.482-105.686-118.482zM835.742 284.84c-57.942 0-105.686 53.174-105.686 118.478s46.818 118.482 105.686 118.482c59.334 0 106.614-53.64 105.688-118.478 0-65.308-46.354-118.482-105.688-118.482z" />
<glyph unicode="&#xe911;" glyph-name="writefreely" horiz-adv-x="1494" d="M326.398 822.626c-67.928-27.714-164.043-198.071-111.689-197.965 11.689 0.026 27.462 19.506 44.156 54.547 30.149 63.267 71.106 92.993 97.712 70.916 21.255-17.641 21.513-89.775 0.91-254.979-39.539-316.996-6.135-421.177 135.046-421.177 82.517 0 169.528 63.124 226.677 164.445l25.359 44.964 4.578-63.285c20.127-278.251 408.917-148.508 518.99 173.19 86.012 251.376-51.838 536.687-202.038 418.17-64.428-50.843-4.336-141.484 66.875-100.878 111.357 63.494 127.429-242.113 21.46-408.032-84.677-132.585-214.644-193.552-277.853-130.344-32.561 32.562-33.85 102.731-5.484 298.609 18.047 124.625 25.9 232.713 22.694 312.261-1.122 27.769-2.859 28.573-61.669 28.573h-60.521l4.592-111.689c12.99-316.177-121.112-587.149-271.854-549.316-49.7 12.471-53.49 49.311-28.803 279.964 27.078 253.024 27.557 292.562 4.095 339.336-27.547 54.927-92.775 77.354-153.232 52.689z" />
<glyph unicode="&#xea96;" glyph-name="twitter" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xea9b;" glyph-name="rss" d="M136.294 209.070c-75.196 0-136.292-61.334-136.292-136.076 0-75.154 61.1-135.802 136.292-135.802 75.466 0 136.494 60.648 136.494 135.802-0.002 74.742-61.024 136.076-136.494 136.076zM0.156 612.070v-196.258c127.784 0 247.958-49.972 338.458-140.512 90.384-90.318 140.282-211.036 140.282-339.3h197.122c-0.002 372.82-303.282 676.070-675.862 676.070zM0.388 960v-196.356c455.782 0 826.756-371.334 826.756-827.644h196.856c0 564.47-459.254 1024-1023.612 1024z" />
<glyph unicode="&#xea9d;" glyph-name="youtube" d="M1013.8 652.8c0 0-10 70.6-40.8 101.6-39 40.8-82.6 41-102.6 43.4-143.2 10.4-358.2 10.4-358.2 10.4h-0.4c0 0-215 0-358.2-10.4-20-2.4-63.6-2.6-102.6-43.4-30.8-31-40.6-101.6-40.6-101.6s-10.2-82.8-10.2-165.8v-77.6c0-82.8 10.2-165.8 10.2-165.8s10-70.6 40.6-101.6c39-40.8 90.2-39.4 113-43.8 82-7.8 348.2-10.2 348.2-10.2s215.2 0.4 358.4 10.6c20 2.4 63.6 2.6 102.6 43.4 30.8 31 40.8 101.6 40.8 101.6s10.2 82.8 10.2 165.8v77.6c-0.2 82.8-10.4 165.8-10.4 165.8zM406.2 315.2v287.8l276.6-144.4-276.6-143.4z" />
<glyph unicode="&#xea9f;" glyph-name="twitch" d="M96 960l-96-160v-736h256v-128h128l128 128h160l288 288v608h-864zM832 416l-160-160h-160l-128-128v128h-192v576h640v-416zM608 704h96v-256h-96v256zM416 704h96v-256h-96v256z" />
<glyph unicode="&#xeab0;" glyph-name="github" d="M512.008 947.358c-282.738 0-512.008-229.218-512.008-511.998 0-226.214 146.704-418.132 350.136-485.836 25.586-4.738 34.992 11.11 34.992 24.632 0 12.204-0.48 52.542-0.696 95.324-142.448-30.976-172.504 60.41-172.504 60.41-23.282 59.176-56.848 74.916-56.848 74.916-46.452 31.778 3.51 31.124 3.51 31.124 51.4-3.61 78.476-52.766 78.476-52.766 45.672-78.27 119.776-55.64 149.004-42.558 4.588 33.086 17.852 55.68 32.506 68.464-113.73 12.942-233.276 56.85-233.276 253.032 0 55.898 20.004 101.574 52.76 137.428-5.316 12.9-22.854 64.972 4.952 135.5 0 0 43.006 13.752 140.84-52.49 40.836 11.348 84.636 17.036 128.154 17.234 43.502-0.198 87.336-5.886 128.256-17.234 97.734 66.244 140.656 52.49 140.656 52.49 27.872-70.528 10.35-122.6 5.036-135.5 32.82-35.856 52.694-81.532 52.694-137.428 0-196.654-119.778-239.95-233.79-252.624 18.364-15.89 34.724-47.046 34.724-94.812 0-68.508-0.596-123.644-0.596-140.508 0-13.628 9.222-29.594 35.172-24.566 203.322 67.776 349.842 259.626 349.842 485.768 0 282.78-229.234 511.998-511.992 511.998z" />
<glyph unicode="&#xeac6;" glyph-name="reddit" d="M256 320c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM640 320c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM643.112 183.222c16.482 12.986 40.376 10.154 53.364-6.332s10.152-40.378-6.334-53.366c-45.896-36.158-115.822-59.524-178.142-59.524-62.322 0-132.248 23.366-178.144 59.522-16.486 12.99-19.32 36.882-6.332 53.368 12.99 16.482 36.882 19.318 53.366 6.332 26.422-20.818 78.722-43.222 131.11-43.222s104.688 22.404 131.112 43.222zM1024 448c0 70.692-57.308 128-128 128-48.116 0-89.992-26.57-111.852-65.82-65.792 35.994-145.952 59.246-233.28 64.608l76.382 171.526 146.194-42.2c13.152-37.342 48.718-64.114 90.556-64.114 53.020 0 96 42.98 96 96s-42.98 96-96 96c-36.56 0-68.342-20.442-84.554-50.514l-162.906 47.024c-18.224 5.258-37.538-3.722-45.252-21.052l-103.77-233.026c-85.138-5.996-163.262-29.022-227.636-64.236-21.864 39.25-63.766 65.804-111.882 65.804-70.692 0-128-57.308-128-128 0-52.312 31.402-97.254 76.372-117.102-8.070-24.028-12.372-49.104-12.372-74.898 0-176.73 200.576-320 448-320 247.422 0 448 143.27 448 320 0 25.792-4.3 50.862-12.368 74.886 44.97 19.85 76.368 64.802 76.368 117.114zM864 772c19.882 0 36-16.118 36-36s-16.118-36-36-36-36 16.118-36 36 16.118 36 36 36zM64 448c0 35.29 28.71 64 64 64 25.508 0 47.572-15.004 57.846-36.646-33.448-25.366-61.166-54.626-81.666-86.738-23.524 9.47-40.18 32.512-40.18 59.384zM512 12c-205.45 0-372 109.242-372 244s166.55 244 372 244c205.45 0 372-109.242 372-244s-166.55-244-372-244zM919.82 388.616c-20.5 32.112-48.218 61.372-81.666 86.738 10.276 21.642 32.338 36.646 57.846 36.646 35.29 0 64-28.71 64-64 0-26.872-16.656-49.914-40.18-59.384z" />
<glyph unicode="&#xeac9;" glyph-name="linkedin" d="M928 960h-832c-52.8 0-96-43.2-96-96v-832c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v832c0 52.8-43.2 96-96 96zM384 128h-128v448h128v-448zM320 640c-35.4 0-64 28.6-64 64s28.6 64 64 64c35.4 0 64-28.6 64-64s-28.6-64-64-64zM832 128h-128v256c0 35.4-28.6 64-64 64s-64-28.6-64-64v-256h-128v448h128v-79.4c26.4 36.2 66.8 79.4 112 79.4 79.6 0 144-71.6 144-160v-288z" />
<glyph unicode="&#xeae7;" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,56 +0,0 @@
each(range(5), {
.gap-@{value}rem {
gap: @value * 1rem;
}
});
.flex {
display: flex;
}
.flex-inline {
display: inline-flex;
}
.flex-inline-col {
.flex-inline();
flex-direction: column;
}
.flex-col {
.flex();
flex-direction: column;
}
.flex-row {
.flex();
flex-direction: row;
}
@flex-justifications-prefixed: flex-start, flex-end;
each(@flex-justifications-prefixed, {
.@{value} {
.flex();
justify-content: @value;
}
});
@flex-justifications: center, space-between, space-around, space-evenly;
each(@flex-justifications, {
.flex-@{value} {
.flex();
justify-content: @value;
}
});
.rounded-corners {
border-radius: 0.3rem;
}
.center {
margin: 0 auto;
}
.card-width {
max-width: 35rem;
}

View File

@@ -1,112 +0,0 @@
@font-face {
font-family: "phunic";
src: url("/fonts/phunic.eot");
src: url("/fonts/phunic.eot#iefix") format("embedded-opentype"),
url("/fonts/phunic.ttf") format("truetype"),
url("/fonts/phunic.woff") format("woff"),
url("/fonts/phunic.svg#phunic") format("svg");
font-weight: normal;
font-style: normal;
font-display: block;
}
i.icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: "phunic" !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&::before {
width: 1.4rem;
display: inline-block;
align-content: center;
text-align: center;
}
}
.phunic-envelope:before {
content: "\e901";
}
.phunic-discord:before {
content: "\e910";
}
.phunic-writefreely:before {
content: "\e911";
}
.phunic-mastodon:before {
content: "\e907";
}
.phunic-link:before {
content: "\e909";
}
.phunic-star:before {
content: "\e900";
}
.phunic-share:before {
content: "\e904";
}
.phunic-terminal:before {
content: "\e905";
}
.phunic-at:before {
content: "\e906";
}
.phunic-conlang:before {
content: "\e908";
}
.phunic-code:before {
content: "\e90a";
}
.phunic-fork:before {
content: "\e90b";
}
.phunic-house:before {
content: "\e90c";
}
.phunic-language:before {
content: "\e90d";
}
.phunic-mic-lines:before {
content: "\e90e";
}
.phunic-question:before {
content: "\e90f";
}
.phunic-emacs:before {
content: "\e902";
}
.phunic-gitea:before {
content: "\e903";
}
.phunic-twitter:before {
content: "\ea96";
}
.phunic-rss:before {
content: "\ea9b";
}
.phunic-youtube:before {
content: "\ea9d";
}
.phunic-twitch:before {
content: "\ea9f";
}
.phunic-github:before {
content: "\eab0";
}
.phunic-reddit:before {
content: "\eac6";
}
.phunic-linkedin:before {
content: "\eac9";
}
.phunic-git:before {
content: "\eae7";
}

View File

@@ -1,174 +0,0 @@
/*
* Nord Theme:
* - Copyright (c) 2016-present Arctic Ice Studio <development@arcticicestudio.com>
* - Copyright (c) 2016-present Sven Greb <development@svengreb.de>
*/
:root {
--nord0: #2e3440;
--nord1: #3b4252;
--nord2: #434c5e;
--nord3: #4c566a;
--nord4: #d8dee9;
--nord5: #e5e9f0;
--nord6: #eceff4;
--nord7: #8fbcbb;
--nord8: #88c0d0;
--nord9: #81a1c1;
--nord10: #5e81ac;
--nord11: #bf616a;
--nord12: #d08770;
--nord13: #ebcb8b;
--nord14: #a3be8c;
--nord15: #b48ead;
scroll-behavior: smooth;
// brand colors
--c-brand: var(--nord10);
--c-brand-light: var(--nord9);
// background colors
--c-bg: var(--nord6);
--c-bg-light: var(--nord6);
--c-bg-lighter: var(--nord5);
--c-bg-dark: var(--nord5);
--c-bg-darker: var(--nord4);
--c-bg-navbar: var(--c-bg);
--c-bg-sidebar: var(--c-bg);
--c-bg-arrow: var(--nord4);
// text colors
--c-text: var(--nord1);
--c-text-accent: var(--c-brand);
--c-text-light: var(--nord2);
--c-text-lighter: var(--nord3);
--c-text-lightest: var(--nord4);
--c-text-quote: var(--nord2);
// border colors
--c-border: var(--nord4);
--c-border-dark: var(--nord4);
// custom container colors
--c-tip: var(--nord14);
--c-tip-bg: var(--c-bg);
--c-tip-title: var(--c-text);
--c-tip-text: var(--c-text);
--c-tip-text-accent: var(--c-text-accent);
--c-warning: var(--nord13);
--c-warning-bg: var(--c-bg);
--c-warning-bg-light: var(--c-bg-light);
--c-warning-bg-lighter: var(--c-bg-lighter);
--c-warning-border-dark: var(--nord3);
--c-warning-details-bg: var(--c-bg);
--c-warning-title: var(--nord12);
--c-warning-text: var(--nord12);
--c-warning-text-accent: var(--nord12);
--c-warning-text-light: var(--nord12);
--c-warning-text-quote: var(--nord12);
--c-danger: var(--nord11);
--c-danger-bg: var(--c-bg);
--c-danger-bg-light: var(--c-bg-light);
--c-danger-bg-lighter: var(--c-bg-light);
--c-danger-border-dark: var(--nord11);
--c-danger-details-bg: var(--nord2);
--c-danger-title: var(--nord11);
--c-danger-text: var(--nord11);
--c-danger-text-accent: var(--nord11);
--c-danger-text-light: var(--nord11);
--c-danger-text-quote: var(--nord11);
--c-details-bg: var(--c-bg-lighter);
// badge component colors
--c-badge-tip: var(--c-tip);
--c-badge-warning: var(--c-warning);
--c-badge-warning-text: var(--c-bg);
--c-badge-danger: var(--c-danger);
--c-badge-danger-text: var(--c-bg);
// transition vars
--t-color: 0.3s ease;
--t-transform: 0.3s ease;
// code blocks vars
--code-bg-color: var(--nord0);
--code-hl-bg-color: var(--nord1);
--code-ln-color: #9e9e9e;
--code-ln-wrapper-width: 3.5rem;
// font vars
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
// layout vars
--navbar-height: 3.6rem;
--navbar-padding-v: 0.7rem;
--navbar-padding-h: 1.5rem;
--sidebar-width: 20rem;
--sidebar-width-mobile: calc(var(--sidebar-width) * 0.82);
--content-width: 740px;
--homepage-width: 960px;
}
html.dark {
// brand colors
--c-brand: var(--nord14);
--c-brand-light: var(--nord14);
// background colors
--c-bg: var(--nord1);
--c-bg-light: var(--nord2);
--c-bg-lighter: var(--nord2);
--c-bg-dark: var(--nord3);
--c-bg-darker: var(--nord3);
// text colors
--c-text: var(--nord4);
--c-text-light: var(--nord5);
--c-text-lighter: var(--nord5);
--c-text-lightest: var(--nord6);
--c-text-quote: var(--c-text);
// border colors
--c-border: var(--nord3);
--c-border-dark: var(--nord3);
// custom container colors
--c-tip: var(--nord14);
--c-warning: var(--nord13);
--c-warning-bg: var(--c-bg);
--c-warning-bg-light: var(--c-bg-light);
--c-warning-bg-lighter: var(--c-bg-lighter);
--c-warning-border-dark: var(--nord3);
--c-warning-details-bg: var(--c-bg);
--c-warning-title: var(--nord13);
--c-warning-text: var(--nord13);
--c-warning-text-accent: var(--nord13);
--c-warning-text-light: var(--nord13);
--c-warning-text-quote: var(--nord13);
--c-danger: var(--nord11);
--c-danger-bg: var(--c-bg);
--c-danger-bg-light: var(--c-bg-light);
--c-danger-bg-lighter: var(--c-bg-light);
--c-danger-border-dark: var(--nord11);
--c-danger-details-bg: var(--nord2);
--c-danger-title: var(--nord11);
--c-danger-text: var(--nord11);
--c-danger-text-accent: var(--nord11);
--c-danger-text-light: var(--nord11);
--c-danger-text-quote: var(--nord11);
--c-details-bg: var(--c-bg-light);
// badge component colors
--c-badge-warning-text: var(--nord0);
--c-badge-danger-text: var(--nord0);
// code blocks vars
--code-hl-bg-color: var(--nord2);
}

View File

@@ -1,56 +0,0 @@
const pages: string[] = [
'/index.md',
'/find-me.md',
'/resume.md',
'/projects.md',
'/conlanging.md',
'/vocal-synthesis.md',
'/about.md',
'/privacy.md',
];
const localePages = (languagePrefix: string) => {
return pages.map((page: string) => `/${languagePrefix}${page}`);
};
export const themeLocales = {
'/': {
selectLanguageName: 'Français',
tip: 'nota bene',
warning: 'attention',
sidebar: pages,
notFound: [
'Cest bien vide ici',
'Pourquoi sommes-nous ici?',
'Erreur 404',
'Le lien ne semble pas être correct',
],
backToHome: 'Retour accueil',
openInNewWindow: 'Ouvrir dans une nouvelle fenêtre',
toggleColorMode: 'Changer de thème',
toggleSidebar: 'Barre latérale',
lastUpdatedText: 'Dernière mise à jour',
},
'/lfn/': {
selectLanguageName: 'Elefen',
tip: 'avisa',
warning: 'averti',
danger: 'peril',
sidebar: localePages('lfn'),
notFound: [
'Ce? Se no ave no cosa asi',
'A do vade tu?',
'Era 404',
'La lia no es coreta',
],
backToHome: 'reversa a la paja prima',
openInNewWindow: 'abri en un nova fenetra',
toggleColorMode: 'cambia la colores',
toggleSidebar: 'bara ladal',
lastUpdatedText: 'Ultima refresci',
},
'/en/': {
selectLanguageName: 'English',
sidebar: localePages('en'),
},
};

View File

@@ -1,106 +0,0 @@
export interface GithubRepo {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner;
html_url: string;
description: string;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
created_at: string;
updated_at: string;
pushed_at: string;
git_url: string;
ssh_url: string;
clone_url: string;
svn_url: string;
homepage: string;
size: number;
stargazers_count: number;
watchers_count: number;
language: string;
has_issues: boolean;
has_projects: boolean;
has_downloads: boolean;
has_wiki: boolean;
has_pages: boolean;
forks_count: number;
mirror_url: null;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license: null;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
topics: any[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
}
export interface Owner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface GithubError {
message: string;
documentation_url: string;
}

View File

@@ -1,24 +0,0 @@
#+setupfile: ./headers
#+language: fr
* À Propos
** Introduction
Ceci est le site web personnel de Lucien Cartier-Tilet, aussi connu
sous le nom de « Pundrak » ou « Phundrak ».
Il est écrit avec [[https://v2.vuepress.vuejs.org/][Vuepress]] et est entièrement open-source. Vous pouvez
trouver son code source sur [[https://labs.phundrak.com/phundrak/phundrak.com][mon instance personnelle Gitea]]. Les icônes
utilisées sur ce site proviennent de plusieurs sources différentes :
- [[https://icomoon.io][IcoMoon]], que jutilise pour consolider toutes les icônes dans une
même fonte, y compris des icônes de leur pack par défaut,
- [[https://fontawesome.com/][FontAwesome]] doù viennent la majorité des icônes (leur
implémentation de leur paquet pour Vue laisse à mon avis plus quà
désirer),
- La {{{icon(conlang)}}} [[https://conlang.org/][Société de Création de Langues]] dont jai modifié
leur logo afin de créer licône pour mes langues construites,
- {{{icon(emacs)}}} [[https://www.gnu.org/software/emacs/][Emacs]] et {{{icon(writefreely)}}} [[https://writefreely.org/][WriteFreely]] dont jai recréé
une partie de leur logo respectif en SVG afin den créer une icône,
- {{{icon(gitea)}}} [[https://gitea.io/][Gitea]] dont jai modifié le logo en SVG pour lavoir en
monochrome.
#+include: other-links

View File

@@ -1,39 +0,0 @@
#+setupfile: ./headers
#+language: fr
* Création de langues
Les /idéolangues/, ou /langues construites/ (en anglais /conlang/), sont des
langues construites et artificielles, nées de lesprit dune ou
parfois quelques personnes. Elles se distinguent ainsi des /langues
naturelles/ qui sont des langues ayant naturellement évolué depuis
dautres langues plus anciennes, comme le Français, lAnglais, le
Mandarin, le Japonais, le bahasa ou le !xhosa (oui, le point
dexclamation fait partie de lorthographe du nom de la langue).
Les idéolangues peuvent avoir différents buts lors de leur création,
par exemple :
- être parlées comme des langues naturelles par des individus afin de
servir de /lingua franca/ entre plusieurs communautés linguistiques,
comme le célèbre [[https://en.wikipedia.org/wiki/Esperanto][espéranto]] ou bien la [[https://elefen.org][lingua franca nova]]
- être une langue secrète que seules quelques personnes connaissent
afin de communiquer entre eux sans que dautres personnes puissent
comprendre, un peu comme un argot, mais plus poussé encore
- être une expérience concrète de linguistique, comme le [[https://en.wikipedia.org/wiki/Lojban][lojban]] qui
essaie dêtre la langue la plus logique qui soit
- complémenter un univers littéraire, comme les langues elfiques de
Tolkien ou le klingon de Star Trek
- juste être une forme dart, comme la peinture ou la poésie
Dans mon cas, les deux dernières justifications sont celles qui me
poussent à créer de nouvelles langues. Mes deux projets principaux
actuellement sont le [[https://conlang.phundrak.com/proto-nyqy][proto-ñyqy]] et l[[https://conlang.phundrak.com/eittlandic][éittlandais]]. La première est une
langue racine qui me permettra de développer toute une famille de
langues dans mon univers littéraire, tandis que la seconde sinscrit
dans un exercice créatif de création dun pays fictif présent dans
notre monde.
Plus dinformations peuvent être trouvées sur [[https://conlang.phundrak.com/][mon site
didéolinguistique]] (en anglais)
#+include: other-links

View File

@@ -1,22 +0,0 @@
#+setupfile: ../headers
#+language: en
* About
** Introduction
This is the personal website of Lucien “Phundrak” Cartier-Tilet.
It is written with [[https://v2.vuepress.vuejs.org/][Vuepress]] and is completely open-source. You can
find the source code on my [[https://labs.phundrak.com/phundrak/phundrak.com][personal Gitea instance]]. Icons used on this
website come from different sources:
- [[https://icomoon.io/][IcoMoon]] which I use to consolidate all the icons used in one font,
including some icons from their default pack
- [[https://fontawesome.com/][FontAwesome]] from which most icons come from --- their Vue package
is, in my opinion, really not usable
- The {{{icon(conlang)}}} [[https://conlang.org/][Language Creation Society]] whose logo I modified in
order to create the icon used here when referring to my constructed
languages
- {{{icon(emacs)}}} [[https://www.gnu.org/software/emacs/][Emacs]] and {{{icon(writefreely)}}} [[https://writefreely.org/][WriteFreely]] whose respective
logo I partially remade as an SVG file in order to create an icon.
- {{{icon(gitea)}}} [[https://gitea.io][Gitea]] whose logo I modified to be monochromatic
#+include: other-links

View File

@@ -1,28 +0,0 @@
#+setupfile: ../headers
#+language: en
* Conlanging
/Conlangs/, short for /constructed languages/, are artificial
languages born out of the mind of a single individual (sometimes a
couple of them), unlike natural languages born through countless
iterations by their native speakers, slowly evolving over time like
English, French, Mandarin, Japanese, Bahasa, or !Xhosa did.
They can serve various goals from their creators:
- be spoken by as many people as possible as a neutral language, like
[[https://en.wikipedia.org/wiki/Esperanto][Esperanto]] and [[https://elefen.org][Lingua Franca Nova]]
- be a secret language between a couple of people
- as a thought experiment, like [[https://en.wikipedia.org/wiki/Lojban][Lojban]]
- fill a litterary universe, like Tolkiens elvish languages or Star
Treks Klingon
- for the sake of art itself
In my case, the last two reasons are the main ones driving me to
create languages. My two main projects at the time of writing this
page are [[https://conlang.phundrak.com/proto-nyqy][Proto-Ñyqy]] and [[https://conlang.phundrak.com/eittlandic][Eittlandic]]. Both are accompanied by their own
worldbuilding project, although Proto-Ñyqys worldbuilding is still
largely secret while Eittlands worldbuilding is mostly public.
More information can be found on my [[https://conlang.phundrak.com/][conlanging website]].
#+include: other-links

View File

@@ -1,31 +0,0 @@
#+setupfile: ../headers
#+language: en
* Where to find me
I am on various websites and some social networks where you can follow
me.
** Social Networks
- {{{icon(mastodon)}}} *Mastodon* :: [[https://mastodon.phundrak.com/@phundrak][@phundrak@mastodon.phundrak.com]]
- {{{icon(twitter)}}} *Twitter* :: [[https://twitter.com/phundrak][@phundrak]], though I harldy use it anymore
and mostly reshare my Mastodon messages when I think to, and
sometimes they get truncated
- {{{icon(writefreely)}}} *Writefreely* ::
- [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] : blog alternative
- [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: short stories,
mainly in French for now
- {{{icon(discord)}}} *Discord* :: =@phundrak= (tell me you come from here,
otherwise theres a chance Ill consider your message as spam)
** Other Websites
- {{{icon(envelope)}}} *Email* :: [[mailto:lucien@phundrak.com][lucien@phundrak.com]]
- {{{icon(rss)}}} *Blog* :: [[https://blog.phundrak.com][blog.phundrak.com]]
- {{{icon(gitea)}}} *Gitea* :: [[https://labs.phundrak.com/phundrak][@phundrak@labs.phundrak.com]]
- {{{icon(github)}}} *GitHub* :: [[https://github.com/Phundrak][Phundrak]]
- {{{icon(youtube)}}} *YouTube* :: [[https://www.youtube.com/@phundrak][@phundrak]]
- {{{icon(reddit)}}} *Reddit* :: [[https://www.reddit.com/user/phundrak][/u/phundrak]]
- {{{icon(linkedin)}}} *LinkedIn* :: [[https://www.linkedin.com/in/lucien-cartier-tilet/][Lucien Cartier-Tilet]]
- {{{icon(twitch)}}} *Twitch* :: [[https://www.twitch.tv/phundrak][phundrak]]
#+include: other-links

12
content/en/index.md Normal file
View File

@@ -0,0 +1,12 @@
---
layout: centered
title: Home
description: Personal Website
---
# Welcome
Web Developer • Worldbuilder • Conlanger
Hi, I'm Lucien Cartier-Tilet. I work as a web developer and consultant. Outside of work, I spend time on worldbuilding
and constructed languages.

Some files were not shown because too many files have changed in this diff Show More