Compare commits

18 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
1b2eb78643 Refactor: add helper functions for year formatting and improve code clarity
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 23:27:55 +00:00
copilot-swe-agent[bot]
c99736b775 Fix potential runtime errors with null/undefined date values
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 23:24:48 +00:00
copilot-swe-agent[bot]
83aca9b710 Fix linting issues: add proper TypeScript types and remove trailing spaces
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 23:23:16 +00:00
copilot-swe-agent[bot]
f422af5de3 Add VueFlow ecosystem page with education, experience, projects, and skills nodes
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 23:21:10 +00:00
copilot-swe-agent[bot]
edea9afe48 Initial plan 2025-12-24 23:11:57 +00:00
85e30d0f5f fix: ajouter une nouvelle ligne pour améliorer la lisibilité dans dropout-reduces-underfitting.md 2025-12-24 23:25:32 +01:00
cf261a108e Merge pull request #16 from ArthurDanjou/copilot/homogenize-projects-content
Homogenize project markdown files to consistent format
2025-12-24 23:24:42 +01:00
fc01e8f987 fix: corriger la syntaxe dans les fichiers de configuration pour assurer la conformité 2025-12-24 23:20:26 +01:00
copilot-swe-agent[bot]
17f3d06acb Move Detailed Report section before Authors section in dropout-reduces-underfitting.md
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 22:13:41 +00:00
copilot-swe-agent[bot]
2e73c26b12 Remove npm lock file and add to gitignore
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 22:07:14 +00:00
copilot-swe-agent[bot]
dff1ce45a0 Complete homogenization of all project files
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 22:06:31 +00:00
copilot-swe-agent[bot]
f87cadc96f Homogenize all project files to match base format
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 22:03:30 +00:00
copilot-swe-agent[bot]
6f16bc4697 Initial plan 2025-12-24 21:59:17 +00:00
719ee024d6 feat: mettre à jour les projets avec des modifications de balisage et ajouter de nouveaux fichiers PDF 2025-12-24 22:46:33 +01:00
82d2ed8dba fix: mettre à jour l'icône du site en remplaçant le favicon par une version WebP 2025-12-24 20:07:03 +01:00
bac370e465 fix: supprimer l'affichage des statistiques dans le composant Stats et mettre à jour la version de nuxt-studio 2025-12-24 19:54:55 +01:00
b8332b13af feat: ajouter des tailles et des améliorations de style aux composants Skills, Stats, StatusPage, Education et Experiences 2025-12-24 19:52:12 +01:00
91422148dd feat: ajouter des composants de statut en direct et mettre à jour la configuration de l'éducation 2025-12-24 19:34:29 +01:00
50 changed files with 883 additions and 470 deletions

View File

@@ -1,5 +1,12 @@
NUXT_PUBLIC_I18N_BASE_URL=
NUXT_API_URL=
STUDIO_GITHUB_CLIENT_ID=
STUDIO_GITHUB_CLIENT_SECRET=
STUDIO_GITHUB_CLIENT_SECRET=
NUXT_DISCORD_USER_ID=
NUXT_WAKATIME_CODING=
NUXT_WAKATIME_EDITORS=
NUXT_WAKATIME_LANGUAGES=
NUXT_WAKATIME_OS=
NUXT_WAKATIME_USER_ID=
NUXT_STATUS_PAGE=

View File

@@ -3,7 +3,7 @@ name: Deploy to Cloudflare Workers
on:
push:
branches:
- '**'
- '**'
permissions:
contents: read
@@ -36,24 +36,32 @@ jobs:
run: bun run build
env:
NUXT_DISCORD_USER_ID: ${{ secrets.NUXT_DISCORD_USER_ID }}
NUXT_WAKATIME_CODING: ${{ secrets.NUXT_WAKATIME_CODING }}
NUXT_WAKATIME_EDITORS: ${{ secrets.NUXT_WAKATIME_EDITORS }}
NUXT_WAKATIME_LANGUAGES: ${{ secrets.NUXT_WAKATIME_LANGUAGES }}
NUXT_WAKATIME_OS: ${{ secrets.NUXT_WAKATIME_OS }}
NUXT_WAKATIME_USER_ID: ${{ secrets.NUXT_WAKATIME_USER_ID }}
NUXT_STATUS_PAGE: ${{ secrets.NUXT_STATUS_PAGE }}
STUDIO_GITHUB_CLIENT_ID: ${{ secrets.STUDIO_GITHUB_CLIENT_ID }}
STUDIO_GITHUB_CLIENT_SECRET: ${{ secrets.STUDIO_GITHUB_CLIENT_SECRET }}
- name: Determine Deployment Target
id: target
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "env_flag=" >> $GITHUB_OUTPUT
echo "env_name=Production" >> $GITHUB_OUTPUT
else
echo "env_flag=--env preview" >> $GITHUB_OUTPUT
echo "env_name=Preview" >> $GITHUB_OUTPUT
fi
- name: Publish to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
command: deploy ${{ steps.target.outputs.env_flag }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
@@ -62,13 +70,14 @@ jobs:
- name: Discord Notification
uses: sarisia/actions-status-discord@v1
if: always() # S'exécute même si le build plante
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
title: "Déploiement Portfolio"
title: "Déploiement Portfolio (${{ steps.target.outputs.env_name }})"
description: |
Build terminé sur la branche **${{ github.ref_name }}**.
Environnement cible : **${{ steps.target.outputs.env_name }}**.
Commit: `${{ github.sha }}` par ${{ github.actor }}.
nofail: false
nodetail: false

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ dist
# Node dependencies
node_modules
package-lock.json
# Logs
logs

View File

@@ -8,7 +8,7 @@ useHead({
separator: '•'
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
{ rel: 'icon', type: 'image/webp', href: '/favicon.webp' }
]
})
</script>

View File

@@ -0,0 +1,3 @@
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import '@vue-flow/controls/dist/style.css';

View File

@@ -1,125 +0,0 @@
<script lang="ts" setup>
import type { Activity } from '~~/types'
import { IDEs } from '~~/types'
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch('/api/activity'), { lazy: true })
useIntervalFn(refresh, 5000)
const currentSession = computed(() => {
const list = activity.value?.data.activities ?? []
const ideActivity = list.find(a => IDEs.some(ide => ide.name === a.name))
if (!ideActivity) return null
const name = ideActivity.assets?.small_text === 'Cursor' ? 'Cursor' : ideActivity.name
const isIdling = ideActivity.details?.includes('Idling') || (!ideActivity.state?.toLowerCase().includes('editing') && name !== 'Visual Studio Code')
const rawProject = ideActivity.details ? ideActivity.details.replace('Workspace:', '').replace('Editing', '').trim() : 'Unknown Context'
const project = rawProject.charAt(0).toUpperCase() + rawProject.slice(1)
const file = ideActivity.state?.replace('Editing', '').trim() || 'No active file'
return {
name,
project,
file,
isIdling,
startTime: ideActivity.timestamps?.start,
icon: IDEs.find(ide => ide.name === name)?.icon ?? 'i-ph-code-duotone'
}
})
const timeAgo = useTimeAgo(computed(() => currentSession.value?.startTime ?? new Date()))
const statusColor = computed(() => {
if (!currentSession.value) return 'red'
return currentSession.value.isIdling ? 'orange' : 'green'
})
const statusLabel = computed(() => {
if (!currentSession.value) return 'System Offline'
if (currentSession.value.isIdling) return 'System Idling'
return 'Active Development'
})
</script>
<template>
<ClientOnly>
<div class="w-full mb-4">
<UCard v-if="activity">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<div class="relative flex h-3 w-3">
<span
v-if="statusColor === 'green'"
class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 bg-green-400"
/>
<span
class="relative inline-flex rounded-full h-3 w-3 transition-colors duration-300"
:class="{
'bg-green-500': statusColor === 'green',
'bg-orange-500': statusColor === 'orange',
'bg-red-500': statusColor === 'red'
}"
/>
</div>
<span class="text-xs font-bold uppercase tracking-wider text-neutral-500 dark:text-neutral-400">
{{ statusLabel }}
</span>
</div>
<UIcon
v-if="currentSession"
:name="currentSession.icon"
class="w-5 h-5 opacity-80"
/>
<UIcon
v-else
name="i-ph-power-duotone"
class="w-5 h-5 text-red-400 opacity-80"
/>
</div>
<div
v-if="currentSession"
class="space-y-1 pl-6 border-l-2 border-neutral-100 dark:border-neutral-800 ml-1.5"
>
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
<h3 class="font-semibold text-neutral-900 dark:text-white truncate">
{{ currentSession.project }}
</h3>
<span class="hidden sm:inline text-neutral-400 text-xs"></span>
<span class="text-sm text-neutral-500 dark:text-neutral-400 truncate">
{{ currentSession.file }}
</span>
</div>
<div class="flex items-center gap-2 text-xs text-neutral-400 mt-1">
<UIcon
name="i-ph-timer-duotone"
class="w-4 h-4"
/>
<span>Started {{ timeAgo }}</span>
</div>
</div>
<div
v-else
class="text-sm text-neutral-500 dark:text-neutral-400 flex items-center gap-2 pl-6 border-l-2 border-red-100 dark:border-red-900/30 ml-1.5"
>
<p>Telemetry disconnected. Research in progress.</p>
</div>
</UCard>
<UCard v-else>
<div class="flex items-center gap-3">
<USkeleton class="h-3 w-3 rounded-full" /> <div class="space-y-2 flex-1">
<USkeleton class="h-4 w-1/3" />
<USkeleton class="h-3 w-2/3" />
</div>
</div>
</UCard>
</div>
</ClientOnly>
</template>

View File

@@ -1,61 +0,0 @@
<script lang="ts" setup>
const { education } = await useContent()
const formatDate = (iso?: string) => {
if (!iso) return 'Present'
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return iso
return useDateFormat(d, 'MMM YYYY', { locales: 'en-US' }).value
}
</script>
<template>
<section
v-if="education && education.length"
class="my-8 space-y-6"
aria-labelledby="education-title"
>
<h2
id="education-title"
class="sr-only"
>
Education
</h2>
<div class="grid gap-4 grid-cols-1">
<UCard
v-for="item in education"
:key="item.id"
variant="outline"
color="neutral"
>
<div>
<h3 class="text-lg md:text-xl font-semibold tracking-tight">
{{ item.degree ?? item.title }}
</h3>
<p class="text-sm text-neutral-600 dark:text-neutral-400 mb-2">
{{ item.institution }}
</p>
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex flex-wrap items-center gap-2">
<span class="font-medium">Dates:</span>
<span>{{ formatDate(item.startDate) }} {{ formatDate(item.endDate) }}</span>
<span
v-if="item.duration"
class="text-neutral-500"
>({{ item.duration }})</span>
</p>
<p
class="text-sm text-neutral-700 dark:text-neutral-300 mt-1"
>
<span class="font-medium">Location:</span> {{ item.location }}
</p>
<p
class="text-sm text-neutral-700 dark:text-neutral-300 mt-3"
>
{{ item.description }}
</p>
</div>
</UCard>
</div>
</section>
</template>

View File

@@ -1,100 +0,0 @@
<script lang="ts" setup>
const { experiences } = await useContent()
const formatDate = (iso?: string) => {
if (!iso) return 'Present'
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return iso
return useDateFormat(d, 'MMM YYYY', { locales: 'en-US' }).value
}
</script>
<template>
<section
v-if="experiences && experiences.length"
class="my-8 space-y-6"
aria-labelledby="experiences-title"
>
<h2
id="experiences-title"
class="sr-only"
>
Experiences
</h2>
<div class="grid gap-4 grid-cols-1">
<UCard
v-for="item in experiences"
:key="item.id"
variant="outline"
color="neutral"
>
<div class="space-y-3">
<div class="flex items-start gap-3">
<div
v-if="item.emoji"
class="text-2xl leading-none"
>
{{ item.emoji }}
</div>
<div class="flex-1">
<h3 class="text-lg md:text-xl font-semibold tracking-tight">
{{ item.title }}<span
v-if="item.type"
class="text-md text-neutral-500 font-normal"
> · {{ item.type }}</span>
</h3>
<p class="text-sm text-neutral-700 dark:text-neutral-300">
<span class="font-medium mr-2">Company:</span>
<span
v-if="item.companyUrl"
class="underline decoration-neutral-400/70 underline-offset-4 hover:decoration-neutral-600 dark:hover:decoration-neutral-300"
>
<NuxtLink
:to="item.companyUrl"
target="_blank"
rel="noreferrer"
>{{ item.company }}</NuxtLink>
</span>
<span v-else>{{ item.company }}</span>
</p>
</div>
</div>
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex flex-wrap items-center gap-2">
<span class="font-medium">Dates:</span>
<span>{{ formatDate(item.startDate) }} {{ formatDate(item.endDate) }}</span>
<span
v-if="item.duration"
class="text-neutral-500"
>({{ item.duration }})</span>
</p>
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex items-center gap-2">
<span class="font-medium">Location:</span>
<span>{{ item.location }}</span>
</p>
<p class="text-sm text-neutral-700 dark:text-neutral-300">
{{ item.description }}
</p>
<div
v-if="item.tags?.length"
class="flex flex-wrap gap-2"
>
<UBadge
v-for="tag in item.tags"
:key="tag"
size="sm"
variant="soft"
color="primary"
>
{{ tag }}
</UBadge>
</div>
</div>
</UCard>
</div>
</section>
</template>

View File

@@ -32,6 +32,7 @@ const { skills } = await useContent()
:key="item.name"
:icon="item.icon"
variant="soft"
size="lg"
color="primary"
class="transition-colors duration-200 hover:opacity-80"
:aria-label="item.name"

View File

@@ -0,0 +1,123 @@
<script lang="ts" setup>
import type { Activity } from '~~/types'
import { IDEs } from '~~/types'
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch('/api/activity'), { lazy: true })
useIntervalFn(refresh, 5000)
const currentSession = computed(() => {
const list = activity.value?.data.activities ?? []
const ideActivity = list.find(a => IDEs.some(ide => ide.name === a.name))
if (!ideActivity) return null
const name = ideActivity.assets?.small_text === 'Cursor' ? 'Cursor' : ideActivity.name
const isIdling = ideActivity.details?.includes('Idling') || (!ideActivity.state?.toLowerCase().includes('editing') && name !== 'Visual Studio Code')
const rawProject = ideActivity.details ? ideActivity.details.replace('Workspace:', '').replace('Editing', '').trim() : 'Unknown Context'
const project = rawProject.charAt(0).toUpperCase() + rawProject.slice(1)
const file = ideActivity.state?.replace('Editing', '').trim() || 'No active file'
return {
name,
project,
file,
isIdling,
startTime: ideActivity.timestamps?.start,
icon: IDEs.find(ide => ide.name === name)?.icon ?? 'i-ph-code-duotone'
}
})
const timeAgo = useTimeAgo(computed(() => currentSession.value?.startTime ?? new Date()))
const statusColor = computed(() => {
if (!currentSession.value) return 'red'
return currentSession.value.isIdling ? 'orange' : 'green'
})
const statusLabel = computed(() => {
if (!currentSession.value) return 'System Offline'
if (currentSession.value.isIdling) return 'System Idling'
return 'Active Development'
})
</script>
<template>
<ClientOnly>
<UCard v-if="activity">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<div class="relative flex h-3 w-3">
<span
v-if="statusColor === 'green'"
class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 bg-green-400"
/>
<span
class="relative inline-flex rounded-full h-3 w-3 transition-colors duration-300"
:class="{
'bg-green-500': statusColor === 'green',
'bg-orange-500': statusColor === 'orange',
'bg-red-500': statusColor === 'red'
}"
/>
</div>
<span class="text-xs font-bold uppercase tracking-wider text-neutral-500 dark:text-neutral-400">
{{ statusLabel }}
</span>
</div>
<UIcon
v-if="currentSession"
:name="currentSession.icon"
class="w-5 h-5 opacity-80"
/>
<UIcon
v-else
name="i-ph-power-duotone"
class="w-5 h-5 text-red-400 opacity-80"
/>
</div>
<div
v-if="currentSession"
class="space-y-1 pl-6 border-l-2 border-neutral-100 dark:border-neutral-800 ml-1.5"
>
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
<h3 class="font-semibold text-neutral-900 dark:text-white truncate">
{{ currentSession.project }}
</h3>
<span class="hidden sm:inline text-neutral-400 text-xs"></span>
<span class="text-sm text-neutral-500 dark:text-neutral-400 truncate">
{{ currentSession.file }}
</span>
</div>
<div class="flex items-center gap-2 text-xs text-neutral-400 mt-1">
<UIcon
name="i-ph-timer-duotone"
class="w-4 h-4"
/>
<span>Started {{ timeAgo }}</span>
</div>
</div>
<div
v-else
class="text-sm text-neutral-500 dark:text-neutral-400 flex items-center gap-2 pl-6 border-l-2 border-red-100 dark:border-red-900/30 ml-1.5"
>
<p>Telemetry disconnected. Research in progress.</p>
</div>
</UCard>
<UCard v-else>
<div class="flex items-center gap-3">
<USkeleton class="h-3 w-3 rounded-full" /> <div class="space-y-2 flex-1">
<USkeleton class="h-4 w-1/3" />
<USkeleton class="h-3 w-2/3" />
</div>
</div>
</UCard>
</ClientOnly>
</template>

View File

@@ -0,0 +1,192 @@
<script lang="ts" setup>
import type { StatusPageData } from '~~/types'
const { data, status } = await useAsyncData<StatusPageData>('home-status', () =>
$fetch('/api/status-page'),
{ lazy: true }
)
const isLoading = computed(() => status.value === 'pending')
const metrics = computed(() => {
if (!data.value || !data.value.publicGroupList) {
return { up: 0, down: 0, maintenance: 0, total: 0, uptime: 0 }
}
let upCount = 0
let downCount = 0
let totalCount = 0
data.value.publicGroupList.forEach((group) => {
group.monitorList.forEach((monitor) => {
totalCount++
const isUp = (monitor as unknown as { status: number }).status !== 0
if (isUp) upCount++
else downCount++
})
})
const activeMaintenances = data.value.maintenanceList?.filter(m => m.active).length || 0
const uptimePercent = totalCount > 0 ? ((upCount / totalCount) * 100).toFixed(1) : '0.0'
return {
up: upCount,
down: downCount,
maintenance: activeMaintenances,
total: totalCount,
uptime: Number(uptimePercent)
}
})
const statusState = computed(() => {
if (isLoading.value) return { color: 'neutral' as const, label: 'Checking status...' }
if (metrics.value.down > 0) return { color: 'red' as const, label: 'Service Disruption' }
if (metrics.value.maintenance > 0) return { color: 'blue' as const, label: 'Maintenance Mode' }
return { color: 'emerald' as const, label: 'All Systems Operational' }
})
</script>
<template>
<ClientOnly>
<UCard class="h-full flex flex-col overflow-hidden">
<div class="p-5 border-b border-neutral-200 dark:border-neutral-800">
<div class="flex items-center justify-between mb-2">
<h3 class="font-bold text-neutral-900 dark:text-white text-sm">
System Status
</h3>
<div class="flex items-center gap-2">
<span
v-if="!isLoading"
class="relative flex h-2.5 w-2.5"
>
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75"
:class="`bg-${statusState.color}-400`"
/>
<span
class="relative inline-flex rounded-full h-2.5 w-2.5"
:class="`bg-${statusState.color}-500`"
/>
</span>
<USkeleton
v-else
class="h-2.5 w-2.5 rounded-full"
/>
<span
v-if="!isLoading"
class="text-xs font-mono font-medium"
:class="`text-${statusState.color}-600 dark:text-${statusState.color}-400`"
>
{{ statusState.label }}
</span>
<USkeleton
v-else
class="h-4 w-24"
/>
</div>
</div>
<div class="mt-4">
<div class="flex justify-between text-xs mb-1.5">
<span class="text-neutral-500">Global Uptime</span>
<span
v-if="!isLoading"
class="font-mono font-bold text-neutral-900 dark:text-white"
>{{ metrics.uptime }}%</span>
<USkeleton
v-else
class="h-4 w-8"
/>
</div>
<UProgress
v-if="!isLoading"
v-model="metrics.uptime"
:color="statusState.color"
size="sm"
/>
<USkeleton
v-else
class="h-2 w-full rounded-full"
/>
</div>
</div>
<div class="grid grid-cols-3 divide-x divide-neutral-200 dark:divide-neutral-800 flex-1">
<div class="p-4 flex flex-col items-center justify-center text-center group">
<span class="text-[10px] text-neutral-400 font-bold uppercase tracking-wider mb-1">Operational</span>
<div
v-if="!isLoading"
class="flex items-center gap-1.5 text-emerald-500"
>
<UIcon
name="i-ph-check-circle-duotone"
class="w-5 h-5"
/>
<span class="text-xl font-bold">{{ metrics.up }}</span>
</div>
<USkeleton
v-else
class="h-6 w-8 mt-1"
/>
</div>
<div class="p-4 flex flex-col items-center justify-center text-center">
<span class="text-[10px] text-neutral-400 font-bold uppercase tracking-wider mb-1">Down</span>
<div
v-if="!isLoading"
class="flex items-center gap-1.5"
:class="metrics.down > 0 ? 'text-red-500' : 'text-neutral-400'"
>
<UIcon
name="i-ph-warning-circle-duotone"
class="w-5 h-5"
/>
<span class="text-xl font-bold">{{ metrics.down }}</span>
</div>
<USkeleton
v-else
class="h-6 w-8 mt-1"
/>
</div>
<div class="p-4 flex flex-col items-center justify-center text-center">
<span class="text-[10px] text-neutral-400 font-bold uppercase tracking-wider mb-1">Maint.</span>
<div
v-if="!isLoading"
class="flex items-center gap-1.5"
:class="metrics.maintenance > 0 ? 'text-blue-500' : 'text-neutral-400'"
>
<UIcon
name="i-ph-wrench-duotone"
class="w-5 h-5"
/>
<span class="text-xl font-bold">{{ metrics.maintenance }}</span>
</div>
<USkeleton
v-else
class="h-6 w-8 mt-1"
/>
</div>
</div>
<div class="p-2 text-center border-t border-neutral-200 dark:border-neutral-800 mt-auto">
<UButton
to="https://go.arthurdanjou.fr/status"
target="_blank"
rel="noopener noreferrer"
variant="link"
color="neutral"
size="xs"
:padded="false"
class="text-xs hover:text-primary-500"
>
View detailed report
</UButton>
</div>
</UCard>
</ClientOnly>
</template>

View File

@@ -30,7 +30,7 @@ const items = computed<TimelineItem[]>(() => {
title: item.title || 'Degree',
description: item.institution || '',
date: formatDate(item.startDate, item.endDate, item.duration),
icon: item.icon || 'i-ph-graduation-cap-duotone' // Context-aware default icon
icon: item.icon || 'i-ph-graduation-cap-duotone'
}))
})
</script>
@@ -41,8 +41,8 @@ const items = computed<TimelineItem[]>(() => {
<UTimeline
:orientation="orientation"
:items="items"
:default-value="items.length"
active-color="primary"
:default-value="2"
size="lg"
color="neutral"
class="w-full max-w-5xl"
/>

View File

@@ -42,7 +42,7 @@ const items = computed<TimelineItem[]>(() => {
:orientation="orientation"
:items="items"
:default-value="items.length"
active-color="primary"
size="lg"
color="neutral"
class="w-full max-w-5xl"
/>

View File

@@ -3,7 +3,7 @@ export function useProjectColors() {
'Active': 'blue',
'Completed': 'green',
'Archived': 'neutral',
'In Progress': 'amber'
'In progress': 'amber'
}
const typeColors: Record<string, string> = {

View File

@@ -1,11 +1,335 @@
<script lang="ts" setup>
import { VueFlow, useVueFlow, Position } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import type { Node, Edge } from '@vue-flow/core'
useSeoMeta({
title: 'Ecosystem',
description: 'Explore my educational background, professional experiences, projects, and technical skills in an interactive visual flow.',
ogTitle: 'Arthur Danjou • Ecosystem',
ogDescription: 'Explore my educational background, professional experiences, projects, and technical skills in an interactive visual flow.'
})
// Types for the data
interface EducationItem {
degree: string
institution: string
startDate: string
endDate: string
}
interface ExperienceItem {
title: string
company: string
startDate: string
endDate: string
}
interface ProjectItem {
title: string
type: string
}
interface SkillCategory {
name: string
items: Array<{ name: string, icon: string }>
}
// Fetch data from APIs
const { data: education } = await useFetch<EducationItem[]>('/api/education')
const { data: experiences } = await useFetch<ExperienceItem[]>('/api/experiences')
const { data: projects } = await useFetch<ProjectItem[]>('/api/projects')
const { data: skills } = await useFetch<SkillCategory[]>('/api/skills')
// Helper function to generate node positions
const generatePosition = (index: number, total: number, column: number, offset = 400) => {
const spacing = 180
const columnWidth = 350
const verticalSpacing = total > 1 ? spacing : 0
const startY = offset - ((total - 1) * verticalSpacing) / 2
return {
x: column * columnWidth + 50,
y: startY + index * verticalSpacing
}
}
// Helper function to format date range
const formatYearRange = (startDate?: string, endDate?: string): string => {
const start = startDate?.substring(0, 4) || '?'
const end = endDate?.substring(0, 4) || 'Present'
return `${start}-${end}`
}
// Create center node
const centerNode: Node = {
id: 'center',
type: 'default',
position: { x: 750, y: 400 },
label: 'Arthur Danjou',
data: { label: 'Arthur Danjou' },
style: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
border: '2px solid #667eea',
borderRadius: '12px',
padding: '20px',
fontSize: '18px',
fontWeight: 'bold',
width: '180px',
height: '80px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
sourcePosition: Position.Right,
targetPosition: Position.Left
}
// Create education nodes
const educationNodes: Node[] = (education.value || []).map((item: EducationItem, index: number) => ({
id: `education-${index}`,
type: 'default',
position: generatePosition(index, education.value?.length || 0, 0),
label: `${item.degree}`,
data: {
label: item.degree,
subtitle: item.institution,
years: formatYearRange(item.startDate, item.endDate)
},
style: {
background: '#3b82f6',
color: '#fff',
border: '2px solid #2563eb',
borderRadius: '8px',
padding: '10px',
fontSize: '11px',
width: '160px',
minHeight: '70px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
sourcePosition: Position.Right,
targetPosition: Position.Left
}))
// Create experience nodes
const experienceNodes: Node[] = (experiences.value || []).map((item: ExperienceItem, index: number) => ({
id: `experience-${index}`,
type: 'default',
position: generatePosition(index, experiences.value?.length || 0, 1),
label: item.title,
data: {
label: item.title,
subtitle: item.company,
years: formatYearRange(item.startDate, item.endDate)
},
style: {
background: '#10b981',
color: '#fff',
border: '2px solid #059669',
borderRadius: '8px',
padding: '10px',
fontSize: '11px',
width: '160px',
minHeight: '70px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
sourcePosition: Position.Right,
targetPosition: Position.Left
}))
// Create project nodes
const projectNodes: Node[] = (projects.value || []).slice(0, 8).map((item: ProjectItem, index: number) => ({
id: `project-${index}`,
type: 'default',
position: generatePosition(index, Math.min(8, projects.value?.length || 0), 3),
label: item.title,
data: {
label: item.title,
subtitle: item.type
},
style: {
background: '#f59e0b',
color: '#fff',
border: '2px solid #d97706',
borderRadius: '8px',
padding: '10px',
fontSize: '11px',
width: '160px',
minHeight: '60px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
sourcePosition: Position.Right,
targetPosition: Position.Left
}))
// Create skill category nodes
const skillNodes: Node[] = (skills.value || []).map((category: SkillCategory, index: number) => ({
id: `skill-${index}`,
type: 'default',
position: generatePosition(index, skills.value?.length || 0, 4),
label: category.name,
data: {
label: category.name,
count: `${category.items?.length || 0} skills`
},
style: {
background: '#8b5cf6',
color: '#fff',
border: '2px solid #7c3aed',
borderRadius: '8px',
padding: '10px',
fontSize: '11px',
width: '160px',
minHeight: '60px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
sourcePosition: Position.Right,
targetPosition: Position.Left
}))
// Combine all nodes
const nodes = ref<Node[]>([
centerNode,
...educationNodes,
...experienceNodes,
...projectNodes,
...skillNodes
])
// Create edges connecting nodes to center
const edges = ref<Edge[]>([
// Connect education to center
...educationNodes.map((node, index) => ({
id: `e-education-${index}`,
source: node.id,
target: 'center',
type: 'smoothstep',
animated: true,
style: { stroke: '#3b82f6', strokeWidth: 2 }
})),
// Connect experiences to center
...experienceNodes.map((node, index) => ({
id: `e-experience-${index}`,
source: node.id,
target: 'center',
type: 'smoothstep',
animated: true,
style: { stroke: '#10b981', strokeWidth: 2 }
})),
// Connect projects to center
...projectNodes.map((node, index) => ({
id: `e-project-${index}`,
source: 'center',
target: node.id,
type: 'smoothstep',
animated: true,
style: { stroke: '#f59e0b', strokeWidth: 2 }
})),
// Connect skills to center
...skillNodes.map((node, index) => ({
id: `e-skill-${index}`,
source: 'center',
target: node.id,
type: 'smoothstep',
animated: true,
style: { stroke: '#8b5cf6', strokeWidth: 2 }
}))
])
// Initialize VueFlow
const { fitView } = useVueFlow()
// Delay for DOM readiness before fitting view
const FIT_VIEW_DELAY_MS = 100
onMounted(() => {
nextTick(() => {
setTimeout(() => {
fitView({ padding: 0.15, duration: 800 })
}, FIT_VIEW_DELAY_MS)
})
})
</script>
<template>
<div />
<div class="w-full h-[calc(100vh-4rem)]">
<div class="mb-4">
<h1 class="text-3xl font-bold">
Ecosystem
</h1>
<p class="text-gray-600 dark:text-gray-400 mt-2">
Interactive visualization of my educational journey, professional experiences, projects, and skills
</p>
</div>
<div class="w-full h-[calc(100%-5rem)] border border-gray-200 dark:border-gray-800 rounded-lg overflow-hidden">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:default-zoom="0.8"
:min-zoom="0.1"
:max-zoom="2"
class="vue-flow-basic"
>
<Background
pattern-color="#aaa"
:gap="16"
variant="dots"
/>
<Controls />
</VueFlow>
</div>
</div>
</template>
<style scoped>
.vue-flow-basic {
background-color: var(--ui-bg);
}
:deep(.vue-flow__node) {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: transform 0.2s, box-shadow 0.2s;
}
:deep(.vue-flow__node:hover) {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
:deep(.vue-flow__edge-path) {
stroke-width: 2;
}
:deep(.vue-flow__controls) {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
:deep(.vue-flow__controls-button) {
background-color: var(--ui-bg);
border: 1px solid var(--ui-border);
border-radius: 0.375rem;
color: var(--ui-text);
transition: all 0.2s;
}
:deep(.vue-flow__controls-button:hover) {
background-color: var(--ui-bg-elevated);
transform: scale(1.05);
}
</style>

View File

@@ -17,7 +17,7 @@
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
"nuxt": "4.2.2",
"nuxt-studio": "1.0.0-alpha.4",
"nuxt-studio": "1.0.0-beta.3",
"vue": "3.5.26",
"vue-router": "4.6.4",
"zod": "^4.2.1",
@@ -239,7 +239,7 @@
"@iconify-json/logos": ["@iconify-json/logos@1.2.10", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-qxaXKJ6fu8jzTMPQdHtNxlfx6tBQ0jXRbHZIYy5Ilh8Lx9US9FsAdzZWUR8MXV8PnWTKGDFO4ZZee9VwerCyMA=="],
"@iconify-json/lucide": ["@iconify-json/lucide@1.2.73", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-++HFkqDNu4jqG5+vYT+OcVj9OiuPCw9wQuh8G5QWQnBRSJ9eKwSStiU8ORgOoK07xJsm/0VIHySMniXUUXP9Gw=="],
"@iconify-json/lucide": ["@iconify-json/lucide@1.2.82", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-fHZWegspOZonl5GNTvOkHsjnTMdSslFh3EzpzUtRyLxO8bOonqk2OTU3hCl0k4VXzViMjqpRK3X1sotnuBXkFA=="],
"@iconify-json/ph": ["@iconify-json/ph@1.2.2", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-PgkEZNtqa8hBGjHXQa4pMwZa93hmfu8FUSjs/nv4oUU6yLsgv+gh9nu28Kqi8Fz9CCVu4hj1MZs9/60J57IzFw=="],
@@ -1917,7 +1917,7 @@
"nuxt-component-meta": ["nuxt-component-meta@0.16.0", "", { "dependencies": { "@nuxt/kit": "^4.2.1", "citty": "^0.1.6", "json-schema-to-zod": "^2.7.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "scule": "^1.3.0", "typescript": "^5.9.3", "ufo": "^1.6.1", "vue-component-meta": "^3.1.5" }, "bin": { "nuxt-component-meta": "bin/nuxt-component-meta.mjs" } }, "sha512-mxsLl+gcF930dM4ozdxskGKEpldJn/fACR18uXrMDvvwxM+rMZW4tzuRMEuxhoyEXtxPLdOLP52wrS6UzBSx6Q=="],
"nuxt-studio": ["nuxt-studio@1.0.0-alpha.4", "", { "dependencies": { "@iconify-json/lucide": "^1.2.72", "@nuxtjs/mdc": "^0.18.3", "@vueuse/core": "^13.9.0", "defu": "^6.1.4", "destr": "^2.0.5", "js-yaml": "^4.1.1", "minimatch": "^10.1.1", "nuxt-component-meta": "^0.15.0", "shiki": "^3.19.0", "unstorage": "1.17.1" } }, "sha512-ggAXJaolRmRCU8lRO4wcxwZDFTHivw8YAhKDcAVr6YdmBL8HLplceNd/sAOlnqUDAPSy6YJNjDhG7wP9Kdaqvw=="],
"nuxt-studio": ["nuxt-studio@1.0.0-beta.3", "", { "dependencies": { "@iconify-json/lucide": "^1.2.82", "@nuxtjs/mdc": "^0.19.1", "@vueuse/core": "^14.1.0", "defu": "^6.1.4", "destr": "^2.0.5", "js-yaml": "^4.1.1", "minimatch": "^10.1.1", "nuxt-component-meta": "^0.16.0", "remark-mdc": "^3.9.0", "shiki": "^3.20.0", "unstorage": "1.17.3" } }, "sha512-Rcx7sfsQ0CeHY0rtqLKSgm2pRPF0mknJ9UVXyKWHkcnAFnhouAM8r6SoPLVHRdd/cmxdM9uTrNTfz6CNbk7vtQ=="],
"nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="],
@@ -2911,16 +2911,8 @@
"nuxt/vue-router": ["vue-router@4.6.3", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg=="],
"nuxt-studio/@nuxtjs/mdc": ["@nuxtjs/mdc@0.18.4", "", { "dependencies": { "@nuxt/kit": "^4.2.1", "@shikijs/core": "^3.15.0", "@shikijs/langs": "^3.15.0", "@shikijs/themes": "^3.15.0", "@shikijs/transformers": "^3.15.0", "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "@vue/compiler-core": "^3.5.24", "consola": "^3.4.2", "debug": "^4.4.3", "defu": "^6.1.4", "destr": "^2.0.5", "detab": "^3.0.2", "github-slugger": "^2.0.0", "hast-util-format": "^1.1.0", "hast-util-to-mdast": "^10.1.2", "hast-util-to-string": "^3.0.1", "mdast-util-to-hast": "^13.2.0", "micromark-util-sanitize-uri": "^2.0.1", "parse5": "^8.0.0", "pathe": "^2.0.3", "property-information": "^7.1.0", "rehype-external-links": "^3.0.0", "rehype-minify-whitespace": "^6.0.2", "rehype-raw": "^7.0.0", "rehype-remark": "^10.0.1", "rehype-slug": "^6.0.0", "rehype-sort-attribute-values": "^5.0.1", "rehype-sort-attributes": "^5.0.1", "remark-emoji": "^5.0.2", "remark-gfm": "^4.0.1", "remark-mdc": "^3.8.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-stringify": "^11.0.0", "scule": "^1.3.0", "shiki": "^3.15.0", "ufo": "^1.6.1", "unified": "^11.0.5", "unist-builder": "^4.0.0", "unist-util-visit": "^5.0.0", "unwasm": "^0.5.0", "vfile": "^6.0.3" } }, "sha512-lM4R0Mbbhw5h5Fwj7LqGiw6eanqjjPkzi+9FaXfn1BdmfbW8GlR2quLIiBXTbw0wUrWYyOWoc5FGIE/gpZUTjQ=="],
"nuxt-studio/@vueuse/core": ["@vueuse/core@13.9.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "13.9.0", "@vueuse/shared": "13.9.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA=="],
"nuxt-studio/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
"nuxt-studio/nuxt-component-meta": ["nuxt-component-meta@0.15.0", "", { "dependencies": { "@nuxt/kit": "^4.2.1", "citty": "^0.1.6", "json-schema-to-zod": "^2.7.0", "mlly": "^1.8.0", "ohash": "^2.0.11", "scule": "^1.3.0", "typescript": "^5.9.3", "ufo": "^1.6.1", "vue-component-meta": "^3.1.5" }, "bin": { "nuxt-component-meta": "bin/nuxt-component-meta.mjs" } }, "sha512-IW8xzHQdpmfgAyDYw4NPVQLnHAWrNltgJUD3Bww5Miogy8dd/dDdEKexCzFvI+gSa/uAe52zDRcIx9wwavmAmg=="],
"nuxt-studio/unstorage": ["unstorage@1.17.1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
@@ -3443,20 +3435,6 @@
"nitropack/unwasm/unplugin": ["unplugin@2.3.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw=="],
"nuxt-studio/@nuxtjs/mdc/@shikijs/transformers": ["@shikijs/transformers@3.15.0", "", { "dependencies": { "@shikijs/core": "3.15.0", "@shikijs/types": "3.15.0" } }, "sha512-Hmwip5ovvSkg+Kc41JTvSHHVfCYF+C8Cp1omb5AJj4Xvd+y9IXz2rKJwmFRGsuN0vpHxywcXJ1+Y4B9S7EG1/A=="],
"nuxt-studio/@nuxtjs/mdc/@vue/compiler-core": ["@vue/compiler-core@3.5.24", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.24", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig=="],
"nuxt-studio/@nuxtjs/mdc/mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
"nuxt-studio/@nuxtjs/mdc/remark-mdc": ["remark-mdc@3.8.1", "", { "dependencies": { "@types/mdast": "^4.0.4", "@types/unist": "^3.0.3", "flat": "^6.0.1", "mdast-util-from-markdown": "^2.0.2", "mdast-util-to-markdown": "^2.1.2", "micromark": "^4.0.2", "micromark-core-commonmark": "^2.0.3", "micromark-factory-space": "^2.0.1", "micromark-factory-whitespace": "^2.0.1", "micromark-util-character": "^2.1.1", "micromark-util-types": "^2.0.2", "parse-entities": "^4.0.2", "scule": "^1.3.0", "stringify-entities": "^4.0.4", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "yaml": "^2.8.1" } }, "sha512-TGFY61OhgziAITAomenbw4THQvEHC7MxZI1kO1YL/VuWQTHZ0RG20G6GGATIFeGnq65IUe7dngiQVcVIeFdB/g=="],
"nuxt-studio/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@13.9.0", "", {}, "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg=="],
"nuxt-studio/@vueuse/core/@vueuse/shared": ["@vueuse/shared@13.9.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g=="],
"nuxt-studio/unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"nuxt/c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"nuxt/unctx/unplugin": ["unplugin@2.3.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw=="],
@@ -3625,20 +3603,6 @@
"clipboardy/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"nuxt-studio/@nuxtjs/mdc/@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.15.0", "", { "dependencies": { "@shikijs/types": "3.15.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg=="],
"nuxt-studio/@nuxtjs/mdc/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.15.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw=="],
"nuxt-studio/@nuxtjs/mdc/@vue/compiler-core/@vue/shared": ["@vue/shared@3.5.24", "", {}, "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A=="],
"nuxt-studio/@nuxtjs/mdc/@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"nuxt-studio/@nuxtjs/mdc/@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"nuxt-studio/@nuxtjs/mdc/remark-mdc/yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
"nuxt-studio/unstorage/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"nuxt/c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"nuxt/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.25", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.25", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw=="],

View File

@@ -62,7 +62,6 @@ export const collections = {
source: 'education/*.md',
schema: z.object({
title: z.string(),
type: z.string().optional(),
degree: z.string().optional(),
institution: z.string(),
startDate: z.string(),

View File

@@ -2,7 +2,7 @@
"body": [
{
"id": "personal-email",
"name": "Email Personnel",
"name": "Personal Email",
"category": "communication",
"icon": "i-ph-envelope-simple-duotone",
"value": "https://go.arthurdanjou.fr/mail-perso",
@@ -10,7 +10,7 @@
},
{
"id": "professional-email",
"name": "Email Professionnel",
"name": "Professional Email",
"category": "communication",
"icon": "i-ph-envelope-simple-duotone",
"value": "https://go.arthurdanjou.fr/mail-pro",
@@ -51,7 +51,7 @@
},
{
"id": "personal-website",
"name": "Site Personnel",
"name": "Portfolio",
"category": "web",
"icon": "i-ph:globe-duotone",
"value": "https://arthurdanjou.fr",
@@ -59,7 +59,7 @@
},
{
"id": "status-page",
"name": "Statut des Services",
"name": "Status Page",
"category": "infrastructure",
"icon": "i-ph:fire-duotone",
"value": "https://go.arthurdanjou.fr/status",

View File

@@ -1,6 +1,6 @@
---
title: Bachelor's Degree in Mathematics
type: Bachelor
degree: Bachelor
institution: Paris-Saclay University
location: Paris, France
startDate: 2021-09

View File

@@ -0,0 +1,16 @@
---
title: "PhD Candidate: AI Safety & Mathematical Robustness"
degree: Doctorate
institution: Academic Labs
location: Paris / International
startDate: 2026-10
endDate: null
duration: 3 years
description: I am actively seeking a PhD position starting in Fall 2026. My research interest lies at the intersection of Applied Mathematics and Deep Learning, specifically focusing on AI Safety, Adversarial Robustness, and Formal Verification. I aim to contribute to developing mathematically grounded methods to ensure the reliability and alignment of modern AI systems.
tags:
- AI Safety
- Robustness
- Formal Verification
- Applied Mathematics
icon: i-ph-student-duotone
---

View File

@@ -1,6 +1,6 @@
---
title: Master's Degree in Applied Mathematics (Year 1)
type: Master
degree: Master
institution: Paris Dauphine-PSL University
location: Paris, France
startDate: 2024-09

View File

@@ -1,6 +1,6 @@
---
title: Master's Degree in Applied Mathematics (Year 2)
type: Master
degree: Master
institution: Paris Dauphine-PSL University
location: Paris, France
startDate: 2025-09

View File

@@ -51,12 +51,14 @@ Mathematical rigor is the cornerstone of Safe AI. My background in :hover-text{t
Research requires discipline and transparency. Here is a real-time overview of my :hover-text{text="current environment" hover="OS, Editor, Activity"} and historical data.
::home-activity
::home-live-status-page{class="mb-4"}
::
::home-stats
::home-live-activity{class="mb-4"}
::
::home-live-stats
---
::home-quote

View File

@@ -14,16 +14,15 @@ tags:
icon: i-ph-house-duotone
---
[ArtHome](https://go.arthurdanjou.fr/arthome) is a customizable browser homepage that lets you organize all your favorite links in one place.
[**ArtHome**](https://go.arthurdanjou.fr/arthome) is a customizable browser homepage that lets you organize all your favorite links in one place.
Create categories and tabs to group your shortcuts, personalize them with icons and colors, and make the page private if you want to keep your links just for yourself. The interface is clean, responsive, and works across all modern browsers.
## 🛠️ Built with
## 🛠️ Technology Stack
- [Nuxt](https://nuxt.com): An open-source framework for building performant, full-stack web applications with Vue.
- [NuxtHub](https://hub.nuxt.com): A Cloudflare-powered platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.
- [NuxtUI](https://ui.nuxt.com): A sleek and flexible component library that helps create beautiful, responsive UIs for Nuxt applications.
- [ESLint](https://eslint.org): A linter that identifies and fixes problems in your JavaScript/TypeScript code.
- [Drizzle ORM](https://orm.drizzle.team/): A lightweight, type-safe ORM built for TypeScript, designed for simplicity and performance.
- [Zod](https://zod.dev/): A TypeScript-first schema declaration and validation library with full static type inference.
- and a lot of ❤️
- **[Nuxt](https://nuxt.com)**: An open-source framework for building performant, full-stack web applications with Vue.
- **[NuxtHub](https://hub.nuxt.com)**: A Cloudflare-powered platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.
- **[NuxtUI](https://ui.nuxt.com)**: A sleek and flexible component library that helps create beautiful, responsive UIs for Nuxt applications.
- **[ESLint](https://eslint.org)**: A linter that identifies and fixes problems in your JavaScript/TypeScript code.
- **[Drizzle ORM](https://orm.drizzle.team/)**: A lightweight, type-safe ORM built for TypeScript, designed for simplicity and performance.
- **[Zod](https://zod.dev/)**: A TypeScript-first schema declaration and validation library with full static type inference.

View File

@@ -18,8 +18,7 @@ icon: i-ph-flask-duotone
[**ArtLab**](https://go.arthurdanjou.fr/status) is my personal homelab, where I experiment with self-hosting and automation.
My homelab is a self-hosted environment where I deploy, test, and maintain personal services. Everything is securely exposed **only through a private VPN** using [Tailscale](https://tailscale.com/), ensuring encrypted, access-controlled connections across all devices.
For selected services, I also use **Cloudflare Tunnels** to enable secure external access without opening ports or exposing my public IP.
My homelab is a self-hosted environment where I deploy, test, and maintain personal services. Everything is securely exposed **only through a private VPN** using [Tailscale](https://tailscale.com/), ensuring encrypted, access-controlled connections across all devices. For selected services, I also use **Cloudflare Tunnels** to enable secure external access without opening ports or exposing my public IP.
## 🛠️ Running Services
@@ -36,7 +35,7 @@ For selected services, I also use **Cloudflare Tunnels** to enable secure extern
- **Beszel**: Self-hosted, lightweight alternative to Notion for notes and knowledge management.
- **Palmr**: Personal logging and journaling tool.
## 🖥️ Hardware
## 🖥️ Hardware Specifications
- **Beelink EQR6**: AMD Ryzen mini PC, main server host.
- **TP-Link 5-port Switch**: Network connectivity for all devices.

View File

@@ -11,7 +11,6 @@ tags:
- Nuxt
- NuxtHub
- Cloudflare Workers
- Wrangler
- TypeScript
icon: i-ph-globe-hemisphere-west-duotone
---

View File

@@ -11,16 +11,13 @@ tags:
- Python
- R
- Data Science
- Machine Learning
- Mathematics
icon: i-ph-book-duotone
---
# ArtStudies
[**ArtStudies Projects**](https://github.com/ArthurDanjou/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
[ArtStudies Projects](https://github.com/ArthurDanjou/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
The projects are organized into two main sections:
The projects are organized into three main sections:
- **L3** Third year of the Bachelor's degree in Mathematics
- **M1** First year of the Master's degree in Mathematics
- **M2** Second year of the Master's degree in Mathematics
@@ -56,20 +53,19 @@ The projects are organized into two main sections:
## 🛠️ Technologies & Tools
- [Python](https://www.python.org): A high-level, interpreted programming language, widely used for data science, machine learning, and scientific computing.
- [R](https://www.r-project.org): A statistical computing environment, perfect for data analysis and visualization.
- [Jupyter](https://jupyter.org): Interactive notebooks combining code, results, and rich text for reproducible research.
- [Pandas](https://pandas.pydata.org): A data manipulation library providing data structures and operations for manipulating numerical tables and time series.
- [NumPy](https://numpy.org): Core package for numerical computing with support for large, multi-dimensional arrays and matrices.
- [SciPy](https://www.scipy.org): A library for advanced scientific computations including optimization, integration, and signal processing.
- [Scikit-learn](https://scikit-learn.org): A robust library offering simple and efficient tools for machine learning and statistical modeling, including classification, regression, and clustering.
- [TensorFlow](https://www.tensorflow.org): A comprehensive open-source framework for building and deploying machine learning and deep learning models.
- [Keras](https://keras.io): A high-level neural networks API, running on top of TensorFlow, designed for fast experimentation.
- [Matplotlib](https://matplotlib.org): A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
- [Plotly](https://plotly.com): An interactive graphing library for creating dynamic visualizations in Python and R.
- [Seaborn](https://seaborn.pydata.org): A statistical data visualization library built on top of Matplotlib, providing a high-level interface for drawing attractive and informative graphics.
- [RMarkdown](https://rmarkdown.rstudio.com): A dynamic tool for combining code, results, and narrative into high-quality documents and presentations.
- [FactoMineR](https://factominer.free.fr/): An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
- [ggplot2](https://ggplot2.tidyverse.org): A grammar-based graphics package for creating complex and elegant visualizations in R.
- [RShiny](https://shiny.rstudio.com): A web application framework for building interactive web apps directly from R.
- and my 🧠.
- **[Python](https://www.python.org)**: A high-level, interpreted programming language, widely used for data science, machine learning, and scientific computing.
- **[R](https://www.r-project.org)**: A statistical computing environment, perfect for data analysis and visualization.
- **[Jupyter](https://jupyter.org)**: Interactive notebooks combining code, results, and rich text for reproducible research.
- **[Pandas](https://pandas.pydata.org)**: A data manipulation library providing data structures and operations for manipulating numerical tables and time series.
- **[NumPy](https://numpy.org)**: Core package for numerical computing with support for large, multi-dimensional arrays and matrices.
- **[SciPy](https://www.scipy.org)**: A library for advanced scientific computations including optimization, integration, and signal processing.
- **[Scikit-learn](https://scikit-learn.org)**: A robust library offering simple and efficient tools for machine learning and statistical modeling, including classification, regression, and clustering.
- **[TensorFlow](https://www.tensorflow.org)**: A comprehensive open-source framework for building and deploying machine learning and deep learning models.
- **[Keras](https://keras.io)**: A high-level neural networks API, running on top of TensorFlow, designed for fast experimentation.
- **[Matplotlib](https://matplotlib.org)**: A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
- **[Plotly](https://plotly.com)**: An interactive graphing library for creating dynamic visualizations in Python and R.
- **[Seaborn](https://seaborn.pydata.org)**: A statistical data visualization library built on top of Matplotlib, providing a high-level interface for drawing attractive and informative graphics.
- **[RMarkdown](https://rmarkdown.rstudio.com)**: A dynamic tool for combining code, results, and narrative into high-quality documents and presentations.
- **[FactoMineR](https://factominer.free.fr/)**: An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
- **[ggplot2](https://ggplot2.tidyverse.org)**: A grammar-based graphics package for creating complex and elegant visualizations in R.
- **[RShiny](https://shiny.rstudio.com)**: A web application framework for building interactive web apps directly from R.

View File

@@ -9,16 +9,11 @@ status: Completed
tags:
- R
- Statistics
- Data Analysis
- GLM
- Mathematics
icon: i-ph-bicycle-duotone
---
# Generalized Linear Models for Bikes Prediction
## Overview
This project was completed as part of the **Generalized Linear Models** course at Paris-Dauphine PSL University. The objective was to develop and compare statistical models to predict the number of bicycle rentals in a bike-sharing system based on various environmental and temporal characteristics.
## 📊 Project Objectives
@@ -48,10 +43,9 @@ The analysis identified critical factors influencing bike-sharing demand:
## 📚 Resources
- **Code Repository**: [GLM Bikes Code](https://go.arthurdanjou.fr/glm-bikes-code)
- **Full Report**: See embedded PDF below
You can find the code here: [GLM Bikes Code](https://go.arthurdanjou.fr/glm-bikes-code)
## 📄 Detailed Report
<iframe src="/projects/bikes-glm/Report.pdf" width="100%" height="1000px">
<iframe src="/projects/bikes-glm.pdf" width="100%" height="1000px">
</iframe>

View File

@@ -9,39 +9,41 @@ status: Completed
tags:
- Python
- Machine Learning
- Data Science
- Classification
- Healthcare
icon: i-ph-heart-half-duotone
---
The project was carried out as part of the `Statistical Learning` course at Paris-Dauphine PSL University. Its objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
This project was carried out as part of the **Statistical Learning** course at Paris-Dauphine PSL University. The objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
This project aims to develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
## 📊 Project Objectives
Develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
The dataset contains 116 observations divided into two classes:
- 1: healthy individuals (controls)
- 2: patients diagnosed with breast cancer
- **1**: healthy individuals (controls)
- **2**: patients diagnosed with breast cancer
There are 9 explanatory variables, including clinical measurements such as age, insulin levels, leptin, insulin resistance, among others.
## 🔍 Methodology
The project follows a comparative approach between several algorithms:
- Logistic Regression
- k-Nearest Neighbors (k-NN)
- Naive Bayes
- Artificial Neural Network (MLP with a 16-8-1 architecture)
Model evaluation is primarily based on the F1-score, which is more suitable in a medical context where identifying positive cases is crucial. Particular attention was paid to stratified cross-validation and to handling class imbalance, notably through the use of class weights and regularization techniques (L2, early stopping).
This project illustrates a concrete application of data science techniques to a public health issue, while implementing a rigorous methodology for supervised modeling.
## 📚 Resources
You can find the code here: [Breast Cancer Detection](https://go.arthurdanjou.fr/breast-cancer-detection-code)
<iframe src="/projects/breast-cancer/report.pdf" width="100%" height="1000px">
## 📄 Detailed Report
<iframe src="/projects/breast-cancer.pdf" width="100%" height="1000px">
</iframe>

View File

@@ -0,0 +1,27 @@
---
slug: data-visualisation
title: Data Visualisation Project
type: Academic Project
description: An interactive data visualization project built with R, R Shiny, and ggplot2 for creating dynamic, explorable visualizations.
publishedAt: 2026-01-05
readingTime: 1
status: In progress
tags:
- R
- R Shiny
- Data Visualization
- ggplot2
icon: i-ph-chart-bar-duotone
---
This project involves creating an interactive data visualization application using R and R Shiny. The goal is to develop dynamic and explorable visualizations that allow users to interact with the data in meaningful ways.
## 🛠️ Technologies & Tools
- **[R](https://www.r-project.org)**: A statistical computing environment, perfect for data analysis and visualization.
- **[R Shiny](https://shiny.rstudio.com)**: A web application framework for R that enables the creation of interactive web applications directly from R.
- **[ggplot2](https://ggplot2.tidyverse.org)**: A powerful R package for creating static and dynamic visualizations using the Grammar of Graphics.
- **[dplyr](https://dplyr.tidyverse.org)**: An R package for data manipulation, providing a consistent set of verbs to help you solve common data manipulation challenges.
- **[tidyr](https://tidyr.tidyverse.org)**: An R package for tidying data, making it easier to work with and visualize.
The project is currently in progress, and more details will be added as development continues.

View File

@@ -4,23 +4,16 @@ title: Dropout Reduces Underfitting
type: Research Project
description: TensorFlow/Keras implementation and reproduction of "Dropout Reduces Underfitting" (Liu et al., 2023). A comparative study of Early and Late Dropout strategies to optimize model convergence.
publishedAt: 2024-12-10
readingTime: 4
readingTime: 6
status: Completed
tags:
- Python
- TensorFlow
- Machine Learning
- Deep Learning
- Research
icon: i-ph-share-network-duotone
---
📉 [Dropout Reduces Underfitting](https://github.com/arthurdanjou/dropoutreducesunderfitting): Reproduction & Analysis
![TensorFlow](https://img.shields.io/badge/TensorFlow-2.x-orange.svg)
![Python](https://img.shields.io/badge/Python-3.8%2B-blue.svg)
![License](https://img.shields.io/badge/License-MIT-green.svg)
> **Study and reproduction of the paper:** Liu, Z., et al. (2023). *Dropout Reduces Underfitting*. arXiv:2303.01500.
The paper is available at: [https://arxiv.org/abs/2303.01500](https://arxiv.org/abs/2303.01500)
@@ -79,7 +72,8 @@ pip install tensorflow numpy matplotlib seaborn scikit-learn
## 📊 Usage
The main notebook pipeline.ipynb contains all necessary code. Here is how to run a typical experiment via the pipeline API.
1. Initialization
### 1. Initialization
Choose your dataset (cifar10, fashion_mnist, mnist) and architecture (cnn, dense).
```python
@@ -89,7 +83,7 @@ from pipeline import ExperimentPipeline
exp = ExperimentPipeline(dataset_name="fashion_mnist", model_type="cnn")
```
2. Learning Curves Comparison
### 2. Learning Curves Comparison
Compare training dynamics (Loss & Accuracy) of the three strategies.
@@ -102,7 +96,7 @@ exp.compare_learning_curves(
)
```
3. Ablation Studies
### 3. Ablation Studies
Study the impact of the "Early" phase duration or Dropout intensity.
@@ -124,7 +118,7 @@ exp.compare_drop_rates(
)
```
4. Data Regimes (Data Scarcity)
### 4. Data Regimes (Data Scarcity)
Verify the paper's hypothesis that Early Dropout shines on large datasets (or limited models) while Standard Dropout protects small datasets.
@@ -145,6 +139,11 @@ According to the paper, you should observe:
- Early Dropout: Higher initial Loss, followed by a sharp drop after the switch_epoch, often reaching a lower minimum than Standard Dropout (reduction of underfitting).
- Late Dropout: Rapid rise in accuracy at the start (potential overfitting), then stabilized by the activation of dropout.
## 📄 Detailed Report
<iframe src="/projects/dropout-reduces-underfitting.pdf" width="100%" height="1000px">
</iframe>
## 📝 Authors
- [Arthur Danjou](https://github.com/ArthurDanjou)
@@ -152,6 +151,7 @@ According to the paper, you should observe:
- [Axelle Meric](https://github.com/AxelleMeric)
- [Philippine Quellec](https://github.com/Philippine35890)
- [Moritz Von Siemens](https://github.com/MoritzSiem)
M.Sc. Statistical and Financial Engineering (ISF) - Data Science Track at Université Paris-Dauphine PSL
Based on the work of Liu, Z., et al. (2023). Dropout Reduces Underfitting.
Based on the work of Liu, Z., et al. (2023). Dropout Reduces Underfitting.

View File

@@ -9,16 +9,12 @@ status: Completed
tags:
- Python
- Machine Learning
- Classification
- Data Science
- Regression
- Finance
- Data Science
icon: i-ph-money-wavy-duotone
---
# Machine Learning for Loan Prediction
## Overview
This project focuses on building machine learning models to predict loan approval outcomes and assess default risk. The objective is to develop robust classification models that can effectively identify creditworthy applicants.
## 📊 Project Objectives
@@ -38,17 +34,7 @@ The study employs various machine learning approaches:
- **Hyperparameter Tuning** - Optimizing model performance
- **Cross-validation** - Ensuring robust generalization
## 📁 Key Findings
[To be completed with your findings]
## 📚 Resources
- **Code Repository**: [Add link to your code]
- **Dataset**: [Add dataset information]
- **Full Report**: See embedded PDF below
## 📄 Detailed Report
<iframe src="/projects/loan-ml/Report.pdf" width="100%" height="1000px">
<iframe src="/projects/loan-ml.pdf" width="100%" height="1000px">
</iframe>

View File

@@ -12,12 +12,14 @@ tags:
- Statistics
- Monte Carlo
- Numerical Methods
- Estimation
icon: i-ph-dice-five-duotone
---
This is the report for the Monte Carlo Methods Project. The project was done as part of the course `Monte Carlo Methods` at the Paris-Dauphine University. The goal was to implement different methods and algorithms using Monte Carlo methods in R.
This report presents the Monte Carlo Methods Project completed as part of the **Monte Carlo Methods** course at Paris-Dauphine University. The goal was to implement different methods and algorithms using Monte Carlo methods in R.
## 🛠️ Methods and Algorithms
Methods and algorithms implemented:
- Plotting graphs of functions
- Inverse c.d.f. Random Variation simulation
- Accept-Reject Random Variation simulation
@@ -25,7 +27,11 @@ Methods and algorithms implemented:
- Cumulative density function
- Empirical Quantile Function
## 📚 Resources
You can find the code here: [Monte Carlo Project Code](https://go.arthurdanjou.fr/monte-carlo-code)
<iframe src="/projects/monte-carlo-project/Report.pdf" width="100%" height="1000px">
## 📄 Detailed Report
<iframe src="/projects/monte-carlo.pdf" width="100%" height="1000px">
</iframe>

View File

@@ -15,9 +15,13 @@ tags:
icon: i-ph-city-duotone
---
This is the French version of the report for the Schelling Segregation Model project. The project was done as part of the course `Projet Numérique` at the Paris-Saclay University. The goal was to implement the Schelling Segregation Model in Python and analyze the results using statistics and data visualization.
This report presents the Schelling Segregation Model project completed as part of the **Projet Numérique** course at Paris-Saclay University. The goal was to implement the Schelling Segregation Model in Python and analyze the results using statistics and data visualization.
## 📚 Resources
You can find the code here: [Schelling Segregation Model Code](https://go.arthurdanjou.fr/schelling-code)
<iframe src="/projects/schelling/Projet.pdf" width="100%" height="1000px">
## 📄 Detailed Report
<iframe src="/projects/schelling.pdf" width="100%" height="1000px">
</iframe>

View File

@@ -15,17 +15,19 @@ tags:
icon: i-ph-dog-duotone
---
[Sevetys](https://sevetys.fr) is a leading French network of over 200 veterinary clinics, employing more than 1,300 professionals. Founded in 2017, the group provides comprehensive veterinary care for companion animals, exotic pets, and livestock, with services ranging from preventive medicine and surgery to cardiology, dermatology, and 24/7 emergency care.
[**Sevetys**](https://sevetys.fr) is a leading French network of over 200 veterinary clinics, employing more than 1,300 professionals. Founded in 2017, the group provides comprehensive veterinary care for companion animals, exotic pets, and livestock, with services ranging from preventive medicine and surgery to cardiology, dermatology, and 24/7 emergency care.
Committed to digital innovation, Sevetys leverages centralized data systems to optimize clinic operations, improve patient data management, and enhance the overall client experience. This combination of medical excellence and operational efficiency supports veterinarians in delivering the highest quality care nationwide.
## 🎯 Internship Objectives
During my two-month internship as a Data Engineer, I focused primarily on cleaning and standardizing customer and patient data — a critical task, as this data is extensively used by clinics, Marketing, and Performance teams. Ensuring data quality was therefore essential to the company's operations.
Additionally, I took charge of revising and enhancing an existing data quality report designed to evaluate the effectiveness of my cleaning processes. The report encompassed 47 detailed metrics assessing data completeness and consistency, providing valuable insights that helped maintain high standards across the organization.
## ⚙️ Stack
## ⚙️ Technology Stack
- [Microsoft Azure Cloud](https://azure.microsoft.com/)
- [PySpark](https://spark.apache.org/docs/latest/api/python/)
- [Python](https://www.python.org/)
- [GitLab]()
- **[Microsoft Azure Cloud](https://azure.microsoft.com/)**: Cloud infrastructure platform
- **[PySpark](https://spark.apache.org/docs/latest/api/python/)**: Distributed data processing framework
- **[Python](https://www.python.org/)**: Primary programming language
- **[GitLab](https://gitlab.com)**: Version control and CI/CD platform

View File

@@ -21,7 +21,7 @@ export default defineNuxtConfig({
}
},
css: ['~/assets/css/main.css'],
css: ['~/assets/css/main.css', '~/assets/css/vue-flow.css'],
colorMode: {
preference: 'system',

View File

@@ -17,13 +17,16 @@
"@nuxt/ui": "^4.3.0",
"@nuxthub/core": "0.10.4",
"@nuxtjs/mdc": "0.19.2",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.1",
"@vueuse/core": "^14.1.0",
"@vueuse/math": "^14.1.0",
"better-sqlite3": "^12.5.0",
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
"nuxt": "4.2.2",
"nuxt-studio": "1.0.0-alpha.4",
"nuxt-studio": "1.0.0-beta.3",
"vue": "3.5.26",
"vue-router": "4.6.4",
"zod": "^4.2.1"

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/projects/ml-loan.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3,6 +3,7 @@ import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'education')
.where('extension', '=', 'md')
.order('startDate', 'DESC')
.all()
if (result.length === 0) {
@@ -10,14 +11,6 @@ export default defineCachedEventHandler(async (event) => {
}
return result
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
.map(edu => ({
degree: edu.degree,
institution: edu.institution,
startDate: edu.startDate,
endDate: edu.endDate,
location: edu.location
}))
}, {
maxAge: 60 * 60 * 24,
name: 'education'

View File

@@ -3,6 +3,7 @@ import { queryCollection } from '@nuxt/content/server'
export default defineCachedEventHandler(async (event) => {
const result = await queryCollection(event, 'experiences')
.where('extension', '=', 'md')
.order('startDate', 'DESC')
.all()
if (result.length === 0) {
@@ -10,16 +11,6 @@ export default defineCachedEventHandler(async (event) => {
}
return result
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
.map(exp => ({
title: exp.title,
company: exp.company,
companyUrl: exp.companyUrl,
startDate: exp.startDate,
endDate: exp.endDate,
location: exp.location,
description: exp.description
}))
},
{
maxAge: 60 * 60 * 24,

View File

@@ -0,0 +1 @@
{"version":"7","dialect":"sqlite","entries":[]}

View File

@@ -35,6 +35,62 @@ export interface Activity {
}
}
export interface StatusTag {
id: number
monitor_id: number
tag_id: number
value: string
name: string
color: string
}
export interface StatusMonitor {
id: number
name: string
sendUrl: number
type: string
url?: string
tags: StatusTag[]
}
export interface StatusGroup {
id: number
name: string
weight: number
monitorList: StatusMonitor[]
}
export interface StatusMaintenance {
id: number
title: string
description: string
strategy: string
active: boolean
status: string // 'under-maintenance', etc.
// ... autres champs optionnels (dateRange, etc.)
}
export interface StatusConfig {
slug: string
title: string
description: string
icon: string
autoRefreshInterval: number
theme: string
published: boolean
showTags: boolean
customCSS: string
footerText: string
showPoweredBy: boolean
}
export interface StatusPageData {
config: StatusConfig
incident: unknown | null
publicGroupList: StatusGroup[]
maintenanceList: StatusMaintenance[]
}
export const IDEs = [
{ name: 'Visual Studio Code', icon: 'i-logos:visual-studio-code' },
{ name: 'IntelliJ IDEA Ultimate', icon: 'i-logos:intellij-idea' },

View File

@@ -3,7 +3,7 @@
"name": "artsite",
"compatibility_date": "2025-12-13",
"compatibility_flags": [
"nodejs_compat",
"nodejs_compat"
],
"preview_urls": true,
"workers_dev": true,
@@ -16,7 +16,7 @@
}
],
"placement": {
"mode": "smart",
"mode": "smart"
},
"assets": {
"binding": "ASSETS",