Compare commits

..

5 Commits

40 changed files with 6069 additions and 4309 deletions
+3
View File
@@ -32,3 +32,6 @@ node_modules
result
.data/
app/coverage/*
# Typst
content/*/*.pdf
+23 -23
View File
@@ -1,5 +1,5 @@
:root {
--text: oklch(38.30% 0.029 266.48);
--text: oklch(38.3% 0.029 266.48);
--text-50: oklch(95.82% 0.004 271.37);
--text-100: oklch(91.83% 0.009 264.52);
--text-200: oklch(83.53% 0.016 266.26);
@@ -8,30 +8,30 @@
--text-500: oklch(57.02% 0.047 267.31);
--text-600: oklch(48.66% 0.039 268.21);
--text-700: oklch(40.13% 0.031 265.23);
--text-800: oklch(30.90% 0.021 265.90);
--text-800: oklch(30.9% 0.021 265.9);
--text-900: oklch(20.86% 0.013 264.25);
--text-950: oklch(15.46% 0.007 270.96);
--background: oklch(95.13% 0.007 260.73);
--background-50: oklch(95.80% 0.007 268.55);
--background-50: oklch(95.8% 0.007 268.55);
--background-100: oklch(91.74% 0.012 259.82);
--background-200: oklch(83.07% 0.027 262.33);
--background-300: oklch(74.46% 0.041 261.48);
--background-400: oklch(65.63% 0.058 260.56);
--background-500: oklch(56.42% 0.075 261.41);
--background-600: oklch(48.32% 0.062 260.40);
--background-600: oklch(48.32% 0.062 260.4);
--background-700: oklch(39.64% 0.048 261.18);
--background-800: oklch(30.43% 0.036 261.92);
--background-900: oklch(20.77% 0.018 259.72);
--background-950: oklch(15.29% 0.010 255.44);
--background-950: oklch(15.29% 0.01 255.44);
--primary: oklch(77.15% 0.062 217.48);
--primary-50: oklch(96.50% 0.009 222.06);
--primary-50: oklch(96.5% 0.009 222.06);
--primary-100: oklch(93.16% 0.019 213.42);
--primary-200: oklch(86.07% 0.039 217.46);
--primary-300: oklch(79.25% 0.057 216.55);
--primary-400: oklch(72.48% 0.075 217.32);
--primary-500: oklch(65.88% 0.089 218.00);
--primary-500: oklch(65.88% 0.089 218);
--primary-600: oklch(55.99% 0.075 218.52);
--primary-700: oklch(45.64% 0.059 218.22);
--primary-800: oklch(34.67% 0.043 219.39);
@@ -41,7 +41,7 @@
--secondary: oklch(69.65% 0.059 248.69);
--secondary-50: oklch(95.95% 0.008 253.85);
--secondary-100: oklch(92.05% 0.015 244.73);
--secondary-200: oklch(83.76% 0.030 248.19);
--secondary-200: oklch(83.76% 0.03 248.19);
--secondary-300: oklch(75.31% 0.048 249.46);
--secondary-400: oklch(66.99% 0.065 248.83);
--secondary-500: oklch(58.35% 0.083 249.96);
@@ -51,16 +51,16 @@
--secondary-900: oklch(20.99% 0.022 251.79);
--secondary-950: oklch(15.56% 0.012 241.97);
--accent: oklch(59.38% 0.078 253.40);
--accent-50: oklch(95.93% 0.007 247.90);
--accent: oklch(59.38% 0.078 253.4);
--accent-50: oklch(95.93% 0.007 247.9);
--accent-100: oklch(91.85% 0.015 251.16);
--accent-200: oklch(83.39% 0.030 254.70);
--accent-200: oklch(83.39% 0.03 254.7);
--accent-300: oklch(74.95% 0.046 253.67);
--accent-400: oklch(66.37% 0.064 253.29);
--accent-500: oklch(57.47% 0.081 254.47);
--accent-600: oklch(49.19% 0.068 253.56);
--accent-700: oklch(40.31% 0.053 254.02);
--accent-800: oklch(30.91% 0.038 255.00);
--accent-800: oklch(30.91% 0.038 255);
--accent-900: oklch(20.99% 0.022 251.79);
--accent-950: oklch(15.35% 0.012 260.39);
}
@@ -68,7 +68,7 @@
--text: oklch(76.63% 0.024 266.86);
--text-50: oklch(15.46% 0.007 270.96);
--text-100: oklch(20.86% 0.013 264.25);
--text-200: oklch(30.90% 0.021 265.90);
--text-200: oklch(30.9% 0.021 265.9);
--text-300: oklch(40.13% 0.031 265.23);
--text-400: oklch(48.66% 0.039 268.21);
--text-500: oklch(57.02% 0.047 267.31);
@@ -79,17 +79,17 @@
--text-950: oklch(95.82% 0.004 271.37);
--background: oklch(16.29% 0.012 260.61);
--background-50: oklch(15.29% 0.010 255.44);
--background-50: oklch(15.29% 0.01 255.44);
--background-100: oklch(20.77% 0.018 259.72);
--background-200: oklch(30.43% 0.036 261.92);
--background-300: oklch(39.64% 0.048 261.18);
--background-400: oklch(48.32% 0.062 260.40);
--background-400: oklch(48.32% 0.062 260.4);
--background-500: oklch(56.42% 0.075 261.41);
--background-600: oklch(65.63% 0.058 260.56);
--background-700: oklch(74.46% 0.041 261.48);
--background-800: oklch(83.07% 0.027 262.33);
--background-900: oklch(91.74% 0.012 259.82);
--background-950: oklch(95.80% 0.007 268.55);
--background-950: oklch(95.8% 0.007 268.55);
--primary: oklch(48.89% 0.064 217.48);
--primary-50: oklch(16.48% 0.015 212.62);
@@ -97,12 +97,12 @@
--primary-200: oklch(34.67% 0.043 219.39);
--primary-300: oklch(45.64% 0.059 218.22);
--primary-400: oklch(55.99% 0.075 218.52);
--primary-500: oklch(65.88% 0.089 218.00);
--primary-500: oklch(65.88% 0.089 218);
--primary-600: oklch(72.48% 0.075 217.32);
--primary-700: oklch(79.25% 0.057 216.55);
--primary-800: oklch(86.07% 0.039 217.46);
--primary-900: oklch(93.16% 0.019 213.42);
--primary-950: oklch(96.50% 0.009 222.06);
--primary-950: oklch(96.5% 0.009 222.06);
--secondary: oklch(47.12% 0.064 249.33);
--secondary-50: oklch(15.56% 0.012 241.97);
@@ -113,20 +113,20 @@
--secondary-500: oklch(58.35% 0.083 249.96);
--secondary-600: oklch(66.99% 0.065 248.83);
--secondary-700: oklch(75.31% 0.048 249.46);
--secondary-800: oklch(83.76% 0.030 248.19);
--secondary-800: oklch(83.76% 0.03 248.19);
--secondary-900: oklch(92.05% 0.015 244.73);
--secondary-950: oklch(95.95% 0.008 253.85);
--accent: oklch(55.80% 0.080 254.61);
--accent: oklch(55.8% 0.08 254.61);
--accent-50: oklch(15.35% 0.012 260.39);
--accent-100: oklch(20.99% 0.022 251.79);
--accent-200: oklch(30.91% 0.038 255.00);
--accent-200: oklch(30.91% 0.038 255);
--accent-300: oklch(40.31% 0.053 254.02);
--accent-400: oklch(49.19% 0.068 253.56);
--accent-500: oklch(57.47% 0.081 254.47);
--accent-600: oklch(66.37% 0.064 253.29);
--accent-700: oklch(74.95% 0.046 253.67);
--accent-800: oklch(83.39% 0.030 254.70);
--accent-800: oklch(83.39% 0.03 254.7);
--accent-900: oklch(91.85% 0.015 251.16);
--accent-950: oklch(95.93% 0.007 247.90);
--accent-950: oklch(95.93% 0.007 247.9);
}
+1
View File
@@ -2,6 +2,7 @@
@import './colors.css';
@import './ui/index.css';
@import './tailwind.css';
@import './typography.css';
@source "../../../content/**/*";
+1 -1
View File
@@ -80,6 +80,6 @@
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',
Montserrat, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
+19
View File
@@ -0,0 +1,19 @@
@layer base {
body {
font-family:
'Noto Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
font-weight: 400;
}
h1,
h2,
h3,
h4,
h5 {
font-family:
'Montserrat', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
font-weight: 700;
}
}
+9
View File
@@ -0,0 +1,9 @@
.hero-image {
max-width: 7rem;
height: auto;
display: block;
}
.centered-image {
margin: 0 auto;
}
+1
View File
@@ -2,3 +2,4 @@
@import './text.css';
@import './background.css';
@import './border.css';
@import './images.css';
+3 -1
View File
@@ -13,6 +13,8 @@
</template>
<script setup lang="ts">
import type { NavigationMenuItem } from '@nuxt/ui';
const route = useRoute();
const items = computed<NavigationMenuItem[]>(() => [
{
@@ -20,7 +22,7 @@ const items = computed<NavigationMenuItem[]>(() => [
to: '/',
active: route.path == '/',
},
...['resume', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
...['resume', 'projects', 'vocal-synthesis', 'languages', 'contact'].map((page) => ({
label: $t(`pages.${page}.name`),
to: `/${page}`,
active: route.path.startsWith(`/${page}`),
+32
View File
@@ -0,0 +1,32 @@
<template>
<div class="flex">
<UButton :icon="icon" target="_blank" :to="link.url">
<span>{{ link.display }}</span>
<span v-if="link.type === 'cargo'"><UIcon name="i-mdi-download" /> {{ crateDownloads }}</span>
</UButton>
</div>
</template>
<script setup lang="ts">
import type { ProjectLink, ProjectLinkType } from '~/types/project';
const crates = useCrates();
const iconDict: Record<ProjectLinkType, string> = {
github: 'mdi:github',
gitea: 'simple-icons:gitea',
cargo: 'simple-icons:rust',
melpa: 'file-icons:melpa',
};
const icon = computed(() => iconDict[link.type]);
const { link } = defineProps<{
link: ProjectLink;
}>();
const cratesData = computed(() => (link.type === 'cargo' ? crates.getCrateInfo(link.display) : null));
const crateDownloads = computed(
() => cratesData.value?.data?.value?.crate.downloads ?? (cratesData.value?.loading ? 'loading' : 'error'),
);
</script>
+23
View File
@@ -0,0 +1,23 @@
<template>
<UPageCard class="w-md bg-background-100">
<div class="text-2xl font-title">{{ project.name }}</div>
<p>
{{ project.description }}
</p>
<div class="flex flex-row flex-wrap gap-2 items-baseline">
<UiProjectLink v-for="link in project.links" :key="link.url" :link="link" />
</div>
<div class="flex flex-row gap-4 items-baseline">
<div>Tools</div>
<UiBadgeList :tools="project.tools" />
</div>
</UPageCard>
</template>
<script lang="ts" setup>
import type { Project } from '~/types/project';
const { project } = defineProps<{
project: Project;
}>();
</script>
+1 -1
View File
@@ -4,7 +4,7 @@
{{ $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 v-for="project in data?.projects" :key="project.title" class="flex flex-row max-w gap-5 font-title">
<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"
+1 -1
View File
@@ -6,7 +6,7 @@
<script setup lang="ts">
type Theme = 'light' | 'dark' | 'system';
const icons: Dictionary<Theme, string> = {
const icons: Record<Theme, string> = {
light: 'material-symbols:light-mode',
dark: 'material-symbols:dark-mode',
system: 'material-symbols:computer-outline',
+21 -16
View File
@@ -13,12 +13,17 @@ export interface UseApi {
patch: <T, B = unknown>(path: string, opts?: FetchOptions, immediate?: boolean, body?: B) => UseApiResponse<T, B>;
}
export interface CreateRequestOptions<PayloadT> {
host?: string;
path: string;
immediate?: boolean;
method: HttpMethod;
body?: PayloadT;
opts?: FetchOptions;
}
const createRequest = <ResponseT = unknown, PayloadT = unknown>(
method: HttpMethod,
url: string,
opts?: FetchOptions,
immediate: boolean = true,
body?: PayloadT,
options: CreateRequestOptions<PayloadT>,
): QueryResult<ResponseT, PayloadT> => {
const response = new QueryResult<ResponseT, PayloadT>();
const { apiBase } = useRuntimeConfig().public;
@@ -28,10 +33,10 @@ const createRequest = <ResponseT = unknown, PayloadT = unknown>(
response.error.value = null;
try {
const res = await $fetch<ResponseT>(url, {
baseURL: apiBase,
...opts,
method,
const res = await $fetch<ResponseT>(options.path, {
...options.opts,
baseURL: options.host ?? apiBase,
method: options.method,
body: requestBody ?? undefined,
});
response.data.value = res;
@@ -48,22 +53,22 @@ const createRequest = <ResponseT = unknown, PayloadT = unknown>(
};
response.run = run;
if (immediate) run(body);
if (options.immediate) run(options.body);
return response;
};
export const useApi = (): UseApi => {
export const useApi = (host?: string): UseApi => {
const get = <T>(path: string, opts?: FetchOptions, immediate: boolean = true) =>
createRequest<T>('GET', path, opts, immediate);
createRequest<T>({ method: 'GET', path, opts, immediate, host });
const del = <T>(path: string, opts?: FetchOptions, immediate: boolean = true) =>
createRequest<T>('DELETE', path, opts, immediate);
createRequest<T>({ method: 'DELETE', path, opts, immediate, host });
const post = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
createRequest<T, B>('POST', path, opts, immediate, body);
createRequest<T, B>({ method: 'POST', path, opts, immediate, body, host });
const put = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
createRequest<T, B>('PUT', path, opts, immediate, body);
createRequest<T, B>({ method: 'PUT', path, opts, immediate, body, host });
const patch = <T, B = unknown>(path: string, opts?: FetchOptions, immediate: boolean = true, body?: B) =>
createRequest<T, B>('PATCH', path, opts, immediate, body);
createRequest<T, B>({ method: 'PATCH', path, opts, immediate, body, host });
return { get, post, put, patch, del };
};
+8
View File
@@ -0,0 +1,8 @@
import type { CrateMeta } from '~/types/crate';
export const useCrates = () => {
const api = useApi('https://crates.io/api/v1/');
const getCrateInfo = (name: string): UseApiResponse<CrateMeta> => api.get<CrateMeta>(`crates/${name}`);
return { getCrateInfo };
};
+10
View File
@@ -0,0 +1,10 @@
import type { GithubRepository } from '~/types/github-repository';
export const useGithub = () => {
const api = useApi('https://api.github.com');
const getRepoMeta = (owner: string, name: string): UseApiResponse<GithubRepository> =>
api.get<GithubRepository>(`repos/${owner}/${name}`);
return { getRepoMeta };
};
+18
View File
@@ -0,0 +1,18 @@
<template>
<NuxtLayout name="default">
<h1 class="text-4xl text-highlighted font-bold mb-8">
{{ $t('pages.projects.name') }}
</h1>
<div class="flex flex-wrap gap-4 justify-center">
<UiSingleProject v-for="project in projects" :key="project.name" :project="project" />
</div>
</NuxtLayout>
</template>
<script lang="ts" setup>
import type { Project, ProjectsWrapper } from '~/types/project';
const { getJsonData } = useDataJson('projects');
const projects$ = await getJsonData<ProjectsWrapper>();
const projects: ComputedRef<Project[]> = computed(() => (projects$?.value ? projects$.value.projects : []));
</script>
+5 -3
View File
@@ -7,7 +7,7 @@
<p>
{{ $t('pages.resume.experience') }}
</p>
<UTimeline v-model="valueExp" reverse :items="resumeContent?.experience" class="w-full">
<UTimeline v-model="valueExp" reverse :items="experiences" class="w-full">
<template #description="{ item }">
<div class="flex flex-col gap-2">
<p>
@@ -22,7 +22,7 @@
<p>
{{ $t('pages.resume.education') }}
</p>
<UTimeline v-model="valueEd" reverse :items="resumeContent?.education" class="w-full" />
<UTimeline v-model="valueEd" reverse :items="education" class="w-full" />
</UPageCard>
<UiBadgeListCard :tools="resumeContent?.otherTools">{{ $t('pages.resume.tools') }}</UiBadgeListCard>
<UiBadgeListCard :tools="resumeContent?.devops">{{ $t('pages.resume.devops') }}</UiBadgeListCard>
@@ -35,7 +35,7 @@
</template>
<script setup lang="ts">
import { ResumeContent } from '~/types/resume';
import { EducationTimelineItem, ResumeContent, ResumeExperienceTimelineItem } from '~/types/resume';
useMeta({
title: $t('pages.resume.name'),
@@ -44,6 +44,8 @@ useMeta({
const { getJsonData } = useDataJson('resume');
const resumeContent$ = await getJsonData<ResumeContent>();
const resumeContent = computed(() => (resumeContent$.value ? resumeContent$.value : new ResumeContent()));
const experiences = computed(() => resumeContent.value.experience.map((e) => new ResumeExperienceTimelineItem(e)));
const education = computed(() => resumeContent.value.education.map((e) => new EducationTimelineItem(e)));
const arrLength = <T,>(array?: T[]) => (array ? array.length - 1 : 0);
const valueExp = computed(() => arrLength(resumeContent.value?.experience));
const valueEd = computed(() => arrLength(resumeContent.value?.education));
+25
View File
@@ -0,0 +1,25 @@
export interface CrateMeta {
crate: Crate;
}
export interface Crate {
id: string;
name: string;
updated_at: string;
versions: number[];
keywords: string[];
created_at: string;
downloads: number;
recent_downloads: number;
default_version: string;
num_versions: number;
yanked: boolean;
max_version: string;
newest_version: string;
max_stable_version: string;
description: string;
homepage: string;
repository: string;
exact_match: boolean;
trustpub_only: boolean;
}
+108
View File
@@ -0,0 +1,108 @@
export interface GithubRepository {
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;
has_discussions: boolean;
forks_count: number;
mirror_url: any;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license: any;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
has_pull_requests: boolean;
pull_request_creation_policy: string;
topics: any[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
temp_clone_token: any;
network_count: number;
subscribers_count: number;
}
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;
user_view_type: string;
site_admin: boolean;
}
+23
View File
@@ -0,0 +1,23 @@
import type { TimePeriod } from './time';
import type { Tool } from './tool';
export type ProjectLinkType = 'github' | 'gitea' | 'cargo' | 'melpa';
export interface ProjectLink {
url: string;
display: string;
type: ProjectLinkType;
}
export interface Project {
name: string;
description: string;
role: string;
dates: TimePeriod;
links: ProjectLink[];
tools: Tool[];
}
export interface ProjectsWrapper {
projects: Project[];
}
+47 -3
View File
@@ -1,14 +1,58 @@
import type { TimelineItem } from '@nuxt/ui';
import type { Tool } from './tool';
import { timeToString, type TimePeriod } from './time';
export class ResumeExperience implements TimelineItem {
tools: Tool[] = [];
export interface ResumeExperience {
dates: TimePeriod;
title: string;
company: string;
description: string;
tools: Tool[];
icon: string;
}
export class ResumeExperienceTimelineItem implements TimelineItem {
tools: Tool[];
description?: string;
date: string;
title: string;
icon: string;
constructor(from: ResumeExperience) {
this.date = timeToString(from.dates);
this.title = `${from.title}${from.company}`;
this.description = from.description;
this.icon = from.icon;
this.tools = from.tools;
}
}
export interface Education {
dates: TimePeriod;
degree: string;
institution: string;
location: string;
description?: string;
icon: string;
}
export class EducationTimelineItem implements TimelineItem {
date: string;
description?: string;
title: string;
icon: string;
constructor(from: Education) {
this.date = timeToString(from.dates);
this.description = from.description;
this.title = `${from.degree}${from.institution}, ${from.location}`;
this.icon = from.icon;
}
}
export class ResumeContent {
experience: ResumeExperience[] = [];
education: TimelineItem[] = [];
education: Education[] = [];
otherTools: Tool[] = [];
devops: Tool[] = [];
os: Tool[] = [];
+9
View File
@@ -0,0 +1,9 @@
import { t } from '../utils/i18n';
export interface TimePeriod {
start: string;
end?: string;
}
export const timeToString = (time: TimePeriod): string =>
time.end ? `${time.start}${time.end}` : `${t('misc.since')} ${time.start}`;
+21
View File
@@ -0,0 +1,21 @@
import { tryUseNuxtApp } from '#app';
/**
* Synchronous translation helper usable outside of Vue components.
*
* It resuses the request-scoped `@nuxtjs/i18n` instance exposed on the Nuxt app
* as `$i18n`, so translations stay in sync with the active locale (including
* server-side rendering and language switches). Falls back to the raw key when
* no Nuxt context is available (e.g. in unit tests).
*
* @param key Translation key
* @param named Named/list arguments forwarded to vue-i18n
* @returns The translated string, or `key` when no i18n instance is found
*/
export const t = <T extends string = string>(key: T, named?: Record<string, unknown> | unknown[]): string => {
const i18n = tryUseNuxtApp()?.$i18n;
if (!i18n) {
return key;
}
return i18n.t(key, named as never);
};
+85
View File
@@ -0,0 +1,85 @@
{
"projects": [
{
"name": "jj-cz",
"description": "A Commitizen alternative for Jujutsu",
"role": "Author",
"dates": {
"start": "February 2026"
},
"links": [
{
"url": "https://labs.phundrak.com/phundrak/jj-cz",
"display": "labs.phundrak.com/phundrak/jj-cz",
"type": "gitea"
},
{
"url": "https://crates.io/crates/jj-cz",
"display": "jj-cz",
"type": "cargo"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "Rust", "link": "https://rust-lang.org/" },
{ "name": "Nix", "link": "https://nixos.org/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
},
{
"name": "Georm",
"description": "Georm, a simple, opinionated SQLx ORM for PostgreSQL",
"dates": {
"start": "January 2025"
},
"role": "Author",
"links": [
{
"url": "https://labs.phundrak.com/phundrak/georm",
"display": "labs.phundrak.com/phundrak/georm",
"type": "gitea"
},
{
"url": "https://github.com/phundrak/georm",
"display": "phundrak/georm",
"type": "github"
},
{
"url": "https://crates.io/crates/georm",
"display": "georm",
"type": "cargo"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "Rust", "link": "https://rust-lang.org/" },
{ "name": "Nix", "link": "https://nixos.org/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
},
{
"name": "f.el",
"description": "Modern API for working with files and directories in Emacs",
"dates": {
"start": "March 2022"
},
"role": "Maintener",
"links": [
{
"url": "https://github.com/rejeep/f.el",
"display": "rejeep/f.el",
"type": "github"
},
{
"url": "https://melpa.org/#/f",
"display": "f",
"type": "melpa"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
}
]
}
+46 -13
View File
@@ -1,8 +1,12 @@
{
"experience": [
{
"date": "Since Septembre 2023",
"title": "Consultant Aubay",
"dates": {
"start": "September 2023",
"end": null
},
"title": "Consultant",
"company": "Aubay",
"description": "Web development consultant working on enterprise applications. Continued focus on Angular front-end development and Java Spring Boot back-end services with PostgreSQL databases.",
"tools": [
{ "name": "Angular", "link": "https://angular.dev/" },
@@ -19,8 +23,12 @@
"icon": "mdi:laptop"
},
{
"date": "February 2023 August 2023",
"title": "Intern Aubay",
"dates": {
"start": "February 2023",
"end": "August 2023"
},
"title": "Intern",
"company": "Aubay",
"description": "Web application development internship focused on full-stack development. Worked on projects using Angular for front-end and Java Spring Boot for back-end, with PostgreSQL databases.",
"tools": [
{ "name": "Angular", "link": "https://angular.dev/" },
@@ -36,7 +44,12 @@
},
{
"date": "October 2014 July 2018",
"title": "CTO Voxwave",
"dates": {
"start": "October 2014",
"end": "July 2018"
},
"title": "CTO",
"company": "VoxWave",
"description": "Co-founded a startup specialized in creating French virtual singers using vocal synthesis. Developed singing synthesis vocal libraries, conducted linguistic research, provided user support, and trained recruits in vocal library development. Led technical development of ALYS, the first professional French singing voice library.",
"tools": [
{ "name": "Alter/Ego", "link": "https://www.plogue.com/products/alter-ego.html" },
@@ -50,26 +63,46 @@
],
"education": [
{
"date": "September 2022 September 2023",
"title": "Master's Degree in Hypermedia Technologies University of Paris 8",
"dates": {
"start": "September 2022",
"end": "September 2023"
},
"degree": "Master's Degree in Hypermedia Technologies",
"institution": "University of Paris 8",
"location": "Saint Denis, France",
"description": "Obtained Master's degree in THYP (Hypermedia Technologies) on 11 September 2023. Repeated the year for health reasons without any lasting effects.",
"icon": "mdi:network"
},
{
"date": "September 2020 September 2021",
"title": "Master's Degree in Computer Science University of Paris 8",
"dates": {
"start": "September 2020",
"end": "September 2021"
},
"degree": "Master's Degree in Computer Science",
"institution": "University of Paris 8",
"location": "Saint Denis, France",
"description": "First year of my Masters degree.",
"icon": "mdi:code-tags"
},
{
"date": "September 2016 July 2019",
"title": "Bachelor's Degree in Computer Science University of Paris 8",
"dates": {
"start": "September 2016",
"end": "July 2019"
},
"degree": "Bachelor's Degree in Computer Science",
"institution": "University of Paris 8",
"location": "Saint Denis, France",
"description": "Bachelor's degree in Computer Science obtained in July 2019",
"icon": "mdi:school-outline"
},
{
"date": "Septembre 2013 Décembre 2014",
"title": "English Literature Université Lyon 2",
"dates": {
"start": "September 2013",
"end": "December 2014"
},
"degree": "English Literature",
"institution": "Université Lyon 2",
"location": "Lyon, France",
"description": "One and a half years of literary English studies in an LLCE English degree. Studies interrupted following the creation of VoxWave.",
"icon": "mdi:book-open-page-variant"
}
+128
View File
@@ -0,0 +1,128 @@
// Based on https://github.com/stuxf/basic-typst-resume-template
#import "@preview/basic-resume:0.2.9": *
#let resumeData = json("resume.json")
#let i18nData = json("../../i18n/locales/en.json")
#let privateData = json("../../private/data.json")
#let projects = json("projects.json");
#let tool-chip(t) = link(t.link)[#t.name]
#let tool-list(tools) = tools.map(tool-chip).join(", ")
#let name = i18nData.website.name
#let location = privateData.location
#let email = privateData.email
#let github = "github.com/phundrak"
#let gitea = "labs.phundrak.com/phundrak"
#let linkedin = "linkedin.com/in/lucien-cartier-tilet/"
#let phone = privateData.phone
#let personal-site = "phundrak.com"
// dates: dates-helper(start-date: "Jan 2021", end-date: "Present"),
#let time-period(dates) = {
let end = dates.at("end", default: none);
if end == none {
[Since #dates.start]
} else {
dates-helper(start-date: dates.start, end-date: end)
}
}
#show: resume.with(
author: name,
location: location,
email: email,
github: github,
linkedin: linkedin,
phone: phone,
personal-site: personal-site,
accent-color: "#5e81ac",
font: "New Computer Modern",
paper: "a4",
author-position: left,
personal-info-position: left,
)
/*
* Lines that start with == are formatted into section headings
* You can use the specific formatting functions if needed
* The following formatting functions are listed below
* #edu(dates: "", degree: "", gpa: "", institution: "", location: "", consistent: false)
* #work(company: "", dates: "", location: "", title: "")
* #project(dates: "", name: "", role: "", url: "")
* certificates(name: "", issuer: "", url: "", date: "")
* #extracurriculars(activity: "", dates: "")
* There are also the following generic functions that don't apply any formatting
* #generic-two-by-two(top-left: "", top-right: "", bottom-left: "", bottom-right: "")
* #generic-one-by-two(left: "", right: "")
*/
== Work Experience
#for e in resumeData.experience [
#work(
title: e.title,
company: e.company,
dates: time-period(e.dates)
)
#if e.description != "" [
#par(e.description)
]
#if "tools" in e and e.tools.len() > 0 [
#par(text(size: 9pt, fill: gray)[#tool-list(e.tools)])
]
]
== Technical Skills
- *Programming Languages*: #tool-list(resumeData.programmingLanguages)
- *Frameworks*: #tool-list(resumeData.frameworks)
- *Operating Systems*: #tool-list(resumeData.os)
- *Devops Technologies*: #tool-list(resumeData.devops)
- *Other Tools*: #tool-list(resumeData.otherTools)
== Education
#for e in resumeData.education [
#edu(
institution: e.institution,
location: e.location,
dates: time-period(e.dates),
degree: e.degree,
)
#if e.description != "" [
#par(e.description)
]
]
== Projects
#for p in projects.projects [
#project(
name: p.name,
role: p.role,
url: p.links.at(0).url,
dates: time-period(p.dates)
)
#par(p.description)
]
== Extracurricular Activities
#extracurriculars(
activity: "Conlanging",
dates: time-period((start: "2016")),
)
Artificial language creation
/ Eittlandic: a sister language to the Nordic languages like Norwegian or Icelandic.
- #link("https://conlang.phundrak.com/eittlandic")[conlang.phundrak.com/eittlandic]
- Naturalistic language
- Evolved from theorized Early Old Norse (an in-between of Proto-Norse and Old Norse)
/ Proto-Ñyqy: a language made as the root language of the Ñyqy language family in a fictional setting.
- #link("https://conlang.phundrak.com/proto-nyqy")[conlang.phundrak.com/proto-nyqy]
- Relatively simple grammar
- Base for several related languages and sub-families, like the Mojhal languages (currently under construction)
/ Zikãti: Experimental language focused on vocabulary generation
- #link("https://conlang.phundrak.com/zik%C3%A3ti")[conlang.phundrak.com/zikãti]
- Vocabulary based on few root words for generic concepts, expanded with prefixes and suffixes
+2
View File
@@ -6,6 +6,8 @@ description: Site web personnel
# Bienvenue
![My usual profile picture](/leon.png){.rounded-full .hero-image .centered-image}
Développeur web • Créateur dunivers • Idéolinguiste
Bonjour, je m'appelle Lucien Cartier-Tilet. Je travaille comme développeur web et consultant. En dehors du travail, je
-2
View File
@@ -180,5 +180,3 @@ officielle de la situation. Cette interdiction est également
applicable à Sa Sainteté le Dalaï Lama, au Vénérable Shamar Rinpoché
et Tai Situ Rinpoche. Si vous avez la moindre question, nhésitez pas
à m[envoyer un email](/contact).
#+include: other-links
+1 -1
View File
@@ -1,5 +1,5 @@
---
titre: Langues et création d'univers
title: Langues et création d'univers
description: Langues construites (conlangs) et projets de création d'univers.
---
+85
View File
@@ -0,0 +1,85 @@
{
"projects": [
{
"name": "jj-cz",
"description": "Une alternative à Commitizen pour Jujutsu",
"role": "Auteur",
"dates": {
"start": "février 2026"
},
"links": [
{
"url": "https://labs.phundrak.com/phundrak/jj-cz",
"display": "labs.phundrak.com/phundrak/jj-cz",
"type": "gitea"
},
{
"url": "https://crates.io/crates/jj-cz",
"display": "jj-cz",
"type": "cargo"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "Rust", "link": "https://rust-lang.org/" },
{ "name": "Nix", "link": "https://nixos.org/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
},
{
"name": "Georm",
"description": "Georm, un ORM simple pour SQLx avec PostgreSQL",
"dates": {
"start": "janvier 2025"
},
"role": "Auteur",
"links": [
{
"url": "https://labs.phundrak.com/phundrak/georm",
"display": "labs.phundrak.com/phundrak/georm",
"type": "gitea"
},
{
"url": "https://github.com/phundrak/georm",
"display": "phundrak/georm",
"type": "github"
},
{
"url": "https://crates.io/crates/georm",
"display": "georm",
"type": "cargo"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "Rust", "link": "https://rust-lang.org/" },
{ "name": "Nix", "link": "https://nixos.org/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
},
{
"name": "f.el",
"description": "Une API moderne pour travailler avec des fichiers et répertoires dans Emacs",
"dates": {
"start": "mars 2022"
},
"role": "Mainteneur",
"links": [
{
"url": "https://github.com/rejeep/f.el",
"display": "rejeep/f.el",
"type": "github"
},
{
"url": "https://melpa.org/#/f",
"display": "f",
"type": "melpa"
}
],
"tools": [
{ "name": "Emacs", "link": "https://www.gnu.org/software/emacs/" },
{ "name": "jj", "link": "https://docs.jj-vcs.dev/latest/" }
]
}
]
}
+46 -14
View File
@@ -1,8 +1,12 @@
{
"experience": [
{
"date": "Depuis septembre 2023",
"title": "Consultant Aubay",
"dates": {
"start": "septembre 2023",
"end": null
},
"title": "Consultant",
"company": "Aubay",
"description": "Consultant en développement web travaillant sur des applications d'entreprise. Je continue à me concentrer sur le développement front-end Angular et les services back-end Java Spring Boot avec des bases de données PostgreSQL.",
"tools": [
{ "name": "Angular", "link": "https://angular.dev/" },
@@ -19,8 +23,12 @@
"icon": "mdi:laptop"
},
{
"date": "Février 2023 Août 2023",
"title": "Stagiaire Aubay",
"dates": {
"start": "février 2023",
"end": "août 2023"
},
"title": "Stagiaire",
"company": "Aubay",
"description": "Stage en développement d'applications web axé sur le développement full-stack. J'ai travaillé sur des projets utilisant Angular pour le front-end et Java Spring Boot pour le back-end, avec des bases de données PostgreSQL.",
"tools": [
{ "name": "Angular", "link": "https://angular.dev/" },
@@ -35,8 +43,12 @@
"icon": "mdi:book"
},
{
"date": "Octobre 2014 Juillet 2018",
"title": "Directeur technique Voxwave",
"dates": {
"start": "octobre 2014",
"end": "juillet 2018"
},
"title": "Directeur technique",
"company": "VoxWave",
"description": "Co-fondateur d'une start-up spécialisée dans la création de chanteurs virtuels français à l'aide de la synthèse vocale. Développement de banques vocales de synthèse chantée, recherche linguistique, assistance aux utilisateurs et formation des recrues au développement de banques vocales. Direction du développement technique d'ALYS, la première banques vocale professionnelle de chant en français.",
"tools": [
{ "name": "Alter/Ego", "link": "https://www.plogue.com/products/alter-ego.html" },
@@ -50,26 +62,46 @@
],
"education": [
{
"date": "Septembre 2022 Septembre 2023",
"title": "Master 2 Technologies de lHypermédia Université Paris 8",
"dates": {
"start": "septembre 2022",
"end": "septembre 2023"
},
"degree": "Master 2 Technologies de lHypermédia",
"institution": "Université Paris 8",
"location": "Saint Denis, France",
"description": "Obtention du diplôme Master 2 THYP le 11 septembre 2023. Redoublement pour causes de santé sans séquelles.",
"icon": "mdi:network"
},
{
"date": "Septembre 2020 Septembre 2021",
"title": "Master 1 Informatique Université Paris 8",
"dates": {
"start": "septembre 2020",
"end": "septembre 2021"
},
"degree": "Master 1 Informatique",
"institution": "Université Paris 8",
"location": "Saint Denis, France",
"description": "",
"icon": "mdi:code-tags"
},
{
"date": "Septembre 2016 Juillet 2019",
"title": "Licence Informatique Université Paris 8",
"dates": {
"start": "septembre 2016",
"end": "Juillet 2019"
},
"degree": "Licence Informatique",
"institution": "Université Paris 8",
"location": "Saint Denis, France",
"description": "Licence dInformatique obtenue en Juillet 2019",
"icon": "mdi:school-outline"
},
{
"date": "Septembre 2013 Décembre 2014",
"title": "Anglais LLCE Université Lyon 2",
"dates": {
"start": "septembre 2013",
"end": "décembre 2014"
},
"degree": "Anglais LLCE",
"institution": "Université Lyon 2",
"location": "Lyon, France",
"description": "Un an et demi d’études danglais littéraire en licence danglais LLCE. Études interrompues suite à la création de VoxWave.",
"icon": "mdi:book-open-page-variant"
}
+128
View File
@@ -0,0 +1,128 @@
// Based on https://github.com/stuxf/basic-typst-resume-template
#import "@preview/basic-resume:0.2.9": *
#let resumeData = json("resume.json")
#let i18nData = json("../../i18n/locales/fr.json")
#let privateData = json("../../private/data.json")
#let projects = json("projects.json");
#let tool-chip(t) = link(t.link)[#t.name]
#let tool-list(tools) = tools.map(tool-chip).join(", ")
#let name = i18nData.website.name
#let location = privateData.location
#let email = privateData.email
#let github = "github.com/phundrak"
#let gitea = "labs.phundrak.com/phundrak"
#let linkedin = "linkedin.com/in/lucien-cartier-tilet/"
#let phone = privateData.phone
#let personal-site = "phundrak.com"
// dates: dates-helper(start-date: "Jan 2021", end-date: "Present"),
#let time-period(dates) = {
let end = dates.at("end", default: none);
if end == none {
[depuis #dates.start]
} else {
dates-helper(start-date: dates.start, end-date: end)
}
}
#show: resume.with(
author: name,
location: location,
email: email,
github: github,
linkedin: linkedin,
phone: phone,
personal-site: personal-site,
accent-color: "#5e81ac",
font: "New Computer Modern",
paper: "a4",
author-position: left,
personal-info-position: left,
)
/*
* Lines that start with == are formatted into section headings
* You can use the specific formatting functions if needed
* The following formatting functions are listed below
* #edu(dates: "", degree: "", gpa: "", institution: "", location: "", consistent: false)
* #work(company: "", dates: "", location: "", title: "")
* #project(dates: "", name: "", role: "", url: "")
* certificates(name: "", issuer: "", url: "", date: "")
* #extracurriculars(activity: "", dates: "")
* There are also the following generic functions that don't apply any formatting
* #generic-two-by-two(top-left: "", top-right: "", bottom-left: "", bottom-right: "")
* #generic-one-by-two(left: "", right: "")
*/
== Expérience professionelle
#for e in resumeData.experience [
#work(
title: e.title,
company: e.company,
dates: time-period(e.dates)
)
#if e.description != "" [
#par(e.description)
]
#if "tools" in e and e.tools.len() > 0 [
#par(text(size: 9pt, fill: gray)[#tool-list(e.tools)])
]
]
== Compétences Techniques
- *Langages de programmation*: #tool-list(resumeData.programmingLanguages)
- *Frameworks*: #tool-list(resumeData.frameworks)
- *Systèmes dexploitation*: #tool-list(resumeData.os)
- *Technologies devops*: #tool-list(resumeData.devops)
- *Autres outils*: #tool-list(resumeData.otherTools)
== Éducation
#for e in resumeData.education [
#edu(
institution: e.institution,
location: e.location,
dates: time-period(e.dates),
degree: e.degree,
)
#if e.description != "" [
#par(e.description)
]
]
== Projets
#for p in projects.projects [
#project(
name: p.name,
role: p.role,
url: p.links.at(0).url,
dates: time-period(p.dates)
)
#par(p.description)
]
== Autres activités
#extracurriculars(
activity: "Idéolinguistique",
dates: time-period((start: "2016")),
)
Création de langues fictives
/ Éittlandais: une langue sœur des autres langues nordiques, tel que le norvégien ou lislandais.
- #link("https://conlang.phundrak.com/eittlandic")[conlang.phundrak.com/eittlandic]
- Émule une langue naturelle
- Évolué depuis une forme théorique intermédiaire du Proto-Norrois et Vieux Norrois
/ Proto-Ñyqy: une langue créée comme racine de la famille de langues construites Ñyqy dans un univers litéraire.
- #link("https://conlang.phundrak.com/proto-nyqy")[conlang.phundrak.com/proto-nyqy]
- Grammaire relativement simple
- Base pour plusieurs langues et sous-familles de langues, telles que les langues Mojhal (en cours de création)
/ Zikãti: Langue expérimentale centrée sur la génération de vocabulaire
- #link("https://conlang.phundrak.com/zik%C3%A3ti")[conlang.phundrak.com/zikãti]
- Vocabulaire basé sur un nombre réduit de mots et concepts racines, dérivés par la suite via des affixes
Generated
+649 -27
View File
@@ -42,11 +42,11 @@
]
},
"locked": {
"lastModified": 1760971495,
"narHash": "sha256-IwnNtbNVrlZIHh7h4Wz6VP0Furxg9Hh0ycighvL5cZc=",
"lastModified": 1767714506,
"narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
"owner": "cachix",
"repo": "cachix",
"rev": "c5bfd933d1033672f51a863c47303fc0e093c2d2",
"rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
"type": "github"
},
"original": {
@@ -56,23 +56,142 @@
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": [
"devenv",
"crate2nix"
],
"flake-compat": [
"devenv",
"crate2nix"
],
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1767714506,
"narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
"owner": "cachix",
"repo": "cachix",
"rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"cachix_3": {
"inputs": {
"devenv": [
"devenv",
"crate2nix",
"crate2nix_stable"
],
"flake-compat": [
"devenv",
"crate2nix",
"crate2nix_stable"
],
"git-hooks": "git-hooks_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1767714506,
"narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
"owner": "cachix",
"repo": "cachix",
"rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"crate2nix": {
"inputs": {
"cachix": "cachix_2",
"crate2nix_stable": "crate2nix_stable",
"devshell": "devshell_2",
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts_2",
"nix-test-runner": "nix-test-runner_2",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1773440526,
"narHash": "sha256-OcX1MYqUdoalY3/vU67PEx8m6RvqGxX0LwKonjzXn7I=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "e697d3049c909580128caa856ab8eb709556a97b",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable": {
"inputs": {
"cachix": "cachix_3",
"crate2nix_stable": [
"devenv",
"crate2nix",
"crate2nix_stable"
],
"devshell": "devshell",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"nix-test-runner": "nix-test-runner",
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1769627083,
"narHash": "sha256-SUuruvw1/moNzCZosHaa60QMTL+L9huWdsCBN6XZIic=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "7c33e664668faecf7655fa53861d7a80c9e464a2",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "0.15.0",
"repo": "crate2nix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"crate2nix": "crate2nix",
"flake-compat": "flake-compat_3",
"flake-parts": "flake-parts_3",
"git-hooks": "git-hooks_3",
"nix": "nix",
"nixd": "nixd",
"nixpkgs": [
"nixpkgs"
]
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1763136231,
"narHash": "sha256-QVtIjPSQ/xVhuXSSENYOYZPfrjjc/W/djuxcJyKxGTw=",
"lastModified": 1774475276,
"narHash": "sha256-z4erC+oMEuBHtox+B46FCv77IPvNy4SyXw/EeBxsD4I=",
"owner": "cachix",
"repo": "devenv",
"rev": "4b8c2bbdb4e01ef8c4093ee1224fe21ed5ea1a5e",
"rev": "f8ca2c061ec2feceee1cf1c5e52c92f58b6aec9c",
"type": "github"
},
"original": {
@@ -81,6 +200,51 @@
"type": "github"
}
},
"devshell": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768818222,
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide",
"repo": "devshell",
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"devshell_2": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768818222,
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide",
"repo": "devshell",
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
@@ -104,13 +268,41 @@
}
},
"flake-compat": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
@@ -123,15 +315,60 @@
"inputs": {
"nixpkgs-lib": [
"devenv",
"crate2nix",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"lastModified": 1768135262,
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"devenv",
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768135262,
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
@@ -178,20 +415,82 @@
"inputs": {
"flake-compat": [
"devenv",
"crate2nix",
"cachix",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"crate2nix",
"cachix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1765404074,
"narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"git-hooks_2": {
"inputs": {
"flake-compat": [
"devenv",
"crate2nix",
"crate2nix_stable",
"cachix",
"flake-compat"
],
"gitignore": "gitignore_2",
"nixpkgs": [
"devenv",
"crate2nix",
"crate2nix_stable",
"cachix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1765404074,
"narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"git-hooks_3": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"gitignore": "gitignore_5",
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"lastModified": 1772893680,
"narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
"type": "github"
},
"original": {
@@ -201,6 +500,102 @@
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"cachix",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_2": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"crate2nix_stable",
"cachix",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_3": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"crate2nix_stable",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_4": {
"inputs": {
"nixpkgs": [
"devenv",
"crate2nix",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_5": {
"inputs": {
"nixpkgs": [
"devenv",
@@ -248,27 +643,153 @@
]
},
"locked": {
"lastModified": 1761648602,
"narHash": "sha256-H97KSB/luq/aGobKRuHahOvT1r7C03BgB6D5HBZsbN8=",
"lastModified": 1774103430,
"narHash": "sha256-MRNVInSmvhKIg3y0UdogQJXe+omvKijGszFtYpd5r9k=",
"owner": "cachix",
"repo": "nix",
"rev": "3e5644da6830ef65f0a2f7ec22830c46285bfff6",
"rev": "e127c1c94cefe02d8ca4cca79ef66be4c527510e",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "devenv-2.30.6",
"ref": "devenv-2.32",
"repo": "nix",
"type": "github"
}
},
"nix-test-runner": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nix-test-runner_2": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nixd": {
"inputs": {
"flake-parts": [
"devenv",
"flake-parts"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1773634079,
"narHash": "sha256-49qb4QNMv77VOeEux+sMd0uBhPvvHgVc0r938Bulvbo=",
"owner": "nix-community",
"repo": "nixd",
"rev": "8ecf93d4d93745e05ea53534e8b94f5e9506e6bd",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixd",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1761313199,
"narHash": "sha256-wCIACXbNtXAlwvQUo1Ed++loFALPjYUA3dpcUJiXO44=",
"lastModified": 1765186076,
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1773840656,
"narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1765186076,
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1769433173,
"narHash": "sha256-Gf1dFYgD344WZ3q0LPlRoWaNdNQq8kSBDLEWulRQSEs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "13b0f9e6ac78abbbb736c635d87845c4f4bee51b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1774287239,
"narHash": "sha256-W3krsWcDwYuA3gPWsFA24YAXxOFUL6iIlT6IknAoNSE=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"rev": "fa7125ea7f1ae5430010a6e071f68375a39bd24c",
"type": "github"
},
"original": {
@@ -278,12 +799,70 @@
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"devenv",
"crate2nix",
"crate2nix_stable",
"flake-compat"
],
"gitignore": "gitignore_3",
"nixpkgs": [
"devenv",
"crate2nix",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1769069492,
"narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_2": {
"inputs": {
"flake-compat": [
"devenv",
"crate2nix",
"flake-compat"
],
"gitignore": "gitignore_4",
"nixpkgs": [
"devenv",
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1769069492,
"narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"alejandra": "alejandra",
"devenv": "devenv",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs_4"
}
},
"rust-analyzer-src": {
@@ -303,6 +882,27 @@
"type": "github"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1773630837,
"narHash": "sha256-zJhgAGnbVKeBMJOb9ctZm4BGS/Rnrz+5lfSXTVah4HQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "f600ea449c7b5bb596fa1cf21c871cc5b9e31316",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
@@ -317,6 +917,28 @@
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"devenv",
"nixd",
"nixpkgs"
]
},
"locked": {
"lastModified": 1772660329,
"narHash": "sha256-IjU1FxYqm+VDe5qIOxoW+pISBlGvVApRjiw/Y/ttJzY=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "3710e0e1218041bbad640352a0440114b1e10428",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
+2 -2
View File
@@ -15,11 +15,11 @@
nixConfig = {
extra-trusted-public-keys = [
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
"phundrak-dot-com.cachix.org-1:c02/xlCknJIDoaQPUzEWSJHPoXcmIXYzCa+hVRhbDgE="
"phundrak.cachix.org-1:osJAkYO0ioTOPqaQCIXMfIRz1/+YYlVFkup3R2KSexk="
];
extra-substituters = [
"https://devenv.cachix.org"
"https://phundrak-dot-com.cachix.org"
"https://phundrak.cachix.org"
];
};
+3
View File
@@ -101,5 +101,8 @@
}
}
}
},
"misc": {
"since": "Since"
}
}
+7
View File
@@ -18,6 +18,10 @@
"home": {
"name": "Accueil"
},
"projects": {
"name": "Projets",
"description": ""
},
"resume": {
"name": "CV",
"description": "",
@@ -101,5 +105,8 @@
}
}
}
},
"misc": {
"since": "Depuis"
}
}
+10
View File
@@ -18,8 +18,17 @@ inputs.devenv.lib.mkShell {
# Node
nodejs_24
nodePackages.pnpm
typst
tinymist # Typst LSP
];
processes = {
frontend.exec = "pnpm dev";
resume-fr.exec = "typst watch content/fr/resume.typ --root .";
resume-en.exec = "typst watch content/en/resume.typ --root .";
};
enterShell = ''
echo "🚀 Nuxt.js development environment loaded!"
echo "📦 Node.js version: $(node --version)"
@@ -27,6 +36,7 @@ inputs.devenv.lib.mkShell {
echo ""
echo "Run 'pnpm install' to install dependencies"
echo "Run 'pnpm dev' to start the development server"
echo "Or run 'devenv up' to start the development server and compile the PDFs"
'';
}
];
+28 -26
View File
@@ -27,37 +27,39 @@
"@nuxtjs/color-mode": "3.5.2",
"@nuxtjs/device": "3.2.4",
"@nuxtjs/tailwindcss": "7.0.0-beta.0",
"better-sqlite3": "^12.6.2",
"eslint": "^9.39.2",
"nitropack": "^2.13.1",
"nuxi": "^3.32.0",
"nuxt": "^4.3.0",
"vite": "^7.3.1",
"vue": "^3.5.27",
"better-sqlite3": "^12.11.1",
"eslint": "^9.39.4",
"nitropack": "^2.13.4",
"nuxi": "^3.35.2",
"nuxt": "^4.4.8",
"vite": "^7.3.5",
"vue": "^3.5.38",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.88",
"@iconify-json/material-symbols": "^1.2.53",
"@iconify-json/material-symbols-light": "^1.2.53",
"@iconify-json/lucide": "^1.2.114",
"@iconify-json/material-symbols": "^1.2.79",
"@iconify-json/material-symbols-light": "^1.2.79",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/simple-icons": "^1.2.69",
"@iconify-json/simple-icons": "^1.2.86",
"@iconify-vue/file-icons": "^1.0.3",
"@iconify-vue/simple-icons": "^1.0.38",
"@nuxt/test-utils": "3.20.1",
"@nuxtjs/i18n": "^10.2.1",
"@tailwindcss/postcss": "^4.1.18",
"@vitest/coverage-v8": "^4.0.18",
"@vitest/ui": "^4.0.18",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.24",
"baseline-browser-mapping": "^2.9.19",
"happy-dom": "^20.5.0",
"less": "^4.5.1",
"playwright-core": "^1.58.1",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"@nuxtjs/i18n": "^10.4.0",
"@tailwindcss/postcss": "^4.3.1",
"@vitest/coverage-v8": "^4.1.9",
"@vitest/ui": "^4.1.9",
"@vue/test-utils": "^2.4.11",
"autoprefixer": "^10.5.0",
"baseline-browser-mapping": "^2.10.38",
"happy-dom": "^20.10.6",
"less": "^4.6.6",
"playwright-core": "^1.61.0",
"postcss": "^8.5.15",
"tailwindcss": "^4.3.1",
"typescript": "^5.9.3",
"vitest": "^4.0.18",
"wrangler": "^4.62.0",
"zod": "^4.3.6"
"vitest": "^4.1.9",
"wrangler": "^4.102.0",
"zod": "^4.4.3"
}
}
+4436 -4175
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -2,6 +2,7 @@ onlyBuiltDependencies:
- '@parcel/watcher'
- better-sqlite3
- esbuild
- less
- sharp
- unrs-resolver
- vue-demi