mirror of
https://github.com/ArthurDanjou/artsite.git
synced 2026-01-14 18:59:59 +01:00
Compare commits
88 Commits
38bf904902
...
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 | ||
|
|
ee74f06cd5 | ||
|
|
476d5b9842 | ||
|
|
cd69b346fe | ||
|
|
5c46d1ab3f |
15
.env.example
15
.env.example
@@ -1,5 +1,12 @@
|
||||
NUXT_PUBLIC_I18N_BASE_URL=
|
||||
NUXT_API_URL=
|
||||
|
||||
STUDIO_GITHUB_CLIENT_ID=
|
||||
STUDIO_GITHUB_CLIENT_SECRET=
|
||||
STUDIO_GITHUB_CLIENT_SECRET=
|
||||
|
||||
NUXT_DISCORD_USER_ID=
|
||||
|
||||
NUXT_WAKATIME_CODING=
|
||||
NUXT_WAKATIME_EDITORS=
|
||||
NUXT_WAKATIME_LANGUAGES=
|
||||
NUXT_WAKATIME_OS=
|
||||
NUXT_WAKATIME_USER_ID=
|
||||
|
||||
NUXT_STATUS_PAGE=
|
||||
45
.github/workflows/cloudflare.yml
vendored
45
.github/workflows/cloudflare.yml
vendored
@@ -3,7 +3,7 @@ name: Deploy to Cloudflare Workers
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -31,30 +31,47 @@ jobs:
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
command: types
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
env:
|
||||
NUXT_DISCORD_USER_ID: ${{ secrets.NUXT_DISCORD_USER_ID }}
|
||||
|
||||
NUXT_WAKATIME_CODING: ${{ secrets.NUXT_WAKATIME_CODING }}
|
||||
NUXT_WAKATIME_EDITORS: ${{ secrets.NUXT_WAKATIME_EDITORS }}
|
||||
NUXT_WAKATIME_LANGUAGES: ${{ secrets.NUXT_WAKATIME_LANGUAGES }}
|
||||
NUXT_WAKATIME_OS: ${{ secrets.NUXT_WAKATIME_OS }}
|
||||
NUXT_WAKATIME_USER_ID: ${{ secrets.NUXT_WAKATIME_USER_ID }}
|
||||
|
||||
NUXT_STATUS_PAGE: ${{ secrets.NUXT_STATUS_PAGE }}
|
||||
|
||||
STUDIO_GITHUB_CLIENT_ID: ${{ secrets.STUDIO_GITHUB_CLIENT_ID }}
|
||||
STUDIO_GITHUB_CLIENT_SECRET: ${{ secrets.STUDIO_GITHUB_CLIENT_SECRET }}
|
||||
STUDIO_GITHUB_MODERATORS: ${{ secrets.STUDIO_GITHUB_MODERATORS }}
|
||||
|
||||
- name: Publish to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Determine Deployment Command
|
||||
id: target
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" = "main" ] || [ "${{ github.ref_name }}" = "master" ]; then
|
||||
echo "wrangler_command=deploy" >> $GITHUB_OUTPUT
|
||||
echo "env_name=Production" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "wrangler_command=versions upload" >> $GITHUB_OUTPUT
|
||||
echo "env_name=Preview" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run Cloudflare Wrangler & Capture URL
|
||||
id: wrangler
|
||||
run: |
|
||||
# Exécuter wrangler et rediriger la sortie vers un fichier tout en l'affichant (tee)
|
||||
bunx wrangler ${{ steps.target.outputs.wrangler_command }} | tee wrangler.log
|
||||
|
||||
# Extraction de l'URL
|
||||
if [ "${{ steps.target.outputs.env_name }}" = "Preview" ]; then
|
||||
PREVIEW_URL=$(grep -o 'https://[^ ]*\.workers\.dev' wrangler.log | head -n 1)
|
||||
echo "DEPLOY_URL=$PREVIEW_URL" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "DEPLOY_URL=https://arthurdanjou.fr" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
@@ -62,13 +79,15 @@ jobs:
|
||||
|
||||
- name: Discord Notification
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
if: always() # S'exécute même si le build plante
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
status: ${{ job.status }}
|
||||
title: "Déploiement Portfolio"
|
||||
title: "Déploiement Portfolio (${{ steps.target.outputs.env_name }})"
|
||||
description: |
|
||||
Build terminé sur la branche **${{ github.ref_name }}**.
|
||||
Environnement : **${{ steps.target.outputs.env_name }}**
|
||||
URL : **${{ steps.wrangler.outputs.DEPLOY_URL }}**
|
||||
Commit: `${{ github.sha }}` par ${{ github.actor }}.
|
||||
nofail: false
|
||||
nodetail: false
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,6 +8,7 @@ dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# Logs
|
||||
logs
|
||||
@@ -24,3 +25,6 @@ logs
|
||||
!.env.example
|
||||
|
||||
.wrangler
|
||||
|
||||
# Localflare generated files
|
||||
.localflare/
|
||||
|
||||
238
README.md
238
README.md
@@ -8,6 +8,9 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
[](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>
|
||||
|
||||
@@ -15,8 +18,14 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
|
||||
- [Features](#-features)
|
||||
- [Tech Stack](#️-tech-stack)
|
||||
- [Prerequisites](#-prerequisites)
|
||||
- [Getting Started](#-getting-started)
|
||||
- [Development](#-development)
|
||||
- [Environment Variables](#-environment-variables)
|
||||
- [Project Structure](#-project-structure)
|
||||
- [Integrations](#-integrations)
|
||||
- [Deployment](#-deployment)
|
||||
- [Contributing](#-contributing)
|
||||
- [License](#-license)
|
||||
- [Contact](#-contact)
|
||||
|
||||
@@ -39,29 +48,173 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
- **Design System** → [NuxtUI](https://ui.nuxt.com/)
|
||||
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio)
|
||||
- **Language** → [TypeScript](https://www.typescriptlang.org/)
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/)
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/) on [Cloudflare](https://cloudflare.com/)
|
||||
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/)
|
||||
- **Package Manager** → [pnpm](https://pnpm.io/)
|
||||
- **Package Manager** → [Bun](https://bun.sh/)
|
||||
- **Internationalization** → [Nuxt i18n](https://i18n.nuxtjs.org/)
|
||||
- **Database ORM** → [Drizzle](https://orm.drizzle.team/)
|
||||
- **Database** → [Cloudflare D1](https://developers.cloudflare.com/d1/) (SQLite)
|
||||
- **Composables** → [VueUse](https://vueuse.org/)
|
||||
- **Validation** → [Zod](https://zod.dev/)
|
||||
- **Globe Visualization** → [Cobe](https://github.com/shuding/cobe)
|
||||
- **Icons** → [Iconify](https://iconify.design/)
|
||||
|
||||
## 📦 Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
- **Node.js** (v18 or higher recommended)
|
||||
- **Bun** (latest version) - [Install Bun](https://bun.sh/docs/installation)
|
||||
- **Git** for version control
|
||||
- **Cloudflare Account** (for deployment)
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Clone the repository**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ArthurDanjou/artsite.git
|
||||
cd artsite
|
||||
```
|
||||
|
||||
2. **Install dependencies**
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
3. **Set up environment variables**
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Then edit `.env` and fill in your configuration values (see [Environment Variables](#-environment-variables) section).
|
||||
|
||||
4. **Start the development server**
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:3000` 🎉
|
||||
|
||||
## 💻 Development
|
||||
|
||||
### Available Scripts
|
||||
|
||||
```bash
|
||||
# Start development server
|
||||
bun run dev
|
||||
|
||||
# Build for production
|
||||
bun run build
|
||||
|
||||
# Preview production build locally
|
||||
bun run preview
|
||||
|
||||
# Lint code
|
||||
bun run lint
|
||||
|
||||
# Deploy to Cloudflare
|
||||
bun run deploy
|
||||
|
||||
# Generate Cloudflare types
|
||||
bun run cf-typegen
|
||||
```
|
||||
|
||||
### Project Configuration
|
||||
|
||||
The project uses:
|
||||
- **ESLint** for code linting with stylistic rules (single quotes, no trailing commas)
|
||||
- **TypeScript** for type safety
|
||||
- **Nuxt DevTools** enabled for enhanced development experience
|
||||
|
||||
## 🔐 Environment Variables
|
||||
|
||||
Create a `.env` file in the root directory with the following variables:
|
||||
|
||||
```env
|
||||
# GitHub Studio Integration (for content management)
|
||||
STUDIO_GITHUB_CLIENT_ID=your_github_client_id
|
||||
STUDIO_GITHUB_CLIENT_SECRET=your_github_client_secret # Keep this secret! Never commit to version control
|
||||
|
||||
# Discord Integration (runtime config - keep private)
|
||||
NUXT_DISCORD_USER_ID=your_discord_user_id # Private
|
||||
|
||||
# WakaTime Integration (for coding statistics - keep private)
|
||||
NUXT_WAKATIME_USER_ID=your_wakatime_user_id # Private
|
||||
NUXT_WAKATIME_CODING=your_coding_stats_api # Private
|
||||
NUXT_WAKATIME_EDITORS=your_editors_stats_api # Private
|
||||
NUXT_WAKATIME_LANGUAGES=your_languages_stats_api # Private
|
||||
NUXT_WAKATIME_OS=your_os_stats_api # Private
|
||||
|
||||
# Status Page URL
|
||||
NUXT_STATUS_PAGE=your_status_page_url
|
||||
```
|
||||
|
||||
> **Note:** Not all variables are required for basic development. The site will work without integrations, but some features may be disabled.
|
||||
|
||||
> **Security:** Never commit your `.env` file or expose sensitive credentials like `STUDIO_GITHUB_CLIENT_SECRET`. Keep all API keys and secrets secure.
|
||||
|
||||
## 📂 Project Structure
|
||||
|
||||
```
|
||||
├── assets/ # Static assets like global styles
|
||||
├── components/ # Vue components
|
||||
├── layouts/ # Page layouts
|
||||
├── pages/ # Application pages
|
||||
├── public/ # Public static files
|
||||
├── server/ # Server API routes
|
||||
├── .env.example # Example environment variables
|
||||
├── nuxt.config.ts # Nuxt configuration
|
||||
├── package.json # Dependencies and scripts
|
||||
└── README.md # Project documentation
|
||||
artsite/
|
||||
├── app/ # Application source code
|
||||
│ ├── assets/ # Static assets (CSS, images)
|
||||
│ ├── components/ # Vue components
|
||||
│ │ ├── content/ # Content-specific components
|
||||
│ │ └── home/ # Home page components
|
||||
│ ├── composables/ # Vue composables
|
||||
│ ├── pages/ # Application pages (file-based routing)
|
||||
│ │ ├── index.vue # Home page
|
||||
│ │ ├── projects/ # Projects section
|
||||
│ │ ├── hobbies.vue # Hobbies page
|
||||
│ │ ├── uses.vue # Uses page
|
||||
│ │ └── ecosystem.vue # Ecosystem page
|
||||
│ ├── app.config.ts # App configuration
|
||||
│ ├── app.vue # Root app component
|
||||
│ └── error.vue # Error page
|
||||
├── content/ # Content files (Markdown & JSON)
|
||||
│ ├── education/ # Education content
|
||||
│ │ ├── bachelor.md # Bachelor's degree information
|
||||
│ │ ├── m1.md # Master's 1st year information
|
||||
│ │ └── m2.md # Master's 2nd year information
|
||||
│ ├── experiences/ # Professional experience content
|
||||
│ │ ├── artdanjproduction.md
|
||||
│ │ ├── erisium.md
|
||||
│ │ ├── hackathon-cnd.md
|
||||
│ │ ├── picard.md
|
||||
│ │ └── sevetys.md
|
||||
│ ├── projects/ # Project portfolio content
|
||||
│ │ ├── artchat.md
|
||||
│ │ ├── arthome.md
|
||||
│ │ ├── artlab.md
|
||||
│ │ ├── artstudies.md
|
||||
│ │ ├── bikes-glm.md
|
||||
│ │ ├── breast-cancer.md
|
||||
│ │ ├── dropout-reduces-underfitting.md
|
||||
│ │ ├── loan-ml.md
|
||||
│ │ ├── monte-carlo-project.md
|
||||
│ │ ├── schelling-segregation-model.md
|
||||
│ │ └── sevetys.md
|
||||
│ ├── contact.json # Contact information data
|
||||
│ ├── hobbies.md # Hobbies page content
|
||||
│ ├── index.md # Home page content
|
||||
│ ├── languages.json # Programming languages data
|
||||
│ ├── skills.json # Skills and expertise data
|
||||
│ └── uses.md # Tools and software used
|
||||
├── public/ # Public static files
|
||||
├── server/ # Server API routes and middleware
|
||||
│ ├── api/ # API endpoints
|
||||
│ └── routes/ # Server routes
|
||||
├── types/ # TypeScript type definitions
|
||||
├── .env.example # Example environment variables
|
||||
├── content.config.ts # Content module configuration
|
||||
├── nuxt.config.ts # Nuxt configuration
|
||||
├── package.json # Dependencies and scripts
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
├── wrangler.jsonc # Cloudflare Workers configuration
|
||||
└── README.md # Project documentation
|
||||
```
|
||||
|
||||
## 🔌 Integrations
|
||||
@@ -70,7 +223,62 @@ My professional portfolio built with modern Nuxt.js technologies, showcasing pro
|
||||
- **Discord** - Display real-time Discord status
|
||||
- **Nuxt Studio** - Headless CMS for content management
|
||||
- **Nuxt i18n** - Internationalization support
|
||||
- **NuxtHub** - CI/CD and deployment
|
||||
- **NuxtHub** - CI/CD and deployment platform
|
||||
- **Cloudflare D1** - Serverless SQL database
|
||||
|
||||
## 🚢 Deployment
|
||||
|
||||
This project is configured to deploy on Cloudflare using NuxtHub.
|
||||
|
||||
### Deploy to Production
|
||||
|
||||
1. **Prerequisites**
|
||||
- Cloudflare account
|
||||
- Wrangler CLI configured (`wrangler login`)
|
||||
- NuxtHub project created
|
||||
|
||||
2. **Deploy**
|
||||
|
||||
```bash
|
||||
bun run deploy
|
||||
```
|
||||
|
||||
This will:
|
||||
- Generate Cloudflare types
|
||||
- Build the application
|
||||
- Deploy to Cloudflare Workers
|
||||
|
||||
### Automatic Deployments
|
||||
|
||||
The project is set up with NuxtHub for automatic deployments on push to the main branch.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Here's how you can help:
|
||||
|
||||
1. **Fork the repository**
|
||||
2. **Create a feature branch**
|
||||
```bash
|
||||
git checkout -b feature/amazing-feature
|
||||
```
|
||||
3. **Make your changes**
|
||||
4. **Commit your changes**
|
||||
```bash
|
||||
git commit -m 'Add some amazing feature'
|
||||
```
|
||||
5. **Push to the branch**
|
||||
```bash
|
||||
git push origin feature/amazing-feature
|
||||
```
|
||||
6. **Open a Pull Request**
|
||||
|
||||
### Development Guidelines
|
||||
|
||||
- Follow the existing code style (enforced by ESLint)
|
||||
- Use TypeScript for type safety
|
||||
- Write descriptive commit messages
|
||||
- Test your changes locally before submitting
|
||||
- Update documentation if needed
|
||||
|
||||
## 📄 License
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
container: {
|
||||
base: 'max-w-4xl'
|
||||
base: 'max-w-5xl'
|
||||
},
|
||||
colors: {
|
||||
primary: 'neutral',
|
||||
white: 'white',
|
||||
black: 'black',
|
||||
cyan: 'cyan',
|
||||
gray: 'gray',
|
||||
zinc: 'zinc',
|
||||
neutral: 'neutral',
|
||||
red: 'red'
|
||||
red: 'red',
|
||||
amber: 'amber',
|
||||
green: 'green',
|
||||
emerald: 'emerald',
|
||||
sky: 'sky',
|
||||
blue: 'blue',
|
||||
purple: 'purple',
|
||||
pink: 'pink',
|
||||
orange: 'orange'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,14 +2,7 @@
|
||||
useHead({
|
||||
titleTemplate: (titleChunk) => {
|
||||
return titleChunk ? `${titleChunk} %separator %siteName` : 'Arthur Danjou %separator AI Safety & Applied Math'
|
||||
},
|
||||
templateParams: {
|
||||
siteName: 'Arthur Danjou',
|
||||
separator: '•'
|
||||
},
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
|
||||
]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
@theme {
|
||||
--animate-wave: wave 2.5s infinite;
|
||||
--font-mono: 'Monaspace Neon', 'ui monaspace', monospace;
|
||||
--font-sofia: 'Sofia Sans', 'ui sans-serif', sans-serif;
|
||||
--font-sans: 'DM Sans', 'ui sans-serif', sans-serif;
|
||||
|
||||
@keyframes wave {
|
||||
0% {
|
||||
|
||||
@@ -23,7 +23,11 @@ const colorVariants = {
|
||||
zinc: 'text-zinc-500/80 decoration-zinc-400/40',
|
||||
orange: 'text-orange-500/80 decoration-orange-400/40',
|
||||
amber: 'text-amber-500/80 decoration-amber-400/40',
|
||||
emerald: 'text-emerald-500/80 decoration-emerald-400/40'
|
||||
emerald: 'text-emerald-500/80 decoration-emerald-400/40',
|
||||
white: 'text-white/80 decoration-white/40',
|
||||
black: 'text-black/80 decoration-black/40',
|
||||
cyan: 'text-cyan-500/80 decoration-cyan-400/40',
|
||||
gray: 'text-gray-500/80 decoration-gray-400/40'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Activity } from '~~/types'
|
||||
import { IDEs } from '~~/types'
|
||||
|
||||
const { data: activity, refresh } = await useAsyncData<Activity>('activity', () => $fetch('/api/activity'), { lazy: true })
|
||||
useIntervalFn(refresh, 5000)
|
||||
|
||||
const currentSession = computed(() => {
|
||||
const list = activity.value?.data.activities ?? []
|
||||
const ideActivity = list.find(a => IDEs.some(ide => ide.name === a.name))
|
||||
|
||||
if (!ideActivity) return null
|
||||
|
||||
const name = ideActivity.assets?.small_text === 'Cursor' ? 'Cursor' : ideActivity.name
|
||||
|
||||
const isIdling = ideActivity.details?.includes('Idling') || (!ideActivity.state?.toLowerCase().includes('editing') && name !== 'Visual Studio Code')
|
||||
|
||||
const rawProject = ideActivity.details ? ideActivity.details.replace('Workspace:', '').replace('Editing', '').trim() : 'Unknown Context'
|
||||
const project = rawProject.charAt(0).toUpperCase() + rawProject.slice(1)
|
||||
const file = ideActivity.state?.replace('Editing', '').trim() || 'No active file'
|
||||
|
||||
return {
|
||||
name,
|
||||
project,
|
||||
file,
|
||||
isIdling,
|
||||
startTime: ideActivity.timestamps?.start,
|
||||
icon: IDEs.find(ide => ide.name === name)?.icon ?? 'i-ph-code-duotone'
|
||||
}
|
||||
})
|
||||
|
||||
const timeAgo = useTimeAgo(computed(() => currentSession.value?.startTime ?? new Date()))
|
||||
|
||||
const statusColor = computed(() => {
|
||||
if (!currentSession.value) return 'red'
|
||||
return currentSession.value.isIdling ? 'orange' : 'green'
|
||||
})
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
if (!currentSession.value) return 'System Offline'
|
||||
if (currentSession.value.isIdling) return 'System Idling'
|
||||
return 'Active Development'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div class="w-full mb-4">
|
||||
<UCard v-if="activity">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative flex h-3 w-3">
|
||||
<span
|
||||
v-if="statusColor === 'green'"
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full opacity-75 bg-green-400"
|
||||
/>
|
||||
<span
|
||||
class="relative inline-flex rounded-full h-3 w-3 transition-colors duration-300"
|
||||
:class="{
|
||||
'bg-green-500': statusColor === 'green',
|
||||
'bg-orange-500': statusColor === 'orange',
|
||||
'bg-red-500': statusColor === 'red'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="text-xs font-bold uppercase tracking-wider text-neutral-500 dark:text-neutral-400">
|
||||
{{ statusLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<UIcon
|
||||
v-if="currentSession"
|
||||
:name="currentSession.icon"
|
||||
class="w-5 h-5 opacity-80"
|
||||
/>
|
||||
<UIcon
|
||||
v-else
|
||||
name="i-ph-power-duotone"
|
||||
class="w-5 h-5 text-red-400 opacity-80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="currentSession"
|
||||
class="space-y-1 pl-6 border-l-2 border-neutral-100 dark:border-neutral-800 ml-1.5"
|
||||
>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
|
||||
<h3 class="font-semibold text-neutral-900 dark:text-white truncate">
|
||||
{{ currentSession.project }}
|
||||
</h3>
|
||||
<span class="hidden sm:inline text-neutral-400 text-xs">•</span>
|
||||
<span class="text-sm text-neutral-500 dark:text-neutral-400 truncate">
|
||||
{{ currentSession.file }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 text-xs text-neutral-400 mt-1">
|
||||
<UIcon
|
||||
name="i-ph-timer-duotone"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
<span>Started {{ timeAgo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="text-sm text-neutral-500 dark:text-neutral-400 flex items-center gap-2 pl-6 border-l-2 border-red-100 dark:border-red-900/30 ml-1.5"
|
||||
>
|
||||
<p>Telemetry disconnected. Research in progress.</p>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard v-else>
|
||||
<div class="flex items-center gap-3">
|
||||
<USkeleton class="h-3 w-3 rounded-full" /> <div class="space-y-2 flex-1">
|
||||
<USkeleton class="h-4 w-1/3" />
|
||||
<USkeleton class="h-3 w-2/3" />
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
@@ -1,61 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const { education } = await useContent()
|
||||
|
||||
const formatDate = (iso?: string) => {
|
||||
if (!iso) return 'Present'
|
||||
const d = new Date(iso)
|
||||
if (Number.isNaN(d.getTime())) return iso
|
||||
return useDateFormat(d, 'MMM YYYY', { locales: 'en-US' }).value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
v-if="education && education.length"
|
||||
class="my-8 space-y-6"
|
||||
aria-labelledby="education-title"
|
||||
>
|
||||
<h2
|
||||
id="education-title"
|
||||
class="sr-only"
|
||||
>
|
||||
Education
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 grid-cols-1">
|
||||
<UCard
|
||||
v-for="item in education"
|
||||
:key="item.id"
|
||||
variant="outline"
|
||||
color="neutral"
|
||||
>
|
||||
<div>
|
||||
<h3 class="text-lg md:text-xl font-semibold tracking-tight">
|
||||
{{ item.degree ?? item.title }}
|
||||
</h3>
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400 mb-2">
|
||||
{{ item.institution }}
|
||||
</p>
|
||||
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex flex-wrap items-center gap-2">
|
||||
<span class="font-medium">Dates:</span>
|
||||
<span>{{ formatDate(item.startDate) }} — {{ formatDate(item.endDate) }}</span>
|
||||
<span
|
||||
v-if="item.duration"
|
||||
class="text-neutral-500"
|
||||
>({{ item.duration }})</span>
|
||||
</p>
|
||||
<p
|
||||
class="text-sm text-neutral-700 dark:text-neutral-300 mt-1"
|
||||
>
|
||||
<span class="font-medium">Location:</span> {{ item.location }}
|
||||
</p>
|
||||
<p
|
||||
class="text-sm text-neutral-700 dark:text-neutral-300 mt-3"
|
||||
>
|
||||
{{ item.description }}
|
||||
</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,100 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const { experiences } = await useContent()
|
||||
|
||||
const formatDate = (iso?: string) => {
|
||||
if (!iso) return 'Present'
|
||||
const d = new Date(iso)
|
||||
if (Number.isNaN(d.getTime())) return iso
|
||||
return useDateFormat(d, 'MMM YYYY', { locales: 'en-US' }).value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
v-if="experiences && experiences.length"
|
||||
class="my-8 space-y-6"
|
||||
aria-labelledby="experiences-title"
|
||||
>
|
||||
<h2
|
||||
id="experiences-title"
|
||||
class="sr-only"
|
||||
>
|
||||
Experiences
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 grid-cols-1">
|
||||
<UCard
|
||||
v-for="item in experiences"
|
||||
:key="item.id"
|
||||
variant="outline"
|
||||
color="neutral"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
v-if="item.emoji"
|
||||
class="text-2xl leading-none"
|
||||
>
|
||||
{{ item.emoji }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg md:text-xl font-semibold tracking-tight">
|
||||
{{ item.title }}<span
|
||||
v-if="item.type"
|
||||
class="text-md text-neutral-500 font-normal"
|
||||
> · {{ item.type }}</span>
|
||||
</h3>
|
||||
<p class="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
<span class="font-medium mr-2">Company:</span>
|
||||
<span
|
||||
v-if="item.companyUrl"
|
||||
class="underline decoration-neutral-400/70 underline-offset-4 hover:decoration-neutral-600 dark:hover:decoration-neutral-300"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="item.companyUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>{{ item.company }}</NuxtLink>
|
||||
</span>
|
||||
<span v-else>{{ item.company }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex flex-wrap items-center gap-2">
|
||||
<span class="font-medium">Dates:</span>
|
||||
<span>{{ formatDate(item.startDate) }} — {{ formatDate(item.endDate) }}</span>
|
||||
<span
|
||||
v-if="item.duration"
|
||||
class="text-neutral-500"
|
||||
>({{ item.duration }})</span>
|
||||
</p>
|
||||
|
||||
<p class="text-sm text-neutral-700 dark:text-neutral-300 flex items-center gap-2">
|
||||
<span class="font-medium">Location:</span>
|
||||
<span>{{ item.location }}</span>
|
||||
</p>
|
||||
|
||||
<p class="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
{{ item.description }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="item.tags?.length"
|
||||
class="flex flex-wrap gap-2"
|
||||
>
|
||||
<UBadge
|
||||
v-for="tag in item.tags"
|
||||
:key="tag"
|
||||
size="sm"
|
||||
variant="soft"
|
||||
color="primary"
|
||||
>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -31,7 +31,7 @@ defineProps({
|
||||
size="20"
|
||||
/>
|
||||
<span
|
||||
class="duration-300 underline-offset-2 font-semibold text-md text-black dark:text-white underline decoration-gray-300 dark:decoration-neutral-700 group-hover:decoration-black dark:group-hover:decoration-white"
|
||||
class="duration-300 underline-offset-2 font-semibold text-md text-black dark:text-white underline decoration-neutral-300 dark:decoration-neutral-700 group-hover:decoration-black dark:group-hover:decoration-white"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
|
||||
@@ -32,6 +32,7 @@ const { skills } = await useContent()
|
||||
:key="item.name"
|
||||
:icon="item.icon"
|
||||
variant="soft"
|
||||
size="lg"
|
||||
color="primary"
|
||||
class="transition-colors duration-200 hover:opacity-80"
|
||||
:aria-label="item.name"
|
||||
|
||||
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>
|
||||
@@ -4,15 +4,19 @@ import { usePrecision } from '@vueuse/math'
|
||||
|
||||
const { data: stats } = await useAsyncData<Stats>('stats', () => $fetch('/api/stats'))
|
||||
|
||||
const startDate = computed(() => stats.value?.coding.range.start ? new Date(stats.value.coding.range.start) : new Date())
|
||||
const yearsCollected = useTimeAgo(startDate).value
|
||||
const formattedDate = useDateFormat(startDate, 'MMM DD, YYYY').value
|
||||
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((stats.value?.coding.grand_total.total_seconds_including_other_language ?? 0) / 3600, 0)
|
||||
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) || [])
|
||||
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>
|
||||
@@ -22,9 +26,9 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
class="space-y-6"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<UCard>
|
||||
<UCard v-if="totalHours">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg text-primary-500 flex items-center justify-center">
|
||||
<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"
|
||||
@@ -41,9 +45,9 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<UCard v-if="formattedDate && yearsCollected">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="p-3 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg text-emerald-500 flex items-center justify-center">
|
||||
<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"
|
||||
@@ -71,11 +75,14 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="col-span-1 lg:col-span-1 space-y-4">
|
||||
<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-primary-500 w-5 h-5"
|
||||
class="text-red-500 w-5 h-5"
|
||||
/>
|
||||
Top Languages
|
||||
</h4>
|
||||
@@ -89,20 +96,23 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
<span>{{ lang.name }}</span>
|
||||
<span>{{ lang.percent }}%</span>
|
||||
</div>
|
||||
<UMeter
|
||||
:value="lang.percent"
|
||||
color="primary"
|
||||
<UProgress
|
||||
v-model="lang.percent"
|
||||
color="red"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-1 lg:col-span-1 space-y-4">
|
||||
<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-orange-500 w-5 h-5"
|
||||
class="text-green-500 w-5 h-5"
|
||||
/>
|
||||
Preferred Editors
|
||||
</h4>
|
||||
@@ -116,16 +126,19 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
<span>{{ editor.name }}</span>
|
||||
<span>{{ editor.percent }}%</span>
|
||||
</div>
|
||||
<UMeter
|
||||
:value="editor.percent"
|
||||
color="orange"
|
||||
<UProgress
|
||||
v-model="editor.percent"
|
||||
color="green"
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-1 lg:col-span-1 space-y-4">
|
||||
<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"
|
||||
@@ -143,8 +156,8 @@ const topOS = computed(() => stats.value?.os.slice(0, 2) || [])
|
||||
<span>{{ osItem.name }}</span>
|
||||
<span>{{ osItem.percent }}%</span>
|
||||
</div>
|
||||
<UMeter
|
||||
:value="osItem.percent"
|
||||
<UProgress
|
||||
v-model="osItem.percent"
|
||||
color="blue"
|
||||
size="sm"
|
||||
/>
|
||||
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>
|
||||
@@ -30,7 +30,7 @@ const items = computed<TimelineItem[]>(() => {
|
||||
title: item.title || 'Degree',
|
||||
description: item.institution || '',
|
||||
date: formatDate(item.startDate, item.endDate, item.duration),
|
||||
icon: item.icon || 'i-ph-graduation-cap-duotone' // Context-aware default icon
|
||||
icon: item.icon || 'i-ph-graduation-cap-duotone'
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
@@ -41,8 +41,8 @@ const items = computed<TimelineItem[]>(() => {
|
||||
<UTimeline
|
||||
:orientation="orientation"
|
||||
:items="items"
|
||||
:default-value="items.length"
|
||||
active-color="primary"
|
||||
:default-value="2"
|
||||
size="lg"
|
||||
color="neutral"
|
||||
class="w-full max-w-5xl"
|
||||
/>
|
||||
|
||||
@@ -42,7 +42,7 @@ const items = computed<TimelineItem[]>(() => {
|
||||
:orientation="orientation"
|
||||
:items="items"
|
||||
:default-value="items.length"
|
||||
active-color="primary"
|
||||
size="lg"
|
||||
color="neutral"
|
||||
class="w-full max-w-5xl"
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export function useProjectColors() {
|
||||
const statusColors: Record<string, string> = {
|
||||
'active': 'green',
|
||||
'completed': 'blue',
|
||||
'archived': 'gray',
|
||||
'in-progress': 'amber'
|
||||
'Active': 'blue',
|
||||
'Completed': 'green',
|
||||
'Archived': 'neutral',
|
||||
'In progress': 'amber'
|
||||
}
|
||||
|
||||
const typeColors: Record<string, string> = {
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Coming Soon',
|
||||
description: 'This page is currently under development.',
|
||||
robots: 'noindex, nofollow'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
<main class="min-h-[60vh] flex flex-col items-center justify-center p-4">
|
||||
<div class="max-w-2xl text-center space-y-8">
|
||||
<h1 class="p-2 text-4xl md:text-6xl font-bold bg-linear-to-r from-inverted/40 to-inverted/75 to-70% bg-clip-text text-transparent duration-200 my-12 md:my-24">
|
||||
Coming Soon... Very Soon...
|
||||
</h1>
|
||||
|
||||
<p class="text-lg md:text-xl text-neutral-600 dark:text-neutral-400 leading-relaxed">
|
||||
This page is currently under development. Please check back later for exciting updates about my ecosystem, projects, and contributions.
|
||||
</p>
|
||||
|
||||
<div class="pt-8">
|
||||
<UButton
|
||||
to="/"
|
||||
icon="i-ph-arrow-left"
|
||||
size="lg"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
>
|
||||
Return Home
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -3,12 +3,27 @@ const { data: page } = await useAsyncData('hobbies', () => {
|
||||
return queryCollection('hobbies').first()
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
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.',
|
||||
ogTitle: 'Balance & Perspectives • Arthur Danjou',
|
||||
ogDescription: 'Beyond the code. Exploring how competitive sports, motorsports strategy, and cultural experiences fuel my research resilience and cognitive flexibility.',
|
||||
twitterCard: 'summary_large_image'
|
||||
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>
|
||||
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
useSeoMeta({
|
||||
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.',
|
||||
ogTitle: 'Arthur Danjou • AI Safety & Applied Mathematics',
|
||||
ogDescription: 'Research Engineer & Master 2 Student at Paris-Dauphine (ISF). Focusing on AI Alignment, Robustness, and Safe Deep Learning.',
|
||||
twitterCard: 'summary_large_image'
|
||||
subtitle: ''
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: head.title,
|
||||
description: head.description,
|
||||
ogTitle: `Arthur Danjou • ${head.title}`,
|
||||
ogDescription: head.description,
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: head.title,
|
||||
twitterDescription: head.description
|
||||
})
|
||||
|
||||
defineOgImageComponent('NuxtSeo', {
|
||||
title: head.title,
|
||||
description: head.description,
|
||||
subtitle: head.subtitle,
|
||||
theme: '#F43F5E'
|
||||
})
|
||||
|
||||
const { data: page } = await useAsyncData('index', () => {
|
||||
|
||||
@@ -13,7 +13,6 @@ if (!project.value) {
|
||||
})
|
||||
}
|
||||
|
||||
// Flatten the structure for ContentRenderer
|
||||
const projectWithBody = computed(() => {
|
||||
if (!project.value) return null
|
||||
return {
|
||||
@@ -27,7 +26,16 @@ useSeoMeta({
|
||||
description: project.value.description,
|
||||
ogTitle: `${project.value.title} • Arthur Danjou`,
|
||||
ogDescription: project.value.description,
|
||||
twitterCard: 'summary_large_image'
|
||||
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()
|
||||
@@ -47,7 +55,6 @@ const formattedDate = computed(() => {
|
||||
v-if="project"
|
||||
class="space-y-8"
|
||||
>
|
||||
<!-- Back Button -->
|
||||
<div>
|
||||
<UButton
|
||||
to="/projects"
|
||||
@@ -60,13 +67,12 @@ const formattedDate = computed(() => {
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Project Header -->
|
||||
<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 flex-shrink-0"
|
||||
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">
|
||||
@@ -76,14 +82,14 @@ const formattedDate = computed(() => {
|
||||
<div class="flex flex-wrap items-center gap-2 mb-4">
|
||||
<UBadge
|
||||
v-if="project.type"
|
||||
:color="typeColors[project.type] || 'neutral'"
|
||||
:color="(typeColors[project.type] || 'neutral') as any"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ project.type }}
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-if="project.status"
|
||||
:color="statusColors[project.status] || 'neutral'"
|
||||
:color="(statusColors[project.status] || 'neutral') as any"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ project.status }}
|
||||
@@ -120,7 +126,6 @@ const formattedDate = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div
|
||||
v-if="project.tags && project.tags.length > 0"
|
||||
class="flex flex-wrap gap-2 pt-2"
|
||||
@@ -128,8 +133,8 @@ const formattedDate = computed(() => {
|
||||
<UBadge
|
||||
v-for="tag in project.tags"
|
||||
:key="tag"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
@@ -138,7 +143,6 @@ const formattedDate = computed(() => {
|
||||
|
||||
<USeparator />
|
||||
|
||||
<!-- Project Content -->
|
||||
<ContentRenderer
|
||||
v-if="projectWithBody"
|
||||
:value="projectWithBody"
|
||||
|
||||
@@ -1,141 +1,185 @@
|
||||
<script lang="ts" setup>
|
||||
const { data: projects } = await useAsyncData('projects', () => {
|
||||
return queryCollection('projects').where('extension', '=', 'md').order('publishedAt', 'DESC').all()
|
||||
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: 'Engineering & Research Labs',
|
||||
description: 'Bridging the gap between theoretical models and production systems. Explore my experimental labs, open-source contributions, and engineering work.',
|
||||
ogTitle: 'Engineering & Research Labs • Arthur Danjou',
|
||||
ogDescription: 'Bridging the gap between theoretical models and production systems. Explore my experimental labs, open-source contributions, and engineering work.',
|
||||
twitterCard: 'summary_large_image'
|
||||
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 selectedTags = ref<string[]>([])
|
||||
|
||||
const statuses = computed(() => {
|
||||
const allStatuses = new Set<string>()
|
||||
const availableTypes = computed(() => {
|
||||
const types = new Set<string>()
|
||||
projects.value?.forEach((project) => {
|
||||
if (project.status) allStatuses.add(project.status)
|
||||
if (project.type) types.add(project.type)
|
||||
})
|
||||
return Array.from(allStatuses).sort()
|
||||
return Array.from(types).sort()
|
||||
})
|
||||
|
||||
const allTags = computed(() => {
|
||||
const tags = new Set<string>()
|
||||
const availableStatuses = computed(() => {
|
||||
const statuses = new Set<string>()
|
||||
projects.value?.forEach((project) => {
|
||||
project.tags?.forEach((tag: string) => tags.add(tag))
|
||||
if (project.status) statuses.add(project.status)
|
||||
})
|
||||
return Array.from(tags).sort()
|
||||
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
|
||||
const tagsMatch = selectedTags.value.length === 0
|
||||
|| selectedTags.value.some(tag => project.tags?.includes(tag))
|
||||
return statusMatch && tagsMatch
|
||||
return typeMatch && statusMatch
|
||||
})
|
||||
})
|
||||
|
||||
const { statusColors, typeColors } = useProjectColors()
|
||||
|
||||
function toggleTag(tag: string) {
|
||||
const index = selectedTags.value.indexOf(tag)
|
||||
if (index > -1) {
|
||||
selectedTags.value.splice(index, 1)
|
||||
}
|
||||
else {
|
||||
selectedTags.value.push(tag)
|
||||
}
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
selectedType.value = null
|
||||
selectedStatus.value = null
|
||||
selectedTags.value = []
|
||||
}
|
||||
|
||||
const hasActiveFilters = computed(() => !!selectedType.value || !!selectedStatus.value)
|
||||
const activeFilterCount = computed(() => (selectedType.value ? 1 : 0) + (selectedStatus.value ? 1 : 0))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-8">
|
||||
<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">
|
||||
<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="text-lg text-neutral-600 dark:text-neutral-400">
|
||||
<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>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex flex-wrap gap-4 items-center">
|
||||
<div class="flex gap-2 items-center">
|
||||
<span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Status:</span>
|
||||
<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="!selectedStatus ? 'solid' : 'ghost'"
|
||||
:variant="!selectedType ? 'solid' : 'ghost'"
|
||||
color="neutral"
|
||||
size="sm"
|
||||
@click="selectedStatus = null"
|
||||
@click="selectedType = null"
|
||||
>
|
||||
All
|
||||
</UButton>
|
||||
<UButton
|
||||
v-for="status in statuses"
|
||||
:key="status"
|
||||
:variant="selectedStatus === status ? 'solid' : 'ghost'"
|
||||
:color="statusColors[status] || 'neutral'"
|
||||
v-for="type in availableTypes"
|
||||
:key="type"
|
||||
:variant="selectedType === type ? 'subtle' : 'ghost'"
|
||||
:color="(typeColors[type] || 'neutral') as any"
|
||||
size="sm"
|
||||
@click="selectedStatus = selectedStatus === status ? null : status"
|
||||
class="transition-all duration-200"
|
||||
:class="selectedType === type ? 'ring-1 ring-inset' : ''"
|
||||
@click="selectedType = selectedType === type ? null : type"
|
||||
>
|
||||
{{ status }}
|
||||
{{ type }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags Filter -->
|
||||
<div
|
||||
v-if="allTags.length > 0"
|
||||
class="flex flex-wrap gap-2"
|
||||
>
|
||||
<UBadge
|
||||
v-for="tag in allTags"
|
||||
:key="tag"
|
||||
:color="selectedTags.includes(tag) ? 'primary' : 'gray'"
|
||||
:variant="selectedTags.includes(tag) ? 'solid' : 'outline'"
|
||||
class="cursor-pointer hover:scale-105 transition-transform"
|
||||
@click="toggleTag(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
</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>
|
||||
|
||||
<!-- Projects Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<UCard
|
||||
v-for="project in filteredProjects"
|
||||
:key="project.slug"
|
||||
:to="`/projects/${project.slug}`"
|
||||
class="hover:scale-[1.02] transition-transform cursor-pointer"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex items-start gap-3 flex-1">
|
||||
<UIcon
|
||||
v-if="project.icon"
|
||||
:name="project.icon"
|
||||
class="text-3xl text-neutral-700 dark:text-neutral-300 flex-shrink-0"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-neutral-900 dark:text-white truncate">
|
||||
{{ project.title }}
|
||||
</h3>
|
||||
<div class="flex items-center gap-2 mt-1 flex-wrap">
|
||||
<UTooltip
|
||||
:text="project.title"
|
||||
:popper="{ placement: 'top-start' }"
|
||||
class="w-full relative z-10"
|
||||
>
|
||||
<NuxtLink
|
||||
:to="`/projects/${project.slug}`"
|
||||
class="block focus:outline-none"
|
||||
>
|
||||
<h3 class="text-lg font-bold truncate group-hover:text-neutral-900 text-neutral-500 dark:group-hover:text-white transition-colors duration-200">
|
||||
{{ project.title }}
|
||||
</h3>
|
||||
</NuxtLink>
|
||||
</UTooltip>
|
||||
|
||||
<div class="flex items-center gap-2 mt-2 flex-wrap relative z-10">
|
||||
<UBadge
|
||||
v-if="project.type"
|
||||
:color="typeColors[project.type] || 'neutral'"
|
||||
:color="(typeColors[project.type] || 'neutral') as any"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
@@ -143,7 +187,7 @@ function clearFilters() {
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-if="project.status"
|
||||
:color="statusColors[project.status] || 'neutral'"
|
||||
:color="(statusColors[project.status] || 'neutral') as any"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
@@ -155,68 +199,80 @@ function clearFilters() {
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
⭐ Favorite
|
||||
⭐
|
||||
</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<p class="text-sm text-neutral-600 dark:text-neutral-400 line-clamp-3">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
<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="gray"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
<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"
|
||||
>
|
||||
{{ tag }}
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-if="project.tags && project.tags.length > 3"
|
||||
color="gray"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
>
|
||||
+{{ project.tags.length - 3 }}
|
||||
</UBadge>
|
||||
<UIcon
|
||||
name="i-ph-hourglass-medium-duotone"
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
{{ project.readingTime }}m
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="project.readingTime"
|
||||
class="text-xs text-neutral-500 dark:text-neutral-400"
|
||||
>
|
||||
{{ project.readingTime }} min read
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-if="filteredProjects.length === 0"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<UIcon
|
||||
name="i-ph-folder-open-duotone"
|
||||
class="text-6xl text-neutral-400 dark:text-neutral-600 mb-4"
|
||||
/>
|
||||
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
||||
No projects found with the selected filters.
|
||||
</p>
|
||||
<UButton
|
||||
class="mt-4"
|
||||
@click="clearFilters"
|
||||
<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"
|
||||
>
|
||||
Clear Filters
|
||||
</UButton>
|
||||
<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>
|
||||
|
||||
@@ -3,12 +3,27 @@ const { data: page } = await useAsyncData('uses', () => {
|
||||
return queryCollection('uses').first()
|
||||
})
|
||||
|
||||
useSeoMeta({
|
||||
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.',
|
||||
ogTitle: 'Research Lab & Technical Setup • Arthur Danjou',
|
||||
ogDescription: 'A curated list of the hardware, software, and self-hosted infrastructure that powers my mathematical modeling and AI research workflows.',
|
||||
twitterCard: 'summary_large_image'
|
||||
subtitle: 'The gear powering my research & development workflow.'
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: head.title,
|
||||
description: head.description,
|
||||
ogTitle: `${head.title} • Arthur Danjou`,
|
||||
ogDescription: head.description,
|
||||
twitterCard: 'summary_large_image',
|
||||
twitterTitle: head.title,
|
||||
twitterDescription: head.description
|
||||
})
|
||||
|
||||
defineOgImageComponent('NuxtSeo', {
|
||||
title: head.title,
|
||||
subtitle: head.subtitle,
|
||||
description: head.description,
|
||||
theme: '#F43F5E'
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
import { defineCollection, z } from '@nuxt/content'
|
||||
import { asSeoCollection } from '@nuxtjs/seo/content'
|
||||
|
||||
export const collections = {
|
||||
index: defineCollection({
|
||||
type: 'page',
|
||||
source: 'index.md'
|
||||
}),
|
||||
projects: defineCollection({
|
||||
type: 'data',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
type: z.string().optional(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number().optional(),
|
||||
tags: z.array(z.string()),
|
||||
favorite: z.boolean().optional(),
|
||||
status: z.enum(['active', 'completed', 'archived', 'in progress']),
|
||||
icon: z.string()
|
||||
})
|
||||
}),
|
||||
projects: defineCollection(
|
||||
asSeoCollection({
|
||||
type: 'data',
|
||||
source: 'projects/*.md',
|
||||
schema: z.object({
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
type: z.string().optional(),
|
||||
description: z.string(),
|
||||
publishedAt: z.string(),
|
||||
readingTime: z.number().optional(),
|
||||
tags: z.array(z.string()),
|
||||
favorite: z.boolean().optional(),
|
||||
status: z.enum(['active', 'completed', 'archived', 'in progress']),
|
||||
icon: z.string()
|
||||
})
|
||||
})),
|
||||
uses: defineCollection({
|
||||
type: 'page',
|
||||
source: 'uses.md'
|
||||
@@ -62,7 +64,6 @@ export const collections = {
|
||||
source: 'education/*.md',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
type: z.string().optional(),
|
||||
degree: z.string().optional(),
|
||||
institution: z.string(),
|
||||
startDate: z.string(),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"body": [
|
||||
{
|
||||
"id": "personal-email",
|
||||
"name": "Email Personnel",
|
||||
"name": "Personal Email",
|
||||
"category": "communication",
|
||||
"icon": "i-ph-envelope-simple-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/mail-perso",
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
{
|
||||
"id": "professional-email",
|
||||
"name": "Email Professionnel",
|
||||
"name": "Professional Email",
|
||||
"category": "communication",
|
||||
"icon": "i-ph-envelope-simple-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/mail-pro",
|
||||
@@ -51,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"id": "personal-website",
|
||||
"name": "Site Personnel",
|
||||
"name": "Portfolio",
|
||||
"category": "web",
|
||||
"icon": "i-ph:globe-duotone",
|
||||
"value": "https://arthurdanjou.fr",
|
||||
@@ -59,7 +59,7 @@
|
||||
},
|
||||
{
|
||||
"id": "status-page",
|
||||
"name": "Statut des Services",
|
||||
"name": "Status Page",
|
||||
"category": "infrastructure",
|
||||
"icon": "i-ph:fire-duotone",
|
||||
"value": "https://go.arthurdanjou.fr/status",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Bachelor's Degree in Mathematics
|
||||
type: Bachelor
|
||||
degree: Bachelor
|
||||
institution: Paris-Saclay University
|
||||
location: Paris, France
|
||||
startDate: 2021-09
|
||||
|
||||
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
|
||||
---
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Master's Degree in Applied Mathematics (Year 1)
|
||||
type: Master
|
||||
degree: Master
|
||||
institution: Paris Dauphine-PSL University
|
||||
location: Paris, France
|
||||
startDate: 2024-09
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Master's Degree in Applied Mathematics (Year 2)
|
||||
type: Master
|
||||
degree: Master
|
||||
institution: Paris Dauphine-PSL University
|
||||
location: Paris, France
|
||||
startDate: 2025-09
|
||||
|
||||
@@ -51,12 +51,14 @@ Mathematical rigor is the cornerstone of Safe AI. My background in :hover-text{t
|
||||
|
||||
Research requires discipline and transparency. Here is a real-time overview of my :hover-text{text="current environment" hover="OS, Editor, Activity"} and historical data.
|
||||
|
||||
::home-activity
|
||||
::home-live-status-page{class="mb-4"}
|
||||
::
|
||||
|
||||
::home-stats
|
||||
::home-live-activity{class="mb-4"}
|
||||
::
|
||||
|
||||
::home-live-stats
|
||||
|
||||
---
|
||||
|
||||
::home-quote
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
slug: artchat
|
||||
title: ArtChat - Portfolio & Blog
|
||||
type: Personal Project
|
||||
description: My personal space on the web — a portfolio, a blog, and a digital lab where I showcase my projects, write about topics I care about, and experiment with design and web technologies.
|
||||
publishedAt: 2024-06-01
|
||||
readingTime: 1
|
||||
favorite: true
|
||||
status: active
|
||||
tags:
|
||||
- Vue.js
|
||||
- Nuxt
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Web
|
||||
icon: i-ph-globe-hemisphere-west-duotone
|
||||
---
|
||||
|
||||
[**ArtChat**](https://go.arthurdanjou.fr/website) is my personal space on the web — a portfolio, a blog, and a digital lab where I showcase my projects, write about topics I care about, and experiment with design and web technologies.
|
||||
|
||||
It's designed to be fast, accessible, and fully responsive. The site also serves as a playground to explore and test modern frontend tools.
|
||||
|
||||
## ⚒️ Tech Stack
|
||||
|
||||
- **UI** → [Vue.js](https://vuejs.org/): A progressive JavaScript framework for building interactive interfaces.
|
||||
- **Framework** → [Nuxt](https://nuxt.com/): A powerful full-stack framework built on Vue, perfect for modern web apps.
|
||||
- **Content System** → [Nuxt Content](https://content.nuxtjs.org/): File-based CMS to manage blog posts and pages using Markdown.
|
||||
- **Design System** → [Nuxt UI](https://nuxtui.com/): Fully styled, customizable UI components tailored for Nuxt.
|
||||
- **CMS & Editing** → [Nuxt Studio](https://nuxt.studio): Visual editing and content management integrated with Nuxt Content.
|
||||
- **Language** → [TypeScript](https://www.typescriptlang.org/): A statically typed superset of JavaScript.
|
||||
- **Styling** → [Sass](https://sass-lang.com/) & [Tailwind CSS](https://tailwindcss.com/): Utility-first CSS framework enhanced with SCSS flexibility.
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/): Cloudflare-powered platform for fast, scalable Nuxt app deployment.
|
||||
- **Package Manager** → [pnpm](https://pnpm.io/): A fast, disk-efficient package manager for JavaScript/TypeScript projects.
|
||||
- **Linter** → [ESLint](https://eslint.org/): A tool for identifying and fixing problems in JavaScript/TypeScript code.
|
||||
- **ORM** → [Drizzle ORM](https://orm.drizzle.team/): A lightweight, type-safe ORM for TypeScript.
|
||||
- **Validation** → [Zod](https://zod.dev/): A TypeScript-first schema declaration and validation library with full static type inference.
|
||||
- **Deployment** → [NuxtHub](https://hub.nuxt.com/): A platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
status: archived
|
||||
status: Archived
|
||||
tags:
|
||||
- Nuxt
|
||||
- Vue.js
|
||||
@@ -14,16 +14,15 @@ tags:
|
||||
icon: i-ph-house-duotone
|
||||
---
|
||||
|
||||
[ArtHome](https://go.arthurdanjou.fr/arthome) is a customizable browser homepage that lets you organize all your favorite links in one place.
|
||||
[**ArtHome**](https://go.arthurdanjou.fr/arthome) is a customizable browser homepage that lets you organize all your favorite links in one place.
|
||||
|
||||
Create categories and tabs to group your shortcuts, personalize them with icons and colors, and make the page private if you want to keep your links just for yourself. The interface is clean, responsive, and works across all modern browsers.
|
||||
|
||||
## 🛠️ Built with
|
||||
## 🛠️ Technology Stack
|
||||
|
||||
- [Nuxt](https://nuxt.com): An open-source framework for building performant, full-stack web applications with Vue.
|
||||
- [NuxtHub](https://hub.nuxt.com): A Cloudflare-powered platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.
|
||||
- [NuxtUI](https://ui.nuxt.com): A sleek and flexible component library that helps create beautiful, responsive UIs for Nuxt applications.
|
||||
- [ESLint](https://eslint.org): A linter that identifies and fixes problems in your JavaScript/TypeScript code.
|
||||
- [Drizzle ORM](https://orm.drizzle.team/): A lightweight, type-safe ORM built for TypeScript, designed for simplicity and performance.
|
||||
- [Zod](https://zod.dev/): A TypeScript-first schema declaration and validation library with full static type inference.
|
||||
- and a lot of ❤️
|
||||
- **[Nuxt](https://nuxt.com)**: An open-source framework for building performant, full-stack web applications with Vue.
|
||||
- **[NuxtHub](https://hub.nuxt.com)**: A Cloudflare-powered platform to deploy and scale Nuxt apps globally with minimal latency and full-stack capabilities.
|
||||
- **[NuxtUI](https://ui.nuxt.com)**: A sleek and flexible component library that helps create beautiful, responsive UIs for Nuxt applications.
|
||||
- **[ESLint](https://eslint.org)**: A linter that identifies and fixes problems in your JavaScript/TypeScript code.
|
||||
- **[Drizzle ORM](https://orm.drizzle.team/)**: A lightweight, type-safe ORM built for TypeScript, designed for simplicity and performance.
|
||||
- **[Zod](https://zod.dev/)**: A TypeScript-first schema declaration and validation library with full static type inference.
|
||||
|
||||
@@ -6,7 +6,7 @@ description: A personal homelab environment where I deploy, test, and maintain s
|
||||
publishedAt: 2025-09-04
|
||||
readingTime: 1
|
||||
favorite: true
|
||||
status: active
|
||||
status: Active
|
||||
tags:
|
||||
- Docker
|
||||
- Proxmox
|
||||
@@ -18,8 +18,7 @@ icon: i-ph-flask-duotone
|
||||
|
||||
[**ArtLab**](https://go.arthurdanjou.fr/status) is my personal homelab, where I experiment with self-hosting and automation.
|
||||
|
||||
My homelab is a self-hosted environment where I deploy, test, and maintain personal services. Everything is securely exposed **only through a private VPN** using [Tailscale](https://tailscale.com/), ensuring encrypted, access-controlled connections across all devices.
|
||||
For selected services, I also use **Cloudflare Tunnels** to enable secure external access without opening ports or exposing my public IP.
|
||||
My homelab is a self-hosted environment where I deploy, test, and maintain personal services. Everything is securely exposed **only through a private VPN** using [Tailscale](https://tailscale.com/), ensuring encrypted, access-controlled connections across all devices. For selected services, I also use **Cloudflare Tunnels** to enable secure external access without opening ports or exposing my public IP.
|
||||
|
||||
## 🛠️ Running Services
|
||||
|
||||
@@ -36,7 +35,7 @@ For selected services, I also use **Cloudflare Tunnels** to enable secure extern
|
||||
- **Beszel**: Self-hosted, lightweight alternative to Notion for notes and knowledge management.
|
||||
- **Palmr**: Personal logging and journaling tool.
|
||||
|
||||
## 🖥️ Hardware
|
||||
## 🖥️ Hardware Specifications
|
||||
|
||||
- **Beelink EQR6**: AMD Ryzen mini PC, main server host.
|
||||
- **TP-Link 5-port Switch**: Network connectivity for all devices.
|
||||
|
||||
45
content/projects/artsite.md
Normal file
45
content/projects/artsite.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
slug: artsite
|
||||
title: ArtSite - Personal Research Hub
|
||||
type: Personal Project
|
||||
description: My digital headquarters. A high-performance portfolio built on the Edge using the full Nuxt ecosystem, deployed to Cloudflare Workers via Wrangler.
|
||||
publishedAt: 2024-06-01
|
||||
readingTime: 2
|
||||
favorite: true
|
||||
status: Active
|
||||
tags:
|
||||
- Nuxt
|
||||
- NuxtHub
|
||||
- Cloudflare Workers
|
||||
- TypeScript
|
||||
icon: i-ph-globe-hemisphere-west-duotone
|
||||
---
|
||||
|
||||
[**ArtSite**](https://go.arthurdanjou.fr/website) is my digital headquarters—a unified platform serving as my engineering portfolio and experimental lab.
|
||||
|
||||
More than just a static site, it is a modern **Portfolio** designed to be fast, accessible, and type-safe. It serves as a live production environment where I experiment with the latest frontend technologies and Edge computing paradigms.
|
||||
|
||||
## ⚡ The Nuxt Stack Architecture
|
||||
|
||||
This project is built entirely on the **Nuxt ecosystem**, leveraging the synergy between its modules for maximum developer experience and performance.
|
||||
|
||||
### Core Engine
|
||||
- **[Nuxt 3](https://nuxt.com/)**: The meta-framework providing the backbone (SSR, Auto-imports, Modules).
|
||||
- **[Nitro](https://nitro.unjs.io/)**: The high-performance server engine that powers the API routes and renders the app at the Edge.
|
||||
|
||||
### Infrastructure & Deployment
|
||||
- **[Cloudflare Workers](https://workers.cloudflare.com/)**: The application runs entirely on Cloudflare's global serverless network (SSR), ensuring minimal latency and high resilience.
|
||||
- **[Wrangler](https://developers.cloudflare.com/workers/wrangler/)**: The command-line tool used for precise deployment pipelines and worker configuration.
|
||||
- **[NuxtHub](https://hub.nuxt.com/)**: Integrated specifically for **advanced cache management** and unifying Cloudflare platform features (KV, D1, Blob) within the Nuxt runtime.
|
||||
|
||||
### Content & Data
|
||||
- **[Nuxt Content](https://content.nuxtjs.org/)**: A Git-based Headless CMS that treats Markdown as a database.
|
||||
- **[Nuxt Studio](https://nuxt.studio)**: A live visual editor allowing for seamless content management directly from the browser.
|
||||
|
||||
### Interface & Design
|
||||
- **[Nuxt UI](https://nuxtui.com/)**: A comprehensive component library built on Headless UI and Tailwind CSS.
|
||||
- **[Tailwind CSS](https://tailwindcss.com/)**: Utility-first styling for rapid and responsive design.
|
||||
|
||||
### Quality Assurance
|
||||
- **[TypeScript](https://www.typescriptlang.org/)**: Strict type safety across the entire stack (Frontend & Backend).
|
||||
- **[Zod](https://zod.dev/)**: Runtime schema validation for API inputs and environment variables.
|
||||
@@ -6,21 +6,18 @@ description: A curated collection of mathematics and data science projects devel
|
||||
publishedAt: 2023-09-01
|
||||
readingTime: 1
|
||||
favorite: true
|
||||
status: in progress
|
||||
status: In progress
|
||||
tags:
|
||||
- Python
|
||||
- R
|
||||
- Data Science
|
||||
- Machine Learning
|
||||
- Mathematics
|
||||
icon: i-ph-book-duotone
|
||||
---
|
||||
|
||||
# ArtStudies
|
||||
[**ArtStudies Projects**](https://github.com/ArthurDanjou/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
|
||||
|
||||
[ArtStudies Projects](https://github.com/ArthurDanjou/artstudies) is a curated collection of academic projects completed throughout my mathematics studies. The repository showcases work in both _Python_ and _R_, focusing on mathematical modeling, data analysis, and numerical methods.
|
||||
|
||||
The projects are organized into two main sections:
|
||||
The projects are organized into three main sections:
|
||||
- **L3** – Third year of the Bachelor's degree in Mathematics
|
||||
- **M1** – First year of the Master's degree in Mathematics
|
||||
- **M2** – Second year of the Master's degree in Mathematics
|
||||
@@ -56,20 +53,19 @@ The projects are organized into two main sections:
|
||||
|
||||
## 🛠️ Technologies & Tools
|
||||
|
||||
- [Python](https://www.python.org): A high-level, interpreted programming language, widely used for data science, machine learning, and scientific computing.
|
||||
- [R](https://www.r-project.org): A statistical computing environment, perfect for data analysis and visualization.
|
||||
- [Jupyter](https://jupyter.org): Interactive notebooks combining code, results, and rich text for reproducible research.
|
||||
- [Pandas](https://pandas.pydata.org): A data manipulation library providing data structures and operations for manipulating numerical tables and time series.
|
||||
- [NumPy](https://numpy.org): Core package for numerical computing with support for large, multi-dimensional arrays and matrices.
|
||||
- [SciPy](https://www.scipy.org): A library for advanced scientific computations including optimization, integration, and signal processing.
|
||||
- [Scikit-learn](https://scikit-learn.org): A robust library offering simple and efficient tools for machine learning and statistical modeling, including classification, regression, and clustering.
|
||||
- [TensorFlow](https://www.tensorflow.org): A comprehensive open-source framework for building and deploying machine learning and deep learning models.
|
||||
- [Keras](https://keras.io): A high-level neural networks API, running on top of TensorFlow, designed for fast experimentation.
|
||||
- [Matplotlib](https://matplotlib.org): A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
|
||||
- [Plotly](https://plotly.com): An interactive graphing library for creating dynamic visualizations in Python and R.
|
||||
- [Seaborn](https://seaborn.pydata.org): A statistical data visualization library built on top of Matplotlib, providing a high-level interface for drawing attractive and informative graphics.
|
||||
- [RMarkdown](https://rmarkdown.rstudio.com): A dynamic tool for combining code, results, and narrative into high-quality documents and presentations.
|
||||
- [FactoMineR](https://factominer.free.fr/): An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
|
||||
- [ggplot2](https://ggplot2.tidyverse.org): A grammar-based graphics package for creating complex and elegant visualizations in R.
|
||||
- [RShiny](https://shiny.rstudio.com): A web application framework for building interactive web apps directly from R.
|
||||
- and my 🧠.
|
||||
- **[Python](https://www.python.org)**: A high-level, interpreted programming language, widely used for data science, machine learning, and scientific computing.
|
||||
- **[R](https://www.r-project.org)**: A statistical computing environment, perfect for data analysis and visualization.
|
||||
- **[Jupyter](https://jupyter.org)**: Interactive notebooks combining code, results, and rich text for reproducible research.
|
||||
- **[Pandas](https://pandas.pydata.org)**: A data manipulation library providing data structures and operations for manipulating numerical tables and time series.
|
||||
- **[NumPy](https://numpy.org)**: Core package for numerical computing with support for large, multi-dimensional arrays and matrices.
|
||||
- **[SciPy](https://www.scipy.org)**: A library for advanced scientific computations including optimization, integration, and signal processing.
|
||||
- **[Scikit-learn](https://scikit-learn.org)**: A robust library offering simple and efficient tools for machine learning and statistical modeling, including classification, regression, and clustering.
|
||||
- **[TensorFlow](https://www.tensorflow.org)**: A comprehensive open-source framework for building and deploying machine learning and deep learning models.
|
||||
- **[Keras](https://keras.io)**: A high-level neural networks API, running on top of TensorFlow, designed for fast experimentation.
|
||||
- **[Matplotlib](https://matplotlib.org)**: A versatile plotting library for creating high-quality static, animated, and interactive visualizations in Python.
|
||||
- **[Plotly](https://plotly.com)**: An interactive graphing library for creating dynamic visualizations in Python and R.
|
||||
- **[Seaborn](https://seaborn.pydata.org)**: A statistical data visualization library built on top of Matplotlib, providing a high-level interface for drawing attractive and informative graphics.
|
||||
- **[RMarkdown](https://rmarkdown.rstudio.com)**: A dynamic tool for combining code, results, and narrative into high-quality documents and presentations.
|
||||
- **[FactoMineR](https://factominer.free.fr/)**: An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
|
||||
- **[ggplot2](https://ggplot2.tidyverse.org)**: A grammar-based graphics package for creating complex and elegant visualizations in R.
|
||||
- **[RShiny](https://shiny.rstudio.com)**: A web application framework for building interactive web apps directly from R.
|
||||
|
||||
@@ -5,20 +5,15 @@ 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
|
||||
status: Completed
|
||||
tags:
|
||||
- R
|
||||
- Statistics
|
||||
- Data Analysis
|
||||
- GLM
|
||||
- Mathematics
|
||||
icon: i-ph-bicycle-duotone
|
||||
---
|
||||
|
||||
# Generalized Linear Models for Bikes Prediction
|
||||
|
||||
## Overview
|
||||
|
||||
This project was completed as part of the **Generalized Linear Models** course at Paris-Dauphine PSL University. The objective was to develop and compare statistical models to predict the number of bicycle rentals in a bike-sharing system based on various environmental and temporal characteristics.
|
||||
|
||||
## 📊 Project Objectives
|
||||
@@ -48,10 +43,9 @@ The analysis identified critical factors influencing bike-sharing demand:
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- **Code Repository**: [GLM Bikes Code](https://go.arthurdanjou.fr/glm-bikes-code)
|
||||
- **Full Report**: See embedded PDF below
|
||||
You can find the code here: [GLM Bikes Code](https://go.arthurdanjou.fr/glm-bikes-code)
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/bikes-glm/Report.pdf" width="100%" height="1000px">
|
||||
<iframe src="/projects/bikes-glm.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -5,43 +5,45 @@ 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
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- Machine Learning
|
||||
- Data Science
|
||||
- Classification
|
||||
- Healthcare
|
||||
icon: i-ph-heart-half-duotone
|
||||
---
|
||||
|
||||
The project was carried out as part of the `Statistical Learning` course at Paris-Dauphine PSL University. Its objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
|
||||
This project was carried out as part of the **Statistical Learning** course at Paris-Dauphine PSL University. The objective is to identify the most effective model for predicting or explaining the presence of breast cancer based on a set of biological and clinical features.
|
||||
|
||||
This project aims to develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
|
||||
## 📊 Project Objectives
|
||||
|
||||
Develop and evaluate several supervised classification models to predict the presence of breast cancer based on biological features extracted from the Breast Cancer Coimbra dataset, provided by the UCI Machine Learning Repository.
|
||||
|
||||
The dataset contains 116 observations divided into two classes:
|
||||
|
||||
- 1: healthy individuals (controls)
|
||||
|
||||
- 2: patients diagnosed with breast cancer
|
||||
- **1**: healthy individuals (controls)
|
||||
- **2**: patients diagnosed with breast cancer
|
||||
|
||||
There are 9 explanatory variables, including clinical measurements such as age, insulin levels, leptin, insulin resistance, among others.
|
||||
|
||||
## 🔍 Methodology
|
||||
|
||||
The project follows a comparative approach between several algorithms:
|
||||
|
||||
- Logistic Regression
|
||||
|
||||
- k-Nearest Neighbors (k-NN)
|
||||
|
||||
- Naive Bayes
|
||||
|
||||
- Artificial Neural Network (MLP with a 16-8-1 architecture)
|
||||
|
||||
Model evaluation is primarily based on the F1-score, which is more suitable in a medical context where identifying positive cases is crucial. Particular attention was paid to stratified cross-validation and to handling class imbalance, notably through the use of class weights and regularization techniques (L2, early stopping).
|
||||
|
||||
This project illustrates a concrete application of data science techniques to a public health issue, while implementing a rigorous methodology for supervised modeling.
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
You can find the code here: [Breast Cancer Detection](https://go.arthurdanjou.fr/breast-cancer-detection-code)
|
||||
|
||||
<iframe src="/projects/breast-cancer/report.pdf" width="100%" height="1000px">
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/breast-cancer.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
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>
|
||||
@@ -4,23 +4,16 @@ title: Dropout Reduces Underfitting
|
||||
type: Research Project
|
||||
description: TensorFlow/Keras implementation and reproduction of "Dropout Reduces Underfitting" (Liu et al., 2023). A comparative study of Early and Late Dropout strategies to optimize model convergence.
|
||||
publishedAt: 2024-12-10
|
||||
readingTime: 4
|
||||
status: completed
|
||||
readingTime: 6
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- TensorFlow
|
||||
- Machine Learning
|
||||
- Deep Learning
|
||||
- Research
|
||||
icon: i-ph-share-network-duotone
|
||||
---
|
||||
|
||||
📉 [Dropout Reduces Underfitting](https://github.com/arthurdanjou/dropoutreducesunderfitting): Reproduction & Analysis
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
> **Study and reproduction of the paper:** Liu, Z., et al. (2023). *Dropout Reduces Underfitting*. arXiv:2303.01500.
|
||||
|
||||
The paper is available at: [https://arxiv.org/abs/2303.01500](https://arxiv.org/abs/2303.01500)
|
||||
@@ -79,7 +72,8 @@ pip install tensorflow numpy matplotlib seaborn scikit-learn
|
||||
## 📊 Usage
|
||||
|
||||
The main notebook pipeline.ipynb contains all necessary code. Here is how to run a typical experiment via the pipeline API.
|
||||
1. Initialization
|
||||
|
||||
### 1. Initialization
|
||||
|
||||
Choose your dataset (cifar10, fashion_mnist, mnist) and architecture (cnn, dense).
|
||||
```python
|
||||
@@ -89,7 +83,7 @@ from pipeline import ExperimentPipeline
|
||||
exp = ExperimentPipeline(dataset_name="fashion_mnist", model_type="cnn")
|
||||
```
|
||||
|
||||
2. Learning Curves Comparison
|
||||
### 2. Learning Curves Comparison
|
||||
|
||||
Compare training dynamics (Loss & Accuracy) of the three strategies.
|
||||
|
||||
@@ -102,7 +96,7 @@ exp.compare_learning_curves(
|
||||
)
|
||||
```
|
||||
|
||||
3. Ablation Studies
|
||||
### 3. Ablation Studies
|
||||
|
||||
Study the impact of the "Early" phase duration or Dropout intensity.
|
||||
|
||||
@@ -124,7 +118,7 @@ exp.compare_drop_rates(
|
||||
)
|
||||
```
|
||||
|
||||
4. Data Regimes (Data Scarcity)
|
||||
### 4. Data Regimes (Data Scarcity)
|
||||
|
||||
Verify the paper's hypothesis that Early Dropout shines on large datasets (or limited models) while Standard Dropout protects small datasets.
|
||||
|
||||
@@ -145,6 +139,11 @@ According to the paper, you should observe:
|
||||
- Early Dropout: Higher initial Loss, followed by a sharp drop after the switch_epoch, often reaching a lower minimum than Standard Dropout (reduction of underfitting).
|
||||
- Late Dropout: Rapid rise in accuracy at the start (potential overfitting), then stabilized by the activation of dropout.
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/dropout-reduces-underfitting.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
## 📝 Authors
|
||||
|
||||
- [Arthur Danjou](https://github.com/ArthurDanjou)
|
||||
@@ -152,6 +151,7 @@ According to the paper, you should observe:
|
||||
- [Axelle Meric](https://github.com/AxelleMeric)
|
||||
- [Philippine Quellec](https://github.com/Philippine35890)
|
||||
- [Moritz Von Siemens](https://github.com/MoritzSiem)
|
||||
|
||||
M.Sc. Statistical and Financial Engineering (ISF) - Data Science Track at Université Paris-Dauphine PSL
|
||||
|
||||
Based on the work of Liu, Z., et al. (2023). Dropout Reduces Underfitting.
|
||||
Based on the work of Liu, Z., et al. (2023). Dropout Reduces Underfitting.
|
||||
@@ -5,20 +5,16 @@ type: Academic Project
|
||||
description: Predicting loan approval and default risk using machine learning classification techniques.
|
||||
publishedAt: 2025-01-24
|
||||
readingTime: 2
|
||||
status: completed
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- Machine Learning
|
||||
- Classification
|
||||
- Data Science
|
||||
- Regression
|
||||
- Finance
|
||||
- Data Science
|
||||
icon: i-ph-money-wavy-duotone
|
||||
---
|
||||
|
||||
# Machine Learning for Loan Prediction
|
||||
|
||||
## Overview
|
||||
|
||||
This project focuses on building machine learning models to predict loan approval outcomes and assess default risk. The objective is to develop robust classification models that can effectively identify creditworthy applicants.
|
||||
|
||||
## 📊 Project Objectives
|
||||
@@ -38,17 +34,7 @@ The study employs various machine learning approaches:
|
||||
- **Hyperparameter Tuning** - Optimizing model performance
|
||||
- **Cross-validation** - Ensuring robust generalization
|
||||
|
||||
## 📁 Key Findings
|
||||
|
||||
[To be completed with your findings]
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- **Code Repository**: [Add link to your code]
|
||||
- **Dataset**: [Add dataset information]
|
||||
- **Full Report**: See embedded PDF below
|
||||
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/loan-ml/Report.pdf" width="100%" height="1000px">
|
||||
<iframe src="/projects/loan-ml.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
@@ -5,19 +5,21 @@ 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
|
||||
status: Completed
|
||||
tags:
|
||||
- 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
|
||||
@@ -25,7 +27,11 @@ Methods and algorithms implemented:
|
||||
- Cumulative density function
|
||||
- Empirical Quantile Function
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
You can find the code here: [Monte Carlo Project Code](https://go.arthurdanjou.fr/monte-carlo-code)
|
||||
|
||||
<iframe src="/projects/monte-carlo-project/Report.pdf" width="100%" height="1000px">
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/monte-carlo.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- Data Visualization
|
||||
@@ -15,9 +15,13 @@ tags:
|
||||
icon: i-ph-city-duotone
|
||||
---
|
||||
|
||||
This is the French version of the report for the Schelling Segregation Model project. The project was done as part of the course `Projet Numérique` at the Paris-Saclay University. The goal was to implement the Schelling Segregation Model in Python and analyze the results using statistics and data visualization.
|
||||
This report presents the Schelling Segregation Model project completed as part of the **Projet Numérique** course at Paris-Saclay University. The goal was to implement the Schelling Segregation Model in Python and analyze the results using statistics and data visualization.
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
You can find the code here: [Schelling Segregation Model Code](https://go.arthurdanjou.fr/schelling-code)
|
||||
|
||||
<iframe src="/projects/schelling/Projet.pdf" width="100%" height="1000px">
|
||||
## 📄 Detailed Report
|
||||
|
||||
<iframe src="/projects/schelling.pdf" width="100%" height="1000px">
|
||||
</iframe>
|
||||
|
||||
@@ -5,7 +5,7 @@ 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
|
||||
status: Completed
|
||||
tags:
|
||||
- Python
|
||||
- PySpark
|
||||
@@ -15,17 +15,19 @@ tags:
|
||||
icon: i-ph-dog-duotone
|
||||
---
|
||||
|
||||
[Sevetys](https://sevetys.fr) is a leading French network of over 200 veterinary clinics, employing more than 1,300 professionals. Founded in 2017, the group provides comprehensive veterinary care for companion animals, exotic pets, and livestock, with services ranging from preventive medicine and surgery to cardiology, dermatology, and 24/7 emergency care.
|
||||
[**Sevetys**](https://sevetys.fr) is a leading French network of over 200 veterinary clinics, employing more than 1,300 professionals. Founded in 2017, the group provides comprehensive veterinary care for companion animals, exotic pets, and livestock, with services ranging from preventive medicine and surgery to cardiology, dermatology, and 24/7 emergency care.
|
||||
|
||||
Committed to digital innovation, Sevetys leverages centralized data systems to optimize clinic operations, improve patient data management, and enhance the overall client experience. This combination of medical excellence and operational efficiency supports veterinarians in delivering the highest quality care nationwide.
|
||||
|
||||
## 🎯 Internship Objectives
|
||||
|
||||
During my two-month internship as a Data Engineer, I focused primarily on cleaning and standardizing customer and patient data — a critical task, as this data is extensively used by clinics, Marketing, and Performance teams. Ensuring data quality was therefore essential to the company's operations.
|
||||
|
||||
Additionally, I took charge of revising and enhancing an existing data quality report designed to evaluate the effectiveness of my cleaning processes. The report encompassed 47 detailed metrics assessing data completeness and consistency, providing valuable insights that helped maintain high standards across the organization.
|
||||
|
||||
## ⚙️ Stack
|
||||
## ⚙️ Technology Stack
|
||||
|
||||
- [Microsoft Azure Cloud](https://azure.microsoft.com/)
|
||||
- [PySpark](https://spark.apache.org/docs/latest/api/python/)
|
||||
- [Python](https://www.python.org/)
|
||||
- [GitLab]()
|
||||
- **[Microsoft Azure Cloud](https://azure.microsoft.com/)**: Cloud infrastructure platform
|
||||
- **[PySpark](https://spark.apache.org/docs/latest/api/python/)**: Distributed data processing framework
|
||||
- **[Python](https://www.python.org/)**: Primary programming language
|
||||
- **[GitLab](https://gitlab.com)**: Version control and CI/CD platform
|
||||
|
||||
@@ -50,13 +50,21 @@ I prioritize tools that offer **AI-integration** and **strong type-checking**.
|
||||
|
||||
::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 Macchiato.
|
||||
* **Font:** JetBrains Mono.
|
||||
* **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[Warp]{color="black" icon="i-simple-icons-warp"} — A Rust-based terminal with AI command suggestion and block-based output.
|
||||
* :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.
|
||||
::
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { definePerson } from 'nuxt-schema-org/schema'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
|
||||
modules: [
|
||||
'@nuxtjs/seo',
|
||||
'@nuxt/ui',
|
||||
'@nuxtjs/mdc',
|
||||
'@nuxt/content',
|
||||
@@ -18,17 +21,46 @@ export default defineNuxtConfig({
|
||||
pageTransition: { name: 'page', mode: 'out-in' },
|
||||
rootAttrs: {
|
||||
class: 'bg-[var(--ui-bg)]'
|
||||
},
|
||||
head: {
|
||||
templateParams: {
|
||||
separator: '•'
|
||||
},
|
||||
titleTemplate: '%s %separator %siteName',
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'apple-touch-icon', href: '/favicon.ico' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
site: {
|
||||
url: 'https://arthurdanjou.fr',
|
||||
name: 'Arthur Danjou',
|
||||
description: 'Research Engineer & Applied Mathematics Student. Focusing on AI Safety, Robustness, and Statistical Learning.',
|
||||
defaultLocale: 'en',
|
||||
indexable: true
|
||||
},
|
||||
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
fallback: 'light'
|
||||
},
|
||||
|
||||
content: {
|
||||
build: {
|
||||
markdown: {
|
||||
highlight: {
|
||||
langs: ['python'],
|
||||
theme: {
|
||||
default: 'catppuccin-latte',
|
||||
dark: 'catppuccin-macchiato'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
database: {
|
||||
type: 'd1',
|
||||
bindingName: 'DB'
|
||||
@@ -85,11 +117,13 @@ export default defineNuxtConfig({
|
||||
|
||||
nitro: {
|
||||
preset: 'cloudflare_module',
|
||||
|
||||
experimental: {
|
||||
openAPI: true
|
||||
},
|
||||
|
||||
prerender: {
|
||||
routes: ['/'],
|
||||
crawlLinks: true
|
||||
}
|
||||
},
|
||||
@@ -108,6 +142,35 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
|
||||
schemaOrg: {
|
||||
identity: definePerson({
|
||||
name: 'Arthur Danjou',
|
||||
givenName: 'Arthur',
|
||||
familyName: 'Danjou',
|
||||
image: '/arthur.webp',
|
||||
description: 'Research Engineer & Applied Mathematics Student. Focusing on AI Safety, Robustness, and Statistical Learning.',
|
||||
jobTitle: 'Research Engineer & Applied Mathematics Student',
|
||||
|
||||
email: 'arthurdanjou@outlook.fr',
|
||||
url: 'https://arthurdanjou.fr',
|
||||
sameAs: [
|
||||
'https://twitter.com/arthurdanj',
|
||||
'https://github.com/arthurdanjou',
|
||||
'https://linkedin.com/in/arthurdanjou'
|
||||
],
|
||||
|
||||
worksFor: {
|
||||
'@type': 'Organization',
|
||||
'name': 'Arthur Danjou',
|
||||
'url': 'https://arthurdanjou.fr'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
seo: {
|
||||
redirectToCanonicalSiteUrl: true
|
||||
},
|
||||
|
||||
studio: {
|
||||
route: '/studio',
|
||||
repository: {
|
||||
|
||||
22956
package-lock.json
generated
22956
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -12,24 +12,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.15",
|
||||
"@nuxt/content": "3.9.0",
|
||||
"@nuxt/content": "3.10.0",
|
||||
"@nuxt/eslint": "1.12.1",
|
||||
"@nuxt/ui": "^4.3.0",
|
||||
"@nuxthub/core": "0.10.4",
|
||||
"@nuxtjs/mdc": "^0.19.1",
|
||||
"@nuxtjs/mdc": "0.19.2",
|
||||
"@nuxtjs/seo": "3.3.0",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"@vueuse/math": "^14.1.0",
|
||||
"better-sqlite3": "^12.5.0",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"nuxt": "4.2.2",
|
||||
"nuxt-studio": "1.0.0-alpha.4",
|
||||
"nuxt-studio": "1.0.0",
|
||||
"vue": "3.5.26",
|
||||
"vue-router": "4.6.4",
|
||||
"zod": "^4.2.1"
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/devicon": "1.2.55",
|
||||
"@iconify-json/devicon": "1.2.56",
|
||||
"@iconify-json/file-icons": "^1.2.2",
|
||||
"@iconify-json/logos": "^1.2.10",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
@@ -39,7 +40,7 @@
|
||||
"@vueuse/nuxt": "14.1.0",
|
||||
"eslint": "9.39.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vue-tsc": "3.2.1",
|
||||
"wrangler": "4.56.0"
|
||||
"vue-tsc": "3.2.2",
|
||||
"wrangler": "4.54.0"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 179 KiB |
BIN
public/projects/bikes-glm.pdf
Normal file
BIN
public/projects/bikes-glm.pdf
Normal file
Binary file not shown.
BIN
public/projects/breast-cancer.pdf
Normal file
BIN
public/projects/breast-cancer.pdf
Normal file
Binary file not shown.
BIN
public/projects/datavis.pdf
Normal file
BIN
public/projects/datavis.pdf
Normal file
Binary file not shown.
BIN
public/projects/dropout-reduces-underfitting.pdf
Normal file
BIN
public/projects/dropout-reduces-underfitting.pdf
Normal file
Binary file not shown.
BIN
public/projects/ml-loan.pdf
Normal file
BIN
public/projects/ml-loan.pdf
Normal file
Binary file not shown.
BIN
public/projects/monte-carlo.pdf
Normal file
BIN
public/projects/monte-carlo.pdf
Normal file
Binary file not shown.
BIN
public/projects/schelling.pdf
Normal file
BIN
public/projects/schelling.pdf
Normal file
Binary file not shown.
@@ -3,6 +3,7 @@ import { queryCollection } from '@nuxt/content/server'
|
||||
export default defineCachedEventHandler(async (event) => {
|
||||
const result = await queryCollection(event, 'education')
|
||||
.where('extension', '=', 'md')
|
||||
.order('startDate', 'DESC')
|
||||
.all()
|
||||
|
||||
if (result.length === 0) {
|
||||
@@ -10,14 +11,6 @@ export default defineCachedEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
return result
|
||||
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
|
||||
.map(edu => ({
|
||||
degree: edu.degree,
|
||||
institution: edu.institution,
|
||||
startDate: edu.startDate,
|
||||
endDate: edu.endDate,
|
||||
location: edu.location
|
||||
}))
|
||||
}, {
|
||||
maxAge: 60 * 60 * 24,
|
||||
name: 'education'
|
||||
|
||||
@@ -3,6 +3,7 @@ import { queryCollection } from '@nuxt/content/server'
|
||||
export default defineCachedEventHandler(async (event) => {
|
||||
const result = await queryCollection(event, 'experiences')
|
||||
.where('extension', '=', 'md')
|
||||
.order('startDate', 'DESC')
|
||||
.all()
|
||||
|
||||
if (result.length === 0) {
|
||||
@@ -10,16 +11,6 @@ export default defineCachedEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
return result
|
||||
.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())
|
||||
.map(exp => ({
|
||||
title: exp.title,
|
||||
company: exp.company,
|
||||
companyUrl: exp.companyUrl,
|
||||
startDate: exp.startDate,
|
||||
endDate: exp.endDate,
|
||||
location: exp.location,
|
||||
description: exp.description
|
||||
}))
|
||||
},
|
||||
{
|
||||
maxAge: 60 * 60 * 24,
|
||||
|
||||
@@ -1,42 +1,56 @@
|
||||
import type { H3Event } from 'h3'
|
||||
|
||||
const WAKATIME_HEADERS = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
const fetchWakatime = async (url: string) => {
|
||||
try {
|
||||
return await $fetch<{ data: unknown[] }>(url, {
|
||||
headers: WAKATIME_HEADERS,
|
||||
timeout: 5000
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Wakatime Error] Failed to fetch ${url}`, err)
|
||||
return { data: [] }
|
||||
}
|
||||
}
|
||||
|
||||
const cachedWakatimeCoding = defineCachedFunction(async (event: H3Event) => {
|
||||
const config = useRuntimeConfig(event)
|
||||
|
||||
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.coding}.json`)
|
||||
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.coding}.json`)
|
||||
}, {
|
||||
maxAge: 24 * 60 * 60,
|
||||
name: 'wakatime',
|
||||
maxAge: 60 * 50,
|
||||
name: 'stats',
|
||||
getKey: () => 'coding'
|
||||
})
|
||||
|
||||
const cachedWakatimeEditors = defineCachedFunction(async (event: H3Event) => {
|
||||
const config = useRuntimeConfig(event)
|
||||
|
||||
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.editors}.json`)
|
||||
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.editors}.json`)
|
||||
}, {
|
||||
maxAge: 24 * 60 * 60,
|
||||
name: 'wakatime',
|
||||
maxAge: 60 * 60,
|
||||
name: 'stats',
|
||||
getKey: () => 'editors'
|
||||
})
|
||||
|
||||
const cachedWakatimeOs = defineCachedFunction(async (event: H3Event) => {
|
||||
const config = useRuntimeConfig(event)
|
||||
|
||||
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.os}.json`)
|
||||
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.os}.json`)
|
||||
}, {
|
||||
maxAge: 24 * 60 * 60,
|
||||
name: 'wakatime',
|
||||
maxAge: 60 * 60,
|
||||
name: 'stats',
|
||||
getKey: () => 'os'
|
||||
})
|
||||
|
||||
const cachedWakatimeLanguages = defineCachedFunction(async (event: H3Event) => {
|
||||
const config = useRuntimeConfig(event)
|
||||
|
||||
return await $fetch<{ data: unknown[] }>(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.languages}.json`)
|
||||
return await fetchWakatime(`https://wakatime.com/share/${config.wakatime.userId}/${config.wakatime.languages}.json`)
|
||||
}, {
|
||||
maxAge: 24 * 60 * 60,
|
||||
name: 'wakatime',
|
||||
maxAge: 60 * 60,
|
||||
name: 'stats',
|
||||
getKey: () => 'languages'
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ export default defineCachedEventHandler(async (event) => {
|
||||
const { statusPage } = useRuntimeConfig(event)
|
||||
return await $fetch(statusPage)
|
||||
}, {
|
||||
maxAge: 60 * 60,
|
||||
maxAge: 60,
|
||||
name: 'status-page'
|
||||
})
|
||||
|
||||
1
server/db/migrations/sqlite/meta/_journal.json
Normal file
1
server/db/migrations/sqlite/meta/_journal.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"7","dialect":"sqlite","entries":[]}
|
||||
@@ -35,12 +35,69 @@ export interface Activity {
|
||||
}
|
||||
}
|
||||
|
||||
export interface StatusTag {
|
||||
id: number
|
||||
monitor_id: number
|
||||
tag_id: number
|
||||
value: string
|
||||
name: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export interface StatusMonitor {
|
||||
id: number
|
||||
name: string
|
||||
sendUrl: number
|
||||
type: string
|
||||
url?: string
|
||||
tags: StatusTag[]
|
||||
}
|
||||
|
||||
export interface StatusGroup {
|
||||
id: number
|
||||
name: string
|
||||
weight: number
|
||||
monitorList: StatusMonitor[]
|
||||
}
|
||||
|
||||
export interface StatusMaintenance {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
strategy: string
|
||||
active: boolean
|
||||
status: string // 'under-maintenance', etc.
|
||||
// ... autres champs optionnels (dateRange, etc.)
|
||||
}
|
||||
|
||||
export interface StatusConfig {
|
||||
slug: string
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
autoRefreshInterval: number
|
||||
theme: string
|
||||
published: boolean
|
||||
showTags: boolean
|
||||
customCSS: string
|
||||
footerText: string
|
||||
showPoweredBy: boolean
|
||||
}
|
||||
|
||||
export interface StatusPageData {
|
||||
config: StatusConfig
|
||||
incident: unknown | null
|
||||
publicGroupList: StatusGroup[]
|
||||
maintenanceList: StatusMaintenance[]
|
||||
}
|
||||
|
||||
export const IDEs = [
|
||||
{ name: 'Visual Studio Code', icon: 'i-logos:visual-studio-code' },
|
||||
{ name: 'IntelliJ IDEA Ultimate', icon: 'i-logos:intellij-idea' },
|
||||
{ name: 'WebStorm', icon: 'i-logos:webstorm' },
|
||||
{ name: 'PyCharm Professional', icon: 'i-logos:pycharm' },
|
||||
{ name: 'Cursor', icon: 'i-vscode-icons-file-type-cursorrules' }
|
||||
{ name: 'Cursor', icon: 'i-vscode-icons-file-type-cursorrules' },
|
||||
{ name: 'Positron', icon: 'i-devicon-positron' }
|
||||
] as const
|
||||
|
||||
interface Nav {
|
||||
|
||||
13
worker-configuration.d.ts
vendored
13
worker-configuration.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable */
|
||||
// Generated by Wrangler by running `wrangler types` (hash: 730a20d40c88c8f7167cb4afae90a541)
|
||||
// Runtime types generated with workerd@1.20251217.0 2025-12-13 nodejs_compat
|
||||
// Generated by Wrangler by running `wrangler types` (hash: 373e9a05bf207b93549ab53665d07e4b)
|
||||
// Runtime types generated with workerd@1.20251210.0 2025-12-13 nodejs_compat
|
||||
declare namespace Cloudflare {
|
||||
interface Env {
|
||||
CACHE: KVNamespace;
|
||||
@@ -13,6 +13,9 @@ declare namespace Cloudflare {
|
||||
NUXT_WAKATIME_OS: string;
|
||||
NUXT_WAKATIME_USER_ID: string;
|
||||
NUXT_STATUS_PAGE: string;
|
||||
NUXT_HUB_CLOUDFLARE_ACCOUNT_ID: string;
|
||||
NUXT_HUB_CLOUDFLARE_API_TOKEN: string;
|
||||
NUXT_HUB_CLOUDFLARE_CACHE_NAMESPACE_ID: string;
|
||||
DB: D1Database;
|
||||
ASSETS: Fetcher;
|
||||
}
|
||||
@@ -22,7 +25,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
|
||||
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
|
||||
};
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "STUDIO_GITHUB_CLIENT_ID" | "STUDIO_GITHUB_CLIENT_SECRET" | "NUXT_DISCORD_USER_ID" | "NUXT_WAKATIME_CODING" | "NUXT_WAKATIME_EDITORS" | "NUXT_WAKATIME_LANGUAGES" | "NUXT_WAKATIME_OS" | "NUXT_WAKATIME_USER_ID" | "NUXT_STATUS_PAGE">> {}
|
||||
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "STUDIO_GITHUB_CLIENT_ID" | "STUDIO_GITHUB_CLIENT_SECRET" | "NUXT_DISCORD_USER_ID" | "NUXT_WAKATIME_CODING" | "NUXT_WAKATIME_EDITORS" | "NUXT_WAKATIME_LANGUAGES" | "NUXT_WAKATIME_OS" | "NUXT_WAKATIME_USER_ID" | "NUXT_STATUS_PAGE" | "NUXT_HUB_CLOUDFLARE_ACCOUNT_ID" | "NUXT_HUB_CLOUDFLARE_API_TOKEN" | "NUXT_HUB_CLOUDFLARE_CACHE_NAMESPACE_ID">> {}
|
||||
}
|
||||
|
||||
// Begin runtime types
|
||||
@@ -8605,7 +8608,7 @@ type AIGatewayHeaders = {
|
||||
[key: string]: string | number | boolean | object;
|
||||
};
|
||||
type AIGatewayUniversalRequest = {
|
||||
provider: AIGatewayProviders | string;
|
||||
provider: AIGatewayProviders | string; // eslint-disable-line
|
||||
endpoint: string;
|
||||
headers: Partial<AIGatewayHeaders>;
|
||||
query: unknown;
|
||||
@@ -8621,7 +8624,7 @@ declare abstract class AiGateway {
|
||||
gateway?: UniversalGatewayOptions;
|
||||
extraHeaders?: object;
|
||||
}): Promise<Response>;
|
||||
getUrl(provider?: AIGatewayProviders | string): Promise<string>;
|
||||
getUrl(provider?: AIGatewayProviders | string): Promise<string>; // eslint-disable-line
|
||||
}
|
||||
interface AutoRAGInternalError extends Error {
|
||||
}
|
||||
|
||||
@@ -3,20 +3,25 @@
|
||||
"name": "artsite",
|
||||
"compatibility_date": "2025-12-13",
|
||||
"compatibility_flags": [
|
||||
"nodejs_compat",
|
||||
"nodejs_compat"
|
||||
],
|
||||
"preview_urls": true,
|
||||
"workers_dev": true,
|
||||
"main": "./.output/server/index.mjs",
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "v2.arthurdanjou.fr",
|
||||
"pattern": "arthurdanjou.fr",
|
||||
"zone_name": "arthurdanjou.fr",
|
||||
"custom_domain": true
|
||||
},
|
||||
{
|
||||
"pattern": "www.arthurdanjou.fr",
|
||||
"zone_name": "arthurdanjou.fr",
|
||||
"custom_domain": true
|
||||
}
|
||||
],
|
||||
"placement": {
|
||||
"mode": "smart",
|
||||
"mode": "smart"
|
||||
},
|
||||
"assets": {
|
||||
"binding": "ASSETS",
|
||||
@@ -25,7 +30,9 @@
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8"
|
||||
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8",
|
||||
"migrations_table": "_hub_migrations",
|
||||
"migrations_dir": ".output/server/db/migrations/"
|
||||
}
|
||||
],
|
||||
"kv_namespaces": [
|
||||
@@ -47,5 +54,30 @@
|
||||
"head_sampling_rate": 1,
|
||||
"persist": true
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"preview": {
|
||||
"routes": [
|
||||
{
|
||||
"pattern": "preview.arthurdanjou.fr",
|
||||
"zone_name": "arthurdanjou.fr",
|
||||
"custom_domain": true
|
||||
}
|
||||
],
|
||||
"d1_databases": [
|
||||
{
|
||||
"binding": "DB",
|
||||
"database_id": "d6e8c2ff-399a-4bec-b1b3-4bfe57d50ea8",
|
||||
"migrations_table": "_hub_migrations",
|
||||
"migrations_dir": ".output/server/db/migrations/"
|
||||
}
|
||||
],
|
||||
"kv_namespaces": [
|
||||
{
|
||||
"binding": "CACHE",
|
||||
"id": "f0766ace1d24423ba6e5cac4fb8f2054"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user