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:
@@ -1,6 +1,6 @@
|
|||||||
# louis-potevin.dev
|
# 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
|
Static multi-page website built with **Astro + TypeScript + Sass**, styled
|
||||||
**exclusively** by the in-house design system **`@unkn0wndo3s/nova-design-system`**
|
**exclusively** by the in-house design system **`@unkn0wndo3s/nova-design-system`**
|
||||||
(tokens, components), featuring a 3D space background controlled via scrolling.
|
(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
|
All modifiable content is centralized. Values to be confirmed are
|
||||||
marked with `TODO` in the code :
|
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 :
|
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.
|
Chaque entrée : résumé, contexte, contributions, stack, liens.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"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": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
// Ambient space background: starfield, subtle nebula, and a slow-drifting,
|
// Ambient space background: starfield, subtle nebula, and a slow-drifting,
|
||||||
// slow-rotating asteroid. No scroll binding, no engine flames.
|
// slow-rotating asteroid. No scroll binding, no engine flames.
|
||||||
// Slight pointer parallax. Respects prefers-reduced-motion.
|
// Slight pointer parallax. Respects prefers-reduced-motion.
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export interface SceneHandle {
|
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;
|
setImpact(value: number): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
@@ -64,7 +62,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
|
|||||||
);
|
);
|
||||||
camera.position.set(0, 0, 18);
|
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);
|
const ambient = new THREE.AmbientLight(0x16323a, 1.0);
|
||||||
scene.add(ambient);
|
scene.add(ambient);
|
||||||
const key = new THREE.DirectionalLight(COLORS.starCool, 0.9);
|
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);
|
rimLight.position.set(8, -3, 4);
|
||||||
scene.add(rimLight);
|
scene.add(rimLight);
|
||||||
|
|
||||||
// ── Starfield ──────────────────────────────────────────────────────────────
|
// Starfield
|
||||||
const STAR_COUNT = prefersReduced ? 700 : 1700;
|
const STAR_COUNT = prefersReduced ? 700 : 1700;
|
||||||
const starGeo = new THREE.BufferGeometry();
|
const starGeo = new THREE.BufferGeometry();
|
||||||
const starPos = new Float32Array(STAR_COUNT * 3);
|
const starPos = new Float32Array(STAR_COUNT * 3);
|
||||||
@@ -106,7 +104,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
|
|||||||
const stars = new THREE.Points(starGeo, starMat);
|
const stars = new THREE.Points(starGeo, starMat);
|
||||||
scene.add(stars);
|
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 nebula = new THREE.Group();
|
||||||
const nebTex = radialTexture('rgba(40,129,129,0.55)');
|
const nebTex = radialTexture('rgba(40,129,129,0.55)');
|
||||||
[
|
[
|
||||||
@@ -129,7 +127,7 @@ export function initScene(canvas: HTMLCanvasElement): SceneHandle {
|
|||||||
});
|
});
|
||||||
scene.add(nebula);
|
scene.add(nebula);
|
||||||
|
|
||||||
// ── Asteroid (cold rock, subtle halo) ──────────────────────────────────────
|
// Asteroid (cold rock, subtle halo)
|
||||||
const asteroid = new THREE.Group();
|
const asteroid = new THREE.Group();
|
||||||
const rockGeo = new THREE.IcosahedronGeometry(1.9, 2);
|
const rockGeo = new THREE.IcosahedronGeometry(1.9, 2);
|
||||||
const pos = rockGeo.attributes.position as THREE.BufferAttribute;
|
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);
|
asteroid.position.set(6.5, 4.5, -2);
|
||||||
scene.add(asteroid);
|
scene.add(asteroid);
|
||||||
|
|
||||||
// ── State & loop ───────────────────────────────────────────────────────────
|
// State & loop
|
||||||
const pointer = { x: 0, y: 0, tx: 0, ty: 0 };
|
const pointer = { x: 0, y: 0, tx: 0, ty: 0 };
|
||||||
let running = true;
|
let running = true;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// Projects - real content. Edit freely.
|
||||||
// Projects — real content. Edit freely.
|
|
||||||
// `icon` matches a simple-icons-astro component (using PascalCase elsewhere).
|
// `icon` matches a simple-icons-astro component (using PascalCase elsewhere).
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export type ProjectStatus = 'live' | 'maintenu' | 'archive' | 'r&d';
|
export type ProjectStatus = 'live' | 'maintenu' | 'archive' | 'r&d';
|
||||||
|
|
||||||
@@ -32,8 +30,8 @@ export const projects: Project[] = [
|
|||||||
slug: 'nova-design-system',
|
slug: 'nova-design-system',
|
||||||
designation: '01',
|
designation: '01',
|
||||||
title: 'Nova Design System',
|
title: 'Nova Design System',
|
||||||
tagline: 'Astro component library — tokens, accessibility, npm distribution.',
|
tagline: 'Astro component library - tokens, accessibility, npm distribution.',
|
||||||
period: '2026 — ongoing',
|
period: '2026 - ongoing',
|
||||||
role: 'Author · maintainer',
|
role: 'Author · maintainer',
|
||||||
status: 'maintenu',
|
status: 'maintenu',
|
||||||
featured: true,
|
featured: true,
|
||||||
@@ -65,7 +63,7 @@ export const projects: Project[] = [
|
|||||||
designation: '02',
|
designation: '02',
|
||||||
title: 'Self-hosted Infrastructure & Deployment',
|
title: 'Self-hosted Infrastructure & Deployment',
|
||||||
tagline: 'Containerized services, CI/CD, and secured ingress on a fully controlled infrastructure.',
|
tagline: 'Containerized services, CI/CD, and secured ingress on a fully controlled infrastructure.',
|
||||||
period: '2025 — ongoing',
|
period: '2025 - ongoing',
|
||||||
role: 'Design & operations',
|
role: 'Design & operations',
|
||||||
status: 'live',
|
status: 'live',
|
||||||
featured: true,
|
featured: true,
|
||||||
@@ -120,9 +118,9 @@ export const projects: Project[] = [
|
|||||||
{
|
{
|
||||||
slug: 'rust-file-organizer',
|
slug: 'rust-file-organizer',
|
||||||
designation: '04',
|
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.',
|
tagline: 'File sorting tool, currently being ported from Java to Rust.',
|
||||||
period: '2025 — ongoing',
|
period: '2025 - ongoing',
|
||||||
role: 'Development',
|
role: 'Development',
|
||||||
status: 'r&d',
|
status: 'r&d',
|
||||||
featured: false,
|
featured: false,
|
||||||
@@ -147,7 +145,7 @@ export const projects: Project[] = [
|
|||||||
designation: '05',
|
designation: '05',
|
||||||
title: 'LLM Training & Inference',
|
title: 'LLM Training & Inference',
|
||||||
tagline: 'Small language model trained on a Reddit conversation dataset.',
|
tagline: 'Small language model trained on a Reddit conversation dataset.',
|
||||||
period: '2025 — ongoing',
|
period: '2025 - ongoing',
|
||||||
role: 'Development',
|
role: 'Development',
|
||||||
status: 'r&d',
|
status: 'r&d',
|
||||||
featured: false,
|
featured: false,
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ export const site = {
|
|||||||
domain: "louis-potevin.dev",
|
domain: "louis-potevin.dev",
|
||||||
location: "Limoges · Nouvelle-Aquitaine",
|
location: "Limoges · Nouvelle-Aquitaine",
|
||||||
// Job Search
|
// Job Search
|
||||||
availability: "Permanent contract (CDI) — September 2026",
|
availability: "Permanent contract (CDI) - September 2026",
|
||||||
mobility: "Anywhere in France, on-site or remote",
|
mobility: "Anywhere in France, on-site or remote",
|
||||||
// Contact
|
// Contact
|
||||||
email: "contact@louis-potevin.dev",
|
email: "contact@louis-potevin.dev",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
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.",
|
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 = [],
|
keywords = [],
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
@@ -91,7 +91,7 @@ const personLd = {
|
|||||||
const siteLd = {
|
const siteLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'WebSite',
|
'@type': 'WebSite',
|
||||||
name: `${site.name} — Portfolio`,
|
name: `${site.name} - Portfolio`,
|
||||||
url: Astro.site?.href ?? 'https://louis-potevin.dev',
|
url: Astro.site?.href ?? 'https://louis-potevin.dev',
|
||||||
inLanguage: ['en-US', 'fr-FR'],
|
inLanguage: ['en-US', 'fr-FR'],
|
||||||
};
|
};
|
||||||
@@ -113,7 +113,7 @@ const siteLd = {
|
|||||||
<link rel="canonical" href={canonical} />
|
<link rel="canonical" href={canonical} />
|
||||||
|
|
||||||
<meta property="og:type" content="website" />
|
<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" content="en_US" />
|
||||||
<meta property="og:locale:alternate" content="fr_FR" />
|
<meta property="og:locale:alternate" content="fr_FR" />
|
||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title} />
|
||||||
|
|||||||
@@ -16,23 +16,23 @@ import {
|
|||||||
import { Send, Mail } from '@lucide/astro';
|
import { Send, Mail } from '@lucide/astro';
|
||||||
import { site } from '../data/site';
|
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 =
|
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.";
|
"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 = [
|
const parcours = [
|
||||||
{
|
{
|
||||||
year: '2024 — 2026',
|
year: '2024 - 2026',
|
||||||
title: 'Professional Experience · Legrand France',
|
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.",
|
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',
|
year: '2024 - 2026',
|
||||||
title: 'BUT MMI — DWDI Track · IUT du Limousin',
|
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.",
|
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',
|
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.",
|
body: "Designing an open-source design system published on npm, managing a self-hosted server infrastructure, and building system tools with Rust and Python.",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { Send, Mail } from '@lucide/astro';
|
|||||||
import BrandLinkedin from '../components/BrandLinkedin.astro';
|
import BrandLinkedin from '../components/BrandLinkedin.astro';
|
||||||
import { site } from '../data/site';
|
import { site } from '../data/site';
|
||||||
|
|
||||||
const title = 'Contact — Louis Potevin';
|
const title = 'Contact - Louis Potevin';
|
||||||
const description =
|
const description =
|
||||||
'Contact Louis Potevin, full-stack web developer. Available for permanent positions (CDI) starting September 2026, anywhere in France or remote.';
|
'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>
|
</section>
|
||||||
|
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
{/* ── Form (Nova components) ── */}
|
{/* Form (Nova components) */}
|
||||||
<section class="formWrap" aria-labelledby="form-title">
|
<section class="formWrap" aria-labelledby="form-title">
|
||||||
<h2 id="form-title" class="formWrap__title">Message</h2>
|
<h2 id="form-title" class="formWrap__title">Message</h2>
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ const directLinks = [
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Direct Links ── */}
|
{/* Direct Links */}
|
||||||
<aside class="direct" aria-label="Direct channels">
|
<aside class="direct" aria-label="Direct channels">
|
||||||
<p class="direct__title">Direct Channels</p>
|
<p class="direct__title">Direct Channels</p>
|
||||||
<ul class="direct__list">
|
<ul class="direct__list">
|
||||||
@@ -140,7 +140,7 @@ const directLinks = [
|
|||||||
const subject = subjectEl?.textContent?.trim() || 'Inquiry';
|
const subject = subjectEl?.textContent?.trim() || 'Inquiry';
|
||||||
const message = val('cf-message');
|
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(
|
const href = `mailto:${email}?subject=${encodeURIComponent(
|
||||||
'[Portfolio] ' + subject,
|
'[Portfolio] ' + subject,
|
||||||
)}&body=${encodeURIComponent(body)}`;
|
)}&body=${encodeURIComponent(body)}`;
|
||||||
@@ -289,7 +289,7 @@ const directLinks = [
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── RESPONSIVE ── */
|
/* RESPONSIVE */
|
||||||
@media (max-width: 820px) {
|
@media (max-width: 820px) {
|
||||||
.layout { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
|
.layout { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
|
||||||
.direct { position: static; }
|
.direct { position: static; }
|
||||||
|
|||||||
+15
-15
@@ -13,7 +13,7 @@ import { featuredProjects } from '../data/projects';
|
|||||||
const capabilities = [
|
const capabilities = [
|
||||||
{
|
{
|
||||||
label: 'Front-end',
|
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',
|
label: 'Back-end',
|
||||||
@@ -42,7 +42,7 @@ const instruments = [
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
{/* ─────────────────────────── HERO ─────────────────────────── */}
|
{/* HERO */}
|
||||||
<section class="hero" aria-labelledby="hero-title">
|
<section class="hero" aria-labelledby="hero-title">
|
||||||
<div class="hero__status">
|
<div class="hero__status">
|
||||||
<span class="hero__dot" aria-hidden="true"></span>
|
<span class="hero__dot" aria-hidden="true"></span>
|
||||||
@@ -55,7 +55,7 @@ const instruments = [
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="hero__lede">
|
<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
|
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.
|
on the user interface as I am with the services and infrastructure running them.
|
||||||
</p>
|
</p>
|
||||||
@@ -79,7 +79,7 @@ const instruments = [
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─────────────────────── PROFILE / CAPABILITIES ─────────────────────── */}
|
{/* PROFILE / CAPABILITIES */}
|
||||||
<section class="band" aria-labelledby="profile-title">
|
<section class="band" aria-labelledby="profile-title">
|
||||||
<Eyebrow index="01" label="Profile" />
|
<Eyebrow index="01" label="Profile" />
|
||||||
<div class="band__split">
|
<div class="band__split">
|
||||||
@@ -93,7 +93,7 @@ const instruments = [
|
|||||||
on npm, a self-hosted infrastructure, and tools built with Rust and Python.
|
on npm, a self-hosted infrastructure, and tools built with Rust and Python.
|
||||||
</p>
|
</p>
|
||||||
<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
|
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.
|
a prime example, built entirely on top of my own design system.
|
||||||
</p>
|
</p>
|
||||||
@@ -112,7 +112,7 @@ const instruments = [
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─────────────────────────── FEATURED PROJECTS ─────────────────────────── */}
|
{/* FEATURED PROJECTS */}
|
||||||
<section class="band" aria-labelledby="proj-title">
|
<section class="band" aria-labelledby="proj-title">
|
||||||
<Eyebrow index="02" label="Featured projects" />
|
<Eyebrow index="02" label="Featured projects" />
|
||||||
<div class="band__head">
|
<div class="band__head">
|
||||||
@@ -126,7 +126,7 @@ const instruments = [
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─────────────────────────── STACK ─────────────────────────── */}
|
{/* STACK */}
|
||||||
<section class="band" aria-labelledby="stack-title">
|
<section class="band" aria-labelledby="stack-title">
|
||||||
<Eyebrow index="03" label="Tech stack" />
|
<Eyebrow index="03" label="Tech stack" />
|
||||||
<h2 id="stack-title" class="band__lead band__lead--center">
|
<h2 id="stack-title" class="band__lead band__lead--center">
|
||||||
@@ -146,9 +146,9 @@ const instruments = [
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ─────────────────────────── CONTACT CTA ─────────────────────────── */}
|
{/* CONTACT CTA */}
|
||||||
<section class="closing" aria-labelledby="cta-title">
|
<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>
|
<h2 id="cta-title" class="closing__title">Let's talk!</h2>
|
||||||
<p class="closing__text">
|
<p class="closing__text">
|
||||||
I am looking for a full-stack permanent position (CDI) starting September 2026.
|
I am looking for a full-stack permanent position (CDI) starting September 2026.
|
||||||
@@ -170,7 +170,7 @@ const instruments = [
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use 'styles/type' as *;
|
@use 'styles/type' as *;
|
||||||
|
|
||||||
/* ── HERO ── */
|
/* HERO */
|
||||||
.hero {
|
.hero {
|
||||||
min-height: clamp(520px, 80vh, 820px);
|
min-height: clamp(520px, 80vh, 820px);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -247,7 +247,7 @@ const instruments = [
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── SECTION BANDS ── */
|
/* SECTION BANDS */
|
||||||
.band { padding: var(--nds-spacing-2xl) 0; }
|
.band { padding: var(--nds-spacing-2xl) 0; }
|
||||||
.band__lead {
|
.band__lead {
|
||||||
@include text-3xl;
|
@include text-3xl;
|
||||||
@@ -295,7 +295,7 @@ const instruments = [
|
|||||||
&:hover { color: var(--nds-accent); }
|
&:hover { color: var(--nds-accent); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Capabilities ── */
|
/* Capabilities */
|
||||||
.caps {
|
.caps {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: var(--nds-spacing-2xl) 0 0;
|
margin: var(--nds-spacing-2xl) 0 0;
|
||||||
@@ -330,7 +330,7 @@ const instruments = [
|
|||||||
gap: var(--nds-spacing-lg);
|
gap: var(--nds-spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── INSTRUMENTS ── */
|
/* INSTRUMENTS */
|
||||||
.instruments {
|
.instruments {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -364,7 +364,7 @@ const instruments = [
|
|||||||
color: var(--nds-neutral);
|
color: var(--nds-neutral);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── CLOSING ── */
|
/* CLOSING */
|
||||||
.closing {
|
.closing {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: var(--nds-spacing-3xl) 0;
|
padding: var(--nds-spacing-3xl) 0;
|
||||||
@@ -403,7 +403,7 @@ const instruments = [
|
|||||||
.hero__dot { animation: none; }
|
.hero__dot { animation: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── RESPONSIVE ── */
|
/* RESPONSIVE */
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.band__split { grid-template-columns: 1fr; gap: var(--nds-spacing-lg); }
|
.band__split { grid-template-columns: 1fr; gap: var(--nds-spacing-lg); }
|
||||||
.caps { grid-template-columns: 1fr; }
|
.caps { grid-template-columns: 1fr; }
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const { project } = Astro.props;
|
const { project } = Astro.props;
|
||||||
const status = statusMeta[project.status];
|
const status = statusMeta[project.status];
|
||||||
const title = `${project.title} — Louis Potevin`;
|
const title = `${project.title} - Louis Potevin`;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={title} description={project.tagline}>
|
<BaseLayout title={title} description={project.tagline}>
|
||||||
@@ -115,7 +115,7 @@ const title = `${project.title} — Louis Potevin`;
|
|||||||
project.links.length === 0 && (
|
project.links.length === 0 && (
|
||||||
<div class="panel panel--muted">
|
<div class="panel panel--muted">
|
||||||
<p class="panel__note">
|
<p class="panel__note">
|
||||||
Internal or private project — no public link available.
|
Internal or private project - no public link available.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -297,7 +297,7 @@ const title = `${project.title} — Louis Potevin`;
|
|||||||
border-top: var(--nds-border-width-thin) solid var(--nds-border);
|
border-top: var(--nds-border-width-thin) solid var(--nds-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── RESPONSIVE ── */
|
/* RESPONSIVE */
|
||||||
@media (max-width: 820px) {
|
@media (max-width: 820px) {
|
||||||
.detail__grid { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
|
.detail__grid { grid-template-columns: 1fr; gap: var(--nds-spacing-xl); }
|
||||||
.detail__side { position: static; }
|
.detail__side { position: static; }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ProjectCard from '../../components/ProjectCard.astro';
|
|||||||
import { Breadcrumb, BreadcrumbItem } from '@unkn0wndo3s/nova-design-system';
|
import { Breadcrumb, BreadcrumbItem } from '@unkn0wndo3s/nova-design-system';
|
||||||
import { projects } from '../../data/projects';
|
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 =
|
const description =
|
||||||
'Projects by Louis Potevin: Astro design system, self-hosted infrastructure, systems tools in Rust, and LLM explorations. Full-stack web developer.';
|
'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" />
|
<Eyebrow index="//" label="All projects" />
|
||||||
<h1 class="head__title">Projects</h1>
|
<h1 class="head__title">Projects</h1>
|
||||||
<p class="head__lede">
|
<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.
|
the infrastructure supporting it, alongside my personal tools.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user