106 Commits

Author SHA1 Message Date
3adf5173e3 fix: améliorer le déploiement avec Cloudflare Wrangler en capturant l'URL de déploiement 2026-01-06 14:58:23 +01:00
5bd407a4ae Merge branch 'master' into feat/vueflow 2026-01-06 14:23:45 +01:00
8e54617c23 fix: corriger la capitalisation du texte d'en-tête sur la page Ecosystem 2026-01-06 14:20:35 +01:00
830ea37cb0 fix: ajouter les secrets API pour Cloudflare Wrangler 2026-01-06 14:15:48 +01:00
f3d3a507dc fix: mettre à jour la commande de déploiement pour Cloudflare Wrangler 2026-01-06 14:14:52 +01:00
33bb54ec5f fix: ajouter la route de pré-rendu pour la page d'accueil 2026-01-06 13:57:18 +01:00
e0bb04d9b8 fix: mettre à jour les dépendances nuxt-studio et vue-tsc vers leurs versions stables 2026-01-06 13:50:45 +01:00
b476033a97 fix: supprimer la configuration de déploiement pour Cloudflare dans nuxt.config.ts 2026-01-05 16:45:18 +01:00
197c15619e fix: ajouter la configuration de déploiement pour Cloudflare dans nuxt.config.ts 2026-01-05 16:41:38 +01:00
0f02b8609a fix: mettre à jour la logique de détermination de la cible de déploiement et supprimer les messages de débogage 2026-01-05 16:35:00 +01:00
d18b9094d5 fix: ajouter des messages de débogage pour la détermination de la cible de déploiement 2026-01-05 15:56:20 +01:00
b3980a2741 fix: mettre à jour le texte d'en-tête pour plus d'impact 2026-01-05 15:51:00 +01:00
b30f2eb523 fix: étendre la condition de déploiement pour inclure la branche master 2026-01-05 15:42:35 +01:00
e32e7bf7bb fix: supprimer l'affichage des heures brutes et du total dans le composant Stats 2026-01-04 20:07:31 +01:00
4dc74c0011 fix: corriger l'utilisation de usePrecision pour totalHours dans le composant Stats 2026-01-04 20:04:38 +01:00
f95a417b37 fix: mettre à jour le statut du projet de 'En cours' à 'Terminé' dans data-visualisation.md 2026-01-04 20:01:00 +01:00
055c16e198 Refactor code structure for improved readability and maintainability 2026-01-04 20:00:43 +01:00
669eec60a1 fix: refactor le calcul de totalHours dans le composant Stats 2026-01-04 19:57:42 +01:00
4cda11a6a3 fix: ajouter des conditions d'affichage pour les cartes dans le composant Stats 2026-01-04 19:54:18 +01:00
df33efc731 fix: corriger la déclaration de totalHours dans le composant Stats 2026-01-04 19:53:15 +01:00
305c91199a fix: corriger la récupération de la date de début dans le composant Stats 2026-01-04 19:46:58 +01:00
ed3019d1b9 fix: corriger l'utilisation des valeurs retournées pour yearsCollected et formattedDate dans le composant Stats 2026-01-04 19:43:10 +01:00
988e18b2f7 fix: gérer les valeurs nulles pour les statistiques dans le composant Stats 2026-01-04 19:41:22 +01:00
5d4f6ee9a4 fix: uniformiser le nom de cache pour les fonctions Wakatime dans le composant Stats 2026-01-04 19:40:10 +01:00
d13594e5d1 fix: uniformiser le nom de cache pour les données Wakatime dans le composant Stats 2026-01-04 19:34:43 +01:00
9aabe422b3 fix: supprimer le chargement paresseux des données dans le composant Stats et améliorer la gestion des requêtes API 2026-01-04 19:30:48 +01:00
e30d956f58 fix: activer le chargement paresseux des données dans le composant Stats 2026-01-04 19:23:08 +01:00
56c63d8db7 fix: mettre à jour les couleurs des icônes et des barres de progression dans le composant Stats 2026-01-04 19:07:47 +01:00
c8116e10b8 fix: supprimer l'affichage des langues, éditeurs et systèmes d'exploitation dans le composant Stats et ajuster les couleurs 2026-01-04 19:03:32 +01:00
9dc21f4287 fix: afficher les langues, éditeurs et systèmes d'exploitation dans le composant Stats 2026-01-04 18:59:57 +01:00
c3aafbf67e fix: améliorer la gestion des valeurs nulles dans le composant Stats 2026-01-04 17:17:56 +01:00
7b2c34ab87 fix: réduire la durée de mise en cache des requêtes Wakatime et de la page de statut 2026-01-04 17:11:22 +01:00
7b32606b7d fix: mettre à jour la version de zod et réduire la durée de mise en cache des requêtes Wakatime 2026-01-04 17:01:29 +01:00
9ee45723f3 fix: améliorer la lisibilité du code et ajuster la taille des icônes dans le composant Activity 2026-01-03 22:26:57 +01:00
6296297855 fix: ajouter l'organisation de travail pour le profil d'Arthur Danjou 2026-01-02 20:40:56 +01:00
8e17fc1e11 fix: remplacer le favicon WebP par un favicon ICO 2026-01-02 20:37:45 +01:00
bd044aed24 fix: ajouter la configuration Schema.org pour le profil d'Arthur Danjou 2026-01-02 20:34:14 +01:00
0f37a0072d fix: ajouter les métadonnées Twitter pour améliorer le partage sur les réseaux sociaux 2026-01-02 20:28:14 +01:00
ca5cf60c4a fix: améliorer la gestion des métadonnées SEO dans plusieurs fichiers de page 2026-01-02 20:27:08 +01:00
fb25eddaf0 fix: corriger l'importation de asSeoCollection dans content.config.ts 2026-01-02 20:02:17 +01:00
2dcf52b65b fix: ajouter l'importation de asSeoCollection dans content.config.ts 2026-01-02 20:00:17 +01:00
aedd8131d9 fix: réorganiser la configuration du site et des collections dans nuxt.config.ts et content.config.ts 2026-01-02 19:27:02 +01:00
9de199e9d0 fix: ajouter l'icône apple-touch-icon dans le fichier app.vue et corriger l'ordre des modules dans nuxt.config.ts 2026-01-02 19:21:43 +01:00
5a6cee085a chore: add @nuxtjs/seo module and update package.json 2026-01-02 19:02:21 +01:00
b26efd9da0 fix: mettre à jour le fichier .gitignore pour inclure les fichiers générés par Localflare et corriger les types dans worker-configuration.d.ts 2026-01-02 18:41:40 +01:00
e21ad51bc3 fix: corriger les routes pour le domaine principal et www dans wrangler.jsonc 2026-01-02 18:24:27 +01:00
ad9f440689 feat: ajouter de nouvelles variantes de couleur pour les icônes dans ProseIcon.vue 2026-01-02 18:23:30 +01:00
75b5c3c28c fix: mettre à jour la couleur de l'icône de Positron dans la section IDEs & Éditeurs 2026-01-02 18:13:45 +01:00
7ef847ed62 feat: ajouter Positron comme IDE léger pour l'analyse statistique dans la section des outils de développement 2026-01-02 18:10:48 +01:00
5990b84432 fix: corriger la logique de détermination du nom d'activité et ajouter Positron à la liste des IDEs 2026-01-02 18:09:23 +01:00
0628baaaba fix: mettre à jour les versions des dépendances zod, @iconify-json/devicon et wrangler 2026-01-02 18:09:19 +01:00
d9a81f664c feat: ajouter des polices personnalisées dans main.css et mettre à jour les thèmes dans uses.md
feat: configurer la mise en surbrillance du code Python dans nuxt.config.ts
2025-12-26 00:23:36 +01:00
e1e8cc4354 fix: ajouter l'attribut lang à la balise html pour la conformité linguistique 2025-12-25 17:06:23 +01:00
ebe067d606 feat: ajouter une page d'accueil temporaire avec des informations sur le développement en cours 2025-12-25 10:34:49 +01:00
75c72d007c fix: ajuster les couleurs de fond pour améliorer la visibilité dans Activity.vue et Stats.vue 2025-12-25 00:26:16 +01: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
fb22fdf057 chore(deps): update @nuxt/content to version 3.10.0 2025-12-24 14:49:20 +01:00
2db5d28dab Refactor: supprimer les références à Drizzle et Cobe dans la section Tech Stack du README 2025-12-24 14:48:36 +01:00
121b35308f Refactor: remplacer UMeter par UProgress pour une meilleure gestion des pourcentages 2025-12-24 14:47:27 +01:00
8d42b682e5 Merge pull request #14 from ArthurDanjou/copilot/improve-readme-file
Enhance README with development setup and security documentation
2025-12-24 14:46:59 +01:00
copilot-swe-agent[bot]
5a5f3c6319 docs: expand content section with all markdown and JSON files
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 13:38:10 +00:00
b790113771 Merge pull request #15 from ArthurDanjou/copilot/complete-projects-page-styling
Implement projects listing and detail pages with filtering
2025-12-24 14:36:51 +01:00
6e6a05a3cd Refactor: supprimer le fichier ArtChat et ajouter le fichier ArtSite avec une description détaillée et une structure améliorée 2025-12-24 14:36:04 +01:00
copilot-swe-agent[bot]
32bf773cdd docs: fix project structure to match actual directory layout
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 13:34:03 +00:00
4d520797c9 Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-24 14:32:08 +01:00
72f9e61a0e Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-24 14:31:52 +01:00
02ecf80cb9 Refactor: update project status values to use consistent capitalization; enhance app configuration for preview environment 2025-12-24 14:30:19 +01:00
00a5c34f36 Refactor: clean up template structure and improve readability; enhance project card navigation 2025-12-24 13:50:55 +01:00
bcf9bd599e Refactor: swap status colors for 'active' and 'completed'; add full-card link overlay for navigation 2025-12-24 13:19:05 +01:00
2c545875d1 chore: update project dependencies and configuration
- Removed Tailwind CSS tag from the artchat project metadata.
- Updated @nuxtjs/mdc package version from ^0.19.1 to 0.19.2 in package.json.
- Added migrations_table and migrations_dir properties to the D1 database configuration in wrangler.jsonc.
2025-12-24 13:14:35 +01:00
0e033b6e02 Refactor code structure for improved readability and maintainability 2025-12-24 12:50:40 +01:00
copilot-swe-agent[bot]
bcac6dd42a docs: remove useless environment variables from README
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-24 11:49:12 +00:00
copilot-swe-agent[bot]
38bf904902 Extract clearFilters function for better maintainability
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:32:49 +00:00
copilot-swe-agent[bot]
a1dbcdce90 Refactor: Extract project color mappings to composable
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:30:13 +00:00
copilot-swe-agent[bot]
66fe877499 Complete projects pages with listing and detail views
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:27:54 +00:00
copilot-swe-agent[bot]
7fed92f539 Initial exploration of projects pages structure
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:15:50 +00:00
copilot-swe-agent[bot]
ee74f06cd5 docs: refine documentation link and security warnings
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:15:35 +00:00
copilot-swe-agent[bot]
476d5b9842 docs: add security warnings for environment variables
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:14:41 +00:00
copilot-swe-agent[bot]
cd69b346fe docs: significantly improve README with comprehensive documentation
Co-authored-by: ArthurDanjou <29738535+ArthurDanjou@users.noreply.github.com>
2025-12-23 23:13:42 +00:00
copilot-swe-agent[bot]
c47a0b4f5f Initial plan 2025-12-23 23:10:54 +00:00
copilot-swe-agent[bot]
5c46d1ab3f Initial plan 2025-12-23 23:10:14 +00:00
6d54535245 feat: réorganiser la déclaration des liens dans les métadonnées de la page 2025-12-24 00:08:50 +01:00
b252f54fa0 feat: mettre à jour les métadonnées SEO et réorganiser les pages de projets 2025-12-24 00:06:35 +01:00
a3210afaaf feat: réorganiser la structure des pages de projets et mettre à jour les statuts des projets 2025-12-23 23:46:14 +01:00
c46feb3052 feat: mettre à jour les couleurs par défaut et réorganiser la configuration des composants 2025-12-23 23:34:20 +01:00
9bc529d9ae chore: remove @modelcontextprotocol/sdk dependency from package.json 2025-12-23 23:34:13 +01:00
9ee02a36a3 feat: intégrer les informations de contact et ajuster les priorités des liens sociaux 2025-12-23 23:34:07 +01:00
466baf1eb8 feat: réorganiser les composants de la page d'accueil et améliorer l'affichage des statistiques 2025-12-23 22:54:23 +01:00
0e70996a3a feat: supprimer les pages 'activity' et 'stats' 2025-12-23 22:54:18 +01:00
9e773355b5 feat: remplacer le champ 'emoji' par 'icon' dans les collections 2025-12-23 22:54:09 +01:00
c942266fa2 feat: ajuster l'animation de vague et réorganiser les styles CSS 2025-12-23 22:54:06 +01:00
6b81bd2854 feat: supprimer les liens de navigation pour les statistiques et l'activité 2025-12-23 22:54:01 +01:00
918528f2b3 chore: update @nuxthub/core to version 0.10.4 and @iconify-json/devicon to version 1.2.55 2025-12-23 22:53:56 +01:00
ae176ecbae feat: mettre à jour les icônes et les dates des diplômes et expériences 2025-12-23 22:53:50 +01:00
81 changed files with 2582 additions and 1177 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
@@ -31,30 +31,47 @@ jobs:
uses: cloudflare/wrangler-action@v3
with:
command: types
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Build
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 }}
STUDIO_GITHUB_MODERATORS: ${{ secrets.STUDIO_GITHUB_MODERATORS }}
- name: Publish to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
- name: Determine Deployment Command
id: target
run: |
if [ "${{ github.ref_name }}" = "main" ] || [ "${{ github.ref_name }}" = "master" ]; then
echo "wrangler_command=deploy" >> $GITHUB_OUTPUT
echo "env_name=Production" >> $GITHUB_OUTPUT
else
echo "wrangler_command=versions upload" >> $GITHUB_OUTPUT
echo "env_name=Preview" >> $GITHUB_OUTPUT
fi
- name: Run Cloudflare Wrangler & Capture URL
id: wrangler
run: |
# Exécuter wrangler et rediriger la sortie vers un fichier tout en l'affichant (tee)
bunx wrangler ${{ steps.target.outputs.wrangler_command }} | tee wrangler.log
# Extraction de l'URL
if [ "${{ steps.target.outputs.env_name }}" = "Preview" ]; then
PREVIEW_URL=$(grep -o 'https://[^ ]*\.workers\.dev' wrangler.log | head -n 1)
echo "DEPLOY_URL=$PREVIEW_URL" >> $GITHUB_OUTPUT
else
echo "DEPLOY_URL=https://arthurdanjou.fr" >> $GITHUB_OUTPUT
fi
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
@@ -62,13 +79,15 @@ 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 : **${{ steps.target.outputs.env_name }}**
URL : **${{ steps.wrangler.outputs.DEPLOY_URL }}**
Commit: `${{ github.sha }}` par ${{ github.actor }}.
nofail: false
nodetail: false

4
.gitignore vendored
View File

@@ -8,6 +8,7 @@ dist
# Node dependencies
node_modules
package-lock.json
# Logs
logs
@@ -24,3 +25,6 @@ logs
!.env.example
.wrangler
# Localflare generated files
.localflare/

238
README.md
View File

@@ -8,6 +8,9 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
[![Vue.js](https://img.shields.io/badge/Vue.js-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white)](https://vuejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![TailwindCSS](https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white)](https://tailwindcss.com/)
[![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?style=for-the-badge&logo=cloudflare&logoColor=white)](https://cloudflare.com/)
[🌐 Live Demo](https://arthurdanjou.fr/) | [🐛 Report Bug](https://github.com/ArthurDanjou/artsite/issues)
</div>
@@ -15,8 +18,14 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
- [Features](#-features)
- [Tech Stack](#-tech-stack)
- [Prerequisites](#-prerequisites)
- [Getting Started](#-getting-started)
- [Development](#-development)
- [Environment Variables](#-environment-variables)
- [Project Structure](#-project-structure)
- [Integrations](#-integrations)
- [Deployment](#-deployment)
- [Contributing](#-contributing)
- [License](#-license)
- [Contact](#-contact)
@@ -39,29 +48,173 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
- **Design System** → [NuxtUI](https://ui.nuxt.com/)
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio)
- **Language** → [TypeScript](https://www.typescriptlang.org/)
- **Deployment** → [NuxtHub](https://hub.nuxt.com/)
- **Deployment** → [NuxtHub](https://hub.nuxt.com/) on [Cloudflare](https://cloudflare.com/)
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/)
- **Package Manager** → [pnpm](https://pnpm.io/)
- **Package Manager** → [Bun](https://bun.sh/)
- **Internationalization** → [Nuxt i18n](https://i18n.nuxtjs.org/)
- **Database ORM** → [Drizzle](https://orm.drizzle.team/)
- **Database** → [Cloudflare D1](https://developers.cloudflare.com/d1/) (SQLite)
- **Composables** → [VueUse](https://vueuse.org/)
- **Validation** → [Zod](https://zod.dev/)
- **Globe Visualization** → [Cobe](https://github.com/shuding/cobe)
- **Icons** → [Iconify](https://iconify.design/)
## 📦 Prerequisites
Before you begin, ensure you have the following installed:
- **Node.js** (v18 or higher recommended)
- **Bun** (latest version) - [Install Bun](https://bun.sh/docs/installation)
- **Git** for version control
- **Cloudflare Account** (for deployment)
## 🚀 Getting Started
1. **Clone the repository**
```bash
git clone https://github.com/ArthurDanjou/artsite.git
cd artsite
```
2. **Install dependencies**
```bash
bun install
```
3. **Set up environment variables**
```bash
cp .env.example .env
```
Then edit `.env` and fill in your configuration values (see [Environment Variables](#-environment-variables) section).
4. **Start the development server**
```bash
bun run dev
```
The application will be available at `http://localhost:3000` 🎉
## 💻 Development
### Available Scripts
```bash
# Start development server
bun run dev
# Build for production
bun run build
# Preview production build locally
bun run preview
# Lint code
bun run lint
# Deploy to Cloudflare
bun run deploy
# Generate Cloudflare types
bun run cf-typegen
```
### Project Configuration
The project uses:
- **ESLint** for code linting with stylistic rules (single quotes, no trailing commas)
- **TypeScript** for type safety
- **Nuxt DevTools** enabled for enhanced development experience
## 🔐 Environment Variables
Create a `.env` file in the root directory with the following variables:
```env
# GitHub Studio Integration (for content management)
STUDIO_GITHUB_CLIENT_ID=your_github_client_id
STUDIO_GITHUB_CLIENT_SECRET=your_github_client_secret # Keep this secret! Never commit to version control
# Discord Integration (runtime config - keep private)
NUXT_DISCORD_USER_ID=your_discord_user_id # Private
# WakaTime Integration (for coding statistics - keep private)
NUXT_WAKATIME_USER_ID=your_wakatime_user_id # Private
NUXT_WAKATIME_CODING=your_coding_stats_api # Private
NUXT_WAKATIME_EDITORS=your_editors_stats_api # Private
NUXT_WAKATIME_LANGUAGES=your_languages_stats_api # Private
NUXT_WAKATIME_OS=your_os_stats_api # Private
# Status Page URL
NUXT_STATUS_PAGE=your_status_page_url
```
> **Note:** Not all variables are required for basic development. The site will work without integrations, but some features may be disabled.
> **Security:** Never commit your `.env` file or expose sensitive credentials like `STUDIO_GITHUB_CLIENT_SECRET`. Keep all API keys and secrets secure.
## 📂 Project Structure
```
├── assets/ # Static assets like global styles
├── components/ # Vue components
├── layouts/ # Page layouts
├── pages/ # Application pages
├── public/ # Public static files
├── server/ # Server API routes
├── .env.example # Example environment variables
├── nuxt.config.ts # Nuxt configuration
├── package.json # Dependencies and scripts
└── README.md # Project documentation
artsite/
├── app/ # Application source code
│ ├── assets/ # Static assets (CSS, images)
│ ├── components/ # Vue components
├── content/ # Content-specific components
│ │ └── home/ # Home page components
│ ├── composables/ # Vue composables
│ ├── pages/ # Application pages (file-based routing)
│ │ ├── index.vue # Home page
├── projects/ # Projects section
│ │ ├── hobbies.vue # Hobbies page
│ │ ├── uses.vue # Uses page
│ │ └── ecosystem.vue # Ecosystem page
│ ├── app.config.ts # App configuration
│ ├── app.vue # Root app component
│ └── error.vue # Error page
├── content/ # Content files (Markdown & JSON)
│ ├── education/ # Education content
│ │ ├── bachelor.md # Bachelor's degree information
│ │ ├── m1.md # Master's 1st year information
│ │ └── m2.md # Master's 2nd year information
│ ├── experiences/ # Professional experience content
│ │ ├── artdanjproduction.md
│ │ ├── erisium.md
│ │ ├── hackathon-cnd.md
│ │ ├── picard.md
│ │ └── sevetys.md
│ ├── projects/ # Project portfolio content
│ │ ├── artchat.md
│ │ ├── arthome.md
│ │ ├── artlab.md
│ │ ├── artstudies.md
│ │ ├── bikes-glm.md
│ │ ├── breast-cancer.md
│ │ ├── dropout-reduces-underfitting.md
│ │ ├── loan-ml.md
│ │ ├── monte-carlo-project.md
│ │ ├── schelling-segregation-model.md
│ │ └── sevetys.md
│ ├── contact.json # Contact information data
│ ├── hobbies.md # Hobbies page content
│ ├── index.md # Home page content
│ ├── languages.json # Programming languages data
│ ├── skills.json # Skills and expertise data
│ └── uses.md # Tools and software used
├── public/ # Public static files
├── server/ # Server API routes and middleware
│ ├── api/ # API endpoints
│ └── routes/ # Server routes
├── types/ # TypeScript type definitions
├── .env.example # Example environment variables
├── content.config.ts # Content module configuration
├── nuxt.config.ts # Nuxt configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── wrangler.jsonc # Cloudflare Workers configuration
└── README.md # Project documentation
```
## 🔌 Integrations
@@ -70,7 +223,62 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
- **Discord** - Display real-time Discord status
- **Nuxt Studio** - Headless CMS for content management
- **Nuxt i18n** - Internationalization support
- **NuxtHub** - CI/CD and deployment
- **NuxtHub** - CI/CD and deployment platform
- **Cloudflare D1** - Serverless SQL database
## 🚢 Deployment
This project is configured to deploy on Cloudflare using NuxtHub.
### Deploy to Production
1. **Prerequisites**
- Cloudflare account
- Wrangler CLI configured (`wrangler login`)
- NuxtHub project created
2. **Deploy**
```bash
bun run deploy
```
This will:
- Generate Cloudflare types
- Build the application
- Deploy to Cloudflare Workers
### Automatic Deployments
The project is set up with NuxtHub for automatic deployments on push to the main branch.
## 🤝 Contributing
Contributions are welcome! Here's how you can help:
1. **Fork the repository**
2. **Create a feature branch**
```bash
git checkout -b feature/amazing-feature
```
3. **Make your changes**
4. **Commit your changes**
```bash
git commit -m 'Add some amazing feature'
```
5. **Push to the branch**
```bash
git push origin feature/amazing-feature
```
6. **Open a Pull Request**
### Development Guidelines
- Follow the existing code style (enforced by ESLint)
- Use TypeScript for type safety
- Write descriptive commit messages
- Test your changes locally before submitting
- Update documentation if needed
## 📄 License

View File

@@ -1,12 +1,25 @@
export default defineAppConfig({
ui: {
container: {
base: 'max-w-4xl'
base: 'max-w-5xl'
},
colors: {
primary: 'neutral',
white: 'white',
black: 'black',
cyan: 'cyan',
gray: 'gray',
zinc: 'zinc',
neutral: 'neutral',
red: 'red'
red: 'red',
amber: 'amber',
green: 'green',
emerald: 'emerald',
sky: 'sky',
blue: 'blue',
purple: 'purple',
pink: 'pink',
orange: 'orange'
}
}
})

View File

@@ -1,6 +1,8 @@
<script lang="ts" setup>
useHead({
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} %separator %siteName` : 'Arthur Danjou %separator AI Safety & Applied Math'
}
})
</script>

View File

@@ -1,38 +1,54 @@
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--animate-wave: wave 2.5s infinite;
--font-mono: 'Monaspace Neon', 'ui monaspace', monospace;
--font-sofia: 'Sofia Sans', 'ui sans-serif', sans-serif;
--font-sans: 'DM Sans', 'ui sans-serif', sans-serif;
@keyframes wave {
0% {
transform: rotate(0deg);
}
10% {
transform: rotate(14deg);
}
20% {
transform: rotate(-8deg);
}
30% {
transform: rotate(14deg);
}
40% {
transform: rotate(-4deg);
}
50% {
transform: rotate(10deg);
}
60%,
100% {
transform: rotate(0deg);
}
}
}
:root {
--animate-wave: wave 3s infinite;
--ui-bg: #f8f8f8;
--ui-font-family: 'DM Sans', sans-serif;
transition-duration: 0.7s;
}
.dark {
--animate-wave: wave 3s infinite;
--ui-bg: #0f0f0f;
--ui-font-family: 'DM Sans', sans-serif;
transition-duration: 0.7s;
}
.sofia {
--ui-font-family: 'Sofia Sans', sans-serif;
}
@keyframes wave {
0%,
50%,
100% {
transform: rotate(-12deg);
}
25%,
75% {
transform: rotate(3deg) scale(1.5);
}
}

View File

@@ -1,9 +1,12 @@
<script setup lang="ts">
import { socials } from '~~/types'
const { contact } = await useContent()
</script>
<template>
<footer class="my-16">
<footer
v-if="contact"
class="my-16"
>
<div class="flex justify-center mb-16">
<USeparator
class="md:w-2/3"
@@ -16,11 +19,11 @@ import { socials } from '~~/types'
<h1>Find me on:</h1>
<div class="flex gap-2 flex-wrap">
<HomeLink
v-for="social in [...socials].sort((a, b) => a.label.localeCompare(b.label))"
:key="social.label"
:href="social.to"
v-for="social in contact.body.filter(item => item.priority === 1)"
:key="social.name"
:href="social.value"
:icon="social.icon"
:label="social.label"
:label="social.name"
target="_blank"
/>
</div>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { navs, socials } from '~~/types'
import { navs } from '~~/types'
const openContactDrawer = ref(false)
const router = useRouter()
@@ -8,14 +8,7 @@ defineShortcuts({
backspace: () => router.back()
})
const socialsList = [
{
label: 'Email',
icon: 'i-ph-envelope-duotone',
to: 'mailto:arthurdanjou@outlook.fr'
},
...socials
]
const { contact } = await useContent()
</script>
<template>
@@ -61,8 +54,14 @@ const socialsList = [
class="h-6"
/>
<UDropdownMenu
v-if="contact"
v-model:open="openContactDrawer"
:items="socialsList"
:items="contact.body.filter(item => item.priority === 1).map(item => ({
label: item.name,
icon: item.icon,
href: item.value,
target: '_blank'
}))"
:content="{
align: 'center',
side: 'bottom',

View File

@@ -6,12 +6,12 @@ defineProps({
},
color: {
type: String,
default: 'gray'
default: 'neutral'
}
})
const colorVariants = {
gray: 'text-gray-500/80 decoration-gray-400/40',
neutral: 'text-neutral-500/80 decoration-neutral-400/40',
red: 'text-red-500/80 decoration-red-400/40',
yellow: 'text-yellow-500/80 decoration-yellow-400/40',
green: 'text-green-500/80 decoration-green-400/40',
@@ -23,7 +23,11 @@ const colorVariants = {
zinc: 'text-zinc-500/80 decoration-zinc-400/40',
orange: 'text-orange-500/80 decoration-orange-400/40',
amber: 'text-amber-500/80 decoration-amber-400/40',
emerald: 'text-emerald-500/80 decoration-emerald-400/40'
emerald: 'text-emerald-500/80 decoration-emerald-400/40',
white: 'text-white/80 decoration-white/40',
black: 'text-black/80 decoration-black/40',
cyan: 'text-cyan-500/80 decoration-cyan-400/40',
gray: 'text-gray-500/80 decoration-gray-400/40'
}
</script>

View File

@@ -1,155 +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, 1000)
const codingActivities = computed(() => {
const list = activity.value?.data.activities ?? []
return list
.filter(a => IDEs.some(ide => ide.name === a.name))
.map(a => ({ ...a, name: a.assets?.small_text === 'Cursor' ? 'Cursor' : a.name }))
})
const codingActivity = computed(() => {
if (!codingActivities.value.length) return null
return codingActivities.value.length > 1
? codingActivities.value[Math.floor(Math.random() * codingActivities.value.length)]
: codingActivities.value[0]
})
const isActive = computed(() => {
const act = codingActivity.value
if (!act) return false
const { name, details = '', state = '' } = act
if (name === 'Visual Studio Code' || name === 'Cursor')
return !details.includes('Idling')
return state.toLowerCase().includes('editing')
})
type FormattedActivity = {
name: string
project: string
state: string
start: {
ago: string
formatted: { date: string, time: string }
}
} | null
const formattedActivity = computed<FormattedActivity>(() => {
const act = codingActivity.value
if (!act) return null
const { name, details = '', state = '', timestamps } = act
const project = details
? (details.charAt(0).toUpperCase() + details.slice(1).replace('Workspace:', '').trim())
: ''
const stateWord = (state && state.split(' ').length >= 2 ? state.split(' ')[1] : 'Secret project') as string
const ago = useTimeAgo(timestamps.start).value
const formatDate = (date: number, format: string) =>
useDateFormat(date, format).value
return {
name,
project,
state: stateWord,
start: {
ago,
formatted: {
date: formatDate(timestamps.start, 'DD MMM YYYY'),
time: formatDate(timestamps.start, 'HH:mm')
}
}
}
})
const editorIcon = computed(() => {
const name = formattedActivity.value?.name ?? codingActivity.value?.name
return IDEs.find(ide => ide.name === name)?.icon ?? 'file'
})
</script>
<template>
<ClientOnly>
<div
v-if="formattedActivity"
class="flex items-start gap-2 mt-4"
>
<UTooltip :text="isActive ? 'I\'m online 👋' : 'I\'m sleeping 😴'">
<div class="relative flex h-3 w-3 mt-2">
<div
v-if="isActive"
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75"
/>
<div
:class="isActive ? 'bg-green-500' : 'bg-amber-500'"
class="relative inline-flex rounded-full h-3 w-3"
/>
</div>
</UTooltip>
<div
v-if="isActive"
class="space-x-1"
>
<span>
I'm actually working on
<strong>{{ formattedActivity.state.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ') }}</strong>,
editing <i>{{ formattedActivity.project.replace('Editing', '') }}</i>, using
<span class="space-x-1">
<UIcon
:name="editorIcon"
size="16"
/>
<strong>{{ formattedActivity.name }}</strong>
</span>.
I've started <strong>{{ formattedActivity.start.ago }}</strong>, on
<strong>{{ formattedActivity.start.formatted.date }}</strong>
at <strong>{{ formattedActivity.start.formatted.time }}</strong>.
</span>
</div>
<div
v-else
class="space-x-1"
>
<span>
I'm idling on my computer with
<span class="space-x-1">
<UIcon
:name="editorIcon"
size="16"
/>
<strong>{{ formattedActivity.name }}</strong>
</span>
running in background.
</span>
</div>
</div>
<div
v-else
class="my-5 flex md:items-start gap-2"
>
<UTooltip text="I'm offline 🫥">
<div class="relative flex h-3 w-3 mt-2">
<div class="relative cursor-not-allowed inline-flex rounded-full h-3 w-3 bg-red-500" />
</div>
</UTooltip>
<p class="not-prose">
I'm currently offline. Come back later to see what I'm working on. <i>I am probably doing some maths or sleeping.</i>
</p>
</div>
</ClientOnly>
</template>

View File

@@ -1,18 +1,11 @@
<script lang="ts" setup>
const { width } = useWindowSize()
</script>
<template>
<ClientOnly>
<div
v-if="width > 1024"
class="text-[12px] italic flex items-center gap-1 mt-4"
>
<UIcon
class="transform -rotate-12 duration-300 animate-wave"
name="i-ph-hand-pointing-duotone"
/>
<p>Hover the bold texts to find out more about me.</p>
</div>
</ClientOnly>
<div class="hidden lg:flex items-center gap-2 mt-6 px-3 py-2 rounded-lg bg-neutral-50 dark:bg-neutral-800/50 border border-neutral-100 dark:border-neutral-800 w-fit transition-opacity hover:opacity-100 opacity-70">
<UIcon
name="i-ph-hand-pointing-duotone"
class="w-5 h-5 text-primary-500 animate-wave origin-bottom"
/>
<p class="text-xs font-medium text-neutral-500 dark:text-neutral-400">
Tip: Hover over the <span class="font-bold text-neutral-900 dark:text-neutral-200">bold text</span> to reveal more details.
</p>
</div>
</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

@@ -31,7 +31,7 @@ defineProps({
size="20"
/>
<span
class="duration-300 underline-offset-2 font-semibold text-md text-black dark:text-white underline decoration-gray-300 dark:decoration-neutral-700 group-hover:decoration-black dark:group-hover:decoration-white"
class="duration-300 underline-offset-2 font-semibold text-md text-black dark:text-white underline decoration-neutral-300 dark:decoration-neutral-700 group-hover:decoration-black dark:group-hover:decoration-white"
>
{{ label }}
</span>

View File

@@ -1,19 +1,32 @@
<template>
<div class="mt-4">
<div class="float-left flex items-center mr-2 mt-1">
<UCard>
<div class="flex items-start sm:items-center gap-4">
<ClientOnly>
<UTooltip text="It's me 👋">
<UTooltip
text="Arthur Danjou • Research & Engineering"
:popper="{ placement: 'top' }"
>
<UAvatar
alt="Avatar"
class="hover:rotate-360 duration-500 transform-gpu rounded-full"
size="xl"
src="/arthur.webp"
alt="Arthur Danjou"
size="xl"
class="ring-2 ring-primary-500/20 dark:ring-primary-400/20 transition-transform duration-700 ease-in-out hover:rotate-360 shadow-sm"
/>
</UTooltip>
</ClientOnly>
<div class="space-y-1">
<h3 class="font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
Let's start a discussion
<UIcon
name="i-ph-chat-circle-dots-duotone"
class="text-primary-500 w-5 h-5"
/>
</h3>
<p class="text-sm text-neutral-600 dark:text-neutral-300 leading-relaxed">
Thanks for stopping by my digital garden! Whether you have a question about a theorem, a suggestion for a project, or just want to say hi, I'd love to hear from you.
</p>
</div>
</div>
<p class="not-prose">
Hello everyone! Thanks for visiting my portfolio. Please leave whatever you like to say, such as suggestions, appreciations, questions or anything!
</p>
</div>
</UCard>
</template>

View File

@@ -20,16 +20,19 @@ const { skills } = await useContent()
:key="skill.id"
>
<div class>
<h3 class="text-xl md:text-2xl font-semibold tracking-tight mb-4">
<h3 class="text-xl md:text-2xl font-semibold tracking-tight">
{{ skill.name }}
</h3>
<div class="flex flex-wrap gap-2">
<p class="text-sm text-neutral-600 dark:text-neutral-400">
{{ skill.description }}
</p>
<div class="flex flex-wrap gap-2 mt-4">
<UBadge
v-for="item in skill.items"
: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

@@ -1,41 +0,0 @@
<script lang="ts" setup>
import type { Stats } from '~~/types'
import { usePrecision } from '@vueuse/math'
const { data: stats } = await useAsyncData<Stats>('stats', () => $fetch('/api/stats'))
const time = useTimeAgo(new Date(stats.value?.coding.range.start ?? 0)).value.split(' ')[0]
const date = useDateFormat(new Date(stats.value?.coding.range.start ?? 0), 'DD MMMM YYYY')
const hours = usePrecision((stats.value?.coding.grand_total.total_seconds_including_other_language ?? 0) / 3600, 0)
const editors = computed(() => stats.value?.editors.slice(0, 3).map(editor => `${editor.name} (${editor.percent}%)`).join(', '))
const os = computed(() => stats.value?.os.slice(0, 2).map(os => `${os.name} (${os.percent}%)`).join(', '))
const languages = computed(() => stats.value?.languages.slice(0, 3).map(language => `${language.name} (${language.percent}%)`).join(', '))
</script>
<template>
<ClientOnly>
<div
v-if="time && date && hours && stats"
class="space-y-1"
>
<p>
I collect some data for {{ time }} years, started the
<HoverText
hover="That was so long ago 🫣"
:text="date"
/>.
I've coded for a total of
<HoverText
hover="That's a lot 😮"
:text="hours"
/>
hours.
</p>
<p>
My best editors are {{ editors || 'N/A' }}. My best OS is {{ os || 'N/A' }}. My top languages are
{{ languages || 'N/A' }}.
</p>
</div>
</ClientOnly>
</template>

View File

@@ -0,0 +1,127 @@
<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.assets?.small_text === 'Positron'
? 'Positron'
: ideActivity.name
const isIdling = ideActivity.details?.toLowerCase().includes('idling')
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-8 h-8 opacity-80"
/>
<UIcon
v-else
name="i-ph-power-duotone"
class="w-8 h-8 text-red-400 opacity-80"
/>
</div>
<div
v-if="currentSession"
class="space-y-1 pl-6 border-l-2 border-neutral-200 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,178 @@
<script lang="ts" setup>
import type { Stats } from '~~/types'
import { usePrecision } from '@vueuse/math'
const { data: stats } = await useAsyncData<Stats>('stats', () => $fetch('/api/stats'))
const startDate = computed(() => new Date(stats.value?.coding?.range?.start ?? new Date()))
const rawHours = computed(() => {
const seconds = stats.value?.coding?.grand_total?.total_seconds_including_other_language ?? 0
return seconds / 3600
})
const totalHours = usePrecision(rawHours, 0)
const yearsCollected = useTimeAgo(startDate)
const formattedDate = useDateFormat(startDate, 'MMM DD, YYYY')
const topLanguages = computed(() => stats.value?.languages.slice(0, 4) ?? [])
const topEditors = computed(() => stats.value?.editors.slice(0, 3) ?? [])
const topOS = computed(() => stats.value?.os.slice(0, 2) ?? [])
</script>
<template>
<ClientOnly>
<div
v-if="stats"
class="space-y-6"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UCard v-if="totalHours">
<div class="flex items-center gap-4">
<div class="p-3 bg-primary-200 dark:bg-primary-900 rounded-lg text-primary-500 flex items-center justify-center">
<UIcon
name="i-ph-clock-duotone"
class="w-8 h-8"
/>
</div>
<div>
<p class="text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
Total Coding Time
</p>
<h3 class="text-3xl font-bold font-mono text-neutral-900 dark:text-white">
{{ totalHours }} <span class="text-lg text-neutral-500 font-normal">hours</span>
</h3>
</div>
</div>
</UCard>
<UCard v-if="formattedDate && yearsCollected">
<div class="flex items-center gap-4">
<div class="p-3 bg-emerald-100 dark:bg-emerald-900/20 rounded-lg text-emerald-500 flex items-center justify-center">
<UIcon
name="i-ph-calendar-check-duotone"
class="w-8 h-8"
/>
</div>
<div>
<p class="text-xs font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
Data Collected Since
</p>
<div class="flex items-baseline gap-2">
<h3 class="text-xl font-bold text-neutral-900 dark:text-white">
{{ formattedDate }}
</h3>
<UBadge
color="neutral"
variant="soft"
size="sm"
>
{{ yearsCollected }}
</UBadge>
</div>
</div>
</div>
</UCard>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-if="topLanguages.length"
class="col-span-1 lg:col-span-1 space-y-4"
>
<h4 class="text-sm font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
<UIcon
name="i-ph-code-block-duotone"
class="text-red-500 w-5 h-5"
/>
Top Languages
</h4>
<div class="space-y-3">
<div
v-for="lang in topLanguages"
:key="lang.name"
class="space-y-1"
>
<div class="flex justify-between text-xs text-neutral-600 dark:text-neutral-300">
<span>{{ lang.name }}</span>
<span>{{ lang.percent }}%</span>
</div>
<UProgress
v-model="lang.percent"
color="red"
size="sm"
/>
</div>
</div>
</div>
<div
v-if="topEditors.length"
class="col-span-1 lg:col-span-1 space-y-4"
>
<h4 class="text-sm font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
<UIcon
name="i-ph-terminal-window-duotone"
class="text-green-500 w-5 h-5"
/>
Preferred Editors
</h4>
<div class="space-y-3">
<div
v-for="editor in topEditors"
:key="editor.name"
class="space-y-1"
>
<div class="flex justify-between text-xs text-neutral-600 dark:text-neutral-300">
<span>{{ editor.name }}</span>
<span>{{ editor.percent }}%</span>
</div>
<UProgress
v-model="editor.percent"
color="green"
size="sm"
/>
</div>
</div>
</div>
<div
v-if="topOS.length"
class="col-span-1 lg:col-span-1 space-y-4"
>
<h4 class="text-sm font-semibold text-neutral-900 dark:text-white flex items-center gap-2">
<UIcon
name="i-ph-desktop-duotone"
class="text-blue-500 w-5 h-5"
/>
Operating Systems
</h4>
<div class="space-y-3">
<div
v-for="osItem in topOS"
:key="osItem.name"
class="space-y-1"
>
<div class="flex justify-between text-xs text-neutral-600 dark:text-neutral-300">
<span>{{ osItem.name }}</span>
<span>{{ osItem.percent }}%</span>
</div>
<UProgress
v-model="osItem.percent"
color="blue"
size="sm"
/>
</div>
</div>
</div>
</div>
</div>
<div
v-else
class="grid grid-cols-1 md:grid-cols-2 gap-4"
>
<USkeleton class="h-24 w-full" />
<USkeleton class="h-24 w-full" />
</div>
</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

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import type { TimelineItem } from '@nuxt/ui'
const { education } = await useContent()
const { width } = useWindowSize()
const orientation = computed<'vertical' | 'horizontal'>(() =>
width.value >= 768 ? 'horizontal' : 'vertical'
)
const formatDate = (start?: string, end?: string, duration?: string) => {
if (!start) return 'N/A'
const startYear = new Date(start).getFullYear()
const endYear = end ? new Date(end).getFullYear() : 'Present'
const durationText = duration ? `(${duration})` : ''
if (startYear === endYear) {
return `${startYear} ${durationText}`
}
return `${startYear} - ${endYear} ${durationText}`
}
const items = computed<TimelineItem[]>(() => {
if (!education) return []
return [...education]
.sort((a, b) => (a.startDate || '').localeCompare(b.startDate || ''))
.map(item => ({
title: item.title || 'Degree',
description: item.institution || '',
date: formatDate(item.startDate, item.endDate, item.duration),
icon: item.icon || 'i-ph-graduation-cap-duotone'
}))
})
</script>
<template>
<ClientOnly>
<div class="w-full flex justify-center">
<UTimeline
:orientation="orientation"
:items="items"
:default-value="2"
size="lg"
color="neutral"
class="w-full max-w-5xl"
/>
</div>
<template #fallback>
<div class="flex flex-col gap-8 w-full max-w-5xl mx-auto pl-4 border-l border-neutral-200 dark:border-neutral-800">
<div
v-for="i in 3"
:key="i"
class="space-y-2"
>
<USkeleton class="h-4 w-1/4" />
<USkeleton class="h-4 w-1/2" />
</div>
</div>
</template>
</ClientOnly>
</template>

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import type { TimelineItem } from '@nuxt/ui'
const { experiences } = await useContent()
const { width } = useWindowSize()
const orientation = computed<'vertical' | 'horizontal'>(() =>
width.value >= 768 ? 'horizontal' : 'vertical'
)
const formatDate = (start?: string, end?: string, duration?: string) => {
if (!start) return 'N/A'
const startYear = new Date(start).getFullYear()
const endYear = end ? new Date(end).getFullYear() : 'Present'
const durationText = duration ? `(${duration})` : ''
if (startYear === endYear) {
return `${startYear} ${durationText}`
}
return `${startYear} - ${endYear} ${durationText}`
}
const items = computed<TimelineItem[]>(() => {
if (!experiences) return []
return [...experiences]
.sort((a, b) => (a.startDate || '').localeCompare(b.startDate || ''))
.map(item => ({
title: item.title || 'Role',
description: item.company || 'Freelance',
date: formatDate(item.startDate, item.endDate, item.duration),
icon: item.icon || 'i-ph-briefcase-duotone'
}))
})
</script>
<template>
<ClientOnly>
<div class="w-full flex justify-center">
<UTimeline
:orientation="orientation"
:items="items"
:default-value="items.length"
size="lg"
color="neutral"
class="w-full max-w-5xl"
/>
</div>
<template #fallback>
<div class="flex flex-col gap-8 w-full max-w-5xl mx-auto pl-4 border-l border-neutral-200 dark:border-neutral-800">
<div
v-for="i in 3"
:key="i"
class="space-y-2"
>
<USkeleton class="h-4 w-1/4" />
<USkeleton class="h-4 w-1/2" />
</div>
</div>
</template>
</ClientOnly>
</template>

View File

@@ -3,6 +3,7 @@ export async function useContent() {
const projects = await queryCollection('projects').where('extension', '=', 'md').order('publishedAt', 'DESC').all()
const education = await queryCollection('education').where('extension', '=', 'md').order('startDate', 'DESC').all()
const experiences = await queryCollection('experiences').where('extension', '=', 'md').order('startDate', 'DESC').all()
const contact = await queryCollection('contact').where('extension', '=', 'json').first()
return { skills, projects, education, experiences }
return { skills, projects, education, experiences, contact }
}

View File

@@ -0,0 +1,21 @@
export function useProjectColors() {
const statusColors: Record<string, string> = {
'Active': 'blue',
'Completed': 'green',
'Archived': 'neutral',
'In progress': 'amber'
}
const typeColors: Record<string, string> = {
'Personal Project': 'purple',
'Academic Project': 'sky',
'Infrastructure Project': 'emerald',
'Internship Project': 'orange',
'Research Project': 'blue'
}
return {
statusColors,
typeColors
}
}

View File

@@ -1,13 +0,0 @@
<script lang="ts" setup>
</script>
<template>
<div>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,13 +1,33 @@
<script lang="ts" setup>
useSeoMeta({
title: 'Coming Soon',
description: 'This page is currently under development.',
robots: 'noindex, nofollow'
})
</script>
<template>
<div>
</div>
<main class="min-h-[60vh] flex flex-col items-center justify-center p-4">
<div class="max-w-2xl text-center space-y-8">
<h1 class="p-2 text-4xl md:text-6xl font-bold bg-linear-to-r from-inverted/40 to-inverted/75 to-70% bg-clip-text text-transparent duration-200 my-12 md:my-24">
Coming Soon... Very Soon...
</h1>
<p class="text-lg md:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed">
This page is currently under development. Please check back later for exciting updates about my ecosystem, projects, and contributions.
</p>
<div class="pt-8">
<UButton
to="/"
icon="i-ph-arrow-left"
size="lg"
color="neutral"
variant="ghost"
>
Return Home
</UButton>
</div>
</div>
</main>
</template>
<style scoped>
</style>

View File

@@ -2,6 +2,29 @@
const { data: page } = await useAsyncData('hobbies', () => {
return queryCollection('hobbies').first()
})
const head = {
title: 'Balance & Perspectives',
description: 'Beyond the code. Exploring how competitive sports, motorsports strategy, and cultural experiences fuel my research resilience and cognitive flexibility.',
subtitle: ''
}
useSeoMeta({
title: head.title,
description: head.description,
ogTitle: `${head.title} • Arthur Danjou`,
ogDescription: head.description,
twitterCard: 'summary_large_image',
twitterTitle: head.title,
twitterDescription: head.description
})
defineOgImageComponent('NuxtSeo', {
title: head.title,
description: head.description,
subtitle: head.subtitle,
theme: '#F43F5E'
})
</script>
<template>

View File

@@ -1,7 +1,25 @@
<script lang="ts" setup>
const head = {
title: 'AI Safety & Applied Mathematics',
description: 'Research Engineer & Master 2 Student at Paris-Dauphine (ISF). Focusing on AI Alignment, Robustness, and Safe Deep Learning.',
subtitle: ''
}
useSeoMeta({
title: 'Arthur Danjou - AI enjoyer and Maths student',
description: 'Developer enjoying Artificial Intelligence and Machine Learning. Mathematics Student at Paris Dauphine-PSL University specialised in Statistics'
title: head.title,
description: head.description,
ogTitle: `Arthur Danjou • ${head.title}`,
ogDescription: head.description,
twitterCard: 'summary_large_image',
twitterTitle: head.title,
twitterDescription: head.description
})
defineOgImageComponent('NuxtSeo', {
title: head.title,
description: head.description,
subtitle: head.subtitle,
theme: '#F43F5E'
})
const { data: page } = await useAsyncData('index', () => {
@@ -16,12 +34,5 @@ const { data: page } = await useAsyncData('index', () => {
:value="page"
class="mt-8 md:mt-16"
/>
<HomeSkills />
<HomeEducation />
<HomeExperiences />
<HomeStats />
<HomeActivity />
<HomeQuote />
<HomeCatchPhrase />
</main>
</template>

View File

@@ -1,12 +0,0 @@
<script lang="ts" setup>
const { data: projects } = await useAsyncData('projects', () => {
return queryCollection('projects').all()
})
</script>
<template>
<div>
PROJECTS PAGE
{{ projects }}
</div>
</template>

View File

@@ -0,0 +1,170 @@
<script lang="ts" setup>
const route = useRoute()
const slug = route.params.slug as string
const { data: project } = await useAsyncData(`project-${slug}`, () => {
return queryCollection('projects').where('extension', '=', 'md').where('slug', '=', slug).first()
})
if (!project.value) {
throw createError({
statusCode: 404,
message: 'Project not found'
})
}
const projectWithBody = computed(() => {
if (!project.value) return null
return {
...project.value,
body: project.value.meta?.body
}
})
useSeoMeta({
title: project.value.title,
description: project.value.description,
ogTitle: `${project.value.title} • Arthur Danjou`,
ogDescription: project.value.description,
twitterCard: 'summary_large_image',
twitterTitle: project.value.title,
twitterDescription: project.value.description
})
defineOgImageComponent('NuxtSeo', {
title: project.value.title,
description: project.value.description,
subtitle: project.value.type ? `Project Type: ${project.value.type}` : '',
theme: '#F43F5E'
})
const { statusColors, typeColors } = useProjectColors()
const formattedDate = computed(() => {
if (!project.value?.publishedAt) return null
return new Date(project.value.publishedAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
})
</script>
<template>
<main
v-if="project"
class="space-y-8"
>
<div>
<UButton
to="/projects"
variant="ghost"
color="neutral"
icon="i-ph-arrow-left"
size="sm"
>
Back to Projects
</UButton>
</div>
<div class="space-y-4">
<div class="flex items-start gap-4">
<UIcon
v-if="project.icon"
:name="project.icon"
class="text-5xl text-neutral-700 dark:text-neutral-300 shrink-0"
/>
<div class="flex-1">
<h1 class="text-3xl sm:text-4xl font-bold text-neutral-900 dark:text-white mb-3">
{{ project.title }}
</h1>
<div class="flex flex-wrap items-center gap-2 mb-4">
<UBadge
v-if="project.type"
:color="(typeColors[project.type] || 'neutral') as any"
variant="subtle"
>
{{ project.type }}
</UBadge>
<UBadge
v-if="project.status"
:color="(statusColors[project.status] || 'neutral') as any"
variant="subtle"
>
{{ project.status }}
</UBadge>
<UBadge
v-if="project.favorite"
color="amber"
variant="subtle"
>
Favorite
</UBadge>
</div>
<p class="text-lg text-neutral-600 dark:text-neutral-400">
{{ project.description }}
</p>
<div class="flex flex-wrap items-center gap-4 mt-4 text-sm text-neutral-500 dark:text-neutral-400">
<span
v-if="formattedDate"
class="flex items-center gap-1"
>
<UIcon name="i-ph-calendar-duotone" />
{{ formattedDate }}
</span>
<span
v-if="project.readingTime"
class="flex items-center gap-1"
>
<UIcon name="i-ph-clock-duotone" />
{{ project.readingTime }} min read
</span>
</div>
</div>
</div>
<div
v-if="project.tags && project.tags.length > 0"
class="flex flex-wrap gap-2 pt-2"
>
<UBadge
v-for="tag in project.tags"
:key="tag"
color="neutral"
variant="subtle"
>
{{ tag }}
</UBadge>
</div>
</div>
<USeparator />
<ContentRenderer
v-if="projectWithBody"
:value="projectWithBody"
class="prose dark:prose-invert max-w-none
prose-headings:font-bold prose-headings:text-neutral-900 dark:prose-headings:text-white
prose-p:text-neutral-700 dark:prose-p:text-neutral-300
prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline
prose-strong:text-neutral-900 dark:prose-strong:text-white
prose-code:text-neutral-800 dark:prose-code:text-neutral-200
prose-pre:bg-neutral-100 dark:prose-pre:bg-neutral-800
prose-ul:text-neutral-700 dark:prose-ul:text-neutral-300
prose-ol:text-neutral-700 dark:prose-ol:text-neutral-300
prose-li:text-neutral-700 dark:prose-li:text-neutral-300
prose-blockquote:border-neutral-300 dark:prose-blockquote:border-neutral-700
prose-blockquote:text-neutral-700 dark:prose-blockquote:text-neutral-400
prose-img:rounded-lg prose-img:shadow-lg"
>
<template #empty>
<p class="text-neutral-600 dark:text-neutral-400">
No content available
</p>
</template>
</ContentRenderer>
</main>
</template>

View File

@@ -0,0 +1,278 @@
<script lang="ts" setup>
const { data: projects } = await useAsyncData('projects', () => {
return queryCollection('projects')
.where('extension', '=', 'md')
.order('favorite', 'DESC')
.order('publishedAt', 'DESC')
.all()
})
const head = {
title: 'Engineering & Research Labs',
description: 'Bridging the gap between theoretical models and production systems. Explore my experimental labs, open-source contributions, and engineering work.'
}
useSeoMeta({
title: head.title,
description: head.description,
ogTitle: `${head.title} • Arthur Danjou`,
ogDescription: head.description,
twitterCard: 'summary_large_image',
twitterTitle: head.title,
twitterDescription: head.description
})
defineOgImageComponent('NuxtSeo', {
title: head.title,
description: head.description,
theme: '#F43F5E'
})
const { statusColors, typeColors } = useProjectColors()
const selectedType = ref<string | null>(null)
const selectedStatus = ref<string | null>(null)
const availableTypes = computed(() => {
const types = new Set<string>()
projects.value?.forEach((project) => {
if (project.type) types.add(project.type)
})
return Array.from(types).sort()
})
const availableStatuses = computed(() => {
const statuses = new Set<string>()
projects.value?.forEach((project) => {
if (project.status) statuses.add(project.status)
})
return Array.from(statuses).sort()
})
const filteredProjects = computed(() => {
if (!projects.value) return []
return projects.value.filter((project) => {
const typeMatch = !selectedType.value || project.type === selectedType.value
const statusMatch = !selectedStatus.value || project.status === selectedStatus.value
return typeMatch && statusMatch
})
})
function clearFilters() {
selectedType.value = null
selectedStatus.value = null
}
const hasActiveFilters = computed(() => !!selectedType.value || !!selectedStatus.value)
const activeFilterCount = computed(() => (selectedType.value ? 1 : 0) + (selectedStatus.value ? 1 : 0))
</script>
<template>
<main class="space-y-8 py-4">
<div class="space-y-4">
<h1 class="text-3xl sm:text-4xl font-bold text-neutral-900 dark:text-white font-mono tracking-tight">
Engineering & Research Labs
</h1>
<p class="max-w-3xl leading-relaxed">
Bridging the gap between theoretical models and production systems. Explore my experimental labs, open-source contributions, and engineering work.
</p>
</div>
<div class="flex flex-col gap-4">
<div class="flex items-center gap-2 overflow-x-auto w-full whitespace-nowrap pb-2">
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300 mr-2 min-w-12.5">Type:</span>
<UButton
:variant="!selectedType ? 'solid' : 'ghost'"
color="neutral"
size="sm"
@click="selectedType = null"
>
All
</UButton>
<UButton
v-for="type in availableTypes"
:key="type"
:variant="selectedType === type ? 'subtle' : 'ghost'"
:color="(typeColors[type] || 'neutral') as any"
size="sm"
class="transition-all duration-200"
:class="selectedType === type ? 'ring-1 ring-inset' : ''"
@click="selectedType = selectedType === type ? null : type"
>
{{ type }}
</UButton>
</div>
<div class="flex items-center gap-4 overflow-x-auto flex-nowrap md:flex-wrap w-full whitespace-nowrap pb-2">
<div class="flex gap-2 items-center">
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Status:</span>
<UButton
:variant="!selectedStatus ? 'solid' : 'ghost'"
color="neutral"
size="sm"
@click="selectedStatus = null"
>
All
</UButton>
<UButton
v-for="status in availableStatuses"
:key="status"
:variant="selectedStatus === status ? 'solid' : 'ghost'"
:color="(statusColors[status] || 'neutral') as any"
size="sm"
@click="selectedStatus = selectedStatus === status ? null : status"
>
{{ status }}
</UButton>
</div>
<div class="ml-auto">
<UButton
v-if="hasActiveFilters"
variant="ghost"
color="neutral"
size="sm"
icon="i-ph-x-circle-duotone"
aria-label="Clear filters"
@click="clearFilters"
>
Clear filters ({{ activeFilterCount }})
</UButton>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UCard
v-for="project in filteredProjects"
:key="project.slug"
class="relative hover:shadow-sm transition-all duration-300 group"
>
<template #header>
<div class="flex items-start gap-4">
<div
class="p-2 rounded-lg shrink-0 flex items-center justify-center"
:class="project.favorite ? 'ring-2 ring-amber-400 text-amber-400' : 'bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300'"
>
<UIcon
:name="project.icon || 'i-ph-code-duotone'"
class="w-6 h-6"
/>
</div>
<div class="flex-1 min-w-0">
<UTooltip
:text="project.title"
:popper="{ placement: 'top-start' }"
class="w-full relative z-10"
>
<NuxtLink
:to="`/projects/${project.slug}`"
class="block focus:outline-none"
>
<h3 class="text-lg font-bold truncate group-hover:text-neutral-900 text-neutral-500 dark:group-hover:text-white transition-colors duration-200">
{{ project.title }}
</h3>
</NuxtLink>
</UTooltip>
<div class="flex items-center gap-2 mt-2 flex-wrap relative z-10">
<UBadge
v-if="project.type"
:color="(typeColors[project.type] || 'neutral') as any"
variant="subtle"
size="xs"
>
{{ project.type }}
</UBadge>
<UBadge
v-if="project.status"
:color="(statusColors[project.status] || 'neutral') as any"
variant="subtle"
size="xs"
>
{{ project.status }}
</UBadge>
<UBadge
v-if="project.favorite"
color="amber"
variant="subtle"
size="xs"
>
</UBadge>
</div>
</div>
</div>
</template>
<p class="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-3 leading-relaxed">
{{ project.description }}
</p>
<template #footer>
<div class="flex items-center justify-between">
<div class="flex flex-wrap gap-1">
<UBadge
v-for="tag in project.tags?.slice(0, 3)"
:key="tag"
color="neutral"
variant="outline"
size="xs"
class="opacity-75"
>
{{ tag }}
</UBadge>
<span
v-if="project.tags && project.tags.length > 3"
class="text-xs text-neutral-400 font-mono ml-1 self-center"
>
+{{ project.tags.length - 3 }}
</span>
</div>
<span
v-if="project.readingTime"
class="text-xs text-neutral-400 font-mono flex items-center gap-1 shrink-0 ml-2"
>
<UIcon
name="i-ph-hourglass-medium-duotone"
class="w-3 h-3"
/>
{{ project.readingTime }}m
</span>
</div>
</template>
<NuxtLink
:to="`/projects/${project.slug}`"
:aria-label="`Open project: ${project.title}`"
class="absolute inset-0 z-0"
/>
</UCard>
</div>
<div
v-if="filteredProjects.length === 0"
class="text-center py-20 border border-dashed border-neutral-200 dark:border-neutral-800 rounded-xl"
>
<UIcon
name="i-ph-flask-duotone"
class="text-6xl text-neutral-300 dark:text-neutral-700 mb-4"
/>
<h3 class="text-lg font-medium text-neutral-900 dark:text-white">
No experiments found
</h3>
<p class="text-neutral-500 dark:text-neutral-400 mb-6">
No projects match the selected filters.
</p>
<UButton
color="primary"
variant="soft"
@click="clearFilters"
>
Clear Filters
</UButton>
</div>
</div>
</main>
</template>

View File

@@ -1,13 +0,0 @@
<script lang="ts" setup>
</script>
<template>
<div>
STATS PAGE
</div>
</template>
<style scoped>
</style>

View File

@@ -2,6 +2,29 @@
const { data: page } = await useAsyncData('uses', () => {
return queryCollection('uses').first()
})
const head = {
title: 'Research Lab & Technical Setup',
description: 'A curated list of the hardware, software, and self-hosted infrastructure that powers my mathematical modeling and AI research workflows.',
subtitle: 'The gear powering my research & development workflow.'
}
useSeoMeta({
title: head.title,
description: head.description,
ogTitle: `${head.title} • Arthur Danjou`,
ogDescription: head.description,
twitterCard: 'summary_large_image',
twitterTitle: head.title,
twitterDescription: head.description
})
defineOgImageComponent('NuxtSeo', {
title: head.title,
subtitle: head.subtitle,
description: head.description,
theme: '#F43F5E'
})
</script>
<template>

547
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,28 @@
import { defineCollection, z } from '@nuxt/content'
import { asSeoCollection } from '@nuxtjs/seo/content'
export const collections = {
index: defineCollection({
type: 'page',
source: 'index.md'
}),
projects: defineCollection({
type: 'data',
source: 'projects/*.md',
schema: z.object({
slug: z.string(),
title: z.string(),
type: z.string().optional(),
description: z.string(),
publishedAt: z.string(),
readingTime: z.number().optional(),
tags: z.array(z.string()),
cover: z.string().optional(),
favorite: z.boolean().optional(),
status: z.string().optional(),
emoji: z.string().optional()
})
}),
projects: defineCollection(
asSeoCollection({
type: 'data',
source: 'projects/*.md',
schema: z.object({
slug: z.string(),
title: z.string(),
type: z.string().optional(),
description: z.string(),
publishedAt: z.string(),
readingTime: z.number().optional(),
tags: z.array(z.string()),
favorite: z.boolean().optional(),
status: z.enum(['active', 'completed', 'archived', 'in progress']),
icon: z.string()
})
})),
uses: defineCollection({
type: 'page',
source: 'uses.md'
@@ -55,7 +56,7 @@ export const collections = {
location: z.string(),
description: z.string(),
tags: z.array(z.string()).optional(),
emoji: z.string().optional()
icon: z.string()
})
}),
education: defineCollection({
@@ -63,7 +64,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(),
@@ -72,7 +72,7 @@ export const collections = {
location: z.string(),
description: z.string().optional(),
tags: z.array(z.string()).optional(),
emoji: z.string().optional()
icon: z.string()
})
}),
contact: defineCollection({

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,11 +10,11 @@
},
{
"id": "professional-email",
"name": "Email Professionnel",
"name": "Professional Email",
"category": "communication",
"icon": "i-ph-envelope-simple-duotone",
"value": "https://go.arthurdanjou.fr/mail-pro",
"priority": 1
"priority": 2
},
{
"id": "linkedin",
@@ -22,7 +22,7 @@
"category": "social",
"icon": "i-ph:linkedin-logo-duotone",
"value": "https://go.arthurdanjou.fr/linkedin",
"priority": 2
"priority": 1
},
{
"id": "github",
@@ -39,7 +39,7 @@
"category": "social",
"icon": "i-ph:x-logo-duotone",
"value": "https://go.arthurdanjou.fr/twitter",
"priority": 3
"priority": 1
},
{
"id": "discord",
@@ -47,23 +47,23 @@
"category": "communication",
"icon": "i-ph:discord-logo-duotone",
"value": "https://go.arthurdanjou.fr/discord",
"priority": 2
},
{
"id": "personal-website",
"name": "Site Personnel",
"category": "web",
"icon": "i-ph:globe-duotone",
"value": "https://arthurdanjou.fr",
"priority": 1
},
{
"id": "personal-website",
"name": "Portfolio",
"category": "web",
"icon": "i-ph:globe-duotone",
"value": "https://arthurdanjou.fr",
"priority": 2
},
{
"id": "status-page",
"name": "Statut des Services",
"name": "Status Page",
"category": "infrastructure",
"icon": "i-ph:fire-duotone",
"value": "https://go.arthurdanjou.fr/status",
"priority": 3
"priority": 2
}
]
}

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
@@ -11,5 +11,5 @@ tags:
- Mathematics
- Physics
- Computer Science
emoji: 🎓
icon: i-ph-math-operations-duotone
---

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,10 +1,10 @@
---
title: Master's Degree in Applied Mathematics (Year 1)
type: Master
degree: Master
institution: Paris Dauphine-PSL University
location: Paris, France
startDate: 2023-09
endDate: 2024-06
startDate: 2024-09
endDate: 2025-06
duration: 1 year
description: First year of specialized study in applied mathematics, combining theoretical knowledge with practical applications in data science, optimization, and machine learning.
tags:
@@ -12,5 +12,5 @@ tags:
- Data Science
- Machine Learning
- Optimization
emoji: 📚
icon: i-ph-number-circle-one-duotone
---

View File

@@ -1,10 +1,10 @@
---
title: Master's Degree in Applied Mathematics (Year 2)
type: Master
degree: Master
institution: Paris Dauphine-PSL University
location: Paris, France
startDate: 2024-09
endDate: 2025-10
startDate: 2025-09
endDate: 2026-10
duration: 1 year
description: Second year of advanced study in applied mathematics with focus on specialized topics, research projects, and professional applications in industry and research.
tags:
@@ -12,5 +12,5 @@ tags:
- Advanced Machine Learning
- Data Engineering
- Research
emoji: 🏆
icon: i-ph-number-circle-two-duotone
---

View File

@@ -14,5 +14,5 @@ tags:
- HomeLab
- Docker
- Self-Hosted
emoji: 💼
icon: i-ph-briefcase-metal-duotone
---

View File

@@ -14,5 +14,5 @@ tags:
- Minecraft
- Backend
- Game Development
emoji: 🎮
icon: i-ph-cube-duotone
---

View File

@@ -1,7 +1,7 @@
---
title: Hackathon CND - Machine Learning for Cybersecurity
type: Hackathon
company: Commissariat au numérique de défense (CND), French Armies ministry
company: French Armies ministry
companyUrl: https://www.defense.gouv.fr/cnd
location: Fort du Mont-Valérien, Suresnes, France
startDate: 2025-11
@@ -15,5 +15,5 @@ tags:
- Cybersecurity
- Streamlit
- LLM
emoji: 🔒
icon: i-ph-shield-check-duotone
---

View File

@@ -13,5 +13,5 @@ tags:
- Customer Service
- Retail
- Team Work
emoji: 🛒
icon: i-ph-shopping-cart-duotone
---

View File

@@ -14,5 +14,5 @@ tags:
- Microsoft Azure
- Data Engineering
- Data Quality
emoji: 📊
icon: i-ph-dog-duotone
---

View File

@@ -1,28 +1,61 @@
---
title: "Balance and Drive: Beyond the Data"
description: Exploring my passions outside of data science and machine learning engineering that fuel my creativity and performance.
title: Balance & Perspectives
description: Exploring the passions—from competitive sports to high-velocity optimization—that fuel my creativity and resilience.
---
While my passion for data science and machine learning engineering is at the core of what I do, I am convinced that personal balance is the key to performance and creativity. Outside of my technical projects, I nurture this balance through several key interests.
# Beyond the Lab
## ⚽ Sports & Team Dynamics
Research demands deep focus, but breakthrough ideas often come from stepping back. I believe that **cognitive flexibility**—the ability to switch between intense analytical work and creative exploration—is key to sustaining long-term performance in AI.
**Rugby and volleyball** are fundamental to my equilibrium. These team sports have taught me the importance of collective strategy, communication, and physical commitment. The dynamics of working under pressure with diverse personalities mirrors the collaborative nature of ML projects.
---
As a long-time supporter of **PSG** in football, I appreciate the tactical analysis, performance management, and data-driven decision-making that occurs at the highest level of sport—much like optimizing complex systems.
## ⚡ High-Velocity Interests
## 🎵 Music & Creative Problem-Solving
I am drawn to environments where strategy, speed, and precision intersect. These are not just pastimes, but exercises in optimization under constraints.
**Music** is my creative outlet and a tool for different thinking patterns. Training my ear to recognize harmony and structure translates directly to identifying elegant solutions in system design, architecture, and mathematical modeling. It reinforces my belief that great engineering, like great music, requires both technical precision and artistic intuition.
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
## 🌍 Travel & Cultural Adaptation
::card{title="Motorsports Strategy" icon="i-ph-flag-checkered-duotone"}
**Formula 1 Enthusiast**
I am fascinated by F1 as the pinnacle of **real-time optimization**. Decisions are made in milliseconds based on :hover-text{text="telemetry data" hover="Tyre degradation, fuel load, weather"}, mirroring the constraints of deploying ML models in production.
* **Focus:** The intersection of human instinct and data-driven strategy.
::
**Travel** provides essential perspective and adaptability. Having discovered highly diverse cultures since childhood—from Egypt and South Africa to Thailand and the United States—has profoundly shaped my curiosity and flexibility. This exposure to different ways of thinking and problem-solving is crucial in a constantly evolving field like AI, where understanding multiple perspectives can lead to breakthrough insights.
::card{title="Competitive Sports" icon="i-ph-trophy-duotone"}
**Rugby & Volleyball**
Team sports are my foundation for resilience. As a :hover-text{text="former Rugby Team Captain" hover="School Championships Participant"}, I learned that leadership isn't about giving orders—it's about maintaining cohesion under pressure.
* **Takeaway:** Collective intelligence always outperforms individual brilliance.
::
## 🏎️ Motorsports & Optimization
::
As a **Formula 1 enthusiast**, I'm fascinated by the pursuit of pure performance and optimization under constraints. F1 represents the pinnacle of real-time, data-driven strategy, where decisions are made in milliseconds based on telemetry, weather, and tactical considerations. This mirrors my approach to machine learning: extracting maximum value from available resources and constraints.
---
## 🎯 Balance as Performance
## 🌍 Perspectives & Culture
These passions are not escapes—they're integral to my professional success. They reinforce my commitment to continuous improvement, adaptability, and the drive to progress. It is this balance that allows me to approach every new challenge with motivation, energy, and fresh perspective.
Curiosity is the fuel of a researcher. Expanding my horizon helps me approach problems with fresh angles.
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
::card{title="Global Exploration" icon="i-ph-airplane-tilt-duotone"}
**Travel & Adaptation**
Exposure to diverse systems fosters adaptability. From the history of **Egypt** and the landscapes of **South Africa** to the vibrancy of **Thailand** and the **USA**, every environment challenges my default way of thinking.
* **Goal:** Understanding complex systems by observing different cultures.
::
::card{title="Tactical Analysis" icon="i-ph-soccer-ball-duotone"}
**Paris Saint-Germain**
As a long-time supporter of **PSG**, I appreciate the tactical analysis and performance management at the highest level. Football is a game of :hover-text{text="spatial optimization" hover="Controlling space & transitions"}, much like architecting a neural network.
::
::
---
## 🎵 Creative Patterns
**Music** serves as my cognitive reset. Training my ear to recognize harmony and structure translates directly to identifying elegant solutions in system design. It reinforces my belief that great engineering, like great music, requires both **technical precision** and **artistic intuition**.
::card{title="Philosophy" icon="i-ph-sparkle-duotone"}
"Balance is not something you find, it's something you create."
::

View File

@@ -1,35 +1,68 @@
---
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.
title: Arthur Danjou • AI Safety & Applied Mathematics
description: Research Engineer & Master 2 Student at Paris-Dauphine (ISF). Focusing on AI Alignment, Robustness, and Safe Deep Learning.
---
Hey, I'm :home-name, a Mathematics student in Statistics at the Paris-Dauphine University in France.
Hey, I'm :home-name, a :hover-text{text="Master 2 student in Statistical & Financial Engineering (Master ISF)" hover="Université Paris-Dauphine - PSL" position="bottom"} at Paris-Dauphine University.
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.
I sit at the intersection of :hover-text{hover="Learning Theory, RL & Advanced ML 🧠" position="top" text="theoretical research"} and :hover-text{hover="From MLOps to Production 🚀" position="right" text="software engineering"}. Unlike a pure theorist, I build what I model. Unlike a pure developer, I understand the math behind the code.
As a software engineer and mathematics student, my :hover-text{hover="My bag of knowledge 🎒" text="expertise"} covers
:prose-icon[TypeScript]{color="blue" icon="i-logos:typescript-icon"},
:prose-icon[Vue]{color="green" icon="i-logos:vue"},
:prose-icon[Nuxt]{color="emerald" icon="i-logos:nuxt-icon"},
:prose-icon[Adonis]{color="purple" icon="i-logos:adonisjs-icon"},
:prose-icon[Java]{color="red" icon="i-logos:java"},
I am dedicating my research to :hover-text{hover="Alignment, Robustness & Interpretability 🧭" text="AI Safety"}. I will soon start my Master's Thesis focusing on :hover-text{hover="Adversarial Defenses & Formal Verification 🛡️" text="Cybersecurity"} and :hover-text{hover="Ensuring AI alignment and stability 🤝" text="Safe Deep Learning"}, exploring how to make AI systems mathematically robust and secure.
To drive this research, I leverage
:prose-icon[Python]{color="amber" icon="i-logos:python"},
:prose-icon[R]{color="blue" 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 use tools like
:prose-icon[scikit-learn]{color="orange" icon="devicon-scikitlearn"} for supervised learning,
:prose-icon[pandas]{color="blue" icon="i-logos:pandas-icon"} for efficient data manipulation,
:prose-icon[NumPy]{color="indigo" icon="i-logos:numpy"} for scientific computation, and
:prose-icon[TensorFlow]{color="orange" icon="i-logos:tensorflow"} as well as :prose-icon[PyTorch]{color="orange" icon="i-logos:pytorch-icon"} to build and train deep learning models.
I also learned other important technologies, such as
:prose-icon[Docker]{color="sky" icon="i-logos:docker-icon"},
:prose-icon[Redis]{color="red" icon="i-logos:redis"},
:prose-icon[MySQL]{color="zinc" icon="i-logos:mysql-icon"} and
:prose-icon[Git]{color="orange" icon="i-logos:git-icon"} to :hover-text{hover="All these technologies complement each other 🔗" text="complete"} my knowledge.
:prose-icon[PyTorch]{color="orange" icon="i-logos:pytorch-icon"} and
:prose-icon[R]{color="blue" icon="i-logos:r-lang"} to design architectures, relying on
:prose-icon[Docker]{color="sky" icon="i-logos:docker-icon"} and
:prose-icon[Linux]{color="zinc" icon="i-logos:linux-tux"} to ensure reproducibility within my :hover-text{hover="I self-host my own GPU cluster 🔌" text="homelab"}.
These tools allow me to go from :hover-text{hover="Exploration, cleaning, reshaping…" text="data preparation"} to :hover-text{hover="Training, evaluation, deployment" text="deployment"} of models in real-world environments, with statistical rigor and performance in mind. I'm passionate about AI and :hover-text{hover="AI is the future of technology 🤖" text="data science"} .
When I'm not deriving generalization bounds or fixing pipelines, I enjoy :hover-text{hover="Former Team Captain 🏉" text="Rugby"} and :hover-text{hover="Exploring the world 🌍" text="Traveling"}.
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.
---
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"} .
## 🛠 Scientific & Technical Arsenal
My research capabilities rely on a :hover-text{text="dual expertise" hover="The Scientist & The Builder"}: :hover-text{text="advanced mathematical modeling" hover="Stochastic Calculus, Optimization, Probability"} for conception, and :hover-text{text="robust engineering" hover="CI/CD, Docker, Kubernetes"} for execution.
::home-skills
::
---
## 💼 Research & Engineering Path
Theoretical knowledge is nothing without concrete application. From :hover-text{text="building distributed systems" hover="High-availability architectures"} to designing :hover-text{text="defensive AI pipelines" hover="Adversarial Robustness"}, my journey reflects a constant shift towards critical challenges.
::home-timeline-experiences{class="mb-8"}
::
---
## 🎓 Academic Foundation
Mathematical rigor is the cornerstone of Safe AI. My background in :hover-text{text="Statistics, Probability, and Optimization" hover="The M280 Trinity 📐"} provides the necessary tools to understand and secure modern Deep Learning architectures.
::home-timeline-education{class="mb-8"}
::
---
## 📊 Live Telemetry
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-live-status-page{class="mb-4"}
::
::home-live-activity{class="mb-4"}
::
::home-live-stats
---
::home-quote
::
::home-catch-phrase
::

View File

@@ -1,98 +0,0 @@
---
title: Arthur Danjou - Data Science & Applied AI Student.
description: Profile of Arthur Danjou, a Data Science and Applied AI student at Paris Dauphine-PSL University, highlighting his skills, experience, and career aspirations.
---
# Arthur Danjou
**Data Science & Applied AI**
Rigorous, curious, and motivated, I put my skills in statistics, machine learning, and applied artificial intelligence to work on concrete and innovative projects[cite: 9]. Passionate about mathematical modelling and the deployment of AI solutions, I enjoy transforming theory into measurable results[cite: 10].
## 📞 Contact & Links
* **Email:** `arthur.danjou@dauphine.eu`
* **Portfolio:** `go.arthurdanjou.fr/portfolio`
* **GitHub:** `go.arthurdanjou.fr/github`
* **LinkedIn:** `go.arthurdanjou.fr/linkedin`
## 📍 Location
* **Current:** Paris, France
* **Timezone:** Europe/Paris (CET/CEST)
* **Remote:** Open (confirmed by "REMOTE" experience)
## 🗓️ Availability
* **Status:** Available for a final-year internship.
* **Start Date:** **April 2026**.
* **Contract Types:** Internship (priority).
## 🎯 Career Goals
* To join a team of Data Scientists or AI Researchers to deepen my knowledge.
* Contribute to high-impact projects.
* **Prepare for a future doctorate in artificial intelligence**.
* Become an expert in Machine Learning Engineering and MLOps.
* Combine mathematical rigor (from education) with practical engineering solutions (from experience).
## 💼 Work Preferences
* **Target Roles:** Data Scientist, AI Researcher.
* **Industries:** AI/ML, Data Science, Health, DevOps.
* **Work Style:** Remote, Hybrid.
* **Company Size:** Startup, Scale-up, Enterprise.
## 🏆 Notable Achievements
* Administration of a personal home lab (servers, databases, storage, backups) for MLOps experimentation since 2022.
* Implemented daily data cleaning pipelines on Azure with PySpark, enhancing data quality by 20-30% at Sevetys.
* Design, development, and maintenance of web, data, and cloud projects (Python, TypeScript, Nuxt 3) via ArtDanj Production.
* Developed an automated monthly data quality report (completeness, consistency) for business teams.
## 📚 Education
* **Master's in Applied Mathematics** (M280) - Paris Dauphine-PSL University (2023-2025)
- Specialization: Data Science & Applied Artificial Intelligence
- Focus: Deep Learning, Probabilistic Models, Statistical Learning Theory
* **Bachelor's in Applied Mathematics** - Paris Dauphine-PSL University (2020-2023)
- Foundation in linear algebra, probability, statistics, and numerical analysis
## 🔐 Certifications & Competencies
* Advanced Python & Data Science practices
* MLOps & Cloud Infrastructure (Azure, Docker, Kubernetes)
* Full-stack Web Development (Nuxt 3, TypeScript, Vue.js)
* Linux System Administration & Networking
## 🎓 Research & Academic Interests
* Machine Learning Engineering and deployment pipelines
* Probabilistic inference and Bayesian methods
* Statistical learning theory and generalization bounds
* Deep Learning architectures for structured data
* Data quality and governance in production systems
* Former rugby team captain, participating in the French school championships.
## 📚 Education & Core Competencies
### Paris Dauphine-PSL University (MSc)
* **Dual Expertise:** Theory & Practice in Advanced Data Science and AI Systems.
* **Core Theoretical Focus:** Strong background in statistical modeling and advanced AI principles (Advanced Machine Learning, Learning Theory, Clustering, Deep Learning, Climate Risk Modeling).
* **Practical Skills:** Hands-on experience in NLP, Reinforcement Learning, Generative AI, Data Quality, and Data Visualisation.
* **Key Courses (M1/M2):** Supervised Statistical Learning, Generalised Linear Models (GLMs), Monte Carlo Methods, Data Analysis, Non-parametric Statistics, Time Series, Numerical Optimisation.
* **Active Engagement:** Scheduled participation in the Natixis and DIRISI Hackathons.
### Technical Skillset
* **Programming:** Python, R, TypeScript, Java, Git, LaTeX.
* **Libraries & Frameworks:** NumPy, Pandas, Scikit-learn, PyTorch, Matplotlib, Seaborn.
* **Databases:** SQL, Redis.
* **Cloud & DevOps:** Proxmox, Docker, Azure, Linux, SysAdmin.
### Statistical & AI Modeling
* **Multivariate Data Analysis:** Principal Component Analysis (PCA), Correspondence Analysis (CA), clustering techniques, correlation analysis.
* **Supervised Learning:** k-NN, linear and logistic regression, CNN, Naive Bayes.
* **Unsupervised Learning:** Clustering, dimensionality reduction, k-means, CNN.
* **IA & Modern Models:** Natural Language Processing, Transformers, Large Language Models, AI agents, embeddings, and fine-tuning.

View File

@@ -1,38 +0,0 @@
---
slug: artchat
title: ArtChat - Portfolio & Blog
type: Personal Project
description: My personal space on the web — a portfolio, a blog, and a digital lab where I showcase my projects, write about topics I care about, and experiment with design and web technologies.
publishedAt: 2024-06-01
readingTime: 1
cover: artchat/cover.png
favorite: true
status: Active
tags:
- Vue.js
- Nuxt
- TypeScript
- Tailwind CSS
- Web
emoji: 🌍
---
[**ArtChat**](https://go.arthurdanjou.fr/website) is my personal space on the web — a portfolio, a blog, and a digital lab where I showcase my projects, write about topics I care about, and experiment with design and web technologies.
It's designed to be fast, accessible, and fully responsive. The site also serves as a playground to explore and test modern frontend tools.
## ⚒️ Tech Stack
- **UI** → [Vue.js](https://vuejs.org/): A progressive JavaScript framework for building interactive interfaces.
- **Framework** → [Nuxt](https://nuxt.com/): A powerful full-stack framework built on Vue, perfect for modern web apps.
- **Content System** → [Nuxt Content](https://content.nuxtjs.org/): File-based CMS to manage blog posts and pages using Markdown.
- **Design System** → [Nuxt UI](https://nuxtui.com/): Fully styled, customizable UI components tailored for Nuxt.
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio): Visual editing and content management integrated with Nuxt Content.
- **Language** → [TypeScript](https://www.typescriptlang.org/): A statically typed superset of JavaScript.
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/): Utility-first CSS framework enhanced with SCSS flexibility.
- **Deployment** → [NuxtHub](https://hub.nuxt.com/): Cloudflare-powered platform for fast, scalable Nuxt app deployment.
- **Package Manager** → [pnpm](https://pnpm.io/): A fast, disk-efficient package manager for JavaScript/TypeScript projects.
- **Linter** → [ESLint](https://eslint.org/): A tool for identifying and fixing problems in JavaScript/TypeScript code.
- **ORM** → [Drizzle ORM](https://orm.drizzle.team/): A lightweight, type-safe ORM for TypeScript.
- **Validation** → [Zod](https://zod.dev/): A TypeScript-first schema declaration and validation library with full static type inference.
- **Deployment** → [NuxtHub](https://hub.nuxt.com/): A platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.

View File

@@ -5,26 +5,24 @@ type: Personal Project
description: A customizable browser homepage that lets you organize all your favorite links in one place with categories, tabs, icons and colors.
publishedAt: 2024-09-04
readingTime: 1
cover: arthome/cover.png
status: Active
status: Archived
tags:
- Nuxt
- Vue.js
- Web
- Productivity
emoji: 🏡
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

@@ -5,7 +5,6 @@ type: Infrastructure Project
description: A personal homelab environment where I deploy, test, and maintain self-hosted services with privacy-focused networking through VPN and Cloudflare Tunnels.
publishedAt: 2025-09-04
readingTime: 1
cover: artlab/cover.png
favorite: true
status: Active
tags:
@@ -14,13 +13,12 @@ tags:
- HomeLab
- Self-Hosted
- Infrastructure
emoji: 🏡
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
@@ -37,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

@@ -0,0 +1,45 @@
---
slug: artsite
title: ArtSite - Personal Research Hub
type: Personal Project
description: My digital headquarters. A high-performance portfolio built on the Edge using the full Nuxt ecosystem, deployed to Cloudflare Workers via Wrangler.
publishedAt: 2024-06-01
readingTime: 2
favorite: true
status: Active
tags:
- Nuxt
- NuxtHub
- Cloudflare Workers
- TypeScript
icon: i-ph-globe-hemisphere-west-duotone
---
[**ArtSite**](https://go.arthurdanjou.fr/website) is my digital headquarters—a unified platform serving as my engineering portfolio and experimental lab.
More than just a static site, it is a modern **Portfolio** designed to be fast, accessible, and type-safe. It serves as a live production environment where I experiment with the latest frontend technologies and Edge computing paradigms.
## ⚡ The Nuxt Stack Architecture
This project is built entirely on the **Nuxt ecosystem**, leveraging the synergy between its modules for maximum developer experience and performance.
### Core Engine
- **[Nuxt 3](https://nuxt.com/)**: The meta-framework providing the backbone (SSR, Auto-imports, Modules).
- **[Nitro](https://nitro.unjs.io/)**: The high-performance server engine that powers the API routes and renders the app at the Edge.
### Infrastructure & Deployment
- **[Cloudflare Workers](https://workers.cloudflare.com/)**: The application runs entirely on Cloudflare's global serverless network (SSR), ensuring minimal latency and high resilience.
- **[Wrangler](https://developers.cloudflare.com/workers/wrangler/)**: The command-line tool used for precise deployment pipelines and worker configuration.
- **[NuxtHub](https://hub.nuxt.com/)**: Integrated specifically for **advanced cache management** and unifying Cloudflare platform features (KV, D1, Blob) within the Nuxt runtime.
### Content & Data
- **[Nuxt Content](https://content.nuxtjs.org/)**: A Git-based Headless CMS that treats Markdown as a database.
- **[Nuxt Studio](https://nuxt.studio)**: A live visual editor allowing for seamless content management directly from the browser.
### Interface & Design
- **[Nuxt UI](https://nuxtui.com/)**: A comprehensive component library built on Headless UI and Tailwind CSS.
- **[Tailwind CSS](https://tailwindcss.com/)**: Utility-first styling for rapid and responsive design.
### Quality Assurance
- **[TypeScript](https://www.typescriptlang.org/)**: Strict type safety across the entire stack (Frontend & Backend).
- **[Zod](https://zod.dev/)**: Runtime schema validation for API inputs and environment variables.

View File

@@ -6,21 +6,18 @@ description: A curated collection of mathematics and data science projects devel
publishedAt: 2023-09-01
readingTime: 1
favorite: true
status: Active
status: In progress
tags:
- Python
- R
- Data Science
- Machine Learning
- Mathematics
emoji: 🎓
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
emoji: 🚲
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
emoji: 💉
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,54 @@
---
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: Completed
tags:
- R
- R Shiny
- Data Visualization
- ggplot2
icon: i-ph-chart-bar-duotone
---
::warning
The project is currently in progress, and more details will be added as development continues.
::
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.
- **[tidyverse](https://www.tidyverse.org)**: A collection of R packages designed for data science that share an underlying design philosophy, grammar, and data structures.
- **[sf](https://r-spatial.github.io/sf/)**: An R package for working with simple features, providing support for spatial data manipulation and analysis.
- **[rnaturalearth](https://docs.ropensci.org/rnaturalearth/)**: An R package that provides easy access to natural earth map data for creating geographical visualizations.
- **[rnaturalearthdata](https://github.com/ropensci/rnaturalearthdata)**: Companion package to rnaturalearth containing large natural earth datasets.
- **[knitr](https://yihui.org/knitr/)**: An R package for dynamic report generation, enabling the integration of code and text.
- **[kableExtra](https://haozhu233.github.io/kableExtra/)**: An R package for customizing tables and enhancing their visual presentation.
- **[gridExtra](https://cran.r-project.org/web/packages/gridExtra/)**: An R package for arranging multiple grid-based plots on a single page.
- **[moments](https://cran.r-project.org/web/packages/moments/)**: An R package for computing moments, skewness, kurtosis and related statistics.
- **[factoextra](http://www.sthda.com/english/rpkgs/factoextra/)**: An R package for multivariate data analysis and visualization, including PCA and clustering methods.
- **[shinydashboard](https://rstudio.github.io/shinydashboard/)**: An R package for creating dashboards with Shiny.
- **[leaflet](https://rstudio.github.io/leaflet/)**: An R package for creating interactive maps using the Leaflet JavaScript library.
- **[plotly](https://plotly.com/r/)**: An R package for creating interactive visualizations with the Plotly library.
- **[RColorBrewer](https://cran.r-project.org/web/packages/RColorBrewer/)**: An R package providing color palettes for maps and other graphics.
- **[DT](https://rstudio.github.io/DT/)**: An R package for creating interactive data tables.
## 📚 Resources
You can find the code here: [Data Visualisation Code](https://go.arthurdanjou.fr/datavis-code)
And the online application here: [Data Visualisation App](https://go.arthurdanjou.fr/datavis-app)
## 📄 Detailed Report
<iframe src="/projects/datavis.pdf" width="100%" height="1000px">
</iframe>

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
status: Active
readingTime: 6
status: Completed
tags:
- Python
- TensorFlow
- Machine Learning
- Deep Learning
- Research
emoji: 🔬
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
emoji: 💰
- 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
emoji: 💻
- 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

@@ -12,12 +12,16 @@ tags:
- Statistics
- Modeling
- Mathematics
emoji: 📊
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

@@ -12,20 +12,22 @@ tags:
- Data Engineering
- Azure
- Big Data
emoji: 🐶
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

@@ -1,79 +1,116 @@
---
title: What I Use
description: My favorite equipment, tools and software for productivity and development
title: Research Lab & Technical Setup
description: A curated list of the hardware, software, and infrastructure that powers my research and engineering workflows.
---
# What I Use
# The Lab
This page documents all the tools, equipment and services I use daily for my work as a developer and my personal projects.
## 🖥️ Hardware
### Computers
- **Apple MacBook Pro 13'** - My main workstation with Apple M1 Chip and 16GB RAM, running macOS Sonoma
- **Custom Built Gaming PC** - A customized computer for gaming with Intel Core i5-10400F, 16GB DDR4, RTX 2060 and Windows 11
### Peripherals and Accessories
- **Apple AirPods Pro** - Probably my most used accessory after my phone and laptop. Excellent sound quality and very convenient
- **Apple iPad Air** - For reading books, watching movies, browsing the web, taking notes and writing equations during classes
- **Apple iPhone 14 Pro** - The best phone on the market in my opinion
- **SteelSeries Apex 9 TKL** - A compact and powerful TKL keyboard perfect for gamers and developers
- **Logitech G203 LightSync Black** - A classic gaming mouse with simple and clean design
### Apple Suite
- Using Mail, Calendar, Notes, Music and Reminders for my daily organization
Research requires a reliable environment. This page documents the hardware infrastructure and software stack I rely on to conduct :hover-text{text="mathematical modeling" hover="M280 Studies"}, deploy :hover-text{text="AI architectures" hover="PyTorch & TensorFlow"}, and maintain my :hover-text{text="digital sovereignty" hover="Self-hosted Homelab"}.
---
## 💻 Development
## 🖥️ Workstations & Compute
### IDEs
My setup is split between mobile efficiency for academic writing and a fixed station for heavier computation.
- **Visual Studio Code** - My main development environment. Flexible, performant and lightweight. Supports Python, JavaScript, TypeScript, SQL and much more. I especially appreciate the extensions and AI integrations
- **Cursor** - A VSCode fork with AI-powered code completions and suggestions to boost productivity
- **JetBrains Suite** (IntelliJ IDEA Ultimate, PyCharm Professional, WebStorm, DataGrip) - Which I've been using for 7 years. The best IDEs for Java, Python, JavaScript, SQL and other languages
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
### Theme and Fonts
::card{title="Daily Driver" icon="i-ph-laptop-duotone"}
**Apple MacBook Pro 13"**
* **Specs:** :hover-text{text="Apple M1 Chip" hover="ARM Architecture"}, 16GB RAM.
* **OS:** macOS Sonoma.
* **Usage:** Academic writing (LaTeX), lightweight coding, and remote server management.
::
- **Theme**: Catppuccin Macchiato - A community-driven pastel theme that strikes the balance between low and high contrast
- **Font**: JetBrains Mono
::card{title="Compute & CUDA Station" icon="i-ph-desktop-tower-duotone"}
**Custom Build PC**
* **Specs:** Intel Core i5-10400F, 16GB DDR4.
* **GPU:** :hover-text{text="NVIDIA RTX 2060" hover="CUDA Capable for small model training"}.
* **OS:** Windows 11 (WSL2).
* **Usage:** Local Deep Learning training, gaming, and heavy compilation tasks.
::
::
### Peripherals
I rely on a specific set of tools to maintain flow during deep work sessions.
* **Audio:** **Apple AirPods Pro** — Essential for deep work sessions and noise cancellation.
* **Input:** :hover-text{text="SteelSeries Apex 9 TKL" hover="OptiPoint Switches"} (Keyboard) & **Logitech G203** (Mouse).
* **Tablets:** **iPad Air** — Dedicated to reading papers and handwriting mathematical proofs.
* **Stylus:** **Apple Pencil** — Essential for annotations and mathematical notation.
---
## 🛠️ Software and Tools
## 🛠️ Development Ecosystem
### Communication and Collaboration
I prioritize tools that offer **AI-integration** and **strong type-checking**.
- **Discord** - For chatting with my friends, clients and community members
- **Notion & Notion Calendar** - My all-in-one tool for note-taking, kanban boards, wikis, and drafts. Notion Calendar allows me to sync my databases with my calendar
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
### Navigation and System Tools
::card{title="IDEs & Editors" icon="i-ph-code-duotone"}
* :prose-icon[VS Code]{color="blue" icon="i-logos:visual-studio-code"} — For general-purpose scripting and remote SSH development.
* :prose-icon[Positron]{color="cyan" icon="i-devicon:positron"} — Lightweight IDE for R and statistical analysis, offering superior performance to RStudio while maintaining VS Code familiarity.
* :prose-icon[JetBrains]{color="purple" icon="i-logos:jetbrains"} — *PyCharm* & *DataGrip* are unrivaled for complex refactoring and database management.
* **Theme:** Catppuccin Latte (Light) / Macchiato (Dark).
* **Font:** GitHub Monaspace Neon (primary, ligatures enabled) & JetBrains Mono.
- **Firefox Browser** - My main browser for browsing, developer tools and the extension marketplace
- **Raycast** - An extensible launcher replacing Apple Spotlight. Allows me to complete tasks, calculate, share common links, and much more thanks to extensions
- **Warp** - A modern, Rust-based terminal reimagined with AI and collaborative tools for better productivity
```python [main.py]
def main():
print("Hello, Research Lab!")
```
::
::card{title="Terminal & System" icon="i-ph-terminal-window-duotone"}
* :prose-icon[Ghostty]{color="gray" icon="i-ph-ghost-duotone"} — A fast, native, and GPU-accelerated terminal emulator.
* :prose-icon[Zsh]{color="green" icon="i-simple-icons-zsh"} — My default shell, optimized for speed and interactivity.
* :prose-icon[Starship]{color="purple" icon="i-simple-icons-starship"} — The minimal, blazing-fast, and infinitely customizable prompt.
* :prose-icon[Raycast]{color="red" icon="i-simple-icons-raycast"} — Replaces Spotlight. I use it for script commands, window management, and quick calculations.
* :prose-icon[Firefox]{color="orange" icon="i-logos:firefox"} — Chosen for its privacy features and robust DevTools.
::
::
---
## 🏠 Personal HomeLab
## 🏠 Infrastructure & Homelab
To bridge the gap between theory and MLOps, I maintain a **self-hosted cluster**. This allows me to experiment with distributed systems, data pipelines, and network security in a controlled environment.
### Hardware Infrastructure
- **Beelink EQR6 AMD Ryzen** - The main server in my homelab running Proxmox to host self-hosted services, run Docker containers and test open-source tools
- **5-Port TP-Link Switch** - To connect my network devices to the main server and ensure fast, stable local communication
- **UGREEN NASync DXP4800 Plus** - A 4-bay NAS to store and manage my data centrally. Currently equipped with 2 8TB hard drives, with the possibility to add 2 more in the future
::div{class="grid grid-cols-1 md:grid-cols-3 gap-4"}
### Self-Hosted Services
::card{title="Compute Node" icon="i-ph-cpu-duotone"}
**Beelink EQR6** *:hover-text{text="AMD Ryzen" hover="Proxmox Host"}*
I maintain several services:
Runs my containerized workloads and Docker services.
::
- **Monitoring & Infrastructure**: Uptime Kuma, Beszel, Traefik, Portainer
- **Security & Privacy**: Cloudflare, AdGuard Home, Vaultwarden, Tailscale
- **Storage & Media**: Minio, Immich
- **Smart Home**: Home Assistant
- **Other Utilities**: MySpeed, Palmr, Cap.so
::card{title="Storage Node" icon="i-ph-hard-drives-duotone"}
**UGREEN NASync DXP4800** *:hover-text{text="16TB Raw Storage" hover="RAID Configuration"}*
*This list is constantly updated as I experiment with new tools and equipment.*
Centralized Data Lake for datasets and backups.
::
::card{title="Network" icon="i-ph-globe-duotone"}
**TP-Link Switch & Tailscale** *:hover-text{text="Mesh VPN" hover="Secure Remote Access"}*
Ensures fast, stable local communication.
::
::
### Service Stack
I run these services using **Docker** and **Portainer**, strictly behind a **Traefik** reverse proxy.
::div{class="grid grid-cols-1 gap-4"}
* :prose-icon[DevOps & Infra]{icon="i-ph-washing-machine-duotone"} — Traefik, Portainer, Gitea.
* :prose-icon[Databases]{icon="i-ph-database-duotone"} — PostgreSQL, Redis.
* :prose-icon[Storage & Media]{icon="i-ph-hard-drives-duotone"} — Minio (S3), Immich.
* :prose-icon[Security]{icon="i-ph-shield-check-duotone"} — Cloudflare Tunnels, AdGuard Home, Vaultwarden.
* :prose-icon[Observability]{icon="i-ph-activity-duotone"} — Uptime Kuma, Beszel.
* :prose-icon[Utilities]{icon="i-ph-wrench-duotone"} — BentoPDF, Palmr, Home Assistant.
::
> *This list is constantly updated as I experiment with new tools and equipment.*

View File

@@ -1,6 +1,9 @@
import { definePerson } from 'nuxt-schema-org/schema'
export default defineNuxtConfig({
modules: [
'@nuxtjs/seo',
'@nuxt/ui',
'@nuxtjs/mdc',
'@nuxt/content',
@@ -16,24 +19,48 @@ export default defineNuxtConfig({
app: {
pageTransition: { name: 'page', mode: 'out-in' },
rootAttrs: {
class: 'bg-[var(--ui-bg)]'
},
head: {
templateParams: {
separator: '•'
}
},
rootAttrs: {
class: 'bg-[var(--ui-bg)]'
},
titleTemplate: '%s %separator %siteName',
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'apple-touch-icon', href: '/favicon.ico' }
]
}
},
css: ['~/assets/css/main.css'],
site: {
url: 'https://arthurdanjou.fr',
name: 'Arthur Danjou',
description: 'Research Engineer & Applied Mathematics Student. Focusing on AI Safety, Robustness, and Statistical Learning.',
defaultLocale: 'en',
indexable: true
},
colorMode: {
preference: 'system',
fallback: 'light'
},
content: {
build: {
markdown: {
highlight: {
langs: ['python'],
theme: {
default: 'catppuccin-latte',
dark: 'catppuccin-macchiato'
}
}
}
},
database: {
type: 'd1',
bindingName: 'DB'
@@ -90,11 +117,13 @@ export default defineNuxtConfig({
nitro: {
preset: 'cloudflare_module',
experimental: {
openAPI: true
},
prerender: {
routes: ['/'],
crawlLinks: true
}
},
@@ -113,6 +142,35 @@ export default defineNuxtConfig({
}
},
schemaOrg: {
identity: definePerson({
name: 'Arthur Danjou',
givenName: 'Arthur',
familyName: 'Danjou',
image: '/arthur.webp',
description: 'Research Engineer & Applied Mathematics Student. Focusing on AI Safety, Robustness, and Statistical Learning.',
jobTitle: 'Research Engineer & Applied Mathematics Student',
email: 'arthurdanjou@outlook.fr',
url: 'https://arthurdanjou.fr',
sameAs: [
'https://twitter.com/arthurdanj',
'https://github.com/arthurdanjou',
'https://linkedin.com/in/arthurdanjou'
],
worksFor: {
'@type': 'Organization',
'name': 'Arthur Danjou',
'url': 'https://arthurdanjou.fr'
}
})
},
seo: {
redirectToCanonicalSiteUrl: true
},
studio: {
route: '/studio',
repository: {

View File

@@ -12,24 +12,25 @@
},
"dependencies": {
"@libsql/client": "^0.15.15",
"@modelcontextprotocol/sdk": "^1.25.1",
"@nuxt/content": "3.9.0",
"@nuxt/content": "3.10.0",
"@nuxt/eslint": "1.12.1",
"@nuxt/ui": "^4.3.0",
"@nuxthub/core": "^0.10.3",
"@nuxtjs/mdc": "^0.19.1",
"@nuxthub/core": "0.10.4",
"@nuxtjs/mdc": "0.19.2",
"@nuxtjs/seo": "3.3.0",
"@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",
"vue": "3.5.26",
"vue-router": "4.6.4",
"zod": "^4.2.1"
"zod": "^4.3.5"
},
"devDependencies": {
"@iconify-json/devicon": "1.2.54",
"@iconify-json/devicon": "1.2.56",
"@iconify-json/file-icons": "^1.2.2",
"@iconify-json/logos": "^1.2.10",
"@iconify-json/ph": "^1.2.2",
@@ -39,7 +40,7 @@
"@vueuse/nuxt": "14.1.0",
"eslint": "9.39.2",
"typescript": "^5.9.3",
"vue-tsc": "3.2.1",
"wrangler": "4.56.0"
"vue-tsc": "3.2.2",
"wrangler": "4.54.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Binary file not shown.

BIN
public/projects/datavis.pdf Normal file

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

@@ -1,42 +1,56 @@
import type { H3Event } from 'h3'
const WAKATIME_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'application/json'
}
const fetchWakatime = async (url: string) => {
try {
return await $fetch<{ data: unknown[] }>(url, {
headers: WAKATIME_HEADERS,
timeout: 5000
})
}
catch (err) {
console.error(`[Wakatime Error] Failed to fetch ${url}`, err)
return { data: [] }
}
}
const cachedWakatimeCoding = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.coding}.json`)
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.coding}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
maxAge: 60 * 50,
name: 'stats',
getKey: () => 'coding'
})
const cachedWakatimeEditors = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.editors}.json`)
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.editors}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
maxAge: 60 * 60,
name: 'stats',
getKey: () => 'editors'
})
const cachedWakatimeOs = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.os}.json`)
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.os}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
maxAge: 60 * 60,
name: 'stats',
getKey: () => 'os'
})
const cachedWakatimeLanguages = defineCachedFunction(async (event: H3Event) => {
const config = useRuntimeConfig(event)
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.languages}.json`)
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.languages}.json`)
}, {
maxAge: 24 * 60 * 60,
name: 'wakatime',
maxAge: 60 * 60,
name: 'stats',
getKey: () => 'languages'
})

View File

@@ -2,6 +2,6 @@ export default defineCachedEventHandler(async (event) => {
const { statusPage } = useRuntimeConfig(event)
return await $fetch(statusPage)
}, {
maxAge: 60 * 60,
maxAge: 60,
name: 'status-page'
})

View File

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

View File

@@ -35,19 +35,69 @@ 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' },
{ name: 'WebStorm', icon: 'i-logos:webstorm' },
{ name: 'PyCharm Professional', icon: 'i-logos:pycharm' },
{ name: 'Cursor', icon: 'i-vscode-icons-file-type-cursorrules' }
] as const
export const socials = [
{ icon: 'i-ph-x-logo-duotone', label: 'Twitter', to: 'https://go.arthurdanjou.fr/twitter' },
{ icon: 'i-ph-github-logo-duotone', label: 'GitHub', to: 'https://go.arthurdanjou.fr/github' },
{ icon: 'i-ph-linkedin-logo-duotone', label: 'LinkedIn', to: 'https://go.arthurdanjou.fr/linkedin' },
{ icon: 'i-ph-discord-logo-duotone', label: 'Discord', to: 'https://go.arthurdanjou.fr/discord' }
{ name: 'Cursor', icon: 'i-vscode-icons-file-type-cursorrules' },
{ name: 'Positron', icon: 'i-devicon-positron' }
] as const
interface Nav {
@@ -62,8 +112,6 @@ export const navs: readonly Nav[] = [
{ label: 'uses', to: '/uses', icon: 'tree-evergreen-duotone' },
{ label: 'projects', to: '/projects', icon: 'folder-duotone' },
{ label: 'hobbies', to: '/hobbies', icon: 'game-controller-duotone' },
{ label: 'stats', to: '/stats', icon: 'chart-bar-duotone' },
{ label: 'activity', to: '/activity', icon: 'activity-duotone' },
{ label: 'ecosystem', to: '/ecosystem', icon: 'graph-duotone' },
{
label: 'resume',

View File

@@ -1,6 +1,6 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: 730a20d40c88c8f7167cb4afae90a541)
// Runtime types generated with workerd@1.20251217.0 2025-12-13 nodejs_compat
// Generated by Wrangler by running `wrangler types` (hash: 373e9a05bf207b93549ab53665d07e4b)
// Runtime types generated with workerd@1.20251210.0 2025-12-13 nodejs_compat
declare namespace Cloudflare {
interface Env {
CACHE: KVNamespace;
@@ -13,6 +13,9 @@ declare namespace Cloudflare {
NUXT_WAKATIME_OS: string;
NUXT_WAKATIME_USER_ID: string;
NUXT_STATUS_PAGE: string;
NUXT_HUB_CLOUDFLARE_ACCOUNT_ID: string;
NUXT_HUB_CLOUDFLARE_API_TOKEN: string;
NUXT_HUB_CLOUDFLARE_CACHE_NAMESPACE_ID: string;
DB: D1Database;
ASSETS: Fetcher;
}
@@ -22,7 +25,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
};
declare namespace NodeJS {
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "STUDIO_GITHUB_CLIENT_ID" | "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">> {}
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "STUDIO_GITHUB_CLIENT_ID" | "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" | "NUXT_HUB_CLOUDFLARE_ACCOUNT_ID" | "NUXT_HUB_CLOUDFLARE_API_TOKEN" | "NUXT_HUB_CLOUDFLARE_CACHE_NAMESPACE_ID">> {}
}
// Begin runtime types

View File

@@ -3,20 +3,25 @@
"name": "artsite",
"compatibility_date": "2025-12-13",
"compatibility_flags": [
"nodejs_compat",
"nodejs_compat"
],
"preview_urls": true,
"workers_dev": true,
"main": "./.output/server/index.mjs",
"routes": [
{
"pattern": "v2.arthurdanjou.fr",
"pattern": "arthurdanjou.fr",
"zone_name": "arthurdanjou.fr",
"custom_domain": true
},
{
"pattern": "www.arthurdanjou.fr",
"zone_name": "arthurdanjou.fr",
"custom_domain": true
}
],
"placement": {
"mode": "smart",
"mode": "smart"
},
"assets": {
"binding": "ASSETS",
@@ -25,7 +30,9 @@
"d1_databases": [
{
"binding": "DB",
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8"
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8",
"migrations_table": "_hub_migrations",
"migrations_dir": ".output/server/db/migrations/"
}
],
"kv_namespaces": [
@@ -47,5 +54,30 @@
"head_sampling_rate": 1,
"persist": true
}
},
"env": {
"preview": {
"routes": [
{
"pattern": "preview.arthurdanjou.fr",
"zone_name": "arthurdanjou.fr",
"custom_domain": true
}
],
"d1_databases": [
{
"binding": "DB",
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8",
"migrations_table": "_hub_migrations",
"migrations_dir": ".output/server/db/migrations/"
}
],
"kv_namespaces": [
{
"binding": "CACHE",
"id": "f0766ace1d24423ba6e5cac4fb8f2054"
}
]
}
}
}