/*
Theme Name: Portfolio
Theme URI: https://github.com/SatelZ/wp-portfolio
Author: Gwendal Guilloux
Description: Thème classique sur mesure pour le portfolio de Charles Bakry (motion design, vidéo, création graphique). Gabarits PHP, identité de marque : vert-noir, bleu #0163D9, lignes de construction blanches.
Requires at least: 6.7
Tested up to: 7.0
Requires PHP: 8.1
Version: 2.0.0
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: portfolio
Tags: portfolio, custom-menu, featured-images, translation-ready, accessibility-ready
*/

/* ==========================================================================
   Typographie auto-hébergée
   Polices fournies par le client, servies depuis le thème. Rien n'est appelé
   chez un tiers : c'est un point RGPD avant d'être un point de performance.
   ========================================================================== */

/* Les deux fichiers fournis sont statiques, une seule graisse chacun : aucun gras
   réel n'existe. Tous les font-weight du thème restent donc à 400, sinon le
   navigateur fabrique un faux-gras qui écrase les rondeurs de ces caractères.
   La hiérarchie se joue à la taille, comme sur la maquette. */

@font-face {
	font-family: "Pally";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("assets/fonts/pally-regular.woff2") format("woff2");
}

@font-face {
	font-family: "Hug";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("assets/fonts/hug.woff2") format("woff2");
}

/* ==========================================================================
   Jetons de design — source de vérité unique.
   theme.json y fait référence en var() pour que l'éditeur de blocs propose
   exactement la même palette sans dupliquer les valeurs.
   ========================================================================== */

:root {
	/* Vert-noir repris du cercle extérieur de logo-rond.svg : valeur de la charte
	   plutôt qu'un relevé à l'œil sur la maquette. */
	--ink: #1f2421;
	--ink-raised: #272e2a;
	--ink-card: #3e4241; /* surface des cartes de contact */
	--paper: #ffffff;

	/* Bleu de marque relevé sur le monogramme. L'aplat #0163d9 ne passe qu'à 3:1
	   sur le fond : réservé aux surfaces pleines avec texte blanc. Le bleu clair
	   monte à 6:1 et sert donc pour tout ce qui est du texte. */
	--blue: #0163d9;
	--signal: #61a0eb;
	--muted: #9aa39b;

	/* La maquette Figma n'utilise que Pally, titres comme pastille. Hug est une
	   fantaisie à tracé irrégulier, façon feutre : vérifiée au rendu dans le
	   navigateur, elle est illisible en paragraphe. Elle reste disponible en
	   accent mais n'est appelée nulle part par défaut — donc jamais téléchargée. */
	--font-display: "Pally", ui-rounded, "Segoe UI", system-ui, sans-serif;
	--font-body: "Pally", ui-rounded, "Segoe UI", system-ui, sans-serif;
	--font-accent: "Hug", "Pally", ui-rounded, system-ui, sans-serif;
	--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

	--fs-small: 0.8125rem;
	--fs-base: 1.0625rem;
	--fs-large: clamp(1.1875rem, 1.1rem + 0.4vw, 1.375rem);
	--fs-xl: clamp(1.75rem, 1.4rem + 1.6vw, 2.5rem);
	--fs-2xl: clamp(2.25rem, 1.6rem + 3vw, 4rem);
	--fs-display: clamp(2.75rem, 1.5rem + 5.6vw, 6.5rem);

	--sp-1: 0.75rem;
	--sp-2: 1.25rem;
	--sp-3: 2rem;
	--sp-4: 3.25rem;
	--sp-5: clamp(4rem, 3rem + 4vw, 7rem);
	--sp-6: clamp(6rem, 4rem + 7vw, 11rem);

	--content: 46rem;
	--wide: 76rem;
	--gutter: 2rem;

	/* Arrondis serrés : la charte est faite de lignes de construction, un angle
	   trop rond la contredit. Assez pour ne pas être une boîte brute, pas assez
	   pour arrondir le trait. --radius-pill reste la forme dessinée dans la
	   maquette (pastille du hero, étiquettes de catégorie) : un seul endroit à
	   changer le jour où elle doit s'aplatir aussi. */
	--radius-s: 0.25rem;
	--radius-m: 0.5rem;
	--radius-l: 0.75rem;
	--radius-pill: 999px;

	/* Traits : le contour d'un bloc et le filet de séparation. Deux valeurs
	   suffisent, elles étaient éparpillées en 14 %, 16 % et 40 %. */
	--border: color-mix(in srgb, var(--paper) 15%, transparent);
	--rule: color-mix(in srgb, var(--muted) 40%, transparent);

	/* Une seule durée pour tout ce qui répond au survol ou au focus : deux
	   vitesses voisines dans la même page se lisent comme un défaut de finition.
	   La rapide ne sert qu'à faire disparaître une barre du burger avant que les
	   deux autres n'aient tourné. */
	--transition: 160ms ease;
	--transition-fast: 120ms ease;

	/* Interlettrage : les étiquettes en capitales s'ouvrent, les titres se
	   resserrent. */
	--tracking: 0.14em;
	--tracking-tight: -0.03em;
}

/* ==========================================================================
   Base
   ========================================================================== */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	/* Les ancres (#projets, #contact) atterrissent sous la pastille fixe sans ça. */
	scroll-padding-top: 7rem;
}

body {
	margin: 0;
	background-color: var(--ink);
	color: var(--paper);
	font-family: var(--font-body);
	font-size: var(--fs-base);
	line-height: 1.65;
	-webkit-text-size-adjust: 100%;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	margin: 0 0 var(--sp-1);
	font-family: var(--font-display);
	font-weight: 400;
	line-height: 1.08;
	letter-spacing: var(--tracking-tight);
}

h1 { font-size: var(--fs-2xl); }
h2 { font-size: var(--fs-xl); }
h3 { font-size: var(--fs-large); }

p {
	margin: 0 0 var(--sp-2);
}

a {
	color: var(--signal);
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

a:hover {
	text-decoration: none;
}

img,
svg,
video,
iframe {
	max-width: 100%;
	height: auto;
}

hr {
	border: 0;
	border-top: 1px solid var(--rule);
	margin: var(--sp-3) 0;
}

:where(a, button, input, textarea, summary):focus-visible {
	outline: 2px solid var(--signal);
	outline-offset: 3px;
}

.skip-link {
	position: absolute;
	left: -9999px;
}

.skip-link:focus {
	left: var(--sp-2);
	top: var(--sp-2);
	z-index: 10;
	padding: 0.75rem 1.25rem;
	background-color: var(--signal);
	color: var(--ink);
	text-decoration: none;
}

/* ==========================================================================
   Gabarit
   ========================================================================== */

.wrap {
	width: 100%;
	max-width: calc(var(--content) + 2 * var(--gutter));
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.wrap--wide {
	max-width: calc(var(--wide) + 2 * var(--gutter));
}

/* Presque pleine largeur : la marge de 116 points de la maquette, en proportion. */
.wrap--full {
	max-width: 120rem;
	padding-inline: max(var(--gutter), 6.04%);
}

.section {
	padding-block: var(--sp-5);
}

.section--raised {
	background-color: var(--ink-raised);
}

/* Toutes les sections suivent la même partition : surtitre, titre, contenu. */
.section__head {
	max-width: 60rem;
	margin: 0 auto var(--sp-4);
	text-align: center;
}

.section__title {
	margin-bottom: 0;
	font-size: var(--fs-2xl);
}

.section__lead {
	margin: var(--sp-1) 0 0;
}

/* Variante alignée à gauche : quand l'entête partage la ligne avec un autre bloc,
   la centrer la décrocherait de son voisin. */
.section__head--start {
	max-width: none;
	margin: 0;
	text-align: left;
}

.section__more {
	margin-top: var(--sp-4);
	text-align: center;
}

.section__aside {
	margin-top: var(--sp-4);
}

/* Listes de mise en page : aucune n'est une énumération à puces. Réglées ici en
   une fois, chacune ne garde ensuite que sa disposition et ses marges propres. */
.site-nav__list,
.social-links,
.skills,
.projects-grid,
.entry__meta {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* Un bloc ne se termine jamais par la marge basse de son dernier enfant : elle
   se cumulerait à l'espacement du bloc suivant. */
.profil__texte > :last-child,
.entry-text > :last-child,
.entry-media > :last-child,
.entry-full > :last-child {
	margin-bottom: 0;
}

/* Étiquettes techniques : même base mono à la petite échelle, partout. */
.eyebrow,
.site-nav__list,
.entry__meta,
.pagination .nav-links,
.entry-content :where(pre, code) {
	font-family: var(--font-mono);
	font-size: var(--fs-small);
}

/* Surtitre : label technique, capitales espacées. */
.eyebrow {
	margin-bottom: var(--sp-1);
	text-transform: uppercase;
	letter-spacing: var(--tracking);
	color: var(--signal);
}

.eyebrow a {
	color: inherit;
}

/* Mesure de lecture : une intro sur toute la largeur « wide » est illisible. */
.lead {
	max-width: 42rem;
	font-size: var(--fs-large);
}

/* ==========================================================================
   En-tête et pied de page
   ========================================================================== */

/* Pastille flottante en haut à droite, comme sur la maquette. Fixe et non
   collante : elle reste visible au défilement sans jamais entrer dans le flux,
   ce qui laisse le cadrage du hero calé sur la grille Figma. */
.site-header {
	position: fixed;
	top: var(--sp-2);
	right: var(--sp-2);
	z-index: 9; /* sous le lien d'évitement, qui doit primer au focus. */
}

.site-header__inner {
	width: auto;
	max-width: none;
	flex-wrap: nowrap;
	gap: var(--sp-3);
	padding: 0.7rem 1.5rem;
	border-radius: var(--radius-pill);
	/* Le fond de la pastille est celui des sections surélevées : sans le filet,
	   elle disparaît quand elle passe au-dessus de l'une d'elles. */
	border: 1px solid var(--border);
	background-color: color-mix(in srgb, var(--ink-raised) 88%, transparent);
	backdrop-filter: blur(10px);
}

/* L'en-tête ne pousse plus rien : seules les pages sans hero réservent sa hauteur. */
body:not(.home) .site-main {
	padding-top: 4.5rem;
}

/* Menu burger. Le bouton est fabriqué par assets/js/nav.js : sans script, la
   page garde son menu complet plutôt qu'un bouton qui ne répond pas. */
.site-nav__toggle {
	display: none;
	place-items: center;
	width: 2.75rem;
	height: 2.75rem;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: none;
	color: inherit;
	cursor: pointer;
}

/* Trois barres : deux en pseudo-éléments, celle du milieu est l'élément lui-même. */
.site-nav__burger {
	position: relative;
}

.site-nav__burger,
.site-nav__burger::before,
.site-nav__burger::after {
	display: block;
	width: 1.5rem;
	height: 2px;
	border-radius: 2px;
	background-color: currentColor;
	transition: translate var(--transition), rotate var(--transition), opacity var(--transition-fast);
}

.site-nav__burger::before,
.site-nav__burger::after {
	content: "";
	position: absolute;
}

.site-nav__burger::before {
	translate: 0 -0.5rem;
}

.site-nav__burger::after {
	translate: 0 0.5rem;
}

/* Ouvert : les deux barres extérieures se rejoignent en croix, celle du milieu
   s'efface. Fond transparent et non opacity : l'opacité du parent emporterait
   ses deux pseudo-éléments avec elle, et la croix ne s'afficherait jamais. */
.site-nav__toggle[aria-expanded="true"] .site-nav__burger {
	background-color: transparent;
}

.site-nav__toggle[aria-expanded="true"] .site-nav__burger::before {
	translate: 0;
	rotate: 45deg;
}

.site-nav__toggle[aria-expanded="true"] .site-nav__burger::after {
	translate: 0;
	rotate: -45deg;
}

/* En dessous, la pastille dépasserait de l'écran : elle prend la ligne entière,
   et le menu se replie derrière le bouton. Sans script, la liste reste affichée
   et passe simplement à la ligne — c'est le repli, pas une panne. */
@media (max-width: 46rem) {
	.site-header {
		left: var(--sp-2);
	}

	.site-header__inner {
		flex-wrap: wrap;
		justify-content: space-between;
	}

	.site-nav--collapsible {
		position: relative;
	}

	.site-nav--collapsible .site-nav__toggle {
		display: grid;
	}

	.site-nav--collapsible .site-nav__list {
		display: none;
	}

	/* Le bouton est le frère précédent de la liste : pas besoin de :has().
	   Panneau détaché sous la pastille plutôt qu'une ligne de plus à l'intérieur :
	   la pastille garde sa hauteur, donc le contenu dessous ne saute pas. */
	.site-nav__toggle[aria-expanded="true"] + .site-nav__list {
		display: grid;
		position: absolute;
		top: calc(100% + 1.4rem);
		right: -1rem;
		gap: var(--sp-2);
		min-width: max-content;
		padding: var(--sp-2) var(--sp-3);
		border: 1px solid var(--border);
		border-radius: var(--radius-l);
		background-color: var(--ink-raised);
	}

	body:not(.home) .site-main {
		padding-top: 6rem;
	}
}

.site-header__inner,
.site-footer__top {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	align-items: center;
	justify-content: space-between;
}

.site-title {
	margin: 0;
	font-family: var(--font-display);
	font-size: var(--fs-large);
	font-weight: 400;
	letter-spacing: var(--tracking-tight);
}

.site-title a {
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
	color: var(--paper);
	text-decoration: none;
}

.site-title__mark {
	width: 2.5rem;
	height: auto;
	flex: none;
}

.site-nav__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-3);
	text-transform: uppercase;
	letter-spacing: var(--tracking);
}

.site-nav__list a {
	color: var(--paper);
	text-decoration: none;
}

.site-nav__list a:hover,
.site-nav__list .current-menu-item > a {
	color: var(--signal);
}

.site-footer {
	margin-top: var(--sp-5);
	padding-block: var(--sp-5) var(--sp-4);
	background-color: var(--ink-raised);
}

.site-footer__nav {
	margin-top: var(--sp-3);
}

.site-footer__rule {
	margin-block: var(--sp-3);
}

.site-footer__legal {
	margin: 0;
	font-size: var(--fs-small);
	color: var(--muted);
}

.social-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-3);
}

.social-links a {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	color: var(--paper);
	text-decoration: none;
}

.social-links a:hover {
	color: var(--signal);
}

.social-links__icon {
	fill: currentColor;
	flex: none;
}

/* ==========================================================================
   Accueil
   ========================================================================== */

/* Le hero reproduit le cadre 1920×1080 de la maquette : le logo, la pastille et
   les coutures de la mosaïque se posent SUR les lignes de construction, ils ne
   sont pas seulement posés à côté. Chaque cote est la valeur Figma divisée par
   1920, notée en commentaire pour pouvoir être revérifiée.

   Deux façons de rater ce cadre, toutes deux essayées :
   — le forcer à 56,25 % de la largeur, et son bas passe sous la ligne de
     flottaison dès qu'une fenêtre n'est pas exactement en 16/9 ;
   — l'étirer à la fenêtre, et les carrés (logo, mosaïque) ne peuvent plus
     toucher leurs deux lignes à la fois : ils décrochent.

   D'où le cadre explicite : il garde son rapport, tient toujours dans la
   fenêtre, et se centre. La marge qui apparaît sur les côtés d'une fenêtre
   écrasée est le prix de l'alignement — et elle ne se voit pas, le fond est uni. */
.hero {
	position: relative;
	height: max(30rem, min(56.25vw, 100svh));
	overflow: hidden;
}

/* La largeur découle de la hauteur par le rapport : une seule cote à régler, et
   le cadre ne peut pas déborder puisque la hauteur est déjà bornée à l'écran. */
.hero__frame {
	position: relative;
	height: 100%;
	aspect-ratio: 1920 / 1080;
	margin-inline: auto;

	/* Référence de toutes les cotes ci-dessous : elles suivent le cadre et non la
	   fenêtre, sans quoi la barre de défilement suffirait à les décaler. */
	container-type: inline-size;
}

/* La zone de dessin déborde d'une largeur de cadre de chaque côté : les traits
   qui touchent un bord filent jusqu'en dehors de l'écran, comme s'ils venaient de
   l'extérieur. L'échelle reste celle du cadre — le SVG mesure trois cadres de
   large pour trois cadres de coordonnées — donc les cotes intérieures ne bougent
   pas d'un pixel. Le débord est coupé par l'overflow du hero.

   Surtout pas de `cover` : il recadre l'image et désaligne tout. */
.hero__lines {
	position: absolute;
	inset: 0 -100% auto -100%;
	aspect-ratio: 5760 / 1081;
	background-image: url("assets/images/lignes-de-construction.svg");
	background-repeat: no-repeat;
	background-size: 100% 100%;
	opacity: 0.55;
	pointer-events: none;
}

.hero__inner {
	position: relative;
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: start;
	width: 100%;
	padding-left: 5.2604cqw; /* 101 — ligne verticale du bord gauche */
	padding-right: 1.5625cqw; /* 30 */
}

.hero__intro {
	padding-top: 8.9062cqw; /* 171 — ligne horizontale haute */
}

.hero__mark {
	display: block;
	width: 8.9062cqw; /* 171 — le logo remplit la bande entre les deux lignes */
	height: auto;
	margin-bottom: 6.0938cqw; /* 117 */
}

.hero__title {
	font-size: 6.7708cqw; /* 130 */
	line-height: 0.95;
	margin-bottom: 1.2083cqw; /* 23 */
}

.hero__badge {
	display: inline-block;
	margin: 0;
	padding: 0.28em 1em;
	border-radius: var(--radius-pill);
	background-color: var(--paper);
	color: var(--ink);
	font-family: var(--font-display);
	font-size: 1.5625cqw; /* 30 */
	line-height: 1.2; /* hauteur totale 53, cote de la maquette */
}

/* Le bouton reprend la pastille de la maquette : même forme, même place. Au
   survol il s'inverse en bleu de marque — 5,5:1 avec le texte blanc. */
.hero__cv {
	text-decoration: none;
	transition: background-color var(--transition), color var(--transition);
}

.hero__cv:hover,
.hero__cv:focus-visible {
	background-color: var(--blue);
	color: var(--paper);
}

.hero__lead {
	max-width: 30cqw;
	margin-top: 2cqw;
	font-size: max(0.9375rem, 0.9896cqw);
	color: var(--muted);
	padding-left: 1.5625cqw; /* 30 — la ligne verticale de gauche de la mosaïque */
}

/* 711 de côté, calée à 332 du haut : son bord bas tombe sur la ligne y=1042 et
   ses coutures sur les verticales x=1418 et x=1658. */
.hero__mosaic {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));

	/* Les lignes de construction vont par paires dans ce bloc — 1408,6 et 1418,2,
	   puis 1647,4 et 1658,5 — et ces paires ne sont pas un doublon : ce sont les
	   deux bords d'une gouttière de dix points. Les tuiles étaient collées, elles
	   reprennent leur écart et rentrent chacune dans sa case. */
	gap: 0.5391cqw; /* 10,35 */
	width: 37.0312cqw; /* 711 */
	margin-top: 17.2917cqw; /* 332 */
	margin-left: auto;

	/* Point de fuite du retournement ci-dessous. En cqw pour que la profondeur
	   suive l'échelle du cadre : en pixels fixes, la perspective s'aplatirait sur
	   un grand écran et se creuserait sur un petit. */
	perspective: 60cqw;
}

.hero__tile {
	display: block;
	width: 100%;
	aspect-ratio: 1;
	object-fit: cover;
	opacity: var(--tile-opacity, 1);
	outline: 1px solid color-mix(in srgb, var(--paper) 45%, transparent);
	outline-offset: -0.5px;
}

/* Au chargement, les tuiles se retournent une à une, de haut en bas, et
   `backwards` les tient dans leur état de départ pendant leur délai d'attente.

   Le profil ne suffit pas à les cacher : le point de fuite est au centre de la
   mosaïque, donc seules les tuiles de la colonne du milieu se réduisent vraiment
   à un trait à 90°. Les autres sont vues de biais et restent visibles en tranche.
   L'opacité est donc animée elle aussi — mais son point d'arrivée est la variable
   de la tuile, sinon le fondu en diagonale de la maquette serait écrasé à 1 par
   le remplissage de fin.

   L'ordre du DOM est déjà celui de la maquette de haut en bas : monogramme,
   puis la rangée du milieu, puis celle du bas. Un simple nth-child suffit. */
@media (prefers-reduced-motion: no-preference) {
	.hero__tile {
		animation: hero-tile-flip 700ms cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
	}

	.hero__tile:nth-child(1) { animation-delay: 150ms; }
	.hero__tile:nth-child(2) { animation-delay: 260ms; }
	.hero__tile:nth-child(3) { animation-delay: 370ms; }
	.hero__tile:nth-child(4) { animation-delay: 480ms; }
	.hero__tile:nth-child(5) { animation-delay: 590ms; }
	.hero__tile:nth-child(6) { animation-delay: 700ms; }
}

@keyframes hero-tile-flip {
	from {
		opacity: 0;
		transform: rotateY(90deg);
	}

	to {
		opacity: var(--tile-opacity, 1);
		transform: rotateY(0);
	}
}

/* Les opacités viennent de la maquette : les webp sont exportés à pleine
   opacité, c'est le calque Figma qui portait la transparence. Elle fait fondre
   le portrait dans le fond en diagonale, du plus lisible en haut à gauche au
   plus effacé en bas à droite. */
.hero__tile--logo { grid-area: 1 / 3; }
.hero__tile--1    { grid-area: 2 / 2; --tile-opacity: 0.6; }
.hero__tile--2    { grid-area: 2 / 3; --tile-opacity: 0.45; }
.hero__tile--3    { grid-area: 3 / 1; --tile-opacity: 0.6; }
.hero__tile--4    { grid-area: 3 / 2; --tile-opacity: 0.45; }
.hero__tile--5    { grid-area: 3 / 3; --tile-opacity: 0.3; }

/* 61 × 100, à 37 du bas. Le point et sa course sont en em : la taille de police
   suit donc la pastille (16/61, le rapport de la maquette), sinon le point
   déborde du boîtier dès que le cadre change de taille. */
.hero__scroll {
	position: absolute;
	left: 50%;
	bottom: 1.9531cqw; /* 37 */
	translate: -50% 0;
	display: grid;
	place-items: start center;
	width: 3.1771cqw; /* 61 */
	height: 5.2083cqw; /* 100 */
	padding-top: 1.1em;
	border-radius: var(--radius-pill);
	background-color: var(--paper);
	font-size: 0.8333cqw; /* 16 */
}

.hero__scroll::before {
	content: "";
	width: 0.55em;
	height: 0.55em;
	border-radius: 50%;
	background-color: var(--ink);
	animation: hero-scroll 1.8s ease-in-out infinite;
}

@keyframes hero-scroll {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(1.4em); }
}

/* Composition simplifiée : sous 60rem, et aussi dès que la fenêtre est trop
   basse — un téléphone couché a la largeur d'une tablette et la hauteur d'une
   bande de menu, le cadre n'y tient pas davantage. Colonne simple, cotes fixes,
   et les lignes redeviennent un bandeau décoratif en haut, à leur rapport
   naturel : étirées sur toute la hauteur d'un hero mobile, leurs traits
   grossiraient de trois ou quatre fois. */
@media (max-width: 60rem), (max-height: 34rem) {
	.hero {
		height: auto;

		/* La pastille de navigation est fixe et flotte au-dessus : sans cette
		   réserve, elle recouvrait le logo rond. */
		padding-block: 7rem var(--sp-5);
	}

	/* Le cadre n'a plus lieu d'être : la colonne prend toute la largeur et se
	   règle sur son contenu. */
	.hero__frame {
		height: auto;
		aspect-ratio: auto;
		container-type: normal;
	}

	/* Sur mobile la grille ne construit plus rien : la composition au cadre est
	   abandonnée, elle ne serait qu'un décor par-dessus le contenu. */
	.hero__lines {
		display: none;
	}

	.hero__inner {
		grid-template-columns: minmax(0, 1fr);
		padding-inline: var(--gutter);
	}

	.hero__intro { padding-top: 0; }

	.hero__mark {
		width: 5rem;
		margin-bottom: var(--sp-3);
	}

	.hero__title {
		font-size: clamp(2.75rem, 9vw, 4.5rem);
		margin-bottom: var(--sp-2);
	}

	.hero__badge { font-size: 1.0625rem; }

	.hero__lead {
		max-width: none;
		margin-top: var(--sp-2);
		font-size: 1rem;
	}

	.hero__mosaic {
		gap: 0.35rem; /* le cadre ne fournit plus d'échelle : cote fixe. */
		width: min(100%, 21rem);
		margin-top: var(--sp-4);
		margin-inline: 0 auto;
	}

	.hero__scroll { display: none; }
}

/* Vise aussi le bloc Bouton du cœur : c'est le seul bouton que Charles peut
   insérer depuis l'éditeur, autant qu'il porte l'identité sans réglage. */
.button,
.wp-block-button__link {
	display: inline-block;
	padding: 0.85rem 1.75rem;
	border-radius: var(--radius-pill);
	background-color: var(--blue);
	color: var(--paper);
	font-weight: 400;
	text-decoration: none;
}

.button:hover,
.wp-block-button__link:hover {
	background-color: var(--paper);
	color: var(--ink);
}

/* ==========================================================================
   Profil
   Deux colonnes en haut — l'entête à gauche, la présentation à droite — puis
   les compétences en 2 × 2 et l'appel à l'action centré, comme la maquette.
   ========================================================================== */

.profil__intro {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--sp-3) var(--sp-5);
	align-items: start;
}

.skills {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--sp-4) var(--sp-5);
	/* Chaque boîte garde sa hauteur propre : repliée, elle ne s'étire pas sur
	   celle de sa voisine ouverte. */
	align-items: start;
	margin: var(--sp-5) 0 0;
}

.skill {
	--logo: 2.25rem;
	--logo-gap: 0.5rem;

	position: relative;
	padding: var(--sp-3);
	border: 1px solid var(--border);
	border-radius: var(--radius-l);
	background-color: var(--ink-raised);
}

/* Logiciels : une colonne de pastilles à cheval sur le bord gauche. Le fond
   opaque découpe la bordure au lieu de la chevaucher. La carte réserve la
   place à gauche pour que le texte ne vienne jamais dessous.
   Ancrée en haut : les pastilles ne bougent pas quand la boîte se déplie.
   --logos porte leur nombre, posé par le gabarit : la carte se réserve la
   hauteur qu'il faut, quel que soit le nombre de logiciels déclarés. */
.skill--logos {
	padding-left: calc(var(--sp-3) + var(--logo) / 2);
	min-height: calc(
		var(--sp-2) * 2 + var(--logos) * var(--logo) + (var(--logos) - 1) * var(--logo-gap)
	);
}

.skill__logos {
	position: absolute;
	top: var(--sp-2);
	left: calc(var(--logo) / -2);
	display: grid;
	gap: var(--logo-gap);
	margin: 0;
	padding: 0;
	list-style: none;
}

.skill__logos li {
	display: grid;
	place-items: center;
	width: var(--logo);
	height: var(--logo);
	border: 1px solid var(--border);
	border-radius: var(--radius-m);
	background-color: var(--ink);
}

.skill__logos img {
	width: 55%;
	height: 55%;
	object-fit: contain;
}

/* Le résumé porte le numéro, le titre et le signe : trois éléments sur deux
   rangées, le signe couvrant les deux à droite. */
.skill__summary {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	column-gap: var(--sp-2);
	cursor: pointer;
	list-style: none; /* Retire le triangle natif, remplacé par le signe ci-dessous. */
}

.skill__summary::-webkit-details-marker {
	display: none;
}

.skill__summary .eyebrow,
.skill__title,
.skill__text {
	grid-column: 1;
}

.skill__title {
	margin: 0 0 var(--sp-1);
	transition: color var(--transition);
}

.skill__summary:hover .skill__title {
	color: var(--signal);
}

/* Le « + » bascule en « × » : une seule forme, une seule rotation. */
.skill__summary::after {
	content: "+";
	grid-column: 2;
	grid-row: 1;
	font-family: var(--font-mono);
	font-size: var(--fs-large);
	line-height: 1;
	color: var(--signal);
	transition: rotate var(--transition);
}

.skill__details[open] .skill__summary::after {
	rotate: 45deg;
}

.skill__text {
	margin: 0;
}

.skill__detail > p {
	margin: var(--sp-2) 0 0;
}

/* Dépliage progressif là où le navigateur sait animer une hauteur automatique.
   Ailleurs, la boîte s'ouvre d'un coup : le contenu est là dans les deux cas. */
@supports (interpolate-size: allow-keywords) {
	.skill__details {
		interpolate-size: allow-keywords;
	}

	.skill__details::details-content {
		overflow: hidden;
		block-size: 0;
		transition: block-size var(--transition), content-visibility var(--transition) allow-discrete;
	}

	.skill__details[open]::details-content {
		block-size: auto;
	}
}

.profil__cta {
	margin: var(--sp-5) 0 0;
	text-align: center;
}

/* Le bouton de la maquette occupe près de deux fois la largeur d'un bouton
   courant : les cotes sont en em pour qu'il grandisse avec son texte. */
.profil__cta .button {
	padding: 1.1em 3.5em;
	font-size: var(--fs-large);
}

@media (max-width: 60rem) {
	.profil__intro,
	.skills {
		grid-template-columns: minmax(0, 1fr);
	}

	.profil__cta .button {
		padding: 1.1em 2.5em;
	}
}

/* ==========================================================================
   Mon univers
   Les catégories à gauche, la pile de cartes à droite. Le filtre est un groupe
   de boutons radio : les règles qui relient la case cochée à sa pile sont
   générées avec les catégories (portfolio_univers_styles).
   ========================================================================== */

/* Deux colonnes, environ 30/70 : les catégories restent à gauche, alignées sur
   le titre, et la pile se centre dans l'espace qui reste plutôt que de coller
   au bord droit. */
.univers__body {
	display: grid;
	grid-template-columns: minmax(0, 3fr) minmax(0, 7fr);
	gap: var(--sp-5);
	align-items: center;
	margin-top: var(--sp-5);
}

.univers__piles {
	min-width: 0;
}

.univers__filtres {
	display: grid;
	gap: var(--sp-2);
	/* Les boutons remplissent leur colonne, sans devenir des bannières sur un
	   très grand écran. */
	width: min(100%, 16rem);
	margin: 0;
	padding: 0;
	border: 0;
}

/* La case elle-même est inutile à l'œil : c'est l'étiquette qui fait le bouton.
   Retirée de l'affichage mais pas du document, elle garde le clavier, le focus
   et l'annonce « sélectionné » du groupe radio. */
.univers__filtre input {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.univers__filtre label {
	display: grid;
	place-items: center;
	padding: 1em 1.25em;
	border: 1px solid var(--border);
	border-radius: var(--radius-m);
	background-color: var(--ink-raised);
	text-align: center;
	cursor: pointer;
	transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}

.univers__filtre label:hover {
	border-color: var(--signal);
}

.univers__filtre input:checked + label {
	border-color: var(--signal);
	background-color: var(--signal);
	color: var(--ink);
}

.univers__filtre input:focus-visible + label {
	outline: 2px solid var(--signal);
	outline-offset: 3px;
}

/* Une seule pile visible à la fois : les règles générées rallument celle dont
   la case est cochée. Sans elles — et sans :has() — toutes restent masquées,
   d'où la sécurité ci-dessous. */
.univers__pile {
	display: none;
	justify-content: center;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
}

@supports not selector(:has(*)) {
	.univers__pile {
		display: flex;
	}
}

.univers__carte {
	flex: 0 0 15rem;
	perspective: 50rem;
}

/* Les cartes se chevauchent comme dans la maquette — un bon tiers de leur
   largeur ; celle que l'on survole passe devant ses voisines. */
.univers__carte + .univers__carte {
	margin-left: -5rem;
}

.univers__carte:hover,
.univers__carte:focus-within {
	z-index: 1;
}

.univers__carte-face {
	position: relative;
	aspect-ratio: 387 / 537;
	overflow: hidden;
	border: 1px solid var(--border);
	border-radius: var(--radius-l);
	background-color: var(--ink-raised);
	/* --ry est posé par univers.js au passage de la souris : la carte pivote
	   sur sa hauteur, jamais vers l'avant. Sans le script, l'angle vaut zéro
	   et la carte reste plate. */
	transform: rotateY(var(--ry, 0deg));
	transition: transform var(--transition), box-shadow var(--transition);
}

.univers__carte:hover .univers__carte-face {
	box-shadow: 0 1.5rem 3rem color-mix(in srgb, var(--ink) 60%, transparent);
}

.univers__visuel {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Dégradé sous le texte : une légende blanche sur une photo claire ne se lit
   pas, et on ne choisit pas les photos à la place de leur auteur. */
.univers__texte {
	position: absolute;
	inset: auto 0 0;
	padding: var(--sp-3) var(--sp-2) var(--sp-2);
	background-image: linear-gradient(to top, var(--ink), transparent);
}

.univers__titre {
	margin: 0 0 var(--sp-1);
	font-size: var(--fs-large);
}

.univers__texte p {
	margin: 0;
	font-size: var(--fs-small);
	color: var(--muted);
}

/* Le bouton couvre la carte entière : c'est la carte qu'on clique. Son
   étiquette n'est lue que par les lecteurs d'écran, le titre étant déjà là. */
.univers__ouvrir {
	position: absolute;
	inset: 0;
	border: 0;
	border-radius: inherit;
	background-color: transparent;
	cursor: pointer;
}

.univers__ouvrir:focus-visible {
	outline: 2px solid var(--signal);
	outline-offset: -4px;
}

.univers__modale {
	width: min(42rem, calc(100vw - var(--sp-3) * 2));
	max-height: min(44rem, calc(100svh - var(--sp-3) * 2));
	padding: var(--sp-4);
	border: 1px solid var(--border);
	border-radius: var(--radius-l);
	background-color: var(--ink-raised);
	color: var(--paper);
	overflow: auto;
	/* Le navigateur pose une marge automatique qui centre l'élément. */
	margin: auto;
}

.univers__modale::backdrop {
	background-color: color-mix(in srgb, #000 70%, transparent);
	backdrop-filter: blur(4px);
}

.univers__article-titre {
	margin: 0 0 var(--sp-2);
	padding-right: var(--sp-3);
}

.univers__article-contenu > :last-child {
	margin-bottom: 0;
}

/* Croix de fermeture : deux traits, comme le burger du menu. Échap ferme
   déjà la fenêtre, ce bouton est là pour la souris et le tactile. */
.univers__fermer {
	position: absolute;
	top: var(--sp-2);
	right: var(--sp-2);
	width: 2.25rem;
	height: 2.25rem;
	border: 1px solid var(--border);
	border-radius: var(--radius-m);
	background-color: transparent;
	cursor: pointer;
	transition: border-color var(--transition);
}

.univers__fermer:hover {
	border-color: var(--signal);
}

.univers__fermer::before,
.univers__fermer::after {
	content: "";
	position: absolute;
	inset: 50% 25%;
	height: 1px;
	background-color: var(--paper);
}

.univers__fermer::before {
	rotate: 45deg;
}

.univers__fermer::after {
	rotate: -45deg;
}

@media (max-width: 60rem) {
	.univers__body {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--sp-3);
	}

	.univers__filtres {
		width: auto;
	}

	/* Les catégories passent en ligne : quatre boutons empilés mangeraient
	   un écran entier avant la première carte. */
	.univers__filtres {
		grid-auto-flow: column;
		grid-auto-columns: minmax(min-content, 1fr);
		gap: var(--sp-1);
	}

	.univers__filtre label {
		padding: 0.75em 0.5em;
		font-size: var(--fs-small);
	}

	/* Plus de chevauchement : sur un écran étroit il ne resterait qu'une
	   tranche de chaque carte. */
	.univers__pile {
		flex-wrap: wrap;
		gap: var(--sp-2);
	}

	.univers__carte {
		flex: 1 1 14rem;
	}

	.univers__carte + .univers__carte {
		margin-left: 0;
	}

	.univers__modale {
		padding: var(--sp-3);
	}
}

/* ==========================================================================
   Projets
   ========================================================================== */

.projects-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--sp-5) var(--sp-3);
}

@media (max-width: 60rem) {
	.projects-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 34rem) {
	.projects-grid { grid-template-columns: minmax(0, 1fr); }
}

.project-card__link {
	display: grid;
	justify-items: center;
	gap: var(--sp-2);
	color: var(--paper);
	text-decoration: none;
}

.project-card__title {
	margin: 0;
	font-size: var(--fs-large);
}

/* Signature de la maquette : au survol le visuel se décale et découvre un bloc
   bleu posé derrière lui. Un box-shadow sans flou suffit — pas d'élément en
   plus, et le décalage reste annulé par prefers-reduced-motion. */
.project-card__media {
	display: block;
	width: min(100%, 17rem);
	aspect-ratio: 1;
	overflow: hidden;
	border-radius: var(--radius-m);
	transition: translate var(--transition), box-shadow var(--transition);
}

.project-card__link:hover .project-card__media,
.project-card__link:focus-visible .project-card__media {
	translate: -0.4rem -0.4rem;
	box-shadow: 0.9rem 0.9rem 0 0 var(--blue);
}

.project-card__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.project-card__type {
	display: inline-block;
	padding: 0.35em 1.5em;
	border: 1px solid var(--paper);
	border-radius: var(--radius-pill);
	font-size: var(--fs-small);
}

/* ==========================================================================
   Contenu éditorial (the_content)
   Le conteneur n'est pas contraint : ce sont les enfants qui le sont, pour que
   les blocs alignés large débordent proprement de la colonne de texte.
   ========================================================================== */

.entry__header {
	padding-top: var(--sp-4);
	padding-bottom: var(--sp-3);
}

/* Une page de texte n'a pas de visuel large : son titre s'aligne sur la colonne
   de lecture plutôt que sur la largeur des fiches de projet. */
.page .entry__header {
	max-width: calc(var(--content) + 2 * var(--gutter));
}

.entry__title {
	font-size: var(--fs-2xl);
}

.entry__lead {
	max-width: 42rem;
	margin-top: var(--sp-2);
	font-size: var(--fs-large);
}

.entry__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-1) var(--sp-2);
	align-items: center;
	margin: var(--sp-2) 0 0;
	color: var(--muted);
}

.entry__type {
	padding: 0.25em 1.1em;
	border: 1px solid currentColor;
	border-radius: var(--radius-pill);
}

.entry-content {
	padding-inline: var(--gutter);
}

.entry-content > * {
	max-width: var(--content);
	margin-inline: auto;
}

.entry-content > .alignwide {
	max-width: var(--wide);
}

/* Pleine largeur réelle : le conteneur a des marges intérieures, il faut les
   reprendre, sinon « pleine largeur » s'arrête à deux centimètres du bord. */
.entry-content > .alignfull {
	max-width: none;
	margin-inline: calc(-1 * var(--gutter));
}

.entry-content > .alignleft,
.entry-content > .alignright {
	max-width: 50%;
}

.entry-content h2,
.entry-content h3 {
	margin-top: var(--sp-4);
}

.entry-content figure {
	margin-block: var(--sp-3);
}

/* ---- Deux colonnes : médias à gauche, texte à droite ---------------------
   grid-auto-flow: dense remonte chaque média à la première ligne libre de sa
   colonne. C'est ce qui rend la mise en page indépendante de l'ordre de saisie :
   Charles écrit son texte et pose ses visuels comme il veut, ils se rangent.

   :has() n'applique la grille que s'il y a effectivement un média — sinon un
   projet tout en texte se retrouverait tassé dans une demi-largeur, avec une
   colonne vide à côté. */
@media (min-width: 60rem) {
	.entry-content:has(> .entry-media) {
		display: grid;
		grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
		grid-auto-flow: dense;

		/* Les deux colonnes se centrent l'une sur l'autre : un visuel vertical
		   face à trois lignes de texte laissait sinon un demi-écran de vide sous
		   le texte, calé en haut. Le plus haut des deux fixe la ligne, l'autre
		   se centre dessus. */
		align-items: center;
		gap: var(--sp-3) var(--sp-4);
		max-width: calc(var(--wide) + 2 * var(--gutter));
		margin-inline: auto;
	}

	.entry-content:has(> .entry-media) > * {
		max-width: none;
		margin-inline: 0;
	}

	.entry-media {
		grid-column: 1;
	}

	.entry-text {
		grid-column: 2;
	}

	/* Aligné large ou pleine largeur : le bloc reprend les deux colonnes. */
	.entry-full {
		grid-column: 1 / -1;
	}
}

/* Les suites de blocs sont emballées par portfolio_content_columns() : en colonne
   unique elles ne se voient pas, chacune coule comme le contenu d'origine. */

.entry-content > .entry-full {
	max-width: var(--wide);
}

/* ---- Médias --------------------------------------------------------------
   Trois provenances possibles pour un projet, toutes posées depuis l'éditeur :
   images et galeries, vidéos hébergées sur le site (bloc Vidéo), intégrations
   YouTube ou Vimeo (bloc Intégration). Le cadrage est commun aux trois. */

/* Un visuel ne dépasse jamais la hauteur de l'écran. Sans ce plafond, une image
   verticale — une affiche, un format story — occupe deux écrans à elle seule et
   le texte qui l'explique n'est plus jamais lu. */
.entry-content img,
.entry-content video,
.entry-content .wp-block-image img {
	display: block;
	max-height: 80svh;
	width: auto;
	margin-inline: auto;
	border-radius: var(--radius-m);
}

.entry-content :where(iframe, .wp-block-embed__wrapper > *) {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	height: auto;
	border: 0;
	border-radius: var(--radius-m);
}

/* WordPress connaît le ratio réel de l'intégration et le pose en classe : on le
   laisse gagner, un format vertical ne doit pas être forcé en 16/9. */
.entry-content .wp-embed-aspect-9-16 iframe { aspect-ratio: 9 / 16; }
.entry-content .wp-embed-aspect-1-1 iframe  { aspect-ratio: 1; }
.entry-content .wp-embed-aspect-4-3 iframe  { aspect-ratio: 4 / 3; }

/* L'espacement de la galerie vient des styles globaux (24 px) : on le laisse,
   il est juste, et on s'y raccroche pour calculer les colonnes plus bas. */
.entry-content .wp-block-gallery img {
	max-height: none;
	border-radius: var(--radius-s);
}

/* Sur mobile le cœur laisse deux vignettes côte à côte puis une seule sur toute
   la largeur : la galerie n'a plus l'air d'une galerie. Deux colonnes franches,
   la fonctionnalité reste la même qu'en grand écran. */
@media (max-width: 40rem) {
	/* :not(#individual-image) reprend l'astuce de spécificité du cœur — sans elle
	   sa règle, qui porte un identifiant factice, gagne toujours. */
	.entry-content .wp-block-gallery.has-nested-images figure.wp-block-image:not(#individual-image) {
		/* flex-grow: 1 du cœur reprendrait toute la ligne dès qu'une vignette
		   passe à la ligne suivante. La base est calculée à partir de l'écart
		   réellement appliqué : quatre pixels d'erreur et tout se remet en pile. */
		flex: 0 0 calc(50% - var(--wp--style--unstable-gallery-gap, 24px) / 2);
		width: calc(50% - var(--wp--style--unstable-gallery-gap, 24px) / 2);
	}
}

/* Les visuels sont centrés dans leur colonne, la légende les suit. */
.entry-content figcaption {
	font-size: var(--fs-small);
	color: var(--muted);
	text-align: center;
}

.entry-content blockquote {
	margin-inline: auto;
	padding-left: var(--sp-2);
	border-left: 3px solid var(--signal);
}

.entry-content pre {
	padding: var(--sp-2);
	background-color: var(--ink-raised);
	overflow-x: auto;
}

.entry-nav,
.entry-cta {
	margin-top: var(--sp-4);
}

.entry-nav {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
	justify-content: space-between;
	padding-top: var(--sp-3);
	border-top: 1px solid var(--rule);
}

.entry-summary + .entry-summary {
	margin-top: var(--sp-4);
}

.entry-summary__title a {
	color: var(--paper);
	text-decoration: none;
}

.entry-summary__title a:hover {
	color: var(--signal);
}

.pagination {
	margin-top: var(--sp-4);
}

.pagination .nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--sp-2);
}

.pagination .current {
	color: var(--muted);
}

/* ==========================================================================
   Contact
   Le formulaire et les réseaux sont deux cartes pleine largeur, comme sur la
   maquette. Le formulaire lui-même est rendu par l'extension portfolio-core :
   ici on ne fait que l'habiller.
   ========================================================================== */

.contact {
	text-align: center;
}

.contact__inner {
	max-width: 120rem;
	margin-inline: auto;
	padding-inline: max(var(--gutter), 1.25%);
}

.contact-form {
	display: grid;
	gap: var(--sp-2);
	padding: var(--sp-3);
	border-radius: var(--radius-l);
	background-color: var(--ink-card);
	text-align: left;
}

/* Bordure en bleu clair et non en bleu de marque : #0163d9 ne tient que 1,9:1
   sur la carte, illisible pour une bordure de champ, qui est un élément
   d'interface porteur de sens. Le bleu clair monte à 3,8:1. */
.contact-form input,
.contact-form textarea {
	width: 100%;
	padding: 0.9rem 1.25rem;
	font: inherit;
	color: var(--paper);
	background-color: transparent;
	border: 1px solid var(--signal);
	border-radius: var(--radius-s);
}

.contact-form :where(input, textarea)::placeholder {
	color: color-mix(in srgb, var(--paper) 72%, transparent);
	opacity: 1; /* Firefox applique 0.54 par défaut. */
}

.contact-form textarea {
	min-height: 8rem;
	resize: vertical;
}

.contact-form__actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--sp-2);
	margin-top: var(--sp-1);
}

.contact-form button {
	padding: 0.55rem 1.75rem;
	font: inherit;
	color: var(--paper);
	background-color: transparent;
	border: 1px solid var(--signal);
	border-radius: var(--radius-s);
	cursor: pointer;
}

.contact-form button:hover {
	color: var(--ink);
	background-color: var(--paper);
	border-color: var(--paper);
}

/* Piège à robots : invisible pour l'humain, sans display:none que les bots détectent. */
.contact-form__trap {
	position: absolute;
	left: -9999px;
}

.contact-form__notice {
	max-width: 48rem;
	margin-inline: auto;
	margin-bottom: var(--sp-2);
	padding: 0.75rem 1rem;
	border-radius: var(--radius-m);
	background-color: var(--ink-card);
}

.contact-social {
	margin-top: var(--sp-3);
	padding: var(--sp-4) var(--sp-3);
	border-radius: var(--radius-l);
	background-color: var(--blue);
}

.contact-social__title {
	margin-bottom: var(--sp-3);
	font-size: var(--fs-large);
}

.social-links--stacked {
	flex-direction: column;
	align-items: center;
	gap: var(--sp-3);
}

/* column-reverse : le libellé se lit au-dessus de l'icône comme sur la maquette,
   mais l'ordre du DOM reste icône puis texte, commun au pied de page. */
.social-links--stacked .social-links__link {
	flex-direction: column-reverse;
	gap: 0.6rem;
}

.social-links--stacked .social-links__icon {
	box-sizing: content-box;
	padding: 0.4rem;
	border-radius: var(--radius-s);
	background-color: #000;
}

.social-links--stacked .social-links__link:hover {
	color: var(--paper);
	text-decoration: underline;
}

@media (max-width: 40rem) {
	.contact-form,
	.contact-social {
		padding: var(--sp-2);
	}
}

/* ==========================================================================
   Plancher qualité
   ========================================================================== */

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* ==========================================================================
   Mouvement
   Rien ici n'est nécessaire à la lecture : chaque effet est un supplément, et
   se coupe entièrement sous prefers-reduced-motion.
   ========================================================================== */

/* Les cibles d'un survol changent d'état, pas de place : aucune ne se déplace
   sous le curseur, ce qui rendrait le pointage instable. */
.site-nav__list a,
.social-links a,
.social-links__icon,
.button,
.wp-block-button__link,
.contact-form button,
.contact-form input,
.contact-form textarea,
.section__more a,
.entry-nav a,
.project-card__title {
	transition: color var(--transition), background-color var(--transition), border-color var(--transition), translate var(--transition);
}

/* Soulignement qui se déplie depuis la gauche : le lien courant le garde ouvert. */
.site-nav__list a {
	position: relative;
}

.site-nav__list a::after {
	content: "";
	position: absolute;
	right: 0;
	bottom: -0.4em;
	left: 0;
	height: 1px;
	background-color: currentColor;
	transform-origin: left center;
	scale: 0 1;
	transition: scale var(--transition);
}

.site-nav__list a:hover::after,
.site-nav__list a:focus-visible::after,
.site-nav__list .current-menu-item > a::after {
	scale: 1 1;
}

.social-links__link:hover .social-links__icon {
	translate: 0 -0.15rem;
}

.section__more a:hover {
	translate: 0.35rem 0;
}

.project-card__link:hover .project-card__title,
.project-card__link:focus-visible .project-card__title {
	color: var(--signal);
}

@media (prefers-reduced-motion: no-preference) {
	html {
		scroll-behavior: smooth;
	}

	/* Apparition à l'entrée dans le champ. Animation pilotée par le défilement :
	   pas une ligne de JavaScript, donc rien à charger et rien qui puisse laisser
	   un bloc invisible si un script échoue. @supports parce que Firefox ne
	   l'implémente pas encore — sans lui, les blocs y resteraient à opacité 0. */
	@supports (animation-timeline: view()) {
		.section__head,
		.skill,
		.project-card,
		.contact-form,
		.contact-social,
		.entry__header,
		.entry-content > *,
		.site-footer__top {
			animation: apparition linear both;
			animation-timeline: view();
			animation-range: entry 5% cover 25%;
		}
	}
}

@keyframes apparition {
	from {
		opacity: 0;
		translate: 0 1.75rem;
	}

	to {
		opacity: 1;
		translate: 0 0;
	}
}

/* Le repli : une seule déclaration coupe transitions, apparitions et défilement
   fluide. animation-iteration-count est indispensable — sans lui, la pastille
   de défilement du hero rejouerait sa boucle toutes les 0,01 ms. */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		transition-duration: 0.01ms !important;
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		scroll-behavior: auto !important;
	}
}
