fix: replace em dashes with hyphens in README.md, package.json, scene.ts, projects.ts, site.ts, BaseLayout.astro, about.astro, contact.astro, index.astro, [slug].astro, and work/index.astro

This commit is contained in:
LOUIS POTEVIN
2026-06-24 14:27:42 +02:00
parent fa37f0358d
commit d1cc9de1f1
11 changed files with 51 additions and 55 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
# louis-potevin.dev
Professional portfolio of **Louis Potevin** full-stack developer.
Professional portfolio of **Louis Potevin** - full-stack developer.
Static multi-page website built with **Astro + TypeScript + Sass**, styled
**exclusively** by the in-house design system **`@unkn0wndo3s/nova-design-system`**
(tokens, components), featuring a 3D space background controlled via scrolling.
@@ -28,9 +28,9 @@ compatibility with Astro 7 in its `peerDependencies`. The **`.npmrc`**
All modifiable content is centralized. Values to be confirmed are
marked with `TODO` in the code :
- **`src/data/site.ts`** name, role, email, external links (Gitea, GitHub,
- **`src/data/site.ts`** - name, role, email, external links (Gitea, GitHub,
npm, LinkedIn). In particular, verify :
- **`src/data/projects.ts`** la liste des projets (les « stations »).
- **`src/data/projects.ts`** - la liste des projets (les « stations »).
Chaque entrée : résumé, contexte, contributions, stack, liens.
---
+1 -1
View File
@@ -3,7 +3,7 @@
"type": "module",
"version": "1.0.0",
"private": true,
"description": "Portfolio Louis Potevin louis-potevin.dev. Astro + Nova Design System.",
"description": "Portfolio Louis Potevin - louis-potevin.dev. Astro + Nova Design System.",
"engines": {
"node": ">=22.12.0"
},
+6 -8
View File
@@ -1,13 +1,11 @@
import * as THREE from 'three';
// ─────────────────────────────────────────────────────────────────────────────
// Ambient space background: starfield, subtle nebula, and a slow-drifting,
// slow-rotating asteroid. No scroll binding, no engine flames.
// Slight pointer parallax. Respects prefers-reduced-motion.
// ─────────────────────────────────────────────────────────────────────────────
export interface SceneHandle {
/** Kept for compatibility no-op (no more impact/flame effect). */
/** Kept for compatibility - no-op (no more impact/flame effect). */
setImpact(value: number): void;
destroy(): void;
}
@@ -64,7 +62,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
);
camera.position.set(0, 0, 18);
// ── Lighting (cold, consistent with Nova tokens) ───────────────────────────
// Lighting (cold, consistent with Nova tokens)
const ambient = new THREE.AmbientLight(0x16323a, 1.0);
scene.add(ambient);
const key = new THREE.DirectionalLight(COLORS.starCool, 0.9);
@@ -74,7 +72,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
rimLight.position.set(8, -3, 4);
scene.add(rimLight);
// ── Starfield ──────────────────────────────────────────────────────────────
// Starfield
const STAR_COUNT = prefersReduced ? 700 : 1700;
const starGeo = new THREE.BufferGeometry();
const starPos = new Float32Array(STAR_COUNT * 3);
@@ -106,7 +104,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
// ── Nebula (soft sprites, pushed far into the background) ──────────────────
// Nebula (soft sprites, pushed far into the background)
const nebula = new THREE.Group();
const nebTex = radialTexture('rgba(40,129,129,0.55)');
[
@@ -129,7 +127,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
});
scene.add(nebula);
// ── Asteroid (cold rock, subtle halo) ──────────────────────────────────────
// Asteroid (cold rock, subtle halo)
const asteroid = new THREE.Group();
const rockGeo = new THREE.IcosahedronGeometry(1.9, 2);
const pos = rockGeo.attributes.position as THREE.BufferAttribute;
@@ -171,7 +169,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
asteroid.position.set(6.5, 4.5, -2);
scene.add(asteroid);
// ── State & loop ───────────────────────────────────────────────────────────
// State & loop
const pointer = { x: 0, y: 0, tx: 0, ty: 0 };
let running = true;
+7 -9
View File
@@ -1,7 +1,5 @@
// ─────────────────────────────────────────────────────────────────────────────
// Projects — real content. Edit freely.
// Projects - real content. Edit freely.
// `icon` matches a simple-icons-astro component (using PascalCase elsewhere).
// ─────────────────────────────────────────────────────────────────────────────
export type ProjectStatus = 'live' | 'maintenu' | 'archive' | 'r&d';
@@ -32,8 +30,8 @@ export const projects: Project[] = [
slug: 'nova-design-system',
designation: '01',
title: 'Nova Design System',
tagline: 'Astro component library tokens, accessibility, npm distribution.',
period: '2026 ongoing',
tagline: 'Astro component library - tokens, accessibility, npm distribution.',
period: '2026 - ongoing',
role: 'Author · maintainer',
status: 'maintenu',
featured: true,
@@ -65,7 +63,7 @@ export const projects: Project[] = [
designation: '02',
title: 'Self-hosted Infrastructure & Deployment',
tagline: 'Containerized services, CI/CD, and secured ingress on a fully controlled infrastructure.',
period: '2025 ongoing',
period: '2025 - ongoing',
role: 'Design & operations',
status: 'live',
featured: true,
@@ -120,9 +118,9 @@ export const projects: Project[] = [
{
slug: 'rust-file-organizer',
designation: '04',
title: 'File Organizer Java to Rust',
title: 'File Organizer - Java to Rust',
tagline: 'File sorting tool, currently being ported from Java to Rust.',
period: '2025 ongoing',
period: '2025 - ongoing',
role: 'Development',
status: 'r&d',
featured: false,
@@ -147,7 +145,7 @@ export const projects: Project[] = [
designation: '05',
title: 'LLM Training & Inference',
tagline: 'Small language model trained on a Reddit conversation dataset.',
period: '2025 ongoing',
period: '2025 - ongoing',
role: 'Development',
status: 'r&d',
featured: false,
+1 -1
View File
@@ -5,7 +5,7 @@ export const site = {
domain: "louis-potevin.dev",
location: "Limoges · Nouvelle-Aquitaine",
// Job Search
availability: "Permanent contract (CDI) September 2026",
availability: "Permanent contract (CDI) - September 2026",
mobility: "Anywhere in France, on-site or remote",
// Contact
email: "contact@louis-potevin.dev",
+3 -3
View File
@@ -15,7 +15,7 @@ export interface Props {
}
const {
title = 'Louis Potevin Full-stack Developer (front, back, web)',
title = 'Louis Potevin - Full-stack Developer (front, back, web)',
description = "Louis Potevin, full-stack web developer: front-end, back-end, TypeScript, Astro, Node. Available for a permanent contract (CDI) starting September 2026, anywhere in France or remote.",
keywords = [],
} = Astro.props;
@@ -91,7 +91,7 @@ const personLd = {
const siteLd = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: `${site.name} Portfolio`,
name: `${site.name} - Portfolio`,
url: Astro.site?.href ?? 'https://louis-potevin.dev',
inLanguage: ['en-US', 'fr-FR'],
};
@@ -113,7 +113,7 @@ const siteLd = {
<link rel="canonical" href={canonical} />
<meta property="og:type" content="website" />
<meta property="og:site_name" content={`${site.name} Portfolio`} />
<meta property="og:site_name" content={`${site.name} - Portfolio`} />
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="fr_FR" />
<meta property="og:title" content={title} />
+5 -5
View File
@@ -16,23 +16,23 @@ import {
import { Send, Mail } from '@lucide/astro';
import { site } from '../data/site';
const title = 'About Louis Potevin, full-stack web developer / développeur full-stack';
const title = 'About - Louis Potevin, full-stack web developer / développeur full-stack';
const description =
"Louis Potevin, full-stack web developer with a BUT MMI degree (DWDI track). 18 months of professional experience at Legrand France. Available for a permanent contract (CDI), anywhere in France or remote.";
const parcours = [
{
year: '2024 2026',
year: '2024 - 2026',
title: 'Professional Experience · Legrand France',
body: "18 months total: 12 weeks of internship, 1 month on a fixed-term contract (CDD), followed by 1 year of work-study apprenticeship. Web development on a production-ready internal corporate application.",
},
{
year: '2024 2026',
title: 'BUT MMI DWDI Track · IUT du Limousin',
year: '2024 - 2026',
title: 'BUT MMI - DWDI Track · IUT du Limousin',
body: "Multimedia and Internet Job Degrees (Métiers du Multimédia et de l'Internet), specializing in Web Development and Interactive Devices (DWDI). Degree completed in 2026.",
},
{
year: '2026 ongoing',
year: '2026 - ongoing',
title: 'Personal Projects',
body: "Designing an open-source design system published on npm, managing a self-hosted server infrastructure, and building system tools with Rust and Python.",
},
+5 -5
View File
@@ -15,7 +15,7 @@ import { Send, Mail } from '@lucide/astro';
import BrandLinkedin from '../components/BrandLinkedin.astro';
import { site } from '../data/site';
const title = 'Contact Louis Potevin';
const title = 'Contact - Louis Potevin';
const description =
'Contact Louis Potevin, full-stack web developer. Available for permanent positions (CDI) starting September 2026, anywhere in France or remote.';
@@ -46,7 +46,7 @@ const directLinks = [
</section>
<div class="layout">
{/* ── Form (Nova components) ── */}
{/* Form (Nova components) */}
<section class="formWrap" aria-labelledby="form-title">
<h2 id="form-title" class="formWrap__title">Message</h2>
@@ -95,7 +95,7 @@ const directLinks = [
</div>
</section>
{/* ── Direct Links ── */}
{/* Direct Links */}
<aside class="direct" aria-label="Direct channels">
<p class="direct__title">Direct Channels</p>
<ul class="direct__list">
@@ -140,7 +140,7 @@ const directLinks = [
const subject = subjectEl?.textContent?.trim() || 'Inquiry';
const message = val('cf-message');
const body = `${message}\n\n ${name}${from ? ` (${from})` : ''}`;
const body = `${message}\n\n- ${name}${from ? ` (${from})` : ''}`;
const href = `mailto:${email}?subject=${encodeURIComponent(
'[Portfolio] ' + subject,
)}&body=${encodeURIComponent(body)}`;
@@ -289,7 +289,7 @@ const directLinks = [
margin: 0;
}
/* ── RESPONSIVE ── */
/* RESPONSIVE */
@media (max-width: 820px) {
.layout { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
.direct { position: static; }
+15 -15
View File
@@ -13,7 +13,7 @@ import { featuredProjects } from '../data/projects';
const capabilities = [
{
label: 'Front-end',
body: 'Sleek and accessible interfaces built with Astro, TypeScript, and Sass all the way to an in-house design system.',
body: 'Sleek and accessible interfaces built with Astro, TypeScript, and Sass - all the way to an in-house design system.',
},
{
label: 'Back-end',
@@ -42,7 +42,7 @@ const instruments = [
---
<BaseLayout>
{/* ─────────────────────────── HERO ─────────────────────────── */}
{/* HERO */}
<section class="hero" aria-labelledby="hero-title">
<div class="hero__status">
<span class="hero__dot" aria-hidden="true"></span>
@@ -55,7 +55,7 @@ const instruments = [
</h1>
<p class="hero__lede">
I design and develop web applications from end to end from front-end
I design and develop web applications from end to end - from front-end
to back-end and production deployment. I am equally comfortable working
on the user interface as I am with the services and infrastructure running them.
</p>
@@ -79,7 +79,7 @@ const instruments = [
</div>
</section>
{/* ─────────────────────── PROFILE / CAPABILITIES ─────────────────────── */}
{/* PROFILE / CAPABILITIES */}
<section class="band" aria-labelledby="profile-title">
<Eyebrow index="01" label="Profile" />
<div class="band__split">
@@ -93,7 +93,7 @@ const instruments = [
on npm, a self-hosted infrastructure, and tools built with Rust and Python.
</p>
<p>
What drives me: mastering the entire cycle the component you look at,
What drives me: mastering the entire cycle - the component you look at,
the service that delivers it, and the CI that deploys it. This website is
a prime example, built entirely on top of my own design system.
</p>
@@ -112,7 +112,7 @@ const instruments = [
</ul>
</section>
{/* ─────────────────────────── FEATURED PROJECTS ─────────────────────────── */}
{/* FEATURED PROJECTS */}
<section class="band" aria-labelledby="proj-title">
<Eyebrow index="02" label="Featured projects" />
<div class="band__head">
@@ -126,7 +126,7 @@ const instruments = [
</div>
</section>
{/* ─────────────────────────── STACK ─────────────────────────── */}
{/* STACK */}
<section class="band" aria-labelledby="stack-title">
<Eyebrow index="03" label="Tech stack" />
<h2 id="stack-title" class="band__lead band__lead--center">
@@ -146,9 +146,9 @@ const instruments = [
</ul>
</section>
{/* ─────────────────────────── CONTACT CTA ─────────────────────────── */}
{/* CONTACT CTA */}
<section class="closing" aria-labelledby="cta-title">
<p class="closing__eyebrow">Available {site.availability}</p>
<p class="closing__eyebrow">Available - {site.availability}</p>
<h2 id="cta-title" class="closing__title">Let's talk!</h2>
<p class="closing__text">
I am looking for a full-stack permanent position (CDI) starting September 2026.
@@ -170,7 +170,7 @@ const instruments = [
<style lang="scss">
@use 'styles/type' as *;
/* ── HERO ── */
/* HERO */
.hero {
min-height: clamp(520px, 80vh, 820px);
display: flex;
@@ -247,7 +247,7 @@ const instruments = [
font-size: 0.75rem;
}
/* ── SECTION BANDS ── */
/* SECTION BANDS */
.band { padding: var(--nds-spacing-2xl) 0; }
.band__lead {
@include text-3xl;
@@ -295,7 +295,7 @@ const instruments = [
&:hover { color: var(--nds-accent); }
}
/* ── Capabilities ── */
/* Capabilities */
.caps {
list-style: none;
margin: var(--nds-spacing-2xl) 0 0;
@@ -330,7 +330,7 @@ const instruments = [
gap: var(--nds-spacing-lg);
}
/* ── INSTRUMENTS ── */
/* INSTRUMENTS */
.instruments {
list-style: none;
margin: 0;
@@ -364,7 +364,7 @@ const instruments = [
color: var(--nds-neutral);
}
/* ── CLOSING ── */
/* CLOSING */
.closing {
text-align: center;
padding: var(--nds-spacing-3xl) 0;
@@ -403,7 +403,7 @@ const instruments = [
.hero__dot { animation: none; }
}
/* ── RESPONSIVE ── */
/* RESPONSIVE */
@media (max-width: 900px) {
.band__split { grid-template-columns: 1fr; gap: var(--nds-spacing-lg); }
.caps { grid-template-columns: 1fr; }
+3 -3
View File
@@ -24,7 +24,7 @@ interface Props {
}
const { project } = Astro.props;
const status = statusMeta[project.status];
const title = `${project.title} Louis Potevin`;
const title = `${project.title} - Louis Potevin`;
---
<BaseLayout title={title} description={project.tagline}>
@@ -115,7 +115,7 @@ const title = `${project.title} — Louis Potevin`;
project.links.length === 0 && (
<div class="panel panel--muted">
<p class="panel__note">
Internal or private project no public link available.
Internal or private project - no public link available.
</p>
</div>
)
@@ -297,7 +297,7 @@ const title = `${project.title} — Louis Potevin`;
border-top: var(--nds-border-width-thin) solid var(--nds-border);
}
/* ── RESPONSIVE ── */
/* RESPONSIVE */
@media (max-width: 820px) {
.detail__grid { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
.detail__side { position: static; }
+2 -2
View File
@@ -5,7 +5,7 @@ import ProjectCard from '../../components/ProjectCard.astro';
import { Breadcrumb, BreadcrumbItem } from '@unkn0wndo3s/nova-design-system';
import { projects } from '../../data/projects';
const title = 'Projects Louis Potevin, full-stack web developer / développeur full-stack';
const title = 'Projects - Louis Potevin, full-stack web developer / développeur full-stack';
const description =
'Projects by Louis Potevin: Astro design system, self-hosted infrastructure, systems tools in Rust, and LLM explorations. Full-stack web developer.';
---
@@ -20,7 +20,7 @@ const description =
<Eyebrow index="//" label="All projects" />
<h1 class="head__title">Projects</h1>
<p class="head__lede">
A showcase of everything I build from business software running in production to
A showcase of everything I build - from business software running in production to
the infrastructure supporting it, alongside my personal tools.
</p>
</section>