Site personnel courcellematthi.eu

This commit is contained in:
lrdlg
2026-08-05 22:40:03 +02:00
commit df12d21165
39 changed files with 4503 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
/* ==========================================================================
base.css — Reset moderne et styles des éléments HTML natifs
--------------------------------------------------------------------------
Uniquement des sélecteurs d'éléments, aucune classe. Objectif : un HTML
sémantique écrit sans classe doit déjà être présentable et accessible.
========================================================================== */
@layer base {
/* ======================================================================
RESET
Reset ciblé plutôt que « * { margin:0; padding:0 } » aveugle : on
neutralise ce qui gêne, on garde ce qui est utile (styles de listes,
comportements natifs des formulaires).
====================================================================== */
*,
*::before,
*::after {
box-sizing: border-box; /* La largeur inclut padding et bordure */
}
/* Suppression des marges par défaut : elles varient d'un navigateur à
l'autre et provoquent des fusions de marges imprévisibles. Les
espacements sont pilotés explicitement par les composants. */
body, h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd {
margin-block: 0;
}
html {
/* Empêche iOS d'agrandir le texte en paysage sans demande explicite */
-webkit-text-size-adjust: 100%;
/* Défilement fluide pour les ancres — neutralisé plus bas si
l'utilisateur a demandé la réduction des animations */
scroll-behavior: smooth;
/* Décale la cible des ancres pour qu'elle ne passe pas sous
l'en-tête collant. Indispensable dès qu'un header est sticky. */
scroll-padding-block-start: calc(var(--header-height) + var(--space-md));
}
body {
min-block-size: 100svh; /* svh : hauteur stable malgré la barre d'URL mobile */
background-color: var(--color-bg);
color: var(--color-ink);
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: var(--leading-normal);
/* Lissage : rend Inter plus fidèle sur écrans macOS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Ligatures et crénage activés pour le texte courant */
text-rendering: optimizeLegibility;
font-kerning: normal;
}
/* ======================================================================
MÉDIAS
====================================================================== */
img, picture, svg, video, canvas {
display: block; /* Supprime l'espace fantôme sous les images
(dû à l'alignement sur la ligne de base) */
max-inline-size: 100%; /* Jamais de débordement horizontal */
block-size: auto; /* Préserve le rapport d'aspect */
}
/* ======================================================================
TYPOGRAPHIE
====================================================================== */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-mono);
font-weight: var(--weight-bold);
line-height: var(--leading-tight);
letter-spacing: -0.02em; /* Compense l'aération naturelle du monospace
aux grandes tailles */
text-wrap: balance; /* Répartit les mots sur des lignes de longueur
comparable — évite le mot orphelin */
}
p, li, dd, figcaption {
text-wrap: pretty; /* Évite les orphelins en fin de paragraphe */
}
/* Les listes porteuses de role="list" sont des listes sémantiques dont on
ne veut pas la puce native. Le rôle explicite est nécessaire : Safari
retire la sémantique de liste quand list-style vaut none. */
ul[role="list"],
ol[role="list"] {
list-style: none;
padding-inline-start: 0;
}
/* Texte mis en avant */
strong, b { font-weight: var(--weight-semibold); }
/* Abréviations : curseur d'aide + soulignement pointillé */
abbr[title] {
text-decoration: underline dotted;
cursor: help;
}
/* ======================================================================
LIENS
====================================================================== */
a {
color: var(--color-accent-dark);
text-decoration-thickness: 1px;
text-underline-offset: 0.2em; /* Le soulignement ne coupe pas les jambages */
transition: color var(--transition-fast);
}
a:hover { color: var(--color-accent); }
/* ======================================================================
ACCESSIBILITÉ
====================================================================== */
/* :focus-visible plutôt que :focus — l'anneau apparaît à la navigation
clavier mais pas au clic souris. Ne jamais faire « outline: none »
sans remplacement visible : c'est la faute d'accessibilité la plus
répandue sur le web. */
:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 3px;
border-radius: var(--radius-sm);
}
/* Marge lors du saut vers une ancre (complément de scroll-padding) */
:target {
scroll-margin-block-start: calc(var(--header-height) + var(--space-md));
}
/* ======================================================================
FORMULAIRES ET BOUTONS
====================================================================== */
button, input, select, textarea {
font: inherit; /* Les navigateurs n'héritent pas la police par défaut */
color: inherit;
}
button {
background: none;
border: none;
cursor: pointer;
}
/* ======================================================================
PRÉFÉRENCES SYSTÈME
Respecter les réglages d'accessibilité de l'utilisateur n'est pas
optionnel : les animations peuvent déclencher nausées et migraines
chez les personnes sensibles aux troubles vestibulaires.
====================================================================== */
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Mode contraste élevé (Windows, préférence système) */
@media (prefers-contrast: more) {
:root {
--color-ink-faint: #4c5a56;
--color-line: #9aa5a2;
--color-line-strong: #6b7674;
}
}
}
+455
View File
@@ -0,0 +1,455 @@
/* ==========================================================================
components.css — Blocs réutilisables
--------------------------------------------------------------------------
Nomenclature BEM : .bloc, .bloc__element, .bloc--modificateur.
Intérêt : la spécificité reste plate (une classe = 0,1,0), le nom dit
à quoi appartient l'élément, et aucun style ne fuit hors de son bloc.
========================================================================== */
@layer components {
/* ======================================================================
EYEBROW — étiquette au-dessus d'un titre
====================================================================== */
.eyebrow {
display: block;
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.04em;
color: var(--color-accent-dark);
margin-block-end: var(--space-md);
}
/* Le prompt de terminal est purement décoratif : généré en CSS, il
n'est donc pas lu par les lecteurs d'écran ni copié avec le texte. */
.eyebrow::before {
content: ">_ ";
color: var(--color-accent);
}
/* ======================================================================
TITRES
====================================================================== */
.page-title {
font-size: var(--text-2xl);
letter-spacing: -0.03em;
margin-block-end: var(--space-sm);
}
.page-subtitle {
font-family: var(--font-mono);
font-size: var(--text-md);
font-weight: var(--weight-regular);
line-height: var(--leading-snug);
color: var(--color-ink-soft);
max-inline-size: 40ch;
margin-block-end: var(--space-lg);
}
.section-title {
font-size: var(--text-xl);
margin-block-end: var(--space-xl);
scroll-margin-block-start: calc(var(--header-height) + var(--space-lg));
}
.section-title::before {
content: ">_ ";
color: var(--color-accent);
}
/* ======================================================================
LISTE DE CONTACTS
====================================================================== */
.contact-list {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs) var(--space-lg);
list-style: none;
margin: 0;
padding: 0;
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-faint);
}
.contact-list li {
display: inline-flex;
align-items: center;
gap: var(--space-2xs);
}
.contact-list__icon {
inline-size: 0.95rem;
block-size: 0.95rem;
stroke: var(--color-accent);
stroke-width: 1.8;
fill: none;
flex-shrink: 0;
}
/* ======================================================================
BOUTONS D'ACTION
Deux niveaux seulement : principal (plein) et secondaire (contour).
Au-delà, la hiérarchie visuelle devient illisible.
====================================================================== */
.actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-sm);
margin-block-start: var(--space-lg);
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
min-block-size: var(--tap-target);
padding-inline: var(--space-md);
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: var(--weight-medium);
text-decoration: none;
border-radius: var(--radius-md);
border: 1px solid transparent;
transition: background-color var(--transition-fast),
border-color var(--transition-fast),
color var(--transition-fast);
}
.button__icon {
inline-size: 1rem;
block-size: 1rem;
stroke: currentcolor;
stroke-width: 1.9;
fill: none;
flex-shrink: 0;
}
.button--primary {
background-color: var(--color-accent-dark);
color: var(--color-ink-inverse);
}
.button--primary:hover {
background-color: var(--color-ink);
color: var(--color-ink-inverse);
}
.button--secondary {
background-color: var(--color-surface);
border-color: var(--color-line-strong);
color: var(--color-ink-soft);
}
.button--secondary:hover {
border-color: var(--color-accent);
color: var(--color-accent-dark);
}
/* ======================================================================
INTRODUCTION
====================================================================== */
.intro { padding-block: var(--space-2xl) var(--space-xl); }
.intro__heading {
font-size: var(--text-lg);
line-height: var(--leading-snug);
max-inline-size: 34ch;
margin-block-end: var(--space-lg);
}
.intro__text {
color: var(--color-ink-soft);
max-inline-size: var(--width-prose);
margin-block-end: var(--space-md);
}
.intro__text strong {
color: var(--color-ink);
font-weight: var(--weight-semibold);
}
/* Dernier paragraphe mis en exergue par un filet latéral plutôt que par
un fond coloré : plus sobre, et imprimable sans consommer d'encre. */
.intro__text--highlight {
border-inline-start: 3px solid var(--color-accent);
padding-inline-start: var(--space-lg);
color: var(--color-ink);
}
/* ======================================================================
ENTRÉE DE PARCOURS (timeline)
====================================================================== */
.entry {
position: relative;
padding-inline-start: var(--space-lg);
padding-block-end: var(--space-xl);
border-inline-start: 2px solid var(--color-line);
}
/* La dernière entrée ne prolonge pas le trait dans le vide */
.entry:last-child {
padding-block-end: 0;
border-inline-start-color: transparent;
}
/* Pastille de jalon. Décorative : aucune information n'en dépend. */
.entry::before {
content: "";
position: absolute;
inset-inline-start: -7px;
inset-block-start: 0.55rem;
inline-size: 12px;
block-size: 12px;
border-radius: 50%;
background-color: var(--color-bg);
border: 2px solid var(--color-accent);
}
.entry__date {
display: block;
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: var(--weight-medium);
color: var(--color-accent-dark);
margin-block-end: var(--space-2xs);
}
.entry__role {
font-size: var(--text-md);
margin-block-end: var(--space-2xs);
}
.entry__org {
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-faint);
margin-block-end: var(--space-md);
}
.entry__note {
color: var(--color-ink-soft);
font-size: var(--text-sm);
max-inline-size: var(--width-prose);
}
/* ======================================================================
FICHE DE COURS
====================================================================== */
.course {
background-color: var(--color-surface);
border: 1px solid var(--color-line);
border-radius: var(--radius-md);
padding: var(--space-md);
margin-block-end: var(--space-xs);
transition: border-color var(--transition-base);
}
.course:last-child { margin-block-end: 0; }
.course:hover { border-color: var(--color-line-strong); }
.course__name {
display: block;
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: var(--weight-bold);
margin-block-end: var(--space-2xs);
}
.course__name::before {
content: "▪ ";
color: var(--color-accent);
}
.course__desc {
font-size: var(--text-sm);
color: var(--color-ink-soft);
line-height: 1.65;
}
/* ======================================================================
LISTE À PUCES PERSONNALISÉE
Puce en ::before plutôt que list-style-image : couleur pilotable par
variable, alignement contrôlé au pixel.
====================================================================== */
.marker-list {
list-style: none;
margin: 0;
padding: 0;
color: var(--color-ink-soft);
font-size: var(--text-sm);
}
.marker-list li {
position: relative;
padding-inline-start: var(--space-md);
margin-block-end: var(--space-xs);
}
.marker-list li:last-child { margin-block-end: 0; }
.marker-list li::before {
content: "▪";
position: absolute;
inset-inline-start: 0;
color: var(--color-accent);
}
/* ======================================================================
CARTE
====================================================================== */
.card {
background-color: var(--color-surface);
border: 1px solid var(--color-line);
border-radius: var(--radius-md);
padding: var(--space-lg);
box-shadow: var(--shadow-sm);
transition: border-color var(--transition-base),
transform var(--transition-base),
box-shadow var(--transition-base);
}
.card:hover {
border-color: var(--color-accent);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.card__title {
display: flex;
align-items: center;
gap: var(--space-xs);
font-size: var(--text-base);
margin-block-end: var(--space-sm);
}
.card__icon {
inline-size: 1.1rem;
block-size: 1.1rem;
stroke: var(--color-accent);
stroke-width: 1.8;
fill: none;
flex-shrink: 0;
}
/* ======================================================================
PROJET
====================================================================== */
.project {
background-color: var(--color-surface);
border: 1px solid var(--color-line);
border-radius: var(--radius-md);
padding: var(--space-lg);
margin-block-end: var(--space-sm);
transition: border-color var(--transition-base);
}
.project:last-child { margin-block-end: 0; }
.project:hover { border-color: var(--color-line-strong); }
.project__header {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: var(--space-2xs) var(--space-sm);
margin-block-end: var(--space-xs);
}
.project__title { font-size: var(--text-base); }
.project__desc {
color: var(--color-ink-soft);
font-size: var(--text-sm);
max-inline-size: var(--width-prose);
}
.project__desc + .project__desc { margin-block-start: var(--space-xs); }
.project__stack {
display: flex;
flex-wrap: wrap;
gap: var(--space-2xs);
list-style: none;
margin: var(--space-sm) 0 0;
padding: 0;
}
.project__stack li {
font-family: var(--font-mono);
font-size: var(--text-2xs);
color: var(--color-ink-faint);
background-color: var(--color-surface-alt);
border-radius: var(--radius-sm);
padding: 2px var(--space-xs);
}
/* ======================================================================
BADGE
====================================================================== */
.badge {
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: var(--weight-medium);
color: var(--color-accent-dark);
background-color: var(--color-accent-wash);
border: 1px solid var(--color-accent-border);
border-radius: var(--radius-sm);
padding: 2px var(--space-xs);
white-space: nowrap;
}
/* Variante neutre : signale un état non abouti sans l'emphase de l'accent */
.badge--muted {
color: var(--color-ink-faint);
background-color: var(--color-surface-alt);
border-color: var(--color-line-strong);
}
/* ======================================================================
FORMATION
====================================================================== */
.education {
border-inline-start: 2px solid var(--color-line);
padding-inline-start: var(--space-lg);
}
.education__degree {
font-size: var(--text-base);
margin-block-end: var(--space-2xs);
}
.education__school {
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-faint);
}
/* ======================================================================
BLOC DE CONTACT (appel à l'action de fin de page)
====================================================================== */
.contact-block {
background-color: var(--color-surface);
border: 1px solid var(--color-line);
border-radius: var(--radius-md);
padding: var(--space-xl);
}
.contact-block__text {
color: var(--color-ink-soft);
max-inline-size: var(--width-prose);
}
}
+330
View File
@@ -0,0 +1,330 @@
/* ==========================================================================
layout.css — Structure de page
--------------------------------------------------------------------------
Conteneurs, en-tête collant, navigation, sections, grilles, pied de page.
Ce fichier positionne ; components.css habille.
========================================================================== */
@layer layout {
/* ======================================================================
CONTENEUR
Un seul conteneur réutilisé partout : la largeur de contenu est décidée
à un seul endroit.
====================================================================== */
.container {
inline-size: 100%;
max-inline-size: var(--width-content);
margin-inline: auto;
padding-inline: var(--gutter);
}
/* ======================================================================
LIEN D'ÉVITEMENT
Premier élément focusable de la page. Permet à un utilisateur au
clavier ou au lecteur d'écran de sauter la navigation pour atteindre
directement le contenu. Critère WCAG 2.4.1 (Contourner des blocs).
Masqué visuellement hors focus, mais jamais display:none — sinon il
ne serait pas focusable.
====================================================================== */
.skip-link {
position: fixed;
inset-block-start: var(--space-xs);
inset-inline-start: var(--space-xs);
z-index: var(--z-skip-link);
padding: var(--space-xs) var(--space-md);
background-color: var(--color-ink);
color: var(--color-ink-inverse);
font-family: var(--font-mono);
font-size: var(--text-sm);
text-decoration: none;
border-radius: var(--radius-sm);
transform: translateY(-200%);
transition: transform var(--transition-fast);
}
.skip-link:focus {
transform: translateY(0);
color: var(--color-ink-inverse);
}
/* ======================================================================
EN-TÊTE COLLANT
====================================================================== */
.site-header {
position: sticky;
inset-block-start: 0;
z-index: var(--z-sticky);
background-color: var(--color-bg-translucent);
border-block-end: 1px solid var(--color-line);
/* Flou d'arrière-plan : le contenu qui défile dessous reste deviné
sans nuire à la lisibilité du menu. Dégradation propre si non
supporté (le fond translucide suffit). */
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.site-header__inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
min-block-size: var(--header-height);
}
.site-header__brand {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: var(--weight-bold);
color: var(--color-ink);
text-decoration: none;
letter-spacing: -0.01em;
white-space: nowrap;
}
.site-header__brand:hover { color: var(--color-ink); }
.site-header__logo {
inline-size: 1.6rem;
block-size: 1.6rem;
flex-shrink: 0;
}
/* ======================================================================
NAVIGATION PRINCIPALE
====================================================================== */
.site-nav__list {
display: flex;
align-items: center;
gap: var(--space-lg);
list-style: none;
margin: 0;
padding: 0;
}
.site-nav__link {
display: inline-block;
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-faint);
text-decoration: none;
padding-block: var(--space-2xs);
border-block-end: 2px solid transparent;
transition: color var(--transition-fast), border-color var(--transition-fast);
}
.site-nav__link:hover { color: var(--color-ink); }
/* Section courante. aria-current n'est pas qu'un sélecteur pratique :
il annonce la position aux lecteurs d'écran. Le repère visuel repose
sur la couleur ET sur une bordure — jamais sur la couleur seule
(WCAG 1.4.1, information non véhiculée par la couleur uniquement). */
.site-nav__link[aria-current="true"] {
color: var(--color-ink);
border-block-end-color: var(--color-accent);
}
/* ======================================================================
BOUTON MENU (mobile)
====================================================================== */
.nav-toggle {
display: none; /* Affiché uniquement sous le point de rupture */
align-items: center;
gap: var(--space-xs);
min-block-size: var(--tap-target);
padding-inline: var(--space-xs);
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-soft);
}
.nav-toggle__icon {
inline-size: 1.15rem;
block-size: 1.15rem;
stroke: currentcolor;
stroke-width: 2;
fill: none;
}
/* Les deux tracés de l'icône se transforment en croix à l'ouverture */
.nav-toggle__line {
transform-origin: center;
transition: transform var(--transition-base), opacity var(--transition-fast);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__line--top { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__line--middle { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__line--bottom { transform: translateY(-5px) rotate(-45deg); }
/* ======================================================================
RÉGIONS DE CONTENU
====================================================================== */
.masthead {
padding-block: var(--space-3xl) var(--space-2xl);
border-block-end: 1px solid var(--color-line);
}
.section {
padding-block: var(--space-3xl);
border-block-start: 1px solid var(--color-line);
}
/* ======================================================================
GRILLE AUTO-ADAPTATIVE
auto-fit + minmax : le nombre de colonnes s'ajuste seul à la largeur
disponible, sans media query. Le min() interne évite le débordement
sur les très petits écrans (une colonne ne peut pas être plus large
que son conteneur).
====================================================================== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(17.5rem, 100%), 1fr));
gap: var(--space-md);
}
.grid--wide {
grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
/* Empilement vertical régulier */
.stack > * + * { margin-block-start: var(--space-md); }
/* ======================================================================
PIED DE PAGE
====================================================================== */
.site-footer {
border-block-start: 1px solid var(--color-line);
padding-block: var(--space-xl) var(--space-2xl);
}
.site-footer__inner {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: var(--space-sm) var(--space-lg);
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink-faint);
}
.site-footer__links {
display: flex;
flex-wrap: wrap;
gap: var(--space-md);
list-style: none;
margin: 0;
padding: 0;
}
/* ======================================================================
BOUTON RETOUR EN HAUT
====================================================================== */
.to-top {
position: fixed;
inset-block-end: var(--space-lg);
inset-inline-end: var(--space-lg);
z-index: var(--z-overlay);
display: grid;
place-items: center;
inline-size: var(--tap-target);
block-size: var(--tap-target);
background-color: var(--color-surface);
border: 1px solid var(--color-line-strong);
border-radius: var(--radius-full);
box-shadow: var(--shadow-md);
color: var(--color-ink-soft);
/* Masqué par défaut : révélé par JS après défilement.
visibility (et non display) pour permettre la transition. */
opacity: 0;
visibility: hidden;
transform: translateY(0.5rem);
transition: opacity var(--transition-base),
transform var(--transition-base),
visibility var(--transition-base);
}
.to-top[data-visible="true"] {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.to-top:hover {
color: var(--color-accent-dark);
border-color: var(--color-accent);
}
/* ======================================================================
POINT DE RUPTURE MOBILE
Une seule media query pour toute la mise en page : le reste est fluide.
Écrite en syntaxe de plage (width <= 46rem), lisible et sans le
traditionnel « max-width: 45.999rem ».
====================================================================== */
@media (width <= 46rem) {
.nav-toggle { display: inline-flex; }
.site-header__inner { position: relative; }
/* Le menu devient un panneau déroulant sous l'en-tête */
.site-nav {
position: absolute;
inset-block-start: 100%;
inset-inline: calc(var(--gutter) * -1);
display: none;
background-color: var(--color-surface);
border-block-end: 1px solid var(--color-line);
box-shadow: var(--shadow-md);
}
.site-nav[data-open="true"] { display: block; }
.site-nav__list {
flex-direction: column;
align-items: stretch;
gap: 0;
padding: var(--space-xs) var(--gutter) var(--space-md);
}
.site-nav__link {
/* Hauteur de cible tactile conforme (44px minimum) */
display: flex;
align-items: center;
min-block-size: var(--tap-target);
padding-inline-start: var(--space-xs);
border-block-end: 1px solid var(--color-line);
border-inline-start: 2px solid transparent;
}
/* Le repère de section active passe du bas au côté */
.site-nav__link[aria-current="true"] {
border-block-end-color: var(--color-line);
border-inline-start-color: var(--color-accent);
background-color: var(--color-accent-glow);
}
.masthead { padding-block: var(--space-2xl) var(--space-xl); }
.section { padding-block: var(--space-2xl); }
.to-top {
inset-block-end: var(--space-md);
inset-inline-end: var(--space-md);
}
}
}
+225
View File
@@ -0,0 +1,225 @@
/* ==========================================================================
print.css — Feuille de style d'impression
--------------------------------------------------------------------------
Chargée via media="print" : elle n'est pas téléchargée en priorité pour
l'affichage écran et ne pénalise donc pas le rendu initial.
Enjeu réel : un CV est imprimé et exporté en PDF. Une page web imprimée
sans feuille dédiée sort avec la navigation, des fonds gris qui vident
les cartouches, des liens dont on ne voit pas la cible, et des blocs
coupés au milieu.
Ce fichier n'utilise pas @layer : chargé en media="print", il doit
surcharger toutes les couches sans discussion.
========================================================================== */
/* --------------------------------------------------------------------------
FORMAT DE PAGE
-------------------------------------------------------------------------- */
@page {
size: A4 portrait;
margin: 15mm 14mm;
}
/* Première page : marge haute réduite, le nom fait office d'en-tête */
@page :first {
margin-block-start: 12mm;
}
/* --------------------------------------------------------------------------
MASQUAGE DES ÉLÉMENTS D'INTERFACE
Rien de ce qui sert à naviguer n'a de sens sur papier.
-------------------------------------------------------------------------- */
.site-header,
.skip-link,
.nav-toggle,
.site-nav,
.to-top,
.actions,
.screen-only {
display: none !important;
}
/* Éléments réservés à l'impression */
.print-only { display: block !important; }
/* --------------------------------------------------------------------------
REMISE À PLAT DES COULEURS
Noir sur blanc : lisible sur toute imprimante, y compris monochrome.
-------------------------------------------------------------------------- */
body {
background: #fff;
color: #000;
font-size: 10.5pt;
line-height: 1.45;
}
.container {
max-inline-size: none;
padding-inline: 0;
}
/* --------------------------------------------------------------------------
GESTION DES SAUTS DE PAGE
Arbitrage important : on autorise une entrée de parcours (.entry) à se
couper entre deux pages, mais jamais une fiche individuelle (.course,
.project, .card).
Pourquoi ? Une entrée « Enseignement » contient quatre fiches de cours et
dépasse la demi-page. Si on interdit sa coupure, elle bascule en entier
sur la page suivante et laisse un vide de plusieurs centimètres. Coupée
entre deux fiches, elle reste parfaitement lisible et le document gagne
une à deux pages.
-------------------------------------------------------------------------- */
.course,
.project,
.card,
.education,
.contact-block {
break-inside: avoid;
}
/* Les titres ne restent jamais seuls en bas de page, séparés de leur contenu */
.section-title,
.entry__role,
.entry__org,
.project__title,
.card__title,
.education__degree {
break-after: avoid;
}
/* Une entrée ne démarre pas en toute fin de page pour n'y poser que sa date */
.entry__date {
break-after: avoid;
}
/* Limite les lignes isolées en haut ou en bas de page (veuves et orphelines) */
p, li {
orphans: 3;
widows: 3;
}
/* --------------------------------------------------------------------------
MISE EN PAGE
-------------------------------------------------------------------------- */
.masthead {
padding-block: 0 10pt;
border-block-end: 1pt solid #000;
}
.section {
padding-block: 10pt;
border-block-start: 0.5pt solid #ccc;
}
.intro { padding-block: 10pt; }
.intro__text--highlight {
border-inline-start: 2pt solid #666;
padding-inline-start: 8pt;
}
/* Grilles : deux colonnes fixes, plus compact que l'auto-fit écran */
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8pt;
}
/* --------------------------------------------------------------------------
CARTES ET FICHES
Fonds supprimés, ombres supprimées, bordures fines.
-------------------------------------------------------------------------- */
.course,
.project,
.card,
.contact-block {
background: transparent;
border: 0.5pt solid #bbb;
box-shadow: none;
padding: 6pt 8pt;
margin-block-end: 5pt;
transform: none;
}
.entry {
border-inline-start: 0.5pt solid #999;
padding-block-end: 10pt;
}
.entry::before {
background: #fff;
border-color: #000;
}
.education { border-inline-start: 0.5pt solid #999; }
/* --------------------------------------------------------------------------
ACCENTS EN NIVEAUX DE GRIS
Le vert #05BC8A imprimé en monochrome donne un gris très clair,
quasi invisible. On force le noir sur tous les éléments porteurs
d'information.
-------------------------------------------------------------------------- */
.eyebrow,
.eyebrow::before,
.entry__date,
.section-title::before,
.course__name::before,
.marker-list li::before {
color: #000;
}
.badge {
background: transparent;
border: 0.5pt solid #999;
color: #333;
}
.project__stack li {
background: transparent;
border: 0.5pt solid #ccc;
color: #444;
}
/* Icônes SVG inline */
.card__icon,
.contact-list__icon {
stroke: #000;
}
/* --------------------------------------------------------------------------
LIENS
Sur papier, un lien dont on ne voit pas la cible est inutile.
On affiche l'URL entre parenthèses — sauf pour mailto et les ancres
internes, où l'adresse est déjà visible ou sans objet.
-------------------------------------------------------------------------- */
a {
color: #000;
text-decoration: none;
}
.contact-list a[href^="http"]::after,
.site-footer a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 8pt;
color: #555;
word-break: break-all;
}
a[href^="#"]::after,
a[href^="mailto:"]::after,
a[href^="tel:"]::after {
content: "";
}
/* --------------------------------------------------------------------------
PIED DE PAGE
-------------------------------------------------------------------------- */
.site-footer {
border-block-start: 0.5pt solid #ccc;
padding-block: 6pt 0;
}
.site-footer__inner { font-size: 8pt; }
.site-footer__links { display: none; }
+146
View File
@@ -0,0 +1,146 @@
/* ==========================================================================
tokens.css — Jetons de conception (design tokens)
--------------------------------------------------------------------------
Source unique de vérité pour les couleurs, la typographie et les espacements.
Aucune règle de style ici : uniquement des variables CSS.
Pourquoi séparer ? Changer l'identité visuelle du site ne doit demander
la modification que d'un seul fichier. Toute valeur littérale (#05BC8A,
1.5rem…) écrite ailleurs que dans ce fichier est une dette technique.
========================================================================== */
/* --------------------------------------------------------------------------
ORDRE DE CASCADE
Déclaré ici en premier, avant toute règle. Une couche listée plus bas
l'emporte sur celles du dessus, quelle que soit la spécificité des
sélecteurs et quel que soit l'ordre des balises <link> dans le <head>.
Cela supprime la cause n°1 des conflits CSS : la course à la spécificité.
-------------------------------------------------------------------------- */
@layer tokens, base, layout, components, utilities;
@layer tokens {
:root {
/* ====================================================================
COULEURS
Palette volontairement restreinte. Contrastes vérifiés WCAG 2.1 :
- ink sur bg : 14,8:1 (AAA, seuil 7:1)
- ink-soft sur bg : 7,4:1 (AAA)
- ink-faint sur bg : 4,1:1 (AA sur texte non essentiel ≥ 14px)
- accent-dark sur bg : 4,3:1 (AA, utilisé pour les liens)
L'accent pur (#05BC8A) n'est jamais utilisé pour du texte sur fond
clair : son contraste (2,3:1) est insuffisant. Il sert uniquement de
couleur de trait, de bordure et de puce décorative.
==================================================================== */
/* ---- Surfaces ---- */
--color-bg: #f7f8f8; /* Fond de page */
--color-bg-translucent: rgb(247 248 248 / 85%); /* En-tête collant */
--color-surface: #ffffff; /* Cartes, fiches */
--color-surface-alt: #f1f3f3; /* Surfaces secondaires, badges neutres */
/* ---- Lignes ---- */
--color-line: #e3e6e6; /* Séparateurs, bordures de cartes */
--color-line-strong: #cfd4d4; /* Bordures nécessitant plus de présence */
/* ---- Texte ---- */
--color-ink: #10201c; /* Titres, texte principal */
--color-ink-soft: #4c5a56; /* Corps de texte, descriptions */
--color-ink-faint: #7b8783; /* Métadonnées, mentions secondaires */
--color-ink-inverse: #f7f8f8; /* Texte sur fond sombre */
/* ---- Accent : identité underscorelab ---- */
--color-accent: #05bc8a; /* Traits, puces, bordures */
--color-accent-dark: #03996f; /* Texte et liens (contraste AA) */
--color-accent-wash: #eafaf4; /* Fond de badge */
--color-accent-border: rgb(5 188 138 / 28%);
--color-accent-glow: rgb(5 188 138 / 12%);
/* ---- États ---- */
--color-focus: #03996f; /* Anneau de focus clavier */
/* ====================================================================
TYPOGRAPHIE
Deux familles seulement :
- JetBrains Mono : titres, métadonnées, éléments d'interface.
Choisie parce que c'est une police de développeur — le sujet du
site est l'informatique, la forme dit la même chose que le fond.
- Inter : corps de texte. Optimisée pour la lecture à l'écran,
hauteur d'x généreuse.
Les piles de repli listent des polices système présentes partout,
pour que la page reste lisible avant le chargement des webfonts.
==================================================================== */
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono",
Menlo, Consolas, "Liberation Mono", monospace;
--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
/* ---- Échelle typographique fluide ----
clamp(min, préféré, max) : la taille s'adapte à la largeur du viewport
sans point de rupture. Le terme central mêle rem (respecte le zoom
utilisateur et la taille de police du navigateur) et vw (fluidité).
Un vw seul casserait l'accessibilité : le zoom n'aurait plus d'effet. */
--text-2xs: 0.7rem;
--text-xs: 0.75rem;
--text-sm: 0.85rem;
--text-base: 1rem;
--text-md: clamp(1rem, 0.96rem + 0.2vw, 1.1rem);
--text-lg: clamp(1.15rem, 1.05rem + 0.5vw, 1.4rem);
--text-xl: clamp(1.35rem, 1.15rem + 1vw, 1.75rem);
--text-2xl: clamp(2.1rem, 1.5rem + 3vw, 3.4rem);
/* ---- Graisses ---- */
--weight-regular: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* ---- Interlignes ---- */
--leading-tight: 1.15; /* Grands titres */
--leading-snug: 1.4; /* Sous-titres */
--leading-normal: 1.7; /* Corps de texte : confort de lecture */
/* ====================================================================
ESPACEMENTS
Échelle géométrique basée sur 0,25rem (4px). Utiliser une échelle
plutôt que des valeurs arbitraires produit un rythme vertical
cohérent sur toute la page.
==================================================================== */
--space-2xs: 0.25rem;
--space-xs: 0.5rem;
--space-sm: 0.75rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
--space-3xl: 4.5rem;
/* ====================================================================
DIMENSIONS
==================================================================== */
--width-content: 60rem; /* Largeur maximale du contenu */
--width-prose: 68ch; /* ~68 caractères : optimum de lisibilité
(au-delà, l'œil perd la ligne au retour) */
--gutter: clamp(1rem, 4vw, 1.5rem); /* Marge latérale fluide */
--header-height: 3.5rem; /* Sert au calcul du scroll-padding */
--tap-target: 2.75rem; /* 44px : cible tactile minimale (WCAG 2.5.5) */
/* ---- Rayons ---- */
--radius-sm: 3px;
--radius-md: 6px;
--radius-full: 999px;
/* ---- Ombres : très légères, le design repose sur les bordures ---- */
--shadow-sm: 0 1px 2px rgb(16 32 28 / 4%);
--shadow-md: 0 4px 12px rgb(16 32 28 / 6%);
/* ---- Transitions ---- */
--transition-fast: 150ms ease;
--transition-base: 200ms ease;
/* ---- Plans d'empilement : centralisés pour éviter la surenchère
de z-index dispersés dans le code ---- */
--z-sticky: 100;
--z-overlay: 150;
--z-skip-link: 200;
}
}
+67
View File
@@ -0,0 +1,67 @@
/* ==========================================================================
utilities.css — Classes utilitaires
--------------------------------------------------------------------------
Dernière couche de la cascade : ces classes doivent pouvoir surcharger
un composant sans recourir à !important.
Volontairement peu nombreuses — une bibliothèque utilitaire complète
n'aurait pas de sens sans outil de purge.
========================================================================== */
@layer utilities {
/* ======================================================================
MASQUAGE VISUEL ACCESSIBLE
Retire l'élément de l'affichage tout en le laissant lisible par les
lecteurs d'écran. À utiliser pour les libellés, les intitulés de
sections de navigation, les annonces de contexte.
Ne PAS utiliser display:none ni visibility:hidden pour cet usage :
ces propriétés retirent aussi l'élément de l'arbre d'accessibilité.
====================================================================== */
.visually-hidden {
position: absolute !important;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
/* ======================================================================
RÉGION D'ANNONCE (live region)
Conteneur des messages dynamiques destinés aux lecteurs d'écran
(confirmation de copie d'adresse, par exemple).
====================================================================== */
.sr-status {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
}
/* ======================================================================
FLUX D'IMPRESSION
====================================================================== */
/* Élément affiché uniquement à l'écran */
.screen-only { }
/* Élément affiché uniquement à l'impression (révélé par print.css) */
.print-only { display: none; }
/* ======================================================================
DIVERS
====================================================================== */
/* Empêche la coupure d'un groupe de mots (« Bac 3 », « Office 365 ») */
.nowrap { white-space: nowrap; }
/* Retire la marge basse résiduelle du dernier enfant d'un bloc */
.flow-end { margin-block-end: 0 !important; }
}