/*
Theme Name: Photography Minimal
Theme URI: https://github.com/thehub-link/photography-minimal
Author: Charles Bergeron
Author URI: https://charlesbergeron.com
Description: Thème sur mesure pour charlesbergeron.com — mise en boîte, Montserrat, grille de galeries — avec galeries synchronisées depuis Lightroom via le plugin lightroom-sync.
Version: 0.1.0
Requires at least: 6.4
Requires PHP: 8.1
License: Proprietary
Text Domain: photography-minimal
*/

/* =========================================================================
   Fonts — self-hosted Montserrat (latin subset, variable, weights 100-900,
   normal + italic), matching the typeface used on charlesbergeron.com.
   See assets/fonts/OFL-montserrat.txt for license. plan/performance.md, levier n°5.
   ========================================================================= */

@font-face {
	font-family: 'Montserrat';
	font-style: normal;
	font-weight: 100 900;
	font-display: swap;
	src: url('assets/fonts/montserrat-var-latin.woff2') format('woff2');
}

@font-face {
	font-family: 'Montserrat';
	font-style: italic;
	font-weight: 100 900;
	font-display: swap;
	src: url('assets/fonts/montserrat-italic-var-latin.woff2') format('woff2');
}

/* =========================================================================
   Design tokens — mise en boîte claire/grise et typographie Montserrat,
   reprenant la présentation de charlesbergeron.com. Voir
   plan/design-siteweb-revision.md.
   ========================================================================= */

:root {
	--color-page-bg: #f0f0f0;
	--color-bg: #ffffff;
	--color-fg: #222222;
	--color-fg-muted: #5a5a5a;
	--color-border: #e5e5e5;

	--font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
	--font-size-body: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
	--font-size-subtitle: clamp(1.25rem, 1.1rem + 1vw, 1.75rem);
	--font-size-title: clamp(1.75rem, 1.3rem + 3vw, 3.125rem);
	--font-size-caption: 0.8125rem;
	--line-height-body: 1.6;

	--space-xs: 0.5rem;
	--space-sm: 1rem;
	--space-md: 2rem;
	--space-lg: 4rem;
	--space-xl: 6rem;

	--container-width: 1150px;
	--container-padding: clamp(1.25rem, 2vw, 3rem);
	--gallery-gap: 8px;
}

[data-theme='dark'] {
	--color-page-bg: #000000;
	--color-bg: #111111;
	--color-fg: #f2f2f2;
	--color-fg-muted: #a6a6a6;
	--color-border: #262626;
}

/* No-JS fallback for the "automatique" palette (data-theme="auto", set server-side in header.php
   when the Customizer palette is left on "Automatique"): with JavaScript, the inline script in
   header.php <head> resolves this to an explicit light/dark attribute before first paint (reading
   a visitor's stored choice, or system preference) so this rule never actually applies then. */
@media (prefers-color-scheme: dark) {
	[data-theme='auto'] {
		--color-page-bg: #000000;
		--color-bg: #111111;
		--color-fg: #f2f2f2;
		--color-fg-muted: #a6a6a6;
		--color-border: #262626;
	}
}

/* =========================================================================
   Reset & base.
   ========================================================================= */

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

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	background-color: var(--color-page-bg);
	color: var(--color-fg);
	font-family: var(--font-family);
	font-size: var(--font-size-body);
	font-weight: 400;
	line-height: var(--line-height-body);
}

.site-boxed {
	max-width: var(--container-width);
	margin-inline: auto;
	background-color: var(--color-bg);
}

img {
	max-width: 100%;
	display: block;
}

picture {
	display: block;
	/* Width already fills the containing block by default (block boxes), but height doesn't —
	   without this, an <img height: 100%> inside (collection tiles, justified grid, blog grid)
	   resolves against picture's own auto/shrink-to-fit height instead of the actual container,
	   so it renders at its intrinsic aspect ratio: harmless for photos matching the container's
	   ratio, but portrait photos then overflow past the bottom border and landscape ones leave an
	   oversized gap. Confirmed against a real WebKit render (2026-07-10). */
	height: 100%;
}

a {
	color: inherit;
}

button {
	font: inherit;
	color: inherit;
}

h1,
h2 {
	font-weight: 600;
	line-height: 1.15;
	margin: 0 0 var(--space-sm);
}

h1.page-title {
	font-size: var(--font-size-title);
	letter-spacing: -0.02em;
	color: var(--color-fg);
	text-align: center;
	padding: var(--space-lg) var(--container-padding) 0;
	max-width: var(--container-width);
	margin-inline: auto;
}

h2 {
	font-size: var(--font-size-subtitle);
}

p {
	margin: 0 0 var(--space-sm);
}

.page-title__divider {
	width: 60px;
	height: 1px;
	margin: var(--space-sm) auto 0;
	background-color: var(--color-fg);
	border: 0;
}

.page-title__tagline,
.home__content > p:first-child {
	max-width: var(--container-width);
	margin: var(--space-sm) auto var(--space-md);
	padding: 0 var(--container-padding);
	text-align: center;
	font-style: italic;
}

.error-404__back {
	text-align: center;
}

.error-404__back a {
	font-size: var(--font-size-caption);
	text-decoration: underline;
}

:focus-visible {
	outline: 2px solid currentColor;
	outline-offset: 2px;
}

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
}

.skip-link.screen-reader-text:focus {
	position: fixed;
	top: var(--space-xs);
	left: var(--space-xs);
	width: auto;
	height: auto;
	clip: auto;
	z-index: 1000;
	padding: var(--space-xs) var(--space-sm);
	background: var(--color-bg);
	color: var(--color-fg);
	border: 1px solid var(--color-border);
}

body.has-lightbox-open {
	overflow: hidden;
}

/* =========================================================================
   Header / navigation.
   ========================================================================= */

.site-header {
	position: sticky;
	top: 0;
	z-index: 100;
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-sm);
	padding: var(--space-sm) var(--container-padding);
	background-color: var(--color-bg);
	border-bottom: 1px solid transparent;
}

.site-logo {
	font-size: var(--font-size-subtitle);
	font-weight: 600;
	text-decoration: none;
	letter-spacing: 0.02em;
	/* Pushes every other header child (mobile menu-toggle, nav, theme-toggle) flush to the right
	   edge as a group, same visual result as the previous `justify-content: space-between` but
	   keeping the nav and theme-toggle adjacent to each other rather than spread apart. */
	margin-right: auto;
}

.site-logo img {
	height: 1.5rem;
	width: auto;
}

/* The custom logo uploaded in Apparence → Personnaliser → Identité du site is a wordmark drawn in
   near-black on a transparent background — invisible against the dark palette's near-black
   background. There is no separate dark-mode logo asset to swap in, so this forces it to a plain
   white silhouette instead of leaving it unreadable (loses any colour accents from the original
   file, e.g. a coloured letter, but stays legible). Duplicated under the [data-theme='auto']
   no-JS fallback below so it also applies without the toggle script's help. */
[data-theme='dark'] .site-logo img {
	filter: brightness(0) invert(1);
}

@media (prefers-color-scheme: dark) {
	[data-theme='auto'] .site-logo img {
		filter: brightness(0) invert(1);
	}
}

.menu-toggle {
	display: none;
}

.site-nav {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-md);
	margin: 0;
	padding: 0;
	list-style: none;
}

.site-nav a {
	display: inline-block;
	text-decoration: none;
	font-size: 0.875rem;
	font-weight: 600;
}

.site-nav a:hover,
.site-nav a:focus-visible {
	color: var(--color-fg-muted);
}

.site-nav > li.current-menu-item > a,
.site-nav > li.current_page_item > a,
.site-nav > li.current-menu-ancestor > a,
.site-nav > li.current_page_ancestor > a {
	padding-bottom: 1.375rem;
	border-bottom: 1px solid var(--color-fg);
}

/* Dropdown submenus ("Photographies", "Projets"). Mobile-first: sub-menus are always visible,
   indented inline (no hover to rely on for touch); from 700px up they collapse into a real
   hover/focus-within dropdown, matching the site's existing menu behaviour. */

.site-nav .sub-menu {
	display: flex;
	flex-direction: column;
	gap: var(--space-xs);
	margin: var(--space-xs) 0 0;
	padding: 0 0 0 var(--space-sm);
	list-style: none;
}

.site-nav li.menu-item-has-children > a::after {
	content: '';
	display: inline-block;
	width: 0.35em;
	height: 0.35em;
	margin-left: 0.4em;
	border-right: 1px solid currentColor;
	border-bottom: 1px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
}

@media (max-width: 699px) {
	.menu-toggle {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		gap: 5px;
		width: 1.75rem;
		height: 1.75rem;
		padding: 0;
		border: 0;
		background: none;
		cursor: pointer;
	}

	.menu-toggle__bar {
		display: block;
		width: 100%;
		height: 2px;
		background-color: currentColor;
	}

	#primary-menu {
		display: none;
		flex-basis: 100%;
		order: 3;
		padding-top: var(--space-sm);
	}

	#primary-menu.is-open {
		display: block;
	}

	.site-nav {
		flex-direction: column;
		gap: var(--space-sm);
	}
}

@media (min-width: 700px) {
	.site-nav li.menu-item-has-children {
		position: relative;
	}

	.site-nav .sub-menu {
		position: absolute;
		top: 100%;
		left: 0;
		z-index: 110;
		min-width: 220px;
		margin-top: var(--space-xs);
		padding: var(--space-xs) 0;
		background-color: var(--color-bg);
		border: 1px solid var(--color-border);
		opacity: 0;
		visibility: hidden;
		transform: translateY(4px);
		transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;
	}

	.site-nav li.menu-item-has-children:hover > .sub-menu,
	.site-nav li.menu-item-has-children:focus-within > .sub-menu {
		opacity: 1;
		visibility: visible;
		transform: translateY(0);
	}

	.site-nav .sub-menu a {
		display: block;
		padding: var(--space-xs) var(--space-sm);
		white-space: nowrap;
	}

	.site-nav .sub-menu a:hover,
	.site-nav .sub-menu a:focus-visible {
		background-color: var(--color-border);
		color: var(--color-fg);
	}
}

/* Light/dark toggle — visitor-facing override of the "automatique" palette (only rendered by
   header.php when the Customizer palette is left on "Automatique"). Sits right after the nav so
   it reads as part of the menu group; see assets/js/theme-toggle.js for the click handler. */
.theme-toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 1.75rem;
	height: 1.75rem;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	cursor: pointer;
}

.theme-toggle__icon {
	width: 1.25rem;
	height: 1.25rem;
}

/* Icon shown is the mode a click will switch *to*: moon while light is active, sun once dark is
   active — flipped by the `data-theme` attribute the inline script/toggle script set on <html>. */
.theme-toggle__icon--sun {
	display: none;
}

[data-theme='dark'] .theme-toggle__icon--moon {
	display: none;
}

[data-theme='dark'] .theme-toggle__icon--sun {
	display: block;
}

/* =========================================================================
   Home / "Toutes les galeries" — grille de couvertures de collection, légende
   sous l'image (titre + extrait), reprenant la présentation de
   charlesbergeron.com. Voir plan/design-siteweb-revision.md.
   ========================================================================= */

.collection-stack {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--space-lg) var(--space-md);
	padding: var(--space-lg) var(--container-padding);
	max-width: var(--container-width);
	margin-inline: auto;
}

@media (max-width: 900px) {
	.collection-stack {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 600px) {
	.collection-stack {
		grid-template-columns: 1fr;
	}
}

.collection-tile {
	display: block;
	text-decoration: none;
}

.collection-tile__media {
	display: block;
	position: relative;
	aspect-ratio: 4 / 3;
	overflow: hidden;
	border: 2px solid #000;
	background-color: var(--color-bg);
	background-size: cover;
	background-position: center;
	box-shadow: 0 14px 28px -10px rgba(0, 0, 0, 0.5);
}

/* Confirmed WebKit-specific bug (2026-07-10): when a box's height comes from `aspect-ratio`
   (rather than an explicit height) and it has padding, this engine resolves a percentage-height
   child (the global `picture { height: 100% }` rule) against its *border* box instead of its
   *content* box — the child then overflows past the padding/border on the bottom edge by exactly
   the padding+border amount (worse for portrait photos, which already render taller).
   Sidesteps that entirely: no more `padding` on `.collection-tile__media` above (an absolutely
   positioned child ignores its containing block's padding anyway — its containing block is the
   *padding edge*, so it would sit flush against the border, not respecting a 6px mat either way);
   the 6px white mat is recreated directly via `inset`, a plain pixel value immune to the
   percentage/aspect-ratio miscalculation. */
.collection-tile__media picture {
	position: absolute;
	inset: 6px;
	height: auto;
}

.collection-tile__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	transition: opacity 0.4s ease;
}

.collection-tile__image.is-loaded {
	opacity: 1;
}

.collection-tile__caption {
	display: block;
	margin-top: var(--space-xs);
}

.collection-tile__title {
	display: block;
	font-weight: 700;
	text-transform: uppercase;
}

.collection-tile__subtitle {
	display: block;
	font-style: italic;
}

/* =========================================================================
   Justified gallery + lightbox.
   plan/design-siteweb.md, "Galerie individuelle". Row layout is computed in
   assets/js/photography-minimal.js; the CSS below is the pre-JS/no-JS fallback
   and the visual chrome around it.
   ========================================================================= */

.justified-gallery {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: var(--gallery-gap);
	padding: var(--space-md) var(--container-padding);
	max-width: var(--container-width);
	margin-inline: auto;
}

.justified-gallery__item {
	flex: 0 0 auto;
	width: 480px;
	margin: 0;
}

.justified-gallery__trigger {
	display: block;
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	background-color: var(--color-border);
	background-size: cover;
	background-position: center;
	overflow: hidden;
	cursor: zoom-in;
}

.justified-gallery__image {
	width: 100%;
	height: 100%;
	object-fit: contain;
	opacity: 0;
	transition: opacity 0.4s ease;
}

.justified-gallery__image.is-loaded {
	opacity: 1;
}

.justified-gallery__caption {
	display: block;
	margin-top: var(--space-xs);
	font-size: var(--font-size-caption);
	color: var(--color-fg-muted);
}

/* Blog listing's justified grid (index.php) — same row-fill layout as the individual-gallery
   photo grid above, with its own title/date caption instead of a single caption line. */
.post-grid__item {
	text-decoration: none;
	color: inherit;
}

.post-grid__media {
	cursor: pointer;
}

.post-grid__title {
	display: block;
	font-weight: 700;
	text-transform: uppercase;
}

.post-grid__date {
	display: block;
	font-style: italic;
}

.lightbox {
	position: fixed;
	inset: 0;
	z-index: 1000;
	display: flex;
	align-items: center;
	justify-content: center;
	background-color: #000;
}

.lightbox[hidden] {
	display: none;
}

.lightbox__figure {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--space-sm);
	max-width: 92vw;
	max-height: 92vh;
	margin: 0;
}

.lightbox__image {
	max-width: 92vw;
	max-height: 80vh;
	width: auto;
	height: auto;
	object-fit: contain;
	opacity: 0;
	transition: opacity 0.3s ease;
}

.lightbox__image.is-loaded {
	opacity: 1;
}

.lightbox__exif {
	color: #d4d4d4;
	font-size: var(--font-size-caption);
	letter-spacing: 0.03em;
	text-align: center;
}

.lightbox__control {
	position: absolute;
	border: 0;
	background: none;
	color: #fff;
	cursor: pointer;
	line-height: 1;
	padding: var(--space-xs);
}

.lightbox__close {
	top: var(--space-sm);
	right: var(--container-padding);
	font-size: 2rem;
}

.lightbox__prev,
.lightbox__next {
	top: 50%;
	transform: translateY(-50%);
	font-size: 3rem;
}

.lightbox__prev {
	left: var(--container-padding);
}

.lightbox__next {
	right: var(--container-padding);
}

/* =========================================================================
   Slideshow (hero viewer + thumbnail strip) — individual gallery page.
   Replaces the justified grid + modal lightbox above for single-gallery.php: one photo at a time
   in a large viewer, thumbnails below jump straight to any photo. See
   assets/js/photography-minimal.js and template-parts/gallery-slideshow.php.
   ========================================================================= */

.slideshow {
	padding: var(--space-md) var(--container-padding) var(--space-lg);
}

/*
 * Height is set in px by assets/js/photography-minimal.js (layoutSlideshow): window height minus
 * everything above and below the viewer (header, title block, thumbnail strip, footer), so the
 * photo always uses exactly whatever room is left with no page scroll — recalculated on resize,
 * same pattern as the justified grid's row-height layout. min-height below is only the pre-JS/
 * no-JS fallback and the floor layoutSlideshow won't shrink past on very short viewports.
 */
.slideshow__viewer {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 250px;
	/* Blur-up placeholder (--slideshow-blur-placeholder set inline per photo, see
	   template-parts/gallery-slideshow.php) shown as the viewer's own background while the real
	   photo loads on top — a CSS custom property rather than `background-image` directly so the
	   :has() rule below (a real stylesheet rule) can still win once loaded; an inline `style`
	   attribute always beats a stylesheet rule for the same property otherwise. */
	background-image: var(--slideshow-blur-placeholder, none);
	background-size: cover;
	background-position: center;
}

.slideshow__viewer:has(.slideshow__image.is-loaded) {
	background-image: none;
}

/*
 * "Plein écran" toggle (.slideshow__fullscreen-toggle below) — a fixed-position overlay covering
 * the viewport, rather than the native Fullscreen API: iOS Safari in particular doesn't support
 * Element.requestFullscreen() on anything but <video>, so a real fullscreen request would silently
 * fail there. This CSS-only approach works identically on every browser.
 */
.slideshow.is-fullscreen {
	position: fixed;
	inset: 0;
	z-index: 1000;
	background-color: var(--color-bg);
	overflow-y: auto;
}

body.has-slideshow-fullscreen {
	overflow: hidden;
}

.slideshow__figure {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--space-xs);
	width: 100%;
	height: 100%;
	margin: 0;
	padding: var(--space-sm) 0;
}

/*
 * <picture> has no height of its own — it just wraps to whatever size the <img> renders at — so
 * .slideshow__image's `max-height: 100%` below would resolve against an undefined containing-block
 * height and be ignored (portrait photos then rendered at full natural size, overflowing the fixed-
 * height viewer down over the thumbnail strip). display: contents removes <picture>'s own box so
 * the <img> is a direct flex child of .slideshow__figure instead, whose height is always definite.
 */
.slideshow__figure picture {
	display: contents;
}

/*
 * Deliberately sized to its own intrinsic/rendered dimensions (max-width/max-height + width/
 * height auto) rather than stretched to fill the figure's width: that way the <img> element's own
 * box matches exactly what's visible on screen, with no invisible letterboxed "dead zone" around
 * a narrower photo — needed so .slideshow__viewer:has(.slideshow__image:hover) below only reveals
 * the prev/next controls when the cursor is actually over the photo, not just somewhere in the
 * (much wider) viewer.
 */
.slideshow__image {
	max-width: 100%;
	max-height: 100%;
	min-height: 0;
	width: auto;
	height: auto;
	object-fit: contain;
	opacity: 0;
	transition: opacity 0.3s ease;
}

.slideshow__image.is-loaded {
	opacity: 1;
}

.slideshow__caption {
	display: flex;
	flex-direction: column;
	align-items: center;
	flex: 0 0 auto;
	gap: 0.25em;
	text-align: center;
}

.slideshow__title {
	font-weight: 700;
}

.slideshow__exif {
	font-size: var(--font-size-caption);
	color: var(--color-fg-muted);
}

.slideshow__control {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 0;
	border-radius: 50%;
	background-color: rgba(0, 0, 0, 0.5);
	color: #fff;
	font-size: 1.75rem;
	line-height: 1;
	cursor: pointer;
	opacity: 0;
	transition: opacity 0.2s ease;
}

/* Hidden until the pointer is actually over the photo — :has() targets .slideshow__image
   specifically rather than .slideshow__viewer:hover, since the viewer is wider than the photo for
   any non-square image and hovering that empty margin shouldn't count. Also revealed when a
   control has keyboard focus (:focus-within keeps them from being invisible-but-focused, which
   would be a keyboard trap), and while hovering a control itself (moving the cursor onto the
   button leaves the image's own hover state). */
.slideshow__viewer:has(.slideshow__image:hover) .slideshow__control,
.slideshow__viewer:has(.slideshow__control:hover) .slideshow__control,
.slideshow__viewer:focus-within .slideshow__control {
	opacity: 1;
}

.slideshow__prev {
	left: var(--space-sm);
}

.slideshow__next {
	right: var(--space-sm);
}

.slideshow__actions {
	display: flex;
	justify-content: center;
	margin-top: var(--space-md);
}

/* Plain underlined-text treatment rather than a boxed button, to stay consistent with the rest of
   the site's minimalist "call to action" links. */
.slideshow__fullscreen-toggle {
	border: 0;
	background: none;
	padding: 0;
	color: var(--color-fg);
	font-size: 0.875rem;
	font-weight: 600;
	text-decoration: underline;
	cursor: pointer;
}

/* "Retour aux galeries" / "Galerie suivante" — closes the dead end at the end of a gallery
   (§3.1/H2), same discreet underlined-link treatment as .single-post__back. */
.slideshow__exit {
	display: flex;
	justify-content: space-between;
	gap: var(--space-sm);
	margin-top: var(--space-sm);
}

.slideshow__exit a {
	font-size: var(--font-size-caption);
	text-decoration: underline;
}

.slideshow__thumbnails {
	display: flex;
	flex: 0 0 auto;
	gap: var(--space-xs);
	margin-top: var(--space-md);
	padding-bottom: var(--space-xs);
	overflow-x: auto;
}

.slideshow__thumbnail {
	flex: 0 0 auto;
	width: 88px;
	height: 88px;
	padding: 0;
	border: 2px solid transparent;
	background-color: var(--color-border);
	overflow: hidden;
	cursor: pointer;
}

.slideshow__thumbnail.is-active {
	border-color: var(--color-fg);
}

.slideshow__thumbnail-image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	transition: opacity 0.3s ease;
}

.slideshow__thumbnail-image.is-loaded {
	opacity: 1;
}

/* =========================================================================
   À propos / Contact.
   plan/design-siteweb.md, "À propos / Contact".
   ========================================================================= */

.about {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--space-lg);
	max-width: var(--container-width);
	margin-inline: auto;
	padding: var(--space-md) var(--container-padding) var(--space-lg);
}

.about__portrait img {
	width: 100%;
	height: auto;
}

.about__bio {
	max-width: 60ch;
	margin-bottom: var(--space-lg);
}

.contact {
	max-width: var(--container-width);
	margin-inline: auto;
	padding: var(--space-md) var(--container-padding) var(--space-lg);
}

.contact__intro {
	max-width: 60ch;
	margin-bottom: var(--space-lg);
}

.contact-form__field {
	display: flex;
	flex-direction: column;
	gap: 0.375rem;
	margin: 0 0 var(--space-sm);
}

.contact-form__field label {
	font-size: 0.875rem;
	font-weight: 600;
}

.contact-form__field input,
.contact-form__field textarea {
	width: 100%;
	font: inherit;
	font-size: var(--font-size-body);
	color: var(--color-fg);
	background: var(--color-bg);
	border: 1px solid var(--color-border);
	padding: 0.625rem 0.75rem;
}

.contact-form__field--honeypot {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
}

.contact-form__submit button {
	padding: 0.75rem 1.5rem;
	background: var(--color-fg);
	color: var(--color-bg);
	border: 0;
	cursor: pointer;
	font-weight: 600;
	font-size: 0.875rem;
}

.contact-form__notice {
	padding: var(--space-xs) var(--space-sm);
	border: 1px solid var(--color-border);
	margin-bottom: var(--space-sm);
}

.contact-form__notice--success {
	border-color: #4a7a4a;
}

.contact-form__notice--error {
	border-color: #a94442;
}

@media (min-width: 900px) {
	.about {
		grid-template-columns: 5fr 7fr;
	}

	/* Aligns the portrait's top with the bio's first paragraph rather than the "À propos" title
	   above it — matches h1.page-title's own box height (padding-top + line-height + margin). */
	.about__portrait {
		margin-top: calc(var(--space-lg) + var(--font-size-title) * 1.15 + var(--space-sm));
	}
}

/* =========================================================================
   Blog (listing + single post).
   ========================================================================= */

.blog,
.single-post {
	max-width: var(--container-width);
	margin-inline: auto;
	padding: var(--space-md) var(--container-padding) var(--space-lg);
}

.blog__empty {
	color: var(--color-fg-muted);
}

.post-grid__sentinel {
	min-height: 1px;
	padding: var(--space-lg) 0;
	text-align: center;
	color: var(--color-fg-muted);
	font-size: var(--font-size-caption);
}

.pagination {
	display: flex;
	gap: var(--space-sm);
	justify-content: center;
	margin-top: var(--space-lg);
	font-size: var(--font-size-caption);
}

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

.single-post__meta {
	font-size: var(--font-size-caption);
	color: var(--color-fg-muted);
	margin-top: -0.5rem;
}

.single-post__thumb {
	margin: var(--space-md) 0;
}

.single-post__thumb img {
	width: 100%;
	height: auto;
}

.single-post__content {
	max-width: 60ch;
}

.single-post__back {
	margin-top: var(--space-lg);
}

.single-post__back a {
	font-size: var(--font-size-caption);
	text-decoration: underline;
}

/* =========================================================================
   Generic page content (page.php) — plain block-authored pages such as the
   privacy policy: no theme.json/block-library CSS is loaded (performance),
   so headings/lists/tables coming out of the_content() need their own rules
   here rather than relying on core's .wp-block-* styles.
   ========================================================================= */

.page__content {
	max-width: var(--container-width);
	margin-inline: auto;
	padding: var(--space-md) var(--container-padding) var(--space-xl);
	font-size: var(--font-size-caption);
	line-height: 1.5;
}

.page__content > * {
	max-width: 60ch;
}

.page__content h2 {
	margin-top: var(--space-md);
	font-size: 1rem;
}

.page__content h3 {
	font-weight: 600;
	font-size: 0.9375rem;
	margin: var(--space-sm) 0 var(--space-xs);
}

.page__content ul,
.page__content ol {
	margin: 0 0 var(--space-sm);
	padding-left: 1.25em;
}

.page__content li {
	margin-bottom: 0.375rem;
}

.page__content li:last-child {
	margin-bottom: 0;
}

.page__content figure {
	margin: 0 0 var(--space-md);
}

.page__content > .wp-block-table {
	max-width: 100%;
	overflow-x: auto;
}

.page__content table {
	width: 100%;
	border-collapse: collapse;
	table-layout: fixed;
}

.page__content th,
.page__content td {
	padding: var(--space-xs) var(--space-sm);
	border: 1px solid var(--color-border);
	text-align: left;
	vertical-align: top;
	overflow-wrap: break-word;
}

.page__content th {
	background-color: var(--color-page-bg);
	font-weight: 600;
}

/* =========================================================================
   Footer.
   ========================================================================= */

.site-footer {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-sm);
	padding: var(--space-md) var(--container-padding);
	font-size: var(--font-size-caption);
	color: var(--color-fg-muted);
	border-top: 1px solid var(--color-border);
}

.site-footer__social {
	display: flex;
	gap: var(--space-sm);
	margin: 0;
	padding: 0;
	list-style: none;
}

.site-footer__social a {
	display: flex;
	color: var(--color-fg-muted);
}

.site-footer__social a:hover,
.site-footer__social a:focus-visible {
	color: var(--color-fg);
}

.site-footer__social svg {
	width: 1rem;
	height: 1rem;
}

/* =========================================================================
   Reduced motion.
   Every transition on the site is a discreet opacity/transform fade (image fade-in, submenu,
   slideshow controls) — none is essential to understanding the UI, so visitors who asked their
   system to reduce motion get them collapsed to effectively instant rather than tuned one by one.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
