/*
 * The shared interface pieces: what a button, a table, a note, a form field
 * and a message look like on this shop.
 *
 * SPLIT OUT OF `product.css`, AND THAT IS THE WHOLE POINT. These rules were
 * written for the product page and are needed on every other page too, so the
 * theme has to be able to load them WITHOUT loading the buy box, the size grid
 * and the estimator with them: `product.css` says of itself that it "has no
 * business restyling anything of theirs", and shipping `.ts-buy input` onto a
 * homepage is exactly that. A second copy in the theme was the alternative, and
 * a second copy of a button is how two buttons stop matching.
 *
 * WHAT BELONGS HERE: a rule that is about a component, not about a page. If it
 * names `.ts-buy`, `.ts-specs`, `.ts-pricing`, `.ts-devis` or `.ts-estimate` it
 * belongs in `product.css`.
 *
 * Loaded after `tokens.css` and before `product.css`, in that order, so the
 * cascade is the same as when all of it was one file.
 *
 * NO HEXADECIMAL LITERAL MAY APPEAR IN THIS FILE. It held nine, and two of them
 * were a second darker blue for a button hover (#1841ba) sitting on the same
 * page as `--ts-accent-dark` (#1b45bd): a filter's submit button and a
 * checkout's Commander button hovered to two different blues, 1,2 points of
 * lightness apart, which reads as a mistake rather than as a choice. The rest
 * were the three message tints. `scripts/palette-guard.mjs` now fails on any
 * hex found here.
 */

/* Digits that line up in a column: prices, quantities, dimensions. */
.ts-num,
.ts-buy input,
.ts-estimate,
.ts-table td,
.ts-table th.ts-num,
.ts-defs dd.ts-num {
	font-variant-numeric: tabular-nums;
	font-feature-settings: 'tnum' 1;
}

.ts-note {
	margin: 0.5rem 0 0;
	color: var(--ts-muted);
	font-size: 0.8125rem;
	line-height: 1.45;
}

.ts-note--strong {
	color: var(--ts-ink);
}

.ts-admin-note {
	margin: 0.75rem 0 0;
	padding: 0.5rem 0.625rem;
	border-left: 3px solid var(--ts-warn);
	background: var(--ts-warn-wash);
	color: var(--ts-warn-ink);
	font-size: 0.8125rem;
	line-height: 1.45;
}

.ts-cta {
	display: block;
	padding: 0.75rem 1rem;
	border: 1px solid var(--ts-accent);
	border-radius: 3px;
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
	font-size: 1rem;
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-align: center;
	text-decoration: none;
	cursor: pointer;
}

.ts-cta:hover,
.ts-cta:focus {
	background: var(--ts-accent-dark);
	border-color: var(--ts-accent-dark);
	color: var(--ts-accent-ink);
}

.ts-cta:focus-visible {
	outline: 2px solid var(--ts-ink);
	outline-offset: 2px;
}

/*
 * 600 AND NOT 500. A ghost button is a button and is set in the same face as
 * the solid one; 500 was a half-step that only existed because Inter shipped it
 * here. The theme now serves Urbanist at 600 and 700 only, so a rule asking for
 * 500 would get a weight the browser invented by smearing the 400 outline.
 * `scripts/theme-fonts-check.mjs` refuses it.
 *
 * It is still visibly lighter than the solid CTA: that difference is carried by
 * the ground and the border, which is where it belongs.
 */
.ts-cta--ghost {
	background: var(--ts-paper);
	border-color: var(--ts-line-strong);
	color: var(--ts-ink);
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-cta--ghost:hover,
.ts-cta--ghost:focus {
	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);
	border-color: var(--ts-ink);
	color: var(--ts-ink);
}

.teeshoop-loop-cta {
	font-variant-numeric: normal;
}

/* ---------------------------------------------------------------- table */

.ts-table__scroll {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

.ts-table {
	width: 100%;
	margin: 0;
	border-collapse: collapse;
	font-size: 0.875rem;
}

.ts-table caption,
.ts-table__caption {
	margin: 0 0 0.375rem;
	color: var(--ts-muted);
	font-size: 0.8125rem;
	text-align: left;
	caption-side: top;
}

.ts-table th,
.ts-table td {
	padding: 0.4375rem 0.625rem;
	border-bottom: 1px solid var(--ts-line);
	text-align: left;
	vertical-align: top;
}

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

.ts-table td.ts-num,
.ts-table th.ts-num {
	text-align: right;
	white-space: nowrap;
}

.ts-table--pricing tbody th {
	white-space: nowrap;
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-table--pricing td b {
	display: block;
	font-size: 0.9375rem;
}

.ts-table__ttc {
	display: block;
	color: var(--ts-muted);
	font-size: 0.75rem;
}

.ts-table__off {
	display: block;
	color: var(--ts-good);
	font-size: 0.6875rem;
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-table--areas {
	max-width: 24rem;
}

.ts-form__row {
	display: grid;
	gap: 0.75rem;
	margin-bottom: 0.75rem;
}

.ts-form__field {
	display: block;
	margin-bottom: 0.75rem;
}

.ts-form__row .ts-form__field {
	margin-bottom: 0;
}

.ts-form__label {
	display: block;
	margin-bottom: 0.1875rem;
	font-size: 0.8125rem;
	font-family: var(--ts-font-display);
	font-weight: 600;
}

.ts-form__hint {
	display: block;
	margin-top: 0.1875rem;
	color: var(--ts-muted);
	font-size: 0.75rem;
}

.ts-form__legal {
	margin: 0.875rem 0 0;
	max-width: 68ch;
	color: var(--ts-muted);
	font-size: 0.75rem;
	line-height: 1.5;
}

/* The honeypot: off-screen rather than display:none, because some bots skip
   what is not rendered and a real person never reaches it (tabindex -1). */
.ts-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.ts-msg {
	margin: 0 0 1rem;
	padding: 0.625rem 0.75rem;
	border-left: 3px solid;
	font-size: 0.9375rem;
}

.ts-msg--good {
	border-color: var(--ts-good);
	background: var(--ts-good-wash);
	color: var(--ts-good-ink);
}

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

/* ------------------------------------------------------------- consent */

/*
 * THE TWO BUTTONS ARE THE SAME BUTTON.
 *
 * `.ts-consent__btn` is one rule used twice, so « Tout refuser » and « Tout
 * accepter » cannot drift apart in size, weight or colour. That is not a style
 * preference: the CNIL's grievance against the sites it has fined is that
 * refusing read as the lesser option, and two selectors would eventually let it.
 * Refuse is written first in the markup, so it is also first under the finger on
 * a phone and first in the tab order.
 *
 * The panel is a strip at the bottom of the page and not a modal: access to a
 * French site may not be conditioned on accepting trackers for this purpose, so
 * nothing here traps focus, dims the page or blocks a click on the catalogue
 * underneath.
 */
.ts-consent {
	/*
	 * STICKY AND NOT FIXED, so nothing on the page is permanently unreachable.
	 *
	 * Fixed, the strip sat over the bottom of every page for as long as the
	 * visitor had not chosen. `npm run verify:site` found it by trying to click
	 * a facet chip and timing out: « ts-consent subtree intercepts pointer
	 * events ». Sticky keeps it pinned to the bottom of the viewport while there
	 * is page left below it, and lets it settle into the flow at the end of the
	 * document, so scrolling to the end always frees whatever it was covering.
	 */
	position: sticky;
	bottom: 0;
	z-index: 60;
	padding: var(--ts-space-3);
	background: var(--ts-paper);
	border-top: 2px solid var(--ts-ink);
	box-shadow: var(--ts-elev-2);

	/*
	 * AND CAPPED, BECAUSE STICKY WAS NOT ENOUGH.
	 *
	 * Measured in Chromium on 26/08/2026: the strip was 601 px of an 812 px
	 * viewport at 375 px wide, 74 % of the screen, and it still intercepted
	 * pointer events over everything under it. `npm run verify:wp-e2e` had been
	 * red since session 11 for exactly that reason and nobody had run it: the
	 * studio's own bottom navigation sat under the strip and Playwright timed
	 * out clicking « Imports » with « ts-consent subtree intercepts pointer
	 * events », which is the same sentence the comment above records from the
	 * previous round. Sticky frees what it covers only when you reach the end of
	 * the document, and inside a full-height iframe there is no end to reach.
	 *
	 * So the height is bounded and the SAME bound is reserved at the foot of the
	 * document (see `--ts-consent-h` below). Nothing is ever permanently under
	 * it, `scroll-padding` tells the browser where the floor really is when it
	 * scrolls a focused control into view, and WCAG 2.2's 2.4.11 Focus Not
	 * Obscured stops being failed by a panel the visitor never opened.
	 */
	max-block-size: var(--ts-consent-h);
	overflow-y: auto;
	overscroll-behavior: contain;
}

/*
 * The floor the strip may occupy, and the space the page keeps free for it.
 *
 * One number, used three times: the strip's own ceiling, the padding under the
 * document and the scroll padding. Three copies of it would be three chances
 * for the reserve to stop matching the thing it reserves for.
 */
:root {
	/*
	 * 26rem, MEASURED AND NOT CHOSEN. At 24rem the panel's own content came to
	 * 410 px against 382 px of room at 375 px wide, so the link to the privacy
	 * notice fell below an internal scroll: a control that is there and that
	 * nobody would find. The three buttons were above it and reachable, but a
	 * panel that has to be scrolled to be read whole is a panel somebody will
	 * shorten by moving a button. 26rem is 416 px, six more than the content
	 * needs at the narrowest width the shop supports.
	 */
	--ts-consent-h: 26rem;
}

/*
 * ONLY WHILE THE STRIP IS THERE. `:has()` is what lets this be CSS rather than
 * a body class set by a script: the panel has to work with JavaScript off, so
 * everything about it must.
 */
html:has(.ts-consent) {
	scroll-padding-block-end: var(--ts-consent-h);
}

body:has(.ts-consent) {
	padding-block-end: var(--ts-consent-h);
}

/*
 * AND ON THE EDITOR, IT DOES NOT FLOAT AT ALL.
 *
 * The personalisation view is a tool that fills the window: the frame is
 * `min(85dvh, 900px)` and its own navigation is pinned to ITS bottom. Reserving
 * space under the document does not help there, because there is no scroll
 * position that puts a viewport-tall tool's floor above a viewport-anchored
 * strip. Measured: `npm run verify:wp-e2e` timed out clicking « Imports » with
 * « ts-consent subtree intercepts pointer events », twice, before and after the
 * cap.
 *
 * So wherever a studio frame is on the page, whether it was reached by pressing
 * « Personnaliser » or embedded in the product page itself, the panel sits in
 * the flow at the end of the page and floats over nothing. The condition is the
 * FRAME's own container and not the view wrapper, because the e2e product
 * embeds the editor inline and the first version of this rule matched only the
 * view: right reasoning, wrong selector, and the harness said so. That costs nothing in law: this view is reached by pressing
 * « Personnaliser », so the visitor has expressly asked for the tool, and the
 * shop still writes nothing to their machine before they choose. The permanent
 * control in the footer is unchanged, and so is the banner on every other page.
 *
 * `:has()` rather than a body class, because the panel has to work with
 * JavaScript off and so must everything about it.
 */
body:has(.teeshoop-studio) .ts-consent {
	position: static;
	max-block-size: none;
	overflow: visible;
}

body:has(.teeshoop-studio) {
	padding-block-end: 0;
}

.ts-consent__box {
	max-width: 62rem;
	margin: 0 auto;
	display: grid;
	gap: var(--ts-space-3);
}

.ts-consent__title {
	margin: 0;
	font-size: var(--ts-text-lg);
	line-height: var(--ts-leading-tight);
}

.ts-consent__lead {
	margin: 0;
	max-width: var(--ts-measure);
	font-size: var(--ts-text-sm);
	color: var(--ts-muted);
}

.ts-consent__list {
	margin: 0;
	padding: 0;
	list-style: none;
	display: grid;
	gap: var(--ts-space-2);
}

.ts-consent__label {
	display: flex;
	gap: var(--ts-space-2);
	align-items: baseline;
	font-family: var(--ts-font-display);
	font-weight: 600;
	cursor: pointer;
}

.ts-consent__detail {
	margin: 0.25rem 0 0 1.65rem;
	max-width: var(--ts-measure);
	font-size: var(--ts-text-sm);
	color: var(--ts-muted);
}

/*
 * THE DETAIL IS FOLDED AND THE CHOICE IS NOT.
 *
 * What the CNIL asks to be immediate is the DECISION: one click to refuse, one
 * to accept, at the same level. What it asks to be clear is the PURPOSE, and the
 * purpose is the label beside the checkbox, which stays visible. Ninety words
 * explaining which three strings are written and for how long is information the
 * visitor is entitled to and does not need in order to decide, and it was two
 * hundred of the six hundred pixels this panel took on a phone.
 *
 * A `<details>` and not a script, like everything else here.
 */
.ts-consent__why > summary {
	margin: 0.25rem 0 0 1.65rem;
	font-size: var(--ts-text-sm);
	color: var(--ts-muted);
	cursor: pointer;
}

.ts-consent__why[open] > summary {
	margin-bottom: 0.25rem;
}

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

.ts-consent__btn {
	flex: 1 1 12rem;
	padding: 0.75rem 1rem;
	border: 1px solid var(--ts-accent);
	border-radius: 3px;
	background: var(--ts-accent);
	color: var(--ts-accent-ink);
	font: inherit;
	font-size: 1rem;
	font-family: var(--ts-font-display);
	font-weight: 600;
	text-align: center;
	cursor: pointer;
}

.ts-consent__btn:hover,
.ts-consent__btn:focus {
	background: var(--ts-accent-dark);
	border-color: var(--ts-accent-dark);
}

.ts-consent__save {
	padding: 0.75rem 1rem;
	border: 1px solid var(--ts-line-strong);
	border-radius: 3px;
	background: var(--ts-paper);
	color: var(--ts-ink);
	font: inherit;
	font-size: 1rem;
	cursor: pointer;
}

.ts-consent__save:hover,
.ts-consent__save:focus {
	background: var(--ts-surface);
	--ts-muted: var(--ts-muted-strong);
	border-color: var(--ts-ink);
}

.ts-consent__btn:focus-visible,
.ts-consent__save:focus-visible {
	outline: var(--ts-focus-width) solid var(--ts-ink);
	outline-offset: var(--ts-focus-offset);
}

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

@media (min-width: 48rem) {
	.ts-consent__box {
		grid-template-columns: 1fr auto;
		align-items: start;
	}

	.ts-consent__title,
	.ts-consent__lead,
	.ts-consent__list,
	.ts-consent__more {
		grid-column: 1;
	}

	.ts-consent__acts {
		grid-column: 2;
		grid-row: 1 / span 3;
		flex-direction: column;
		align-items: stretch;
		min-width: 15rem;
	}

	.ts-consent__btn {
		flex: 0 0 auto;
	}
}
