Site personnel courcellematthi.eu
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user