diff --git a/README.md b/README.md index b1bee73..76e67d6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Arnaud Durand — portfolio -Site FR/EN « Papier d’atelier », étapes 5A–5B terminées, contrôle final du 14 septembre 2026. Six projets, sept événements et trois récits par langue. Destination prévue : GitHub Pages. Site : https://wesper-dev.github.io/ (français : /fr/). +Portfolio FR/EN « Papier d’atelier », publié sur GitHub Pages : https://wesper-dev.github.io/ (français : /fr/). Six projets, sept événements et trois récits par langue. Le workflow contrôle les pull requests et déploie les versions de main après un build réussi. ## Démarrer et vérifier @@ -31,7 +31,7 @@ Seul **`dist/client/`** est un artefact statique public. `dist/server/` et les a ## Indexation -Le build par défaut est un aperçu : `noindex, nofollow` et `Disallow: /`. Préparer un export indexable après validation du résultat : +Le build par défaut est un aperçu : `noindex, nofollow` et `Disallow: /`. Pour construire localement le même export indexable que celui publié par le workflow : ```sh SITE_PUBLIC=1 npm run build @@ -42,12 +42,12 @@ Cette commande construit uniquement en local ; elle ne publie rien. Les canonica ## Ajouter ou modifier du contenu -- `content/projects.json` : six fiches bilingues, rôle, statut, dépôt, tags et éventuel récit. Les aperçus d’accueil consomment ces fiches. +- `content/projects.json` : six fiches bilingues, rôle, statut, dépôt et éventuel récit. Les tags sont stockés dans chaque langue (`fr.tags`, `en.tags`) ; conserver les noms techniques et traduire les libellés éditoriaux. Les aperçus d’accueil consomment ces fiches. - `content/hackathons.json` : événements, date connue, contexte/contribution/résultat FR/EN. `project` mène au récit local ; `link` est une destination publique facultative et explicitement libellée. Ne pas ajouter une source privée comme lien de navigation par défaut. - `content/stories/{en,fr}/` : récits Markdown. Sous-ensemble pris en charge : titre `#`, sections `##`, paragraphes, listes de liens HTTPS et code en ligne. Aucun HTML brut. Modifier les deux langues ensemble. - `content/home.ts` : présentation, parcours, engagements et libellés. `content/indexes.json` : introductions des index. - `components/` : présentation ; les quatre composants de page partagent navigation, contact et pied de page via `site-shell.tsx`. -- `lib/metadata.ts` : métadonnées par route. `public/og.jpg` : carte typographique générale ; le récit Droit de Retard utilise sa propre capture comme image de partage. +- `lib/metadata.ts` : métadonnées par route. `public/og.jpg` : carte typographique générale ; `public/images/droit-de-retard-social.jpg` est la carte 1200 × 630 du récit Droit de Retard. La capture complète reste affichée dans la page. - `public/fonts/` : polices WOFF2 auto-hébergées, sources et licences SIL OFL. Modèle de fiche projet : identifiant stable, nom, date réellement connue (ou null), dépôt, statut, contribution personnelle, résumé FR/EN, tags, crédits et liens publics. Garder la preuve et son niveau de vérification dans le dossier privé de travail, puis copier uniquement les faits sélectionnés. Ne pas transformer une hypothèse en fait. @@ -103,6 +103,12 @@ Ajouter un objet à `content/hackathons.json` sans modifier les composants, en r } ``` -Pour les projets, partir d’une fiche existante dans `content/projects.json` : conserver `role`, `status` et `summary` dans les deux langues, créditer l’origine collective et utiliser `year: null` lorsque l’année est inconnue. Les dates ne doivent pas être déduites d’un intitulé ou inventées. +Pour les projets, partir d’une fiche existante dans `content/projects.json` : conserver `role`, `status`, `summary` et `tags` dans les deux langues, créditer l’origine collective et utiliser `year: null` lorsque l’année est inconnue. Les dates ne doivent pas être déduites d’un intitulé ou inventées. Pour une nouvelle image : conserver l’original hors de l’export, dimensionner pour l’usage réel, choisir JPEG/WebP pour une photographie ou capture adaptée et vérifier visuellement la compression. La carte générale de partage reste en 1200 × 630 et sous 300 ko ; reporter tout changement de nom/dimensions dans `lib/metadata.ts`. Renseigner l’alternative textuelle et le crédit avant de publier. + +## HTTPS et en-têtes de sécurité + +HTTPS est activé sur GitHub Pages. Au relevé du 14 septembre 2026, les réponses du site ne portent pas les en-têtes HTTP `Strict-Transport-Security`, `Content-Security-Policy`, `X-Content-Type-Options`, `Referrer-Policy` ni `X-Frame-Options` ; `Access-Control-Allow-Origin: *` est émis par la plateforme. Ces en-têtes de réponse ne sont pas configurables depuis les fichiers de ce dépôt : un contrôle complet demanderait un proxy/CDN configurable ou un autre hébergement. Voir [HTTPS sur Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/securing-your-github-pages-site-with-https) et la [demande de prise en charge des en-têtes](https://github.com/orgs/community/discussions/54257). + +Le contenu est statique et public, sans authentification, formulaire de saisie ni script tiers ; CORS ne donne ici accès qu’aux fichiers déjà publics. Cette architecture réduit l’exposition, sans constituer une garantie générale de sécurité. Aucun changement d’hébergement n’est prévu pour ce seul motif. Certaines protections, dont une partie de CSP, peuvent aussi être définies en HTML avec leurs limites ; une [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy) devra être conçue et testée avec les scripts du site avant ajout. Cette note documente les limites observées, elle ne remplace pas la maintenance des dépendances. diff --git a/components/project-index.tsx b/components/project-index.tsx index 7530d11..656a936 100644 --- a/components/project-index.tsx +++ b/components/project-index.tsx @@ -2,4 +2,4 @@ import projects from '../content/projects.json'; import indexes from '../content/indexes.json'; import type { Locale } from '../content/home'; import { SiteShell, pagePath, Arrow } from './site-shell'; -export default function ProjectIndex({locale}:{locale:Locale}) {const t=indexes[locale].projects;return

{locale==='fr'?'Carnet de réalisations':'A collection of work'}

{t.title}.

{t.intro}

{projects.map((p,i)=>)}
} +export default function ProjectIndex({locale}:{locale:Locale}) {const t=indexes[locale].projects;return

{locale==='fr'?'Carnet de réalisations':'A collection of work'}

{t.title}.

{t.intro}

{projects.map((p,i)=>)}
} diff --git a/components/project-story.tsx b/components/project-story.tsx index c857366..624475b 100644 --- a/components/project-story.tsx +++ b/components/project-story.tsx @@ -7,5 +7,5 @@ import {SiteShell,pagePath,Arrow} from './site-shell'; function inline(text:string) {return text.split(/(\[[^\]]+\]\(https?:\/\/[^)]+\)|`[^`]+`)/g).map((part,i)=>{const link=part.match(/^\[([^\]]+)\]\((https?:\/\/[^)]+)\)$/);return link?{link[1]}:part.startsWith('`')?{part.slice(1,-1)}:{part}})} export default function ProjectStory({locale,id}:{locale:Locale;id:StoryId}) { const [heading,lead,...blocks]=stories[locale][id].trim().split(/\n\s*\n/);const p=projects.find(p=>p.id===id)!;const fr=locale==='fr'; - return
{fr?'← Tous les projets':'← All projects'}

{p.tags.join(' · ')} · {p.year}

{heading.replace(/^# /,'')}

{lead}

{blocks.map((block,i)=>block.startsWith('## ')?

{block.slice(3)}

:block.startsWith('- ')?
    {block.split('\n').map((line,j)=>
  • {inline(line.slice(2))}
  • )}
:

{inline(block.replace(/\n/g,' '))}

)}
{id==='droit-de-retard'&&
{fr?'Interface
{fr?'Interface de démonstration du dépôt Droit de Retard.':'Demo interface from the Droit de Retard repository.'} {fr?'Ouvrir l’image en taille réelle':'Open full-size image'} ↗ · {fr?'Source et crédit':'Source and credit'}
}
; + return
{fr?'← Tous les projets':'← All projects'}

{p[locale].tags.join(' · ')} · {p.year}

{heading.replace(/^# /,'')}

{lead}

{blocks.map((block,i)=>block.startsWith('## ')?

{block.slice(3)}

:block.startsWith('- ')?
    {block.split('\n').map((line,j)=>
  • {inline(line.slice(2))}
  • )}
:

{inline(block.replace(/\n/g,' '))}

)}
{id==='droit-de-retard'&&
{fr?'Interface
{fr?'Interface de démonstration du dépôt Droit de Retard.':'Demo interface from the Droit de Retard repository.'} {fr?'Ouvrir l’image en taille réelle':'Open full-size image'} ↗ · {fr?'Source et crédit':'Source and credit'}
}
; } diff --git a/content/home.ts b/content/home.ts index 5074903..0351070 100644 --- a/content/home.ts +++ b/content/home.ts @@ -8,7 +8,7 @@ export const home = { role: 'AI Platform / Applied AI Engineer', availability: 'Looking for a permanent role after 16 October 2026', selected: 'A few things I’ve built', selectedNote: 'Ideas explored in teams, then taken a little further.', code: 'Explore the repository', screenshot: 'Droit de Retard · demo interface from the public repository', - projects: projects.filter(p=>p.story).map(p=>({name:p.name,category:p.tags.join(' · '),text:p.en.summary,role:p.en.role,url:p.repo,id:p.id})), + projects: projects.filter(p=>p.story).map(p=>({name:p.name,category:p.en.tags.join(' · '),text:p.en.summary,role:p.en.role,url:p.repo,id:p.id})), foundation:'And underneath, a systems foundation.', foundationText:'Projects from École 42 Paris: processes, concurrency and the small details that make software work.', systems:[{name:'Minishell',text:'A shell in C · pipes, redirections & processes'},{name:'Inception',text:'Docker infrastructure · NGINX, WordPress & MariaDB'},{name:'Philosophers',text:'Concurrency in C · threads & mutexes'}], @@ -27,7 +27,7 @@ export const home = { role:'AI Platform / Applied AI Engineer', availability:'À la recherche d’un CDI après le 16 octobre 2026', selected:'Quelques projets en chemin',selectedNote:'Des idées explorées en équipe, puis prolongées.', code:'Explorer le dépôt', screenshot:'Droit de Retard · interface de démonstration du dépôt public', - projects: projects.filter(p=>p.story).map(p=>({name:p.name,category:p.tags.join(' · '),text:p.fr.summary,role:p.fr.role,url:p.repo,id:p.id})), + projects: projects.filter(p=>p.story).map(p=>({name:p.name,category:p.fr.tags.join(' · '),text:p.fr.summary,role:p.fr.role,url:p.repo,id:p.id})), foundation:'Et sous le capot, un socle systèmes.',foundationText:'Des projets d’École 42 Paris : les processus, la concurrence et les détails qui font fonctionner un logiciel.', systems:[{name:'Minishell',text:'Un shell en C · pipes, redirections et processus'},{name:'Inception',text:'Infrastructure Docker · NGINX, WordPress et MariaDB'},{name:'Philosophers',text:'Concurrence en C · threads et mutex'}], hackTitle:'Des rencontres. Des problèmes nouveaux.',hackIntro:'Les hackathons me permettent de rencontrer des gens et d’explorer des terrains inconnus. Certaines idées deviennent des projets au long cours ; d’autres restent des expériences.',hackMore:'Quelques explorations en équipe', diff --git a/content/projects.json b/content/projects.json index e5ec97b..47b1d5f 100644 --- a/content/projects.json +++ b/content/projects.json @@ -1,8 +1,157 @@ [ - {"id":"droit-de-retard","name":"Droit de Retard","year":"2026","repo":"https://github.com/Wesper-Dev/droit-de-retard","story":"droit-de-retard","tags":["Python","Agents","Local-first"],"fr":{"summary":"Préparer un brouillon de réclamation aérienne à partir de documents, avec des validations et calculs explicites en Python.","role":"Continuation personnelle d’un projet collectif : validation des appels d’outils, tests de régression et CI.","status":"Prototype informatif"},"en":{"summary":"Prepare a draft flight claim from documents, with explicit validation and calculations in Python.","role":"Personal continuation of a team project: tool-call validation, regression tests and CI.","status":"Informational prototype"}}, - {"id":"diary","name":"Diary / Kura","year":"2025","repo":"https://github.com/Wesper-Dev/Diary","story":"diary","tags":["Audio","React","FastAPI"],"fr":{"summary":"Relier l’enregistrement vocal dans le navigateur au traitement audio, au stockage et aux rapports hebdomadaires.","role":"Continuation personnelle de Kura, projet développé en équipe pendant deux hackathons.","status":"Preuve de concept"},"en":{"summary":"Connect browser voice recordings to audio processing, storage and weekly reports.","role":"Personal continuation of Kura, a team project developed across two hackathons.","status":"Proof of concept"}}, - {"id":"croix-rouge","name":"Croix-Rouge","year":"2025","repo":"https://github.com/Wesper-Dev/CroixRouge-Hackathon","upstream":"https://github.com/copeugne/CroixRouge-Hackathon","story":"croix-rouge","tags":["AI","Volunteering"],"fr":{"summary":"Plateforme collective de formation aux premiers secours, présentée à ChangeNOW 2025 ; échanges avec les visiteurs au stand Croix-Rouge.","role":"Bénévolat : partie de la couche IA et cadrage avec les formateurs. Le dépôt est un fork du projet collectif.","status":"Projet collectif présenté publiquement"},"en":{"summary":"A collective first-aid training platform, shown at ChangeNOW 2025; conversations with visitors at the Red Cross stand.","role":"Volunteer contribution to part of the AI layer and problem framing with trainers. The repository is a fork of the team project.","status":"Team project presented publicly"}}, - {"id":"minishell","name":"Minishell","year":null,"repo":"https://github.com/Wesper-Dev/Minishell","story":null,"tags":["C","UNIX"],"fr":{"summary":"Un shell en C : exécution de commandes, pipes, redirections et gestion des processus.","role":"Projet du cursus 42 ; travail sur l’exécution et les appels système UNIX.","status":"Projet pédagogique"},"en":{"summary":"A shell in C: command execution, pipes, redirections and process handling.","role":"42 curriculum project; work on execution and UNIX system calls.","status":"Learning project"}}, - {"id":"inception","name":"Inception","year":null,"repo":"https://github.com/Wesper-Dev/Inception","story":null,"tags":["Docker","Linux"],"fr":{"summary":"Infrastructure multi-conteneurs avec NGINX, WordPress et MariaDB, volumes persistants et réseau dédié.","role":"Projet du cursus 42 : Dockerfiles et orchestration des services.","status":"Projet pédagogique"},"en":{"summary":"Multi-container infrastructure with NGINX, WordPress and MariaDB, persistent volumes and a dedicated network.","role":"42 curriculum project: Dockerfiles and service orchestration.","status":"Learning project"}}, - {"id":"philosophers","name":"Philosophers","year":null,"repo":"https://github.com/Wesper-Dev/Philosophers","story":null,"tags":["C","Concurrency"],"fr":{"summary":"Simulation du problème des philosophes avec threads POSIX, mutex et surveillance de la simulation.","role":"Projet du cursus 42 consacré à la synchronisation et aux ressources partagées.","status":"Projet pédagogique"},"en":{"summary":"Dining philosophers simulation with POSIX threads, mutexes and a monitoring routine.","role":"42 curriculum project focused on synchronisation and shared resources.","status":"Learning project"}} + { + "id": "droit-de-retard", + "name": "Droit de Retard", + "year": "2026", + "repo": "https://github.com/Wesper-Dev/droit-de-retard", + "story": "droit-de-retard", + "fr": { + "summary": "Préparer un brouillon de réclamation aérienne à partir de documents, avec des validations et calculs explicites en Python.", + "role": "Continuation personnelle d’un projet collectif : validation des appels d’outils, tests de régression et CI.", + "status": "Prototype informatif", + "tags": [ + "Python", + "Agents", + "Local-first" + ] + }, + "en": { + "summary": "Prepare a draft flight claim from documents, with explicit validation and calculations in Python.", + "role": "Personal continuation of a team project: tool-call validation, regression tests and CI.", + "status": "Informational prototype", + "tags": [ + "Python", + "Agents", + "Local-first" + ] + } + }, + { + "id": "diary", + "name": "Diary / Kura", + "year": "2025", + "repo": "https://github.com/Wesper-Dev/Diary", + "story": "diary", + "fr": { + "summary": "Relier l’enregistrement vocal dans le navigateur au traitement audio, au stockage et aux rapports hebdomadaires.", + "role": "Continuation personnelle de Kura, projet développé en équipe pendant deux hackathons.", + "status": "Preuve de concept", + "tags": [ + "Audio", + "React", + "FastAPI" + ] + }, + "en": { + "summary": "Connect browser voice recordings to audio processing, storage and weekly reports.", + "role": "Personal continuation of Kura, a team project developed across two hackathons.", + "status": "Proof of concept", + "tags": [ + "Audio", + "React", + "FastAPI" + ] + } + }, + { + "id": "croix-rouge", + "name": "Croix-Rouge", + "year": "2025", + "repo": "https://github.com/Wesper-Dev/CroixRouge-Hackathon", + "upstream": "https://github.com/copeugne/CroixRouge-Hackathon", + "story": "croix-rouge", + "fr": { + "summary": "Plateforme collective de formation aux premiers secours, présentée à ChangeNOW 2025 ; échanges avec les visiteurs au stand Croix-Rouge.", + "role": "Bénévolat : partie de la couche IA et cadrage avec les formateurs. Le dépôt est un fork du projet collectif.", + "status": "Projet collectif présenté publiquement", + "tags": [ + "IA", + "Bénévolat" + ] + }, + "en": { + "summary": "A collective first-aid training platform, shown at ChangeNOW 2025; conversations with visitors at the Red Cross stand.", + "role": "Volunteer contribution to part of the AI layer and problem framing with trainers. The repository is a fork of the team project.", + "status": "Team project presented publicly", + "tags": [ + "AI", + "Volunteering" + ] + } + }, + { + "id": "minishell", + "name": "Minishell", + "year": null, + "repo": "https://github.com/Wesper-Dev/Minishell", + "story": null, + "fr": { + "summary": "Un shell en C : exécution de commandes, pipes, redirections et gestion des processus.", + "role": "Projet du cursus 42 ; travail sur l’exécution et les appels système UNIX.", + "status": "Projet pédagogique", + "tags": [ + "C", + "UNIX" + ] + }, + "en": { + "summary": "A shell in C: command execution, pipes, redirections and process handling.", + "role": "42 curriculum project; work on execution and UNIX system calls.", + "status": "Learning project", + "tags": [ + "C", + "UNIX" + ] + } + }, + { + "id": "inception", + "name": "Inception", + "year": null, + "repo": "https://github.com/Wesper-Dev/Inception", + "story": null, + "fr": { + "summary": "Infrastructure multi-conteneurs avec NGINX, WordPress et MariaDB, volumes persistants et réseau dédié.", + "role": "Projet du cursus 42 : Dockerfiles et orchestration des services.", + "status": "Projet pédagogique", + "tags": [ + "Docker", + "Linux" + ] + }, + "en": { + "summary": "Multi-container infrastructure with NGINX, WordPress and MariaDB, persistent volumes and a dedicated network.", + "role": "42 curriculum project: Dockerfiles and service orchestration.", + "status": "Learning project", + "tags": [ + "Docker", + "Linux" + ] + } + }, + { + "id": "philosophers", + "name": "Philosophers", + "year": null, + "repo": "https://github.com/Wesper-Dev/Philosophers", + "story": null, + "fr": { + "summary": "Simulation du problème des philosophes avec threads POSIX, mutex et surveillance de la simulation.", + "role": "Projet du cursus 42 consacré à la synchronisation et aux ressources partagées.", + "status": "Projet pédagogique", + "tags": [ + "C", + "Concurrence" + ] + }, + "en": { + "summary": "Dining philosophers simulation with POSIX threads, mutexes and a monitoring routine.", + "role": "42 curriculum project focused on synchronisation and shared resources.", + "status": "Learning project", + "tags": [ + "C", + "Concurrency" + ] + } + } ] diff --git a/lib/metadata.ts b/lib/metadata.ts index 242bd89..8291dc1 100644 --- a/lib/metadata.ts +++ b/lib/metadata.ts @@ -3,6 +3,6 @@ import type { Locale } from '../content/home'; const origin='https://wesper-dev.github.io'; export function pageMetadata(locale:Locale,path:string,title:string,description:string):Metadata { const url=origin+(locale==='fr'?'/fr/':'/')+path; - const image=path==='projects/droit-de-retard/'?'/images/droit-de-retard.png':'/og.jpg'; - return {title,description,metadataBase:new URL(origin),icons:{icon:'/favicon.svg'},robots:{index:process.env.SITE_PUBLIC==='1',follow:process.env.SITE_PUBLIC==='1'},alternates:{canonical:url,languages:{en:origin+'/'+path,fr:origin+'/fr/'+path,'x-default':origin+'/'+path}},openGraph:{title,description,type:'website',url,locale:locale==='fr'?'fr_FR':'en_US',alternateLocale:locale==='fr'?'en_US':'fr_FR',siteName:'Arnaud Durand',images:[{url:origin+image,width: image==='/og.jpg'?1200:1440,height:image==='/og.jpg'?630:1180,alt:path==='projects/droit-de-retard/'?'Droit de Retard — demo interface':'Arnaud Durand — AI, Systems, Projects'}]},twitter:{card:'summary_large_image',title,description,images:[origin+image]}}; + const image=path==='projects/droit-de-retard/'?'/images/droit-de-retard-social.jpg':'/og.jpg'; + return {title,description,metadataBase:new URL(origin),icons:{icon:'/favicon.svg'},robots:{index:process.env.SITE_PUBLIC==='1',follow:process.env.SITE_PUBLIC==='1'},alternates:{canonical:url,languages:{en:origin+'/'+path,fr:origin+'/fr/'+path,'x-default':origin+'/'+path}},openGraph:{title,description,type:'website',url,locale:locale==='fr'?'fr_FR':'en_US',alternateLocale:locale==='fr'?'en_US':'fr_FR',siteName:'Arnaud Durand',images:[{url:origin+image,width:1200,height:630,alt:path==='projects/droit-de-retard/'?(locale==='fr'?'Droit de Retard — aperçu du prototype':'Droit de Retard — prototype preview'):(locale==='fr'?'Arnaud Durand — IA, systèmes, projets':'Arnaud Durand — AI, Systems, Projects')}]},twitter:{card:'summary_large_image',title,description,images:[origin+image]}}; } diff --git a/public/images/droit-de-retard-social.jpg b/public/images/droit-de-retard-social.jpg new file mode 100644 index 0000000..6f950a1 Binary files /dev/null and b/public/images/droit-de-retard-social.jpg differ