mirror of
https://github.com/ArthurDanjou/artsite.git
synced 2026-02-05 16:33:33 +01:00
Compare commits
185 Commits
bcebe13ea5
...
feat/vuefl
| Author | SHA1 | Date | |
|---|---|---|---|
| 3adf5173e3 | |||
| 5bd407a4ae | |||
| 8e54617c23 | |||
| 830ea37cb0 | |||
| f3d3a507dc | |||
| 33bb54ec5f | |||
| e0bb04d9b8 | |||
| b476033a97 | |||
| 197c15619e | |||
| 0f02b8609a | |||
| d18b9094d5 | |||
| b3980a2741 | |||
| b30f2eb523 | |||
| e32e7bf7bb | |||
| 4dc74c0011 | |||
| f95a417b37 | |||
| 055c16e198 | |||
| 669eec60a1 | |||
| 4cda11a6a3 | |||
| df33efc731 | |||
| 305c91199a | |||
| ed3019d1b9 | |||
| 988e18b2f7 | |||
| 5d4f6ee9a4 | |||
| d13594e5d1 | |||
| 9aabe422b3 | |||
| e30d956f58 | |||
| 56c63d8db7 | |||
| c8116e10b8 | |||
| 9dc21f4287 | |||
| c3aafbf67e | |||
| 7b2c34ab87 | |||
| 7b32606b7d | |||
| 9ee45723f3 | |||
| 6296297855 | |||
| 8e17fc1e11 | |||
| bd044aed24 | |||
| 0f37a0072d | |||
| ca5cf60c4a | |||
| fb25eddaf0 | |||
| 2dcf52b65b | |||
| aedd8131d9 | |||
| 9de199e9d0 | |||
| 5a6cee085a | |||
| b26efd9da0 | |||
| e21ad51bc3 | |||
| ad9f440689 | |||
| 75b5c3c28c | |||
| 7ef847ed62 | |||
| 5990b84432 | |||
| 0628baaaba | |||
| d9a81f664c | |||
| e1e8cc4354 | |||
| ebe067d606 | |||
| 75c72d007c | |||
| 85e30d0f5f | |||
| cf261a108e | |||
| fc01e8f987 | |||
|
|
17f3d06acb | ||
|
|
2e73c26b12 | ||
|
|
dff1ce45a0 | ||
|
|
f87cadc96f | ||
|
|
6f16bc4697 | ||
| 719ee024d6 | |||
| 82d2ed8dba | |||
| bac370e465 | |||
| b8332b13af | |||
| 91422148dd | |||
| fb22fdf057 | |||
| 2db5d28dab | |||
| 121b35308f | |||
| 8d42b682e5 | |||
|
|
5a5f3c6319 | ||
| b790113771 | |||
| 6e6a05a3cd | |||
|
|
32bf773cdd | ||
| 4d520797c9 | |||
| 72f9e61a0e | |||
| 02ecf80cb9 | |||
| 00a5c34f36 | |||
| bcf9bd599e | |||
| 2c545875d1 | |||
| 0e033b6e02 | |||
|
|
bcac6dd42a | ||
|
|
38bf904902 | ||
|
|
a1dbcdce90 | ||
|
|
66fe877499 | ||
|
|
7fed92f539 | ||
|
|
ee74f06cd5 | ||
|
|
476d5b9842 | ||
|
|
cd69b346fe | ||
|
|
c47a0b4f5f | ||
|
|
5c46d1ab3f | ||
| 6d54535245 | |||
| b252f54fa0 | |||
| a3210afaaf | |||
| c46feb3052 | |||
| 9bc529d9ae | |||
| 9ee02a36a3 | |||
| 466baf1eb8 | |||
| 0e70996a3a | |||
| 9e773355b5 | |||
| c942266fa2 | |||
| 6b81bd2854 | |||
| 918528f2b3 | |||
| ae176ecbae | |||
| e0589826bb | |||
| c04bf9f82b | |||
| 4030bdb693 | |||
| db44535528 | |||
| 986fd2b791 | |||
| 5f74bec60a | |||
| 17306539fe | |||
| 14bed2c651 | |||
| e57a0cbb1f | |||
| 0e7e3f5a37 | |||
| a33bcb8bfc | |||
| c39830803e | |||
| ba91408b6d | |||
| adea7fe35e | |||
| ed8ffedde5 | |||
| cffa2e20fe | |||
| 6e28fdd17a | |||
| 929899722b | |||
| 6654f42588 | |||
| ab0ad65d5e | |||
| 4bbf401636 | |||
| dc04539d1e | |||
| 2ffffe3e32 | |||
| 55b79f0dc2 | |||
| 80c88466d3 | |||
| 59bcfbd248 | |||
| 041c25d1fd | |||
| 442cab6026 | |||
| 2e5cdde145 | |||
| ac46398a9d | |||
| 6e810e47cf | |||
| 124a1901d8 | |||
| 7206a17a6a | |||
| fbe223b50c | |||
| fcfebe8505 | |||
| de09902846 | |||
| 191a13cfd4 | |||
| 6afb6b1f4d | |||
| c26827cfd2 | |||
| 81018f1749 | |||
| 9f04417e73 | |||
| b72a7f494a | |||
| 2aa2be0c0a | |||
| 8486095702 | |||
| 9861b91f2d | |||
| d3efe47785 | |||
| 3d2ffba475 | |||
| ea1c5a5407 | |||
| 319e2d0e9a | |||
| dca0ca8ae2 | |||
| 15dc39937b | |||
| 0ffece4813 | |||
| 795b2d5f07 | |||
| c8cf4db9b4 | |||
| 33aee7fd62 | |||
| 7c799b7206 | |||
| c8d6894a51 | |||
| 7262cc4850 | |||
| d1e7ed2075 | |||
| 9bbcc92d7f | |||
| 63ab0704f7 | |||
| 6ca88154a9 | |||
| 9f34ae9a61 | |||
| 114a1cf03a | |||
| 360326f810 | |||
| 02101fbd00 | |||
| 6b881140b2 | |||
| 28c4caf60d | |||
| daa3b6e1e4 | |||
| 45055232f0 | |||
| bddbdec3e7 | |||
| dc69b8344a | |||
| 58447fd8cc | |||
| acdd693d34 | |||
| 990971b3e3 | |||
| 064e20ddc3 | |||
| ab4a679851 | |||
| c82a875381 | |||
| 2f7f944238 |
13
.env.example
13
.env.example
@@ -1,15 +1,12 @@
|
||||
NUXT_DISCORD_ID=
|
||||
NUXT_DISCORD_TOKEN=
|
||||
STUDIO_GITHUB_CLIENT_ID=
|
||||
STUDIO_GITHUB_CLIENT_SECRET=
|
||||
|
||||
NUXT_DISCORD_USER_ID=
|
||||
|
||||
NUXT_HUB_ENV=
|
||||
NUXT_HUB_PROJECT_KEY=
|
||||
|
||||
NUXT_PUBLIC_I18N_BASE_URL=
|
||||
NUXT_PUBLIC_SITE_URL=
|
||||
|
||||
NUXT_WAKATIME_CODING=
|
||||
NUXT_WAKATIME_EDITORS=
|
||||
NUXT_WAKATIME_LANGUAGES=
|
||||
NUXT_WAKATIME_OS=
|
||||
NUXT_WAKATIME_USER_ID=
|
||||
|
||||
NUXT_STATUS_PAGE=
|
||||
95
.github/workflows/cloudflare.yml
vendored
Normal file
95
.github/workflows/cloudflare.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: Deploy to Cloudflare Workers
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Setup Wrangler Types
|
||||
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: 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 }}
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
|
||||
- name: Discord Notification
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ job.status }}
|
||||
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
|
||||
image: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
||||
username: "GitHub Actions"
|
||||
35
.github/workflows/nuxthub.yml
vendored
35
.github/workflows/nuxthub.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Deploy to NuxtHub
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy to NuxtHub
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
|
||||
url: ${{ steps.deploy.outputs.deployment-url }}
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build application
|
||||
run: pnpm run build
|
||||
|
||||
- name: Deploy to NuxtHub
|
||||
uses: nuxt-hub/action@v1
|
||||
id: deploy
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -8,6 +8,7 @@ dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# Logs
|
||||
logs
|
||||
@@ -22,4 +23,8 @@ logs
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.wrangler
|
||||
|
||||
.wrangler
|
||||
|
||||
# Localflare generated files
|
||||
.localflare/
|
||||
|
||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"todo-tree.tree.scanMode": "workspace only",
|
||||
"todo-tree.tree.disableCompactFolders": false,
|
||||
"todo-tree.tree.showCountsInTree": false,
|
||||
"todo-tree.tree.showBadges": true
|
||||
}
|
||||
319
README.md
319
README.md
@@ -2,14 +2,15 @@
|
||||
|
||||
# [Arthur Danjou | Portfolio 2024](https://arthurdanjou.fr/)
|
||||
|
||||

|
||||
|
||||
My professional portfolio built with modern Nuxt.js technologies, showcasing projects, skills, and experience.
|
||||
|
||||
[](https://nuxt.com/)
|
||||
[](https://vuejs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://tailwindcss.com/)
|
||||
[](https://cloudflare.com/)
|
||||
|
||||
[🌐 Live Demo](https://arthurdanjou.fr/) | [🐛 Report Bug](https://github.com/ArthurDanjou/artsite/issues)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -17,12 +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)
|
||||
- [Adding Content](#-adding-content)
|
||||
- [Projects](#projects)
|
||||
- [Writings](#writings)
|
||||
- [Uses Page](#uses-page)
|
||||
- [Integrations](#-integrations)
|
||||
- [Deployment](#-deployment)
|
||||
- [Contributing](#-contributing)
|
||||
- [License](#-license)
|
||||
- [Contact](#-contact)
|
||||
|
||||
@@ -45,105 +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
|
||||
├── content/ # Markdown content for the portfolio
|
||||
│ ├── projects/ # Portfolio projects
|
||||
│ ├── writings/ # Writings
|
||||
│ └── uses/ # Uses page items
|
||||
├── layouts/ # Page layouts
|
||||
├── pages/ # Application pages
|
||||
├── public/ # Public static files
|
||||
│ ├── projects/ # Projects images
|
||||
│ └── writings/ # Writings images
|
||||
├── server/ # Server API routes
|
||||
├── utils/ # Utility functions
|
||||
├── .env.example # Example environment variables
|
||||
├── nuxt.config.ts # Nuxt configuration
|
||||
├── package.json # Dependencies and scripts
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
## 🍱 Adding Content
|
||||
|
||||
### Projects
|
||||
|
||||
1. Create a new `.md` file in the `/content/projects/` directory
|
||||
2. Follow the structure of existing projects:
|
||||
|
||||
```md
|
||||
---
|
||||
---
|
||||
slug: project-slug
|
||||
title: Project Title
|
||||
description: A brief description of the project
|
||||
publishedAt: YYYY/MM/DD
|
||||
readingTime: 1
|
||||
cover: project-slug/cover.png
|
||||
tags:
|
||||
- web
|
||||
---
|
||||
|
||||
## Project content goes here
|
||||
|
||||
Detailed description and information about the project.
|
||||
```
|
||||
|
||||
3. Add related project images to `/public/projects/project-slug/`
|
||||
|
||||
### Writings
|
||||
|
||||
1. Create a new `.md` file in the `/content/writings/` directory
|
||||
2. Follow the structure of existing projects:
|
||||
|
||||
```md
|
||||
---
|
||||
slug: article-slug
|
||||
title: The title of the article
|
||||
description: A brief description of the article
|
||||
readingTime: 1
|
||||
publishedAt: YYYY/MM/DD
|
||||
cover: article-slug/cover.png
|
||||
tags:
|
||||
- tag1
|
||||
- tag2
|
||||
- tag3
|
||||
---
|
||||
|
||||
## Writing content goes here
|
||||
|
||||
Detailed description and information about the article.
|
||||
```
|
||||
|
||||
3. Add related writing images to `/public/writings/article-slug/`
|
||||
|
||||
### Uses Page
|
||||
|
||||
Add new items to the `/content/uses/` directory following the existing pattern:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Name of the item",
|
||||
"description": {
|
||||
"en": "Item description in English",
|
||||
"fr": "Item description in French",
|
||||
"es": "Item description in Spanish"
|
||||
},
|
||||
"category": "Item category name"
|
||||
}
|
||||
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
|
||||
@@ -152,6 +223,62 @@ Add new items to the `/content/uses/` directory following the existing pattern:
|
||||
- **Discord** - Display real-time Discord status
|
||||
- **Nuxt Studio** - Headless CMS for content management
|
||||
- **Nuxt i18n** - Internationalization support
|
||||
- **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
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
amber: 'amber',
|
||||
green: 'green',
|
||||
emerald: 'emerald',
|
||||
sky: 'sky',
|
||||
blue: 'blue',
|
||||
purple: 'purple',
|
||||
pink: 'pink',
|
||||
orange: 'orange'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
useHead({
|
||||
link: [{ rel: 'icon', type: 'image/webp', href: '/favicon.webp' }],
|
||||
titleTemplate: (titleChunk) => {
|
||||
return titleChunk ? `${titleChunk} %separator %siteName` : 'Arthur Danjou %separator AI Safety & Applied Math'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -8,7 +10,6 @@ useHead({
|
||||
<UApp>
|
||||
<NuxtLoadingIndicator color="#808080" />
|
||||
<AppBackground />
|
||||
<AppVisitors />
|
||||
<UContainer class="z-50 relative">
|
||||
<AppHeader />
|
||||
<NuxtPage class="mt-12" />
|
||||
@@ -18,8 +19,6 @@ useHead({
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/assets/css/main.css";
|
||||
|
||||
.sofia {
|
||||
font-family: 'Sofia Sans', sans-serif;
|
||||
}
|
||||
|
||||
@@ -1,42 +1,54 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@plugin "@tailwindcss/typography";
|
||||
@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;
|
||||
|
||||
@theme static {
|
||||
--animate-wave: wave 3s infinite
|
||||
@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 {
|
||||
--ui-black: #000000;
|
||||
--ui-white: #ffffff;
|
||||
--ui-bg-black: #0a0a0a;
|
||||
--ui-bg-white: #f8f8f8;
|
||||
--ui-bg: #f8f8f8;
|
||||
|
||||
--ui-font-family: 'DM Sans', sans-serif;
|
||||
transition-duration: 0.7s;
|
||||
--ui-bg: #f8f8f8;
|
||||
--ui-font-family: 'DM Sans', sans-serif;
|
||||
transition-duration: 0.7s;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ui-black: #000000;
|
||||
--ui-white: #ffffff;
|
||||
--ui-bg-white: #f8f8f8;
|
||||
--ui-bg-black: #0a0a0a;
|
||||
--ui-bg: #0a0a0a;
|
||||
|
||||
--ui-font-family: 'DM Sans', sans-serif;
|
||||
transition-duration: 0.7s;
|
||||
--ui-bg: #0f0f0f;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: rotate(-12deg);
|
||||
}
|
||||
25%, 75% {
|
||||
transform: rotate(3deg) scale(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
.sofia {
|
||||
--ui-font-family: 'Sofia Sans', sans-serif;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="pointer-events-none fixed inset-0 z-40 size-full overflow-hidden">
|
||||
<div class="noise pointer-events-none absolute inset-[-200%] z-50 size-[400%] bg-[url('/noise.png')] opacity-[4%]" />
|
||||
<div class="noise pointer-events-none absolute inset-[-200%] z-50 size-[400%] bg-[url('/noise.png')] opacity-4" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { socials } from '~~/types'
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
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"
|
||||
@@ -17,20 +16,20 @@ const { t } = useI18n({
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col md:flex-row gap-2 md:items-center">
|
||||
<h1>{{ t('find') }}</h1>
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-2 md:items-center">
|
||||
<h1>{{ t('email') }}</h1>
|
||||
<h1>Or send me an email:</h1>
|
||||
<div class="flex">
|
||||
<HomeLink
|
||||
blanked
|
||||
@@ -41,31 +40,7 @@ const { t } = useI18n({
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 w-full flex justify-center text-xs">
|
||||
{{
|
||||
t('copyright', {
|
||||
date: new Date().getFullYear(),
|
||||
})
|
||||
}}
|
||||
© {{ new Date().getFullYear() }} Arthur Danjou. All rights reserved.
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"find": "Find me on:",
|
||||
"email": "Or send me an email:",
|
||||
"copyright": "© {date} Arthur Danjou. All rights reserved."
|
||||
},
|
||||
"fr": {
|
||||
"find": "Retrouvez-moi sur :",
|
||||
"email": "Ou envoyez-moi un email :",
|
||||
"copyright": "© {date} Arthur Danjou. Tous droits réservés."
|
||||
},
|
||||
"es": {
|
||||
"find": "Encuéntrame en :",
|
||||
"email": "O envíame un mail",
|
||||
"copyright": "2024 Arthur Danjour. Todos los derechos reservados."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,42 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { navs, socials } from '~~/types'
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
import { navs } from '~~/types'
|
||||
|
||||
const openContactDrawer = ref(false)
|
||||
const router = useRouter()
|
||||
defineShortcuts({
|
||||
c: () => openContactDrawer.value = !openContactDrawer.value,
|
||||
backspace: () => router.back(),
|
||||
backspace: () => router.back()
|
||||
})
|
||||
|
||||
const socialsList = [
|
||||
{
|
||||
label: 'Email',
|
||||
icon: 'i-ph:envelope-duotone',
|
||||
to: 'mailto:arthurdanjou@outlook.fr',
|
||||
},
|
||||
...socials,
|
||||
]
|
||||
const { contact } = await useContent()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex md:items-center justify-between my-8 gap-2">
|
||||
<NuxtLinkLocale
|
||||
<NuxtLink
|
||||
class="handwriting text-xl sm:text-3xl text-nowrap gap-2 font-bold duration-300 text-neutral-600 hover:text-black dark:text-neutral-400 dark:hover:text-white"
|
||||
to="/"
|
||||
>
|
||||
Arthur Danjou
|
||||
</NuxtLinkLocale>
|
||||
</NuxtLink>
|
||||
<nav class="flex gap-2 items-center justify-end flex-wrap">
|
||||
<UTooltip
|
||||
v-for="nav in navs"
|
||||
:key="nav.label.en"
|
||||
:text="nav.label[locale]"
|
||||
:key="nav.label"
|
||||
:text="nav.label"
|
||||
:delay-duration="4"
|
||||
>
|
||||
<UButton
|
||||
:icon="`i-ph:${nav.icon}`"
|
||||
:icon="`i-ph-${nav.icon}`"
|
||||
:target="nav.target ? nav.target : '_self'"
|
||||
:href="nav.to"
|
||||
:aria-label="nav.label"
|
||||
@@ -47,7 +38,7 @@ const socialsList = [
|
||||
</UTooltip>
|
||||
<UTooltip
|
||||
:delay-duration="4"
|
||||
:text="t('status')"
|
||||
text="Status Page"
|
||||
>
|
||||
<UButton
|
||||
icon="i-ph-warning-duotone"
|
||||
@@ -58,19 +49,28 @@ const socialsList = [
|
||||
variant="ghost"
|
||||
/>
|
||||
</UTooltip>
|
||||
<USeparator orientation="vertical" class="h-6" />
|
||||
<USeparator
|
||||
orientation="vertical"
|
||||
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',
|
||||
sideOffset: 8,
|
||||
sideOffset: 8
|
||||
}"
|
||||
>
|
||||
<UTooltip
|
||||
:kbds="['C']"
|
||||
:text="t('contact.button')"
|
||||
text="Contact Me"
|
||||
:delay-duration="4"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
@@ -83,8 +83,7 @@ const socialsList = [
|
||||
/>
|
||||
</UTooltip>
|
||||
</UDropdownMenu>
|
||||
<USeparator orientation="vertical" class="h-6" />
|
||||
<LangSwitcher />
|
||||
<ThemeSwitcher />
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
@@ -116,29 +115,3 @@ const socialsList = [
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"status": "status page",
|
||||
"contact": {
|
||||
"button": "contact me",
|
||||
"title": "Contact me"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"status": "page de statut",
|
||||
"contact": {
|
||||
"button": "me contacter",
|
||||
"title": "Me contacter"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"status": "página de estado",
|
||||
"contact": {
|
||||
"button": "contactame",
|
||||
"title": "Contactame"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1
|
||||
class="text-3xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
<p class="mt-4 text-base">
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,20 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const { visitors } = useVisitors()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
color="green"
|
||||
variant="outline"
|
||||
class="shadow-xl fixed z-50 bottom-4 right-4 rounded-full px-1.5 py-0.5 bg-white ring ring-green-400 dark:bg-neutral-950 dark:ring-green-600"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<p class="text-neutral-500">
|
||||
{{ visitors }}
|
||||
</p>
|
||||
<div class="w-3 h-3 bg-green-200/70 dark:bg-green-800/70 rounded-full border-2 border-green-400 dark:border-green-600" />
|
||||
</div>
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,66 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const openSelectMenu = ref(false)
|
||||
|
||||
const { locale, setLocale, locales, t } = useI18n()
|
||||
const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0])
|
||||
const lang = ref(locale.value)
|
||||
watch(lang, () => changeLocale(lang.value))
|
||||
|
||||
async function changeLocale(newLocale: string) {
|
||||
document.body.style.animation = 'switch-on .2s'
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
|
||||
await setLocale(newLocale as 'en' | 'fr' | 'es')
|
||||
document.body.style.animation = 'switch-off .5s'
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 200))
|
||||
document.body.style.animation = ''
|
||||
}
|
||||
|
||||
defineShortcuts({
|
||||
l: () => lang.value = currentLocale.value!.code === 'en' ? 'fr' : currentLocale.value!.code === 'fr' ? 'es' : 'en',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<ThemeSwitcher />
|
||||
<UTooltip
|
||||
:kbds="['L']"
|
||||
:text="t('language')"
|
||||
class="cursor-pointer"
|
||||
:delay-duration="4"
|
||||
:content="{
|
||||
align: 'center',
|
||||
side: 'right',
|
||||
sideOffset: 8,
|
||||
}"
|
||||
>
|
||||
<USelect
|
||||
v-model="lang"
|
||||
v-model:open="openSelectMenu"
|
||||
:items="locales"
|
||||
size="sm"
|
||||
:leading-icon="(currentLocale!.icon as string)"
|
||||
label-key="label"
|
||||
value-key="code"
|
||||
variant="soft"
|
||||
@update:model-value="changeLocale"
|
||||
/>
|
||||
</UTooltip>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"language": "change language"
|
||||
},
|
||||
"fr": {
|
||||
"language": "changer de langue"
|
||||
},
|
||||
"es": {
|
||||
"language": "cambiar idioma"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
const nextTheme = computed(() => (colorMode.value === 'dark' ? 'light' : 'dark'))
|
||||
|
||||
@@ -21,7 +20,7 @@ function toggleDark(event: MouseEvent | { clientX: number, clientY: number }) {
|
||||
const y = event.clientY
|
||||
const endRadius = Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y),
|
||||
Math.max(y, innerHeight - y)
|
||||
)
|
||||
const transition = document.startViewTransition(async () => {
|
||||
switchTheme()
|
||||
@@ -31,34 +30,34 @@ function toggleDark(event: MouseEvent | { clientX: number, clientY: number }) {
|
||||
.then(() => {
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`,
|
||||
`circle(${endRadius}px at ${x}px ${y}px)`
|
||||
]
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: colorMode.value === 'dark'
|
||||
? [...clipPath].reverse()
|
||||
: clipPath,
|
||||
: clipPath
|
||||
},
|
||||
{
|
||||
duration: 400,
|
||||
easing: 'ease-out',
|
||||
pseudoElement: colorMode.value === 'dark'
|
||||
? '::view-transition-old(root)'
|
||||
: '::view-transition-new(root)',
|
||||
},
|
||||
: '::view-transition-new(root)'
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
defineShortcuts({
|
||||
t: () => toggleDark({ clientX: window.innerWidth, clientY: 0 }),
|
||||
t: () => toggleDark({ clientX: window.innerWidth, clientY: 0 })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTooltip
|
||||
:kbds="['T']"
|
||||
:text="t('theme')"
|
||||
text="switch theme"
|
||||
class="cursor-pointer"
|
||||
:delay-duration="4"
|
||||
>
|
||||
@@ -73,20 +72,6 @@ defineShortcuts({
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"theme": "switch theme"
|
||||
},
|
||||
"fr": {
|
||||
"theme": "changer de thème"
|
||||
},
|
||||
"es": {
|
||||
"theme": "cambiar tema"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<style>
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
|
||||
@@ -4,15 +4,16 @@ import type { PropType } from 'vue'
|
||||
defineProps({
|
||||
text: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
hover: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<'top' | 'right' | 'bottom' | 'left'>,
|
||||
},
|
||||
default: 'top'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -24,7 +25,7 @@ defineProps({
|
||||
:content="{
|
||||
align: 'center',
|
||||
side: position,
|
||||
sideOffset: 8,
|
||||
sideOffset: 8
|
||||
}"
|
||||
:text="hover"
|
||||
>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
target: {
|
||||
type: String as PropType<'_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined>,
|
||||
default: undefined,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
:href="href"
|
||||
:target="target"
|
||||
class="sofia font-medium duration-300 underline-offset-2 text-md text-black dark:text-white underline decoration-gray-300 dark:decoration-neutral-700 hover:decoration-black dark:hover:decoration-white"
|
||||
>
|
||||
<slot />
|
||||
</NuxtLink>
|
||||
</template>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2
|
||||
:id="id"
|
||||
>
|
||||
<a
|
||||
v-if="id && generate"
|
||||
:href="`#${id}`"
|
||||
class="text-xl font-bold border-transparent border-b-2 hover:border-black dark:hover:border-white duration-300"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
<slot v-else />
|
||||
</h2>
|
||||
</template>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, useRuntimeConfig } from '#imports'
|
||||
|
||||
const props = defineProps<{ id?: string }>()
|
||||
|
||||
const { headings } = useRuntimeConfig().public.mdc
|
||||
const generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h2
|
||||
:id="id"
|
||||
>
|
||||
<a
|
||||
v-if="id && generate"
|
||||
:href="`#${id}`"
|
||||
class="text-lg font-semibold text-neutral-800 dark:text-neutral-200"
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
<slot v-else />
|
||||
</h2>
|
||||
</template>
|
||||
@@ -2,16 +2,16 @@
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
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',
|
||||
@@ -24,12 +24,19 @@ const colorVariants = {
|
||||
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',
|
||||
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>
|
||||
|
||||
<template>
|
||||
<span class="inline-flex items-center transform translate-y-1 gap-1">
|
||||
<UIcon :name="icon" size="16" />
|
||||
<UIcon
|
||||
:name="icon"
|
||||
size="16"
|
||||
/>
|
||||
<span
|
||||
:class="colorVariants[color as keyof typeof colorVariants]"
|
||||
class="sofia font-semibold underline-offset-2 underline"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{ src: string, caption?: string, rounded?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col justify-center items-center prose-none my-8">
|
||||
<img :src="src" :alt="caption" class="w-full h-auto m-0 prose-none" :class="{ 'rounded-lg': rounded }">
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-300 prose-none">
|
||||
{{ caption }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<p class="text-neutral-700 dark:text-neutral-300">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
@@ -1,202 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UseTimeAgoMessages } from '@vueuse/core'
|
||||
import type { Activity } from '~~/types'
|
||||
import { activityMessages, IDEs } from '~~/types'
|
||||
|
||||
const { locale, locales, t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch<Activity>('/api/activity'))
|
||||
|
||||
useIntervalFn(async () => await refresh(), 5000)
|
||||
const codingActivity = computed(() => {
|
||||
const activities = activity.value!.data.activities.filter(activity => IDEs.some(ide => ide.name === activity.name)).map(activity => ({
|
||||
...activity,
|
||||
name: activity.assets?.small_text === 'Cursor' ? 'Cursor' : activity.name,
|
||||
}))
|
||||
|
||||
return activities.length > 1
|
||||
? activities[Math.floor(Math.random() * activities.length)]
|
||||
: activities[0]
|
||||
})
|
||||
|
||||
const currentLocale = computed(() => locales.value.find((l: { code: string }) => l.code === locale.value))
|
||||
|
||||
const isActive = computed(() => {
|
||||
if (!codingActivity.value)
|
||||
return
|
||||
|
||||
const { name, details, state } = codingActivity.value
|
||||
|
||||
return name === 'Visual Studio Code' || name === 'Cursor'
|
||||
? !details.includes('Idling')
|
||||
: state.toLowerCase().includes('editing')
|
||||
})
|
||||
|
||||
const getActivity = computed(() => {
|
||||
if (!codingActivity.value)
|
||||
return
|
||||
|
||||
const { name, details, state, timestamps } = codingActivity.value
|
||||
|
||||
const project = details
|
||||
? details
|
||||
.charAt(0)
|
||||
.toUpperCase()
|
||||
+ details
|
||||
.slice(1)
|
||||
.replace('Workspace:', '')
|
||||
.trim()
|
||||
: ''
|
||||
|
||||
const stateWord = state && state.split(' ').length >= 2 ? state.split(' ')[1] : t('secret')
|
||||
const ago = useTimeAgo(timestamps.start, {
|
||||
messages: activityMessages[locale.value as keyof typeof activityMessages] as UseTimeAgoMessages,
|
||||
}).value
|
||||
const formatDate = (date: number, format: string) => useDateFormat(date, format, { locales: currentLocale.value?.code ?? 'en' }).value
|
||||
|
||||
return {
|
||||
name,
|
||||
project,
|
||||
state: stateWord,
|
||||
start: {
|
||||
ago,
|
||||
formated: {
|
||||
date: formatDate(timestamps.start, 'DD MMM YYYY'),
|
||||
time: formatDate(timestamps.start, 'HH:mm'),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-if="getActivity"
|
||||
class="flex items-start gap-2"
|
||||
>
|
||||
<UTooltip :text="isActive ? t('tooltip.online') : t('tooltip.idling')">
|
||||
<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>
|
||||
<i18n-t
|
||||
v-if="isActive"
|
||||
keypath="working"
|
||||
tag="div"
|
||||
>
|
||||
<template #state>
|
||||
<strong>{{ getActivity.state!.split(' ').map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ') }}</strong>
|
||||
</template>
|
||||
<template #project>
|
||||
<i>{{ getActivity.project.replaceAll('Editing', '') }}</i>
|
||||
</template>
|
||||
<template #editor>
|
||||
<span class="space-x-1">
|
||||
<UIcon
|
||||
:name="IDEs.find(ide => ide.name === getActivity!.name)!.icon"
|
||||
size="16"
|
||||
/>
|
||||
<strong>{{ getActivity.name }}</strong></span>
|
||||
</template>
|
||||
<template #start>
|
||||
<strong>{{ getActivity.start.ago }}</strong>
|
||||
</template>
|
||||
<template #format>
|
||||
<strong>{{ getActivity.start.formated.date }}</strong> {{ t('separator') }}
|
||||
<strong>{{ getActivity.start.formated.time }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<i18n-t
|
||||
v-else
|
||||
keypath="idling"
|
||||
tag="div"
|
||||
class="space-x-1"
|
||||
>
|
||||
<template #editor>
|
||||
<span class="space-x-1">
|
||||
<UIcon
|
||||
:name="IDEs.find(ide => ide.name === getActivity!.name)!.icon"
|
||||
size="16"
|
||||
/>
|
||||
<strong>{{ getActivity.name }}</strong>
|
||||
</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="my-5 flex md:items-start gap-2"
|
||||
>
|
||||
<UTooltip :text="t('tooltip.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>
|
||||
<i18n-t
|
||||
keypath="offline"
|
||||
tag="p"
|
||||
class="not-prose"
|
||||
>
|
||||
<template #maths>
|
||||
<i>{{ t('maths') }}</i>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"offline": "I'm currently offline. Come back later to see what I'm working on. {maths}",
|
||||
"working": "I'm actually working on {state}, editing {project}, using {editor}. I've started {start}, the {format}.",
|
||||
"idling": "I'm idling on my computer with {editor} running in background.",
|
||||
"maths": "I am probably doing some maths or sleeping.",
|
||||
"tooltip": {
|
||||
"online": "I'm online 👋",
|
||||
"offline": "I'm offline 🫥",
|
||||
"idling": "I'm sleeping 😴"
|
||||
},
|
||||
"separator": "at",
|
||||
"secret": "Secret Project"
|
||||
},
|
||||
"fr": {
|
||||
"offline": "Je suis actuellement hors ligne. Revenez plus tard pour voir sur quoi je travaille. {maths}",
|
||||
"working": "Je travaille actuellement sur {state}, éditant {project}, en utilisant {editor}. J'ai commencé {start}, le {format}.",
|
||||
"idling": "Je suis en veille sur mon ordinateur avec {editor} en arrière-plan.",
|
||||
"maths": "Je suis probablement en train de faire des maths ou en train de dormir.",
|
||||
"tooltip": {
|
||||
"online": "Je suis connecté 👋",
|
||||
"offline": "Je suis déconnecté 🫥",
|
||||
"idling": "Je dors 😴"
|
||||
},
|
||||
"separator": "à",
|
||||
"secret": "Projet Secret"
|
||||
},
|
||||
"es": {
|
||||
"offline": "Ahora mismo estoy desconectado. Vuelve más tarde para ver en lo que estoy trabajando. {maths}",
|
||||
"working": "Estoy trabajando en {state}, editando {project}, y utilizando {editor}. He empezado {start}, el {format}.",
|
||||
"idling": "Estoy en reposo en mi ordenador con {editor} en segundo plano.",
|
||||
"maths": "Estoy probablemente haciendo matemáticas o durmiendo.",
|
||||
"tooltip": {
|
||||
"online": "Estoy conectado 👋",
|
||||
"offline": "Estoy desconectado 🫥",
|
||||
"idling": "Estoy durmiendo 😴"
|
||||
},
|
||||
"separator": "a",
|
||||
"secret": "Proyecto Secreto"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,35 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
const { width } = useWindowSize()
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div
|
||||
v-if="width > 1024"
|
||||
class="group text-[12px] italic flex items-center gap-1"
|
||||
>
|
||||
<UIcon
|
||||
class="transform -rotate-12 duration-300 group-hover:animate-wave"
|
||||
name="i-ph-hand-pointing-duotone"
|
||||
/>
|
||||
<p>{{ t('quote') }}</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>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"quote": "Hover the bold texts to find out more about me."
|
||||
},
|
||||
"fr": {
|
||||
"quote": "Survolez les textes en gras pour en savoir plus sur moi."
|
||||
},
|
||||
"es": {
|
||||
"quote": "Pase el cursor sobre los textos en negrita para obtener más información sobre mí."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
blanked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -30,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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
const styleVars = {
|
||||
'--animated-speed': '50s',
|
||||
'--animated-speed': '50s'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,39 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<div class="float-left flex items-center mt-2 mr-2">
|
||||
<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-[360deg] duration-500 transform-gpu rounded-full"
|
||||
src="/arthur.webp"
|
||||
alt="Arthur Danjou"
|
||||
size="xl"
|
||||
src="/favicon.webp"
|
||||
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>
|
||||
<p class="not-prose">
|
||||
{{ t('quote') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"quote": "Hello everyone! Thanks for visiting my portfolio. Please leave whatever you like to say, such as suggestions, appreciations, questions or anything!"
|
||||
},
|
||||
"fr": {
|
||||
"quote": "Bonjour tout le monde ! Merci de visiter mon portfolio. N'hésitez pas à laisser ce que vous avez à dire, comme des suggestions, des appréciations, des questions ou autre chose !"
|
||||
},
|
||||
"es": {
|
||||
"quote": "Hola a todos ! Muchas gracias por visitar mi portfolio. No dudes en dejar cualquier comentario, como sugerencias, apreciaciones. preguntas, o cualquier cosa !"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
<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>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
46
app/components/home/Skills.vue
Normal file
46
app/components/home/Skills.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
const { skills } = await useContent()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
v-if="skills"
|
||||
class="my-8 space-y-6"
|
||||
aria-labelledby="skills-title"
|
||||
>
|
||||
<h2
|
||||
id="skills-title"
|
||||
class="sr-only"
|
||||
>
|
||||
Skills
|
||||
</h2>
|
||||
|
||||
<div
|
||||
v-for="skill in skills.body"
|
||||
:key="skill.id"
|
||||
>
|
||||
<div class>
|
||||
<h3 class="text-xl md:text-2xl font-semibold tracking-tight">
|
||||
{{ skill.name }}
|
||||
</h3>
|
||||
<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"
|
||||
>
|
||||
{{ item.name }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,84 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Stats } from '~~/types'
|
||||
import { usePrecision } from '@vueuse/math'
|
||||
|
||||
const { locale, locales, t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
const currentLocale = computed(() => locales.value.find(l => l.code === locale.value))
|
||||
|
||||
const { data: stats } = await useAsyncData<Stats>('stats', () => $fetch('/api/stats'))
|
||||
|
||||
const time = useTimeAgo(new Date(stats.value!.coding.data.range.start) ?? new Date()).value.split(' ')[0]
|
||||
const date = useDateFormat(new Date(stats.value!.coding.data.range.start ?? new Date()), 'DD MMMM YYYY', { locales: currentLocale.value?.code ?? 'en' })
|
||||
const hours = usePrecision(stats.value!.coding.data.grand_total.total_seconds_including_other_language / 3600, 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<i18n-t
|
||||
v-if="stats && stats.coding && stats.editors && stats.os && stats.languages && time && date && hours"
|
||||
keypath="stats"
|
||||
tag="p"
|
||||
>
|
||||
<template #time>
|
||||
{{ time }}
|
||||
</template>
|
||||
<template #date>
|
||||
<HoverText
|
||||
:hover="t('tooltip.date')"
|
||||
:text="date"
|
||||
/>
|
||||
</template>
|
||||
<template #hours>
|
||||
<HoverText
|
||||
:hover="t('tooltip.hours')"
|
||||
:text="hours"
|
||||
/>
|
||||
</template>
|
||||
<template #editors>
|
||||
{{ stats.editors.data.slice(0, 2).map(editor => `${editor.name} (${editor.percent}%)`).join(t('separator')) }}
|
||||
</template>
|
||||
<template
|
||||
v-if="stats.os.data[0]"
|
||||
#os
|
||||
>
|
||||
{{ stats.os.data[0].name }} ({{ stats.os.data[0].percent }}%)
|
||||
</template>
|
||||
<template #languages>
|
||||
{{
|
||||
stats.languages.data.slice(0, 2).map(language => `${language.name} (${language.percent}%)`).join(t('separator'))
|
||||
}}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"stats": "I collect some data for {time} years, started the {date}. I've coded for a total of {hours} hours. My best editors are {editors}. My best OS is {os}. My top languages are {languages}.",
|
||||
"separator": " and ",
|
||||
"tooltip": {
|
||||
"date": "That was so long ago 🫣",
|
||||
"hours": "That's a lot 😮"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stats": "Je collecte des données depuis {time} ans, commencé le {date}. J'ai codé un total de {hours} heures. Mes meilleurs éditeurs sont {editors}. Mon meilleur OS est {os}. Mes langages préférés sont {languages}.",
|
||||
"separator": " et ",
|
||||
"tooltip": {
|
||||
"date": "C'était il y a si longtemps 🫣",
|
||||
"hours": "C'est beaucoup 😮"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stats": "Recopilo datos desde hace {time} años, empecé el {date}. He programado durante un total de {hours} horas. Mis mejores editores son {editors}. Mi mejor OS es {os}. Y mis lenguajes favoritos son {languages}.",
|
||||
"separator": " y ",
|
||||
"tooltip": {
|
||||
"date": "hace tato tiempo…🫣",
|
||||
"hours": "es mucho 😮"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
127
app/components/home/live/Activity.vue
Normal file
127
app/components/home/live/Activity.vue
Normal 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>
|
||||
178
app/components/home/live/Stats.vue
Normal file
178
app/components/home/live/Stats.vue
Normal 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>
|
||||
192
app/components/home/live/StatusPage.vue
Normal file
192
app/components/home/live/StatusPage.vue
Normal 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>
|
||||
64
app/components/home/timeline/Education.vue
Normal file
64
app/components/home/timeline/Education.vue
Normal 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>
|
||||
64
app/components/home/timeline/Experiences.vue
Normal file
64
app/components/home/timeline/Experiences.vue
Normal 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>
|
||||
@@ -1,45 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
const closed = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UAlert
|
||||
v-if="locale !== 'en' && !closed"
|
||||
:description="t('alert.description')"
|
||||
:title="t('alert.title')"
|
||||
color="red"
|
||||
icon="i-ph-warning-duotone"
|
||||
variant="soft"
|
||||
:close="{
|
||||
color: 'red',
|
||||
}"
|
||||
@update:open="closed = true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"alert": {
|
||||
"title": "Translations alert!",
|
||||
"description": "For time reasons, all article translations will only be available in English. Thank you for your understanding."
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"alert": {
|
||||
"title": "Attention aux traductions !",
|
||||
"description": "Pour des raisons de temps, toutes les traductions d'articles ne seront disponibles qu'en anglais. Merci de votre compréhension."
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"alert": {
|
||||
"title": "¡Atención a las traducciones!",
|
||||
"description": "Por razones de tiempo, todas las traducciones de los artículos estarán disponibles solo en inglés. Gracias por su comprensión."
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,68 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8 border bg-white/70 dark:bg-black/70 border-gray-200 dark:border-neutral-700 rounded-md">
|
||||
<NuxtImg
|
||||
src="/arthur.webp"
|
||||
alt="Arthur Danjou"
|
||||
class="w-24 h-24 rounded-full float-left mr-4 mb-4"
|
||||
/>
|
||||
<i18n-t
|
||||
keypath="thanks"
|
||||
tag="p"
|
||||
class="text-neutral-600 dark:text-neutral-400 text-justify"
|
||||
>
|
||||
<template #linkedin>
|
||||
<HomeLink
|
||||
href="https://www.linkedin.com/in/arthurdanjou/"
|
||||
icon="i-ph-linkedin-logo-duotone"
|
||||
label="LinkedIn"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #github>
|
||||
<HomeLink
|
||||
href="https://github.com/arthurdanjou"
|
||||
icon="i-ph-github-logo-duotone"
|
||||
label="GitHub"
|
||||
target="_blank"
|
||||
class="inline-flex items-start gap-1 transform translate-y-1"
|
||||
/>
|
||||
</template>
|
||||
<template #comment>
|
||||
<strong class="text-neutral-800 dark:text-neutral-200">{{ t('comment') }}</strong>
|
||||
</template>
|
||||
<template #name>
|
||||
<strong class="text-neutral-800 dark:text-neutral-200">{{ t('name') }}</strong>
|
||||
</template>
|
||||
<template #jump>
|
||||
<br> <br>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"thanks": "Thanks for reading! My name is {name}, and I love writing about AI, data science, and the intersection between mathematics and programming. {jump} I've been coding and exploring math for years, and I'm always learning something new—whether it's self-hosting tools in my homelab, experimenting with machine learning models, or diving into statistical methods. {jump} I share my knowledge here because I know how valuable clear, hands-on resources can be, especially when you're just getting started or exploring something deeply technical. {jump} If you have any questions or just want to chat, feel free to reach out to me on {linkedin} or {github }. {jump} I hope you enjoyed this post and learned something useful. If you did, {comment}—it really helps and means a lot!",
|
||||
"comment": "consider sharing it",
|
||||
"name": "Arthur"
|
||||
},
|
||||
"es": {
|
||||
"thanks": "¡Gracias por leer! Me llamo {name} y me encanta escribir sobre inteligencia artificial, ciencia de datos y todo lo que se encuentra en la intersección entre las matemáticas y la programación. {jump} Llevo años programando y explorando las matemáticas, y cada día aprendo algo nuevo — ya sea autoalojando herramientas en mi homelab, experimentando con modelos de aprendizaje automático o profundizando en métodos estadísticos. {jump} Comparto mis conocimientos aquí porque sé lo valiosos que pueden ser los recursos claros, prácticos y accesibles, especialmente cuando uno está empezando o explorando temas técnicos en profundidad. {jump} Si tienes alguna pregunta o simplemente quieres charlar, no dudes en dejar un comentario abajo o contactarme por {linkedin} o {github}. {jump} Espero que este artículo te haya gustado y que hayas aprendido algo útil. Si es así, {comment} — ¡me ayuda mucho y significa mucho para mí!",
|
||||
"comment": "considera compartirlo",
|
||||
"name": "Arthur"
|
||||
},
|
||||
"fr": {
|
||||
"thanks": "Merci de votre lecture ! Je m'appelle {name}, et j'adore écrire sur l'intelligence artificielle, la data science, et tout ce qui se situe à l'intersection entre les mathématiques et la programmation. {jump} Je code et j'explore les maths depuis des années, et j'apprends encore de nouvelles choses chaque jour — que ce soit en auto-hébergeant des outils dans mon homelab, en expérimentant des modèles de machine learning ou en approfondissant des méthodes statistiques. {jump} Je partage mes connaissances ici parce que je sais à quel point des ressources claires, pratiques et accessibles peuvent être précieuses, surtout quand on débute ou qu'on explore un sujet technique en profondeur. {jump} Si vous avez des questions ou simplement envie d'échanger, n'hésitez pas à laisser un commentaire ci-dessous ou à me contacter sur {linkedin} ou {github}. {jump} J'espère que cet article vous a plu et qu'il vous a appris quelque chose d'utile. Si c'est le cas, {comment} — ça m'aide beaucoup et ça me fait vraiment plaisir !",
|
||||
"comment": "pensez à le partager",
|
||||
"name": "Arthur"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,23 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UsesItem } from '#components'
|
||||
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object as PropType<typeof UsesItem>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<p class="text-base font-semibold text-black dark:text-white">
|
||||
{{ item.name }}
|
||||
</p>
|
||||
<p class="text-sm">
|
||||
{{ locale === 'en' ? item.description.en : locale === 'es' ? item.description.es : item.description.fr }}
|
||||
</p>
|
||||
</li>
|
||||
</template>
|
||||
@@ -1,22 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: Object as PropType<{ en: string, fr: string, es: string }>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { locale } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<USeparator
|
||||
:label="locale === 'en' ? title.en : locale === 'es' ? title.es : title.fr"
|
||||
size="xs"
|
||||
/>
|
||||
<ul class="space-y-8">
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
9
app/composables/content.ts
Normal file
9
app/composables/content.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export async function useContent() {
|
||||
const skills = await queryCollection('skills').where('extension', '=', 'json').first()
|
||||
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, contact }
|
||||
}
|
||||
21
app/composables/projects.ts
Normal file
21
app/composables/projects.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,10 @@
|
||||
</h1>
|
||||
<p class="text-center flex gap-1">
|
||||
I think you're lost, let's go back
|
||||
<HomeLink label="home" href="/" />
|
||||
<HomeLink
|
||||
label="home"
|
||||
href="/"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</UApp>
|
||||
|
||||
33
app/pages/ecosystem.vue
Normal file
33
app/pages/ecosystem.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
useSeoMeta({
|
||||
title: 'Coming Soon',
|
||||
description: 'This page is currently under development.',
|
||||
robots: 'noindex, nofollow'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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>
|
||||
38
app/pages/hobbies.vue
Normal file
38
app/pages/hobbies.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
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>
|
||||
<main>
|
||||
<ContentRenderer
|
||||
v-if="page"
|
||||
:value="page"
|
||||
class="mt-8 md:mt-16"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
@@ -1,24 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
const { locale } = useI18n()
|
||||
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
|
||||
})
|
||||
|
||||
const { data: page } = await useAsyncData(`/home/${locale.value}`, () => {
|
||||
return queryCollection('main').path(`/home/${locale.value}`).first()
|
||||
}, {
|
||||
watch: [locale],
|
||||
defineOgImageComponent('NuxtSeo', {
|
||||
title: head.title,
|
||||
description: head.description,
|
||||
subtitle: head.subtitle,
|
||||
theme: '#F43F5E'
|
||||
})
|
||||
|
||||
const { data: page } = await useAsyncData('index', () => {
|
||||
return queryCollection('index').first()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="!max-w-none prose dark:prose-invert">
|
||||
<ContentRenderer v-if="page" :value="page" class="mt-8 md:mt-16" />
|
||||
<HomeStats />
|
||||
<HomeActivity />
|
||||
<HomeQuote />
|
||||
<HomeCatchPhrase />
|
||||
<main class="max-w-none! prose dark:prose-invert">
|
||||
<ContentRenderer
|
||||
v-if="page"
|
||||
:value="page"
|
||||
class="mt-8 md:mt-16"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -1,182 +1,170 @@
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: project } = await useAsyncData(`projects/${route.params.slug}`, () =>
|
||||
queryCollection('projects').path(`/projects/${route.params.slug}`).first())
|
||||
const slug = route.params.slug as string
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
const { data: project } = await useAsyncData(`project-${slug}`, () => {
|
||||
return queryCollection('projects').where('extension', '=', 'md').where('slug', '=', slug).first()
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: project.value?.title,
|
||||
description: project.value?.description,
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
|
||||
function top() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
if (!project.value) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
message: 'Project not found'
|
||||
})
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard({
|
||||
source: `https://arthurdanjou.fr/projects/${route.params.slug}`,
|
||||
copiedDuring: 4000,
|
||||
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">
|
||||
<div class="flex">
|
||||
<NuxtLinkLocale
|
||||
class="flex items-center gap-2 mb-8 group text-sm hover:text-black dark:hover:text-white duration-300"
|
||||
to="/projects"
|
||||
>
|
||||
<UIcon
|
||||
class="group-hover:-translate-x-1 transform duration-300"
|
||||
name="i-ph-arrow-left-duotone"
|
||||
size="20"
|
||||
/>
|
||||
{{ t('back') }}
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
<PostAlert class="mb-8" />
|
||||
<main
|
||||
v-if="project"
|
||||
class="space-y-8"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-end justify-between gap-2 flex-wrap">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1
|
||||
class="font-bold text-3xl text-black dark:text-white"
|
||||
>
|
||||
<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>
|
||||
<UTooltip
|
||||
:text="t('tooltip.favorite')" :content="{
|
||||
align: 'center',
|
||||
side: 'right',
|
||||
sideOffset: 8,
|
||||
}"
|
||||
arrow
|
||||
>
|
||||
<UIcon
|
||||
|
||||
<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"
|
||||
name="i-ph-star-duotone"
|
||||
size="24"
|
||||
class="text-amber-500"
|
||||
/>
|
||||
</UTooltip>
|
||||
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
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
</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"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(project.publishedAt, 'DD MMMM YYYY').value }} </p>
|
||||
</div>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<p class="mt-2 text-base">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="project.cover"
|
||||
class="w-full rounded-md my-8"
|
||||
|
||||
<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"
|
||||
>
|
||||
<ProseImg
|
||||
:src="`/projects/${project.cover}`"
|
||||
label="Project cover"
|
||||
/>
|
||||
</div>
|
||||
<USeparator
|
||||
class="my-4"
|
||||
icon="i-ph-pencil-line-duotone"
|
||||
/>
|
||||
<ClientOnly>
|
||||
<ContentRenderer
|
||||
:value="project"
|
||||
class="!max-w-none prose dark:prose-invert"
|
||||
/>
|
||||
</ClientOnly>
|
||||
<div class="space-y-4 mt-8">
|
||||
<PostFooter />
|
||||
<div class="flex gap-4 items-center flex-wrap">
|
||||
<UButton
|
||||
color="neutral"
|
||||
icon="i-ph-arrow-fat-lines-up-duotone"
|
||||
:label="t('top')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="top()"
|
||||
/>
|
||||
<UButton
|
||||
v-if="copied"
|
||||
color="green"
|
||||
icon="i-ph-check-square-duotone"
|
||||
:label="t('link.copied')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="copy()"
|
||||
/>
|
||||
<UButton
|
||||
v-else
|
||||
color="neutral"
|
||||
icon="i-ph-square-duotone"
|
||||
:label="t('link.copy')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="copy()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #empty>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">
|
||||
No content available
|
||||
</p>
|
||||
</template>
|
||||
</ContentRenderer>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.prose h2 a,
|
||||
.prose h3 a,
|
||||
.prose h4 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.prose img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.katex-html {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"back": "Go back",
|
||||
"link": {
|
||||
"copied": "Link copied",
|
||||
"copy": "Copy link"
|
||||
},
|
||||
"top": "Go to top"
|
||||
|
||||
},
|
||||
"fr": {
|
||||
"back": "Retourner en arrière",
|
||||
"link": {
|
||||
"copied": "Lien copié",
|
||||
"copy": "Copier le lien"
|
||||
},
|
||||
"top": "Remonter en haut"
|
||||
},
|
||||
"es": {
|
||||
"back": "Volver atrás",
|
||||
"link": {
|
||||
"copied": "Link copiado",
|
||||
"copy": "Copiar link"
|
||||
},
|
||||
"top": "Ir arribaarr"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,117 +1,278 @@
|
||||
<script setup lang="ts">
|
||||
import { TAGS } from '~~/types'
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Projects',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const { data: projects } = await useAsyncData('all-projects', () => {
|
||||
<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-12">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<PostAlert />
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-8">
|
||||
<NuxtLink
|
||||
v-for="(project, id) in projects"
|
||||
:key="id"
|
||||
:to="project.path"
|
||||
>
|
||||
<li
|
||||
class="flex flex-col h-full group hover:bg-neutral-200/50 duration-300 p-2 rounded-lg dark:hover:bg-neutral-800/50 transition-colors justify-center"
|
||||
<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"
|
||||
>
|
||||
<article class="space-y-2">
|
||||
<div
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="font-bold duration-300 text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-white">
|
||||
{{ project.title }}
|
||||
</h1>
|
||||
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="t('tooltip.favorite')" :content="{
|
||||
align: 'center',
|
||||
side: 'right',
|
||||
sideOffset: 8,
|
||||
}"
|
||||
arrow
|
||||
:text="project.title"
|
||||
:popper="{ placement: 'top-start' }"
|
||||
class="w-full relative z-10"
|
||||
>
|
||||
<UIcon
|
||||
v-if="project.favorite"
|
||||
name="i-ph-star-duotone"
|
||||
size="16"
|
||||
class="text-amber-500 hover:rotate-360 duration-500"
|
||||
/>
|
||||
</UTooltip>
|
||||
</div>
|
||||
<h3 class="text-md text-neutral-500 dark:text-neutral-400 italic">
|
||||
{{ project.description }}
|
||||
</h3>
|
||||
</div>
|
||||
</article>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center mt-1">
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<ClientOnly>
|
||||
<p>{{ useDateFormat(project.publishedAt, 'DD MMM YYYY').value }} </p>
|
||||
</ClientOnly>
|
||||
<span class="w-2" />
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
v-for="tag in project.tags.sort((a: any, b: any) => a.localeCompare(b))"
|
||||
:key="tag"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
<NuxtLink
|
||||
:to="`/projects/${project.slug}`"
|
||||
class="block focus:outline-none"
|
||||
>
|
||||
{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}
|
||||
<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>
|
||||
</ClientOnly>
|
||||
<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>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "All my projects I have worked on, both academic and personal",
|
||||
"description": "A collection of my projects using R, Python, or web development technologies. These projects span various domains, including data analysis, machine learning, and web applications, showcasing my skills in coding, problem-solving, and project development.",
|
||||
"tooltip": {
|
||||
"favorite": "This project is one of my favorites"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"title": "Tous mes projets auxquels j'ai travaillé, académiques et personnels",
|
||||
"description": "Une collection de mes projets réalisés en R, Python, ou en développement web. Ces projets couvrent divers domaines, y compris l'analyse de données, l'apprentissage automatique et les applications web, mettant en avant mes compétences en codage, résolution de problèmes et développement de projets.",
|
||||
"tooltip": {
|
||||
"favorite": "Ce projet est l'un de mes favoris"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"title": "Todos mis proyectos en los que he trabajado, académicos y personales",
|
||||
"description": "Una colección de mis proyectos realizados en R, Python o tecnologías de desarrollo web. Estos proyectos abarcan diversos campos, como análisis de datos, aprendizaje automático y aplicaciones web, mostrando mis habilidades en programación, resolución de problemas y desarrollo de proyectos.",
|
||||
"tooltip": {
|
||||
"favorite": "Este proyecto es uno de mis favoritos"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,99 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
<script lang="ts" setup>
|
||||
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: 'Things I use',
|
||||
description: t('description'),
|
||||
title: head.title,
|
||||
description: head.description,
|
||||
ogTitle: `${head.title} • Arthur Danjou`,
|
||||
ogDescription: head.description,
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: head.title,
|
||||
twitterDescription: head.description
|
||||
})
|
||||
|
||||
const { data: items } = await useAsyncData('uses', async () => await queryCollection('uses').all())
|
||||
const { data: categories } = await useAsyncData('categories', async () => await queryCollection('categories').all())
|
||||
|
||||
const photos = [
|
||||
{
|
||||
src: '/uses/jetbrains.webp',
|
||||
caption: 'caption.jetbrains',
|
||||
},
|
||||
{
|
||||
src: '/uses/pycharm.webp',
|
||||
caption: 'caption.pycharm',
|
||||
},
|
||||
{
|
||||
src: '/uses/vscode.webp',
|
||||
caption: 'caption.vscode',
|
||||
},
|
||||
]
|
||||
defineOgImageComponent('NuxtSeo', {
|
||||
title: head.title,
|
||||
subtitle: head.subtitle,
|
||||
description: head.description,
|
||||
theme: '#F43F5E'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
<ContentRenderer
|
||||
v-if="page"
|
||||
:value="page"
|
||||
class="mt-8 md:mt-16"
|
||||
/>
|
||||
<div v-if="items" class="mt-12 space-y-12">
|
||||
<UsesList v-for="category in categories" :key="category.id" :title="category.name">
|
||||
<UsesItem
|
||||
v-for="(item, id) in items.filter(item => item.category === String(category.meta.title).toLowerCase())"
|
||||
:key="id"
|
||||
:item="item"
|
||||
/>
|
||||
<div v-if="category.carousel && category.carousel === 'ides'" class="relative">
|
||||
<UCarousel
|
||||
v-slot="{ item }"
|
||||
arrows
|
||||
loop
|
||||
class="rounded-lg"
|
||||
:autoplay="{ delay: 4000 }"
|
||||
:items="photos"
|
||||
:prev="{ variant: 'ghost' }"
|
||||
:next="{ variant: 'ghost' }"
|
||||
prev-icon="i-lucide-chevron-left"
|
||||
next-icon="i-lucide-chevron-right"
|
||||
>
|
||||
<ProseImg
|
||||
rounded
|
||||
:src="item.src"
|
||||
:label="t(item.caption)"
|
||||
:caption="t(item.caption)"
|
||||
/>
|
||||
</UCarousel>
|
||||
</div>
|
||||
</UsesList>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "My uses",
|
||||
"description": "Software I use, gadgets I love, and other things I recommend. Here’s a big list of all of my favorite stuff.",
|
||||
"caption": {
|
||||
"jetbrains": "My IntelliJ IDE",
|
||||
"vscode": "My Visual Studio Code IDE",
|
||||
"pycharm": "My PyCharm IDE"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"title": "Mes usages",
|
||||
"description": "Logiciels que j'utilise, gadgets que j'adore et autres choses que je recommande. Voici une grande liste de toutes mes choses préférées.",
|
||||
"caption": {
|
||||
"jetbrains": "Mon IDE IntelliJ Idea Ultimate",
|
||||
"vscode": "Mon IDE Visual Studio Code",
|
||||
"pycharm": "Mon IDE PyCharm"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"title": "Mis aplicaciones.",
|
||||
"description": "Los programas que utilizo, los gadgets que adoro y otras cosas que recomiendo. Aquí te hago una lista de todas mis cosas preferidas. ",
|
||||
"caption": {
|
||||
"jetbrains": "Mi IDE IntelliJ Idea Ultimate",
|
||||
"vscode": "Mi IDE Visual Studio Code",
|
||||
"pycharm": "Mi IDE PyCharm"
|
||||
}
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: post } = await useAsyncData(`writings/${route.params.slug}`, () =>
|
||||
queryCollection('writings').path(`/writings/${route.params.slug}`).first())
|
||||
|
||||
const {
|
||||
data: postDB,
|
||||
refresh,
|
||||
} = await useAsyncData(`writings/${route.params.slug}/db`, () => $fetch(`/api/posts/${route.params.slug}`, { method: 'POST' }))
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
|
||||
function top() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
const { copy, copied } = useClipboard({
|
||||
source: `https://arthurdanjou.fr/writings/${route.params.slug}`,
|
||||
copiedDuring: 4000,
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
title: post.value?.title,
|
||||
description: post.value?.description,
|
||||
author: 'Arthur Danjou',
|
||||
})
|
||||
|
||||
function getDetails() {
|
||||
const likes = postDB.value?.likes ?? 0
|
||||
const views = postDB.value?.views ?? 0
|
||||
|
||||
const like = likes > 1 ? t('likes.many') : t('likes.one')
|
||||
const view = views > 1 ? t('views.many') : t('views.one')
|
||||
|
||||
return {
|
||||
likes: `${likes} ${like}`,
|
||||
views: `${views} ${view}`,
|
||||
}
|
||||
}
|
||||
|
||||
const likeCookie = useCookie<boolean>(`post:like:${route.params.slug}`, {
|
||||
maxAge: 7200,
|
||||
})
|
||||
|
||||
async function handleLike() {
|
||||
if (likeCookie.value)
|
||||
return
|
||||
await $fetch(`/api/posts/like/${route.params.slug}`, { method: 'PUT' })
|
||||
await refresh()
|
||||
likeCookie.value = true
|
||||
}
|
||||
|
||||
function scrollToSection(id: string) {
|
||||
const element = document.getElementById(id)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-if="post && postDB">
|
||||
<div class="flex">
|
||||
<NuxtLinkLocale
|
||||
class="flex items-center gap-2 mb-8 group text-sm hover:text-black dark:hover:text-white duration-300"
|
||||
to="/writings"
|
||||
>
|
||||
<UIcon
|
||||
class="group-hover:-translate-x-1 transform duration-300"
|
||||
name="i-ph-arrow-left-duotone"
|
||||
size="20"
|
||||
/>
|
||||
{{ t('back') }}
|
||||
</NuxtLinkLocale>
|
||||
</div>
|
||||
<PostAlert class="mb-8" />
|
||||
<div class="border-l-2 pl-2 rounded-none border-gray-300 dark:border-neutral-700 flex gap-1 items-center">
|
||||
<UIcon name="i-ph-heart-duotone" size="16" />
|
||||
<p>{{ getDetails().likes }} </p>·
|
||||
<UIcon name="i-ph-eye-duotone" size="16" />
|
||||
<p>{{ getDetails().views }}</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="flex items-end gap-4 flex-wrap">
|
||||
<h1
|
||||
class="font-bold text-3xl text-black dark:text-white"
|
||||
>
|
||||
{{ post.title }}
|
||||
</h1>
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<UIcon name="ph:calendar-duotone" size="16" />
|
||||
<p>{{ useDateFormat(post.publishedAt, 'DD MMMM YYYY').value }} </p>·
|
||||
<UIcon name="ph:timer-duotone" size="16" />
|
||||
<p>{{ post.readingTime }}min long</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="my-4 text-base">
|
||||
{{ post.description }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="post.body.toc && post.body.toc.links.length > 0" class="pt-4 top-0 flex justify-end sticky z-50">
|
||||
<UPopover
|
||||
mode="click"
|
||||
:content="{
|
||||
align: 'end',
|
||||
side: 'bottom',
|
||||
sideOffset: 8,
|
||||
}"
|
||||
>
|
||||
<UButton
|
||||
:label="t('toc')"
|
||||
variant="solid"
|
||||
color="neutral"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
|
||||
<template #content>
|
||||
<div class="p-2 z-50 flex flex-col gap-y-2">
|
||||
<div
|
||||
v-for="link in post!.body!.toc!.links"
|
||||
:key="link.id"
|
||||
class="inline"
|
||||
>
|
||||
<UButton
|
||||
size="lg"
|
||||
:label="link.text"
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
:block="true"
|
||||
class="flex justify-start items-start p-1 cursor-pointer"
|
||||
@click="scrollToSection(link.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</div>
|
||||
<div
|
||||
v-if="post.cover"
|
||||
class="w-full rounded-md mb-8"
|
||||
>
|
||||
<ProseImg
|
||||
:src="`/writings/${post.cover}`"
|
||||
label="Writing cover"
|
||||
/>
|
||||
</div>
|
||||
<USeparator
|
||||
class="mt-4"
|
||||
icon="i-ph-pencil-line-duotone"
|
||||
/>
|
||||
<article class="mt-8">
|
||||
<ClientOnly>
|
||||
<ContentRenderer
|
||||
:value="post"
|
||||
class="!max-w-none prose dark:prose-invert"
|
||||
/>
|
||||
</ClientOnly>
|
||||
<div class="space-y-4 mt-8">
|
||||
<PostFooter />
|
||||
<div class="flex gap-4 items-center flex-wrap justify-between sm:justify-start">
|
||||
<UButton
|
||||
:label="(postDB?.likes ?? 0) > 1 ? `${postDB?.likes ?? 0} likes` : `${postDB?.likes ?? 0} like`"
|
||||
:color="likeCookie ? 'red' : 'neutral'"
|
||||
icon="i-ph-heart-duotone"
|
||||
size="lg"
|
||||
:variant="likeCookie ? 'solid' : 'outline'"
|
||||
@click.prevent="handleLike()"
|
||||
/>
|
||||
<UButton
|
||||
color="neutral"
|
||||
icon="i-ph-arrow-fat-lines-up-duotone"
|
||||
:label="t('top')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="top()"
|
||||
/>
|
||||
<UButton
|
||||
v-if="copied"
|
||||
color="green"
|
||||
icon="i-ph-check-square-duotone"
|
||||
:label="t('link.copied')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="copy()"
|
||||
/>
|
||||
<UButton
|
||||
v-else
|
||||
color="neutral"
|
||||
icon="i-ph-square-duotone"
|
||||
:label="t('link.copy')"
|
||||
size="lg"
|
||||
variant="outline"
|
||||
@click.prevent="copy()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.prose h2 a,
|
||||
.prose h3 a,
|
||||
.prose h4 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.prose img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.katex-html {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"likes": {
|
||||
"one": "like",
|
||||
"many": "likes"
|
||||
},
|
||||
"views": {
|
||||
"one": "view",
|
||||
"many": "views"
|
||||
},
|
||||
"link": {
|
||||
"copied": "Link copied",
|
||||
"copy": "Copy link"
|
||||
},
|
||||
"top": "Go to top",
|
||||
"back": "Go back",
|
||||
"toc": "Table of contents"
|
||||
},
|
||||
"fr": {
|
||||
"likes": {
|
||||
"one": "like",
|
||||
"many": "likes"
|
||||
},
|
||||
"views": {
|
||||
"one": "vue",
|
||||
"many": "vues"
|
||||
},
|
||||
"link": {
|
||||
"copied": "Lien copié",
|
||||
"copy": "Copier le lien"
|
||||
},
|
||||
"top": "Remonter en haut",
|
||||
"back": "Retourner en arrière",
|
||||
"toc": "Table des matières"
|
||||
},
|
||||
"es": {
|
||||
"likes": {
|
||||
"one": "like",
|
||||
"many": "likes"
|
||||
},
|
||||
"views": {
|
||||
"one": "view",
|
||||
"many": "views"
|
||||
},
|
||||
"link": {
|
||||
"copied": "Link copiado",
|
||||
"copy": "Copiar link"
|
||||
},
|
||||
"top": "Ir arribaarr",
|
||||
"back": "Volver atrás",
|
||||
"toc": "Tabla de contenidos"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,111 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { TAGS } from '~~/types'
|
||||
|
||||
const { t } = useI18n({
|
||||
useScope: 'local',
|
||||
})
|
||||
useSeoMeta({
|
||||
title: 'My Shelf',
|
||||
description: t('description'),
|
||||
})
|
||||
|
||||
const { data: writings } = await useAsyncData('all-writings', () => {
|
||||
return queryCollection('writings')
|
||||
.order('publishedAt', 'DESC')
|
||||
.all()
|
||||
})
|
||||
|
||||
const groupedWritings = computed(() => {
|
||||
const grouped: Record<string, any[]> = {}
|
||||
writings.value!.forEach((writing: any) => {
|
||||
const year = new Date(writing.publishedAt).getFullYear().toString()
|
||||
if (!grouped[year]) {
|
||||
grouped[year] = []
|
||||
}
|
||||
grouped[year].push(writing)
|
||||
})
|
||||
return Object.entries(grouped).reverse()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-12 mb-12 relative">
|
||||
<AppTitle
|
||||
:description="t('description')"
|
||||
:title="t('title')"
|
||||
/>
|
||||
<PostAlert />
|
||||
<div class="space-y-8">
|
||||
<div v-for="year in groupedWritings" :key="year[0]" class="lg:space-y-6 relative">
|
||||
<h2 class="text-4xl lg:absolute top-2 -left-16 font-bold opacity-10 select-none pointer-events-none lg:[writing-mode:vertical-rl] lg:[text-orientation:upright] pl-1 lg:pl-0">
|
||||
{{ year[0] }}
|
||||
</h2>
|
||||
<ul class="relative grid grid-cols-1 gap-2">
|
||||
<NuxtLink
|
||||
v-for="(writing, id) in year[1]"
|
||||
:key="id"
|
||||
:to="writing.path"
|
||||
>
|
||||
<li
|
||||
class="h-full group hover:bg-neutral-200/50 duration-300 p-1 lg:p-2 rounded-lg dark:hover:bg-neutral-800/50 transition-colors"
|
||||
>
|
||||
<h1
|
||||
class="font-bold text-lg duration-300 text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-900 dark:group-hover:text-white"
|
||||
>
|
||||
{{ writing.title }}
|
||||
</h1>
|
||||
<h3 class="text-neutral-600 dark:text-neutral-400 italic">
|
||||
{{ writing.description }}
|
||||
</h3>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between mt-1">
|
||||
<div
|
||||
class="text-sm text-neutral-500 duration-300 flex items-center gap-1"
|
||||
>
|
||||
<ClientOnly>
|
||||
<p>{{ useDateFormat(writing.publishedAt, 'DD MMM').value }} </p>
|
||||
</ClientOnly>
|
||||
<span>·</span>
|
||||
<p>{{ writing.readingTime }}min</p>
|
||||
<span class="w-2" />
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<ClientOnly>
|
||||
<UBadge
|
||||
v-for="tag in writing.tags.sort((a: any, b: any) => a.localeCompare(b))"
|
||||
:key="tag"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
>
|
||||
{{ TAGS.find(color => color.label.toLowerCase() === tag)?.label }}
|
||||
</UBadge>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</NuxtLink>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<i18n lang="json">
|
||||
{
|
||||
"en": {
|
||||
"title": "Writings on math, artificial intelligence, development, and my passions.",
|
||||
"description": "All my reflections on programming, mathematics, artificial intelligence design, etc., are organized chronologically."
|
||||
},
|
||||
"fr": {
|
||||
"title": "Écrits sur les maths, l'intelligence artificielle, le développement et mes passions.",
|
||||
"description": "Toutes mes réflexions sur la programmation, les mathématiques, la conception de l'intelligence artificielle, etc., sont mises en ordre chronologique.",
|
||||
"alert": {
|
||||
"title": "Attentions aux traductions!",
|
||||
"description": "Par soucis de temps, toutes les traductions des articles seront disponibles uniquement en anglais. Merci de votre compréhension."
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"title": "Escritos sobre matemáticas, inteligencia artificial, desarrollo y mis pasiones.",
|
||||
"description": "Todas mis reflexiones sobre programación, matemáticas, diseño de inteligencia artificial, etc., están organizadas cronológicamente."
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -1,60 +1,112 @@
|
||||
import { defineCollection, z } from '@nuxt/content'
|
||||
import { asSeoCollection } from '@nuxtjs/seo/content'
|
||||
|
||||
export const collections = {
|
||||
main: defineCollection({
|
||||
index: defineCollection({
|
||||
type: 'page',
|
||||
source: 'home/*.md',
|
||||
}),
|
||||
projects: defineCollection({
|
||||
type: 'page',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.date(),
|
||||
tags: z.array(z.string()),
|
||||
cover: z.string(),
|
||||
favorite: z.boolean().optional(),
|
||||
}),
|
||||
}),
|
||||
writings: defineCollection({
|
||||
type: 'page',
|
||||
source: 'writings/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
publishedAt: z.date(),
|
||||
readingTime: z.number(),
|
||||
cover: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
categories: defineCollection({
|
||||
type: 'data',
|
||||
source: 'uses/categories/*.json',
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
name: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
carousel: z.string().optional(),
|
||||
}),
|
||||
source: 'index.md'
|
||||
}),
|
||||
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: 'data',
|
||||
source: 'uses/*.json',
|
||||
schema: z.object({
|
||||
name: z.string(),
|
||||
description: z.object({
|
||||
en: z.string(),
|
||||
fr: z.string(),
|
||||
es: z.string(),
|
||||
}),
|
||||
category: z.string(),
|
||||
}),
|
||||
type: 'page',
|
||||
source: 'uses.md'
|
||||
}),
|
||||
skills: defineCollection({
|
||||
type: 'data',
|
||||
source: 'skills.json',
|
||||
schema: z.object({
|
||||
body: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
items: z.array(z.object({
|
||||
name: z.string(),
|
||||
icon: z.string().optional()
|
||||
}))
|
||||
}))
|
||||
})
|
||||
}),
|
||||
experiences: defineCollection({
|
||||
type: 'data',
|
||||
source: 'experiences/*.md',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
type: z.string().optional(),
|
||||
company: z.string(),
|
||||
companyUrl: z.string().url().optional(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string().optional(),
|
||||
duration: z.string().optional(),
|
||||
location: z.string(),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
icon: z.string()
|
||||
})
|
||||
}),
|
||||
education: defineCollection({
|
||||
type: 'data',
|
||||
source: 'education/*.md',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
degree: z.string().optional(),
|
||||
institution: z.string(),
|
||||
startDate: z.string(),
|
||||
endDate: z.string().optional(),
|
||||
duration: z.string().optional(),
|
||||
location: z.string(),
|
||||
description: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
icon: z.string()
|
||||
})
|
||||
}),
|
||||
contact: defineCollection({
|
||||
type: 'data',
|
||||
source: 'contact.json',
|
||||
schema: z.object({
|
||||
body: z.array(z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
category: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
value: z.string().url(),
|
||||
username: z.string().optional(),
|
||||
priority: z.number().optional()
|
||||
}))
|
||||
})
|
||||
}),
|
||||
hobbies: defineCollection({
|
||||
type: 'page',
|
||||
source: 'hobbies.md'
|
||||
}),
|
||||
languages: defineCollection({
|
||||
type: 'data',
|
||||
source: 'languages.json',
|
||||
schema: z.object({
|
||||
body: z.array(z.object({
|
||||
name: z.string(),
|
||||
level: z.string(),
|
||||
proficiency: z.string()
|
||||
}))
|
||||
})
|
||||
}),
|
||||
profile: defineCollection({
|
||||
type: 'page',
|
||||
source: 'profile.md'
|
||||
})
|
||||
}
|
||||
|
||||
69
content/contact.json
Normal file
69
content/contact.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"id": "personal-email",
|
||||
"name": "Personal Email",
|
||||
"category": "communication",
|
||||
"icon": "i-ph-envelope-simple-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/mail-perso",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "professional-email",
|
||||
"name": "Professional Email",
|
||||
"category": "communication",
|
||||
"icon": "i-ph-envelope-simple-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/mail-pro",
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"id": "linkedin",
|
||||
"name": "LinkedIn",
|
||||
"category": "social",
|
||||
"icon": "i-ph:linkedin-logo-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/linkedin",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "github",
|
||||
"name": "GitHub",
|
||||
"category": "social",
|
||||
"icon": "i-ph:github-logo-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/github",
|
||||
"username": "ArthurDanjou",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "twitter",
|
||||
"name": "Twitter / X",
|
||||
"category": "social",
|
||||
"icon": "i-ph:x-logo-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/twitter",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "discord",
|
||||
"name": "Discord",
|
||||
"category": "communication",
|
||||
"icon": "i-ph:discord-logo-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/discord",
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"id": "personal-website",
|
||||
"name": "Portfolio",
|
||||
"category": "web",
|
||||
"icon": "i-ph:globe-duotone",
|
||||
"value": "https://arthurdanjou.fr",
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"id": "status-page",
|
||||
"name": "Status Page",
|
||||
"category": "infrastructure",
|
||||
"icon": "i-ph:fire-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/status",
|
||||
"priority": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
15
content/education/bachelor.md
Normal file
15
content/education/bachelor.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Bachelor's Degree in Mathematics
|
||||
degree: Bachelor
|
||||
institution: Paris-Saclay University
|
||||
location: Paris, France
|
||||
startDate: 2021-09
|
||||
endDate: 2024-06
|
||||
duration: 3 years
|
||||
description: Comprehensive study of pure and applied mathematics, providing a strong foundation in mathematical theory and problem-solving.
|
||||
tags:
|
||||
- Mathematics
|
||||
- Physics
|
||||
- Computer Science
|
||||
icon: i-ph-math-operations-duotone
|
||||
---
|
||||
16
content/education/doctorate.md
Normal file
16
content/education/doctorate.md
Normal 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
|
||||
---
|
||||
16
content/education/m1.md
Normal file
16
content/education/m1.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Master's Degree in Applied Mathematics (Year 1)
|
||||
degree: Master
|
||||
institution: Paris Dauphine-PSL University
|
||||
location: Paris, France
|
||||
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:
|
||||
- Applied Mathematics
|
||||
- Data Science
|
||||
- Machine Learning
|
||||
- Optimization
|
||||
icon: i-ph-number-circle-one-duotone
|
||||
---
|
||||
16
content/education/m2.md
Normal file
16
content/education/m2.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Master's Degree in Applied Mathematics (Year 2)
|
||||
degree: Master
|
||||
institution: Paris Dauphine-PSL University
|
||||
location: Paris, France
|
||||
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:
|
||||
- Applied Mathematics
|
||||
- Advanced Machine Learning
|
||||
- Data Engineering
|
||||
- Research
|
||||
icon: i-ph-number-circle-two-duotone
|
||||
---
|
||||
18
content/experiences/artdanjproduction.md
Normal file
18
content/experiences/artdanjproduction.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Freelancer
|
||||
type: Freelance
|
||||
company: ArtDanjProduction
|
||||
companyUrl: https://go.arthurdanjou.fr/website
|
||||
location: Paris, France
|
||||
startDate: 2022-02
|
||||
endDate: null
|
||||
duration: 3+ years
|
||||
description: As a freelancer, I designed, developed, and maintained various personal projects, exploring new programming languages and technologies. I also write documentation and articles related to my projects, fix bugs, and ensure their smooth operation in production. Additionally, I manage my Proxmox and Docker-based homelab, hosting multiple services, and set up network infrastructure to optimize performance and stability.
|
||||
tags:
|
||||
- Java
|
||||
- TypeScript
|
||||
- HomeLab
|
||||
- Docker
|
||||
- Self-Hosted
|
||||
icon: i-ph-briefcase-metal-duotone
|
||||
---
|
||||
18
content/experiences/erisium.md
Normal file
18
content/experiences/erisium.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Junior Developer
|
||||
type: Employment
|
||||
company: Erisium
|
||||
companyUrl: https://x.com/Erisium
|
||||
location: Remote, France
|
||||
startDate: 2021-09
|
||||
endDate: 2022-09
|
||||
duration: 1 year
|
||||
description: At Erisium, one of the most popular French-speaking Minecraft servers, I worked as a Junior Java Developer. I developed mini-games designed by the game design team, and worked on backend infrastructure optimizations to handle several thousand concurrent players. This experience allowed me to solve a wide range of complex bugs and to grow within a collaborative, high-performance technical environment.
|
||||
tags:
|
||||
- Java
|
||||
- Docker
|
||||
- Minecraft
|
||||
- Backend
|
||||
- Game Development
|
||||
icon: i-ph-cube-duotone
|
||||
---
|
||||
19
content/experiences/hackathon-cnd.md
Normal file
19
content/experiences/hackathon-cnd.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Hackathon CND - Machine Learning for Cybersecurity
|
||||
type: Hackathon
|
||||
company: French Armies ministry
|
||||
companyUrl: https://www.defense.gouv.fr/cnd
|
||||
location: Fort du Mont-Valérien, Suresnes, France
|
||||
startDate: 2025-11
|
||||
endDate: 2025-11
|
||||
duration: 3 days
|
||||
description: Developed a Python ML pipeline during the CND hackathon to classify system logs for bug and attack detection. Implemented feature extraction and preprocessing, trained and evaluated models (tree-based and lightweight neural), tuned thresholds to favor recall, and delivered a realtime prototype with visualization and reproducible code in collaboration with CND engineers. Implemented a Streamlit application to test the classifier interactively and used an LLM to generate contextual help explaining the likely origin and indicators of detected bugs or attacks for end users.
|
||||
tags:
|
||||
- Python
|
||||
- Machine Learning
|
||||
- AI
|
||||
- Cybersecurity
|
||||
- Streamlit
|
||||
- LLM
|
||||
icon: i-ph-shield-check-duotone
|
||||
---
|
||||
17
content/experiences/picard.md
Normal file
17
content/experiences/picard.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Sales Assistant II
|
||||
type: Student Job
|
||||
company: Picard Surgelés
|
||||
companyUrl: https://picard.fr
|
||||
location: Paris, France
|
||||
startDate: 2022-09
|
||||
endDate: 2024-10
|
||||
duration: 2+ years
|
||||
description: As part of my student job at Picard, I welcomed and advised customers while handling product restocking and in-store deliveries. I placed orders according to overall stock, monitored the cold chain, and maintained freezer cleanliness, ensuring product quality and safety.
|
||||
tags:
|
||||
- Sales
|
||||
- Customer Service
|
||||
- Retail
|
||||
- Team Work
|
||||
icon: i-ph-shopping-cart-duotone
|
||||
---
|
||||
18
content/experiences/sevetys.md
Normal file
18
content/experiences/sevetys.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Data Engineer Intern
|
||||
type: Internship
|
||||
company: Sevetys
|
||||
companyUrl: https://sevetys.fr
|
||||
location: Paris, France
|
||||
startDate: 2025-06
|
||||
endDate: 2025-07
|
||||
duration: 2 months
|
||||
description: At Sevetys, I worked as a Data Engineer on topics related to client and patient data. My responsibilities included Python development using PySpark on Microsoft Azure, data modeling based on business needs, and ensuring data quality. This experience allowed me to deepen my data engineering skills while working autonomously in a demanding cloud-based environment.
|
||||
tags:
|
||||
- Python
|
||||
- PySpark
|
||||
- Microsoft Azure
|
||||
- Data Engineering
|
||||
- Data Quality
|
||||
icon: i-ph-dog-duotone
|
||||
---
|
||||
61
content/hobbies.md
Normal file
61
content/hobbies.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Balance & Perspectives
|
||||
description: Exploring the passions—from competitive sports to high-velocity optimization—that fuel my creativity and resilience.
|
||||
---
|
||||
|
||||
# Beyond the Lab
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ High-Velocity Interests
|
||||
|
||||
I am drawn to environments where strategy, speed, and precision intersect. These are not just pastimes, but exercises in optimization under constraints.
|
||||
|
||||
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
|
||||
|
||||
::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.
|
||||
::
|
||||
|
||||
::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.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Perspectives & Culture
|
||||
|
||||
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."
|
||||
::
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: Arthur Danjou • Mathematics Lover and IA Enthusiast
|
||||
description: I'm Arthur, a Mathematics lover and IA enthusiast. I'm currently
|
||||
studying at the University of Paris-Saclay. I'm passionate about Mathematics,
|
||||
Computer Science, and Artificial Intelligence.
|
||||
---
|
||||
|
||||
Hey, I'm :home-name, a Mathematics student in Statistics at the Paris-Dauphine University in France.
|
||||
|
||||
With a :hover-text{hover="Technology is evolving far too quickly 🤯" position="top" text="deep understanding"} of emerging technologies, I'm at the heart of a rapidly expanding field. My background in :hover-text{hover="Maths is my main passion ∑" position="right" text="mathematics"} gives me a head start in understanding the concepts and theories behind these :hover-text{hover="My second passion 📱" text="technologies"} and in designing them effectively.
|
||||
|
||||
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"},
|
||||
: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.
|
||||
|
||||
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"} .
|
||||
|
||||
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"} .
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
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 Dauphine-PSL. I'm passionate about
|
||||
Mathematics, Computer Science, and Artificial Intelligence.
|
||||
---
|
||||
|
||||
Hola ! Soy :home-name, estudiante de matemáticas especializado en Estadística en la Universidad Paris-Dauphine, en Francia.
|
||||
|
||||
Con una :hover-text{hover="la tecnología avanza demasiado rápido 🤯" position="top" text="comprensión profunda"} de las tecnologías emergentes, me sitúo en el centro de un ámbito en plena expansión. Mi formación en :hover-text{hover="las matemáticas son mi mayor pasión Σ" position="right" text="matemáticas"} me permite comprender ampliamente los conceptos y las teorías que gobiernan las dichas :hover-text{hover="mi segunda pasión 📲" text="tecnologías"} y también poder concebirlas de manera eficaz.
|
||||
|
||||
Como ingeniero de software y estudiante de matemáticas, mi :hover-text{hover="mi mochila de conocimientos 🎒" text="conocimientos"} cubre
|
||||
: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"},
|
||||
:prose-icon[Python]{color="amber" icon="i-logos:python"},
|
||||
:prose-icon[R]{color="blue" icon="i-logos:r-lang"},
|
||||
esto me permite :hover-text{hover="entender rápidamente la complejidad de los proyectos 🏎️" text="comprender"} las diferentes necesidades de los proyectos matemáticos, y proponer las mejores soluciones.
|
||||
Utilizo herramientas como
|
||||
:prose-icon[scikit-learn]{color="orange" icon="devicon-scikitlearn"} para el aprendizaje supervisado,
|
||||
:prose-icon[pandas]{color="blue" icon="i-logos:pandas-icon"} para la manipulación eficiente de datos,
|
||||
:prose-icon[NumPy]{color="indigo" icon="i-logos:numpy"} para el cálculo científico, y
|
||||
:prose-icon[TensorFlow]{color="orange" icon="i-logos:tensorflow"} así como :prose-icon[PyTorch]{color="orange" icon="i-logos:pytorch-icon"} para construir y entrenar modelos de aprendizaje profundo. También he aprendido otras tecnologías importantes como :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"} y
|
||||
:prose-icon[Git]{color="orange" icon="i-logos:git-icon"} que :hover-text{hover="todas estas tecnologías se complementan 📎" text="completan"} mis competencias.
|
||||
|
||||
Estas herramientas me permiten ir desde :hover-text{hover="Exploración, limpieza, reorganización…" text="preparación de datos"} hasta :hover-text{hover="Entrenamiento, evaluación, despliegue" text="despliegue"} de modelos en entornos reales, siempre con rigurosidad estadística y un enfoque en el rendimiento. Me apasiona la IA y la :hover-text{hover="La IA es el futuro de la tecnología 🤖" text="ciencia de datos"} .
|
||||
|
||||
Estoy :hover-text{hover="me gusta estar siempre al día 🖥️" position="top" text="constantemente"} aprendiendo cosas nuevas, desde la tecnología hasta las finanzas, pasando por el emprendimiento. Me gusta :hover-text{hover="me encanta compartir y ayudar a los demás 🫂" text="compartir"} mis conocimientos y aprender nuevos teoremas y tecnologías. Soy una persona :hover-text{hover="busco cosas nuevas que descubrir 🔍" text="curiosa"} y con el deseo de seguir aprendiendo y creciendo a lo largo de toda mi vida.
|
||||
|
||||
Aparte de la programación, me gusta el :hover-text{hover="el deporte me permite gastar mi energía 🏋️♂️" text="deporte"} y :hover-text{hover="los viajes me permiten desconectar ✈️" text="viajar"} . Mi pasión, mi compromiso y mis ganas de aprender y mejorar son las cualidades que me permiten triunfar en mi :hover-text{hover="carrera que ya he empezado, y le queda mucho para terminar 😎" text="carrera"} y en mis :hover-text{hover="solo me quedan 2 años de estudios 💪" text="estudios"} .
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
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 Dauphine-PSL. I'm passionate about
|
||||
Mathematics, Computer Science, and Artificial Intelligence.
|
||||
---
|
||||
|
||||
Salut, je suis :home-name, étudiant en mathématiques spécialisé en Statistiques à l'Université Paris-Dauphine en France.
|
||||
|
||||
Avec une :hover-text{hover="La technologie évolue beaucoup trop vite 🤯" position="top" text="compréhension profonde"} des technologies émergentes, je suis au cœur d'un domaine en pleine expansion. Ma formation en :hover-text{hover="Les
|
||||
mathématiques sont ma principale passion ∑" position="right" text="mathématiques"} me donne une longueur d'avance pour
|
||||
comprendre les concepts et les théories qui sous-tendent ces :hover-text{hover="Ma deuxième passion 📱" text="technologies"} et à les concevoir efficacement.
|
||||
|
||||
En tant qu'ingénieur logiciel et étudiant en mathématiques, mon :hover-text{hover="Mon sac de connaissances 🎒" text="expertise"} couvre
|
||||
:prose-icon[TypeScript]{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"},
|
||||
:prose-icon[Python]{color="amber" icon="i-logos:python"},
|
||||
:prose-icon[R]{color="blue" icon="i-logos:r-lang"},
|
||||
ce qui me permet de :hover-text{hover="Comprendre rapidement la complexité des projets 🏎️" text="comprendre"} les
|
||||
différents besoins des projets mathématiques et de proposer les meilleures solutions.
|
||||
J'utilise des outils comme
|
||||
:prose-icon[scikit-learn]{color="orange" icon="i-devicon-scikitlearn"} pour l'apprentissage supervisé,
|
||||
:prose-icon[pandas]{color="blue" icon="i-logos:pandas-icon"} pour la manipulation efficace de données,
|
||||
:prose-icon[NumPy]{color="indigo" icon="i-logos:numpy"} pour le calcul scientifique, et
|
||||
:prose-icon[TensorFlow]{color="orange" icon="i-logos:tensorflow"} ainsi que :prose-icon[PyTorch]{color="orange" icon="i-logos:pytorch-icon"} pour la création et l'entraînement de modèles profonds.
|
||||
J'ai également appris d'autres technologies importantes, telles que
|
||||
: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"} et
|
||||
:prose-icon[Git]{color="orange" icon="i-logos:git-icon"} pour :hover-text{hover="Toutes ces technologies se complètent 🔗" text="compléter"} mes connaissances.
|
||||
|
||||
Ma maîtrise de ces bibliothèques me permet de passer de la :hover-text{hover="Exploration, nettoyage, mise en forme…" text="préparation des données"} jusqu'au :hover-text{hover="Entraînement, évaluation, déploiement" text="déploiement"} de modèles dans des environnements réels, toujours avec rigueur statistique et souci de performance.
|
||||
Je suis passionné par l'IA et la :hover-text{hover="L'IA est l'avenir de la technologie 🤖" text="science des données"} .
|
||||
|
||||
Je suis :hover-text{hover="Je dois toujours chercher à être à jour 🖥️" position="top" text="constamment"} dans
|
||||
l'apprentissage de nouvelles choses, de la technologie à la finance en passant par l'entrepreneuriat. J'aime
|
||||
:hover-text{hover="J'aime partager et aider les autres 🫂" text="partager"} mes connaissances et apprendre de nouveaux
|
||||
théorèmes et technologies. Je suis une personne :hover-text{hover="Je cherche à découvrir de nouvelles choses" text="curieuse"} et désireuse de continuer à apprendre et à grandir tout au long de ma vie.
|
||||
|
||||
Outre la programmation, j'aime le :hover-text{hover="Le sport me permet de dépenser de l'énergie 🏋️♂️" text="sport"} et :hover-text{hover="Les voyages me libèrent et m'évadent ✈️" text="voyager"} .
|
||||
Ma passion, mon engagement et mon envie d'apprendre et de progresser sont les qualités qui me permettent de réussir dans
|
||||
ma :hover-text{hover="Carrière déjà commencée et loin d'être terminée 😎" text="carrière"} et mes :hover-text{hover="Il
|
||||
ne me reste que 2 ans d'études 💪" text="études"} .
|
||||
68
content/index.md
Normal file
68
content/index.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
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 :hover-text{text="Master 2 student in Statistical & Financial Engineering (Master ISF)" hover="Université Paris-Dauphine - PSL" position="bottom"} at Paris-Dauphine University.
|
||||
|
||||
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.
|
||||
|
||||
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[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"}.
|
||||
|
||||
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"}.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 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
|
||||
::
|
||||
19
content/languages.json
Normal file
19
content/languages.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"name": "French",
|
||||
"level": "Native",
|
||||
"proficiency": "C2"
|
||||
},
|
||||
{
|
||||
"name": "English",
|
||||
"level": "Fluent",
|
||||
"proficiency": "C1"
|
||||
},
|
||||
{
|
||||
"name": "Spanish",
|
||||
"level": "Intermediate",
|
||||
"proficiency": "A2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,24 +1,28 @@
|
||||
---
|
||||
slug: arthome
|
||||
title: 🏡 ArtHome
|
||||
description: Your personalized browser homepage
|
||||
publishedAt: 2024/09/04
|
||||
title: ArtHome - Browser Homepage
|
||||
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: Archived
|
||||
tags:
|
||||
- web
|
||||
- Nuxt
|
||||
- Vue.js
|
||||
- Web
|
||||
- Productivity
|
||||
icon: i-ph-house-duotone
|
||||
---
|
||||
|
||||
[ArtHome](https://home.arthurdanjou.fr) 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.
|
||||
|
||||
44
content/projects/artlab.md
Normal file
44
content/projects/artlab.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
slug: artlab
|
||||
title: ArtLab - Personal HomeLab
|
||||
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
|
||||
favorite: true
|
||||
status: Active
|
||||
tags:
|
||||
- Docker
|
||||
- Proxmox
|
||||
- HomeLab
|
||||
- Self-Hosted
|
||||
- Infrastructure
|
||||
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.
|
||||
|
||||
## 🛠️ Running Services
|
||||
|
||||
- **MinIO**: S3-compatible object storage for static files and backups.
|
||||
- **Immich**: Self-hosted photo management platform — a private alternative to Google Photos.
|
||||
- **Jellyfin**: Media server for streaming movies, shows, and music.
|
||||
- **Portainer & Docker**: Container orchestration and service management.
|
||||
- **Traefik**: Reverse proxy and automatic HTTPS with Let's Encrypt.
|
||||
- **Homepage**: A sleek dashboard to access and monitor all services.
|
||||
- **Proxmox**: Virtualization platform used to manage VMs and containers.
|
||||
- **Uptime Kuma**: Self-hosted uptime monitoring.
|
||||
- **Home Assistant**: Smart home automation and device integration.
|
||||
- **AdGuard Home**: Network-wide ad and tracker blocking via DNS.
|
||||
- **Beszel**: Self-hosted, lightweight alternative to Notion for notes and knowledge management.
|
||||
- **Palmr**: Personal logging and journaling tool.
|
||||
|
||||
## 🖥️ Hardware Specifications
|
||||
|
||||
- **Beelink EQR6**: AMD Ryzen mini PC, main server host.
|
||||
- **TP-Link 5-port Switch**: Network connectivity for all devices.
|
||||
- **UGREEN NASync DXP4800 Plus**: 4-bay NAS, currently populated with 2 × 8TB drives for storage and backups.
|
||||
|
||||
This homelab is a sandbox for DevOps experimentation, infrastructure reliability, and privacy-respecting digital autonomy.
|
||||
@@ -1,31 +1,45 @@
|
||||
---
|
||||
slug: artsite
|
||||
title: 🌍 ArtSite
|
||||
description: My personal website, portfolio, and blog — all in one.
|
||||
publishedAt: 2024/06/01
|
||||
readingTime: 1
|
||||
cover: artsite/cover.png
|
||||
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:
|
||||
- web
|
||||
- Nuxt
|
||||
- NuxtHub
|
||||
- Cloudflare Workers
|
||||
- TypeScript
|
||||
icon: i-ph-globe-hemisphere-west-duotone
|
||||
---
|
||||
|
||||
[**ArtSite**](https://arthurdanjou.fr) 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.
|
||||
[**ArtSite**](https://go.arthurdanjou.fr/website) is my digital headquarters—a unified platform serving as my engineering portfolio and experimental lab.
|
||||
|
||||
It’s designed to be fast, accessible, and fully responsive. The site also serves as a playground to explore and test modern frontend tools.
|
||||
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.
|
||||
|
||||
### ⚒️ Tech Stack
|
||||
## ⚡ The Nuxt Stack Architecture
|
||||
|
||||
- **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.
|
||||
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.
|
||||
71
content/projects/artstudies.md
Normal file
71
content/projects/artstudies.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
slug: artstudies
|
||||
title: ArtStudies - Academic Projects Collection
|
||||
type: Academic Project
|
||||
description: A curated collection of mathematics and data science projects developed during my academic journey, spanning Bachelor's and Master's studies.
|
||||
publishedAt: 2023-09-01
|
||||
readingTime: 1
|
||||
favorite: true
|
||||
status: In progress
|
||||
tags:
|
||||
- Python
|
||||
- R
|
||||
- Data Science
|
||||
- Mathematics
|
||||
icon: i-ph-book-duotone
|
||||
---
|
||||
|
||||
[**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 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
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
- `L3`
|
||||
- `Analyse Matricielle`
|
||||
- `Analyse Multidimensionnelle`
|
||||
- `Calculs Numériques`
|
||||
- `Équations Différentielles`
|
||||
- `Méthodes Numériques`
|
||||
- `Probabilités`
|
||||
- `Projet Numérique`
|
||||
- `Statistiques`
|
||||
|
||||
- `M1`
|
||||
- `Data Analysis`
|
||||
- `General Linear Models`
|
||||
- `Monte Carlo Methods`
|
||||
- `Numerical Methods`
|
||||
- `Numerical Optimization`
|
||||
- `Portfolio Management`
|
||||
- `Statistical Learning`
|
||||
|
||||
- `M2`
|
||||
- `Data Visualisation`
|
||||
- `Deep Learning`
|
||||
- `Linear Models`
|
||||
- `Machine Learning`
|
||||
- `VBA`
|
||||
- `SQL`
|
||||
|
||||
## 🛠️ 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.
|
||||
@@ -1,18 +1,51 @@
|
||||
---
|
||||
slug: bikes-glm
|
||||
title: 🚲 Generalized Linear Models for Bikes prediction
|
||||
description: Predicting the number of bikes rented in a bike-sharing system using Generalized Linear Models.
|
||||
publishedAt: 2025/01/24
|
||||
title: Generalized Linear Models for Bikes Prediction
|
||||
type: Academic Project
|
||||
description: Predicting the number of bikes rented in a bike-sharing system using Generalized Linear Models and various statistical techniques.
|
||||
publishedAt: 2025-01-24
|
||||
readingTime: 1
|
||||
status: Completed
|
||||
tags:
|
||||
- r
|
||||
- data
|
||||
- maths
|
||||
- R
|
||||
- Statistics
|
||||
- GLM
|
||||
- Mathematics
|
||||
icon: i-ph-bicycle-duotone
|
||||
---
|
||||
|
||||
The project was done as part of the course `Generalised Linear Model` at the Paris-Dauphine PSL University. The goal of the project is to determine the best model that predicts/explains the number of bicycle rentals, based on various characteristics of the day (temperature, humidity, wind speed, etc.).
|
||||
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.
|
||||
|
||||
You can find the code here: [GLM Bikes Code](https://github.com/ArthurDanjou/Studies/blob/master/M1/General%20Linear%20Models/Projet/GLM%20Code%20-%20DANJOU%20%26%20DUROUSSEAU.rmd)
|
||||
## 📊 Project Objectives
|
||||
|
||||
<iframe src="/projects/bikes-glm/Report.pdf" width="100%" height="1000px">
|
||||
- Determine the best predictive model for bicycle rental counts
|
||||
- Analyze the impact of various features (temperature, humidity, wind speed, seasonality, etc.)
|
||||
- Apply and evaluate different generalized linear modeling techniques
|
||||
- Validate model assumptions and performance metrics
|
||||
|
||||
## 🔍 Methodology
|
||||
|
||||
The study employs rigorous statistical approaches including:
|
||||
|
||||
- **Exploratory Data Analysis (EDA)** - Understanding feature distributions and relationships
|
||||
- **Model Comparison** - Testing multiple GLM families (Poisson, Negative Binomial, Gaussian)
|
||||
- **Feature Selection** - Identifying the most influential variables
|
||||
- **Model Diagnostics** - Validating assumptions and checking residuals
|
||||
- **Cross-validation** - Ensuring robust performance estimates
|
||||
|
||||
## 📁 Key Findings
|
||||
|
||||
The analysis identified critical factors influencing bike-sharing demand:
|
||||
- Seasonal patterns and weather conditions
|
||||
- Temperature and humidity effects
|
||||
- Holiday and working day distinctions
|
||||
- Time-based trends and cyclical patterns
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
You can find the code here: [GLM Bikes Code](https://go.arthurdanjou.fr/glm-bikes-code)
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/bikes-glm.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -1,42 +1,49 @@
|
||||
---
|
||||
slug: breast-cancer
|
||||
title: 💉 Breast Cancer Detection
|
||||
description: Prediction of breast cancer presence by comparing several supervised classification models.
|
||||
publishedAt: 2025/06/06
|
||||
title: Breast Cancer Detection
|
||||
type: Academic Project
|
||||
description: Prediction of breast cancer presence by comparing several supervised classification models using machine learning techniques.
|
||||
publishedAt: 2025-06-06
|
||||
readingTime: 2
|
||||
status: Completed
|
||||
tags:
|
||||
- python
|
||||
- data
|
||||
- maths
|
||||
- Python
|
||||
- Machine Learning
|
||||
- Classification
|
||||
- Healthcare
|
||||
icon: i-ph-heart-half-duotone
|
||||
---
|
||||
|
||||
The project was carried out as part of the `Statistical Learning` course at Paris-Dauphine PSL University. Its objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
|
||||
This project was carried out as part of the **Statistical Learning** course at Paris-Dauphine PSL University. The objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
|
||||
|
||||
This project aims to develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
|
||||
## 📊 Project Objectives
|
||||
|
||||
Develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
|
||||
|
||||
The dataset contains 116 observations divided into two classes:
|
||||
|
||||
- 1: healthy individuals (controls)
|
||||
|
||||
- 2: patients diagnosed with breast cancer
|
||||
- **1**: healthy individuals (controls)
|
||||
- **2**: patients diagnosed with breast cancer
|
||||
|
||||
There are 9 explanatory variables, including clinical measurements such as age, insulin levels, leptin, insulin resistance, among others.
|
||||
|
||||
## 🔍 Methodology
|
||||
|
||||
The project follows a comparative approach between several algorithms:
|
||||
|
||||
- Logistic Regression
|
||||
|
||||
- k-Nearest Neighbors (k-NN)
|
||||
|
||||
- Naive Bayes
|
||||
|
||||
- Artificial Neural Network (MLP with a 16-8-1 architecture)
|
||||
|
||||
Model evaluation is primarily based on the F1-score, which is more suitable in a medical context where identifying positive cases is crucial. Particular attention was paid to stratified cross-validation and to handling class imbalance, notably through the use of class weights and regularization techniques (L2, early stopping).
|
||||
|
||||
This project illustrates a concrete application of data science techniques to a public health issue, while implementing a rigorous methodology for supervised modeling.
|
||||
|
||||
You can find the code here: [Breast Cancer Detection](https://github.com/ArthurDanjou/breast-cancer-detection)
|
||||
## 📚 Resources
|
||||
|
||||
<iframe src="/projects/breast-cancer/report.pdf" width="100%" height="1000px">
|
||||
You can find the code here: [Breast Cancer Detection](https://go.arthurdanjou.fr/breast-cancer-detection-code)
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/breast-cancer.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
54
content/projects/data-visualisation.md
Normal file
54
content/projects/data-visualisation.md
Normal 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>
|
||||
157
content/projects/dropout-reduces-underfitting.md
Normal file
157
content/projects/dropout-reduces-underfitting.md
Normal file
@@ -0,0 +1,157 @@
|
||||
---
|
||||
slug: dropout-reduces-underfitting
|
||||
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: 6
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- TensorFlow
|
||||
- Deep Learning
|
||||
- Research
|
||||
icon: i-ph-share-network-duotone
|
||||
---
|
||||
|
||||
> **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)
|
||||
|
||||
This repository contains a robust and modular implementation in **TensorFlow/Keras** of **Early Dropout** and **Late Dropout** strategies. The goal is to verify the hypothesis that dropout, traditionally used to reduce overfitting, can also combat underfitting when applied solely during the initial training phase.
|
||||
|
||||
## 🎯 Scientific Objectives
|
||||
|
||||
The study aims to validate the three operating regimes of Dropout described in the paper:
|
||||
|
||||
1. **Early Dropout** (Targeting Underfitting): Active only during the initial phase to reduce gradient variance and align their direction, allowing for better final optimization.
|
||||
2. **Late Dropout** (Targeting Overfitting): Disabled at the start to allow rapid learning, then activated to regularize final convergence.
|
||||
3. **Standard Dropout**: Constant rate throughout training (Baseline).
|
||||
4. **No Dropout**: Control experiment without dropout.
|
||||
|
||||
## 🛠️ Technical Architecture
|
||||
|
||||
Unlike naive Keras callback implementations, this project uses a **dynamic approach via the TensorFlow graph** to ensure the dropout rate is properly updated on the GPU without model recompilation.
|
||||
|
||||
### Key Components
|
||||
|
||||
* **`DynamicDropout`**: A custom layer inheriting from `keras.layers.Layer` that reads its rate from a shared `tf.Variable`.
|
||||
* **`DropoutScheduler`**: A Keras `Callback` that drives the rate variable based on the current epoch and the chosen strategy (`early`, `late`, `standard`).
|
||||
* **`ExperimentPipeline`**: An orchestrator class that handles data loading (MNIST, CIFAR-10, Fashion MNIST), model creation (Dense or CNN), and execution of comparative benchmarks.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── README.md # This documentation file
|
||||
├── Dropout reduces underfitting.pdf # Original research paper
|
||||
├── pipeline.py # Main experiment pipeline
|
||||
├── pipeline.ipynb # Jupyter notebook for experiments
|
||||
├── pipeline_mnist.ipynb # Jupyter notebook for MNIST experiments
|
||||
├── pipeline_cifar10.ipynb # Jupyter notebook for CIFAR-10 experiments
|
||||
├── pipeline_cifar100.ipynb # Jupyter notebook for CIFAR-100 experiments
|
||||
├── pipeline_fashion_mnist.ipynb # Jupyter notebook for Fashion MNIST experiments
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .python-version # Python version specification
|
||||
└── uv.lock # Dependency lock file
|
||||
```
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/arthurdanjou/dropoutreducesunderfitting.git
|
||||
cd dropoutreducesunderfitting
|
||||
```
|
||||
|
||||
## Install dependencies
|
||||
```bash
|
||||
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
|
||||
|
||||
Choose your dataset (cifar10, fashion_mnist, mnist) and architecture (cnn, dense).
|
||||
```python
|
||||
from pipeline import ExperimentPipeline
|
||||
|
||||
# Fashion MNIST is recommended to observe underfitting/overfitting nuances
|
||||
exp = ExperimentPipeline(dataset_name="fashion_mnist", model_type="cnn")
|
||||
```
|
||||
|
||||
### 2. Learning Curves Comparison
|
||||
|
||||
Compare training dynamics (Loss & Accuracy) of the three strategies.
|
||||
|
||||
```python
|
||||
exp.compare_learning_curves(
|
||||
modes=["standard", "early", "late"],
|
||||
switch_epoch=10, # The epoch where dropout state changes
|
||||
rate=0.4, # Dropout rate
|
||||
epochs=30
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Ablation Studies
|
||||
|
||||
Study the impact of the "Early" phase duration or Dropout intensity.
|
||||
|
||||
```python
|
||||
# Impact of the switch epoch on final performance
|
||||
exp.compare_switch_epochs(
|
||||
switch_epochs=[5, 10, 15, 20],
|
||||
modes=["early"],
|
||||
rate=0.4,
|
||||
epochs=30
|
||||
)
|
||||
|
||||
# Impact of the dropout rate
|
||||
exp.compare_drop_rates(
|
||||
rates=[0.2, 0.4, 0.6],
|
||||
modes=["standard", "early"],
|
||||
switch_epoch=10,
|
||||
epochs=25
|
||||
)
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
```python
|
||||
# Training on 10%, 50% and 100% of the dataset
|
||||
exp.run_dataset_size_comparison(
|
||||
fractions=[0.1, 0.5, 1.0],
|
||||
modes=["standard", "early"],
|
||||
rate=0.3,
|
||||
switch_epoch=10
|
||||
)
|
||||
```
|
||||
|
||||
## 📈 Expected Results
|
||||
|
||||
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)
|
||||
- [Alexis Mathieu](https://github.com/Alex6535)
|
||||
- [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.
|
||||
40
content/projects/loan-ml.md
Normal file
40
content/projects/loan-ml.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
slug: loan-ml
|
||||
title: Machine Learning for Loan Prediction
|
||||
type: Academic Project
|
||||
description: Predicting loan approval and default risk using machine learning classification techniques.
|
||||
publishedAt: 2025-01-24
|
||||
readingTime: 2
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- Machine Learning
|
||||
- Regression
|
||||
- Finance
|
||||
- Data Science
|
||||
icon: i-ph-money-wavy-duotone
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
- Build and compare multiple classification models for loan prediction
|
||||
- Identify key factors influencing loan approval decisions
|
||||
- Evaluate model performance using appropriate metrics
|
||||
- Optimize model parameters for better predictive accuracy
|
||||
|
||||
## 🔍 Methodology
|
||||
|
||||
The study employs various machine learning approaches:
|
||||
|
||||
- **Exploratory Data Analysis (EDA)** - Understanding applicant characteristics and patterns
|
||||
- **Feature Engineering** - Creating meaningful features from raw data
|
||||
- **Model Comparison** - Testing multiple algorithms (Logistic Regression, Random Forest, Gradient Boosting, etc.)
|
||||
- **Hyperparameter Tuning** - Optimizing model performance
|
||||
- **Cross-validation** - Ensuring robust generalization
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/loan-ml.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
@@ -1,17 +1,25 @@
|
||||
---
|
||||
slug: monte-carlo-project
|
||||
title: 💻 Monte Carlo Methods Project
|
||||
description: A project to demonstrate the use of Monte Carlo methods in R.
|
||||
publishedAt: 2024/11/24
|
||||
title: Monte Carlo Methods Project
|
||||
type: Academic Project
|
||||
description: An implementation of different Monte Carlo methods and algorithms in R, including inverse CDF simulation, accept-reject methods, and stratification techniques.
|
||||
publishedAt: 2024-11-24
|
||||
readingTime: 3
|
||||
status: Completed
|
||||
tags:
|
||||
- r
|
||||
- maths
|
||||
- R
|
||||
- Mathematics
|
||||
- Statistics
|
||||
- Monte Carlo
|
||||
- Numerical Methods
|
||||
- Estimation
|
||||
icon: i-ph-dice-five-duotone
|
||||
---
|
||||
|
||||
This is the report for the Monte Carlo Methods Project. The project was done as part of the course `Monte Carlo Methods` at the Paris-Dauphine University. The goal was to implement different methods and algorithms using Monte Carlo methods in R.
|
||||
This report presents the Monte Carlo Methods Project completed as part of the **Monte Carlo Methods** course at Paris-Dauphine University. The goal was to implement different methods and algorithms using Monte Carlo methods in R.
|
||||
|
||||
## 🛠️ Methods and Algorithms
|
||||
|
||||
Methods and algorithms implemented:
|
||||
- Plotting graphs of functions
|
||||
- Inverse c.d.f. Random Variation simulation
|
||||
- Accept-Reject Random Variation simulation
|
||||
@@ -19,7 +27,11 @@ Methods and algorithms implemented:
|
||||
- Cumulative density function
|
||||
- Empirical Quantile Function
|
||||
|
||||
You can find the code here: [Monte Carlo Project Code](https://github.com/ArthurDanjou/Studies/blob/0c83e7e381344675e113c43b6f8d32e88a5c00a7/M1/Monte%20Carlo%20Methods/Project%201/003_rapport_DANJOU_DUROUSSEAU.rmd)
|
||||
## 📚 Resources
|
||||
|
||||
<iframe src="/projects/monte-carlo-project/Report.pdf" width="100%" height="1000px">
|
||||
You can find the code here: [Monte Carlo Project Code](https://go.arthurdanjou.fr/monte-carlo-code)
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/monte-carlo.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
---
|
||||
slug: schelling-segregation-model
|
||||
title: 📊 Schelling Segregation Model
|
||||
description: A Python implementation of the Schelling Segregation Model using Statistics and Data Visualization.
|
||||
publishedAt: 2024/05/03
|
||||
title: Schelling Segregation Model
|
||||
type: Academic Project
|
||||
description: A Python implementation of the Schelling Segregation Model using statistics and data visualization to analyze spatial segregation patterns.
|
||||
publishedAt: 2024-05-03
|
||||
readingTime: 4
|
||||
status: Completed
|
||||
tags:
|
||||
- python
|
||||
- maths
|
||||
- Python
|
||||
- Data Visualization
|
||||
- Statistics
|
||||
- Modeling
|
||||
- Mathematics
|
||||
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.
|
||||
|
||||
You can find the code here: [Schelling Segregation Model Code](https://github.com/ArthurDanjou/Studies/blob/e1164f89bd11fc59fa79d94aa51fac69b425d68b/L3/Projet%20Num%C3%A9rique/Segregation.ipynb)
|
||||
## 📚 Resources
|
||||
|
||||
<iframe src="/projects/schelling/Projet.pdf" width="100%" height="1000px">
|
||||
You can find the code here: [Schelling Segregation Model Code](https://go.arthurdanjou.fr/schelling-code)
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/schelling.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
---
|
||||
slug: sevetys
|
||||
title: 🐶 Data Engineer Internship
|
||||
description: Summary of my internship as a Data Engineer at Sevetys
|
||||
publishedAt: 2025/07/31
|
||||
title: Data Engineer Internship at Sevetys
|
||||
type: Internship Project
|
||||
description: Summary of my internship as a Data Engineer at Sevetys, focusing on data quality, cleaning, standardization, and comprehensive data quality metrics.
|
||||
publishedAt: 2025-07-31
|
||||
readingTime: 2
|
||||
status: Completed
|
||||
tags:
|
||||
- python
|
||||
- data
|
||||
favorite: false
|
||||
- Python
|
||||
- PySpark
|
||||
- Data Engineering
|
||||
- Azure
|
||||
- Big Data
|
||||
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.
|
||||
|
||||
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.
|
||||
## 🎯 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
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
slug: studies
|
||||
title: 🎓 Studies Projects
|
||||
description: A curated collection of mathematics and data science projects developed during my academic journey.
|
||||
publishedAt: 2023/09/01
|
||||
readingTime: 1
|
||||
favorite: true
|
||||
tags:
|
||||
- data
|
||||
- python
|
||||
- r
|
||||
---
|
||||
|
||||
[Studies Projects](https://github.com/ArthurDanjou/studies) 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:
|
||||
- **L3** – Third year of the Bachelor's degree in Mathematics
|
||||
- **M1** – First year of the Master's degree in Mathematics
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
- `L3`
|
||||
- `Analyse Matricielle`
|
||||
- `Analyse Multidimensionnelle`
|
||||
- `Calculs Numériques`
|
||||
- `Équations Différentielles`
|
||||
- `Méthodes Numériques`
|
||||
- `Probabilités`
|
||||
- `Projet Numérique`
|
||||
- `Statistiques`
|
||||
|
||||
- `M1`
|
||||
- `Data Analysis`
|
||||
- `General Linear Models`
|
||||
- `Monte Carlo Methods`
|
||||
- `Numerical Methods`
|
||||
- `Numerical Optimization`
|
||||
- `Portfolio Management`
|
||||
- `Statistical Learning`
|
||||
|
||||
## 🛠️ 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.
|
||||
- [Matplotlib](https://matplotlib.org): A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
|
||||
- [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.
|
||||
- and my 🧠.
|
||||
121
content/skills.json
Normal file
121
content/skills.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"body": [
|
||||
{
|
||||
"id": "scientific-computing",
|
||||
"name": "Scientific Computing & AI",
|
||||
"description": "Core expertise in mathematics, statistics, and machine learning. Building and training neural networks, statistical models, and data science solutions.",
|
||||
"items": [
|
||||
{
|
||||
"name": "Python",
|
||||
"icon": "i-logos-python"
|
||||
},
|
||||
{
|
||||
"name": "PyTorch",
|
||||
"icon": "i-logos-pytorch-icon"
|
||||
},
|
||||
{
|
||||
"name": "R Lang",
|
||||
"icon": "i-logos-r-lang"
|
||||
},
|
||||
{
|
||||
"name": "LaTeX",
|
||||
"icon": "i-file-icons-latex"
|
||||
},
|
||||
{
|
||||
"name": "Tensorflow",
|
||||
"icon": "i-logos-tensorflow"
|
||||
},
|
||||
{
|
||||
"name": "Scikit-Learn",
|
||||
"icon": "i-devicon-scikitlearn"
|
||||
},
|
||||
{
|
||||
"name": "Pandas",
|
||||
"icon": "i-devicon-pandas"
|
||||
},
|
||||
{
|
||||
"name": "NumPy",
|
||||
"icon": "i-logos-numpy"
|
||||
},
|
||||
{
|
||||
"name": "MatPlotLib",
|
||||
"icon": "i-devicon-matplotlib"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data-engineering-mlops",
|
||||
"name": "Data Engineering & MLOps",
|
||||
"description": "Infrastructure, data pipelines, and production deployment. Managing databases, containerization, and scalable systems for ML models.",
|
||||
"items": [
|
||||
{
|
||||
"name": "PostgreSQL",
|
||||
"icon": "i-logos-postgresql"
|
||||
},
|
||||
{
|
||||
"name": "MySQL",
|
||||
"icon": "i-logos-mysql-icon"
|
||||
},
|
||||
{
|
||||
"name": "Docker",
|
||||
"icon": "i-logos-docker-icon"
|
||||
},
|
||||
{
|
||||
"name": "Linux",
|
||||
"icon": "i-logos-linux-tux"
|
||||
},
|
||||
{
|
||||
"name": "Git",
|
||||
"icon": "i-logos-git-icon"
|
||||
},
|
||||
{
|
||||
"name": "Proxmox",
|
||||
"icon": "i-devicon-proxmox-wordmark"
|
||||
},
|
||||
{
|
||||
"name": "Redis",
|
||||
"icon": "i-logos-redis"
|
||||
},
|
||||
{
|
||||
"name": "Apache Spark (PySpark)",
|
||||
"icon": "i-logos-apache-spark"
|
||||
},
|
||||
{
|
||||
"name": "Cloudflare",
|
||||
"icon": "i-logos-cloudflare-icon"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "software-development",
|
||||
"name": "Fullstack Development",
|
||||
"description": "Web and backend development with modern frameworks. Building responsive UIs and scalable server-side applications.",
|
||||
"items": [
|
||||
{
|
||||
"name": "TypeScript",
|
||||
"icon": "i-logos-typescript-icon"
|
||||
},
|
||||
{
|
||||
"name": "Vue.js & Nuxt",
|
||||
"icon": "i-logos-nuxt-icon"
|
||||
},
|
||||
{
|
||||
"name": "Java",
|
||||
"icon": "i-logos-java"
|
||||
},
|
||||
{
|
||||
"name": "TailwindCSS",
|
||||
"icon": "i-logos-tailwindcss-icon"
|
||||
},
|
||||
{
|
||||
"name": "AdonisJs",
|
||||
"icon": "i-logos-adonisjs-icon"
|
||||
},
|
||||
{
|
||||
"name": "Gradio",
|
||||
"icon": "i-logos-gradio-icon"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
116
content/uses.md
Normal file
116
content/uses.md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Research Lab & Technical Setup
|
||||
description: A curated list of the hardware, software, and infrastructure that powers my research and engineering workflows.
|
||||
---
|
||||
|
||||
# The Lab
|
||||
|
||||
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"}.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Workstations & Compute
|
||||
|
||||
My setup is split between mobile efficiency for academic writing and a fixed station for heavier computation.
|
||||
|
||||
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
|
||||
|
||||
::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.
|
||||
::
|
||||
|
||||
::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.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Development Ecosystem
|
||||
|
||||
I prioritize tools that offer **AI-integration** and **strong type-checking**.
|
||||
|
||||
::div{class="grid grid-cols-1 md:grid-cols-2 gap-6"}
|
||||
|
||||
::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.
|
||||
|
||||
```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.
|
||||
::
|
||||
|
||||
::
|
||||
|
||||
---
|
||||
|
||||
## 🏠 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
|
||||
|
||||
::div{class="grid grid-cols-1 md:grid-cols-3 gap-4"}
|
||||
|
||||
::card{title="Compute Node" icon="i-ph-cpu-duotone"}
|
||||
**Beelink EQR6** *:hover-text{text="AMD Ryzen" hover="Proxmox Host"}*
|
||||
|
||||
Runs my containerized workloads and Docker services.
|
||||
::
|
||||
|
||||
::card{title="Storage Node" icon="i-ph-hard-drives-duotone"}
|
||||
**UGREEN NASync DXP4800** *:hover-text{text="16TB Raw Storage" hover="RAID Configuration"}*
|
||||
|
||||
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.*
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Beelink EQR6 AMD Ryzen",
|
||||
"description": {
|
||||
"en": "I use my Beelink as the main server in my homelab, running Proxmox, to host self-hosted services, run Docker containers, and test open-source tools.",
|
||||
"fr": "J’utilise mon Beelink comme premier serveur de mon homelab, avec Proxmox, pour héberger mes services en auto-hébergement, faire tourner des conteneurs Docker et tester des services open-source.",
|
||||
"es": "Utilizo mi Beelink como primer servidor de mi homelab, con Proxmox, para alojar servicios autogestionados, ejecutar contenedores Docker y probar herramientas de código abierto."
|
||||
},
|
||||
"category": "homelab"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Programming",
|
||||
"description": {
|
||||
"en": "Java, Python, Html, Css, JavaScript, TypeScript, SQL.",
|
||||
"fr": "Java, Python, Html, Css, JavaScript, TypeScript, SQL.",
|
||||
"es": "Java, Python, Html, Css, JavaScript, TypeScript, SQL."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Switch TP-Link 5 ports",
|
||||
"description": {
|
||||
"en": "I use my 5-port TP-Link switch to connect my various network devices to my main server and ensure fast, stable local communication.",
|
||||
"fr": "J’utilise mon switch TP-Link 5 ports pour connecter mes différents appareils réseau à mon serveur principal et assurer une communication locale rapide et stable.",
|
||||
"es": "Utilizo mi switch TP-Link de 5 puertos para conectar mis distintos dispositivos de red a mi servidor principal y garantizar una comunicación local rápida y estable."
|
||||
},
|
||||
"category": "homelab"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "FrontEnd",
|
||||
"description": {
|
||||
"en": "Nuxt Stack (Framework, UI, Hub, Content, Studio), VueJS, TailwindCSS, Vite.",
|
||||
"fr": "Nuxt Stack (Framework, UI, Hub, Content, Studio), VueJS, TailwindCSS, Vite.",
|
||||
"es": "Nuxt Stack (Framework, UI, Hub, Content, Studio), VueJS, TailwindCSS, Vite."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "BackEnd",
|
||||
"description": {
|
||||
"en": "AdonisJs, Nuxt (powered by Nitro), PostgreSQL, Redis, MariaDB.",
|
||||
"fr": "AdonisJs, Nuxt (powered by Nitro), PostgreSQL, Redis, MariaDB.",
|
||||
"es": "AdonisJs, Nuxt (powered by Nitro), PostgreSQL, Redis, MariaDB."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Self-Hosted Services",
|
||||
"description": {
|
||||
"en": "Uptime Kuma, Beszel, Traefik, Cloudflare, MySpeed, AdGuard Home, Portainer, Home Assistant, Minio, Immich, Vaultwarden, Tailscale, Palmr and Cap.so",
|
||||
"fr": "Uptime Kuma, Beszel, Traefik, Cloudflare, MySpeed, AdGuard Home, Portainer, Home Assistant, Minio, Immich, Vaultwarden, Tailscale, Palmr et Cap.so",
|
||||
"es": "Uptime Kuma, Beszel, Traefik, Cloudflare, MySpeed, AdGuard Home, Portainer, Home Assistant, Minio, Immich, Vaultwarden, Tailscale, Palmr y Cap.so"
|
||||
},
|
||||
"category": "homelab"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "DevOps",
|
||||
"description": {
|
||||
"en": "Docker, Git, GitHub, Traefik.",
|
||||
"fr": "Docker, Git, GitHub, Traefik.",
|
||||
"es": "Docker, Git, GitHub, Traefik."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Python Frameworks",
|
||||
"description": {
|
||||
"en": "Pytorch, Scikit-learn, Tensorflow, Numpy, Matplotlib, Pandas, Seaborn.",
|
||||
"fr": "Pytorch, Scikit-learn, Tensorflow, Numpy, Matplotlib, Pandas, Seaborn.",
|
||||
"es": "Pytorch, Scikit-learn, Tensorflow, Numpy, Matplotlib, Pandas, Seaborn."
|
||||
},
|
||||
"category": "stack"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Apple AirPods Pro",
|
||||
"description": {
|
||||
"en": "Probably my most used item after my phone and laptop. I use them for everything from listening to music to taking calls. They are super convenient and the sound quality is great.",
|
||||
"fr": "Probablement l'objet que j'utilise le plus après mon téléphone et mon ordinateur portable. Je les utilise pour tout, de l'écoute de musique à la prise d'appels. Ils sont super pratiques et la qualité sonore est excellente.",
|
||||
"es": "Probablemente el objeto que más utilizo después de mi móvil y mi portátil. Los utilizo para todo, para escuchar música y contentar llamadas. Son súper prácticos y la calidad sonora es excelente."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Apple iPad Air",
|
||||
"description": {
|
||||
"en": "I use my iPad to read books, watch movies, and browse the web, but also to take notes and write some equations during my math classes.",
|
||||
"fr": "J'utilise mon iPad pour lire des livres, regarder des films et naviguer sur le web, mais aussi pour prendre des notes et écrire des équations pendant mes cours de mathématiques.",
|
||||
"es": "Utilizo mi iPad para leer libros, ver películas y navegar por internet, pero también para coger apuntes y escribir ecuaciones en mis clases de matemáticas."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Apple iPhone 14 Pro",
|
||||
"description": {
|
||||
"en": "I don't upgrade my phone every year, but when I do, I go for the best. The iPhone 14 Pro is the best phone on the market, and I'm excited to get my hands on it.",
|
||||
"fr": "Je n'améliore pas mon téléphone chaque année, mais quand je le fais, je vais pour le meilleur. L'iPhone 14 Pro est le meilleur téléphone sur le marché, et je suis excité de mettre la main dessus.",
|
||||
"es": "No cambio de teléfono cada año, pero cuando lo hago, voy a por lo mejor. El iPhone 14 Pro es el mejor teléfono del mercado, y estoy muy contento de tenerlo a mano."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Apple MacBook Pro 13'",
|
||||
"description": {
|
||||
"en": "My main programming computer is a MacBook Pro 13' 2020 with the Apple M1 Chip and 16Go RAM. I use MacOS Sorona.",
|
||||
"fr": "Mon ordinateur principal pour programmer est un MacBook Pro 13' 2020 avec la puce Apple M1 et 16Go de RAM. J'utilise MacOS Sorona.",
|
||||
"es": "Mi ordenador principal para programar es un Apple MacBook Pro 13’ 2020 con el chip Apple M1 y 16Go de Ram. Utilizo MAcOs Sorona."
|
||||
},
|
||||
"category": "hardware"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Apple Suite",
|
||||
"description": {
|
||||
"en": "I'm using the Apple Suite including Mail, Calendar, Notes, Music and Reminders for my daily organization.",
|
||||
"fr": "J'utilise la suite Apple comprenant Mail, Calendar, Notes, Music et Reminders pour mon organisation quotidienne.",
|
||||
"es": "Utilizo la suite Apple que tiene Mail, Calendar, Notes, Music y Reminders para mi organización diaria."
|
||||
},
|
||||
"category": "software"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "hardware",
|
||||
"name": {
|
||||
"en": "Hardware",
|
||||
"fr": "Matériel",
|
||||
"es": "Material"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"id": "software",
|
||||
"name": {
|
||||
"en": "Software",
|
||||
"fr": "Logiciel",
|
||||
"es": "Programas"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id": "ide",
|
||||
"name": {
|
||||
"en": "IDE & Font",
|
||||
"fr": "IDE & Police",
|
||||
"es": "IDE y Fuente"
|
||||
},
|
||||
"carousel": "ides"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user