/*
Theme Name: Dos & Posture
Theme URI: https://github.com/NicoWolyusO/lanneau
Author: Dos & Posture project
Description: Custom block theme for the Dos & Posture rebuild. Presentation only: the content model, the editorial workflow and the professional identity live in the dos-posture-core plugin. No page builder, no bundled font, no third-party request.
Version: 0.3.17-v0
Requires at least: 6.7
Tested up to: 7.0
Requires PHP: 8.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: dos-posture
Tags: block-theme, accessibility-ready, editor-style, full-site-editing
*/

/*
 * Design tokens live in theme.json. This file holds only what theme.json
 * cannot express: component anatomy, focus behaviour, motion preferences and
 * the mobile navigation.
 *
 * No @font-face, no @import, no external URL. The page must be renderable with
 * zero outbound requests.
 */

/* ---------------------------------------------------------------------------
 * Focus. Never removed, never replaced by a shadow alone.
 * ------------------------------------------------------------------------ */

/* The ring is COMPOSITE, and the white halo is not decoration: measured on a
   #336699 button fill, the dark ring alone reaches only 2.61:1 against the
   fill, which is below the 3.00 required of a focus indicator. The halo
   separates the ring from whatever it sits on and brings the pair to 6.00:1.
   Removing the box-shadow silently breaks the contrast of the focus state on
   every filled button on the site. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline: 3px solid var(--wp--preset--color--deep);
	outline-offset: 2px;
	box-shadow: 0 0 0 2px var(--wp--preset--color--base);
	border-radius: 6px;
}

/* Inverted on the dark footer, where the dark ring would disappear. */
.dp-footer :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
	outline-color: var(--wp--preset--color--base);
	box-shadow: 0 0 0 2px var(--wp--preset--color--deep);
}

/* ---------------------------------------------------------------------------
 * Motion. Rare, short, and cancelled on request.
 * ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		/* Delays too: a `backwards` fill keeps its element invisible for the
		   whole delay, so staggered entrances would still play as a sequence
		   of pops instead of an instant render. */
		animation-delay: 0s !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		transition-delay: 0s !important;
		scroll-behavior: auto !important;
	}
}

/* ---------------------------------------------------------------------------
 * Base rhythm and reading comfort
 * ------------------------------------------------------------------------ */

/* ⛔ LA COUPURE DES MOTS LONGS EST POSÉE UNE FOIS, ICI, ET ELLE EST HÉRITÉE.
 *
 * Elle était appliquée PAR ÉNUMÉRATION — `h1, h2, h3, .dp-card__title`, puis
 * `.entry-content`, puis `.dp-topics__name` — et les commentaires de ces trois
 * règles disent eux-mêmes ce qui n'allait pas : « la coupure déclarée plus haut
 * n'atteignait pas ici ». Une liste de sélecteurs arrive toujours UN COMPOSANT
 * APRÈS le défaut, et rien ne signale l'oubli.
 *
 * MESURE DU 2026-09-02 qui a rendu ça concret, texte doublé à 320 px sur le site
 * local : `.dp-takeaways` débordait de 54 px, `.dp-video__frame` de 48,
 * `.dp-precautions` de 46, le bouton de recherche du cœur de 49 — quatre
 * composants, aucun dans l'énumération, tous découverts un par un.
 *
 * `overflow-wrap` est hérité : le poser sur `body` couvre ce qui existe et ce
 * qui n'est pas encore écrit. Ce qu'il NE fait PAS, et c'est la raison pour
 * laquelle les règles ci-dessous survivent : il n'ajoute ni `hyphens: auto` ni
 * `text-wrap: balance`, dont la portée reste délibérément étroite — une
 * césure automatique sur tout le site est un changement que personne n'a
 * demandé. Il ne coupe pas non plus un mot qui tient. */
body {
	text-rendering: optimizeLegibility;
	-webkit-text-size-adjust: 100%;
	overflow-wrap: break-word;
}

/* Long French titles must break rather than overflow at 320px.
 *
 * `hyphens: auto` used to apply at every width, which is why a 1440px article
 * showed "Le nerf sciatique : défini-tion, trajet et anatomie" — a hyphenated
 * heading in a column with room to spare, which reads as a template that has
 * lost control of its own typography. Hyphenation is a last resort for narrow
 * columns, so it is now scoped to them.
 *
 * `text-wrap: balance` evens out the lines of a short heading instead of
 * leaving one word alone on the last line. Browsers that do not support it
 * simply wrap as before. */
h1,
h2,
h3,
.dp-card__title {
	overflow-wrap: break-word;
	text-wrap: balance;
}

@media (width < 30rem) {
	h1,
	h2,
	h3,
	.dp-card__title {
		hyphens: auto;
	}
}

.entry-content > * + * {
	margin-block-start: var(--wp--preset--spacing--40);
}

/* Prose breaks a long unbreakable string rather than pushing the page sideways.
 *
 * MEASURED at 320px on /thierry-lanneau and /cabinet before this rule existed:
 * a 72-character word with no break opportunity took `scrollWidth` to 602 in a
 * 320px viewport from a list item, and to 503 from a paragraph - two-dimensional
 * scrolling, which is the WCAG 1.4.10 failure. The wrapping declared further up
 * did not reach here: it names `h1, h2, h3, .dp-card__title` and prose is none
 * of those.
 *
 * `overflow-wrap` ALONE, on purpose, and for the reason already written on
 * `.dp-topics__name`: joining the heading group would also grant
 * `text-wrap: balance` and, below 30rem, `hyphens: auto` - automatic
 * hyphenation across every paragraph of the site, a change nothing asked for.
 *
 * What this does NOT do: it does not hyphenate, and it does not break a word
 * that fits. A French compound long enough to overflow 280px is broken mid-word
 * rather than allowed to overflow, which is the trade the criterion forces. */
.entry-content {
	overflow-wrap: break-word;
}

/* Reading comfort in running prose: slightly looser leading than the page
   default, quiet underlines that only assert themselves on hover, and list
   markers in the soft accent rather than full ink. */
.entry-content p {
	line-height: 1.72;
}

.entry-content a {
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--wp--custom--accent-soft);
}

.entry-content a:hover {
	text-decoration-color: currentColor;
}

.entry-content li::marker {
	color: var(--wp--custom--accent-soft);
}

/* Touch targets. */
a,
button,
summary {
	min-height: 1px;
}

.dp-header a,
.dp-footer a,
.wp-block-navigation a,
.dp-topics__card,
.dp-related__link {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
}

/* ---------------------------------------------------------------------------
 * Header and site navigation
 * ------------------------------------------------------------------------ */

.dp-header {
	border-bottom: 1px solid var(--wp--preset--color--border);
	background-color: var(--wp--preset--color--base);
}

/* Logo — the BRAND-001 reference reconstruction: "DOS" light grey, the spine
   stroke in the brand blue, "POSTURE" in full ink. The SVG scales as one
   piece; the footer recolours it for the dark ground. */
.dp-logo {
	display: inline-flex;
	align-items: center;
	min-height: 44px;
	text-decoration: none;
}

.dp-logo__svg {
	display: block;
	height: 38px;
	width: auto;
}

.dp-logo__dos {
	fill: #55606D;
	font: 300 30px/1 var(--wp--preset--font-family--heading);
	letter-spacing: 2.5px;
}

.dp-logo__post {
	fill: var(--wp--preset--color--contrast);
	font: 700 34px/1 var(--wp--preset--font-family--heading);
	letter-spacing: 0.5px;
}

.dp-logo__spine {
	stroke: var(--wp--preset--color--primary);
	fill: none;
	stroke-linecap: round;
}

@media (min-width: 75em) {
	.dp-header .dp-logo__svg {
		height: 44px;
	}
}

.dp-footer .dp-logo__svg {
	height: 34px;
}

.dp-footer .dp-logo__dos {
	fill: #8FA6BC;
}

.dp-footer .dp-logo__post {
	fill: #FFFFFF;
}

.dp-footer .dp-logo__spine {
	stroke: var(--wp--custom--accent-soft);
}

.dp-header .wp-block-navigation {
	font-size: var(--wp--preset--font-size--small);
}

.dp-header .wp-block-navigation a {
	position: relative;
	text-decoration: none;
	color: var(--wp--preset--color--contrast);
	padding-block: 0.25rem;
}

/* The underline grows from the left instead of appearing: the direction's one
   navigation gesture. It lives on a pseudo-element so the layout never moves. */
.dp-header .wp-block-navigation a::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 2px;
	background: var(--wp--preset--color--primary);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.dp-header .wp-block-navigation a:hover::after,
.dp-header .wp-block-navigation a:focus-visible::after,
.dp-header .wp-block-navigation .current-menu-item > a::after {
	transform: scaleX(1);
}

/* Navigation and call-to-action sit in one actions group; the CTA is its own
   element, not a navigation item dressed as a button. */
.dp-header__actions {
	gap: 1.5rem;
}

/* Mobile bar per the content contract: wordmark, search, Menu. The appointment
   action moves inside the overlay, where it is the first reachable element.
   Selector depth matters: the generic touch-target rule (`.dp-header a`)
   forces inline-flex, and a hidden link must stay unfocusable. */
.dp-header a.dp-header__search {
	display: none;
	align-items: center;
	justify-content: center;
	min-width: 44px;
	min-height: 44px;
	color: var(--wp--preset--color--contrast);
}

.dp-header__search svg {
	width: 21px;
	height: 21px;
	stroke-width: 1.75;
}

@media (max-width: 37.4375em) {
	.dp-header .dp-cta {
		display: none;
	}

	.dp-header a.dp-header__search {
		display: inline-flex;
	}

	.dp-header__actions {
		gap: 0.25rem;
	}
}

.dp-cta a,
a.dp-cta {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 48px;
	white-space: nowrap;
	padding: 0.6rem 1.4rem;
	background-color: var(--wp--preset--color--primary);
	color: var(--wp--preset--color--base);
	text-decoration: none;
	border-radius: var(--wp--custom--radius-sm);
	font-weight: 600;
	letter-spacing: -0.012em;
	box-shadow: 0 1px 2px rgba(11, 36, 64, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.14);
	transition: background-color 0.16s, box-shadow 0.16s;
}

.dp-cta a:hover,
a.dp-cta:hover {
	background-color: var(--wp--preset--color--primary-strong);
	color: var(--wp--preset--color--base);
	box-shadow: 0 8px 22px -10px rgba(11, 36, 64, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

/* The CTA's decorative shadow outranks the global halo rule (0,1,1 vs 0,1,0):
   restate the composite ring here or the focused button loses its halo. */
.dp-cta a:focus-visible,
a.dp-cta:focus-visible {
	outline: 3px solid var(--wp--preset--color--deep);
	outline-offset: 2px;
	box-shadow: 0 0 0 2px var(--wp--preset--color--base);
}

/* hasIcon:false renders the visible label the content contract requires:
   the word "Menu", not a hamburger glyph alone. */
.dp-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
	font: 600 1rem/1 var(--wp--preset--font-family--body);
	letter-spacing: -0.012em;
	color: var(--wp--preset--color--contrast);
	padding-inline: 0.5rem;
}

/* ---------------------------------------------------------------------------
 * Mobile navigation overlay — the system's designed state 16.
 *
 * Requirements carried from the design foundation: a button with a VISIBLE
 * label (hasIcon:false makes core render the word "Menu"), Escape and the
 * close button both close (core), and "Prendre rendez-vous" is the first
 * entry of the menu list (the dialog's own close button precedes it in the
 * DOM) — which is why the extra `dp-nav-rdv` item exists in the menu markup.
 * In the horizontal row it is hidden: the header's own CTA already carries it.
 * ------------------------------------------------------------------------ */

.dp-header .wp-block-navigation__responsive-container:not(.is-menu-open) .dp-nav-rdv {
	display: none;
}

/* Tap-size the two menu buttons without redeclaring `display`: the core
   stylesheet hides the burger above its own 600px breakpoint with an
   equal-specificity rule, and this stylesheet loads after it. */
.dp-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
	min-width: 44px;
	min-height: 44px;
	align-items: center;
	justify-content: center;
}

.dp-header .wp-block-navigation__responsive-container-close {
	min-width: 44px;
	min-height: 44px;
	display: flex;
	align-items: center;
	justify-content: center;
}

.dp-header .wp-block-navigation__responsive-container.is-menu-open {
	background: linear-gradient(168deg, #0B2440 0%, #12365C 62%, #16406B 100%);
	color: #FFFFFF;
	padding: 1.25rem 1.5rem 2.5rem;
}

.dp-header .is-menu-open .wp-block-navigation__responsive-container-close {
	color: #FFFFFF;
}

.dp-header .is-menu-open .wp-block-navigation__responsive-container-content {
	padding-top: 2.5rem;
	align-items: flex-start;
	justify-content: flex-start;
}

/* Core aligns the overlay's list with a deeper selector than the plain
   container; restate the left alignment at that depth or lose it. */
.dp-header .is-menu-open .wp-block-navigation__container,
.dp-header .is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__container {
	flex-direction: column;
	align-items: flex-start;
	justify-content: flex-start;
	text-align: left;
	gap: 0.375rem;
}

.dp-header .is-menu-open .wp-block-navigation-item {
	justify-content: flex-start;
}

/* Big editorial links; the desktop underline gesture is retired here. */
.dp-header .is-menu-open .wp-block-navigation-item__content {
	color: #FFFFFF;
	font-family: var(--wp--preset--font-family--heading);
	font-size: clamp(1.625rem, 7vw, 2.125rem);
	font-weight: 700;
	letter-spacing: -0.03em;
	line-height: 1.15;
	padding-block: 0.4rem;
}

.dp-header .is-menu-open .wp-block-navigation-item__content::after {
	display: none;
}

.dp-header .is-menu-open .wp-block-navigation-item__content:hover {
	color: #CFE3F7;
}

/* The one action, first and dressed as such. */
.dp-header .is-menu-open .dp-nav-rdv {
	margin-block-end: 1.25rem;
}

.dp-header .is-menu-open .dp-nav-rdv .wp-block-navigation-item__content {
	display: inline-flex;
	align-items: center;
	min-height: 48px;
	background: #FFFFFF;
	color: #12365C;
	font-family: var(--wp--preset--font-family--body);
	font-size: 1.0625rem;
	font-weight: 600;
	letter-spacing: -0.012em;
	padding: 0.7rem 1.4rem;
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: 0 10px 26px -8px rgba(0, 0, 0, 0.5);
}

.dp-header .is-menu-open .dp-nav-rdv .wp-block-navigation-item__content:hover {
	background: var(--wp--custom--accent-mist);
	color: #0B2440;
}

/* Items rise in sequence when the overlay opens; reduced motion collapses it. */
.dp-header .is-menu-open .wp-block-navigation-item {
	animation: dp-rise 0.4s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

.dp-header .is-menu-open .wp-block-navigation-item:nth-child(2) {
	animation-delay: 0.05s;
}

.dp-header .is-menu-open .wp-block-navigation-item:nth-child(3) {
	animation-delay: 0.1s;
}

.dp-header .is-menu-open .wp-block-navigation-item:nth-child(4) {
	animation-delay: 0.15s;
}

.dp-header .is-menu-open .wp-block-navigation-item:nth-child(5) {
	animation-delay: 0.2s;
}

.dp-header .is-menu-open .wp-block-navigation-item:nth-child(6) {
	animation-delay: 0.25s;
}

/* Focus on the dark ground: inverted ring, same composite. */
.dp-header .is-menu-open :where(a, button):focus-visible {
	outline-color: #FFFFFF;
	box-shadow: 0 0 0 2px var(--wp--preset--color--deep);
}

/* The RDV button's decorative shadow outranks the ring rule above, so the
   composite must be restated at the same depth — the exact trap the design
   system documents on its own .btn. */
.dp-header .is-menu-open .dp-nav-rdv .wp-block-navigation-item__content:focus-visible {
	outline: 3px solid #FFFFFF;
	outline-offset: 2px;
	box-shadow: 0 0 0 2px var(--wp--preset--color--deep);
}

/* ---------------------------------------------------------------------------
 * Home hero: the radial wash and the hairline are the page's one scenic
 * gesture; everything inside stays plain typography.
 * ------------------------------------------------------------------------ */

.dp-hero {
	position: relative;
	/* The radial wash bleeds past the container by design; without clipping it
	   the page gains 260px of horizontal scroll. */
	overflow: hidden;
}

.dp-hero::before {
	content: "";
	position: absolute;
	left: -20%;
	right: -20%;
	top: -260px;
	height: 640px;
	z-index: -1;
	background:
		radial-gradient(50% 50% at 50% 50%, rgba(51, 102, 153, 0.13) 0%, rgba(51, 102, 153, 0) 72%),
		radial-gradient(34% 40% at 82% 38%, rgba(143, 180, 220, 0.12) 0%, rgba(143, 180, 220, 0) 70%);
	pointer-events: none;
}

/* Entrance: one short rise per hero child, staggered, played once on load.
   `backwards` keeps each element at its from-state during its delay. Under
   prefers-reduced-motion the global kill collapses it to an instant render,
   and without CSS animation support the elements simply stay visible. */
@keyframes dp-rise {
	from {
		opacity: 0;
		transform: translateY(14px);
	}

	to {
		opacity: 1;
		transform: none;
	}
}

.dp-hero__text > * {
	animation: dp-rise 0.55s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

.dp-hero__text > *:nth-child(2) {
	animation-delay: 0.06s;
}

.dp-hero__text > *:nth-child(3) {
	animation-delay: 0.12s;
}

.dp-hero__text > *:nth-child(4) {
	animation-delay: 0.18s;
}

.dp-hero__text > *:nth-child(5) {
	animation-delay: 0.24s;
}

.dp-hero__text > *:nth-child(6) {
	animation-delay: 0.3s;
}

.dp-hero__mediawrap {
	animation: dp-rise 0.65s 0.22s cubic-bezier(0.2, 0.7, 0.3, 1) backwards;
}

.dp-hero__label {
	margin: 0 0 1rem;
	font-size: 0.6875rem;
	font-weight: 700;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	color: var(--wp--preset--color--primary);
}

/* Le bloc `core/search` du cœur garde son champ à une largeur intrinsèque de 20
 * caractères, et le rembourrage de son bouton est en `em`. Mesure du 2026-09-02,
 * texte doublé à 320 px : le bouton part hors écran et la page déborde de 105 px
 * — sur les résultats de recherche ET sur le 404, deux gabarits que rien ne
 * visitait.
 *
 * ⛔ CE QUI NE MARCHE PAS, ET QUI A ÉTÉ ESSAYÉ PUIS MESURÉ : `flex-wrap: wrap`.
 * Il replie bien le bouton à texte doublé — mais AUSSI à texte NORMAL, à 320 et
 * 390 px : l'enveloppe passe de 53,64 à 99,69 px et la page grandit de 46 px.
 * C'est une régression du rendu ordinaire, et un point de rupture vers 400 px que
 * `06-responsive-specification.md` ne déclare pas. Une planche n'autorise pas à
 * décider un point de rupture, et un correctif de zoom encore moins.
 *
 * CE QUI MARCHE : laisser le CHAMP céder en premier. `flex: 1 1 0` lui donne une
 * base nulle, donc il absorbe tout le rétrécissement tant qu'il le peut ; le
 * bouton ne rétrécit qu'une fois le champ à zéro, c'est-à-dire seulement à texte
 * agrandi, et son libellé se replie alors dans sa propre boîte. Vérifié : à texte
 * normal, enveloppe et bouton sont identiques à l'octet près à l'état antérieur,
 * aux trois largeurs mesurées. */
.wp-block-search__inside-wrapper {
	min-width: 0;
}

.wp-block-search__input {
	min-width: 0;
	flex: 1 1 0;
}

/* Le rembourrage du bouton du cœur est en `em` : à texte doublé il passe de
 * 12/20 px à 25/48 px, et le bouton réclame 279 px dans une enveloppe de 240.
 * Le plafonner le fait tenir et son libellé se replie — mesuré à 320 px. */
.wp-block-search__button {
	min-width: 0;
	max-width: 100%;
}

/* ⛔ `min-width: auto` EST LA VALEUR PAR DÉFAUT D'UN ENFANT DE FLEX, et elle
 * l'empêche de descendre sous la largeur intrinsèque de son contenu —
 * rembourrage compris. À texte doublé, un rembourrage en `em` double, la largeur
 * minimale double avec lui, et l'enfant refuse de rentrer dans une colonne qui,
 * elle, n'a pas bougé. Mesuré le 2026-09-02 à 320 px : `.dp-card__bd` réclamait
 * 281 px dans une colonne de 240, `.dp-video__load-text` 192 px.
 * C'est le même correctif que sur `.dp-chapters__label`, et il vaut la peine
 * d'être groupé : la cause est une seule. */
.dp-card__bd,
.dp-video__load-text {
	min-width: 0;
}

/* Un encadré dont le rembourrage double ne doit pas déborder de son parent. */
.dp-precautions__rank {
	max-width: 100%;
}

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

/* Même défaut que sur le bloc `core/search`, un composant plus loin : à texte
 * doublé et 320 px, le champ garde sa largeur intrinsèque de 20 caractères et le
 * bouton, en `flex: none`, ne peut pas rétrécir — la boîte réclamait 274 px pour
 * 238 disponibles et poussait la page à 323. Mesuré le 2026-09-02.
 * Même correctif, et pour la même raison : `min-width: 0` des deux côtés et un
 * bouton qui devient rétrécissable, JAMAIS `flex-wrap` — mesuré, il repliait la
 * boîte à texte normal à 320 px et allongeait la page de 50 px. */
.dp-searchbox {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-block-start: var(--wp--preset--spacing--50);
	max-width: 34rem;
	padding: 0.4rem 0.4rem 0.4rem 0.9rem;
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--custom--field-border);
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: var(--wp--preset--shadow--raised);
	transition: border-color 0.16s;
}

.dp-searchbox:hover,
.dp-searchbox:focus-within {
	border-color: var(--wp--preset--color--primary);
}

/* The input suppresses its own outline because the BOX carries the ring:
   without this rule the search field has no visible focus indicator at all. */
.dp-searchbox:focus-within {
	outline: 3px solid var(--wp--preset--color--deep);
	outline-offset: 1px;
	box-shadow: 0 0 0 2px var(--wp--preset--color--base), var(--wp--preset--shadow--raised);
}

.dp-searchbox__icon {
	width: 20px;
	height: 20px;
	color: var(--wp--preset--color--primary);
	flex: none;
	stroke-width: 1.75;
}

.dp-searchbox input {
	flex: 1;
	min-width: 0;
	min-height: 44px;
	border: 0;
	outline: 0;
	background: none;
	font: inherit;
	color: var(--wp--preset--color--contrast);
}

.dp-searchbox input::placeholder {
	color: var(--wp--preset--color--ink-muted);
	opacity: 1;
}

.dp-searchbox input[type="search"] {
	min-width: 0;
}

.dp-searchbox__submit {
	flex: 0 1 auto;
	min-width: 0;
	max-width: 100%;
	cursor: pointer;
	border: 0;
	font-size: var(--wp--preset--font-size--small);
	padding: 0.55rem 1rem;
}

.dp-chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem;
	margin-block-start: var(--wp--preset--spacing--40);
}

.dp-chips__label {
	font-size: 0.875rem;
	font-weight: 500;
	color: var(--wp--preset--color--ink);
	margin-inline-end: 0.125rem;
}

.dp-chip {
	display: inline-flex;
	align-items: center;
	min-height: 36px;
	padding: 0 0.875rem;
	border-radius: 999px;
	background-color: var(--wp--custom--accent-pale);
	color: var(--wp--preset--color--primary-strong);
	font-size: 0.875rem;
	font-weight: 600;
	text-decoration: none;
	border: 1px solid transparent;
	transition: background-color 0.16s, border-color 0.16s;
}

.dp-chip:hover {
	background-color: var(--wp--custom--accent-mist);
	border-color: var(--wp--custom--accent-faint);
}

/* ---------------------------------------------------------------------------
 * Editorial image styles. The portrait is the system's 4/5 slot: the author
 * picks "Portrait (photo verticale, à droite)" in the Styles panel of any
 * image block and it composes itself — floated beside the prose on wide
 * screens, full column on narrow ones. It used to be a hand-typed
 * `dp-portrait` class that nothing in the interface offered; that class had no
 * single occurrence anywhere outside this stylesheet, so it was dropped rather
 * than kept as an alias the Styles panel could not represent.
 * ------------------------------------------------------------------------ */

.entry-content .wp-block-image img {
	border-radius: var(--wp--custom--radius-sm);
}

/* `is-style-dp-portrait` is what the Styles panel writes (see inc/blocks.php).
   The ratio, the rounding and the shadow are carried by the IMAGE, not by the
   figure: a figure clipped to 4/5 with `overflow: hidden` swallows its own
   `figcaption`, and `core/image` renders the caption inside the figure —
   measured on this install. The author would have lost every caption on a
   portrait, silently. */
.wp-block-image.is-style-dp-portrait {
	/* 300px: the reference value of dp-system.css, the most recent system. */
	max-width: 300px;
}

.wp-block-image.is-style-dp-portrait img {
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: var(--wp--preset--shadow--raised);
}

@media (min-width: 48em) {
	.entry-content .wp-block-image.is-style-dp-portrait {
		float: right;
		margin: 0 0 1.5rem 2.5rem;
	}
}

/* Image slots. Designed to hold without a photograph — the hatched surface IS
   the design until rights-cleared imagery is provided — and to accept one
   without recomposing the page. The local demo mu-plugin may fill them from
   the local media library; nothing is versioned. */
.dp-demo-slot {
	position: relative;
	display: flex;
	align-items: flex-end;
	padding: 12px;
	border-radius: var(--wp--custom--radius-sm);
	overflow: hidden;
	box-shadow: var(--wp--preset--shadow--raised);
	background:
		repeating-linear-gradient(135deg, transparent 0 11px, rgba(51, 102, 153, 0.055) 11px 22px),
		var(--wp--custom--accent-pale);
}

.dp-demo-slot img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	border-radius: 0;
}

/* Portrait slot in running prose: same geometry as the portrait image style,
   so an approved photograph substitutes without recomposing. */
.dp-slot-portrait {
	aspect-ratio: 4 / 5;
	/* A float shrinks to fit, and this slot's content is absolutely
	   positioned: without an explicit width the box collapses to nothing. */
	width: min(300px, 100%);
	margin: 0 0 1.5rem;
}

@media (min-width: 48em) {
	.entry-content .dp-slot-portrait {
		float: right;
		margin: 0.375rem 0 1.5rem 2.5rem;
	}
}

/* Wide editorial photo slot: a full-column 16/9 surface. */
.dp-slot-photo {
	aspect-ratio: 16 / 9;
	margin: 0 0 var(--wp--preset--spacing--50);
}

/* The system's hero image is 16/11 on mobile, 4/3.4 on desktop — the page
   composes without it either way. */
.dp-hero__media {
	display: flex;
	aspect-ratio: 16 / 11;
}

.dp-hero__media-label {
	font-size: 0.71875rem;
	font-weight: 600;
	line-height: 1.35;
	color: var(--wp--preset--color--primary-strong);
	background: rgba(255, 255, 255, 0.92);
	border-radius: 6px;
	padding: 6px 9px;
	position: relative;
}

@media (min-width: 64em) {
	.dp-hero__inner {
		display: grid;
		grid-template-columns: minmax(0, 1.02fr) minmax(0, 1fr);
		gap: 4.75rem;
		align-items: center;
	}

	.dp-hero__media {
		/* The retained direction composes the hero image at 4/3.4: taller than
		   a 16/11 thumbnail, short of a portrait, so the text column keeps the
		   lead. */
		aspect-ratio: 4 / 3.4;
	}
}

.dp-hero__text h1 {
	max-width: 16ch;
	margin-block: 0;
}

.dp-hero__stand {
	margin-block: 1.125rem 0;
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.6;
	color: var(--wp--preset--color--ink);
	max-width: 44ch;
}

.dp-hero__disc {
	margin-block: 1.75rem 0;
	font-size: 0.875rem;
	line-height: 1.6;
	color: var(--wp--preset--color--ink-muted);
	border-inline-start: 2px solid var(--wp--custom--accent-faint);
	padding-inline-start: 0.9375rem;
	max-width: 58ch;
}

/* ---------------------------------------------------------------------------
 * Home sections
 * ------------------------------------------------------------------------ */

.dp-assure {
	margin-block-start: var(--wp--preset--spacing--70);
}

.dp-assure__list {
	list-style: none;
	margin: 0;
	padding: var(--wp--preset--spacing--50) 0 0;
	border-top: 1px solid var(--wp--preset--color--border);
	display: grid;
	gap: 1.125rem;
}

@media (min-width: 48em) {
	.dp-assure__list {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: 2rem;
	}
}

.dp-assure__list li {
	display: flex;
	gap: 0.8125rem;
	align-items: flex-start;
}

.dp-assure__icon {
	width: 36px;
	height: 36px;
	border-radius: 999px;
	background: var(--wp--custom--accent-pale);
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--wp--preset--color--primary);
}

.dp-assure__icon svg {
	width: 18px;
	height: 18px;
	stroke-width: 2;
}

.dp-assure__list b {
	display: block;
	font-weight: 600;
	font-size: 0.96875rem;
	letter-spacing: -0.012em;
}

.dp-assure__d {
	display: block;
	color: var(--wp--preset--color--ink);
	font-size: 0.90625rem;
	margin-top: 0.1875rem;
	line-height: 1.5;
}

.dp-sec {
	margin-block-start: var(--wp--preset--spacing--80);
}

.dp-sec__head {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 0.875rem 1.5rem;
	flex-wrap: wrap;
	margin-block-end: 1.625rem;
}

.dp-sec__head h2 {
	margin: 0;
}

.dp-more {
	font-weight: 600;
	font-size: 0.9375rem;
	/* `white-space: nowrap` was REMOVED here on 2026-08-31 by L8-007: it was the
	   third carrier of the WCAG 1.4.4 failure. It made the link's minimum width
	   equal to its full width — 406px at a 320px viewport with text at 200%,
	   inside a 225px container.

	   WHAT IT COSTS, re-measured on 2026-08-31 after an adversarial review showed
	   the first explanation here was WRONG. `.dp-more` is inline-flex with
	   `flex-wrap: nowrap` and `align-items: center`: its two flex items stay on
	   ONE flex line, so the arrow is centred against the whole text block — it is
	   NOT carried onto the last line, and the earlier claim that it was came from
	   a coarse heuristic, not from a measurement.

	   The real geometry at 320px and 200%: the link is 99px tall, its text sits on
	   two lines (4-44 and 54-94), and the arrow spans 42-58 — centred at 50, while
	   the last line centres at 74. So the arrow STRADDLES the boundary between the
	   two lines. That is a minor typographic blemish, not a loss: the link keeps
	   one accessible name, one target, and its full text. The fix stands; only the
	   reason for it needed correcting. */
	display: inline-flex;
	align-items: center;
	gap: 0.4375rem;
	text-decoration: none;
	color: var(--wp--preset--color--primary);
}

.dp-more:hover {
	color: var(--wp--preset--color--primary-strong);
}

.dp-more svg {
	width: 16px;
	height: 16px;
	stroke-width: 2;
	transition: transform 0.18s;
}

.dp-more:hover svg {
	transform: translateX(3px);
}

/* Home content cards: bordered, lifted on hover — the list treatment stays
   for archives, this is the showcase treatment of the retained direction.
   The visual strip always renders: a quiet gradient by default, the featured
   image over it when one exists. */
/* `1fr` vaut `minmax(auto, 1fr)`, et ce `auto` est la largeur MINIMALE du
 * contenu : une piste de grille refuse donc de descendre sous ce que sa carte
 * réclame. À texte doublé et 320 px, les quatre cartes réclamaient 281 px dans
 * une grille de 240 et débordaient de 2 px chacune — la page passait à 323.
 * `min-width: 0` sur l'ÉLÉMENT de grille remet sa contribution minimale à zéro,
 * et la piste redevient élastique. Mesuré le 2026-09-02. */
.dp-home-cards .wp-block-post {
	min-width: 0;
}

.dp-home-cards .dp-card {
	height: 100%;
	padding: 0;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: var(--wp--preset--shadow--low);
	transition: box-shadow 0.18s, border-color 0.18s, transform 0.18s;
}

.dp-home-cards .dp-card:hover {
	border-color: var(--wp--custom--accent-faint);
	box-shadow: var(--wp--preset--shadow--raised);
	transform: translateY(-3px);
}

.dp-card__ph {
	position: relative;
	aspect-ratio: 16 / 7;
	overflow: hidden;
	transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.3, 1);
	/* The default cover is designed, not empty: a quiet blue wash under a set
	   of flowing contour lines — movement, drawn with the brand ink only. The
	   inline SVG keeps the page at zero outbound requests. A featured image,
	   when one exists, simply sits on top of it. */
	background-image:
		url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 175'%3E%3Cg fill='none' stroke='%23336699' stroke-opacity='.11' stroke-width='1.5'%3E%3Cpath d='M-20 148 C 90 108 150 168 260 128 S 430 98 430 98'/%3E%3Cpath d='M-20 163 C 90 123 150 183 260 143 S 430 113 430 113'/%3E%3Cpath d='M-20 178 C 90 138 150 198 260 158 S 430 128 430 128'/%3E%3Cpath d='M-20 133 C 90 93 150 153 260 113 S 430 83 430 83' stroke-opacity='.06'/%3E%3C/g%3E%3C/svg%3E"),
		radial-gradient(120% 140% at 15% 0%, rgba(51, 102, 153, 0.14), transparent 60%),
		linear-gradient(145deg, var(--wp--custom--accent-mist), var(--wp--custom--accent-pale));
	background-size: cover, auto, auto;
	background-position: center bottom, 0 0, 0 0;
}

.dp-home-cards .dp-card:hover .dp-card__ph {
	transform: scale(1.03);
}

/* Four sibling covers, four moods of the same blue: the grid reads as a set,
   not as one tile repeated. */
.dp-home-cards .wp-block-post:nth-child(4n+2) .dp-card__ph {
	background-image:
		url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 175'%3E%3Cg fill='none' stroke='%23336699' stroke-opacity='.11' stroke-width='1.5'%3E%3Cpath d='M-20 148 C 90 108 150 168 260 128 S 430 98 430 98'/%3E%3Cpath d='M-20 163 C 90 123 150 183 260 143 S 430 113 430 113'/%3E%3Cpath d='M-20 178 C 90 138 150 198 260 158 S 430 128 430 128'/%3E%3C/g%3E%3C/svg%3E"),
		radial-gradient(110% 130% at 88% 8%, rgba(27, 74, 120, 0.16), transparent 62%),
		linear-gradient(215deg, #DFEAF5, var(--wp--custom--accent-pale));
	background-position: right top, 0 0, 0 0;
}

.dp-home-cards .wp-block-post:nth-child(4n+3) .dp-card__ph {
	background-image:
		url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 175'%3E%3Cg fill='none' stroke='%231B4A78' stroke-opacity='.10' stroke-width='1.5'%3E%3Cpath d='M-20 40 C 90 80 150 20 260 60 S 430 90 430 90'/%3E%3Cpath d='M-20 25 C 90 65 150 5 260 45 S 430 75 430 75'/%3E%3Cpath d='M-20 55 C 90 95 150 35 260 75 S 430 105 430 105'/%3E%3C/g%3E%3C/svg%3E"),
		radial-gradient(120% 150% at 12% 96%, rgba(143, 180, 220, 0.2), transparent 58%),
		linear-gradient(165deg, var(--wp--custom--accent-pale), var(--wp--custom--accent-mist));
	background-position: center top, 0 0, 0 0;
}

.dp-home-cards .wp-block-post:nth-child(4n) .dp-card__ph {
	background-image:
		url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 175'%3E%3Cg fill='none' stroke='%23336699' stroke-opacity='.09' stroke-width='1.5'%3E%3Ccircle cx='330' cy='40' r='56'/%3E%3Ccircle cx='330' cy='40' r='86'/%3E%3Ccircle cx='330' cy='40' r='118'/%3E%3C/g%3E%3C/svg%3E"),
		radial-gradient(120% 140% at 85% 100%, rgba(51, 102, 153, 0.12), transparent 60%),
		linear-gradient(125deg, #E9F0F8, #F4F8FC);
	background-position: right top, 0 0, 0 0;
}

.dp-card__ph .wp-block-post-featured-image {
	position: absolute;
	inset: 0;
	margin: 0;
}

.dp-card__ph .wp-block-post-featured-image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.dp-card__bd {
	display: flex;
	flex-direction: column;
	flex: 1;
	padding: 1.25rem 1.375rem 1.375rem;
}

.dp-card__kicker,
.dp-kicker {
	margin: 0 0 0.75rem;
	align-self: flex-start;
	display: inline-flex;
	align-items: center;
	font-size: 0.65625rem;
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--wp--preset--color--primary-strong);
	background: var(--wp--custom--accent-pale);
	border-radius: 6px;
	padding: 6px 9px;
}

/* The row wrapper exists so the constrained layout centres it like any other
   block; the badge itself must not carry auto margins or it would centre. */
.dp-kicker-row {
	margin-block: 1.125rem 0.25rem;
}

.dp-kicker-row .dp-kicker {
	margin: 0;
}

.dp-home-cards .dp-card__title {
	margin: 0;
}

.dp-home-cards .dp-card__excerpt {
	margin-block-start: 0.6875rem;
	flex: 1;
}

.dp-card__foot {
	margin-block-start: 1rem;
	padding-block-start: 0.875rem;
	border-top: 1px solid var(--wp--preset--color--border);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.75rem;
	flex-wrap: wrap;
}

.dp-card__more {
	font-weight: 600;
	font-size: 0.9375rem;
}

.dp-card__more a,
a.dp-card__more {
	display: inline-flex;
	align-items: center;
	gap: 0.4375rem;
	color: var(--wp--preset--color--primary);
	text-decoration: none;
}

.dp-card__more a::after,
a.dp-card__more::after {
	content: "→";
	transition: transform 0.18s;
}

.dp-card__more a:hover,
a.dp-card__more:hover {
	color: var(--wp--preset--color--primary-strong);
}

.dp-card__more a:hover::after,
a.dp-card__more:hover::after {
	transform: translateX(3px);
}

.dp-card__date {
	font-size: 0.84375rem;
	color: var(--wp--preset--color--ink-muted);
	font-variant-numeric: tabular-nums;
}

/* The volumetry card — real counts, rendered by dos-posture/content-counts.
   In flow under the image on mobile; floating over its lower edge on desktop. */
.dp-hero__mediawrap {
	display: block;
	margin-block-start: 1.75rem;
}

.dp-counts {
	display: flex;
	/* RISK-039, fourth carrier. Without `wrap` the three blocks are flex items
	   pinned to their min-content floor: at 320px with text at 200% they stop
	   fitting, the third leaves to the right, and the `overflow: hidden` on
	   `.dp-hero` — which exists to clip the ::before wash — DELETES it from view
	   instead of making it reachable. The "6 thèmes" block had its LEFT edge at
	   350px inside a 320px viewport.

	   THE CHOICE, MEASURED at 320px: without zoom, height 71px and two separators
	   — EXACTLY the previous state, so this declaration never shows in ordinary
	   use. At 200%, height 342px, nothing off-screen, labels on a single line.
	   Above 64em, where the card is absolutely positioned inside the hero, wrap
	   and nowrap measure IDENTICALLY at 1440px, and at 1024px the card grows
	   upward without ever leaving the hero. The two competing options were
	   measured and discarded: `min-width: 0` breaks "articles" across seven
	   lines, and hiding the separators below 30rem removes them AT 100% too. */
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 0.625rem;
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: var(--wp--preset--shadow--low);
	padding: 0.875rem 1rem;
	margin-block-start: 0.75rem;
}

@media (min-width: 64em) {
	.dp-hero__mediawrap {
		position: relative;
		margin-block: 0 1.625rem;
	}

	.dp-counts {
		position: absolute;
		left: 14px;
		right: 14px;
		bottom: -26px;
		margin-block-start: 0;
		background: rgba(255, 255, 255, 0.94);
		backdrop-filter: blur(12px) saturate(1.3);
		box-shadow: var(--wp--preset--shadow--raised);
	}
}

.dp-counts > div {
	text-align: center;
	flex: 1;
}

.dp-counts b {
	display: block;
	font-size: 1.375rem;
	font-weight: 700;
	line-height: 1;
	color: var(--wp--preset--color--primary);
	letter-spacing: -0.03em;
	font-variant-numeric: tabular-nums;
}

.dp-counts span {
	display: block;
	margin-top: 0.375rem;
	font-size: 0.65625rem;
	font-weight: 600;
	line-height: 1.2;
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--wp--preset--color--ink-muted);
}

.dp-counts i {
	width: 1px;
	background: var(--wp--preset--color--border);
	flex: none;
}

/* ---------------------------------------------------------------------------
 * Video band: the home's second dark surface, echoing the footer.
 * ------------------------------------------------------------------------ */

.dp-videos-band {
	position: relative;
	overflow: hidden;
	margin-block-start: var(--wp--preset--spacing--80);
	padding-block: var(--wp--preset--spacing--80);
	background-color: var(--wp--preset--color--deep);
	color: #DCE8F4;
}

.dp-videos-band::before {
	content: "";
	position: absolute;
	left: -20%;
	top: -40%;
	width: 620px;
	height: 620px;
	border-radius: 50%;
	background: radial-gradient(circle, rgba(143, 180, 220, 0.16), transparent 66%);
	pointer-events: none;
}

.dp-videos-band h2 {
	color: #FFFFFF;
	margin: 0;
}

.dp-videos-band__label {
	color: var(--wp--custom--accent-soft);
	margin-block-end: 0.75rem;
}

.dp-videos-band__sub {
	margin: 0.625rem 0 0;
	color: #9FBBD8;
	font-size: 1rem;
	line-height: 1.6;
	max-width: 56ch;
}

.dp-more--light {
	color: #CFE3F7;
}

.dp-more--light:hover {
	color: #FFFFFF;
}

.dp-vcard {
	height: 100%;
	border-radius: var(--wp--custom--radius-sm);
	overflow: hidden;
	background: rgba(255, 255, 255, 0.05);
	border: 1px solid rgba(220, 232, 244, 0.16);
	transition: background-color 0.18s, border-color 0.18s, transform 0.18s;
}

.dp-vcard:hover {
	background: rgba(255, 255, 255, 0.09);
	border-color: rgba(220, 232, 244, 0.3);
	transform: translateY(-3px);
}

.dp-vcard__poster {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 16 / 9;
	background:
		radial-gradient(70% 80% at 30% 20%, rgba(255, 255, 255, 0.1), transparent 65%),
		linear-gradient(145deg, #123B63, #1E5A93);
}

/* Sibling posters vary in the same deep register, like chapters of one film. */
.wp-block-post:nth-child(3n+2) .dp-vcard__poster {
	background:
		radial-gradient(75% 85% at 72% 24%, rgba(143, 180, 220, 0.18), transparent 66%),
		linear-gradient(160deg, #0E2E4F, #2A6099);
}

.wp-block-post:nth-child(3n) .dp-vcard__poster {
	background:
		radial-gradient(80% 90% at 50% 88%, rgba(255, 255, 255, 0.09), transparent 62%),
		linear-gradient(120deg, #16406B, #123B63 55%, var(--wp--custom--accent-deep));
}

.dp-vcard__play {
	width: 56px;
	height: 56px;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.96);
	display: flex;
	align-items: center;
	justify-content: center;
	box-shadow: 0 10px 26px -8px rgba(0, 0, 0, 0.6);
	transition: transform 0.2s;
}

.dp-vcard:hover .dp-vcard__play {
	transform: scale(1.07);
}

.dp-vcard__play::after {
	content: "";
	border-left: 16px solid #12365C;
	border-top: 10px solid transparent;
	border-bottom: 10px solid transparent;
	margin-left: 5px;
}

.dp-vcard__body {
	padding: 1rem 1.125rem 1.125rem;
}

.dp-vcard__title {
	font-size: 1.0625rem;
	margin: 0;
	letter-spacing: -0.022em;
}

.dp-vcard__title a {
	color: #FFFFFF;
	text-decoration: underline;
	text-underline-offset: 4px;
	text-decoration-thickness: 1px;
	text-decoration-color: rgba(207, 227, 247, 0.35);
}

.dp-vcard__title a:hover {
	color: #CFE3F7;
	text-decoration-color: currentColor;
}

/* The card announces the duration and the transcription before any click:
   what the content contract requires a video card to say. */
.dp-vcard-meta {
	margin: 0.5rem 0 0;
	font-size: 0.84375rem;
	color: #9FBBD8;
	font-variant-numeric: tabular-nums;
}

/* Light variant for the video archive: same poster, editorial card chrome. */
.dp-vcard--light {
	background: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border);
	box-shadow: var(--wp--preset--shadow--low);
}

.dp-vcard--light:hover {
	background: var(--wp--preset--color--base);
	border-color: var(--wp--custom--accent-faint);
	box-shadow: var(--wp--preset--shadow--raised);
}

.dp-vcard--light .dp-vcard__title a {
	color: var(--wp--preset--color--contrast);
	text-decoration-color: var(--wp--custom--accent-faint);
}

.dp-vcard--light .dp-vcard__title a:hover {
	color: var(--wp--preset--color--primary-strong);
	text-decoration-color: currentColor;
}

.dp-vcard--light .dp-vcard-meta {
	color: var(--wp--preset--color--ink-muted);
}

.dp-vcard--light .dp-vcard__excerpt {
	margin: 0.5rem 0 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
}

.dp-videos-band :where(a, button):focus-visible {
	outline-color: #FFFFFF;
	box-shadow: 0 0 0 2px var(--wp--preset--color--deep);
}

/* ---------------------------------------------------------------------------
 * Who writes / closing call-to-action band
 * ------------------------------------------------------------------------ */

.dp-who__inner {
	display: grid;
	gap: 1.75rem;
	align-items: center;
	/* SAME DEFECT AS `.dp-topics__grid`, AND THIS ONE SET THE 1.4.4 FAILURE.
	   `grid-template-columns` existed only from 48em; below it the implicit
	   `auto` column took the min-content width of `.dp-who__text`. Measured
	   2026-08-31 with text at 200%: computed track 405.53px AT 320 AS WELL AS AT
	   390px, right edge 446px — exactly the page's scrollWidth, gutters included.
	   RISK-039 blamed the topics grid alone: that was a PARTIAL cause, and at
	   390px the topics grid did not overflow at all. */
	grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 48em) {
	.dp-who__inner {
		grid-template-columns: 280px minmax(0, 1fr);
		gap: 3.5rem;
	}
}

.dp-who__media {
	aspect-ratio: 4 / 5;
	max-width: 280px;
}

.dp-who__text h2 {
	margin-block: 0 0.875rem;
}

.dp-who__text p:not(.dp-hero__label) {
	margin: 0 0 1.25rem;
	color: var(--wp--preset--color--ink);
	max-width: 52ch;
}

.dp-who__links {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 1.75rem;
}

.dp-cta-band {
	position: relative;
	overflow: hidden;
	margin-block-start: var(--wp--preset--spacing--80);
	padding-block: var(--wp--preset--spacing--70);
	background: linear-gradient(150deg, #12365C 0%, #1B4A78 55%, var(--wp--custom--accent-deep) 100%);
	color: #FFFFFF;
}

.dp-cta-band::before {
	content: "";
	position: absolute;
	right: -14%;
	top: -46%;
	width: 460px;
	height: 460px;
	border-radius: 50%;
	background: radial-gradient(circle, rgba(255, 255, 255, 0.10), transparent 66%);
	pointer-events: none;
}

.dp-cta-band__inner {
	position: relative;
}

.dp-cta-band__inner h2 {
	color: #FFFFFF;
	margin: 0;
	max-width: 17ch;
}

.dp-cta-band__inner p {
	color: #C9DDF0;
	margin: 1rem 0 0;
	max-width: 46ch;
	font-size: 1.03125rem;
}

.dp-cta-band__actions {
	margin-block-start: 1.625rem;
	display: flex;
	gap: 0.6875rem;
	flex-wrap: wrap;
}

.dp-btn-white,
.dp-btn-outline {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 50px;
	padding: 0 1.5rem;
	border-radius: var(--wp--custom--radius-sm);
	font-weight: 600;
	letter-spacing: -0.012em;
	text-decoration: none;
	transition: background-color 0.16s, color 0.16s;
}

.dp-btn-white {
	background: #FFFFFF;
	color: #12365C;
}

.dp-btn-white:hover {
	background: var(--wp--custom--accent-mist);
	color: #0B2440;
}

.dp-btn-outline {
	border: 1px solid rgba(255, 255, 255, 0.55);
	color: #FFFFFF;
}

.dp-btn-outline:hover {
	background: rgba(255, 255, 255, 0.1);
}

.dp-cta-band :where(a, button):focus-visible {
	outline-color: #FFFFFF;
	box-shadow: 0 0 0 2px #1B4A78;
}

/* ---------------------------------------------------------------------------
 * Summary of a long page
 *
 * Transposed from the accepted plate (`archetypes-l5-003/deltas.css`), not
 * redesigned. Three of its rules are load-bearing and each is written there:
 *
 *   - NOT `position: sticky`. A sticky summary eats the usable height at 320px,
 *     and 1.4.10 is measured at that width.
 *   - the entries are at least 24px tall. A first version of the plate left them
 *     at 19px, under WCAG 2.5.8 (AA), and a summary entry is not a link inside a
 *     sentence, so the inline exception does not cover it. The padding carries
 *     the target; `block` makes it measurable AND keeps the number level with
 *     the title: an outside marker sits on the item's first line, which a block
 *     link carries, padding included. An `inline-block` link left the number
 *     4px above the title (top-aligned) or on its last line (baseline-aligned);
 *     Thierry saw it on 03/11. `tools/design/check_toc.py` measures it.
 *   - an ORDERED list, because the order is the information.
 * ------------------------------------------------------------------------ */

.dp-toc {
	margin-block: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
	padding: 14px 16px;
	border: 1px solid var(--wp--preset--color--border);
	border-inline-start: 3px solid var(--wp--preset--color--primary);
	border-radius: var(--wp--custom--radius-sm);
	background-color: var(--wp--preset--color--surface);
}

.dp-toc__title {
	margin: 0 0 0.625rem;
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.3;
	color: var(--wp--preset--color--ink);
}

.dp-toc__list {
	margin: 0;
	padding-inline-start: 1.25em;
}

.dp-toc__item + .dp-toc__item {
	margin-block-start: 2px;
}

.dp-toc__link {
	display: block;
	padding-block: 4px;
	min-height: 24px;
	text-decoration-thickness: 1px;
	text-underline-offset: 2px;
}

/* Mobile first: the values above are those of 320px.
 *
 * ⛔ THE PLATE'S OWN BREAKPOINT WAS NOT CARRIED OVER, and the guard is why.
 * `deltas.css` steps this padding at `30em`; `check_responsive.py` refused it —
 * `06-responsive-specification.md` decides SIX thresholds for this theme and
 * `(min-width: 30em)` is not one of them. Adding a seventh threshold to the
 * product for a padding refinement would decide a breakpoint in passing, so the
 * step moves to the nearest DECIDED one. The plate is a reference for shape, not
 * a licence to add thresholds. */
@media (min-width: 40em) {
	.dp-toc {
		padding: 18px 20px;
	}
}

/* ---------------------------------------------------------------------------
 * Breadcrumb
 * ------------------------------------------------------------------------ */

.dp-breadcrumb__list {
	display: flex;
	flex-wrap: wrap;
	gap: 0.25rem 0.5rem;
	list-style: none;
	margin: 0;
	padding: 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

/* A breadcrumb link is a navigation control, not prose: it does not get the
   inline exception of WCAG 2.2 target size, so it is padded to 24px. */
.dp-breadcrumb__item a {
	display: inline-block;
	min-height: 24px;
	padding-block: 0.15rem;
}

.dp-breadcrumb__item + .dp-breadcrumb__item::before {
	content: "›";
	margin-inline-end: 0.5rem;
	color: var(--wp--custom--accent-soft);
}

.dp-breadcrumb__item a {
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--wp--custom--accent-faint);
}

.dp-breadcrumb__item a:hover {
	color: var(--wp--preset--color--primary-strong);
	text-decoration-color: currentColor;
}

/* ---------------------------------------------------------------------------
 * Article metadata
 * ------------------------------------------------------------------------ */

.dp-article-meta {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

.dp-article-meta__line {
	display: flex;
	flex-wrap: wrap;
	gap: 0.25rem 1rem;
	margin: 0;
}

.dp-article-meta__due {
	margin-block-start: 0.5rem;
	padding: 0.5rem 0.75rem;
	background-color: var(--wp--preset--color--caution-surface);
	border-inline-start: 4px solid var(--wp--preset--color--caution);
	color: var(--wp--preset--color--caution);
}

/* ---------------------------------------------------------------------------
 * Precautions. Meaning is carried by the heading and the icon, not by colour.
 * ------------------------------------------------------------------------ */

.dp-precautions {
	background-color: var(--wp--preset--color--caution-surface);
	border-inline-start: 4px solid var(--wp--preset--color--caution);
	border-radius: 0 var(--wp--custom--radius-sm) var(--wp--custom--radius-sm) 0;
	padding: var(--wp--preset--spacing--40);
	/* After the shorthand, deliberately: the 4px rule counts as part of the
	   inset, or this box lands 2px off the reading column that every other
	   outdented box lines up on. */
	padding-inline-start: calc(var(--wp--preset--spacing--40) - 4px);
}

.dp-precautions__title {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-block-start: 0;
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--caution);
	/* `anywhere` and not `break-word`: the heading is a flex container whose
	   text is an ANONYMOUS flex item, so no selector can give it
	   `min-inline-size: 0` — and its automatic minimum size is the width of its
	   longest word. `anywhere` is the one value that also drives the intrinsic
	   minimum to zero, which is what actually lets it shrink. Same family of
	   defect as the `minmax(0, 1fr)` tracks on the grids, in a flex context.

	   MEASURED at 320px with text at 200% (WCAG 1.4.4) on the article: the
	   heading carried 85px of content outside its own box, and "Consulter sans
	   attendre" is a medical alert — a clipped one is worse than an ugly one.
	   After: 0px. Document overflow 13px -> 5px. `flex-wrap: wrap` was measured
	   too and only got the heading to 31px, so it is not used. */
	overflow-wrap: anywhere;
}

/* The icon is the SECOND of the four signals that separate the two cases
   without colour - triangle frames, circle interrupts - so it is drawn, not
   typed: no @font-face ships with this theme and an emoji-substituted glyph
   would carry the platform's shape instead of the specified one.
   `currentColor` keeps it on the heading's own colour, and `aria-hidden`
   keeps it out of the accessible name, which the heading text already gives. */
.dp-precautions__icon {
	inline-size: 1.1875rem;
	block-size: 1.1875rem;
	flex: none;
}

.dp-precautions__body > :first-child {
	margin-block-start: 0;
}

/* ---------------------------------------------------------------------------
 * The urgent case. Six differences, and the first four work in greyscale:
 * title, icon, rank label, and the shape of the content. The 6px rule and this
 * surface only confirm what those already say (WCAG 1.4.1).
 *
 * MEASURED on this palette by tools/design/check_contrast.py, which now guards
 * both pairs, and identical to the two independent implementations recorded in
 * `04-foundation-deltas.md` B.1:
 *   caution #8A3A22 on caution-surface-urgent #F7E9E3 -> 6.54:1
 *   ink     #48535F on caution-surface-urgent #F7E9E3 -> 6.62:1
 *   base    #FFFFFF on caution           #8A3A22 -> 7.75:1  (the rank label)
 * ------------------------------------------------------------------------ */
.dp-precautions--urgent {
	background-color: var(--wp--preset--color--caution-surface-urgent);
	border-inline-start-width: 6px;
	padding-inline-start: calc(var(--wp--preset--spacing--40) - 6px);
}

/* The rank label. It is a SOLID PLATE, not tinted text: the plate is what a
   reader sees before reading, and it is the one signal here that survives being
   printed, photocopied or read at a glance. */
.dp-precautions__rank {
	display: flex;
	align-items: center;
	gap: 0.375rem;
	/* Same anonymous-flex-item defect as `__title`, and this one carries the
	   words "Consulter sans attendre". Measured at 320px / 200%: 45px of the
	   badge sat outside its own box. */
	overflow-wrap: anywhere;
	inline-size: fit-content;
	margin-block: 0 var(--wp--preset--spacing--20);
	padding: 0.25rem 0.5rem;
	border-radius: var(--wp--custom--radius-sm);
	background-color: var(--wp--preset--color--caution);
	color: var(--wp--preset--color--base);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
}

.dp-precautions__rank .dp-precautions__icon {
	inline-size: 0.8125rem;
	block-size: 0.8125rem;
}

/* A list, because the fourth signal is the SHAPE of the content: prose frames a
   gesture, a list of signs interrupts. The block builds the list itself from one
   sign per line, so this shape cannot be lost to an editor's paste. */
.dp-precautions__signs {
	margin-block: 0;
	padding-inline-start: 1.25rem;
	color: var(--wp--preset--color--ink);
}

.dp-precautions__signs > li + li {
	margin-block-start: 0.3125rem;
}

/* The closing line of each box: the informative-content notice on the standard
   case, THE EMERGENCY NUMBER on the urgent one. The number sits here and only
   here - the accepted archetype puts it under the warning signs, and a number
   repeated under ordinary exercise advice is a number nobody reads on the day
   it counts. */
.dp-precautions__footer {
	margin-block-start: var(--wp--preset--spacing--30);
	margin-block-end: 0;
	padding-block-start: var(--wp--preset--spacing--20);
	border-block-start: 1px solid var(--wp--preset--color--caution);
	font-size: var(--wp--preset--font-size--small);
	/* `ink`, and NOT `ink-muted`, on a measured failure found by an independent
	   review on 2026-08-29. `ink-muted` on `caution-surface` is 4.47:1 against
	   the 4.50 WCAG 1.4.3 requires - the ONLY text on the site under threshold,
	   and it carries the medical-responsibility sentence of the precautions box.
	   `small` renders at roughly 14.5-15.5px, so the large-text allowance of 3.00
	   does not apply. `ink` gives 7.06:1 on the same surface.
	   The pair is now checked by tools/design/check_contrast.py, so it cannot
	   come back silently. */
	color: var(--wp--preset--color--ink);
}

/* ---------------------------------------------------------------------------
 * Key takeaways, sources
 * ------------------------------------------------------------------------ */

.dp-takeaways,
.dp-sources,
.dp-related,
.dp-practical,
.dp-qualifications,
.dp-chapters,
.dp-transcript {
	margin-block-start: var(--wp--preset--spacing--60);
}

.dp-takeaways {
	background-color: var(--wp--preset--color--surface);
	border-radius: var(--wp--custom--radius-sm);
	padding: var(--wp--preset--spacing--40);
}

.dp-takeaways__title,
.dp-sources__title,
.dp-related__title,
.dp-practical__title,
.dp-qualifications__heading,
.dp-chapters__title,
.dp-transcript__title {
	margin-block-start: 0;
	font-size: var(--wp--preset--font-size--x-large);
}

.dp-sources__list {
	padding-inline-start: 1.25rem;
}

.dp-sources__item {
	margin-block-end: 0.5rem;
}

.dp-sources__external {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
	margin-inline-start: 0.25rem;
}

.dp-sources__note {
	display: block;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

/* ---------------------------------------------------------------------------
 * Deferred video player
 * ------------------------------------------------------------------------ */

.dp-video {
	margin: 0;
}

/* LE BOUTON ET LE LIBELLE PARTAGEAIENT LA MEME BOITE. L affiche etait posee en
   `position: absolute; inset: 0` sur tout le cadre, son libelle cale en bas, et
   le bouton centre PAR-DESSUS. A 390 px le bouton passe sur trois lignes,
   grandit, et recouvre le titre. MESURE AU NAVIGATEUR le 2026-09-08 sur main :
   bouton 440-548, libelle 511-571, soit 37 px de CHEVAUCHEMENT a 390 px ; 0 a
   1440 px. Ce sont deux RANGEES d une grille desormais, donc elles ne peuvent
   plus se recouvrir a aucune largeur. Le degrade passe au cadre, puisque c est
   lui qui remplissait le cadre de toute facon.
   Origine : PR #16, ouverte le 2026-08-14 et restee en brouillon 25 jours. */
.dp-video__frame {
	/* Same defect again: the video title is an anonymous flex item and would
	   not shrink below its longest word — 40px outside the frame at 320px /
	   200%. Independent of the video freeze question (RISK-063). */
	overflow-wrap: anywhere;
	position: relative;
	/* ⛔ `box-sizing` EST LA CONDITION DU RATIO, pas un detail. Le theme est en
	   `content-box` : avec le padding qu ajoute la grille, `aspect-ratio: 16 / 9`
	   s applique a la boite de CONTENU et le cadre servi tombe a 1,707 au lieu de
	   1,778. Mesure au navigateur le 2026-09-08 : 640 x 375 au lieu de 640 x 360. */
	box-sizing: border-box;
	/* ⓘ CE QUI RESTE, MESURE ET NON DEDUIT. A 390 px un cadre 16/9 fait 188 px de
	   haut : 156 une fois le padding retire, pour un contenu qui en demande 176
	   - bouton sur trois lignes 108, gouttiere 8, libelle 60. Le bouton deborde
	   donc sa rangee et il RESTE 3 px de recouvrement, contre 37 avant la grille.
	   ⛔ ON S ARRETE LA, DELIBEREMENT : mesure au navigateur le 2026-09-08, RIEN
	   n est rogne, le libelle se lit en entier (`scrollWidth <= clientWidth`), les
	   deux elements restent dans le cadre et il n y a aucun debordement
	   horizontal. `L8-007` a deja paye cette lecon : un critere ecrit en pixels
	   est un PROXY plus strict que ce qu il nomme, et le poursuivre coute plus
	   qu il ne rend. `min-height: max-content` a ete essaye et RETIRE : servi, il
	   ne deplacait pas le cadre d un pixel - une regle sans effet est un mensonge
	   dans la feuille. */
	aspect-ratio: 16 / 9;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--wp--custom--radius-sm);
	overflow: hidden;
	display: grid;
	grid-template-rows: 1fr auto;
	justify-items: center;
	gap: 0.5rem;
	padding: var(--wp--preset--spacing--40);
	background:
		radial-gradient(70% 70% at 50% 45%, rgba(255, 255, 255, 0.14), transparent 70%),
		linear-gradient(145deg, #123B63, #1E5A93);
}

/* Une fois le lecteur en place, le cadre cesse d etre une grille a deux rangees
   et redevient une boite simple, sans quoi l iframe s assoirait dans une cellule
   avec du padding au lieu de remplir le 16/9. L attribut est pose par
   assets/video-embed.js, qui l ecrivait deja sans qu AUCUNE regle ne le lise. */
.dp-video__frame[data-dp-video-loaded="true"] {
	display: block;
	padding: 0;
	gap: 0;
}

.dp-video__poster {
	grid-row: 2;
	justify-self: start;
	display: flex;
	align-items: flex-end;
}

.dp-video__poster-label {
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	letter-spacing: -0.024em;
	color: #FFFFFF;
}

/* The video's real thumbnail, kept by the site (Thierry, 24/10: « la vraie
   miniature de la vidéo YouTube, avec le bouton « Lire la vidéo »
   par-dessus »). It fills the 16/9 frame behind the button; the dark ground
   only shows while the image loads. */
.dp-video__frame--image {
	background: var(--wp--preset--color--contrast);
}

/* `grid-area: auto` is what lets `inset: 0` reach the frame: an absolutely
   positioned grid item that keeps its row is placed inside that row only. */
.dp-video__frame--image .dp-video__poster {
	position: absolute;
	inset: 0;
	grid-area: auto;
	justify-self: stretch;
	display: block;
}

.dp-video__poster-image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.dp-video__frame--image .dp-video__load {
	grid-row: 1 / -1;
	z-index: 1;
}

.dp-video__load {
	position: relative;
	grid-row: 1;
	align-self: center;
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	min-height: 44px;
	min-width: 0;
	max-width: 100%;
	padding: 0.75rem 1.25rem;
	background-color: #FFFFFF;
	color: #12365C;
	border: 0;
	border-radius: var(--wp--custom--radius-sm);
	font: inherit;
	font-weight: 600;
	letter-spacing: -0.012em;
	cursor: pointer;
	text-align: start;
	box-shadow: 0 10px 26px -8px rgba(0, 0, 0, 0.5);
	transition: background-color 0.16s, box-shadow 0.16s;
}

.dp-video__load:hover {
	background-color: var(--wp--custom--accent-mist);
	color: #0B2440;
}

.dp-video__frame :where(button):focus-visible {
	outline-color: #FFFFFF;
	box-shadow: 0 0 0 2px #12365C;
}

.dp-video__iframe {
	width: 100%;
	height: 100%;
	border: 0;
	display: block;
}

/* The video that cannot be played.
 *
 * It does NOT keep the 16/9 frame: a missing player has no reason to reserve a
 * large empty rectangle, and a box the size of a video reads as a video that
 * failed to load rather than as one that is not there.
 *
 * Same pending pair as the search error, and for the same reason: `caution`
 * belongs to the health voice. WCAG 1.4.1 is satisfied by the sentence alone;
 * the icon is aria-hidden and the ground only confirms. */
.dp-video__unavailable {
	color: var(--wp--preset--color--pending);
	background-color: var(--wp--preset--color--pending-surface);
	border: 1px solid var(--wp--custom--field-border);
	border-radius: var(--wp--custom--radius-sm);
	padding: var(--wp--preset--spacing--40);
}

.dp-video__unavailable-title {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0;
	font-weight: 600;
	color: var(--wp--preset--color--contrast);
}

.dp-video__unavailable-icon {
	flex: none;
	width: 20px;
	height: 20px;
}

.dp-video__unavailable-body {
	margin-block: 0.375rem 0;
	font-size: var(--wp--preset--font-size--small);
}

/* The consent window the play button opens (owner, 04/11): over the poster,
   inside the player, YouTube's logo, one sentence and two buttons. */
.dp-video__consent {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: safe center;
	gap: 0.625rem;
	overflow-y: auto;
	padding: 0.75rem 1rem;
	background-color: color-mix(in srgb, var(--wp--preset--color--contrast) 88%, transparent);
	color: var(--wp--preset--color--base);
	text-align: center;
	line-height: 1.4;
}

.dp-video__consent[hidden] {
	display: none;
}

/* The play button showed through the sentence (1440 px, 04/11); at 320 px the
   16:9 frame is lower than the window (relecture of 04/11): it grows. */
.dp-video__frame:has(> .dp-video__consent:not([hidden])) {
	min-height: 12.5rem;
}

.dp-video__frame:has(> .dp-video__consent:not([hidden])) .dp-video__load {
	visibility: hidden;
}

.dp-video__consent p {
	margin: 0;
}

.dp-video__consent-brand {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	font-weight: 700;
	font-size: 1.0625rem;
	letter-spacing: -0.01em;
}

.dp-video__consent-logo {
	width: 34px;
	height: auto;
	flex: none;
}

.dp-video__consent-text {
	max-width: 30rem;
	font-size: 0.9375rem;
}

.dp-video__consent-actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.5rem;
}

.dp-video__consent-actions button {
	min-height: 44px;
	padding: 0.5rem 1.125rem;
	border-radius: var(--wp--custom--radius-sm);
	font: inherit;
	font-size: 0.9375rem;
	font-weight: 600;
	cursor: pointer;
}

.dp-video__consent-yes {
	border: 2px solid var(--wp--preset--color--base);
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

.dp-video__consent-no {
	border: 2px solid color-mix(in srgb, var(--wp--preset--color--base) 75%, transparent);
	background-color: transparent;
	color: var(--wp--preset--color--base);
}

@media (hover: hover) {
	.dp-video__consent-yes:hover {
		background-color: color-mix(in srgb, var(--wp--preset--color--base) 86%, transparent);
	}

	.dp-video__consent-no:hover {
		border-color: var(--wp--preset--color--base);
		background-color: color-mix(in srgb, var(--wp--preset--color--base) 12%, transparent);
	}
}

@media (max-width: 37.4375em) {
	.dp-video__consent {
		gap: 0.5rem;
		padding: 0.5rem 0.75rem;
	}

	.dp-video__consent-text {
		font-size: 0.875rem;
	}
}

/* « Voir sur YouTube », under the player: a new tab (owner, 04/11). */
.dp-video__youtube {
	display: inline-flex;
	align-self: flex-start;
	align-items: center;
	gap: 0.375rem;
	min-height: 44px;
	color: var(--wp--preset--color--contrast);
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.dp-video__youtube-icon {
	width: 16px;
	height: 16px;
	flex: none;
}

.dp-video__transcript-note {
	display: block;
}

.dp-video__caption {
	margin-block-start: 0.5rem;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

.dp-video__duration {
	display: inline-block;
	font-weight: 600;
	margin-inline-end: 0.5rem;
}

/* A video in an article body keeps its author's caption; the YouTube notice
   goes on its own line below it. */
.dp-video__credit {
	display: block;
	margin-block-end: 0.25rem;
	color: var(--wp--preset--color--ink);
}

/* Body videos carry long legacy titles, and the button already says them.
   Measured in the browser on 2026-10-21 at 390 px: a button of four lines,
   46 px over the poster's copy of the title, its first line cut by the frame;
   at 320 px the button left the frame. At that width the poster drops its
   copy, and before the click the frame takes the height of its content
   instead of 16/9. The loaded player is 16/9 again. */
@media (max-width: 37.4375em) {
	.dp-video--embed .dp-video__frame:not([data-dp-video-loaded="true"]) {
		aspect-ratio: auto;
		min-height: 10rem;
	}

	.dp-video--embed .dp-video__poster {
		display: none;
	}

	/* With the thumbnail there is no copy of the title to drop: the frame
	   keeps 16/9 and shows the image. */
	.dp-video--embed .dp-video__frame--image:not([data-dp-video-loaded="true"]) {
		aspect-ratio: 16 / 9;
		min-height: 0;
	}

	.dp-video--embed .dp-video__frame--image .dp-video__poster {
		display: block;
	}
}

/* ---------------------------------------------------------------------------
 * Chapters and transcript
 * ------------------------------------------------------------------------ */

.dp-chapters__list {
	list-style: none;
	margin: 0;
	padding: 0;
}

/* Le libellé de chapitre doit pouvoir RÉTRÉCIR. Mesure du 2026-09-02, texte
 * doublé à 320 px : une colonne de temps figée à 4.5rem plus un libellé qui
 * refuse de descendre sous la largeur intrinsèque de son texte poussaient la
 * ligne hors du cadre — 73 px de débordement, échec WCAG 1.4.4.
 * ⛔ `flex-wrap: wrap` a été essayé et RETIRÉ : mesuré, il repliait des lignes à
 * texte NORMAL et allongeait l'article de 60 px à 320 px. `min-width: 0` seul
 * suffit, et ne change rien au rendu ordinaire — vérifié aux trois largeurs. */
.dp-chapters__item {
	display: flex;
	gap: 0.75rem;
	padding-block: 0.25rem;
	border-bottom: 1px solid var(--wp--preset--color--border);
}

.dp-chapters__time {
	flex: 0 0 4.5rem;
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--ink-muted);
}

.dp-chapters__label {
	min-width: 0;
}

.dp-transcript__intro {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

.dp-transcript__body {
	max-width: var(--wp--style--global--content-size);
}

/* ---------------------------------------------------------------------------
 * Topic grid and related content
 * ------------------------------------------------------------------------ */

.dp-topics__grid,
.dp-related__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 1.25rem;
}

/* RISK-039. With no track declared the implicit column is `auto`: its floor is
   min-content, so a card can end up wider than the grid containing it. Measured
   2026-08-31 at 320px with text at 200%: computed track 292.53px inside a 225px
   container.

   The rule is DEDICATED rather than added to the group above. `.dp-related__list`
   carries the same latent defect, but it is NOT realised — its computed track
   already equals its container, 225px at 320px and 295px at 390px, measured on an
   article page whose scrollWidth under zoom stays at 313px. A latent defect that
   produces nothing is outside L8-007's bounds. */
.dp-topics__grid {
	grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 40em) {
	.dp-topics__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 64em) {
	.dp-topics__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.dp-topics__card {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	gap: 0.5rem;
	height: 100%;
	/* height:100% plus padding needs border-box, or the card paints 46px
	   past its grid row — measured as the 404's clipped closing line. */
	box-sizing: border-box;
	padding: 1.375rem;
	background-color: var(--wp--preset--color--base);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--wp--custom--radius-sm);
	box-shadow: var(--wp--preset--shadow--low);
	text-decoration: none;
	color: var(--wp--preset--color--contrast);
	transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s;
}

.dp-topics__card:hover {
	border-color: var(--wp--custom--accent-faint);
	box-shadow: var(--wp--preset--shadow--raised);
	transform: translateY(-3px);
}

.dp-topics__name {
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	letter-spacing: -0.024em;
	transition: color 0.18s;
	/* RISK-039. This is a `span`, so the wrapping set further up on
	   `h1, h2, h3, .dp-card__title` does not reach it. `overflow-wrap` ALONE, and
	   deliberately: adding it to that list would also grant `text-wrap: balance`
	   and, below 30rem, `hyphens: auto` — automatic hyphenation on every topic
	   name, a visual change nothing measured or asked for. */
	overflow-wrap: break-word;
}

/* The hub with no rubrique online yet. It is not decoration: two redirections
 * are DECIDED onto /douleurs-et-exercices in the L4-003 register, so a visitor
 * can reach this page from a dead address before any rubrique Page exists. The
 * sentence offers the exit the design requires of every empty state. */
.dp-topics__empty {
	margin: 0;
	color: var(--wp--preset--color--ink);
	font-size: var(--wp--preset--font-size--medium);
}

.dp-topics__card:hover .dp-topics__name {
	color: var(--wp--preset--color--primary-strong);
}

.dp-topics__desc {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
	line-height: 1.55;
	flex: 1;
}

/* The card foot: content count left, round arrow right, above a rule —
   the retained direction's affordance for the whole-card link. */
.dp-topics__foot {
	margin-block-start: 0.75rem;
	padding-block-start: 0.875rem;
	border-top: 1px solid var(--wp--preset--color--border);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 0.5rem;
}

.dp-topics__count {
	font-size: 0.78125rem;
	font-weight: 600;
	color: var(--wp--preset--color--primary-strong);
	font-variant-numeric: tabular-nums;
}

.dp-topics__arrow {
	width: 30px;
	height: 30px;
	border-radius: 999px;
	background: var(--wp--custom--accent-pale);
	color: var(--wp--preset--color--primary);
	display: flex;
	align-items: center;
	justify-content: center;
	transition: background-color 0.18s, color 0.18s;
}

.dp-topics__arrow svg {
	width: 15px;
	height: 15px;
	stroke-width: 2;
}

.dp-topics__card:hover .dp-topics__arrow {
	background: var(--wp--preset--color--primary);
	color: #FFFFFF;
}

.dp-related__link {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem;
	padding: 0.75rem 0;
	border-bottom: 1px solid var(--wp--preset--color--border);
	text-decoration: none;
	color: var(--wp--preset--color--contrast);
}

.dp-related__link:hover .dp-related__name {
	text-decoration: underline;
}

.dp-related__kind,
.dp-related__duration {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

.dp-related__kind {
	flex: 0 0 4.5rem;
	text-transform: uppercase;
	letter-spacing: 0.04em;
}

/* ---------------------------------------------------------------------------
 * Practical information and the non-publishable identity marker
 * ------------------------------------------------------------------------ */

.dp-practical {
	background-color: var(--wp--preset--color--surface);
	border-radius: var(--wp--custom--radius-sm);
	padding: var(--wp--preset--spacing--40);
}

/* The address, the access note and the practice details: the one place on the
 * site where the EDITOR types a value nobody chose the length of. "Adresse
 * longue" is a variant of 05-page-template-matrix.md, and it was measured
 * failing at 320px on all three pages carrying this block - `scrollWidth` 602
 * against a 320px viewport, from a single unbreakable string. Same
 * `overflow-wrap`-alone reasoning as prose above. */
.dp-practical__list {
	margin: 0;
	overflow-wrap: break-word;
}

.dp-practical__list dt {
	font-weight: 600;
	margin-block-start: var(--wp--preset--spacing--30);
}

.dp-practical__list dd {
	margin-inline-start: 0;
}

.dp-practical__hours {
	list-style: none;
	margin: 0;
	padding: 0;
}

/* The sentence that qualifies the schedule (DEC-106). It is NOT styled as a
 * footnote: a muted or smaller treatment would undercut the only thing that
 * makes the published hours true, and RISK-025 already records what happens
 * when meaning is carried by a colour that fails non-text contrast. Body
 * colour, body size, a little weight and a little air - nothing else. */
.dp-practical__appointment {
	font-weight: 600;
	margin-block: var(--wp--preset--spacing--20) 0;
}

/* The legal identity and the physical access, added 2026-09-09 with T3.
 * Same surface and same wrapping reasoning as `.dp-practical` above: these
 * carry values an editor types and nobody chose the length of - a registered
 * office, a legal form, an access sentence - and the 320px measurement that
 * produced `overflow-wrap` there applies unchanged here. */
.dp-legal,
.dp-access {
	background-color: var(--wp--preset--color--surface);
	border-radius: var(--wp--custom--radius-sm);
	padding: var(--wp--preset--spacing--40);
}

.dp-legal__list {
	margin: 0;
	overflow-wrap: break-word;
}

.dp-legal__row {
	margin-block-start: var(--wp--preset--spacing--30);
}

.dp-legal__label {
	font-weight: 600;
}

.dp-legal__value {
	margin-inline-start: 0;
}

/* Both say what is NOT established - the missing clauses, the absent
 * accessibility survey. They are the substance of these pages, not fine print,
 * so they keep body contrast and are only set apart by spacing. */
.dp-legal__pending,
.dp-access__caveat {
	margin-block-start: var(--wp--preset--spacing--40);
	overflow-wrap: break-word;
}

.dp-practical__warning,
.dp-legal__warning,
.dp-qualifications__warning,
.dp-access__warning,
.dp-identity-notice {
	background-color: var(--wp--preset--color--caution-surface);
	border-inline-start: 4px solid var(--wp--preset--color--caution);
	border-radius: 0 4px 4px 0;
	padding: 0.75rem 1rem;
	color: var(--wp--preset--color--caution);
}

.dp-identity-notice__text {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
}

/* Qualification and training. No surface of its own: this is running content on
 * the practitioner page, not a details panel like the practice block, and
 * 13-design-foundation.md counts filled blocks in the reading column as a
 * measured cost. A title can be long and unbreakable, so the same
 * `overflow-wrap` reasoning applies here as on the practical list. */
.dp-qualifications__subheading {
	font-size: var(--wp--preset--font-size--large);
	margin-block-start: var(--wp--preset--spacing--40);
}

.dp-qualifications__list {
	overflow-wrap: break-word;
	padding-inline-start: 1.25rem;
}

.dp-qualifications__list li {
	margin-block-start: 0.25rem;
}

/* The bound is not decoration: EM-12 forbids this list from reading as
 * complete, so it must be legible rather than a footnote. */
.dp-qualifications__bound {
	font-size: var(--wp--preset--font-size--small);
	margin-block-start: var(--wp--preset--spacing--30);
}

.dp-practical__value--unvalidated,
.dp-legal__value--unvalidated,
.dp-access__value--unvalidated,
.dp-qualifications__value--unvalidated {
	background-color: var(--wp--preset--color--caution-surface);
	color: var(--wp--preset--color--caution);
	padding: 0 0.25rem;
	border-radius: 2px;
	font-style: italic;
}

/* ---------------------------------------------------------------------------
 * Cards in query loops
 * ------------------------------------------------------------------------ */

.dp-card {
	padding-block: var(--wp--preset--spacing--40);
	border-bottom: 1px solid var(--wp--preset--color--border);
}

/* List treatment: the date sits above the title as a quiet dateline. */
.dp-card > .dp-card__date {
	margin-block-end: 0.375rem;
}

.dp-card__title {
	font-size: var(--wp--preset--font-size--x-large);
	margin-block: 0 0.35rem;
}

/* Clickable titles carry a resting underline: with no card chrome around
   them, the underline is the non-chromatic affordance WCAG G183 asks for.
   Kept discreet by its colour and thickness, not by its absence. */
.dp-card__title a {
	color: var(--wp--preset--color--contrast);
	text-decoration: underline;
	text-underline-offset: 4px;
	text-decoration-thickness: 1px;
	text-decoration-color: var(--wp--custom--accent-faint);
}

.dp-card__title a:hover {
	color: var(--wp--preset--color--primary-strong);
	text-decoration-color: currentColor;
	text-decoration-thickness: 2px;
}

.dp-card__excerpt {
	margin: 0;
	color: var(--wp--preset--color--ink-muted);
}

/* ---------------------------------------------------------------------------
 * Search summary and empty states
 * ------------------------------------------------------------------------ */

.dp-search-summary {
	font-size: var(--wp--preset--font-size--large);
	color: var(--wp--preset--color--ink-muted);
}

.dp-search-summary__term {
	color: var(--wp--preset--color--contrast);
	font-weight: 600;
}

/* The error case of the same sentence: a query that carried no word.
 *
 * It is a NOTICE, not a result count, so it stops looking like one - the large
 * type of a count would announce a measurement that did not happen. The pending
 * pair is deliberate rather than the caution pair: `caution` is the health
 * voice of the precautions box, and spending it on "you typed nothing" is how a
 * warning colour stops meaning anything on the day it matters.
 *
 * WCAG 1.4.1: the whole message is carried by the sentence. Remove every colour
 * here and a reader loses nothing - the ground and the rule only confirm. */
.dp-search-summary--error {
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--pending);
	background-color: var(--wp--preset--color--pending-surface);
	border-inline-start: 4px solid var(--wp--custom--accent-soft);
	border-radius: var(--wp--custom--radius-sm);
	padding: var(--wp--preset--spacing--40);
}

/* ---------------------------------------------------------------------------
 * Footer
 * ------------------------------------------------------------------------ */

/* The footer is the one dark surface of the system: deep blue ground, muted
   blue-grey text, white on hover. Column headings are the only uppercase
   labels on the page. */
.dp-footer {
	border-top: 0;
	background-color: var(--wp--preset--color--deep);
	color: #B9CCDF;
	font-size: var(--wp--preset--font-size--small);
	/* The gap above the footer belongs to the stylesheet, not to the template
	   part, so a page that closes on a dark full-width band can remove it. */
	margin-block-start: var(--wp--preset--spacing--90);
}

/* The home closes on the deep call-to-action band: two dark surfaces meet,
   the white gap between them would read as a seam. The template part wraps
   the group in its own <footer> element, so the group is a descendant of
   main's adjacent sibling, never the sibling itself. */
main:has(> .dp-cta-band:last-child) + footer .dp-footer {
	margin-block-start: 0;
}

main > .dp-cta-band:last-child {
	margin-block-end: 0;
}

.dp-footer a {
	color: #B9CCDF;
	text-decoration: none;
}

.dp-footer a:hover {
	color: #FFFFFF;
}

.dp-footer .wp-block-site-title,
.dp-footer .wp-block-site-title a {
	color: #FFFFFF;
}

.dp-footer h2.wp-block-heading {
	color: #FFFFFF;
	font-size: 0.6875rem;
	font-weight: 700;
	letter-spacing: 0.15em;
	text-transform: uppercase;
	margin-block-end: 0.875rem;
}

.dp-footer .wp-block-list {
	list-style: none;
	padding: 0;
}

.dp-footer .wp-block-list li {
	margin-block-end: 0.625rem;
}

/* The identity notice keeps its dashed "pending" voice on the dark ground. */
.dp-footer .dp-identity-notice {
	background-color: rgba(255, 255, 255, 0.05);
	border: 1px dashed rgba(185, 204, 223, 0.4);
	border-radius: var(--wp--custom--radius-sm);
	color: #B9CCDF;
}

/* Closing legal line, separated by the footer's own faint rule. */
.dp-footer > .has-small-font-size:last-child {
	color: #8FA6BC;
	border-top: 1px solid rgba(185, 204, 223, 0.16);
	padding-block-start: 1.375rem;
}

/* ---------------------------------------------------------------------------
 * Forms
 * ------------------------------------------------------------------------ */

.dp-form label {
	display: block;
	font-weight: 600;
	margin-block-end: 0.25rem;
}

.dp-form input[type="text"],
.dp-form input[type="email"],
.dp-form input[type="tel"],
.dp-form select,
.dp-form textarea {
	/* Without this the 1px border on each side pushes a 100%-wide field two
	   pixels past the viewport at 320px, and the page scrolls sideways. */
	box-sizing: border-box;
	width: 100%;
	min-height: 44px;
	/* His mock-up of 01/11: 13 × 14 px inside, corners of 10 px. */
	padding: 0.8125rem 0.875rem;
	/* Dedicated token, not a text colour reused. A control border is a
	   non-text element: it needs 3.00:1, and the general rule-colour measures
	   1.51:1 on white. #7F8FA1 gives 3.31:1 on white, 3.10:1 on the tint.
	   It is a CUSTOM token, not a palette entry: this is a form-control style
	   role, not a brand or content colour, so the editor must not offer it as
	   a swatch for text or backgrounds. That is what 13-design-foundation.md
	   requires, and putting it in the palette contradicted it. */
	border: 1px solid var(--wp--custom--field-border);
	border-radius: 10px;
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
	font: inherit;
	transition: border-color 0.15s;
}

.dp-form input[type="text"]:hover,
.dp-form input[type="email"]:hover,
.dp-form input[type="tel"]:hover,
.dp-form select:hover,
.dp-form textarea:hover {
	border-color: var(--wp--preset--color--primary);
}

.dp-form textarea {
	min-height: 8rem;
}

.dp-form__field + .dp-form__field {
	margin-block-start: var(--wp--preset--spacing--30);
}

.dp-form__hint {
	display: block;
	font-weight: 400;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
}

.dp-form__required {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
	margin-block-end: var(--wp--preset--spacing--40);
}

/* The robot trap: out of sight, taking no room. Should this sheet fail to
 * load, its label tells a person to leave it empty. */
.dp-form__trap {
	position: absolute !important;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ---- Thierry's form: the moments, the fees box, « Envoyer » ------------
 * Checkbox rows read as one line each, label at normal weight; the whole
 * label is the target, at least 44px high on a phone.
 * ---------------------------------------------------------------------- */

.dp-form__choices {
	border: 0;
	margin-inline: 0;
	padding: 0;
	min-inline-size: 0;
}

/* Prénom beside Nom, E-mail beside Téléphone (his mock-up of 01/11); one per
 * line on a phone. Measured on the form, not the window: beside « Avant votre
 * demande » the form is far narrower than the screen. The grid's gap spaces
 * the fields, not their margins. */
.dp-form__form {
	container-type: inline-size;
}

.dp-form__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--wp--preset--spacing--30);
}

@container (min-width: 40em) {
	.dp-form__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

.dp-form__grid > .dp-form__field + .dp-form__field {
	margin-block-start: 0;
}

.dp-form__grid + .dp-form__field {
	margin-block-start: var(--wp--preset--spacing--30);
}

/* « Cochez tous les créneaux qui vous conviennent. »: in his mock-up a quiet
 * line above the pills, not a bold label. */
.dp-form__choices legend {
	font-weight: 400;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
	padding: 0;
	margin-block-end: 0.5rem;
}

/* The moments as pills: the box inside its label, the whole pill the target,
 * at least 44px high; several in a row, wrapping on a phone. */
.dp-form__pills {
	display: flex;
	flex-wrap: wrap;
	gap: 0.625rem;
}

.dp-form .dp-form__pill {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	box-sizing: border-box;
	min-block-size: 44px;
	margin: 0;
	padding: 0.6875rem 0.875rem;
	border: 1px solid var(--wp--custom--field-border);
	border-radius: 10px;
	font-weight: 400;
	font-size: 0.9375rem;
	cursor: pointer;
}

.dp-form__pill input[type="checkbox"] {
	flex: none;
	inline-size: 1.125rem;
	block-size: 1.125rem;
	margin: 0;
	accent-color: var(--g-primary, var(--wp--preset--color--primary));
}

/* A ticked pill says so twice: the box, and its border in the button's colour. */
.dp-form__pill:has(input:checked) {
	border-color: var(--g-primary, var(--wp--preset--color--primary));
}

.dp-form__check {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	column-gap: 0.75rem;
}

.dp-form__check input[type="checkbox"] {
	flex: none;
	inline-size: 1.25rem;
	block-size: 1.25rem;
	margin: 0;
	/* Design G's terracotta where the page wears it, the theme's otherwise. */
	accent-color: var(--g-primary, var(--wp--preset--color--primary));
}

.dp-form .dp-form__check label {
	flex: 1 1 0;
	min-inline-size: 0;
	min-block-size: 44px;
	display: flex;
	align-items: center;
	font-weight: 400;
	margin: 0;
}

.dp-form__check .dp-form__error {
	flex-basis: 100%;
}

/* The fees box on his warm tint (design G's, set in design-g.css). */
.dp-form .dp-form__check--fees {
	align-items: flex-start;
	padding: 1rem;
	border-radius: 12px;
	background-color: var(--g-form-tint, var(--wp--preset--color--surface));
}

.dp-form__check--fees input[type="checkbox"] {
	margin-block-start: 0.75rem;
}

.dp-form__fees {
	margin-block-start: var(--wp--preset--spacing--40);
}

.dp-form__send {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 0.5rem 1rem;
	margin-block-start: var(--wp--preset--spacing--30);
}

.dp-form__send button {
	border: 0;
	font-family: inherit;
	cursor: pointer;
}

.dp-form__send button:disabled {
	opacity: 0.5;
	cursor: not-allowed;
}

/* `.dp-form__hint` is a block: without this the hint under « Envoyer » would
   stay on screen once the script hides it. */
.dp-form__hint[hidden] {
	display: none;
}

/* ---- Error states -------------------------------------------------------
 * Colour is never the only carrier. Each error is a sentence next to its
 * field, with an icon; the doubled border and the tint are added on top.
 * A greyscale print of this page still says which field is wrong.
 * ---------------------------------------------------------------------- */

.dp-form__error,
.dp-form__summary-title {
	display: flex;
	align-items: flex-start;
	gap: 0.5rem;
	font-weight: 600;
	color: var(--wp--preset--color--caution);
}

.dp-form__error {
	font-size: var(--wp--preset--font-size--small);
	margin-block: 0.25rem;
}

.dp-form__icon {
	inline-size: 1.05em;
	block-size: 1.05em;
	flex: none;
	margin-block-start: 0.15em;
}

/* The border is DOUBLED rather than merely recoloured: width is a
   non-chromatic signal, and it survives a colour-blind reading. */
.dp-form__field--error :where(input, select, textarea) {
	border-width: 2px;
	border-color: var(--wp--preset--color--caution);
}

.dp-form__summary {
	border-inline-start: 4px solid var(--wp--preset--color--caution);
	background-color: var(--wp--preset--color--caution-surface);
	padding: var(--wp--preset--spacing--40);
	border-radius: 0 var(--wp--custom--radius-sm) var(--wp--custom--radius-sm) 0;
	margin-block-end: var(--wp--preset--spacing--50);
}

.dp-form__summary ul {
	margin-block: 0.5rem 0;
	padding-inline-start: 1.25rem;
}

.dp-form__summary a {
	color: var(--wp--preset--color--caution);
	font-weight: 600;
}

/* Focus target for the summary itself, which is not natively focusable. */
.dp-form__summary:focus-visible {
	outline: 3px solid var(--wp--preset--color--deep);
	outline-offset: 2px;
}

/* ---- Success ------------------------------------------------------------
 * The brand colour, not a green introduced for the occasion.
 * ---------------------------------------------------------------------- */

.dp-form__ok {
	border-inline-start: 4px solid var(--wp--preset--color--primary);
	background-color: var(--wp--preset--color--surface);
	padding: var(--wp--preset--spacing--50);
	border-radius: 0 var(--wp--custom--radius-sm) var(--wp--custom--radius-sm) 0;
}

.dp-form__ok-title {
	font-weight: 700;
	margin-block-end: 0.5rem;
}

/* Focus target for the confirmation, which is not natively focusable. */
.dp-form__ok:focus-visible {
	outline: 3px solid var(--wp--preset--color--deep);
	outline-offset: 2px;
}

.dp-form__notice {
	margin-block-start: var(--wp--preset--spacing--50);
	padding-block-start: 1.125rem;
	border-block-start: 1px solid var(--g-line, var(--wp--custom--field-border));
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink);
}

/* ---- Channel not open ---------------------------------------------------
 * Shown when no recipient is configured. This is a configuration state, not
 * a mistake the visitor made, so it borrows the neutral pending treatment
 * rather than the caution one.
 * ---------------------------------------------------------------------- */

.dp-form__closed {
	border-inline-start: 4px solid var(--wp--preset--color--pending);
	background-color: var(--wp--preset--color--pending-surface);
	padding: var(--wp--preset--spacing--50);
	border-radius: 0 var(--wp--custom--radius-sm) var(--wp--custom--radius-sm) 0;
	color: var(--wp--preset--color--pending);
}

.dp-form__closed-title {
	font-weight: 700;
	margin-block-end: 0.5rem;
}

/* ---------------------------------------------------------------------------
 * Page heads.
 *
 * There used to be a third composed width here: the head was pinned to 52rem
 * while the content below sat at either 40rem or the wide size. Centred, three
 * widths mean three left edges, and measurement showed up to four on a single
 * page — a topic hub put its head at 298px, its list at 394px and its grid at
 * 89px on a 1440px viewport. It read as a defect because it is one.
 *
 * A head now takes the width of what it introduces, and each template says so
 * itself: the prose and list templates drop `align:wide` so the head sits on
 * the reading measure with the content, while the video archive keeps it so
 * the head shares the edge of the grid it covers. One left edge per page; only
 * the line length varies. `headWidth` remains in theme.json as an available
 * token, deliberately unused by any rule.
 * ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
 * Byline
 * ------------------------------------------------------------------------ */

.dp-byline .wp-block-post-author__byline {
	display: block;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--ink-muted);
	margin-block-end: 0.125rem;
}

.dp-byline .wp-block-post-author__name {
	font-weight: 600;
	font-size: var(--wp--preset--font-size--medium);
	color: var(--wp--preset--color--contrast);
}

/* ---------------------------------------------------------------------------
 * Scroll reveals — progressive enhancement only.
 *
 * The hiding class exists exclusively under `html.dp-js`, which the reveal
 * script sets after checking that IntersectionObserver exists AND that the
 * visitor does not prefer reduced motion. No JavaScript, an old browser or a
 * reduced-motion preference all render the page fully visible, statically.
 * ------------------------------------------------------------------------ */

html.dp-js .dp-reveal {
	opacity: 0;
	transform: translateY(16px);
}

html.dp-js .dp-reveal.is-in {
	opacity: 1;
	transform: none;
	transition: opacity 0.6s cubic-bezier(0.2, 0.7, 0.3, 1), transform 0.6s cubic-bezier(0.2, 0.7, 0.3, 1);
}

html.dp-js .dp-reveal-grid > * {
	opacity: 0;
	transform: translateY(16px);
}

html.dp-js .dp-reveal-grid.is-in > * {
	opacity: 1;
	transform: none;
	transition: opacity 0.55s cubic-bezier(0.2, 0.7, 0.3, 1), transform 0.55s cubic-bezier(0.2, 0.7, 0.3, 1);
}

html.dp-js .dp-reveal-grid.is-in > *:nth-child(2) {
	transition-delay: 0.07s;
}

html.dp-js .dp-reveal-grid.is-in > *:nth-child(3) {
	transition-delay: 0.14s;
}

html.dp-js .dp-reveal-grid.is-in > *:nth-child(4) {
	transition-delay: 0.21s;
}

html.dp-js .dp-reveal-grid.is-in > *:nth-child(5) {
	transition-delay: 0.28s;
}

html.dp-js .dp-reveal-grid.is-in > *:nth-child(n+6) {
	transition-delay: 0.35s;
}

/* Print knows nothing of scrolling: without this, any section not yet
   revealed on screen prints as blank paper. */
@media print {
	html.dp-js .dp-reveal,
	html.dp-js .dp-reveal-grid > * {
		opacity: 1;
		transform: none;
	}
}

/* Text for screen readers only (the video button keeps its full name). clip-path,
   not clip: core lifts clip-path on focus, so « Aller au contenu » shows on Tab. */
.screen-reader-text { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
