Merge branch 'refs/heads/master' into feature_es
# Conflicts: # app/components/AppHeader.vue # app/components/content/Quote.vue # app/components/home/Map.vue # app/pages/index.vue # content/home/en.md # content/home/fr.md # nuxt.config.ts # package.json # pnpm-lock.yaml # tailwind.config.ts
@@ -16,9 +16,6 @@ NUXT_DISCORD_ID=
|
||||
NUXT_DISCORD_TOKEN=
|
||||
NUXT_DISCORD_USER_ID=
|
||||
|
||||
# Cloud files
|
||||
NUXT_PUBLIC_CLOUD_RESUME=
|
||||
|
||||
# Nuxt I18N
|
||||
NUXT_PUBLIC_I18N_BASE_URL=
|
||||
|
||||
|
||||
11
README.md
@@ -14,18 +14,17 @@
|
||||
- **Framework** → [Nuxt.js](https://nuxtjs.org/)
|
||||
- **Content** → [Nuxt Content](https://content.nuxtjs.org/)
|
||||
- **Design System** → [NuxtUI](https://nuxtui.com/)
|
||||
- **CMS & Editing** → [Nuxt Studio](https://studio.nuxtjs.org/)
|
||||
- **Langage** → [Typescript](https://www.typescriptlang.org/)
|
||||
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio)
|
||||
- **Language** → [Typescript](https://www.typescriptlang.org/)
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/)
|
||||
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/)
|
||||
- **Package Manager** → [pnpm](https://pnpm.io/)
|
||||
|
||||
## 🍱 Adding content
|
||||
|
||||
### Writings
|
||||
### Portfolio
|
||||
|
||||
Add a new `.md` file in `/content/writings/` and follow the same pattern as the other articles. Add images
|
||||
in `/public/writings/`.
|
||||
Add a new `.md` file in `/content/portfolio/` and follow the same pattern as the other articles. Add images in `/public/portflio/`.
|
||||
|
||||
### Uses
|
||||
|
||||
@@ -76,4 +75,4 @@ NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN=...
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT](./LICENSE) © Arthur Danjou
|
||||
[MIT](./LICENSE) © Arthur Danjou
|
||||
|
||||
@@ -3,10 +3,7 @@ export default defineAppConfig({
|
||||
gray: 'neutral',
|
||||
primary: 'gray',
|
||||
container: {
|
||||
constrained: 'max-w-3xl'
|
||||
constrained: 'max-w-4xl',
|
||||
},
|
||||
icons: {
|
||||
dynamic: true
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
40
app/app.vue
@@ -1,3 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }],
|
||||
})
|
||||
|
||||
const head = useLocaleHead({
|
||||
addDirAttribute: true,
|
||||
identifierAttribute: 'id',
|
||||
addSeoAttributes: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Html
|
||||
:dir="head.htmlAttrs.dir"
|
||||
@@ -27,29 +39,19 @@
|
||||
</template>
|
||||
</Head>
|
||||
<Body>
|
||||
<NuxtLoadingIndicator color="#808080" />
|
||||
<AppBackground />
|
||||
<UContainer class="z-50 relative">
|
||||
<AppHeader />
|
||||
<NuxtPage class="mt-12" />
|
||||
<AppFooter />
|
||||
</UContainer>
|
||||
<div>
|
||||
<NuxtLoadingIndicator color="#808080" />
|
||||
<AppBackground />
|
||||
<UContainer class="z-50 relative">
|
||||
<AppHeader />
|
||||
<NuxtPage class="mt-12" />
|
||||
<AppFooter />
|
||||
</UContainer>
|
||||
</div>
|
||||
</Body>
|
||||
</Html>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }]
|
||||
})
|
||||
|
||||
const head = useLocaleHead({
|
||||
addDirAttribute: true,
|
||||
identifierAttribute: 'id',
|
||||
addSeoAttributes: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'DM Sans', sans-serif;
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
const socials = [
|
||||
{
|
||||
icon: 'i-ph-x-logo-duotone',
|
||||
icon: 'x-logo-duotone',
|
||||
label: 'Twitter',
|
||||
link: 'https://twitter.com/ArthurDanj'
|
||||
link: 'https://twitter.com/ArthurDanj',
|
||||
},
|
||||
{
|
||||
icon: 'i-ph-github-logo-duotone',
|
||||
icon: 'github-logo-duotone',
|
||||
label: 'GitHub',
|
||||
link: 'https://github.com/ArthurDanjou'
|
||||
link: 'https://github.com/ArthurDanjou',
|
||||
},
|
||||
{
|
||||
icon: 'i-ph-linkedin-logo-duotone',
|
||||
icon: 'linkedin-logo-duotone',
|
||||
label: 'LinkedIn',
|
||||
link: 'https://www.linkedin.com/in/arthurdanjou/'
|
||||
}, {
|
||||
icon: 'i-ph-discord-logo-duotone',
|
||||
link: 'https://www.linkedin.com/in/arthurdanjou/',
|
||||
},
|
||||
{
|
||||
icon: 'discord-logo-duotone',
|
||||
label: 'Discord',
|
||||
link: 'https://discordapp.com/users/179635349100691456'
|
||||
}
|
||||
link: 'https://discordapp.com/users/179635349100691456',
|
||||
},
|
||||
]
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -43,7 +44,7 @@ const { t } = useI18n({
|
||||
v-for="social in socials.sort((a, b) => a.label.localeCompare(b.label))"
|
||||
:key="social.label"
|
||||
:href="social.link"
|
||||
:icon="social.icon"
|
||||
:icon="`i-ph:${social.icon}`"
|
||||
:label="social.label"
|
||||
target="_blank"
|
||||
/>
|
||||
@@ -63,7 +64,7 @@ const { t } = useI18n({
|
||||
<div class="mt-8 w-full flex justify-center text-xs">
|
||||
{{
|
||||
t('copyright', {
|
||||
date: new Date().getFullYear()
|
||||
date: new Date().getFullYear(),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ const navs = [
|
||||
es: 'inicio'
|
||||
},
|
||||
to: '/',
|
||||
icon: 'i-ph-house-line-duotone',
|
||||
icon: 'house-line-duotone',
|
||||
shortcut: {
|
||||
en: 'H',
|
||||
fr: 'A',
|
||||
@@ -28,7 +28,7 @@ const navs = [
|
||||
es: 'usos'
|
||||
},
|
||||
to: '/uses',
|
||||
icon: 'i-ph-backpack-duotone',
|
||||
icon: 'backpack-duotone',
|
||||
shortcut: {
|
||||
en: 'U',
|
||||
fr: 'U',
|
||||
@@ -37,12 +37,12 @@ const navs = [
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'writings',
|
||||
fr: 'écrits',
|
||||
en: 'portfolio',
|
||||
fr: 'portfolio',
|
||||
es: 'escritos'
|
||||
},
|
||||
to: '/writings',
|
||||
icon: 'i-ph-books-duotone',
|
||||
to: '/portfolio',
|
||||
icon: 'books-duotone',
|
||||
shortcut: {
|
||||
en: 'W',
|
||||
fr: 'E',
|
||||
@@ -55,9 +55,8 @@ const navs = [
|
||||
fr: 'cv',
|
||||
es: 'currículum'
|
||||
},
|
||||
to: config.public.cloud.resume,
|
||||
to: '/Resume2024.pdf',
|
||||
target: '_blank',
|
||||
icon: 'i-ph-address-book-duotone',
|
||||
shortcut: {
|
||||
en: 'R',
|
||||
fr: 'C',
|
||||
@@ -78,6 +77,7 @@ async function toggleTheme() {
|
||||
}
|
||||
|
||||
const { locale, setLocale, locales, t, availableLocales } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0])
|
||||
|
||||
async function changeLocale(newLocale?: string) {
|
||||
document.body.style.animation = 'switch-on .2s'
|
||||
@@ -103,16 +103,9 @@ watch(lang, () => changeLocale(lang.value))
|
||||
|
||||
const router = useRouter()
|
||||
defineShortcuts({
|
||||
h: () => router.push('/'),
|
||||
a: () => router.push('/'),
|
||||
u: () => router.push('/uses'),
|
||||
w: () => router.push('/writings'),
|
||||
e: () => router.push('/writings'),
|
||||
r: () => window.open(config.public.cloud.resume, '_blank'),
|
||||
c: () => window.open(config.public.cloud.resume, '_blank'),
|
||||
t: () => toggleTheme(),
|
||||
l: () => changeLocale(),
|
||||
backspace: () => router.back()
|
||||
backspace: () => router.back(),
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -128,11 +121,10 @@ defineShortcuts({
|
||||
<UTooltip
|
||||
v-for="nav in navs"
|
||||
:key="nav.label.en"
|
||||
:shortcuts="[nav.shortcut[locale]]"
|
||||
:text="nav.label[locale]!"
|
||||
>
|
||||
<UButton
|
||||
:icon="nav.icon"
|
||||
:icon="`i-ph:${nav.icon}`"
|
||||
:target="nav.target ? nav.target : '_self'"
|
||||
:to="nav.to"
|
||||
:aria-label="nav.label"
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,40 +3,56 @@ import { type Activity, IDEs } from '~~/types'
|
||||
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch('/api/activity'))
|
||||
useIntervalFn(async () => await refresh(), 5000)
|
||||
const codingActivity = computed(() => activity.value!.data.activities.filter(activity => IDEs.some(ide => ide.name === activity.name))[0])
|
||||
const codingActivity = computed(() => activity.value!.data.activities.find(activity => IDEs.some(ide => ide.name === activity.name)))
|
||||
|
||||
const { locale, locales } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0])
|
||||
const currentLocale = computed(() => locales.value.find(l => l.code === locale.value))
|
||||
|
||||
const getActivity = computed(() => {
|
||||
const activity = codingActivity.value
|
||||
if (!activity) return
|
||||
if (!codingActivity.value)
|
||||
return
|
||||
|
||||
const active = activity.name === 'Visual Studio Code' ? !activity.details.includes('Idling') : activity.state.toLowerCase().includes('editing')
|
||||
const capitalise = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
|
||||
const project = activity.details ? capitalise(activity.details.replace('Workspace:', '')) : ''
|
||||
const state = activity.state.split(' ')[1]
|
||||
const start = {
|
||||
ago: useTimeAgo(activity.timestamps.start).value,
|
||||
formated: `${useDateFormat(
|
||||
activity.timestamps.start,
|
||||
'DD MMM YYYY',
|
||||
{ locales: currentLocale.value!.code ?? 'en' }
|
||||
).value} ${t('separator')}
|
||||
${useDateFormat(activity.timestamps.start, 'HH:mm:ss', { locales: currentLocale.value!.code ?? 'en' }).value}`
|
||||
}
|
||||
const { name, details, state, timestamps } = codingActivity.value
|
||||
const isActive = name === 'Visual Studio Code'
|
||||
? !details.includes('Idling')
|
||||
: state.toLowerCase().includes('editing')
|
||||
const project = details
|
||||
? details
|
||||
.charAt(0)
|
||||
.toUpperCase()
|
||||
+ details
|
||||
.slice(1)
|
||||
.replace('Workspace:', '')
|
||||
.trim()
|
||||
: ''
|
||||
const stateWord = state.split(' ')[1]
|
||||
const timeAgo = useTimeAgo(timestamps.start).value
|
||||
const formatDate = (date: number, format: string) => useDateFormat(date, format, { locales: currentLocale.value?.code ?? 'en' }).value
|
||||
|
||||
return {
|
||||
active,
|
||||
name: activity.name,
|
||||
active: isActive,
|
||||
name,
|
||||
project,
|
||||
state,
|
||||
start
|
||||
state: stateWord,
|
||||
start: {
|
||||
ago: locale.value === 'en'
|
||||
? timeAgo
|
||||
: timeAgo
|
||||
.replace('ago', '')
|
||||
.replace('hours', 'heures')
|
||||
.replace('minutes', 'minutes')
|
||||
.replace('seconds', 'secondes')
|
||||
.trim(),
|
||||
formated: {
|
||||
date: formatDate(timestamps.start, 'DD MMM YYYY'),
|
||||
time: formatDate(timestamps.start, 'HH:mm:ss'),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -62,7 +78,6 @@ const { t } = useI18n({
|
||||
v-if="getActivity.active"
|
||||
keypath="working"
|
||||
tag="div"
|
||||
class="space-x-1"
|
||||
>
|
||||
<template #project>
|
||||
<strong>{{ getActivity.project }}</strong>
|
||||
@@ -71,17 +86,19 @@ const { t } = useI18n({
|
||||
{{ getActivity.state }}
|
||||
</template>
|
||||
<template #editor>
|
||||
<UIcon
|
||||
:name="IDEs.find(ide => ide.name === getActivity!.name)!.icon"
|
||||
size="16"
|
||||
/>
|
||||
<strong>{{ getActivity.name }}</strong>
|
||||
<span class="space-x-1">
|
||||
<UIcon
|
||||
:name="`i-logos:${IDEs.find(ide => ide.name === getActivity!.name)!.icon}`"
|
||||
size="16"
|
||||
/>
|
||||
<strong>{{ getActivity.name }}</strong></span>
|
||||
</template>
|
||||
<template #start>
|
||||
<strong>{{ getActivity.start.ago }}</strong>
|
||||
</template>
|
||||
<template #format>
|
||||
<strong>{{ getActivity.start.formated }}</strong>
|
||||
<strong>{{ getActivity.start.formated.date }}</strong> {{ t('separator') }}
|
||||
<strong>{{ getActivity.start.formated.time }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<i18n-t
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
const { width } = useWindowSize()
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div
|
||||
@@ -13,13 +20,6 @@
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { width } = useWindowSize()
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
|
||||
@@ -4,15 +4,15 @@ import type { PropType } from 'vue'
|
||||
defineProps({
|
||||
text: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
hover: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<'top' | 'right' | 'bottom' | 'left'>
|
||||
}
|
||||
type: String as PropType<'top' | 'right' | 'bottom' | 'left'>,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import type { PropType } from 'vue'
|
||||
defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
target: {
|
||||
type: String as PropType<'_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined>,
|
||||
default: undefined,
|
||||
required: false
|
||||
}
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2
|
||||
:id="id"
|
||||
@@ -12,12 +21,3 @@
|
||||
<slot v-else />
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2
|
||||
:id="id"
|
||||
@@ -12,12 +21,3 @@
|
||||
<slot v-else />
|
||||
</h2>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'gray',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="inline-flex items-center">
|
||||
<UIcon
|
||||
class="mb-1 mr-1"
|
||||
:name="icon"
|
||||
class="mr-1"
|
||||
:name="`i-logos:${icon}`"
|
||||
/>
|
||||
<span class="sofia font-medium underline-offset-2 underline decoration-neutral-300 dark:decoration-neutral-700">
|
||||
<span
|
||||
:class="`text-${color}-500 decoration-${color}-300`"
|
||||
class="sofia font-medium underline-offset-2 underline"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2 mt-4">
|
||||
<div class="flex items-center">
|
||||
<UTooltip text="It's me 👋">
|
||||
<UAvatar
|
||||
alt="Avatar"
|
||||
chip-color="gray"
|
||||
chip-position="top-right"
|
||||
chip-text="!"
|
||||
class="hover:rotate-[360deg] duration-500 transform-gpu"
|
||||
size="md"
|
||||
src="/favicon.png"
|
||||
/>
|
||||
<div class="flex items-center w-12 h-12">
|
||||
<UAvatar
|
||||
alt="Avatar"
|
||||
class="hover:rotate-[360deg] duration-500 transform-gpu"
|
||||
size="md"
|
||||
src="/favicon.png"
|
||||
/>
|
||||
</div>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<p class="not-prose">
|
||||
@@ -19,12 +24,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
})
|
||||
</script>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Stats } from '~~/types'
|
||||
|
||||
const { locale, locales } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.find(l => l.code === locale.value))
|
||||
|
||||
const { data: stats } = await useFetch<Stats>('/api/stats')
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const formatDate = (date: Date, format: string) => useDateFormat(date, format, { locales: currentLocale.value?.code ?? 'en' }).value
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,12 +20,12 @@ const { t } = useI18n({
|
||||
tag="p"
|
||||
>
|
||||
<template #time>
|
||||
{{ useTimeAgo(new Date(stats.coding.data.range.start)).value }}
|
||||
{{ useTimeAgo(new Date(stats.coding.data.range.start)).value.split(' ')[0] }}
|
||||
</template>
|
||||
<template #date>
|
||||
<HoverText
|
||||
:hover="t('tooltip.date')"
|
||||
:text="useDateFormat(new Date(stats.coding.data.range.start), 'Do MMMM YYYY').value"
|
||||
:text="formatDate(new Date(stats.coding.data.range.start), 'DD MMMM YYYY')"
|
||||
/>
|
||||
</template>
|
||||
<template #hours>
|
||||
@@ -29,6 +34,9 @@ const { t } = useI18n({
|
||||
:text="usePrecision(stats.coding.data.grand_total.total_seconds_including_other_language / 3600, 0).value"
|
||||
/>
|
||||
</template>
|
||||
<template #editors>
|
||||
{{ stats.editors.data.slice(0, 2).map(editor => `${editor.name} (${editor.percent}%)`).join(' and ') }}
|
||||
</template>
|
||||
<template
|
||||
v-if="stats.os.data[0]"
|
||||
#os
|
||||
@@ -47,7 +55,7 @@ const { t } = useI18n({
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"stats": "I collect some data for {time}, started the {date}. I've coded for a total of {hours} hours. My best editors are {editors}. My best OS is {os}. My top languages are {languages}.",
|
||||
"stats": "I collect some data for {time} years, started the {date}. I've coded for a total of {hours} hours. My best editors are {editors}. My best OS is {os}. My top languages are {languages}.",
|
||||
"separator": " and ",
|
||||
"tooltip": {
|
||||
"date": "That was so long ago 🫣",
|
||||
@@ -55,7 +63,7 @@ const { t } = useI18n({
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stats": "Je collecte des données depuis {time}, commencé le {date}. J'ai codé un total de {hours} heures. Mes meilleurs éditeurs sont {editors}. Mon meilleur OS est {os}. Mes langages préférés sont {languages}.",
|
||||
"stats": "Je collecte des données depuis {time} ans, commencé le {date}. J'ai codé un total de {hours} heures. Mes meilleurs éditeurs sont {editors}. Mon meilleur OS est {os}. Mes langages préférés sont {languages}.",
|
||||
"separator": " et ",
|
||||
"tooltip": {
|
||||
"date": "C'était il y a si longtemps 🫣",
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
blanked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function adjustZoom(amount: number) {
|
||||
}
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
@@ -36,7 +36,7 @@ const config = useRuntimeConfig()
|
||||
style: isDark ? config.public.mapbox.style.dark : config.public.mapbox.style.light,
|
||||
center: coordinates,
|
||||
zoom,
|
||||
projection: 'globe'
|
||||
projection: 'globe',
|
||||
}"
|
||||
class="relative z-10"
|
||||
map-id="map"
|
||||
@@ -45,7 +45,7 @@ const config = useRuntimeConfig()
|
||||
:lnglat="coordinates"
|
||||
:options="{
|
||||
color: '#808080',
|
||||
size: 1.5
|
||||
size: 1.5,
|
||||
}"
|
||||
marker-id="marker"
|
||||
/>
|
||||
@@ -87,7 +87,7 @@ const config = useRuntimeConfig()
|
||||
|
||||
<style lang="scss">
|
||||
.map-button {
|
||||
@apply z-30 absolute bottom-2 dark:bg-gray-800 dark:hover:bg-gray-900 bg-gray-200 hover:bg-gray-100 duration-300 border border-neutral-300 dark:border-neutral-700 cursor-pointer flex items-center justify-center rounded-full p-2
|
||||
@apply z-30 absolute bottom-2 dark:bg-gray-900 dark:hover:bg-gray-900 bg-gray-200 hover:bg-gray-100 duration-300 border border-neutral-300 dark:border-neutral-700 cursor-pointer flex items-center justify-center rounded-full p-2
|
||||
}
|
||||
|
||||
.mapboxgl-control-container {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import type { UsesItem } from '#components'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object as PropType<typeof UsesItem>,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<UDivider
|
||||
@@ -9,12 +18,3 @@
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="!max-w-none prose dark:prose-invert">
|
||||
<ContentDoc :path="`/home/${locale}`" />
|
||||
<HomeMap />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: post } = await useAsyncData(`writing:${route.params.slug}`, () => queryContent(`/writings/${route.params.slug}`).findOne())
|
||||
const { data: post } = await useAsyncData(`portfolio:${route.params.slug}`, () => queryContent(`/portfolio/${route.params.slug}`).findOne())
|
||||
const {
|
||||
data: postDB,
|
||||
refresh
|
||||
} = await useAsyncData(`writing:${route.params.slug}:db`, () => $fetch(`/api/posts/${route.params.slug}`, { method: 'POST' }))
|
||||
refresh,
|
||||
} = await useAsyncData(`portfolio:${route.params.slug}:db`, () => $fetch(`/api/posts/${route.params.slug}`, { method: 'POST' }))
|
||||
|
||||
const { locale, locales } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0])
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
function top() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard({
|
||||
source: `https://arthurdanjou.fr/writings/${route.params.slug}`,
|
||||
copiedDuring: 4000
|
||||
source: `https://arthurdanjou.fr/portfolio/${route.params.slug}`,
|
||||
copiedDuring: 4000,
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: post.value?.title,
|
||||
description: post.value?.description,
|
||||
author: 'Arthur Danjou'
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
|
||||
function getDetails() {
|
||||
@@ -39,15 +39,19 @@ function getDetails() {
|
||||
const like = likes > 1 ? t('likes.many') : t('likes.one')
|
||||
const view = views > 1 ? t('views.many') : t('views.one')
|
||||
|
||||
return `${likes} ${like} · ${views} ${view}`
|
||||
return {
|
||||
likes: `${likes} ${like}`,
|
||||
views: `${views} ${view}`,
|
||||
}
|
||||
}
|
||||
|
||||
const likeCookie = useCookie<boolean>(`post:like:${route.params.slug}`, {
|
||||
maxAge: 7200
|
||||
maxAge: 7200,
|
||||
})
|
||||
|
||||
async function handleLike() {
|
||||
if (likeCookie.value) return
|
||||
if (likeCookie.value)
|
||||
return
|
||||
await $fetch(`/api/posts/like/${route.params.slug}`, { method: 'PUT' })
|
||||
await refresh()
|
||||
likeCookie.value = true
|
||||
@@ -59,7 +63,7 @@ async function handleLike() {
|
||||
<div class="flex">
|
||||
<NuxtLinkLocale
|
||||
class="flex items-center gap-2 mb-8 group text-sm hover:text-black dark:hover:text-white duration-300"
|
||||
to="/writings"
|
||||
to="/portfolio"
|
||||
>
|
||||
<UIcon
|
||||
class="group-hover:-translate-x-1 transform duration-300"
|
||||
@@ -78,20 +82,27 @@ async function handleLike() {
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<p class="border-l-2 pl-2 border-gray-300 dark:border-gray-700 rounded-sm">
|
||||
{{ getDetails() }}
|
||||
</p>
|
||||
<div class="border-l-2 pl-2 border-gray-300 dark:border-gray-700 rounded-sm flex gap-1 items-center">
|
||||
<UIcon name="i-ph-heart-duotone" size="16" />
|
||||
<p>{{ getDetails().likes }} </p>·
|
||||
<UIcon name="i-ph-eye-duotone" size="16" />
|
||||
<p>{{ getDetails().views }}</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="flex items-end gap-2 flex-wrap">
|
||||
<div class="flex items-end gap-4 flex-wrap">
|
||||
<h1
|
||||
class="font-bold text-3xl text-black dark:text-white"
|
||||
>
|
||||
{{ post.title }}
|
||||
</h1>
|
||||
<p class="text-sm text-neutral-500">
|
||||
{{ useDateFormat(post.publishedAt, 'DD MMMM YYYY', { locales: currentLocale!.code ?? 'en' }).value }} ·
|
||||
{{ post.readingTime }}min long
|
||||
</p>
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(post.publishedAt, 'DD MMMM YYYY').value }} </p>·
|
||||
<UIcon name="ph:timer-duotone" size="16" />
|
||||
<p>{{ post.readingTime }}min long</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-base">
|
||||
{{ post.description }}
|
||||
@@ -102,7 +113,7 @@ async function handleLike() {
|
||||
class="w-full rounded-md my-8"
|
||||
>
|
||||
<NuxtImg
|
||||
:src="`/writings/${post.cover}`"
|
||||
:src="`/portfolio/${post.cover}`"
|
||||
alt="Writing cover"
|
||||
/>
|
||||
</div>
|
||||
@@ -133,7 +144,7 @@ async function handleLike() {
|
||||
<div class="flex gap-4 items-center flex-wrap">
|
||||
<UButton
|
||||
:label="postDB?.likes > 1 ? `${postDB?.likes} likes` : `${postDB?.likes} like`"
|
||||
:color="likeCookie ? 'red': 'white'"
|
||||
:color="likeCookie ? 'red' : 'white'"
|
||||
icon="i-ph-heart-duotone"
|
||||
size="lg"
|
||||
variant="solid"
|
||||
119
app/pages/portfolio/index.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { type Tag, TAGS } from '~~/types'
|
||||
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Shelf',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const tagFilter = ref<string | undefined>(undefined)
|
||||
|
||||
const { data: writings, refresh } = await useAsyncData('all-portfolio', () => queryContent('/portfolio')
|
||||
.sort({ publishedAt: -1 })
|
||||
.where({
|
||||
tags: { $contains: tagFilter.value },
|
||||
})
|
||||
.without('body')
|
||||
.find())
|
||||
|
||||
watch(tagFilter, async () => await refresh())
|
||||
|
||||
const tags: Array<{ label: string, icon: string } & Tag> = [
|
||||
{
|
||||
label: 'All',
|
||||
icon: 'i-ph-books-duotone',
|
||||
color: 'black',
|
||||
},
|
||||
...TAGS,
|
||||
]
|
||||
|
||||
function updateTag(index: number) {
|
||||
const tag = tags[index]
|
||||
tagFilter.value = tag?.label.toLowerCase() === 'all' ? undefined : tag?.label.toLowerCase()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-12">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<UAlert
|
||||
v-if="locale !== 'en'"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<UTabs :items="tags" class="hidden md:block" @change="updateTag" />
|
||||
<UTabs :items="tags" orientation="vertical" class="md:hidden" @change="updateTag" />
|
||||
<ul class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<NuxtLink
|
||||
v-for="(writing, id) in writings"
|
||||
:key="id"
|
||||
:to="writing._path"
|
||||
>
|
||||
<li
|
||||
class=" h-full border p-4 shadow-sm border-neutral-200 rounded-md hover:border-neutral-500 dark:border-neutral-700 dark:hover:border-neutral-500 duration-300"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-black dark:text-white"
|
||||
>
|
||||
{{ writing.title }}
|
||||
</h1>
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(writing.publishedAt, 'DD MMMM YYYY').value }} </p>·
|
||||
<UIcon name="ph:timer-duotone" size="16" />
|
||||
<p>{{ writing.readingTime }}min long</p>
|
||||
</div>
|
||||
<h3>
|
||||
{{ writing.description }}
|
||||
</h3>
|
||||
</article>
|
||||
<div class="flex gap-2 mt-4 flex-wrap">
|
||||
<UBadge
|
||||
v-for="tag in writing.tags"
|
||||
:key="tag"
|
||||
:color="TAGS.find(color => color.label.toLowerCase() === tag)?.color || 'black'"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
:ui="{ rounded: 'rounded-full' }"
|
||||
>
|
||||
{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "Writing on my life, development, academic and personal projects and passions",
|
||||
"description": "All my thoughts on programming, mathematics, artificial intelligence design, etc., are put together in chronological order. I also write about my projects, my discoveries, and my thoughts.",
|
||||
"alert": {
|
||||
"title": "Translations alert!",
|
||||
"description": "Due to time constraints, all article translations will be available only in English. Thank you for your understanding."
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"title": "Écrits sur ma vie, le développement, mes projets et mes passions.",
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique. J'écris aussi sur mes projets, mes découvertes et mes pensées.",
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,15 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n({
|
||||
useScope: 'local'
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Things I use',
|
||||
description: t('description')
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const { data: items } = await useAsyncData('uses', () => queryContent('/uses').find()
|
||||
)
|
||||
const { data: items } = await useAsyncData('uses', () => queryContent('/uses').find())
|
||||
|
||||
const hardware = items.value!.filter(item => item.category === 'hardware')
|
||||
const software = items.value!.filter(item => item.category === 'software')
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'local'
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Shelf',
|
||||
description: t('description')
|
||||
})
|
||||
|
||||
const { data: writings } = await useAsyncData('all-writings', () =>
|
||||
queryContent('/writings').sort({ published: -1 }).without('body').find()
|
||||
)
|
||||
|
||||
const { data: writingsDB } = await useAsyncData('all-writings-db', () =>
|
||||
$fetch(`/api/posts`)
|
||||
)
|
||||
|
||||
function getDetails(slug: string) {
|
||||
const writing = writingsDB.value!.find(writing => writing.slug === slug)
|
||||
if (!writing) return ''
|
||||
|
||||
const like = writing.likes! > 1 ? t('likes.many') : t('likes.one')
|
||||
const view = writing.views! > 1 ? t('views.many') : t('views.one')
|
||||
|
||||
return `${writing.likes} ${like} · ${writing.views} ${view}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<UAlert
|
||||
v-if="locale !== 'en'"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
class="mt-12"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="outline"
|
||||
/>
|
||||
<ul class="mt-12 space-y-24">
|
||||
<li
|
||||
v-for="(writing, id) in writings"
|
||||
:key="id"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="writing._path"
|
||||
class="group"
|
||||
>
|
||||
<article class="space-y-1">
|
||||
<div class="border-l-2 pl-2 border-gray-300 dark:border-gray-700 rounded-sm">
|
||||
<p>{{ getDetails(writing.slug) }}</p>
|
||||
</div>
|
||||
<div class="flex items-end gap-2 flex-wrap">
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-neutral-600 group-hover:text-black dark:text-neutral-400 dark:group-hover:text-white"
|
||||
>
|
||||
{{ writing.title }}
|
||||
</h1>
|
||||
<p
|
||||
class="mb-0.5 text-sm text-neutral-500 group-hover:text-black dark:group-hover:text-white duration-300"
|
||||
>
|
||||
{{ useDateFormat(writing.publishedAt, 'DD MMMM YYYY').value }} · {{ writing.readingTime }}min long
|
||||
</p>
|
||||
</div>
|
||||
<h3>
|
||||
{{ writing.description }}
|
||||
</h3>
|
||||
</article>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "Writing on my life, development and my passions.",
|
||||
"description": "All my thoughts on programming, mathematics, artificial intelligence design, etc., are put together in chronological order. I also write about my projects, my discoveries, and my thoughts.",
|
||||
"likes": {
|
||||
"one": "like",
|
||||
"many": "likes"
|
||||
},
|
||||
"views": {
|
||||
"one": "view",
|
||||
"many": "views"
|
||||
},
|
||||
"alert": {
|
||||
"title": "Translations alert!",
|
||||
"description": "Due to time constraints, all article translations will be available only in English. Thank you for your understanding."
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"title": "Écrits sur ma vie, le développement et mes passions.",
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique. J'écris aussi sur mes projets, mes découvertes et mes pensées.",
|
||||
"likes": {
|
||||
"one": "like",
|
||||
"many": "likes"
|
||||
},
|
||||
"views": {
|
||||
"one": "vue",
|
||||
"many": "vues"
|
||||
},
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -3,43 +3,43 @@ title: Arthur Danjou • Mathematics Lover and IA Enthusiast
|
||||
description: I'm Arthur, a Mathematics lover and IA enthusiast. I'm currently studying at the University of Paris-Saclay. I'm passionate about Mathematics, Computer Science, and Artificial Intelligence.
|
||||
---
|
||||
|
||||
Hey, I'm Arthur Danjou, a mathematics student at the Paris-Saclay Faculty of Science in France.
|
||||
Hey, I'm Arthur Danjou, a Mathematics student in Statistics at the Paris-Dauphine University in France.
|
||||
|
||||
With a :hover-text{hover="Technology is evolving far too quickly 🤯" position="top" text="deep understanding of emerging
|
||||
technologies"},
|
||||
I'm at the heart of a rapidly expanding field. My background in :hover-text{hover="Maths is my main passion ∑"
|
||||
position="right" text="mathematics"}
|
||||
gives me a head start in understanding the concepts and theories behind these
|
||||
:hover-text{hover="My second passion 📱" text="technologies"} and in designing them effectively.
|
||||
With a :hover-text{hover="Technology is evolving far too quickly 🤯" position="top" text="deep understanding"} of
|
||||
emerging technologies, I'm at the heart of a rapidly expanding field. My background in :hover-text{hover="Maths is my
|
||||
main passion ∑" position="right" text="mathematics"} gives me a head start in
|
||||
understanding the concepts and theories behind these :hover-text{hover="My second passion 📱" text="technologies"} and in designing them
|
||||
effectively.
|
||||
|
||||
As a software engineer and mathematics student, my :hover-text{hover="My bag of knowledge 🎒" text="expertise"} covers
|
||||
:prose-icon[TypeScript]{icon="i-logos-typescript-icon"},
|
||||
:prose-icon[Vue]{icon="i-logos:vue"},
|
||||
:prose-icon[Nuxt]{icon="i-logos:nuxt-icon"},
|
||||
:prose-icon[Adonis]{icon="i-logos:adonisjs-icon"},
|
||||
:prose-icon[Java]{icon="i-logos:java"},
|
||||
:prose-icon[Python]{icon="i-logos:python"},
|
||||
:prose-icon[R]{icon="i-logos:r-lang"},
|
||||
which enables me to :hover-text{hover="need to quickly understand the complexity of projects 🏎️" text="understand"}
|
||||
the different needs of mathematical projects and to propose the best solutions. I also learned other important
|
||||
technologies, such as
|
||||
:prose-icon[Docker]{icon="i-logos:docker-icon"},
|
||||
:prose-icon[Redis]{icon="i-logos:redis"},
|
||||
:prose-icon[MySQL]{icon="i-logos:mysql-icon"} and
|
||||
:prose-icon[Git]{icon="i-logos:git-icon"} to :hover-text{hover="All these technologies complement each other 🔗" text="complete"} my knowledge.
|
||||
:prose-icon[TypeScript]{icon="typescript-icon" color="blue"},
|
||||
:prose-icon[Vue]{icon="vue" color="green"},
|
||||
:prose-icon[Nuxt]{icon="nuxt-icon" color="emerald"},
|
||||
:prose-icon[Adonis]{icon="adonisjs-icon" color="purple"},
|
||||
:prose-icon[Java]{icon="java" color="red"},
|
||||
:prose-icon[Python]{icon="python" color="amber"},
|
||||
:prose-icon[R]{icon="r-lang" color="blue"},
|
||||
which enables me to :hover-text{hover="need to quickly understand the complexity of projects 🏎️" text="understand"} the
|
||||
different needs of mathematical projects and to propose the best solutions.
|
||||
I also learned other important technologies, such as
|
||||
:prose-icon[Docker]{icon="docker-icon" color="sky"},
|
||||
:prose-icon[Redis]{icon="redis" color="red"},
|
||||
:prose-icon[MySQL]{icon="mysql-icon" color="zinc"} and
|
||||
:prose-icon[Git]{icon="git-icon" color="orange"} to :hover-text{hover="All these technologies complement each
|
||||
other 🔗" text="
|
||||
complete"} my knowledge.
|
||||
|
||||
I'm :hover-text{hover="As tech is always evolving, I need to be up-to-date 🖥️" position="top" text="constantly"}
|
||||
learning new things, from technology to finance and entrepreneurship. I love
|
||||
:hover-text{hover="I love sharing my knowledge and helping others 🫂" text="sharing"} my knowledge and learning new
|
||||
theorems and technologies.
|
||||
I'm a :hover-text{hover="I'm constantly looking to discover new things" text="curious"} person and eager to continue
|
||||
learning and growing throughout my life.
|
||||
learning new things, from technology to finance and entrepreneurship. I love :hover-text{hover="I love sharing my
|
||||
knowledge and helping
|
||||
others 🫂" text="sharing"} my knowledge and learning new theorems and technologies. I'm a :hover-text{hover="I'm
|
||||
constantly looking to discover new things" text="curious"} person and eager to continue learning and growing throughout
|
||||
my life.
|
||||
|
||||
As well as programming, I enjoy :hover-text{hover="Sport allows me to burn off energy 🏋️♂️" text="sport"} and
|
||||
:hover-text{hover="Travelling frees me and gets me away from it all ✈️" text="travelling"}.
|
||||
My passion, commitment and eagerness to learn and progress are the qualities that enable me to succeed in my
|
||||
:hover-text{hover="Career already begun and far from over 😎" text="career"} and :hover-text{hover="Only 2 years of study
|
||||
left 💪" text="studies"}.
|
||||
:hover-text{hover="Travelling frees me and gets me away from it all ✈️" text="travelling"}. My passion, commitment and
|
||||
eagerness to learn and progress are the qualities that enable me to succeed in my :hover-text{hover="Career already
|
||||
begun and far from over 😎" text="career"} and :hover-text{hover="Only 2 years of study left 💪" text="studies"}.
|
||||
|
||||
::stats
|
||||
::
|
||||
|
||||
@@ -3,38 +3,36 @@ title: Arthur Danjou • Mathematics Lover and IA Enthusiast
|
||||
description: I'm Arthur, a Mathematics lover and IA enthusiast. I'm currently studying at the University of Paris-Saclay. I'm passionate about Mathematics, Computer Science, and Artificial Intelligence.
|
||||
---
|
||||
|
||||
Salut, je suis Arthur Danjou, étudiant en mathématiques à la faculté des sciences de Paris-Saclay en France.
|
||||
Salut, je suis Arthur Danjou, étudiant en mathématiques spécialisé en Statistiques à l'Université Paris-Dauphine en France.
|
||||
|
||||
Avec une :hover-text{hover="La technologie évolue beaucoup trop vite 🤯" position="top" text="compréhension profonde des
|
||||
technologies émergentes"},
|
||||
je suis au cœur d'un domaine en pleine expansion. Ma formation en :hover-text{hover="Les mathématiques sont ma
|
||||
principale passion ∑" position="right" text="mathématiques"}
|
||||
me donne une longueur d'avance pour comprendre les concepts et les théories qui sous-tendent ces
|
||||
:hover-text{hover="Ma deuxième passion 📱" text="technologies"} et à les concevoir efficacement.
|
||||
Avec une :hover-text{hover="La technologie évolue beaucoup trop vite 🤯" position="top" text="compréhension profonde"}
|
||||
des technologies émergentes, je suis au cœur d'un domaine en pleine expansion. Ma formation en :hover-text{hover="Les
|
||||
mathématiques sont ma principale passion ∑" position="right" text="mathématiques"} me donne une longueur d'avance pour
|
||||
comprendre les concepts et les théories qui sous-tendent ces :hover-text{hover="Ma deuxième passion 📱" text="
|
||||
technologies"} et à les concevoir efficacement.
|
||||
|
||||
En tant qu'ingénieur logiciel et étudiant en mathématiques, mon :hover-text{hover="Mon sac de connaissances 🎒" text="
|
||||
expertise"} couvre
|
||||
:prose-icon[TypeScript]{icon="i-logos-typescript-icon"},
|
||||
:prose-icon[Vue]{icon="i-logos:vue"},
|
||||
:prose-icon[Nuxt]{icon="i-logos:nuxt-icon"},
|
||||
:prose-icon[Adonis]{icon="i-logos:adonisjs-icon"},
|
||||
:prose-icon[Java]{icon="i-logos:java"},
|
||||
:prose-icon[Python]{icon="i-logos:python"},
|
||||
:prose-icon[R]{icon="i-logos:r-lang"},
|
||||
ce qui me permet de :hover-text{hover="Comprendre rapidement la complexité des projets 🏎️" text="comprendre"}
|
||||
les différents besoins des projets mathématiques et de proposer les meilleures solutions. J'ai également appris d'autres
|
||||
technologies importantes, telles que
|
||||
:prose-icon[Docker]{icon="i-logos:docker-icon"},
|
||||
:prose-icon[Redis]{icon="i-logos:redis"},
|
||||
:prose-icon[MySQL]{icon="i-logos:mysql-icon"} et
|
||||
:prose-icon[Git]{icon="i-logos:git-icon"} pour :hover-text{hover="Toutes ces technologies se complètent 🔗" text="
|
||||
:prose-icon[TypeScript]{icon="typescript-icon" color="blue"},
|
||||
:prose-icon[Vue]{icon="vue" color="green"},
|
||||
:prose-icon[Nuxt]{icon="nuxt-icon" color="emerald"},
|
||||
:prose-icon[Adonis]{icon="adonisjs-icon" color="purple"},
|
||||
:prose-icon[Java]{icon="java" color="red"},
|
||||
:prose-icon[Python]{icon="python" color="amber"},
|
||||
:prose-icon[R]{icon="r-lang" color="blue"},
|
||||
ce qui me permet de :hover-text{hover="Comprendre rapidement la complexité des projets 🏎️" text="comprendre"} les
|
||||
différents besoins des projets mathématiques et de proposer les meilleures solutions.
|
||||
J'ai également appris d'autres technologies importantes, telles que
|
||||
:prose-icon[Docker]{icon="docker-icon" color="sky"},
|
||||
:prose-icon[Redis]{icon="redis" color="red"},
|
||||
:prose-icon[MySQL]{icon="mysql-icon" color="zinc"} et
|
||||
:prose-icon[Git]{icon="git-icon" color="orange"} pour :hover-text{hover="Toutes ces technologies se complètent 🔗" text="
|
||||
compléter"} mes connaissances.
|
||||
|
||||
Je suis :hover-text{hover="Je dois toujours chercher à être à jour 🖥️" position="top" text="constamment"} dans
|
||||
l'apprentissage de nouvelles choses, de la technologie à la finance en passant par l'entrepreneuriat. J'aime
|
||||
:hover-text{hover="J'aime partager et aider les autres 🫂" text="partager"} mes connaissances et apprendre de nouveaux
|
||||
théorèmes et technologies.
|
||||
Je suis une personne :hover-text{hover="Je cherche à découvrir de nouvelles choses" text="
|
||||
théorèmes et technologies. Je suis une personne :hover-text{hover="Je cherche à découvrir de nouvelles choses" text="
|
||||
curieuse"} et désireuse de continuer à apprendre et à grandir tout au long de ma vie.
|
||||
|
||||
Outre la programmation, j'aime le :hover-text{hover="Le sport me permet de dépenser de l'énergie 🏋️♂️" text="sport"}
|
||||
|
||||
25
content/portfolio/arthome.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
slug: arthome
|
||||
title: ArtHome
|
||||
description: 🏡 ArtHome - Your personalised home page in your browser
|
||||
publishedAt: 2024/09/04
|
||||
readingTime: 1
|
||||
cover: arthome/cover.png
|
||||
tags:
|
||||
- project
|
||||
- web
|
||||
---
|
||||
|
||||
[ArtHome](https://home.arthurdanjou.fr) is a personalised page where you can create categories and tabs to have a one page with all your shortcuts on all browsers.
|
||||
|
||||
You can customize your tabs and categories with different colors and icons. Feel free to set your page in private if you want to keep secret your tabs.
|
||||
|
||||
Made with:
|
||||
|
||||
- [Nuxt](https://nuxt.com): Nuxt is an **open source framework** that makes web development intuitive and powerful.Create performant and production-grade full-stack web apps and websites with confidence.
|
||||
- [NuxtHub](https://hub.nuxt.com): Deploy and scale your Nuxt applications worldwide with NuxtHub, the Cloudflare-powered platform that ensures lightning-fast performance at low cost and with full-stack capabilities.
|
||||
- [NuxtUI](https://ui.nuxt.com): Nuxt UI simplifies the creation of stunning and responsive web applications with itscomprehensive collections of fully styled and customizable UI components designed for Nuxt.
|
||||
- [Eslint](https://eslint.org): ESLint is an open source project that helps you find and fix problems with your JavaScript code.
|
||||
- [Drizzle](https://orm.drizzle.team/): Drizzle ORM is a headless TypeScript ORM with a head
|
||||
- [Zod](https://zod.dev/): TypeScript-first schema validation with static type inference
|
||||
- and ❤️
|
||||
25
content/portfolio/artsite.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
slug: artsite
|
||||
title: ArtSite
|
||||
description: 🌍 My personal website, my portfolio, and my blog. 🚀
|
||||
publishedAt: 2024/06/01
|
||||
readingTime: 1
|
||||
cover: artsite/cover.png
|
||||
tags:
|
||||
- project
|
||||
- web
|
||||
---
|
||||
|
||||
[**ArtSite**](https://arthurdanjou.fr) is my personal website, my portfolio, and my blog. It's a place where I can share my projects, my thoughts, and my experiences. It's also a place where I can experiment with new technologies and design ideas.
|
||||
|
||||
## ⚒️ Tech stack
|
||||
|
||||
- **UI** → [Vue.js](https://vuejs.org/)
|
||||
- **Framework** → [Nuxt.js](https://nuxtjs.org/)
|
||||
- **Content** → [Nuxt Content](https://content.nuxtjs.org/)
|
||||
- **Design System** → [NuxtUI](https://nuxtui.com/)
|
||||
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio)
|
||||
- **Langage** → [Typescript](https://www.typescriptlang.org/)
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/)
|
||||
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/)
|
||||
- **Package Manager** → [pnpm](https://pnpm.io/)
|
||||
108
content/portfolio/how-my-website-works.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
slug: how-my-website-works
|
||||
title: How my website works?
|
||||
description: My new website is using a fantastical stack and I am explaining how my playground works
|
||||
publishedAt: 2024/06/21
|
||||
readingTime: 5
|
||||
tags:
|
||||
- article
|
||||
- web
|
||||
---
|
||||
|
||||
My personal website is an overengineered playground where I tinker, explore new technologies, experiment with tools, break conventional rules, and satisfy my deep curiosity about web software.
|
||||
|
||||
While it's still fresh in my mind, I wanted to document how this version of the site works, the tools I used to build it, and the challenges I overcame to bring it to its current state.
|
||||
|
||||

|
||||
|
||||
## Ideas and Goals
|
||||
|
||||
Most of the time, I work on my site for fun and without any profit motive. However, while building this latest version, I managed to keep a few key ideas and goals in mind:
|
||||
|
||||
### Reduce writing friction
|
||||
|
||||
This new version of my website was built with the idea that I should be able to add, edit, and delete content directly from the front-end. This means that everything needs to be backed by a database or CMS, which quickly adds complexity. But at the end of the day, adding a bookmark should be a matter of pasting a URL and clicking save. Writing a blog post should be a matter of typing some Markdown and clicking publication.
|
||||
|
||||
Extra friction on these processes would make me less likely to keep things up to date or share new things.
|
||||
|
||||
### A playground for ideas
|
||||
|
||||
I want my website to be a playground where I can safely experiment with new technologies and packages, including testing frameworks (like the new Nuxt 3 stack), improving CSS styles with Tailwind, and discovering new technologies and frameworks, in a way that allows for easy isolation and deletion. This requirement made Nuxt.js an obvious choice, thanks to its support for hybrid page rendering strategies—static, server-rendered, or client-rendered. More on this below.
|
||||
|
||||
### Fast
|
||||
|
||||
The new version of my website is faster than the old one, thanks to the latest version of Nuxt. This improvement enhances the overall user experience and ensures that the site remains responsive and efficient.
|
||||
|
||||
## FrontEnd & BackEnd with Nuxt 3
|
||||
|
||||
I wanted this version of my site to reflect my personality, especially because it seemed like a fun project! What would a 'personal application' look like, showcasing everything I've created? I aimed for a clean, monochrome design with plenty of 'Easter eggs' to keep things interesting.
|
||||
|
||||
### Nuxt 3
|
||||
|
||||
Nuxt.js is my front-end framework of choice. I particularly appreciate it for its comprehensive and complementary Vue and Nuxt ecosystem. The filesystem-based router provides an intuitive and powerful abstraction for building the route hierarchy. Nuxt.js also benefits from a large community that has thoroughly tested the framework, addressing edge cases and developing creative solutions for common Vue, data recovery, and performance issues. Whenever I encounter a problem, I turn to the Nuxt.js discussions on [GitHub](https://github.com/nuxt) or their [Discord server](https://go.nuxt.com/discord). Almost every time, I find that others have already come up with innovative solutions to similar challenges.
|
||||
|
||||
Nuxt.js is also fast. It optimizes performance by speeding up local builds, automatically compressing static assets, and ensuring quick deployment times. The regular project updates mean my site continually gets faster over time—at no extra cost!
|
||||
|
||||
### Styling
|
||||
|
||||
#### Tailwind CSS
|
||||
|
||||
Tailwind is my favorite CSS authoring tool... ever. It's incredibly effective. I often see debates on Twitter about whether Tailwind is the best or worst thing ever, and I prefer not to engage in that discussion. Here's my take:
|
||||
|
||||
Tailwind is a toolkit that makes everything great by default and fast. The magic lies in its token system and the default values built into the framework. Once I grasped the semantics of Tailwind, I was able to style my tags at the speed of thought.
|
||||
|
||||
Tailwind provides everything I need out of the box, but I've gradually added a bit of custom CSS to make things more unique.
|
||||
|
||||
#### Nuxt UI
|
||||
|
||||
Nuxt UI is a new tool I've been using since its release to enhance and streamline my Nuxt projects. It’s a module that offers a collection of Vue components and composables built with Tailwind CSS and Headless UI, designed to help you create beautiful and accessible user interfaces.
|
||||
|
||||
Nuxt UI aims to provide everything you need for the UI when building a Nuxt app, including components, icons, colors, dark mode, and keyboard shortcuts. It's an excellent tool for both beginners and experienced developers.
|
||||
|
||||
### Database & Deployment
|
||||
|
||||
#### NuxtHub & Cloudflare workers
|
||||
|
||||

|
||||
|
||||
NuxtHub is an innovative deployment and management platform tailored for Nuxt, leveraging the power of Cloudflare. Deploy your application effortlessly with database, key-value, and blob storage—all configured seamlessly within your Cloudflare account.
|
||||
|
||||
NuxtHub enables cost-effective hosting of high-performance Nuxt applications across multiple environments. It simplifies the process of launching your app swiftly, eliminating concerns about server setup or complex deployment pipelines with just a single command.
|
||||
|
||||
#### Drizzle
|
||||
|
||||
Drizzle is a unique ORM that offers both relational and SQL-like query APIs, combining the best of both worlds for accessing relational data. Lightweight, performant, typesafe, and designed to be serverless-ready, Drizzle is also flexible and gluten-free—delivering a sober and seamless experience.
|
||||
|
||||
Drizzle isn't just a library; it's an exceptional journey 🤩. It empowers you to build your project without imposing on your structure or workflow. With Drizzle, you can define and manage database schemas in TypeScript, access your data using SQL-like or relational methods, and use optional tools to enhance your development experience significantly.
|
||||
|
||||
One word : `If you know SQL — you know Drizzle.`
|
||||
|
||||
### Writing
|
||||
|
||||
#### Nuxt Studio
|
||||
|
||||

|
||||
|
||||
Nuxt Studio introduces a fresh editing experience for your Nuxt Content website, providing limitless customization and a user-friendly interface. Edit your website effortlessly with our editor reminiscent of Notion, fostering seamless collaboration between developers and copywriters. It offers a rich text editor, markdown support, and a live preview, enabling you to create and edit content with ease.
|
||||
|
||||
#### Markdown
|
||||
|
||||
I've abandoned using rich text editors on the web. They're overly complex, each with its own intricate abstractions for blocks and elements. To avoid another major rewrite soon, I've sought the simplest, most straightforward solution for publishing content on my site: plain text.
|
||||
|
||||
The article you're currently reading is plain text stored in MySQL, rendered using vue-markdown. You can view my custom element renderings here. I enhance my Markdown capabilities by employing plugins like remark-gfm, which add support for tables, strikethrough, footnotes, and other features.
|
||||
|
||||
Compromises are inevitable! I've chosen to sacrifice some features for simplicity and speed. I'm content with my decision, as it aligns with my goal of reducing friction in the writing process.
|
||||
|
||||
## How much everything costs
|
||||
|
||||
I'm often asked how much it costs to run my website. Here's a breakdown of the costs:
|
||||
|
||||
- NuxtHub: 0€
|
||||
- Cloudflare Workers: 0€
|
||||
- Nuxt Studio: 0€
|
||||
|
||||
Total: 0€ thanks to nuxt free plan and cloudflare free plan
|
||||
|
||||
## Thanks
|
||||
|
||||
I want to thank the Nuxt team for their hard work and dedication to the project. I also want to thank the community for their support and for providing me with the tools I needed to build this site. I want to add a special thanks to [Estéban](https://x.com/soubiran_) for solving `All` my problems and for inspiring me to rewrite my website.
|
||||
45
content/portfolio/studies.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
slug: studies
|
||||
title: Studies projects
|
||||
description: 🎓 Studies projects - a collection of projects done during my studies.
|
||||
publishedAt: 2023/09/01
|
||||
readingTime: 1
|
||||
tags:
|
||||
- project
|
||||
- data
|
||||
- python
|
||||
- r
|
||||
---
|
||||
|
||||
[Studies projects](https://github.com/ArthurDanjou/studies) is a collection of mathematics projects done during my studies. It includes projects in _Python_ and in _R_.
|
||||
|
||||
The projects are divided into two main categories: _L3_ and _M1_, corresponding to the third year of the bachelor's degree and the first year of the master's degree in mathematics.
|
||||
|
||||
File structure:
|
||||
- `L3`
|
||||
- `Analyse Matricielle`
|
||||
- `Analyse Multidimensionnelle`
|
||||
- `Calculs Numériques`
|
||||
- `Equations Différentielles`
|
||||
- `Méthodes Numériques`
|
||||
- `Probabilités`
|
||||
- `Projet Numérique`
|
||||
- `Statistiques`
|
||||
- `M1`
|
||||
- `Data Analysis`
|
||||
- `General Linear Models`
|
||||
- `Monte Carlo Methods`
|
||||
- `Portfolio Management`
|
||||
|
||||
Made with:
|
||||
- [Python](https://www.python.org): Python is an interpreted, high-level and general-purpose programming language.
|
||||
- [R](https://www.r-project.org): R is a programming language and free software environment for statistical computing and graphics.
|
||||
- [Jupyter](https://jupyter.org): Jupyter is a free, open-source, interactive web tool known as a computational notebook, which researchers can use to combine software code, computational output, explanatory text and multimedia resources in a single document.
|
||||
- [Pandas](https://pandas.pydata.org): Pandas is a fast, powerful, flexible and easy to use open source data analysis and data manipulation library built on top of the Python programming language.
|
||||
- [Numpy](https://numpy.org): NumPy is the fundamental package for scientific computing in Python.
|
||||
- [Scipy](https://www.scipy.org): SciPy is a free and open-source Python library used for scientific and technical computing.
|
||||
- [Matplotlib](https://matplotlib.org): Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python.
|
||||
- [RMarkdown](https://rmarkdown.rstudio.com): R Markdown is an authoring framework for data science. You can use a single R Markdown file to save and execute code and generate high-quality reports that can be shared with an audience.
|
||||
- [FactoMineR](https://factominer.free.fr/): FactoMineR is an R package dedicated to multivariate exploratory data analysis.
|
||||
- [ggplot2](https://ggplot2.tidyverse.org): ggplot2 is a system for declaratively creating graphics, based on The Grammar of Graphics.
|
||||
- and my 🧠
|
||||
95
content/portfolio/what-is-machine-learning.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
slug: what-is-machine-learning
|
||||
title: What is Machine Learning?
|
||||
description: An introduction to machine learning, exploring its main types, key model selection criteria, and the workflow from training to evaluation, with a focus on practical insights.
|
||||
readingTime: 3
|
||||
publishedAt: 2024/11/26
|
||||
tags:
|
||||
- article
|
||||
- ml
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Machine Learning (ML) is a key discipline in artificial intelligence (AI), enabling systems to learn from data to make predictions or discover patterns. It is the driving force behind many modern innovations, from personalised recommendations to autonomous vehicles.
|
||||
|
||||
In this article, we will cover:
|
||||
|
||||
1. **The types of Machine Learning**, to understand the different approaches.
|
||||
2. **Three considerations for choosing a supervised learning model**, one of the most common ML paradigms.
|
||||
3. **The typical ML workflow**, exploring the essential steps for developing a model.
|
||||
4. **Model evaluation through the R² score**, an important metric for regression problems.
|
||||
|
||||
## The Types of Machine Learning
|
||||
|
||||
To start, it is important to understand the three main categories of machine learning:
|
||||
|
||||
1. **Supervised Learning** This type of learning relies on labeled data, where the model learns to map inputs $X$ to outputs $y$. Common tasks include:
|
||||
- **Classification**: Assigning data to categories (e.g., spam detection).
|
||||
- **Regression**: Predicting continuous values (e.g., house price estimation).
|
||||
|
||||
2. **Unsupervised Learning** In this case, no labels are provided, and the goal is to find structures or patterns. Common tasks include:
|
||||
- **Clustering**: Grouping similar data points (e.g., customer segmentation).
|
||||
- **Dimensionality Reduction**: Simplifying data while retaining key information (e.g., PCA).
|
||||
- **Anomaly Detection**: Identifying unusual data points (e.g., fraud detection).
|
||||
|
||||
3. **Reinforcement Learning** This learning type involves an agent interacting with an environment. The agent learns by trial and error to maximize cumulative rewards, as seen in robotics and gaming.
|
||||
|
||||

|
||||
|
||||
With this overview of ML types, let’s now focus on supervised learning, the most widely used approach, and explore how to choose the right model.
|
||||
|
||||
## Three Considerations for Choosing a Supervised Learning Model
|
||||
|
||||
Selecting the right supervised learning model is critical and depends on several factors:
|
||||
|
||||
1. **Problem Type**
|
||||
- Is it a regression or classification problem?
|
||||
- **Key Point**: Determine if the target variable is continuous or discrete.
|
||||
|
||||
2. **Problem Complexity**
|
||||
- Is the relationship between input features and the target variable linear or nonlinear?
|
||||
- **Key Point**: Understand whether the data allows for easy predictions or requires more complex models.
|
||||
|
||||
3. **Algorithmic Approach**
|
||||
- Should you choose a feature-based or similarity-based model?
|
||||
- **Key Point**: The choice of the model (e.g., linear regressions vs k-NN) depends on the dataset’s size and complexity.
|
||||
|
||||
Once the model type is defined, the next step is to delve into the full workflow of developing an ML model.
|
||||
|
||||
## The Typical Workflow in Machine Learning
|
||||
|
||||
A machine learning project generally follows these steps:
|
||||
|
||||
1. **Data Preparation**
|
||||
- Splitting data into training and testing sets.
|
||||
- Preprocessing: scaling, handling missing values, etc.
|
||||
2. **Model Training**
|
||||
- Fitting the model on training data: `model.fit(X, y)`.
|
||||
- Optimising parameters and hyperparameters.
|
||||
3. **Prediction and Evaluation**
|
||||
- Making predictions on unseen data: `model.predict(X)`.
|
||||
- Comparing predictions ($$\hat{y}$$) with actual values ($$y$$).
|
||||
|
||||

|
||||
|
||||
Evaluation is a crucial step to verify the performance of a model. For regression problems, the R² score is a key indicator.
|
||||
|
||||
## Evaluating Models: The R² Score
|
||||
|
||||
For regression problems, the **R² score** measures the proportion of the target’s variance explained by the model:
|
||||
|
||||
$$R2 = 1 - \frac{\text{SS}_{\text{residual}}}{\text{SS}_{\text{total}}}$$ where:
|
||||
|
||||
- $$\text{SS}\_{\text{residual}}$$ : Sum of squared residuals between actual and predicted values.
|
||||
- $$\text{SS}\_{\text{total}}$$ : Total sum of squares relative to the target’s mean.
|
||||
|
||||
A $$R^2$$ close to 1 indicates a good fit.
|
||||
|
||||

|
||||
|
||||
With these concepts in mind, you are better equipped to understand and apply ML models in your projects.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Machine learning is revolutionising how we solve complex problems using data. Supervised, unsupervised, and reinforcement learning approaches allow us to tackle a wide variety of use cases. In supervised learning, the model choice depends on the problem type, its complexity, and the appropriate algorithmic approach. Finally, a structured workflow and metrics like the R² score ensure the quality of predictions and analyses.
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Probablement l'objet que j'utilise le plus après mon téléphone et mon ordinateur portable. Je les utilise pour tout, de l'écoute de musique à la prise d'appels. Ils sont super pratiques et la qualité sonore est excellente."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise mon iPad pour lire des livres, regarder des films et naviguer sur le web, mais aussi pour prendre des notes et écrire des équations pendant mes cours de mathématiques."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Je n'améliore pas mon téléphone chaque année, mais quand je le fais, je vais pour le meilleur. L'iPhone 14 Pro est le meilleur téléphone sur le marché, et je suis excité de mettre la main dessus."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Mon ordinateur principal pour programmer est un MacBook Pro 13' 2020 avec la puce Apple M1 et 16Go de RAM. J'utilise MacOS Sorona."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise la suite Apple comprenant Mail, Calendar, Notes, Music et Reminders pour mon organisation quotidienne."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise Discord pour discuter et parler avec mes amis et mes clients et discuter avec certains membres de la communauté."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'ai acheté un ordinateur personnalisé pour le jeu. J'ai choisi un Intel Core i5-10400F, avec 16Go DDR4 et ma carte graphique est une RTX 2060. J'utilise Windows 11."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise Google Chrome pour naviguer, l'outil de développement et le marché des extensions."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise la suite JetBrains pour le développement depuis 7 ans. C'est le meilleur IDE pour Java, Python, JavaScript, SQL et plus encore. J'ai utilisé cette suite pour développer et créer ce site web."
|
||||
},
|
||||
"category": "ide"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Cette souris de jeu est conçue pour être le compagnon de jeu parfait. Avec un design classique et une disposition simple, cette souris est parfaite pour tout joueur."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Notion est mon outil de prise de notes, de kanban, de wikis et de brouillon tout-en-un."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Raycast est mon lanceur extensible remplaçant Apple Spotlight. Il me permet d'accomplir des tâches, de calculer, de partager des liens communs et bien plus encore grâce aux extensions."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "J'utilise TypeScript, Vue 3 avec Nuxt 3, Nuxt Stack (UI, Hub, Content, Studio) & TailwindCss pour le FrontEnd. Nuxt (alimenté par Nitro) et AdonisJs sont utilisés pour le BackEnd en fonction de la complexité du projet. PostgreSQL est utilisé pour la base de données, Redis pour le caching. Docker est utilisé pour la conteneurisation. Les applications sont déployées sur NuxtHub (alimenté par CloudFlare) ou Vercel."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Ce clavier TKL est un excellent choix pour les joueurs qui veulent un clavier compact avec de nombreuses fonctionnalités."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Mon thème est Catppuccin Macchiato, un thème pastel piloté par la communauté qui vise à être le juste milieu entre les thèmes à faible et à fort contraste. Mes polices principales sont Vercel Geist et JetBrains Mono"
|
||||
},
|
||||
"category": "ide"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
"fr": "Warp est un terminal moderne basé sur Rust réimaginé avec l'IA et des outils collaboratifs pour une meilleure productivité. Je l'adore jusqu'à présent !"
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
---
|
||||
slug: how-my-website-works
|
||||
title: How my website works?
|
||||
description: My new website is using a fantastical stack and I am explaining how my playground works
|
||||
publishedAt: 2024/06/21
|
||||
readingTime: 5
|
||||
---
|
||||
|
||||
My personal website is an over-engineered playground where I tinker, explore new technologies, experiment with tools,
|
||||
break conventional rules, and satisfy my deep curiosity about web software.
|
||||
|
||||
While it's still fresh in my mind, I wanted to document how this version of the site works, the tools I used to build
|
||||
it, and the challenges I overcame to bring it to its current state.
|
||||
|
||||

|
||||
|
||||
## Ideas and Goals
|
||||
|
||||
Most of the time, I work on my site for fun and without any profit motive. However, while building this latest version,
|
||||
I managed to keep a few key ideas and goals in mind:
|
||||
|
||||
### Reduce writing friction
|
||||
|
||||
This new version of my website was built with the idea that I should be able to add, edit, and delete content directly
|
||||
from the front-end. This means that everything needs to be backed by a database or CMS, which quickly adds complexity.
|
||||
But at the end of the day, adding a bookmark should be a matter of pasting a URL and clicking save. Writing a blog post
|
||||
should be a matter of typing some markdown and clicking publish.
|
||||
|
||||
Extra friction on these processes would make me less likely to keep things up to date or share new things.
|
||||
|
||||
### A playground for ideas
|
||||
|
||||
I want my website to be a playground where I can safely experiment with new technologies and packages, including testing
|
||||
frameworks (like the new Nuxt 3 stack), improving CSS styles with Tailwind, and discovering new technologies and
|
||||
frameworks, in a way that allows for easy isolation and deletion. This requirement made Nuxt.js an obvious choice,
|
||||
thanks to its support for hybrid page rendering strategies—static, server-rendered, or client-rendered. More on this
|
||||
below.
|
||||
|
||||
### Fast
|
||||
|
||||
The new version of my website is faster than the old one, thanks to the latest version of Nuxt. This improvement
|
||||
enhances the overall user experience and ensures that the site remains responsive and efficient.
|
||||
|
||||
## FrontEnd & BackEnd with Nuxt 3
|
||||
|
||||
I wanted this version of my site to reflect my personality, especially because it seemed like a fun project! What would
|
||||
a 'personal application' look like, showcasing everything I've created? I aimed for a clean, monochrome design with
|
||||
plenty of 'Easter eggs' to keep things interesting.
|
||||
|
||||
### Nuxt 3
|
||||
|
||||
Nuxt.js is my front-end framework of choice. I particularly appreciate it for its comprehensive and complementary Vue
|
||||
and Nuxt ecosystem. The filesystem-based router provides an intuitive and powerful abstraction for building the route
|
||||
hierarchy. Nuxt.js also benefits from a large community that has thoroughly tested the framework, addressing edge cases
|
||||
and developing creative solutions for common Vue, data recovery, and performance issues. Whenever I encounter a problem,
|
||||
I turn to the Nuxt.js discussions on [GitHub](https://github.com/nuxt) or
|
||||
their [Discord server](https://go.nuxt.com/discord). Almost every time, I find that others have already come up with
|
||||
innovative solutions to similar challenges.
|
||||
|
||||
Nuxt.js is also fast. It optimizes performance by speeding up local builds, automatically compressing static assets, and
|
||||
ensuring quick deployment times. The regular project updates mean my site continually gets faster over time—at no extra
|
||||
cost!
|
||||
|
||||
### Styling
|
||||
|
||||
#### Tailwind CSS
|
||||
|
||||
Tailwind is my favorite CSS authoring tool... ever. It's incredibly effective. I often see debates on Twitter about
|
||||
whether Tailwind is the best or worst thing ever, and I prefer not to engage in that discussion. Here's my take:
|
||||
|
||||
Tailwind is a toolkit that makes everything great by default and fast. The magic lies in its token system and the
|
||||
default values built into the framework. Once I grasped the semantics of Tailwind, I was able to style my tags at the
|
||||
speed of thought.
|
||||
|
||||
Tailwind provides everything I need out of the box, but I've gradually added a bit of custom CSS to make things more
|
||||
unique.
|
||||
|
||||
#### Nuxt UI
|
||||
|
||||
Nuxt UI is a new tool I've been using since its release to enhance and streamline my Nuxt projects. It’s a module that
|
||||
offers a collection of Vue components and composables built with Tailwind CSS and Headless UI, designed to help you
|
||||
create beautiful and accessible user interfaces.
|
||||
|
||||
Nuxt UI aims to provide everything you need for the UI when building a Nuxt app, including components, icons, colors,
|
||||
dark mode, and keyboard shortcuts. It's an excellent tool for both beginners and experienced developers.
|
||||
|
||||
### Database & Deployment
|
||||
|
||||
#### NuxtHub & Cloudflare workers
|
||||
|
||||

|
||||
|
||||
NuxtHub is an innovative deployment and management platform tailored for Nuxt, leveraging the power of Cloudflare.
|
||||
Deploy your application effortlessly with database, key-value, and blob storage—all configured seamlessly within your
|
||||
Cloudflare account.
|
||||
|
||||
NuxtHub enables cost-effective hosting of high-performance Nuxt applications across multiple environments. It simplifies
|
||||
the process of launching your app swiftly, eliminating concerns about server setup or complex deployment pipelines with
|
||||
just a single command.
|
||||
|
||||
#### Drizzle
|
||||
|
||||
Drizzle is a unique ORM that offers both relational and SQL-like query APIs, combining the best of both worlds for
|
||||
accessing relational data. Lightweight, performant, typesafe, and designed to be serverless-ready, Drizzle is also
|
||||
flexible and gluten-free—delivering a sober and seamless experience.
|
||||
|
||||
Drizzle isn't just a library; it's an exceptional journey 🤩. It empowers you to build your project without imposing on
|
||||
your structure or workflow. With Drizzle, you can define and manage database schemas in TypeScript, access your data
|
||||
using SQL-like or relational methods, and utilize optional tools to enhance your development experience significantly.
|
||||
|
||||
One word : `If you know SQL — you know Drizzle.`
|
||||
|
||||
### Writing
|
||||
|
||||
#### Nuxt Studio
|
||||
|
||||

|
||||
|
||||
Nuxt Studio introduces a fresh editing experience for your Nuxt Content website, providing limitless customization and a
|
||||
user-friendly interface. Edit your website effortlessly with our editor reminiscent of Notion, fostering seamless
|
||||
collaboration between developers and copywriters. It offers a rich text editor, markdown support, and a live preview,
|
||||
enabling you to create and edit content with ease.
|
||||
|
||||
#### Markdown
|
||||
|
||||
I've abandoned using rich text editors on the web. They're overly complex, each with its own intricate abstractions for
|
||||
blocks and elements. To avoid another major rewrite in the near future, I've sought the simplest, most straightforward
|
||||
solution for publishing content on my site: plain text.
|
||||
|
||||
The article you're currently reading is plain text stored in MySQL, rendered using vue-markdown. You can view my custom
|
||||
element renderings here. I enhance my Markdown capabilities by employing plugins like remark-gfm, which adds support for
|
||||
tables, strikethrough, footnotes, and other features.
|
||||
|
||||
Compromises are inevitable! I've chosen to sacrifice some features for simplicity and speed. I'm content with my
|
||||
decision, as it aligns with my goal of reducing friction in the writing process.
|
||||
|
||||
## How much everything costs
|
||||
|
||||
I'm often asked how much it costs to run my website. Here's a breakdown of the costs:
|
||||
|
||||
- NuxtHub: 0€
|
||||
- Cloudflare Workers: 0€
|
||||
- Nuxt Studio: 0€
|
||||
Total: 0€ thanks to nuxt free plan and cloudflare free plan
|
||||
|
||||
## Thanks
|
||||
|
||||
I want to thank the Nuxt team for their hard work and dedication to the project. I also want to thank the community for
|
||||
their support and for providing me with the tools I needed to build this site. I want to add a special thanks
|
||||
to [Estéban](https://x.com/soubiran_) for solving `All` my problems and for inspiring me rewriting my website.
|
||||
@@ -3,5 +3,5 @@ import type { Config } from 'drizzle-kit'
|
||||
export default {
|
||||
dialect: 'sqlite',
|
||||
schema: './server/database/schema.ts',
|
||||
out: './server/database/migrations'
|
||||
out: './server/database/migrations',
|
||||
} satisfies Config
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default withNuxt(
|
||||
export default antfu(
|
||||
// Your custom configs here
|
||||
)
|
||||
|
||||
@@ -6,15 +6,14 @@ export default defineNuxtConfig({
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
head: {
|
||||
templateParams: {
|
||||
separator: '•'
|
||||
}
|
||||
}
|
||||
separator: '•',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt Modules
|
||||
modules: [
|
||||
'@nuxthub/core',
|
||||
'@nuxt/eslint',
|
||||
'@nuxt/ui',
|
||||
'@nuxt/content',
|
||||
'@vueuse/nuxt',
|
||||
@@ -22,7 +21,7 @@ export default defineNuxtConfig({
|
||||
'@nuxthq/studio',
|
||||
'@nuxt/image',
|
||||
'@nuxtjs/i18n',
|
||||
'nuxt-mapbox'
|
||||
'nuxt-mapbox',
|
||||
],
|
||||
|
||||
// Nuxt Hub
|
||||
@@ -30,26 +29,50 @@ export default defineNuxtConfig({
|
||||
cache: true,
|
||||
kv: true,
|
||||
database: true,
|
||||
analytics: true
|
||||
analytics: true,
|
||||
},
|
||||
|
||||
// Nuxt Content
|
||||
content: {
|
||||
highlight: {
|
||||
theme: 'github-dark'
|
||||
}
|
||||
theme: 'github-dark',
|
||||
},
|
||||
markdown: {
|
||||
remarkPlugins: ['remark-math'],
|
||||
rehypePlugins: {
|
||||
'rehype-katex': {
|
||||
output: 'mathml',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt UI
|
||||
ui: {
|
||||
safelistColors: [
|
||||
'gray',
|
||||
'zinc',
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'green',
|
||||
'emerald',
|
||||
'sky',
|
||||
'blue',
|
||||
'purple',
|
||||
],
|
||||
},
|
||||
|
||||
// Nuxt Color Mode
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
fallback: 'light'
|
||||
fallback: 'light',
|
||||
},
|
||||
|
||||
// Nuxt Devtools
|
||||
devtools: {
|
||||
enabled: true,
|
||||
timeline: { enabled: true }
|
||||
timeline: { enabled: true },
|
||||
},
|
||||
|
||||
// Nuxt I18N
|
||||
@@ -58,31 +81,26 @@ export default defineNuxtConfig({
|
||||
locales: [
|
||||
{
|
||||
code: 'en',
|
||||
iso: 'en-EN',
|
||||
language: 'en-EN',
|
||||
icon: 'i-twemoji-flag-united-kingdom'
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
iso: 'fr-FR',
|
||||
language: 'fr-FR',
|
||||
icon: 'i-twemoji-flag-france'
|
||||
},
|
||||
{
|
||||
code: 'es',
|
||||
iso: 'es-ES',
|
||||
language: 'es-ES',
|
||||
icon: 'i-twemoji-flag-spain'
|
||||
}
|
||||
],
|
||||
defaultLocale: 'en'
|
||||
defaultLocale: 'en',
|
||||
},
|
||||
|
||||
// Nuxt Eslint
|
||||
eslint: {
|
||||
config: {
|
||||
stylistic: {
|
||||
quotes: 'single',
|
||||
commaDangle: 'never'
|
||||
}
|
||||
}
|
||||
// Nuxt Icon
|
||||
icon: {
|
||||
serverBundle: 'remote',
|
||||
},
|
||||
|
||||
// Nuxt Google Fonts
|
||||
@@ -92,15 +110,15 @@ export default defineNuxtConfig({
|
||||
'Inter': [400, 500, 600, 700, 800, 900],
|
||||
'Sofia Sans': [400],
|
||||
'DM Sans': [400, 500, 600, 700, 800, 900],
|
||||
'Dancing Script': [400, 700]
|
||||
}
|
||||
'Dancing Script': [400, 700],
|
||||
},
|
||||
},
|
||||
|
||||
// Nitro
|
||||
nitro: {
|
||||
experimental: {
|
||||
openAPI: true
|
||||
}
|
||||
openAPI: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Nuxt Env
|
||||
@@ -108,28 +126,28 @@ export default defineNuxtConfig({
|
||||
discord: {
|
||||
userId: '',
|
||||
id: '',
|
||||
token: ''
|
||||
token: '',
|
||||
},
|
||||
wakatime: {
|
||||
userId: '',
|
||||
coding: '',
|
||||
editors: '',
|
||||
languages: '',
|
||||
os: ''
|
||||
os: '',
|
||||
},
|
||||
public: {
|
||||
cloud: {
|
||||
resume: ''
|
||||
i18n: {
|
||||
baseUrl: '',
|
||||
},
|
||||
mapbox: {
|
||||
accessToken: '',
|
||||
style: {
|
||||
light: '',
|
||||
dark: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
dark: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-07-08'
|
||||
compatibilityDate: '2024-08-19',
|
||||
})
|
||||
|
||||
51
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "artsite",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.4.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev --host",
|
||||
@@ -9,36 +9,39 @@
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"db:generate": "drizzle-kit generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.229",
|
||||
"@nuxt/content": "^2.13.2",
|
||||
"@nuxt/eslint": "^0.3.13",
|
||||
"@nuxt/image": "^1.7.0",
|
||||
"@nuxt/ui": "^2.17.0",
|
||||
"@nuxthq/studio": "^2.0.3",
|
||||
"@nuxthub/core": "^0.7.1",
|
||||
"@nuxt/content": "^2.13.4",
|
||||
"@nuxt/image": "^1.8.1",
|
||||
"@nuxt/ui": "^2.18.5",
|
||||
"@nuxthq/studio": "^2.1.1",
|
||||
"@nuxthub/core": "^0.8.7",
|
||||
"@nuxtjs/google-fonts": "^3.2.0",
|
||||
"@nuxtjs/i18n": "^8.3.1",
|
||||
"drizzle-orm": "^0.31.4",
|
||||
"@nuxtjs/i18n": "^8.5.3",
|
||||
"drizzle-orm": "^0.33.0",
|
||||
"h3-zod": "^0.5.3",
|
||||
"nuxt": "^3.12.4",
|
||||
"nuxt": "^3.13.2",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/devtools": "^1.3.9",
|
||||
"@nuxt/eslint-config": "^0.3.13",
|
||||
"@nuxt/ui": "^2.17.0",
|
||||
"@types/node": "^20.14.11",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"@vueuse/nuxt": "^10.11.0",
|
||||
"drizzle-kit": "^0.22.8",
|
||||
"eslint": "^9.7.0",
|
||||
"mapbox-gl": "^3.5.2",
|
||||
"nuxt-mapbox": "^1.6.0",
|
||||
"typescript": "^5.5.3",
|
||||
"vue-tsc": "^2.0.26",
|
||||
"wrangler": "^3.65.1"
|
||||
"@antfu/eslint-config": "^3.10.0",
|
||||
"@nuxt/devtools": "^1.6.1",
|
||||
"@nuxt/ui": "^2.19.2",
|
||||
"@types/node": "^22.10.0",
|
||||
"@vueuse/core": "^11.3.0",
|
||||
"@vueuse/nuxt": "^11.1.0",
|
||||
"drizzle-kit": "^0.28.1",
|
||||
"eslint": "^9.15.0",
|
||||
"mapbox-gl": "^3.8.0",
|
||||
"nuxt-mapbox": "^1.6.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"wrangler": "^3.90.0"
|
||||
}
|
||||
}
|
||||
|
||||
5789
pnpm-lock.yaml
generated
BIN
public/Resume2024.pdf
Normal file
BIN
public/portfolio/ML/model.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
public/portfolio/ML/r2.png
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
public/portfolio/ML/types.png
Normal file
|
After Width: | Height: | Size: 316 KiB |
1
public/portfolio/arthome/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This is a placeholder file to keep this folder in your repository.
|
||||
BIN
public/portfolio/arthome/cover.png
Normal file
|
After Width: | Height: | Size: 410 KiB |
BIN
public/portfolio/artsite/cover.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 268 KiB |
@@ -2,16 +2,16 @@ import { useValidatedParams, z } from 'h3-zod'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { slug } = await useValidatedParams(event, {
|
||||
slug: z.string()
|
||||
slug: z.string(),
|
||||
})
|
||||
return useDB().insert(tables.posts).values({
|
||||
slug
|
||||
slug,
|
||||
}).onConflictDoUpdate({
|
||||
target: tables.posts.slug,
|
||||
set: {
|
||||
slug,
|
||||
views: sql`${tables.posts.views}
|
||||
+ 1`
|
||||
}
|
||||
+ 1`,
|
||||
},
|
||||
}).returning().get()
|
||||
})
|
||||
|
||||
@@ -2,12 +2,10 @@ import { useValidatedParams, z } from 'h3-zod'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { slug } = await useValidatedParams(event, {
|
||||
slug: z.string()
|
||||
slug: z.string(),
|
||||
})
|
||||
return useDB().update(tables.posts)
|
||||
.set({
|
||||
likes: sql`${tables.posts.likes}
|
||||
+ 1`
|
||||
})
|
||||
.where(eq(tables.posts.slug, slug))
|
||||
return useDB().update(tables.posts).set({
|
||||
likes: sql`${tables.posts.likes}
|
||||
+ 1`,
|
||||
}).where(eq(tables.posts.slug, slug))
|
||||
})
|
||||
|
||||
@@ -10,9 +10,9 @@ export default defineCachedEventHandler(async (event) => {
|
||||
coding,
|
||||
editors,
|
||||
os,
|
||||
languages
|
||||
languages,
|
||||
}
|
||||
}, {
|
||||
maxAge: 24 * 60 * 60,
|
||||
name: 'wakatime'
|
||||
name: 'wakatime',
|
||||
})
|
||||
|
||||
@@ -110,4 +110,4 @@
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,4 @@
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
export const posts = sqliteTable('posts', {
|
||||
slug: text('slug').primaryKey(),
|
||||
likes: integer('likes').default(0),
|
||||
views: integer('views').default(0),
|
||||
createdAt: text('created_at').default(sql`(CURRENT_DATE)`)
|
||||
createdAt: text('created_at').default(sql`(CURRENT_DATE)`),
|
||||
})
|
||||
|
||||
@@ -2,7 +2,8 @@ import { consola } from 'consola'
|
||||
import { migrate } from 'drizzle-orm/d1/migrator'
|
||||
|
||||
export default defineNitroPlugin(async () => {
|
||||
if (!import.meta.dev) return
|
||||
if (!import.meta.dev)
|
||||
return
|
||||
|
||||
onHubReady(async () => {
|
||||
await migrate(useDB(), { migrationsFolder: 'server/database/migrations' })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { drizzle } from 'drizzle-orm/d1'
|
||||
import * as schema from '../database/schema'
|
||||
|
||||
export { sql, eq, and, or, asc, desc, sum } from 'drizzle-orm'
|
||||
export { and, asc, desc, eq, or, sql, sum } from 'drizzle-orm'
|
||||
|
||||
export const tables = schema
|
||||
|
||||
|
||||
@@ -14,42 +14,42 @@ export default <Partial<Config>>{
|
||||
'./Error.{js,ts,vue}',
|
||||
'./error.{js,ts,vue}',
|
||||
'./app.config.{js,ts}',
|
||||
'content/**/*.md'
|
||||
'content/**/*.md',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
wave: 'wave 3s infinite',
|
||||
slide: 'slide 3s infinite'
|
||||
slide: 'slide 3s infinite',
|
||||
},
|
||||
keyframes: {
|
||||
wave: {
|
||||
'0%, 50%, 100%': {
|
||||
transform: 'rotate(-12deg)'
|
||||
transform: 'rotate(-12deg)',
|
||||
},
|
||||
'25%, 75%': {
|
||||
transform: 'rotate(12deg) scale(1.5)'
|
||||
}
|
||||
transform: 'rotate(12deg) scale(1.5)',
|
||||
},
|
||||
},
|
||||
slide: {
|
||||
'0%, 100%': {
|
||||
transform: 'translateX(0) translateY(0)'
|
||||
transform: 'translateX(0) translateY(0)',
|
||||
},
|
||||
'20%': {
|
||||
transform: 'translateX(10px)'
|
||||
transform: 'translateX(10px)',
|
||||
},
|
||||
'40%': {
|
||||
transform: 'translateY(-10px) translateX(10px)'
|
||||
transform: 'translateY(-10px) translateX(10px)',
|
||||
},
|
||||
'60%': {
|
||||
transform: 'translateY(10px) translateX(-10px)'
|
||||
transform: 'translateY(10px) translateX(-10px)',
|
||||
},
|
||||
'80%': {
|
||||
transform: 'translateY(-10px)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transform: 'translateY(-10px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [typography]
|
||||
plugins: [typography],
|
||||
}
|
||||
|
||||
52
types.ts
@@ -1,3 +1,5 @@
|
||||
import type { BadgeColor } from '#ui/types'
|
||||
|
||||
interface WakatimeData {
|
||||
name: string
|
||||
percent: number
|
||||
@@ -41,7 +43,51 @@ export interface Activity {
|
||||
}
|
||||
|
||||
export const IDEs = [
|
||||
{ name: 'Visual Studio Code', icon: 'i-logos-visual-studio-code' },
|
||||
{ name: 'IntelliJ IDEA Ultimate', icon: 'i-logos-intellij-idea' },
|
||||
{ name: 'WebStorm', icon: 'i-logos-webstorm' }
|
||||
{ name: 'Visual Studio Code', icon: 'visual-studio-code' },
|
||||
{ name: 'IntelliJ IDEA Ultimate', icon: 'intellij-idea' },
|
||||
{ name: 'WebStorm', icon: 'webstorm' },
|
||||
]
|
||||
|
||||
export interface Tag {
|
||||
label: string
|
||||
icon: string
|
||||
color: BadgeColor
|
||||
}
|
||||
|
||||
export const TAGS = [
|
||||
{
|
||||
label: 'Article',
|
||||
icon: 'i-ph-pencil-line-duotone',
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
label: 'Project',
|
||||
icon: 'i-ph-briefcase-duotone',
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
label: 'R',
|
||||
icon: 'i-vscode-icons-file-type-r',
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
label: 'ML',
|
||||
icon: 'i-ph-brain-duotone',
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
label: 'Data',
|
||||
icon: 'i-ph-database-duotone',
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
label: 'Web',
|
||||
icon: 'i-ph-globe-duotone',
|
||||
color: 'cyan',
|
||||
},
|
||||
{
|
||||
label: 'Python',
|
||||
icon: 'i-vscode-icons-file-type-python',
|
||||
color: 'amber',
|
||||
},
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
||||