/*
Theme Name: Teeshoop
Theme URI: https://teeshoop.com
Author: Teeshoop
Description: Le thème de teeshoop.com. Classique, sans dépendance, écrit pour une boutique WooCommerce de plusieurs centaines de références textiles personnalisables. Il ne calcule aucun prix : tous les chiffres viennent de l'extension Teeshoop Core.
Version: 1.0.0
Requires at least: 6.6
Tested up to: 7.0.4
Requires PHP: 8.1
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: teeshoop
Tags: e-commerce, woocommerce, custom-logo, custom-menu, translation-ready
*/

/*
 * ─────────────────────────────────────────────────────────────────────────────
 * THE LOOK, AND WHY IT IS THIS ONE
 *
 * We sell printing, and the one thing our two competitors do not do is publish
 * the numbers: neither mistertee.fr nor tostadora.fr states a single print
 * dimension on a product page. Our product page already does, in centimetres,
 * and bills the ink rather than the box the file was dropped into. So the site
 * is built to look like what it is, a printer's specification sheet, and not
 * like a marketplace: hairline rules instead of cards, tabular figures wherever
 * digits sit in a column, one accent used for actions and nothing else, and
 * white space instead of decoration.
 *
 * There is no gradient, no glass, no drop shadow that is not a real elevation,
 * no icon that carries no meaning and no animation that serves nothing. Those
 * are house rules (CLAUDE.md §7) and they are also what stops a page reading as
 * generated, which on a market where every competitor uses the same three
 * WordPress themes is the difference a buyer notices before the price.
 *
 * MOBILE FIRST, LITERALLY. Everything below is authored at 375 px. There are
 * three widenings and they are at the bottom: 640 px, 900 px and 1200 px.
 *
 * NO COLOUR, SIZE OR SPACE IS CHOSEN HERE. Every one of them comes from
 * `teeshoop-core/assets/tokens.css`, which is the one home for them, so
 * session 13b can drop in the real brand and change nothing else. Where a rule
 * needs a translucent wash it is `color-mix` of one of those same tokens, not a
 * fourth grey somebody picked.
 * ─────────────────────────────────────────────────────────────────────────────
 */

/* ──────────────────────────────────────────────────────────────── base ── */

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

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

body {
	margin: 0;
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-family: var(--ts-font);
	font-size: var(--ts-text);
	line-height: var(--ts-leading);
	/* Kerning and contextual alternates, off by default in some engines. */
	font-feature-settings: 'kern' 1, 'calt' 1;
	text-rendering: optimizeLegibility;
}

img,
svg,
video {
	max-width: 100%;
	height: auto;
	vertical-align: middle;
}

/*
 * ONE FOCUS RING FOR THE WHOLE SITE, drawn outside the element so that showing
 * it never moves anything. `:focus-visible` rather than `:focus` so a mouse
 * click on a button does not leave a ring behind, while every keyboard tab
 * does. Session 12 audits this; it is here from the first line because
 * retrofitting focus is the one thing that cannot be done cheaply later.
 */
:focus-visible {
	/*
	 * TWO TONES, AND THAT IS THE FIX RATHER THAN A DECORATION.
	 *
	 * One accent-coloured ring cannot pass 1.4.11 everywhere, and measurement
	 * said so: #1f4fd8 is 6,63:1 on white and 2,71:1 on the ink footer, where the
	 * skip link, which is the FIRST tab stop on every page of the shop, lives. It
	 * was 2,26:1 on our own accent buttons and 1,81:1 on WooCommerce's dark
	 * add-to-cart in the block cart. Twelve assertions of
	 * `npm run verify:a11y` were red on it.
	 *
	 * No single colour fixes that. A ring must clear 3:1 against white, against
	 * #14171a and against whatever WooCommerce paints, and the band of relative
	 * luminance that clears all three is narrow enough that the colour ends up
	 * a washed grey nobody would notice.
	 *
	 * So the ring is an ink line with a paper halo around it, which is the
	 * technique WCAG's own understanding document describes: on a light ground the
	 * ink is the visible part, on a dark one the halo is, and the two are 17,99:1
	 * apart from each other, so ONE of them always clears 3:1 whatever is behind.
	 * `scripts/a11y-verify.mjs` measures both and takes the better, which is what
	 * the criterion asks and not a relaxation of it.
	 *
	 * `box-shadow` and not a second outline because an element may have only one,
	 * and the halo has to sit OUTSIDE the ink line: `outline-offset` puts the ink
	 * two pixels off the box, and the shadow's spread starts at the box, so the
	 * orders match.
	 */
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
	box-shadow: 0 0 0 calc(var(--ts-focus-width) + var(--ts-focus-offset) + 2px) var(--ts-paper);
	border-radius: 1px;
}

a {
	color: var(--ts-accent);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.15em;
}

a:hover {
	color: var(--ts-accent-dark);
}

/* The skip link is real, and it is the first thing in the tab order. */
.ts-skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100;
	padding: var(--ts-space-3) var(--ts-space-4);
	background: var(--ts-ink);
	color: var(--ts-paper);
	text-decoration: none;
}

.ts-skip:focus {
	left: 0;
}

/* ────────────────────────────────────────────────────────── typography ── */

/*
 * URBANIST 700, WHICH IS THE ASSOCIATE'S TITLE FACE AND HIS TITLE WEIGHT.
 * `--wd-entities-title-font` and `--wd-entities-title-font-weight` on the live
 * site, measured, not chosen. The weight is a token rather than a literal
 * because `scripts/theme-fonts-check.mjs` reads the declared weights out of
 * `tokens.css` to decide which woff2 the theme is obliged to ship.
 */
h1,
h2,
h3,
h4 {
	margin: 0 0 var(--ts-space-3);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-display);
	line-height: var(--ts-leading-tight);
	/*
	 * Urbanist needs negative tracking above about 24 px or the display sizes
	 * read loose, the same way Inter did before it. The value is per size, not
	 * global: at body size the same tracking closes the letters up and costs
	 * legibility.
	 */
	letter-spacing: -0.015em;
	text-wrap: balance;
}

h1 {
	font-size: var(--ts-display);
	letter-spacing: -0.022em;
}

h2 {
	font-size: var(--ts-display-sm);
}

h3 {
	font-size: var(--ts-display-xs);
	letter-spacing: -0.01em;
}

p {
	margin: 0 0 var(--ts-space-4);
	max-width: var(--ts-measure);
}

/*
 * BOLD INSIDE RUNNING TEXT IS LATO 700, SAID OUT LOUD.
 *
 * A browser maps `strong` and `b` to `bold` on its own, and `bold` is 700, so
 * this rule changes nothing that renders. It is here because the only weights
 * this theme ships are Urbanist 600/700 and Lato 400/700, and the one weight a
 * shopper's browser can reach WITHOUT any rule of ours is exactly this one. Left
 * implicit, `scripts/theme-fonts-check.mjs` reads the stylesheets, finds nothing
 * asking for Lato 700, and reports 23 040 octets shipped on every page for
 * nothing. It found precisely that, which is how this rule got written.
 */
strong,
b {
	font-family: var(--ts-font);
	font-weight: var(--ts-weight-strong);
}

/*
 * The uppercase micro-label above a section. It is a LABEL, not a badge: no
 * pill, no background, no colour of its own beyond the muted ink, so a page
 * with six of them does not read as badge soup.
 */
.ts-eyebrow {
	display: block;
	margin: 0 0 var(--ts-space-2);
	color: var(--ts-muted);
	font-size: var(--ts-eyebrow-size);
	font-family: var(--ts-font-display);
	font-weight: 600;
	letter-spacing: var(--ts-eyebrow-tracking);
	text-transform: uppercase;
}

.ts-lead {
	max-width: 46ch;
	font-size: var(--ts-text-lg);
	line-height: var(--ts-leading-loose);
}

.ts-muted {
	color: var(--ts-muted);
}

/* ───────────────────────────────────────────────────────────── layout ── */

/*
 * LA GOUTTIÈRE DE LA PAGE.
 *
 * ELLE SE FAIT ÉCRASER PAR UN RACCOURCI `padding`, ET C'EST ARRIVÉ CINQ FOIS.
 * Mesuré le 9 septembre 2026 à 375 px, `padding-left` calculé sur chaque
 * élément portant `ts-wrap` : le bandeau d'accueil, les cinq sections de
 * l'accueil, l'entête de la boutique, la page de devis et la fiche produit
 * rendaient tous 0 px. Le texte touchait le bord de l'écran sur quatre pages,
 * y compris celle où le client tape son adresse.
 *
 * La cause n'est pas une gouttière absente, c'est que ces règles déclaraient
 * `padding: <haut> 0 <bas>`, à la même spécificité et plus bas dans le fichier,
 * donc leur `0` horizontal gagnait. Elles disent toutes `padding-block`
 * maintenant, qui ne touche que ce qu'elles voulaient toucher.
 *
 * UNE RÈGLE QUI VEUT ESPACER LE HAUT ET LE BAS D'UN BLOC `ts-wrap` DOIT ÉCRIRE
 * `padding-block`. C'est la seule chose à retenir de ce commentaire.
 */
.ts-wrap {
	margin: 0 auto;
	padding: 0 var(--ts-gutter);
	max-width: var(--ts-page);
}

.ts-main {
	display: block;
	min-height: 40vh;
}

/*
 * A section is separated from the next by a hairline and space, never by a
 * card. Cards on a catalogue page turn a listing into a wall of boxes, and
 * boxes are what every generated shop looks like.
 */
.ts-section {
	padding-block: var(--ts-space-7);
	border-top: 1px solid var(--ts-line);
}

.ts-section:first-child {
	border-top: 0;
}

.ts-section__head {
	margin: 0 0 var(--ts-space-5);
}

.ts-section__head p {
	margin-bottom: 0;
}

/* ─────────────────────────────────────────────────────────── masthead ── */

.ts-mast {
	position: sticky;
	top: 0;
	z-index: 40;
	background: var(--ts-paper);
	border-bottom: 1px solid var(--ts-line);
}

/*
 * WHATEVER IS SCROLLED TO STOPS UNDER THE MASTHEAD, AND THAT IS NOT COSMETIC.
 *
 * The block checkout answers a refused submission by scrolling its error notice
 * into view. The browser puts the top of that notice at the top of the VIEWPORT,
 * and the masthead is pinned there, so the customer is refused and reads nothing
 * that tells them why. Session 12 made the withdrawal-right acknowledgement
 * refusable server side, which is the one refusal a customer meets while trying
 * to pay us.
 *
 * `scroll-padding` is the one line that fixes it for every anchor, every
 * `scrollIntoView` and every focus the browser brings into view, rather than for
 * the one case somebody noticed. The value is the bar's own `min-height` plus
 * its border, so the two cannot drift apart without the number being wrong in
 * both places at once.
 */
html {
	scroll-padding-block-start: calc(3.5rem + 1px + var(--ts-space-2));
}

.ts-mast__bar {
	display: flex;
	align-items: center;
	gap: var(--ts-space-3);
	min-height: 3.5rem;
}

/*
 * THE WORDMARK SHRINKS BEFORE ANYTHING ELSE DOES.
 *
 * At 375 px the bar carries a menu button, the name, "Devis" and "Panier", and
 * measured before this was written the row was 457 px wide: every page on the
 * site scrolled sideways. The two controls are the ones a buyer needs, so the
 * name is the one that gives way, and it truncates rather than wrapping the bar
 * onto two lines.
 */
.ts-wordmark {
	flex: 0 1 auto;
	min-width: 0;
	margin-right: auto;
	overflow: hidden;
	color: var(--ts-ink);
	font-size: 1.0625rem;
	font-family: var(--ts-font-display);
	font-weight: 600;
	letter-spacing: 0.06em;
	text-overflow: ellipsis;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
}

.custom-logo-link {
	margin-right: auto;
	display: inline-flex;
	align-items: center;
}

.custom-logo {
	max-height: 2.25rem;
	width: auto;
}

/*
 * The menu button does not exist without JavaScript.
 *
 * It is `hidden` in the markup and revealed by `site.js`, because a button that
 * toggles a panel is useless when nothing can toggle it. With the script
 * blocked the navigation below is simply visible, which is the honest
 * degradation: every link reachable, no control that does nothing.
 */
/*
 * Below 640 px the button is the icon alone and its name lives in text only a
 * screen reader reads. A hamburger is one of the few icons that genuinely
 * carries its meaning; forty-five pixels of the word "Menu" is what pushed the
 * bar over the viewport.
 */
.ts-burger__label {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	clip-path: inset(50%);
	overflow: hidden;
}

.ts-burger {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	gap: var(--ts-space-2);
	margin: 0;
	padding: var(--ts-space-2) var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font: inherit;
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: 600;
	cursor: pointer;
}

.ts-burger__bars,
.ts-burger__bars::before,
.ts-burger__bars::after {
	display: block;
	width: 1rem;
	height: 1.5px;
	background: currentColor;
}

.ts-burger__bars {
	position: relative;
}

.ts-burger__bars::before,
.ts-burger__bars::after {
	position: absolute;
	left: 0;
	content: '';
}

.ts-burger__bars::before {
	top: -5px;
}

.ts-burger__bars::after {
	top: 5px;
}

.ts-nav__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-1) var(--ts-space-5);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ts-nav__list a,
.ts-nav a {
	display: block;
	padding: var(--ts-space-2) 0;
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-decoration: none;
}

.ts-nav__list a:hover,
.ts-nav a:hover {
	color: var(--ts-accent);
	text-decoration: underline;
}

/* Current section, stated with a rule rather than with a colour alone. */
.ts-nav .current-menu-item > a,
.ts-nav .current-cat > a,
.ts-nav__item--current > a {
	box-shadow: inset 0 -2px 0 0 var(--ts-accent);
}

.ts-mast__tools {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: var(--ts-space-2);
	margin-left: auto;
}

.ts-mast__quote,
.ts-mast__cart {
	display: inline-flex;
	align-items: center;
	gap: var(--ts-space-2);
	padding: var(--ts-space-2) var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	color: var(--ts-ink);
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-decoration: none;
	white-space: nowrap;
}

.ts-mast__quote:hover,
.ts-mast__cart:hover {
	border-color: var(--ts-ink);
	color: var(--ts-ink);
}

.ts-mast__cart-n {
	min-width: 1.25rem;
	padding: 0 0.25rem;
	border-radius: 999px;
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
	font-size: var(--ts-text-xs);
	text-align: center;
}

/*
 * WITH JAVASCRIPT the navigation becomes a drawer under the bar. Without it,
 * none of this applies and the list stays where it is, visible.
 */
.has-js .ts-nav {
	display: none;
	position: absolute;
	left: 0;
	right: 0;
	top: 100%;
	padding: var(--ts-space-3) var(--ts-gutter) var(--ts-space-5);
	border-bottom: 1px solid var(--ts-line);
	background: var(--ts-paper);
	box-shadow: var(--ts-elev-1);
}

.has-js .ts-nav.is-open {
	display: block;
}

.has-js .ts-nav .ts-nav__list {
	flex-direction: column;
	gap: 0;
}

.has-js .ts-nav .ts-nav__list > li {
	border-top: 1px solid var(--ts-line);
}

.has-js .ts-nav .ts-nav__list > li:first-child {
	border-top: 0;
}

.has-js .ts-nav .ts-nav__list a {
	padding: var(--ts-space-3) 0;
}

/* ───────────────────────────────────────────────────────────── buttons ── */

/*
 * `.ts-cta` lives in the plugin's `components.css` and is the SAME button the
 * product page uses. What is here is only the secondary size and the
 * side-by-side row, which the product page has no use for.
 */
.ts-cta--wide {
	width: 100%;
}

.ts-btnrow {
	display: flex;
	flex-direction: column;
	gap: var(--ts-space-3);
	margin: var(--ts-space-5) 0 0;
}

.ts-btnrow .ts-cta {
	margin: 0;
}

/* ──────────────────────────────────────────────────────────────── home ── */

.ts-hero {
	padding-block: var(--ts-space-6) var(--ts-space-7);
}

.ts-hero__title {
	margin-bottom: var(--ts-space-4);
	font-size: var(--ts-display-lg);
	letter-spacing: -0.028em;
}

.ts-hero__strap {
	margin: 0 0 var(--ts-space-5);
	max-width: 34ch;
	color: var(--ts-muted);
	font-size: var(--ts-text-lg);
	line-height: var(--ts-leading);
}

/*
 * THE HERO PHOTOGRAPH.
 *
 * One file at three widths, framed by `object-fit` rather than by cropping more
 * files. The source is 2 560 x 853 (3:1) with the subject at about 68 % across
 * and a wide empty peach field on the left: at 375 px a 3:1 strip would be
 * 125 px tall and she would be a thumbnail, so the box is 4:3 on a phone and
 * opens out to 3:2 and then to the frame's own ratio as the page widens.
 * `object-position` keeps her in shot at every one of them.
 *
 * The rounded corner is `--ts-radius`, which is the associate's 10 px, and it
 * is here because this IS a photograph in a frame. The rules and the section
 * edges elsewhere on this page stay square.
 */
.ts-hero__media {
	margin: var(--ts-space-6) 0 0;
	overflow: hidden;
	border-radius: var(--ts-radius);
	background: var(--ts-surface);
	/*
	 * SUR CETTE TEINTE, LE GRIS SECONDAIRE EST L'AUTRE.
	 *
	 * `--ts-muted` est le #767676 de l'associé et il vaut 4,54:1 sur du papier,
	 * ce qui passe AA de quatre centièmes. Sur `--ts-surface` il tombe à 4,24:1
	 * et échoue. Redéclarer le jeton ICI plutôt que de changer la couleur de
	 * chaque texte : une propriété personnalisée cascade, donc tout ce qui est
	 * dessiné dans ce bloc et lit `var(--ts-muted)` prend la variante sombre
	 * sans avoir à être listé quelque part, et rien ne rote quand un enfant est
	 * ajouté. Mesuré par `npm run verify:a11y`, qui refusait 13 textes sur la
	 * page catalogue.
	 */
	--ts-muted: var(--ts-muted-strong);
}

.ts-hero__photo {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 4 / 3;
	object-fit: cover;
	object-position: 68% center;
}

@media (min-width: 45rem) {
	.ts-hero__photo {
		aspect-ratio: 3 / 2;
	}
}

@media (min-width: 60rem) {
	.ts-hero__photo {
		aspect-ratio: 16 / 10;
	}
}

/*
 * The print-zone drawing, now beside the paragraph that makes it useful.
 *
 * `ts-zone-bloc` AND NOT `ts-zone`: `print_zone_figure()` already puts
 * `ts-zone` on the <figure> it returns, so a wrapper of the same name made the
 * selector match twice and `scripts/site-shots.mjs` died on a strict-mode
 * locator violation. It is the harness that caught it, which is the argument
 * for the harness.
 */
.ts-zone-bloc {
	margin-top: var(--ts-space-5);
	max-width: 34rem;
}

.ts-zone-bloc img,
.ts-zone-bloc svg {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * The masthead logo. 300 x 54 is its natural size and it is drawn at a height
 * rather than a width so the bar keeps the same rhythm if the file is ever
 * replaced by one of another proportion.
 */
.ts-logo {
	display: inline-flex;
	align-items: center;
	line-height: 0;
}

.ts-logo img {
	display: block;
	width: auto;
	height: 30px;
}

@media (min-width: 45rem) {
	.ts-logo img {
		height: 36px;
	}
}

.ts-hero__caption {
	margin: 0;
	padding: var(--ts-space-3) var(--ts-space-4);
	border-top: 1px solid var(--ts-line);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
	max-width: none;
}

/*
 * The four facts, as a specification block and not as four badges. Each is a
 * term and its value, ruled off from the next, which is how a spec sheet reads
 * and how a phone can show four of them without scrolling past them.
 */
.ts-facts {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	border-top: 1px solid var(--ts-line);
}

.ts-facts__item {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--ts-space-4);
	padding: var(--ts-space-3) 0;
	border-bottom: 1px solid var(--ts-line);
}

.ts-facts__term {
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
}

.ts-facts__value {
	font-size: var(--ts-text-lg);
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-align: right;
	font-variant-numeric: tabular-nums;
}

/* The bound the price is true up to, under the block that prints the price. */
.ts-facts__note {
	margin: var(--ts-space-3) 0 0;
	max-width: none;
}

.ts-facts__value small {
	display: block;
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	font-weight: 400;
}

/* ──────────────────────────────────────────────────── cards and grids ── */

/*
 * The garment tiles and the WooCommerce loop share one grid. Two columns on a
 * phone, because a one-column list of t-shirts is a scroll nobody finishes, and
 * the photographs are square so two fit honestly at 375 px.
 */
.ts-grid,
.woocommerce ul.products,
ul.products {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ts-space-5) var(--ts-space-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.woocommerce ul.products::before,
.woocommerce ul.products::after,
ul.products::before,
ul.products::after {
	display: none;
}

/*
 * THE SELECTOR IS LONG BECAUSE WOOCOMMERCE'S IS.
 *
 * `woocommerce-layout.css` sets the card width four different ways, and the one
 * that actually applies is `.woocommerce ul.products.columns-3 li.product`,
 * which is one class more specific than the obvious override. Measured on the
 * mirror before this was written: the grid was correctly three columns of
 * 293 px and every card inside was 90,19 px, which is 30,75 % of a column. The
 * attribute selector matches every `columns-N` at once and ties with theirs, and
 * ours is printed last.
 */
.woocommerce ul.products li.product,
.woocommerce ul.products[class*='columns-'] li.product,
.woocommerce[class*='columns-'] ul.products li.product,
.woocommerce-page ul.products[class*='columns-'] li.product,
ul.products li.product,
.ts-card {
	position: relative;
	float: none;
	width: auto;
	margin: 0;
	padding: 0;
	text-align: left;
}

.woocommerce ul.products li.product a img,
ul.products li.product a img,
.ts-card__media img {
	display: block;
	width: 100%;
	aspect-ratio: 1 / 1;
	object-fit: contain;
	margin: 0 0 var(--ts-space-3);
	border: 1px solid var(--ts-line);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.woocommerce ul.products li.product .woocommerce-loop-product__title,
ul.products li.product .woocommerce-loop-product__title,
.ts-card__title {
	margin: 0 0 var(--ts-space-1);
	padding: 0;
	font-size: var(--ts-text);
	font-family: var(--ts-font-display);
	font-weight: 600;
	line-height: var(--ts-leading);
	letter-spacing: 0;
}

.woocommerce ul.products li.product a,
ul.products li.product a {
	color: var(--ts-ink);
	text-decoration: none;
}

.woocommerce ul.products li.product a:hover .woocommerce-loop-product__title,
ul.products li.product a:hover .woocommerce-loop-product__title {
	text-decoration: underline;
	text-decoration-color: var(--ts-accent);
}

/*
 * THE PRICE ON A PRODUCT PAGE, WHICH IS THE ONE FIGURE A BUYER READS.
 *
 * WooCommerce's own stylesheet paints it #958e09 on white at 18,75 px weight
 * 400. Measured: 3,41:1, against the 4,5:1 that size and weight require, so the
 * number the customer pays was the only text on the buying path below AA. The
 * theme keeps Woo's stylesheets deliberately (see the note above the enqueue),
 * and it already overrides the LOOP price below and had never overridden this
 * one. `--ts-ink` on paper is 17,99:1.
 */
.woocommerce:where(body:not(.woocommerce-uses-block-theme)) div.product p.price,
.woocommerce:where(body:not(.woocommerce-uses-block-theme)) div.product span.price,
div.product p.price,
div.product span.price {
	color: var(--ts-ink);
}

/* ──────────────────────────────────────────────── the legal pages ── */

/*
 * The four documents the plugin renders through `the_content`.
 *
 * They are the only pages on this site that are mostly prose with a definition
 * list in the middle, and they arrive with no styling of their own: the theme
 * constrains `p` to `--ts-measure` and stops. A `dl` then renders with the
 * browser's own indent, which reads as a page nobody finished, on the one page
 * whose whole job is to look like it was written on purpose.
 */
.ts-legal__h {
	margin: var(--ts-space-5) 0 var(--ts-space-2);
	font-size: var(--ts-text-lg);
}

.ts-legal__h3 {
	margin: var(--ts-space-4) 0 var(--ts-space-1);
	font-size: var(--ts-text);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-legal__list {
	margin: 0 0 var(--ts-space-3);
	padding-inline-start: 1.25em;
	max-width: var(--ts-measure);
}

.ts-legal__list li {
	margin: 0 0 var(--ts-space-1);
}

/*
 * A label and its value on one row, and the label carries the weight because a
 * reader looking for the SIRET is looking for the word first.
 */
.ts-legal__dl {
	display: grid;
	grid-template-columns: minmax(9rem, auto) 1fr;
	gap: var(--ts-space-1) var(--ts-space-3);
	margin: 0 0 var(--ts-space-4);
	max-width: var(--ts-measure);
}

.ts-legal__dl dt {
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-legal__dl dd {
	margin: 0;
	font-variant-numeric: tabular-nums;
}

/*
 * A missing mention is not styled like an absent one: it is named, in the
 * warning colour, because the page is the place whoever can fill it in will
 * read it. `--ts-warn` is measured at 5,05:1 on paper.
 */
.ts-legal__gap {
	color: var(--ts-warn);
}

.ts-legal__draft,
.ts-legal__note,
.ts-legal__stop {
	margin: 0 0 var(--ts-space-4);
	padding: var(--ts-space-3);
	border-inline-start: 3px solid var(--ts-line-strong);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	max-width: var(--ts-measure);
}

.ts-legal__draft p,
.ts-legal__note p,
.ts-legal__stop p {
	margin: 0;
}

.ts-legal__stop {
	border-inline-start-color: var(--ts-bad);
	background: var(--ts-bad-wash);
	color: var(--ts-bad-ink);
}

.ts-legal__note {
	border-inline-start-color: var(--ts-accent);
}

/*
 * The sort control. It replaces WooCommerce's `.woocommerce-ordering`, which was
 * a bare select that navigated on change; see `sort_control()` in functions.php.
 */
.ts-sort {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	align-items: center;
	margin: 0 0 var(--ts-space-3);
	font-size: var(--ts-text-sm);
}

.ts-sort__label {
	color: var(--ts-muted);
}

.ts-sort__select,
.ts-sort__go {
	min-block-size: 40px;
	padding: 0 var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font: inherit;
}

.ts-sort__go {
	border-color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: 600;
	cursor: pointer;
}

.ts-sort__go:hover {
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.woocommerce ul.products li.product .price,
ul.products li.product .price {
	display: block;
	margin: 0;
	color: var(--ts-ink);
	font-size: var(--ts-text-sm);
	font-variant-numeric: tabular-nums;
}

/* What a product with no photograph looks like: a labelled tile, not a frame. */
.ts-nomedia {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	aspect-ratio: 1 / 1;
	margin: 0 0 var(--ts-space-3);
	border: 1px dashed var(--ts-line-strong);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	color: var(--ts-muted);
	font-size: var(--ts-eyebrow-size);
	font-family: var(--ts-font-display);
	font-weight: 600;
	letter-spacing: var(--ts-eyebrow-tracking);
	text-transform: uppercase;
}

.ts-card__meta {
	margin: 0;
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	max-width: none;
}

/*
 * The line that says what the shop can promise about a blank, in words and never
 * a figure: the four strings come from `Shelf::availability()` and the ledger
 * fails if one of them disappears.
 *
 * THE CLASS NAMES ARE THE PLUGIN'S, not ones invented here. `Shelf::availability`
 * returns `teeshoop-stock-in`, `teeshoop-stock-thin`, `teeshoop-stock-late` and
 * `teeshoop-stock-unknown`
 * and WooCommerce prints them on the `<p class="stock">`. This file first styled
 * `.ts-stock--yes/no/unknown`, which nothing on the site emits: three rules that
 * matched nothing, so the one sentence the shop says about supplier stock was
 * rendered in WooCommerce's default green whatever it said, including
 * « Rupture ».
 */
.woocommerce p.stock {
	margin: var(--ts-space-2) 0 0;
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.woocommerce p.stock.teeshoop-stock-in {
	color: var(--ts-good);
}

.woocommerce p.stock.teeshoop-stock-late {
	color: var(--ts-bad);
}

/*
 * « Stock limité, nous consulter », question 48, added 2 September 2026. Warning
 * and not bad: the article can still be bought, in a quantity the shop wants to
 * discuss first. Semantic colour, kept apart from the brand accent, as
 * `CLAUDE.md` section 7 requires.
 */
.woocommerce p.stock.teeshoop-stock-thin {
	color: var(--ts-warn);
}

.woocommerce p.stock.teeshoop-stock-unknown {
	color: var(--ts-muted);
}

/* ────────────────────────────────────────────── an honest empty state ── */

.ts-empty {
	padding: var(--ts-space-6);
	border: 1px dashed var(--ts-line-strong);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-empty__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-display-xs);
}

.ts-empty p:last-child {
	margin-bottom: 0;
}

/* ──────────────────────────────────────────────────────────── filters ── */

.ts-shop {
	display: block;
	padding-block: var(--ts-space-6) var(--ts-space-8);
}

.ts-shop__head {
	margin: 0 0 var(--ts-space-5);
}

.ts-shop__count {
	margin: 0;
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
	font-variant-numeric: tabular-nums;
}

.ts-filters {
	margin: 0 0 var(--ts-space-6);
	border: 1px solid var(--ts-line);
	background: var(--ts-paper);
}

.ts-filters__toggle {
	display: none;
	align-items: center;
	justify-content: space-between;
	gap: var(--ts-space-3);
	width: 100%;
	padding: var(--ts-space-3) var(--ts-space-4);
	border: 0;
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	color: var(--ts-ink);
	font: inherit;
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-align: left;
	cursor: pointer;
}

/* A disclosure triangle, drawn rather than iconified, that says which way the
   panel will go. It is the one mark on this site that means "this opens". */
.ts-filters__toggle::after {
	display: block;
	width: 0.5rem;
	height: 0.5rem;
	content: '';
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: rotate(45deg) translate(-0.15rem, -0.15rem);
}

.ts-filters__toggle[aria-expanded='true']::after {
	transform: rotate(225deg) translate(-0.1rem, -0.1rem);
}

.has-js .ts-filters__toggle {
	display: flex;
}

.has-js .ts-filters__body {
	display: none;
}

.has-js .ts-filters__body.is-open {
	display: block;
}

.ts-filters__body {
	padding: var(--ts-space-4);
}

.ts-facet {
	margin: 0 0 var(--ts-space-5);
	padding: 0;
	border: 0;
}

.ts-facet:last-of-type {
	margin-bottom: var(--ts-space-4);
}

.ts-facet__legend {
	display: block;
	margin: 0 0 var(--ts-space-2);
	padding: 0;
	color: var(--ts-muted);
	font-size: var(--ts-eyebrow-size);
	font-family: var(--ts-font-display);
	font-weight: 600;
	letter-spacing: var(--ts-eyebrow-tracking);
	text-transform: uppercase;
}

.ts-facet__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
	/*
	 * A colour facet on this catalogue is several hundred terms long. It is
	 * capped in height and scrolls in place, so a phone never has to swipe past
	 * four hundred checkboxes to reach the next facet.
	 */
	/*
	 * The colour facet on this catalogue is four hundred and forty-two terms and
	 * the brief forbids merging them, so the list stays long and scrolls inside
	 * its own box. The border is what says so: without it the last visible item
	 * looks cut off rather than scrollable.
	 */
	max-height: 11rem;
	padding: var(--ts-space-2);
	border: 1px solid var(--ts-line);
	overflow-y: auto;
	overscroll-behavior: contain;
}

/*
 * The colour facet is not a list any more, it is eleven groups, so the height
 * cap above moves off the container. With every group shut the whole facet is
 * eleven lines tall and needs no box; the cap goes on the OPEN group's list
 * instead, because « Bleus » alone holds ninety-six names and an open group
 * with nothing bounding it is the four-hundred-checkbox swipe the cap was
 * written to prevent, one level down.
 */
.ts-facet--couleur .ts-facet__list,
.ts-facet--couleur > .ts-fam {
	max-height: none;
	overflow: visible;
}

.ts-facet__list--short {
	max-height: none;
	padding: 0;
	border: 0;
	overflow: visible;
}

/*
 * A facet value is a real checkbox with a real label, so the whole panel works
 * with the keyboard and submits without a script. The checkbox itself is
 * visually replaced by the chip, never removed from the accessibility tree.
 */
.ts-chip {
	/*
	 * POSITIONED, SO THE HIDDEN CHECKBOX BELONGS TO IT.
	 *
	 * `.ts-chip input` is `position: absolute` and clipped, and with no
	 * positioned ancestor here its containing block was the whole filter FORM.
	 * The checkbox therefore escaped `.ts-facet__list`'s `overflow-y: auto`, so
	 * the browser had nothing to scroll when focus landed on it: measured at
	 * 1440 px, the list stayed at scrollTop 0 through a 500 px facet with 174 px
	 * of room, and the chip the keyboard was on sat clipped out of sight with its
	 * focus ring drawn on it. 90 of 255 tab stops on the catalogue and 75 of 227
	 * on a category were in that state, invisible to anybody using a mouse.
	 */
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--ts-space-2);
	padding: 0.3125rem var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-size: var(--ts-text-sm);
	text-decoration: none;
	cursor: pointer;
	white-space: nowrap;
}

/* A category chip is a link and a facet chip is a checkbox; they look alike on
   purpose, because to a buyer they do the same job. */
a.ts-chip:hover {
	color: var(--ts-accent);
	border-color: var(--ts-accent);
}

.ts-chip input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	clip-path: inset(50%);
	overflow: hidden;
}

.ts-chip:hover {
	border-color: var(--ts-ink);
}

.ts-chip:has(input:checked) {
	border-color: var(--ts-accent);
	background: var(--ts-accent-wash);
	color: var(--ts-accent);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-chip:has(input:focus-visible) {
	outline: var(--ts-focus-width) solid var(--ts-accent);
	outline-offset: var(--ts-focus-offset);
}

.ts-chip__n {
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	font-variant-numeric: tabular-nums;
}

.ts-chip:has(input:checked) .ts-chip__n {
	color: inherit;
}

/*
 * ─── the colour facet ───────────────────────────────────────────────────────
 *
 * A swatch is a MEASURED value (`Swatch` in the plugin), delivered per chip as
 * `--ts-sw-a` and, for a two-tone colourway, `--ts-sw-b`. No hexadecimal is
 * written here: this file holds no colour that is not a token, and a measured
 * one is data, so it arrives on the element.
 *
 * The border is not decoration. « White » and « Off White » are real articles
 * and their swatches are all but invisible on the panel's own paper, so every
 * swatch carries an edge, and that edge is `--ts-line-strong`, the token
 * chosen for WCAG 1.4.11's 3:1 on a control boundary.
 */
.ts-chip__swatch {
	width: 0.875rem;
	height: 0.875rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 2px;
	background: var(--ts-sw-a, transparent);
	flex: 0 0 auto;
}

/*
 * Two colours, split hard on the diagonal rather than blended. « Navy/White »
 * is two dyes and a gradient between them would draw a colour the garment does
 * not come in.
 */
.ts-chip__swatch--deux {
	background: linear-gradient(135deg, var(--ts-sw-a) 0 50%, var(--ts-sw-b) 50% 100%);
}

.ts-fam {
	border: 1px solid var(--ts-line);
}

.ts-fam__group + .ts-fam__group {
	border-top: 1px solid var(--ts-line);
}

/*
 * The native disclosure marker is removed on both engines (`list-style` for
 * Firefox and Safari, `::-webkit-details-marker` for the rest) and a chevron is
 * drawn below, so that it matches the one the filter panel's own button uses.
 */
.ts-fam__head {
	display: flex;
	align-items: center;
	gap: var(--ts-space-2);
	padding: var(--ts-space-2) var(--ts-space-3);
	background: var(--ts-paper);
	cursor: pointer;
	list-style: none;
}

.ts-fam__head::-webkit-details-marker {
	display: none;
}

.ts-fam__head::before {
	flex: 0 0 auto;
	width: 0.4375rem;
	height: 0.4375rem;
	content: '';
	border-right: 2px solid var(--ts-muted);
	border-bottom: 2px solid var(--ts-muted);
	transform: rotate(-45deg) translate(0.1rem, -0.1rem);
}

.ts-fam__group[open] > .ts-fam__head::before {
	transform: rotate(45deg) translate(-0.05rem, -0.05rem);
}

.ts-fam__head:hover {
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-fam__head:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-accent);
	outline-offset: calc(-1 * var(--ts-focus-width));
}

.ts-fam__name {
	/*
	 * The name pushes everything after it to the right, whether that is a strip
	 * and a count or a count alone. Hanging the margin off the count instead
	 * needed `:first-of-type`, which is never true of it: the name is a span too.
	 */
	margin-right: auto;
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

/*
 * Six dots of what is inside, so the eye can find the greens without reading
 * eleven words. Decorative and marked as such: the heading beside it is the
 * information (WCAG 1.4.1).
 */
.ts-fam__strip {
	display: flex;
	gap: 2px;
}

.ts-fam__dot {
	width: 0.625rem;
	height: 0.625rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 50%;
	background: var(--ts-sw-a, transparent);
}

.ts-fam__n {
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.ts-fam__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	max-height: 60vh;
	margin: 0;
	padding: var(--ts-space-3);
	border-top: 1px solid var(--ts-line);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	overflow-y: auto;
	overscroll-behavior: contain;
	list-style: none;
}

/* « Tous les bleus » is the whole family and reads as the heading of its list. */
.ts-chip--all {
	flex: 1 0 100%;
	justify-content: flex-start;
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	border-style: dashed;
}

.ts-fam__why,
.ts-fam__source {
	margin: 0;
	padding: var(--ts-space-3);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
	max-width: none;
}

.ts-fam__why {
	border-top: 1px solid var(--ts-line);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-fam__source {
	padding-left: 0;
	padding-right: 0;
}

.ts-facet__range {
	display: flex;
	align-items: center;
	gap: var(--ts-space-2);
}

.ts-facet__range input {
	width: 6rem;
	padding: 0.375rem 0.5rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	font: inherit;
	font-size: 1rem;
	font-variant-numeric: tabular-nums;
}

.ts-filters__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ts-space-3);
	padding-top: var(--ts-space-4);
	border-top: 1px solid var(--ts-line);
}

.ts-filters__actions .ts-cta {
	flex: 1 1 auto;
	display: inline-block;
	width: auto;
}

.ts-filters__clear {
	font-size: var(--ts-text-sm);
}

/* What is currently filtering the list, and a way out of each one. */
.ts-applied {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ts-space-2);
	margin: 0 0 var(--ts-space-4);
	padding: 0;
	list-style: none;
}

.ts-applied__item a {
	display: inline-flex;
	align-items: center;
	gap: var(--ts-space-2);
	padding: 0.25rem var(--ts-space-2);
	border: 1px solid var(--ts-accent);
	border-radius: var(--ts-radius);
	background: var(--ts-accent-wash);
	color: var(--ts-accent);
	font-size: var(--ts-text-sm);
	text-decoration: none;
}

.ts-applied__item a::after {
	content: '\00d7';
	font-size: 1rem;
	line-height: 1;
}

/* ───────────────────────────────────────────────────────── pagination ── */

.woocommerce nav.woocommerce-pagination ul,
.ts-pager {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	margin: var(--ts-space-6) 0 0;
	padding: 0;
	border: 0;
	list-style: none;
}

.woocommerce nav.woocommerce-pagination ul li {
	border: 0;
	margin: 0;
	overflow: visible;
}

.woocommerce nav.woocommerce-pagination ul li a,
.woocommerce nav.woocommerce-pagination ul li span {
	display: block;
	min-width: 2.5rem;
	padding: var(--ts-space-2) var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-size: var(--ts-text-sm);
	font-variant-numeric: tabular-nums;
	text-align: center;
	text-decoration: none;
}

.woocommerce nav.woocommerce-pagination ul li span.current {
	border-color: var(--ts-accent);
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────── studio ── */

/*
 * The editor is an iframe on our own product page, and it is dressed like the
 * rest of the site: our rule above it, our breadcrumb back, no browser chrome
 * of its own. A customer never sees that it is served from another origin, and
 * that is the point: the frame boundary is an engineering decision, not
 * something a buyer should have to notice.
 */
.ts-studio {
	padding: var(--ts-space-4) 0 var(--ts-space-7);
}

.ts-studio__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--ts-space-3);
	margin: 0 0 var(--ts-space-4);
	padding-bottom: var(--ts-space-3);
	border-bottom: 1px solid var(--ts-line);
}

.ts-studio__title {
	margin: 0;
	font-size: var(--ts-display-xs);
}

.ts-studio__back {
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

/* ─────────────────────────────────────────────────────────────── footer ── */

/*
 * THE FOOTER IS THE BRAND GROUND, and this one line is most of what makes a
 * page read as this shop's. It was the ink, which is a near-black every theme
 * has. The navy is the associate's, measured off his logo.
 *
 * THE MASTHEAD IS NOT PAINTED WITH IT, deliberately: the logo is two colours on
 * a transparent ground and one of them IS this navy, so on a navy bar half the
 * mark disappears. The logo is never repeated down here for the same reason.
 *
 * Everything drawn on this ground is measured against #010050 and not against
 * the old #14171a: white 18,80:1, `--ts-on-brand-muted` 8,58:1,
 * `--ts-on-brand-warn` 12,02:1. The focus ring survives the change without an
 * override because its visible edge is the white halo, not the ink outline.
 */
.ts-foot {
	margin-top: var(--ts-space-8);
	border-top: 1px solid var(--ts-line);
	background: var(--ts-accent);
	color: var(--ts-paper);
}

.ts-foot a {
	color: var(--ts-paper);
	text-decoration: none;
}

.ts-foot a:hover {
	color: var(--ts-paper);
	text-decoration: underline;
}

.ts-foot__grid {
	display: grid;
	gap: var(--ts-space-6);
	padding-top: var(--ts-space-7);
	padding-bottom: var(--ts-space-6);
}

.ts-foot__title {
	margin: 0 0 var(--ts-space-3);
	font-size: var(--ts-eyebrow-size);
	font-family: var(--ts-font-display);
	font-weight: 600;
	letter-spacing: var(--ts-eyebrow-tracking);
	text-transform: uppercase;
	/*
	 * Deliberately not `--ts-muted`: measured on this ground it is 3,00:1, and
	 * this is a heading rather than a hint.
	 */
	color: var(--ts-on-brand-muted);
}

.ts-foot__list {
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: var(--ts-text-sm);
	line-height: var(--ts-leading-loose);
}

/*
 * 2.5.8 TARGET SIZE, WHICH IS NEW IN WCAG 2.2 AND WHICH THESE LINKS FAILED.
 *
 * Measured at 375 px: « Polos » was 35 x 17 with 6,1 px between it and the link
 * below, so 23,1 px between the two centres, under the 24 px circle the spacing
 * exception draws. Three links per page, on the navigation a phone buyer uses.
 *
 * The link becomes a block with vertical padding rather than the list gaining a
 * gap, because the padding also makes the TARGET bigger and not only the
 * distance: a 17 px link 24 px from its neighbour still asks for a 17 px tap.
 */
.ts-foot__list a {
	display: inline-block;
	padding-block: 0.25rem;
	min-block-size: 24px;
}

.ts-foot__list .ts-num {
	font-variant-numeric: tabular-nums;
}

.ts-foot__address {
	margin: 0 0 var(--ts-space-3);
	font-style: normal;
	font-size: var(--ts-text-sm);
	line-height: var(--ts-leading-loose);
}

.ts-foot__address span {
	display: block;
}

.ts-foot__name {
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-foot__legal {
	margin: 0;
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading-loose);
}

.ts-foot__legal dt {
	color: var(--ts-on-brand-muted);
}

.ts-foot__legal dd {
	margin: 0 0 var(--ts-space-2);
}

/*
 * The missing legal identity, said out loud.
 *
 * Warn-coloured on a dark ground, so the paper tone cannot be used:
 * `--ts-warn` is chosen against white. `--ts-on-brand-warn` is chosen against
 * this one and measures 11,50:1 on it, for the text and for the rule.
 */
.ts-foot__gap {
	margin: var(--ts-space-3) 0 0;
	padding: var(--ts-space-3);
	border-left: 3px solid var(--ts-on-brand-warn);
	background: color-mix(in srgb, var(--ts-paper) 6%, transparent);
	color: var(--ts-on-brand-warn);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
	max-width: none;
}

/* The admin-only assumptions marker, readable on the dark ground it sits on. */
.ts-foot__marker .ts-admin-note {
	margin: 0 0 var(--ts-space-4);
	border-left-color: var(--ts-on-brand-warn);
	background: color-mix(in srgb, var(--ts-paper) 6%, transparent);
	color: var(--ts-on-brand-warn);
}

.ts-foot__base {
	padding-top: var(--ts-space-4);
	padding-bottom: var(--ts-space-5);
	border-top: 1px solid color-mix(in srgb, var(--ts-paper) 12%, transparent);
}

.ts-foot__copy {
	margin: 0;
	color: var(--ts-on-brand-muted);
	font-size: var(--ts-text-xs);
	font-variant-numeric: tabular-nums;
}

.ts-foot__base {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2) var(--ts-space-4);
	align-items: baseline;
	justify-content: space-between;
}

.ts-foot__consent {
	margin: 0;
	font-size: var(--ts-text-xs);
}

.ts-foot__consent a {
	color: var(--ts-paper);
}

.ts-foot__consent-date {
	color: var(--ts-on-brand-muted);
}

.ts-foot__consent-date::before {
	content: ', ';
}

/* ───────────────────────────────────────── WooCommerce, brought into line ── */

.woocommerce-breadcrumb {
	margin: var(--ts-space-4) 0 var(--ts-space-3);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
}

.woocommerce-breadcrumb a {
	color: var(--ts-muted);
}

/*
 * WooCommerce floats the sort control right and the result count left, which in
 * a grid container leaves both hanging over the products. The count is removed
 * in `functions.php` (the listing prints its own); the sort stays, as a block.
 */
.woocommerce .woocommerce-result-count,
.woocommerce .woocommerce-ordering {
	float: none;
	display: flex;
	justify-content: flex-end;
	margin: 0 0 var(--ts-space-4);
	font-size: var(--ts-text-sm);
}

.woocommerce-notices-wrapper:empty {
	display: none;
}

.woocommerce .woocommerce-ordering select {
	padding: var(--ts-space-2) var(--ts-space-3);
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	font: inherit;
	font-size: 1rem;
}

.woocommerce-message,
.woocommerce-info,
.woocommerce-error {
	margin: 0 0 var(--ts-space-5);
	padding: var(--ts-space-3) var(--ts-space-4);
	border: 0;
	border-left: 3px solid var(--ts-accent);
	border-radius: 0;
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	color: var(--ts-ink);
	font-size: var(--ts-text);
	list-style: none;
}

.woocommerce-message::before,
.woocommerce-info::before,
.woocommerce-error::before {
	display: none;
}

.woocommerce-message {
	border-left-color: var(--ts-good);
}

.woocommerce-error {
	border-left-color: var(--ts-bad);
}

/*
 * WooCommerce's own buttons, brought into the same language as `.ts-cta`.
 *
 * They appear on the listing ("Choix des options"), in the cart, at the
 * checkout and in the account area, so they are restyled rather than replaced:
 * a second button component would be a second set of states to keep in step.
 */
.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button,
.woocommerce #respond input#submit {
	display: inline-block;
	padding: 0.5rem 0.875rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-family: inherit;
	font-size: var(--ts-text-sm);
	font-weight: 600;
	line-height: 1.3;
	text-decoration: none;
	white-space: normal;
}

.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover,
.woocommerce #respond input#submit:hover {
	border-color: var(--ts-ink);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	color: var(--ts-ink);
}

/* The one that completes a purchase is the accent one. */
.woocommerce button.button.alt,
.woocommerce a.button.alt,
.woocommerce input.button.alt,
.woocommerce #place_order {
	border-color: var(--ts-accent);
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
}

.woocommerce button.button.alt:hover,
.woocommerce a.button.alt:hover,
.woocommerce input.button.alt:hover,
.woocommerce #place_order:hover {
	border-color: var(--ts-accent-dark);
	background: var(--ts-accent-dark);
	color: var(--ts-accent-ink);
}

.woocommerce ul.products li.product .button {
	margin-top: var(--ts-space-2);
}

/*
 * THE PRODUCT PAGE IS A GRID, NOT TWO FLOATS.
 *
 * WooCommerce lays a product out with `div.images { float: left; width: 48% }`
 * and `div.summary { float: right; width: 48% }`, and clears only the tabs.
 * Everything the Teeshoop plugin adds below the summary (the price grid at
 * priority 5 and the devis block at 12) therefore rendered INSIDE the 48 %
 * gutter left by the floats: the heading « Le prix par quantité » came out four
 * words wide, stacked down the page, over the photograph.
 *
 * Turning the wrapper into a two-column grid fixes it without copying a single
 * WooCommerce template: the two known children take a column each and
 * everything else spans the full width, whatever a plugin adds later.
 */
.woocommerce div.product {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 var(--ts-space-7);
	align-items: start;
}

.woocommerce div.product > div.images,
.woocommerce div.product > div.summary,
.woocommerce div.product > * {
	float: none;
	width: auto;
	grid-column: 1 / -1;
}

/* The catalogue's own state: a reference with no published tariff. */
.teeshoop-unpriced {
	margin: var(--ts-space-4) 0 var(--ts-space-2);
	padding: var(--ts-space-3) var(--ts-space-4);
	border-left: 3px solid var(--ts-warn);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	font-size: var(--ts-text);
}

.teeshoop-colour-source {
	margin: 0 0 var(--ts-space-4);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
}

/* ───────────────────────────────────────────────── hero, and the drawing ── */

.ts-hero__second {
	font-family: var(--ts-font-display);
	font-weight: 600;
}

/*
 * The print-zone drawing. Three rectangles, all to one scale, all real: two
 * garment zones from `Garments::areas()` and an A4 sheet, because "30,5 cm"
 * means nothing to a buyer and "wider than a sheet of paper" means everything.
 */
.ts-zone {
	margin: 0;
	padding: var(--ts-space-5) var(--ts-space-4) var(--ts-space-4);
	border: 1px solid var(--ts-line);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-zone__svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

.ts-zone__ref {
	fill: color-mix(in srgb, var(--ts-accent) 10%, transparent);
	stroke: var(--ts-accent);
	stroke-width: 1.5;
}

.ts-zone__max {
	fill: none;
	stroke: var(--ts-line-strong);
	stroke-width: 1.5;
	stroke-dasharray: 5 4;
}

.ts-zone__a4 {
	fill: var(--ts-paper);
	stroke: var(--ts-line-strong);
	stroke-width: 1.5;
}

/*
 * THE KEY IS HTML, NOT SVG TEXT.
 *
 * The drawing is a 640-unit viewBox displayed at whatever width it gets, so a
 * 13-unit label inside it rendered at 6,9 px on a 375 px phone: the one place on
 * the page where the numbers are the point, in the smallest type on the site.
 * Out here they are at the site's own size and they zoom with everything else.
 */
.ts-zone__caption {
	margin: var(--ts-space-4) 0 0;
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
}

.ts-zone__key {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2) var(--ts-space-4);
	margin: 0 0 var(--ts-space-3);
	padding: 0;
	list-style: none;
	font-size: var(--ts-text-sm);
}

/* Each key item carries the mark it names, so the list reads against the drawing. */
.ts-zone__key-item {
	display: flex;
	align-items: center;
	gap: var(--ts-space-2);
	color: var(--ts-ink);
}

.ts-zone__key-item::before {
	display: block;
	width: 1.25rem;
	height: 0.75rem;
	flex: 0 0 auto;
	content: '';
}

.ts-zone__key-item--ref::before {
	border: 1.5px solid var(--ts-accent);
	background: color-mix(in srgb, var(--ts-accent) 10%, transparent);
}

.ts-zone__key-item--max::before {
	border: 1.5px dashed var(--ts-line-strong);
}

.ts-zone__key-item--a4::before {
	border: 1.5px solid var(--ts-line-strong);
	background: var(--ts-paper);
}

.ts-zone__note {
	margin: 0;
	max-width: none;
}

/* ────────────────────────────────────────────────── the families, as tiles ── */

/*
 * THE CATALOGUE'S OWN SHAPE, WITH THE ASSOCIATE'S PHOTOGRAPHS IN IT.
 *
 * This was a ruled list, argued for with « tiles need a photograph each and a
 * category has no single honest photograph ». The photographs existed the whole
 * time: eleven of them, people wearing marked garments, attached to these very
 * terms on teeshoop.com since May 2025.
 *
 * THE TILE IS THE SIZE THE FILE CAN FILL. Those photographs are 170 x 170,
 * measured, and that is their original: there is no larger version on the
 * server. So the picture is drawn at 96 px (128 px from the medium breakpoint),
 * which a 170 px file still covers on a 1x screen and softens only slightly on
 * a 2x one. Blowing it up to a 400 px banner would be the one thing worse than
 * the list it replaces.
 *
 * That is also why this is a ROW and not a card: photograph left, name and
 * count right, on one rule. It reads as a catalogue index, which is what it is,
 * and it stays legible at 375 px without a second layout.
 */
.ts-fams {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	border-top: 1px solid var(--ts-line);
}

@media (min-width: 45rem) {
	.ts-fams {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--ts-space-6);
	}
}

@media (min-width: 75rem) {
	.ts-fams {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.ts-fams__item {
	border-bottom: 1px solid var(--ts-line);
}

.ts-fams__link {
	display: flex;
	align-items: center;
	gap: var(--ts-space-4);
	padding: var(--ts-space-3) 0;
	color: var(--ts-ink);
	text-decoration: none;
}

/*
 * The picture is a fixed square and never a fraction of the row, so eleven
 * tiles line up whatever the length of their names. `object-fit: cover` is
 * there for the day a category is given a photograph that is not square.
 */
.ts-fams__media {
	flex: 0 0 auto;
	display: block;
	overflow: hidden;
	width: 96px;
	height: 96px;
	border-radius: var(--ts-radius);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-fams__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

@media (min-width: 45rem) {
	.ts-fams__media {
		width: 128px;
		height: 128px;
	}
}

.ts-fams__text {
	display: flex;
	flex-direction: column;
	gap: var(--ts-space-1);
	min-width: 0;
}

/*
 * THE TILE WITH NO PHOTOGRAPH, designed rather than avoided. It keeps the row
 * height so the grid does not stagger, and it says so with the brand wash
 * instead of a grey rectangle pretending to be a loading image.
 */
.ts-fams__link--typeonly {
	padding-left: var(--ts-space-4);
	border-left: 3px solid var(--ts-accent-wash);
}

.ts-fams__link:hover .ts-fams__name {
	color: var(--ts-accent);
	text-decoration: underline;
}

.ts-fams__name {
	font-size: var(--ts-text-lg);
	font-family: var(--ts-font-display);
	font-weight: 600;
	overflow-wrap: anywhere;
}

.ts-fams__n {
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
	white-space: nowrap;
}

/* ──────────────────────────────────────────────────────────────── sectors ── */

.ts-sectors {
	display: grid;
	gap: var(--ts-space-5);
}

.ts-sector {
	padding-top: var(--ts-space-4);
	border-top: 2px solid var(--ts-ink);
}

.ts-sector__title {
	margin: 0 0 var(--ts-space-3);
	font-size: var(--ts-display-xs);
}

.ts-sector__needs {
	margin: 0 0 var(--ts-space-3);
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
	max-width: none;
}

.ts-sector__answer {
	margin: 0;
	max-width: none;
}

.ts-section__more {
	margin: var(--ts-space-5) 0 0;
	font-family: var(--ts-font-display);
	font-weight: 600;
}

/* ────────────────────────────────────────────────────────────────── steps ── */

/*
 * Numbered, because this IS a sequence: the numbers carry the order, they are
 * not decoration on four unrelated blocks.
 */
.ts-steps {
	display: grid;
	gap: var(--ts-space-5);
	margin: 0;
	padding: 0;
	list-style: none;
	counter-reset: ts-step;
}

.ts-steps__item {
	padding-left: 2.5rem;
	counter-increment: ts-step;
	position: relative;
}

.ts-steps__item::before {
	position: absolute;
	left: 0;
	top: 0.1em;
	display: block;
	width: 1.75rem;
	content: counter(ts-step);
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
	font-variant-numeric: tabular-nums;
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-align: right;
	border-right: 2px solid var(--ts-accent);
	padding-right: 0.5rem;
	line-height: 1.4;
}

.ts-steps__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-text-lg);
}

.ts-steps__item p {
	margin: 0;
	color: var(--ts-muted);
}

/* ─────────────────────────────────────────────────────── the way to a human ── */

.ts-advice {
	margin-top: var(--ts-space-8);
	padding: var(--ts-space-6) 0;
	border-top: 1px solid var(--ts-line);
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
}

.ts-advice__inner {
	display: grid;
	gap: var(--ts-space-4);
}

.ts-advice__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-display-xs);
}

.ts-advice__lead {
	margin: 0;
	color: var(--ts-muted);
}

.ts-advice__cta {
	margin: 0;
	max-width: none;
}

.ts-advice__cta .ts-cta {
	display: inline-block;
}

/* ──────────────────────────────────────────────────────── prose and lists ── */

.ts-prose {
	padding: var(--ts-space-6) 0 var(--ts-space-7);
}

.ts-prose__title {
	margin-bottom: var(--ts-space-5);
}

.ts-prose__meta {
	margin-top: -1rem;
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
}

.ts-single {
	padding-block: var(--ts-space-5) var(--ts-space-7);
}

.ts-postlist {
	margin: 0;
	padding: 0;
	list-style: none;
}

.ts-postlist__item {
	padding: var(--ts-space-5) 0;
	border-top: 1px solid var(--ts-line);
}

.ts-postlist__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-display-xs);
}

.ts-postlist__date {
	margin: 0 0 var(--ts-space-2);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
}

.ts-checks {
	margin: var(--ts-space-5) 0;
	padding: 0;
	list-style: none;
}

.ts-checks li {
	position: relative;
	padding: var(--ts-space-2) 0 var(--ts-space-2) var(--ts-space-5);
	border-bottom: 1px solid var(--ts-line);
	max-width: var(--ts-measure);
}

.ts-checks li::before {
	position: absolute;
	left: 0;
	top: var(--ts-space-2);
	content: '';
	width: 0.5rem;
	height: 0.5rem;
	margin-top: 0.45em;
	background: var(--ts-accent);
}

.ts-defs--wide dt {
	margin-top: var(--ts-space-4);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-defs--wide dd {
	margin: var(--ts-space-1) 0 0;
	max-width: var(--ts-measure);
	color: var(--ts-muted);
}

.ts-devispage {
	display: grid;
	gap: var(--ts-space-6);
	padding-block: var(--ts-space-6) var(--ts-space-7);
}

/* ──────────────────────────────────────────────────────── shop internals ── */

.ts-shop__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-display-sm);
}

.ts-shop__intro {
	margin: 0 0 var(--ts-space-3);
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
}

.ts-shop__intro p {
	margin: 0 0 var(--ts-space-2);
}

.ts-facet__rangelabel {
	display: block;
}

.ts-facet__unit {
	color: var(--ts-muted);
	font-size: var(--ts-text-sm);
	white-space: nowrap;
}

.ts-facet__find {
	display: block;
	margin: 0 0 var(--ts-space-2);
}

.ts-facet__findinput {
	width: 100%;
	padding: 0.375rem 0.5rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: var(--ts-radius);
	font: inherit;
	/* 16 px, or iOS zooms the viewport the moment it is focused. */
	font-size: 1rem;
}

.ts-filters__gap {
	margin: var(--ts-space-4) 0 0;
	padding-top: var(--ts-space-3);
	border-top: 1px solid var(--ts-line);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
	max-width: none;
}

/* Only visible to a screen reader, and still focusable. */
.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	clip-path: inset(50%);
	overflow: hidden;
	word-wrap: normal;
}

/* ──────────────────────────────────────── the editor, dressed as our own ── */

/* ────────────────────────────────────────────────────────────── 640 px ── */

@media (min-width: 640px) {
	:root {
		--ts-gutter: var(--ts-gutter-wide);
	}

	.ts-burger__label {
		position: static;
		width: auto;
		height: auto;
		margin: 0;
		clip-path: none;
	}

	.ts-mast__tools {
		gap: var(--ts-space-3);
	}

	.ts-facts {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--ts-space-6);
	}

	.ts-btnrow {
		flex-direction: row;
		align-items: center;
	}

	.ts-btnrow .ts-cta {
		flex: 0 0 auto;
		min-width: 14rem;
	}

	.ts-grid,
	.woocommerce ul.products,
	ul.products {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.ts-sectors {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		gap: var(--ts-space-5);
	}

	.ts-steps {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--ts-space-6) var(--ts-space-5);
	}

	.ts-advice__inner {
		grid-template-columns: minmax(0, 1fr) auto;
		align-items: center;
		gap: var(--ts-space-6);
	}

	.ts-devispage {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
		gap: var(--ts-space-7);
		align-items: start;
	}

	/*
	 * The administrative facts read as a table of terms, which is what they are.
	 * Stacked they left half the page empty on a desktop and the eye had to
	 * travel back to the left margin for every one of the six.
	 */
	.ts-defs--wide {
		display: grid;
		grid-template-columns: 12rem minmax(0, 1fr);
		gap: var(--ts-space-2) var(--ts-space-5);
	}

	.ts-defs--wide dt {
		margin-top: 0;
		grid-column: 1;
		padding-top: var(--ts-space-4);
		border-top: 1px solid var(--ts-line);
	}

	.ts-defs--wide dd {
		grid-column: 2;
		margin: 0;
		padding-top: var(--ts-space-4);
		border-top: 1px solid var(--ts-line);
	}

	.ts-defs--wide dt:first-of-type,
	.ts-defs--wide dt:first-of-type + dd {
		border-top: 0;
		padding-top: 0;
	}
}

/* ────────────────────────────────────────────────────────────── 900 px ── */

@media (min-width: 900px) {
	.ts-burger {
		display: none;
	}

	/*
	 * At this width the navigation is a row in the bar again, whatever the
	 * script did to it at a smaller size.
	 */
	.has-js .ts-nav {
		display: block;
		position: static;
		padding: 0;
		border: 0;
		box-shadow: none;
	}

	.has-js .ts-nav .ts-nav__list {
		flex-direction: row;
		gap: var(--ts-space-1) var(--ts-space-5);
	}

	.has-js .ts-nav .ts-nav__list > li {
		border-top: 0;
	}

	.has-js .ts-nav .ts-nav__list a {
		padding: var(--ts-space-2) 0;
	}

	.ts-nav {
		margin: 0 var(--ts-space-5);
	}

	.ts-hero {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
		align-items: center;
		gap: var(--ts-space-7);
		padding-block: var(--ts-space-8);
	}

	.ts-hero__media {
		margin: 0;
	}

	.ts-facts {
		grid-template-columns: repeat(4, minmax(0, 1fr));
		border-bottom: 1px solid var(--ts-line);
	}

	.ts-facts__item {
		flex-direction: column;
		align-items: flex-start;
		justify-content: flex-end;
		gap: var(--ts-space-1);
		border-bottom: 0;
		border-left: 1px solid var(--ts-line);
		padding: var(--ts-space-4);
	}

	.ts-facts__item:first-child {
		border-left: 0;
		padding-left: 0;
	}

	.ts-facts__value {
		text-align: left;
	}

	/* The listing gets a filter column, sticky, because the list is long. */
	.ts-shop {
		display: grid;
		grid-template-columns: 15rem minmax(0, 1fr);
		align-items: start;
		/*
		 * The row gap is small and the column gap is not. With one value the
		 * heading and the grid of products sat 48 px apart for no reason, which
		 * on a listing reads as a page that failed to load something.
		 */
		column-gap: var(--ts-space-7);
		row-gap: var(--ts-space-4);
	}

	.ts-shop__head,
	.ts-shop__results {
		grid-column: 2;
	}

	.ts-filters {
		/* Exactly the two rows that exist. `span 3` invented a third, empty
		   one, and the page ended on a band of nothing. */
		grid-row: 1 / span 2;
		grid-column: 1;
		position: sticky;
		top: 4.5rem;
		max-height: calc(100vh - 6rem);
		overflow-y: auto;
	}

	/*
	 * AND IT STOPS ABOVE THE CONSENT STRIP, WHICH IS THE FLOOR OF THE PAGE.
	 *
	 * `components.css` reserves the strip's height twice, at the foot of the
	 * document (`body:has(.ts-consent) { padding-block-end }`) and for the root
	 * scroller (`html:has(.ts-consent) { scroll-padding-block-end }`), so that
	 * WCAG 2.2's 2.4.11 Focus Not Obscured « stops being failed by a panel the
	 * visitor never opened ». Both of those are about the DOCUMENT scroller.
	 *
	 * The rule above makes this panel a SECOND scroller, sticky and as tall as
	 * the viewport, and neither reserve reaches it. Measured on the shop at
	 * 1440 x 900 on a first visit: the strip occupies y 675 to 900, the panel
	 * runs from y 89 to 893, so its bottom 218 px are under the strip, and it
	 * has 2 940 px of content in an 802 px port. `npm run verify:a11y` found a
	 * tab stop down there: « 1 arrêt masqué sur 400, recouvert par
	 * section#ts-consent ».
	 *
	 * A `scroll-padding-block-end` here would NOT be enough, and that is worth
	 * writing down because it is the obvious fix. Scroll padding only applies
	 * when a scroll happens; an element already inside the port is left where
	 * it is, and where it is may be under the strip. Bounding the panel is
	 * provable instead of usually right: nothing it contains can be below y 477.
	 *
	 * It uses the SAME `--ts-consent-h` as the other two reserves, which is the
	 * fourth use of that one number and exactly what its comment asks for. It
	 * over-reserves (the strip measures 225 px at this width against a 416 px
	 * ceiling) and that is the safe direction: the token is the maximum the
	 * strip may occupy, so this can never reserve too little at any width.
	 * It applies only while the strip is there, so a returning visitor gets the
	 * full-height panel back.
	 */
	html:has(.ts-consent) .ts-filters {
		max-height: calc(100vh - 6rem - var(--ts-consent-h));
	}

	/*
	 * From here the panel is a column, so the button that opens it has nothing
	 * to open and the panel is open whatever the script did at a smaller width.
	 */
	.has-js .ts-filters__toggle {
		display: none;
	}

	.has-js .ts-filters__body {
		display: block;
	}

	.ts-foot__grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
		gap: var(--ts-space-6);
	}

	/*
	 * CINQ COLONNES POUR CINQ ÉTAPES, et le nombre suit la liste.
	 *
	 * Il y en avait quatre quand la séquence en comptait quatre. Le choix des
	 * quantités par coloris est devenu une étape le 9 septembre 2026, et une
	 * grille de quatre laissait la cinquième seule sur une deuxième rangée : un
	 * déroulé qui se lit « 1 2 3 4 » puis « 5 » plus bas ressemble à une liste
	 * tronquée, ce qui est exactement ce qu'un déroulé ne doit pas faire.
	 */
	.ts-steps {
		grid-template-columns: repeat(5, minmax(0, 1fr));
	}

	.woocommerce div.product {
		grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	}

	.woocommerce div.product > div.images {
		grid-column: 1;
		position: sticky;
		top: 4.5rem;
	}

	.woocommerce div.product > div.summary {
		grid-column: 2;
	}
}

/* ───────────────────────────────────────────────────────────── 1200 px ── */

@media (min-width: 1200px) {
	.ts-grid,
	.woocommerce ul.products,
	ul.products {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}

	.ts-shop__results ul.products {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* ────────────────────────────────────────────────────────── less motion ── */

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

/* ----------------------------------------------------------- editorial */

/*
 * The category and landing-page copy, which sits UNDER the products.
 *
 * Measured across five French competitors on 26 August 2026: none puts more
 * than 145 words above the grid and all put between 809 and 3 239 below it.
 * The width is the reading measure and not the page width, because a thousand
 * words set across 1 440 px is a thousand words nobody reads.
 */
.ts-edito {
	padding-block: var(--ts-space-7);
	display: grid;
	gap: var(--ts-space-6);
	border-top: 1px solid var(--ts-line);
}

.ts-edito__section {
	max-width: var(--ts-measure);
}

.ts-edito__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-display-xs);
	line-height: var(--ts-leading-tight);
}

.ts-edito__section p {
	margin: 0 0 var(--ts-space-2);
	line-height: var(--ts-leading-loose);
}

.ts-edito__list {
	margin: 0 0 var(--ts-space-2);
	padding-left: 1.1rem;
	line-height: var(--ts-leading-loose);
}

.ts-edito__list li {
	margin-bottom: 0.35rem;
}

.ts-edito__links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-1) var(--ts-space-3);
}

.ts-faq__list {
	margin: 0;
}

.ts-faq__q {
	margin-top: var(--ts-space-3);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-faq__q:first-child {
	margin-top: 0;
}

.ts-faq__a {
	margin: 0.25rem 0 0;
	line-height: var(--ts-leading-loose);
	color: var(--ts-text);
}

@media (min-width: 60rem) {
	.ts-edito {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--ts-space-7);
	}
}

/* ------------------------------------------------- le chemin d'achat */

/*
 * PANIER, CAISSE, COMMANDE REÇUE, MON COMPTE.
 *
 * ── POURQUOI CE BLOC N'EXISTAIT PAS, ET POURQUOI C'EST GRAVE ─────────────────
 *
 * Mesuré le 4 septembre 2026 : `grep` sur nos deux feuilles de style rendait
 * ZÉRO sélecteur pour le panier, la caisse, le compte client et la page de
 * commande reçue. Le thème habillait l'accueil, l'archive boutique, la fiche
 * produit et les boutons, et s'arrêtait exactement là où le client sort sa
 * carte. Les quatre écrans où l'argent bouge étaient ceux qui ressemblaient le
 * moins à une boutique : les gabarits par défaut de WooCommerce, dans la police
 * du thème et rien d'autre.
 *
 * ── LE VOCABULAIRE EST CELUI QUI EXISTE ──────────────────────────────────────
 *
 * Rien d'inventé ici. `.ts-cta` (le bouton de la fiche produit), `.ts-table`
 * (la grille de prix) et `.ts-msg` (les messages) vivent dans
 * `wp-plugins/teeshoop-core/assets/components.css`, et ce bloc AMÈNE les
 * éléments de WooCommerce dedans plutôt que d'ouvrir un second jeu de boutons.
 * Un « Commander » qui ne ressemble pas à « Ajouter au panier » est deux
 * marques sur un même parcours.
 *
 * ── LE TÉLÉPHONE D'ABORD, ET LE TABLEAU DE PANIER EN EST LA RAISON ───────────
 *
 * Un tableau de panier à cinq colonnes ne tient pas dans 375 px : WooCommerce le
 * laisse déborder et le client fait défiler latéralement une page où il doit
 * lire un prix. Sous 45 rem chaque ligne devient une carte, et les intitulés de
 * colonne reviennent depuis l'attribut `data-title` que WooCommerce écrit déjà
 * sur chaque cellule. Aucun gabarit n'est copié.
 *
 * ── LES CHIFFRES SONT TABULAIRES PARTOUT OÙ ILS S'ALIGNENT ───────────────────
 *
 * Prix, quantités, totaux. `font-variant-numeric: tabular-nums` est déjà la
 * règle de `.ts-num` dans le plugin ; ici elle est portée aux éléments de
 * WooCommerce, qui n'ont pas nos classes.
 */

.woocommerce-cart-form,
.woocommerce-checkout,
.woocommerce-order,
.woocommerce-MyAccount-content,
.woocommerce-MyAccount-navigation {
	font-size: var(--ts-text);
}

/* -- le tableau du panier et celui de la caisse ------------------------- */

.woocommerce table.shop_table {
	width: 100%;
	margin: 0 0 var(--ts-space-5);
	border: 0;
	border-collapse: collapse;
	border-radius: 0;
	font-size: var(--ts-text-sm);
}

.woocommerce table.shop_table th,
.woocommerce table.shop_table td {
	padding: var(--ts-space-3) var(--ts-space-3);
	border-bottom: 1px solid var(--ts-line);
	text-align: left;
	vertical-align: top;
}

.woocommerce table.shop_table thead th {
	border-bottom: 2px solid var(--ts-ink);
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	white-space: nowrap;
}

/*
 * Les colonnes d'argent et de quantité, à droite et en chiffres tabulaires.
 * WooCommerce ne pose aucune classe dessus ; ce sont ses noms de colonne.
 */
.woocommerce table.shop_table td.product-subtotal,
.woocommerce table.shop_table td.product-price,
.woocommerce table.shop_table td.product-total,
.woocommerce table.shop_table th.product-subtotal,
.woocommerce table.shop_table th.product-price,
.woocommerce table.shop_table th.product-total,
.woocommerce table.shop_table tfoot td,
.woocommerce .cart_totals td,
.woocommerce .woocommerce-Price-amount,
.woocommerce ul.order_details li strong {
	font-variant-numeric: tabular-nums;
}

.woocommerce table.shop_table td.product-subtotal,
.woocommerce table.shop_table td.product-price,
.woocommerce table.shop_table td.product-total,
.woocommerce table.shop_table th.product-subtotal,
.woocommerce table.shop_table th.product-price,
.woocommerce table.shop_table th.product-total {
	text-align: right;
	white-space: nowrap;
}

.woocommerce table.shop_table td.product-name a {
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
	text-decoration: none;
}

.woocommerce table.shop_table td.product-name a:hover,
.woocommerce table.shop_table td.product-name a:focus-visible {
	text-decoration: underline;
}

/*
 * La personnalisation, sous le nom de l'article.
 *
 * `Cart::item_data` y écrit la taille, les faces imprimées et la couleur. C'est
 * la seule trace que le client a de ce qu'il a dessiné, et par défaut elle sort
 * en gras collé au titre.
 */
.woocommerce table.shop_table td.product-name dl.variation {
	margin: var(--ts-space-2) 0 0;
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading-loose);
}

.woocommerce table.shop_table td.product-name dl.variation dt,
.woocommerce table.shop_table td.product-name dl.variation dd {
	display: inline;
	margin: 0;
	padding: 0;
	float: none;
}

.woocommerce table.shop_table td.product-name dl.variation dt {
	font-weight: var(--ts-weight-strong);
}

.woocommerce table.shop_table td.product-name dl.variation dd::after {
	content: '\A';
	white-space: pre;
}

.woocommerce table.shop_table td.product-name dl.variation p {
	display: inline;
	margin: 0;
}

.woocommerce table.shop_table td.product-thumbnail img {
	width: 4rem;
	height: auto;
	border: 1px solid var(--ts-line);
}

/*
 * Le retrait d'une ligne : une croix, pas un bouton rouge rond.
 *
 * Elle garde une cible de 44 px et un anneau de focus, parce qu'elle est la
 * seule action destructrice de la page et qu'un client au clavier doit voir
 * où il est avant d'appuyer.
 */
.woocommerce a.remove {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 2.75rem;
	height: 2.75rem;
	margin: -0.5rem 0 0 -0.5rem;
	border-radius: 0;
	background: none;
	color: var(--ts-muted-strong);
	font-size: 1.25rem;
	line-height: 1;
	text-decoration: none;
}

.woocommerce a.remove:hover,
.woocommerce a.remove:focus {
	background: var(--ts-bad-wash);
	color: var(--ts-bad-ink);
}

.woocommerce a.remove:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: calc(var(--ts-focus-offset) * -1);
}

/* -- la quantité, le coupon, les actions -------------------------------- */

.woocommerce .quantity input.qty {
	width: 5rem;
	min-height: 2.75rem;
	padding: 0.375rem 0.5rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 3px;
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-family: var(--ts-font);
	font-size: var(--ts-text);
	font-variant-numeric: tabular-nums;
	text-align: right;
}

.woocommerce .quantity input.qty:focus-visible,
.woocommerce form .form-row input:focus-visible,
.woocommerce form .form-row select:focus-visible,
.woocommerce form .form-row textarea:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
}

.woocommerce table.shop_table td.actions {
	padding-top: var(--ts-space-4);
	border-bottom: 0;
}

.woocommerce table.shop_table td.actions .coupon {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	align-items: center;
	margin: 0 0 var(--ts-space-4);
	float: none;
}

.woocommerce table.shop_table td.actions .coupon label {
	width: 100%;
	color: var(--ts-muted);
	font-size: var(--ts-text-xs);
}

.woocommerce table.shop_table td.actions .coupon .input-text {
	flex: 1 1 12rem;
	min-height: 2.75rem;
	padding: 0.5rem 0.625rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 3px;
	font-size: var(--ts-text);
}

/* -- les totaux ---------------------------------------------------------- */

.woocommerce .cart-collaterals,
.woocommerce-page .cart-collaterals {
	width: auto;
	float: none;
}

.woocommerce .cart_totals {
	width: auto;
	float: none;
	padding: var(--ts-space-4);
	border: 1px solid var(--ts-line);
	background: var(--ts-surface);
}

.woocommerce .cart_totals > h2,
.woocommerce #order_review_heading,
.woocommerce-order h2,
.woocommerce-MyAccount-content h2,
.woocommerce-MyAccount-content h3 {
	margin: 0 0 var(--ts-space-3);
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-lg);
	font-weight: var(--ts-weight-display);
	letter-spacing: -0.01em;
}

.woocommerce .cart_totals table.shop_table {
	margin: 0;
	font-size: var(--ts-text);
}

.woocommerce .cart_totals table.shop_table th {
	width: 45%;
	color: var(--ts-muted-strong);
	font-family: var(--ts-font);
	font-weight: var(--ts-weight-body);
	text-transform: none;
	letter-spacing: normal;
	font-size: var(--ts-text);
}

.woocommerce .cart_totals table.shop_table td {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.woocommerce .cart_totals .order-total th,
.woocommerce .cart_totals .order-total td {
	border-bottom: 0;
	border-top: 2px solid var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-display);
	font-size: var(--ts-text-lg);
}

/*
 * La mention de base d'imposition, sous le total.
 *
 * `Settings::price_pair()` décide ce qu'elle dit ; ce bloc décide seulement
 * qu'elle est lisible et qu'elle ne passe pas pour une ligne de prix.
 */
.woocommerce .cart_totals .includes_tax,
.woocommerce .cart_totals small,
.woocommerce-checkout-review-order-table .includes_tax {
	display: block;
	color: var(--ts-muted-strong);
	font-family: var(--ts-font);
	font-size: var(--ts-text-xs);
	font-weight: var(--ts-weight-body);
}

.woocommerce .wc-proceed-to-checkout {
	padding: var(--ts-space-4) 0 0;
}

/* -- le panier vide ------------------------------------------------------ */

.woocommerce .cart-empty {
	margin: 0 0 var(--ts-space-3);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-lg);
	font-weight: var(--ts-weight-display);
}

.woocommerce .return-to-shop {
	margin: 0 0 var(--ts-space-6);
}

/* -- la caisse ----------------------------------------------------------- */

.woocommerce-checkout #customer_details {
	display: grid;
	gap: var(--ts-space-5);
	margin: 0 0 var(--ts-space-6);
}

.woocommerce-checkout #customer_details .col-1,
.woocommerce-checkout #customer_details .col-2 {
	width: auto;
	float: none;
}

.woocommerce-checkout h3 {
	margin: 0 0 var(--ts-space-3);
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-lg);
	font-weight: var(--ts-weight-display);
}

.woocommerce form .form-row {
	display: grid;
	gap: 0.25rem;
	margin: 0 0 var(--ts-space-3);
	padding: 0;
}

.woocommerce form .form-row label {
	color: var(--ts-muted-strong);
	font-size: var(--ts-text-sm);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
}

.woocommerce form .form-row .required {
	color: var(--ts-bad);
	text-decoration: none;
}

.woocommerce form .form-row input.input-text,
.woocommerce form .form-row textarea,
.woocommerce form .form-row select,
.woocommerce form .form-row .select2-container .select2-selection--single {
	width: 100%;
	min-height: 2.75rem;
	padding: 0.5rem 0.625rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 3px;
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-family: var(--ts-font);
	font-size: var(--ts-text);
	line-height: 1.4;
}

/*
 * L'ERREUR DIT CE QUI NE VA PAS, ET LA BORDURE NE LE DIT PAS TOUTE SEULE.
 *
 * WooCommerce marque un champ invalide avec `woocommerce-invalid`. Une bordure
 * rouge est une information portée par la seule couleur, ce que WCAG 1.4.1
 * refuse : le champ prend aussi un trait plus épais, et le message de
 * WooCommerce reste au-dessus du formulaire, où un lecteur d'écran l'annonce.
 */
.woocommerce form .form-row.woocommerce-invalid input.input-text,
.woocommerce form .form-row.woocommerce-invalid select {
	border-color: var(--ts-bad);
	border-width: 2px;
}

.woocommerce form .form-row.woocommerce-validated input.input-text {
	border-color: var(--ts-good);
}

.woocommerce-checkout #order_review {
	width: auto;
	float: none;
	padding: var(--ts-space-4);
	border: 1px solid var(--ts-line);
	background: var(--ts-surface);
}

.woocommerce-checkout-review-order-table tfoot th {
	color: var(--ts-muted-strong);
	font-family: var(--ts-font);
	font-weight: var(--ts-weight-body);
	text-transform: none;
	letter-spacing: normal;
}

.woocommerce-checkout-review-order-table tfoot td {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.woocommerce-checkout-review-order-table tfoot .order-total th,
.woocommerce-checkout-review-order-table tfoot .order-total td {
	border-top: 2px solid var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-display);
	font-size: var(--ts-text-lg);
}

.woocommerce-checkout #payment {
	border-radius: 0;
	background: transparent;
}

.woocommerce-checkout #payment ul.payment_methods {
	margin: 0;
	padding: var(--ts-space-4) 0 0;
	border-bottom: 0;
	list-style: none;
}

.woocommerce-checkout #payment ul.payment_methods li {
	margin: 0 0 var(--ts-space-2);
	padding: var(--ts-space-3);
	border: 1px solid var(--ts-line);
	background: var(--ts-paper);
}

.woocommerce-checkout #payment ul.payment_methods li label {
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
}

.woocommerce-checkout #payment div.payment_box {
	margin: var(--ts-space-2) 0 0;
	padding: var(--ts-space-3);
	border-radius: 0;
	background: var(--ts-surface);
	color: var(--ts-muted-strong);
	font-size: var(--ts-text-sm);
}

.woocommerce-checkout #payment div.payment_box::before {
	display: none;
}

.woocommerce-checkout #payment .place-order {
	padding: var(--ts-space-4) 0 0;
}

.woocommerce-checkout .woocommerce-terms-and-conditions-wrapper {
	margin: 0 0 var(--ts-space-3);
	font-size: var(--ts-text-sm);
	line-height: var(--ts-leading-loose);
}

/* -- la commande reçue --------------------------------------------------- */

.woocommerce .woocommerce-thankyou-order-received {
	margin: 0 0 var(--ts-space-4);
	padding: var(--ts-space-4);
	border-left: 3px solid var(--ts-good);
	background: var(--ts-good-wash);
	color: var(--ts-good-ink);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-lg);
	font-weight: var(--ts-weight-display);
}

.woocommerce ul.order_details {
	display: grid;
	gap: var(--ts-space-3);
	margin: 0 0 var(--ts-space-6);
	padding: 0;
	list-style: none;
}

.woocommerce ul.order_details li {
	margin: 0;
	padding: 0 0 var(--ts-space-3);
	border-bottom: 1px solid var(--ts-line);
	border-right: 0;
	color: var(--ts-muted-strong);
	font-size: var(--ts-text-xs);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	float: none;
}

.woocommerce ul.order_details li strong {
	display: block;
	margin-top: 0.25rem;
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-lg);
	font-weight: var(--ts-weight-display);
	text-transform: none;
	letter-spacing: normal;
}

.woocommerce .woocommerce-customer-details address {
	padding: var(--ts-space-3);
	border: 1px solid var(--ts-line);
	border-radius: 0;
	font-style: normal;
	line-height: var(--ts-leading-loose);
}

/* -- mon compte ---------------------------------------------------------- */

.woocommerce-account .woocommerce-MyAccount-navigation ul {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-1);
	margin: 0 0 var(--ts-space-5);
	padding: 0 0 var(--ts-space-2);
	border-bottom: 1px solid var(--ts-line);
	list-style: none;
}

.woocommerce-account .woocommerce-MyAccount-navigation li a {
	display: block;
	padding: 0.625rem var(--ts-space-3);
	color: var(--ts-muted-strong);
	font-family: var(--ts-font-display);
	font-size: var(--ts-text-sm);
	font-weight: var(--ts-weight-nav);
	text-decoration: none;
}

.woocommerce-account .woocommerce-MyAccount-navigation li a:hover,
.woocommerce-account .woocommerce-MyAccount-navigation li a:focus {
	background: var(--ts-surface);
	color: var(--ts-ink);
}

.woocommerce-account .woocommerce-MyAccount-navigation li a:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
}

/*
 * L'onglet courant porte un trait, pas seulement une couleur : le repère ne
 * peut pas dépendre de la seule teinte (WCAG 1.4.1).
 */
.woocommerce-account .woocommerce-MyAccount-navigation li.is-active a {
	border-bottom: 2px solid var(--ts-accent);
	color: var(--ts-accent);
}

.woocommerce-account .woocommerce-MyAccount-content > p:first-child {
	margin-top: 0;
}

.woocommerce-account .woocommerce-Address {
	margin: 0 0 var(--ts-space-5);
}

.woocommerce-account .woocommerce-Address-title {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
	align-items: baseline;
	justify-content: space-between;
}

.woocommerce-account table.woocommerce-orders-table td.woocommerce-orders-table__cell-order-number a {
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
	font-variant-numeric: tabular-nums;
}

/* -- 375 px d'abord : le tableau devient une pile de cartes --------------- */

@media (max-width: 44.999rem) {
	/*
	 * `data-title` est écrit par WooCommerce sur chaque cellule de panier et de
	 * commande. Le rendre visible ici est ce qui remplace l'en-tête de colonne,
	 * et c'est pour ça qu'aucun gabarit n'a besoin d'être copié.
	 */
	.woocommerce table.shop_table.cart thead,
	.woocommerce table.woocommerce-orders-table thead,
	.woocommerce table.woocommerce-table--order-details thead {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.woocommerce table.shop_table.cart tbody tr,
	.woocommerce table.woocommerce-orders-table tbody tr,
	.woocommerce table.woocommerce-table--order-details tbody tr {
		display: grid;
		grid-template-columns: 4.5rem minmax(0, 1fr);
		gap: 0 var(--ts-space-3);
		align-items: start;
		margin: 0 0 var(--ts-space-4);
		padding: 0 0 var(--ts-space-3);
		border-bottom: 1px solid var(--ts-line);
	}

	.woocommerce table.shop_table.cart tbody td,
	.woocommerce table.woocommerce-orders-table tbody td,
	.woocommerce table.woocommerce-table--order-details tbody td {
		grid-column: 2;
		padding: 0.125rem 0;
		border-bottom: 0;
		text-align: left;
	}

	.woocommerce table.shop_table.cart tbody td.product-thumbnail {
		grid-column: 1;
		grid-row: 1 / span 4;
	}

	.woocommerce table.shop_table.cart tbody td.product-remove {
		grid-column: 1;
		padding: 0;
	}

	.woocommerce table.shop_table.cart tbody td[data-title]::before,
	.woocommerce table.woocommerce-orders-table tbody td[data-title]::before,
	.woocommerce table.woocommerce-table--order-details tbody td[data-title]::before {
		content: attr(data-title) ' ';
		color: var(--ts-muted-strong);
		font-size: var(--ts-text-xs);
		font-family: var(--ts-font-display);
		font-weight: var(--ts-weight-nav);
	}

	.woocommerce table.shop_table.cart tbody td.product-thumbnail::before,
	.woocommerce table.shop_table.cart tbody td.product-name::before,
	.woocommerce table.shop_table.cart tbody td.product-remove::before {
		content: none;
	}

	.woocommerce table.shop_table.cart tbody td.product-subtotal,
	.woocommerce table.shop_table.cart tbody td.product-price {
		text-align: left;
	}

	.woocommerce table.shop_table td.actions .coupon .button,
	.woocommerce table.shop_table td.actions > .button {
		width: 100%;
	}
}

@media (min-width: 45rem) {
	.woocommerce-checkout #customer_details {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--ts-space-6);
	}

	.woocommerce ul.order_details {
		grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
	}
}

@media (min-width: 60rem) {
	/*
	 * Les totaux à côté du tableau, pas dessous : sur un écran large, un client
	 * qui change une quantité doit voir le total bouger sans faire défiler.
	 */
	.woocommerce-cart .woocommerce {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 22rem;
		gap: 0 var(--ts-space-6);
		align-items: start;
	}

	/*
	 * Les avis de WooCommerce vivent DANS le même conteneur que le formulaire
	 * et les totaux (« Le coupon a bien été appliqué », « Article supprimé »).
	 * Sans cette ligne ils prendraient une colonne et pousseraient les totaux
	 * hors de l'écran le jour où le client applique un code.
	 */
	.woocommerce-cart .woocommerce > .woocommerce-notices-wrapper,
	.woocommerce-cart .woocommerce > .cart-empty,
	.woocommerce-cart .woocommerce > .return-to-shop {
		grid-column: 1 / -1;
	}

	.woocommerce-cart .woocommerce > .woocommerce-cart-form {
		grid-column: 1;
	}

	.woocommerce-cart .woocommerce > .cart-collaterals {
		grid-column: 2;
	}

	.woocommerce-checkout .woocommerce > form.checkout {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 24rem;
		gap: var(--ts-space-6);
		align-items: start;
	}

	.woocommerce-checkout .woocommerce > form.checkout > #customer_details {
		grid-column: 1;
		margin-bottom: 0;
	}

	.woocommerce-checkout .woocommerce > form.checkout > #order_review_heading,
	.woocommerce-checkout .woocommerce > form.checkout > #order_review {
		grid-column: 2;
	}

	.woocommerce-checkout .woocommerce > form.checkout > #order_review_heading {
		align-self: end;
	}

	.woocommerce-account .woocommerce {
		display: grid;
		grid-template-columns: 14rem minmax(0, 1fr);
		gap: var(--ts-space-6);
		align-items: start;
	}

	.woocommerce-account .woocommerce-MyAccount-navigation ul {
		display: block;
		border-bottom: 0;
		border-right: 1px solid var(--ts-line);
		padding-bottom: 0;
		padding-right: var(--ts-space-2);
	}

	.woocommerce-account .woocommerce-MyAccount-navigation li.is-active a {
		border-bottom: 0;
		border-left: 2px solid var(--ts-accent);
	}
}

/* ------------------------------- le panier et la caisse en blocs */

/*
 * WOOCOMMERCE A DEUX CAISSES, ET C'EST LA SECONDE QUI EST INSTALLÉE.
 *
 * Mesuré le 4 septembre 2026 : les pages `cart` et `checkout` du miroir
 * contiennent `<!-- wp:woocommerce/cart -->` et `<!-- wp:woocommerce/checkout -->`,
 * donc les blocs React, pas les raccourcis classiques. « Mon compte » est resté
 * le raccourci `[woocommerce_my_account]`. Le bloc précédent de cette feuille
 * habille le chemin classique, que l'extension sert aussi (`Checkout.php` :
 * « REGISTERED TWICE, because WooCommerce has two checkouts ») et que « Mon
 * compte » emprunte ; celui-ci habille les blocs.
 *
 * ── CE QUE CE BLOC FAIT, ET CE QU'IL SE REFUSE À FAIRE ──────────────────────
 *
 * Il ne re-peint pas une bibliothèque de composants React. Les blocs de
 * WooCommerce arrivent avec leur propre feuille de style, une spécificité
 * élevée et un balisage qui change d'une version à l'autre : les combattre
 * classe par classe donnerait une feuille qui casse au prochain
 * `wp plugin update woocommerce`, en silence, sur les écrans où l'argent bouge.
 *
 * Il fait les quatre choses qui manquaient vraiment, et qui sont les quatre que
 * `CLAUDE.md` section 7 exige nommément :
 *
 *   1. les boutons parlent la même langue que `.ts-cta`, pour qu'un même
 *      parcours n'ait pas deux marques ;
 *   2. les chiffres qui s'alignent en colonne sont tabulaires ;
 *   3. le total à payer est dans la police d'affichage, comme sur la fiche ;
 *   4. l'anneau de focus est celui du reste du site, visible au clavier.
 *
 * Tout le reste est laissé à WooCommerce, délibérément.
 */

.wc-block-components-button:not(.is-link) {
	border-radius: 3px;
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
}

.wc-block-components-button:not(.is-link):hover,
.wc-block-components-button:not(.is-link):focus {
	background: var(--ts-accent-dark);
	color: var(--ts-accent-ink);
}

.wc-block-components-button:not(.is-link):focus-visible,
.wc-block-components-text-input input:focus-visible,
.wc-block-components-quantity-selector input:focus-visible,
.wc-block-components-checkout-step input:focus-visible,
.wc-block-components-checkout-step select:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
}

.wc-block-components-product-price,
.wc-block-components-totals-item__value,
.wc-block-components-order-summary-item__total-price,
.wc-block-formatted-money-amount,
.wc-block-components-quantity-selector__input {
	font-variant-numeric: tabular-nums;
}

.wc-block-components-totals-footer-item .wc-block-components-totals-item__label,
.wc-block-components-totals-footer-item .wc-block-components-totals-item__value {
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-display);
	color: var(--ts-ink);
}

.wc-block-components-checkout-step__title,
.wc-block-cart__totals-title,
.wc-block-cart__empty-cart__title {
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-display);
	color: var(--ts-ink);
	letter-spacing: -0.01em;
}

.wc-block-components-product-name {
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
}

/*
 * La personnalisation sous le nom de l'article : taille, faces, couleur, ce que
 * `Cart::item_data` écrit. C'est la seule trace que le client a de ce qu'il a
 * dessiné, et par défaut elle sort dans la même graisse que le titre.
 */
.wc-block-components-product-details {
	color: var(--ts-muted-strong);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading-loose);
}

.wc-block-components-product-details__name {
	font-weight: var(--ts-weight-strong);
}

/* ══════════════════════════════════════════════════ la démonstration ══ */

/*
 * LE VÊTEMENT DE LA PAGE D'ACCUEIL, ET LE SEUL MOUVEMENT DU SITE.
 *
 * ── CE QUI BOUGE, ET POURQUOI CE N'EST PAS DE LA DÉCORATION ──────────────────
 *
 * La règle §7 interdit « l'animation qui ne sert à rien », ce qui exclut le
 * fondu au défilement sur chaque bloc qu'une page comme celle-ci reçoit
 * d'habitude. Il reste UNE transition : la couleur du vêtement. Elle est
 * l'information elle-même, puisque la question à laquelle ce bloc répond est
 * « à quoi ça ressemble sur un vêtement foncé ». Rien d'autre ne bouge sur
 * cette page.
 *
 * ── POURQUOI @property ET PAS UNE TRANSITION SUR `fill` ──────────────────────
 *
 * La teinte est portée par une propriété personnalisée, parce que le SVG vient
 * du studio avec un jeton `__COLOR__` et que la boutique n'a pas le droit de le
 * redécouper pour y accrocher une classe. Une propriété personnalisée n'est
 * interpolable que si elle est DÉCLARÉE : sans `@property`, le vêtement change
 * de couleur d'un seul coup. Un navigateur qui ne connaît pas `@property`
 * l'ignore et obtient exactement ce saut, ce qui est une dégradation et pas une
 * panne.
 *
 * Le bloc `prefers-reduced-motion` plus haut porte `!important` et couvre donc
 * cette transition comme les autres : un visiteur qui a demandé moins
 * d'animation voit le vêtement changer instantanément, et le nuancier marche
 * toujours.
 */
/*
 * `initial-value` EST OBLIGATOIRE ET N'EST JAMAIS PEINT.
 *
 * Une propriété enregistrée en `<color>` doit déclarer une valeur initiale, et
 * `initial-value` n'accepte pas `var()` : c'est le seul littéral de couleur de
 * cette feuille, et il ne peut pas venir de `tokens.css`. Il n'atteint jamais un
 * écran : quand la démonstration existe, `demo_css()` écrit un `:root` avec la
 * teinte d'ouverture juste après cette feuille ; quand elle n'existe pas, il n'y
 * a pas de vêtement à peindre.
 */
@property --ts-demo-tint {
	syntax: '<color>';
	inherits: true;
	initial-value: #f6f7f9;
}

/*
 * LARGEUR PLAFONNÉE, PARCE QUE LE DESSIN EST CARRÉ.
 *
 * Le gabarit est un carré (repère 800 x 800), donc sa hauteur suit sa largeur :
 * mesuré sans plafond, la colonne média faisait 676 px de côté à 1 440 px et
 * 768 à 768 px, ce qui poussait le bandeau à 965 px puis à 1 418 px de haut et
 * reléguait le titre et les deux boutons sous la ligne de flottaison. Un
 * vêtement plus grand ne dit rien de plus qu'un vêtement lisible.
 *
 * 30 rem, et pas un pourcentage : au téléphone la valeur est plus large que
 * l'écran et ne s'applique donc pas, ce qui laisse le dessin pleine largeur là
 * où c'est le seul endroit disponible.
 */
.ts-demo {
	margin: 0;
	max-width: 30rem;
	margin-inline: auto;
}

/*
 * LE VÊTEMENT ET SA ZONE, DANS LE MÊME REPÈRE.
 *
 * Le rectangle est positionné en pourcentages du dessin (`--z-x` … `--z-h`,
 * calculés en PHP depuis `printAreaPx`), donc il reste sur la même maille de
 * tissu à toutes les largeurs, sans que le CSS ait à connaître un seul
 * centimètre.
 */
.ts-demo__stage {
	position: relative;
	overflow: hidden;
	border-radius: var(--ts-radius);
	background: var(--ts-surface);
	transition: --ts-demo-tint 380ms ease;
}

.ts-demo__svg {
	display: block;
	width: 100%;
	height: auto;
}

/*
 * LE TRACÉ DE LA ZONE : UNE ENCRE ET UN HALO, comme l'anneau de focus.
 *
 * Un pointillé d'une seule couleur ne peut pas se voir sur dix-sept coloris :
 * le blanc disparaît sur le blanc et le noir sur le noir, et il n'existe pas de
 * gris qui tienne les deux. C'est le problème que `:focus-visible` a déjà résolu
 * plus haut dans cette feuille, avec la technique que le document
 * « Understanding » de WCAG décrit : un trait sombre doublé d'un halo clair. Sur
 * un vêtement clair c'est le trait qui se lit, sur un foncé c'est le halo, et
 * l'un des deux est toujours lisible.
 */
.ts-demo__zone {
	position: absolute;
	left: var(--z-x);
	top: var(--z-y);
	width: var(--z-w);
	height: var(--z-h);
	display: flex;
	align-items: flex-end;
	justify-content: center;
	padding-bottom: var(--ts-space-2);
	outline: 2px dashed rgba(255, 255, 255, 0.9);
	outline-offset: 0;
	box-shadow:
		inset 0 0 0 2px rgba(0, 0, 0, 0.5),
		0 0 0 2px rgba(0, 0, 0, 0.3);
}

/*
 * La cote est du texte HTML et pas du texte SVG, pour la raison écrite dans
 * `print_zone_figure()` : sur un repère de 800 unités affiché à 320 px, une
 * étiquette dessinée dans le SVG sortait à 6,9 px.
 */
.ts-demo__dim {
	padding: 2px var(--ts-space-2);
	border: 1px solid var(--ts-line-strong);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-size: var(--ts-text-xs);
	line-height: 1.5;
	white-space: nowrap;
}

.ts-demo__colours {
	margin: var(--ts-space-4) 0 0;
	padding: 0;
	border: 0;
}

/* La même étiquette majuscule que `.ts-eyebrow`, aux mêmes jetons. */
.ts-demo__legend {
	margin: 0 0 var(--ts-space-2);
	padding: 0;
	color: var(--ts-muted-strong);
	font-family: var(--ts-font-display);
	font-size: var(--ts-eyebrow-size);
	font-weight: var(--ts-weight-nav);
	letter-spacing: var(--ts-eyebrow-tracking);
	text-transform: uppercase;
}

.ts-demo__swatches {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ts-space-2);
}

.ts-demo__swatch {
	display: contents;
}

/*
 * Le bouton radio est masqué À L'ŒIL et jamais au clavier ni au lecteur
 * d'écran : `display:none` le sortirait de l'ordre de tabulation et de l'arbre
 * d'accessibilité, et le nuancier deviendrait un ensemble d'étiquettes que
 * personne ne peut atteindre sans souris.
 */
.ts-demo__radio {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	white-space: nowrap;
	clip-path: inset(50%);
}

.ts-demo__chip {
	display: inline-flex;
	align-items: center;
	gap: var(--ts-space-2);
	padding: 3px var(--ts-space-2) 3px 3px;
	border: 1px solid var(--ts-line);
	background: var(--ts-paper);
	color: var(--ts-ink);
	font-size: var(--ts-text-xs);
	line-height: 1.6;
	cursor: pointer;
}

/*
 * LA PASTILLE PORTE UN LISERÉ, et c'est une exigence et pas un ornement. Le
 * blanc du nuancier est #f6f6f6 : sans bord il n'aurait aucune frontière visible
 * sur du papier, donc « Blanc » serait la seule couleur du nuancier qu'on ne
 * peut pas voir. WCAG 1.4.11 demande 3:1 à ce qui délimite un contrôle, ce que
 * `--ts-line-strong` a été dérivé pour tenir.
 */
.ts-demo__chip::before {
	content: '';
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
	border: 1px solid var(--ts-line-strong);
	background: var(--chip);
}

.ts-demo__chip:hover {
	border-color: var(--ts-line-strong);
}

.ts-demo__radio:checked + .ts-demo__chip {
	border-color: var(--ts-accent);
	box-shadow: inset 0 0 0 1px var(--ts-accent);
}

/* L'anneau du site, reporté sur l'étiquette, puisque la case est masquée. */
.ts-demo__radio:focus-visible + .ts-demo__chip {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
	box-shadow: 0 0 0 calc(var(--ts-focus-width) + var(--ts-focus-offset) + 2px) var(--ts-paper);
}

.ts-demo__note {
	margin: var(--ts-space-3) 0 0;
	max-width: none;
	color: var(--ts-muted-strong);
	font-size: var(--ts-text-xs);
	line-height: var(--ts-leading);
}

/*
 * Le nom du produit porte la légende, donc il se lit d'abord.
 *
 * EN URBANIST 600 ET PAS EN LATO GRAS, ET C'EST UNE MESURE. La première version
 * disait `--ts-weight-strong`, c'est-à-dire Lato 700, qui n'était demandé nulle
 * part ailleurs sur cette page : le banc a vu les polices passer de 47 à 70 ko,
 * soit les 23 040 octets de `lato-latin-700.woff2`, pour mettre en gras cinq
 * mots. Urbanist 600 est déjà chargé par `.ts-eyebrow`, porte la même
 * distinction, et ne coûte pas une requête.
 */
.ts-demo__who {
	display: block;
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: var(--ts-weight-nav);
}

/*
 * Le lien prend sa propre ligne. Laissé dans le flux du paragraphe, il se
 * lisait comme la fin de la phrase sur la mesure de la couleur.
 */
.ts-demo__link {
	display: block;
	margin-top: var(--ts-space-2);
}

/*
 * LE CAS OÙ LE NUANCIER NE PEUT RIEN PEINDRE.
 *
 * La teinte suit le bouton coché grâce à `:has()`, parce qu'il faut lire un état
 * qui est plus bas dans l'arbre que l'élément à repeindre. Un navigateur qui ne
 * connaît ni `:has()` NI le script se retrouverait avec dix-sept boutons qui ne
 * changent rien, c'est-à-dire un contrôle cassé, ce qui est pire qu'un contrôle
 * absent. Dans ce cas précis le nuancier est retiré et le vêtement reste dans sa
 * teinte d'ouverture, qui est une couleur réelle du catalogue. La légende, elle,
 * reste : « dix-sept coloris » est vrai avec ou sans sélecteur.
 *
 * `html.has-js` est posé dans l'en-tête du document avant toute peinture, donc
 * ce retrait n'a jamais lieu sur une page dont le script tournera.
 */
@supports not (selector(:has(*))) {
	html:not(.has-js) .ts-demo__swatches {
		display: none;
	}
}

/*
 * LE BLOC MÉDIA DU BANDEAU EST UN CADRE PHOTO, et la démonstration n'en est pas
 * une : elle porte son propre fond sur la scène, plus un nuancier et une légende
 * qui doivent rester sur le papier. On neutralise donc le cadre quand c'est la
 * démonstration qui est dedans, sans toucher au cas de la photographie.
 */
.ts-hero:not(.ts-hero--photo) .ts-hero__media {
	overflow: visible;
	border-radius: 0;
	background: none;
}

/* ══════════════════════════════════════════ ce que nous prenons en charge ══ */

/*
 * SIX PRISES EN CHARGE, EN LIGNES RÉGLÉES ET PAS EN CARTES.
 *
 * Le reste du site est une fiche technique : filets fins, pas d'ombre portée,
 * pas de fond de carte. Six tuiles ombrées ici seraient le seul endroit du site
 * qui ressemble à une page de modèle. Le titre porte le poids, le filet sépare,
 * et c'est tout.
 */
.ts-serv {
	display: grid;
	gap: 0;
	margin: 0;
	padding: 0;
	list-style: none;
	border-top: 1px solid var(--ts-line);
}

.ts-serv__item {
	padding: var(--ts-space-4) 0;
	border-bottom: 1px solid var(--ts-line);
}

.ts-serv__title {
	margin: 0 0 var(--ts-space-2);
	font-size: var(--ts-text-lg);
	letter-spacing: -0.005em;
}

.ts-serv__body {
	margin: 0;
	color: var(--ts-muted);
	max-width: var(--ts-measure);
}

.ts-serv__more {
	margin: var(--ts-space-2) 0 0;
	font-size: var(--ts-text-sm);
}

/*
 * SA PHOTOGRAPHIE, EN BANDEAU SOUS LES SIX LIGNES.
 *
 * Le fichier est un 3:1 très large avec le sujet à environ 68 % de la largeur,
 * donc il est recadré par `object-fit` et non par trois fichiers de plus. Il est
 * en `loading="lazy"` ici : il est sous la ligne de flottaison sur toutes les
 * largeurs mesurées, et le bandeau du haut n'a plus besoin de lui.
 */
.ts-serv__figure {
	margin: var(--ts-space-6) 0 0;
	overflow: hidden;
	border-radius: var(--ts-radius);
	background: var(--ts-surface);
}

.ts-serv__photo {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 5 / 3;
	object-fit: cover;
	object-position: 68% center;
}

@media (min-width: 45rem) {
	.ts-serv {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		column-gap: var(--ts-space-6);
	}

	.ts-serv__photo {
		aspect-ratio: 3 / 1;
	}
}

@media (min-width: 75rem) {
	.ts-serv {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* ══════════════════════════════════════════════ la grille des familles ══ */

/*
 * LA TUILE SANS PHOTOGRAPHIE GARDE LA HAUTEUR DE CELLE QUI EN A UNE.
 *
 * Mesuré sur ce miroir le 9 septembre 2026 : cinq des onze familles n'ont pas
 * encore reçu leur photographie, et la vignette vivait dans le flux normal. Une
 * rangée qui mélangeait les deux avait donc un trou, et sur deux ou trois
 * colonnes la grille entière se décalait : elle ne se lisait pas comme « à
 * moitié remplie », elle se lisait comme cassée.
 *
 * La boîte est maintenant toujours là. Vide, elle est peinte du lavis de la
 * marque, ce qui est un état vide DESSINÉ : un aplat de couleur de marque ne se
 * confond pas avec une image en cours de chargement, ce qu'un carré gris aurait
 * fait. Le liseré à gauche que portait l'ancienne variante devient inutile et
 * s'en va.
 */
.ts-fams__link--typeonly {
	padding-left: 0;
	border-left: 0;
}

.ts-fams__link--typeonly .ts-fams__media {
	background: var(--ts-accent-wash);
}
