/* Components — UI kit (from Confluence spec + Figma tokens) */

/* Primary button (pill) */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--size-button-min-width);
  max-width: var(--size-control-max);
  /* Métriques du composant Figma primary_button (component set 157:2919,
     SIPTECS-8635), toutes variantes. 14px vertical : aucun token --space-* ne
     tombe dessus ; avec line-height 20px, les 48px de --size-button-height. */
  padding: 14px var(--space-10);
  border: none;
  border-radius: var(--radius-button);
  background: var(--color-button-bg);
  color: var(--color-button-text);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-regular); /* 400 — état Default du 157:2919 */
  line-height: 20px; /* texte Roboto 14px/20, cf. 157:2919 */
  text-align: center;
  transition: background var(--transition-base), color var(--transition-base);
}
.btn:hover { background: var(--color-text); color: var(--color-bg); }
.btn:disabled,
.btn[aria-disabled="true"] {
  background: var(--color-button-bg-disabled);
  color: var(--color-button-text-disabled);
  cursor: not-allowed;
}
.btn.is-active { background: var(--color-accent); color: var(--color-button-text-active); }
/* Light variant — CTA « Commencer » de l'introduction (Figma 157:2919) :
   pilule pleine largeur, plafond --size-control-max hérité de .btn. */
.btn--light {
  width: 100%;
  background: var(--color-text);
  color: var(--color-button-text-active);
  /* Animate: Instant dans Figma — neutralise la transition héritée de .btn. */
  transition: none;
}
/* CTA_Hover (157:2919) : fond INCHANGÉ (blanc), seul le poids évolue. */
.btn--light:hover {
  background: var(--color-text);
  color: var(--color-button-text-active);
  font-weight: var(--font-weight-medium); /* 500 — état Hover du 157:2919 */
}
/* État pressé (clic -> Validated du 157:2919) : même token que .btn.is-active. */
.btn--light:active {
  background: var(--color-button-bg-active);
  color: var(--color-button-text-active);
  font-weight: var(--font-weight-medium); /* 500 — état Validated du 157:2919 */
}

/* Primary purchase (accent pill + icons) */
.btn-purchase {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  max-width: var(--size-control-max);
  padding: var(--space-5) var(--space-8);
  border: none;
  border-radius: var(--radius-button);
  background: var(--color-accent);
  color: var(--color-button-text-active);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-light);
  line-height: 26px;
  transition: background var(--transition-base);
}
.btn-purchase:hover { background: var(--color-text); }
.btn-purchase .icon { width: 20px; height: 20px; }
/* SIPTECS-8655 : `.btn-purchase` n'habille qu'un `<a>` (résultat et quick-buy),
   donc pas d'état `:disabled` — il ne cible jamais un lien. */

/* Link */
.link {
  display: inline-flex;
  align-items: center;
  padding: 2px 0;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-regular);
  line-height: 20px;
  color: var(--color-text);
  border-bottom: 1px solid transparent;
  transition: font-weight var(--transition-fast), border-color var(--transition-fast);
}
.link:hover { border-bottom-color: currentColor; } /* keep weight constant to avoid layout shift */
/* Lien légal de l'opt-in (UI KIT `Links_Defaut`, 557:3955) : ce modificateur ne
   vise que lui — `.link` sert aussi « Je passe cette étape » et « Recommencer ». */
/* `display: block` reproduit le LINE SEPARATOR du libellé opt-in (351:4806,
   calque `label`) : la coupure est VOULUE, avant « Voir notre… ». Un bloc plutôt
   qu'un `<br>` : phrase et lien restent deux chaînes distinctes dans content.py.
   `padding: 0`/`line-height: inherit` : un seul paragraphe, interligne 16,41. */
.link--legal {
  display: block;
  padding: 0;
  line-height: inherit;
  font-weight: var(--font-weight-light);  /* 300 */
  text-decoration: underline;
  text-underline-offset: 2px;
  border-bottom: 0;                       /* sinon double trait au survol */
}

/* Selector (answer) */
/* Figma UI KIT `Selector_Desktop` / `Selector_Mobile` (node 328:2341) — two
   states only, and they *invert*: white box/black text, dark/white on hover.
   The 0.75px white ring survives the inversion, so it stays in both. */
.selector {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  max-width: 415px;
  padding: var(--space-3);
  border: 0.75px solid var(--color-text);
  border-radius: var(--radius-selector);
  background: var(--color-text);
  color: #000;
  font-size: var(--text-sm);
  font-weight: var(--font-weight-light);
  line-height: 26px;
  text-align: center;
  text-wrap: balance; /* even out wrapped lines: no lone orphan word */
  transition: background var(--transition-base), color var(--transition-base), border-color var(--transition-base);
}
.selector:hover { background: var(--color-bg); color: var(--color-text); }
/* Selected: no such variant in the Figma kit (choosing an answer navigates away
   immediately) — kept for the styleguide and any future multi-choice screen. */
.selector.is-selected,
.selector[aria-pressed="true"] {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-button-text-active);
}
/* Mobile : 13px, pour que les réponses longues tiennent sur une ligne dans 345px. */
@media (max-width: 768px) { .selector { max-width: var(--size-control-max); font-size: 13px; } }
/* answer list (rendered as <ul role=list>) */
.selectors { list-style: none; margin: 0; padding: 0; }
.selectors > li { display: flex; }

/* Text field */
/* `fields-text` (328:2533, Figma) : gap 12px label / ligne du champ / message. */
.field { display: flex; flex-direction: column; gap: var(--space-3); width: var(--size-field-width); max-width: 100%; text-align: left; }
/* 14px (`--text-sm`), pas 12px : aligne le floating-label sur la légende
   Civilité/opt-in (`legend.field__label` plus bas). Écart assumé avec Figma,
   qui met ce libellé à 12px — D34, SIPTECS-8652. */
.field__label { font-size: var(--text-sm); font-weight: var(--font-weight-light); line-height: 22px; color: var(--color-text); }
/* Astérisque « champ requis » (SIPTECS-8652, D34) : tonal, jamais rouge/jaune
   (réservés à l'erreur/l'actif). Le glyphe reste `aria-hidden` : l'accessibilité
   tient à `required`/`aria-required` sur le contrôle. Il ne subsiste que sur les
   trois champs bloquants (civilité, e-mail, opt-in). */
.field__required { color: var(--color-text-muted); margin-inline-start: .15em; }
/* Ligne du champ (`Frame 59`, Figma) : porte l'input ET les deux pastilles d'état
   (toujours dans le DOM, cf. form.html). La gouttière de 36px est réservée EN
   PERMANENCE — sinon chaque champ change de largeur au fil de la saisie ; écart
   assumé avec Figma. `padding-right` et non `gap` : la pastille est en absolu. */
.field__row { display: flex; align-items: center; width: 100%; position: relative; padding-right: 36px; }
.field__control {
  flex: 1;
  min-width: 0;
  /* 13 + 20 + 13 + 2 = 48px, la hauteur de `fields-text` (328:2533, Figma).
     13px vertical posé en dur : hors grille 4px, aucun token ne tombe dessus. */
  padding: 13px var(--space-10);
  background: var(--color-field-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-field);
  color: var(--color-field-text);
  /* 16px (`--text-base`) et non 14px : SIPTECS-8726, Safari iOS — sous 16px, tout
     champ déclenche le zoom automatique au focus, actif jusqu'à la validation du
     formulaire. Rien à faire côté meta viewport (c'est le viewport hôte qui arbitre),
     et `user-scalable=no` violerait le WCAG 1.4.4. Écart assumé avec Figma (14px). */
  font-size: var(--text-base);
  line-height: 20px;
  /* Halo de focus et fond de substitution autofill passent tous deux par
     box-shadow : une couche par propriété personnalisée, chaque état ne redéfinit
     QUE la sienne. Dans une liste box-shadow la PREMIÈRE ombre peint au-dessus —
     le halo doit précéder le fond, sinon les 1000px de l'autofill le recouvrent. */
  --field-halo: inset 0 0 0 0 transparent;
  --field-autofill-bg: inset 0 0 0 0 transparent;
  box-shadow: var(--field-halo), var(--field-autofill-bg);
  /* box-shadow SORT de la transition : il porte le fond de substitution de
     l'autofill (inset 1000px, SIPTECS-8726), qu'on ne veut pas voir s'animer.
     Contrepartie assumée : le halo de focus devient sec là où border-color
     continue de glisser. */
  transition: border-color var(--transition-fast);
}
.field__control::placeholder { color: var(--color-field-placeholder); font-style: italic; }
.field__control:hover { border-color: var(--color-border-focus); }
/* inset shadow instead of growing the border -> no content reflow on focus */
.field__control:focus { outline: none; border-color: var(--color-border-focus); --field-halo: inset 0 0 0 1px var(--color-border-focus); }
.field.is-valid .field__control { border-color: var(--color-accent); }
.field.is-error .field__control { border-color: var(--color-error); }
/* Un champ en erreur qui reprend le focus revient ENTIÈREMENT à l'état Focused :
   sinon `.field.is-error .field__control` (0-3-0) bat `.field__control:focus`
   (0-2-0) sans rien dire de son box-shadow. En CSS et pas seulement en JS (cf.
   form-validation.js) : l'erreur peut venir du serveur. `:focus-within` sur
   `.field` équivaut à `:focus` sur l'input, seul élément focusable du composant. */
.field.is-error:focus-within .field__control,
.field.is-valid:focus-within .field__control {
  border-color: var(--color-border-focus);
  --field-halo: inset 0 0 0 1px var(--color-border-focus);
}
/* Autofill (SIPTECS-8726) : Safari/Chrome peignent eux-mêmes le fond du champ et
   ignorent `background` — sans ceci, fond clair sous un texte blanc, illisible sur
   le thème sombre. `--color-bg` et non un hexadécimal : seul token de fond redéclaré
   par le thème contribué (`templates/base.html`, bloc style nonce) ;
   `--color-field-bg` (transparent) ne l'est jamais.
   `-webkit-text-fill-color` est nécessaire en plus
   de `color`, que WebKit/Blink rendent inerte ici. Les quatre pseudo-classes sont
   pour WebKit des états distincts, qui n'héritent pas : il faut toutes les répéter. */
.field__control:-webkit-autofill,
.field__control:-webkit-autofill:hover,
.field__control:-webkit-autofill:focus,
.field__control:-webkit-autofill:active {
  --field-autofill-bg: inset 0 0 0 1000px var(--color-bg);
  -webkit-text-fill-color: var(--color-field-text);
  caret-color: var(--color-field-text);
}
.field.is-error:focus-within .field__status--error,
.field.is-valid:focus-within .field__status--valid {
  display: none;
}
.field.is-error:focus-within .field__error { display: none; }
.field__error { font-size: var(--text-xs); font-weight: var(--font-weight-light); line-height: 20px; color: var(--color-error); }
/* Pastille de statut (26×26 — Figma states Validated/Error) : toujours dans le
   DOM, seule `.field.is-valid`/`.field.is-error` en affiche une. Couleur du glyphe
   par `color` et non un `fill` : les SVG sont en `currentColor`, convention du
   dépôt, ce qui la fait survivre à la surcharge de thème du back-office. */
.field__status {
  display: none;
  align-items: center;
  justify-content: center;
  /* par-dessus la gouttière réservée par `.field__row` : son apparition ne pousse
     rien, tous les champs gardent la même largeur */
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border-radius: 30px;
}
.field__status .icon { width: 16px; height: 16px; }
.field__status--valid { background: var(--color-accent); color: var(--color-bg); }
/* --color-error-badge (#FF2121) : PAS --color-error (#E30000, bordure/message) —
   Figma pose deux rouges différents sur cet état, cf. tokens.css. */
.field__status--error { background: var(--color-error-badge); color: var(--color-bg); }
.field.is-valid .field__status--valid { display: flex; }
.field.is-error .field__status--error { display: flex; }

/* Progress bar */
/* Figma UI KIT `Progress-bar` (node 328:2341): an 8px track (accent, dimmed)
   carrying the accent fill, then a 4px black band, edge to edge — see the
   placement rule in base.css. */
.progress {
  width: 100%;
  height: var(--size-progressbar-height);
  background: linear-gradient(
    var(--color-progress-track) 0 var(--size-progressbar-track),
    #000 var(--size-progressbar-track) 100%
  );
  border-radius: var(--radius-progress);
  overflow: hidden;
}
.progress__fill { height: var(--size-progressbar-track); background: var(--color-accent); transition: width var(--transition-base); }

/* Product tile (card) */
.product-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  max-width: var(--size-control-max);
  padding-bottom: var(--space-4);
  border-radius: var(--radius-card);
  background: var(--color-text);
  color: #000;
  border: 2px solid transparent;
  text-align: left;
  transition: border-color var(--transition-base), box-shadow var(--transition-base);
}
/* Hover (Figma `product tile` 108:6143, `State=Hover` 217:4156, SIPTECS-8646) :
   contour et ombre BLANCS posés VERS L'EXTÉRIEUR — la page est sombre et la carte
   blanche : un liseré peint DANS la carte ou une ombre noire y sont invisibles.
   Spread en box-shadow, pas border-color : aucun reflow. `:has()` réserve le
   survol aux tuiles réellement cliquables : sans URL de PDP la tuile garde un
   `<a href="#">`, sans `data-pid`. */
.product-tile:has(a[data-pid]):hover {
  box-shadow: 0 0 0 2px #EFEFEF, 0 4px 10px 3px rgba(255, 255, 255, .21);
}
/* UI KIT `product tile` > `Top-card` : bande portant le fond, qui épouse en hauteur
   un visuel CARRÉ centré. La hauteur ne suit PAS la largeur (190 de haut à 338, 345
   et 366 de large) — une `aspect-ratio` rendrait la tuile plus carrée en
   rétrécissant. `#F6F6F6` est
   le défaut du kit ; la valeur contribuée arrive par `data-bg` et le CSSOM
   (models.TILE_BACKGROUND_DEFAULT). */
.product-tile__media {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--size-tile-media);
  background: #F6F6F6;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
}
/* `cover` sur une boîte carrée = le `scaleMode: FILL` de la maquette */
.product-tile__media > img { width: auto; height: 100%; aspect-ratio: 1; max-width: 100%; object-fit: cover; }
.product-tile__body { display: flex; flex-direction: column; gap: var(--space-2); padding: 0 var(--space-4); }
.product-tile__title { font-size: var(--text-sm); font-weight: var(--font-weight-medium); line-height: 26px; color: #000; }
.product-tile__desc { font-size: var(--text-xs); font-weight: var(--font-weight-light); line-height: 24px; color: #575757; }
.product-tile__row { display: flex; align-items: center; justify-content: space-between; }
.product-tile__price { font-size: var(--text-sm); font-weight: var(--font-weight-medium); color: var(--color-bg); }
.product-tile__price--old { color: #575757; text-decoration: line-through; }
/* Mention légale (prix au litre, obligatoire DE/AT) : subordonnée au prix. */
.product-tile__unit { font-size: var(--text-xs); font-weight: var(--font-weight-light); color: #575757; }
.product-tile__oos { color: var(--color-error); font-size: var(--text-sm); font-weight: var(--font-weight-medium); }
.product-tile.is-oos { opacity: .85; }
/* Icône « ajouter au panier » de la tuile résultat (Figma `product tile` 108:6143 +
   frame résultat mobile 756:4013, SIPTECS-8638) : nue, SANS cercle ni fond —
   contrairement à `.qb-add` (quick-buy), pas la même variante du kit. Deux
   incarnations exclusives : un `<button>` `disabled` côté gabarit, que cart-bridge.js
   arme ou masque une fois l'origine parent validée ; ou, sans exécuteur panier chez
   l'hôte, un `<a>` de repli vers la fiche produit. */
.product-tile__add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  flex: none;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.product-tile__add .icon { width: 24px; height: 24px; }
.product-tile__add:disabled { opacity: .4; cursor: not-allowed; }
/* Tuile en erreur (SCAPI muet / produit dépublié) : elle se retire plutôt que de
   crier. Pas de rouge — `--color-error` est pris par la rupture, un état différent
   (*ce produit existe*). Le cadre 2px reste posé : zéro décalage de la grille. */
.product-tile--error {
  background: transparent;
  border-color: var(--color-border);
}
/* la zone média perd son aplat clair : le cadre reste, le contenu manque */
.product-tile--error .product-tile__media { background: transparent; }
.product-tile--error .product-tile__title {
  color: var(--color-text-muted);
  font-weight: var(--font-weight-light);
}
/* Une tuile en erreur n'est pas cliquable. Déjà garanti par `:has(a[data-pid])`
   ci-dessus ; règle explicite gardée si le markup erreur gagnait un lien. */
.product-tile--error:hover { border-color: var(--color-border); }

/* Diagnostic form */
.diag-form { width: 100%; display: flex; flex-direction: column; gap: var(--space-5); text-align: left; }
.diag-form .field { width: 100%; }
/* Écrans `Form-page-Desktop` (351:4806) et `-Mobile` (351:5391) : gap 12 entre la
   légende et la rangée d'options, 24 entre options (`colissimo-methods`). */
.form-group { display: flex; flex-direction: column; gap: var(--space-3); border: 0; padding: 0; margin: 0; }
.radio-row { display: flex; flex-wrap: wrap; gap: var(--space-6); }
/* Interligne 16,41 (~1.17, l'interligne intrinsèque de Roboto) ; la taille vient
   de `.field__label`, 14px comme les deux légendes (D34, SIPTECS-8652) — écart
   assumé avec Figma, qui met le `floating-label` à 12px. Deux corrections propres
   au `<legend>` : le `gap` du fieldset ne s'y applique PAS (le navigateur la sort
   du flux, elle n'est pas un item flex), et l'UA lui pose `padding-inline: 2px`. */
legend.field__label {
  line-height: 1.17;
  padding: 0;
  margin-bottom: var(--space-3);
}
.radio { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); font-weight: var(--font-weight-light); cursor: pointer; }
/* `radio-button` (748:5256, Figma), 3 états — un seul sélecteur `.radio` pour la
   civilité ET l'opt-in (voulu). Le contour (16×16, 1px) ne change JAMAIS ; seule
   la pastille intérieure 10×10 apparaît/change de couleur. `accent-color` ne sait
   rendre que le natif du navigateur : rendu maison, repli sous contraste forcé. */
.radio input {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  margin: 0;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: transparent;
  /* la grille cale la pastille : `transform` reste libre pour l'animation */
  display: grid;
  place-content: center;
}
/* `::before` et non un `radial-gradient` : un `background-image` ne s'interpole pas,
   l'apparition resterait un à-coup. Figma joue ici un FONDU (SMART_ANIMATE /
   EASE_OUT / 300ms) et non un grossissement — la pastille fait 10×10 au même endroit
   dans les trois états : `opacity` et `background-color`, PAS de `transform`.
   `prefers-reduced-motion` : base.css. */
.radio input::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-border-hover); /* Hover : pastille blanche */
  opacity: 0;                            /* Default : pastille masquée */
  transition: opacity 300ms ease-out, background-color 300ms ease-out;
}
.radio:hover input::before { opacity: 1; }
/* Après la règle de survol, à spécificité égale : survoler un radio déjà
   sélectionné ne doit pas le repasser au blanc. */
.radio input:checked::before { opacity: 1; background: var(--color-accent); }
/* Repli contraste forcé (Windows High Contrast…) : `appearance: none` efface le
   rendu système que ce mode sait recolorer — on le lui rend. */
@media (forced-colors: active) {
  .radio input { appearance: auto; }
}
/* base.css pose un `:focus-visible` global à `border-radius: 2px` — anneau
   presque carré sur un contrôle rond. Spécificité 0-2-1 contre 0-1-0 pour le
   `:where(...)` global : cette règle ne surcharge que le rayon. */
.radio input:focus-visible { border-radius: 50%; }
.form-actions { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); margin-top: var(--space-2); }
.form-actions .btn { width: 100%; max-width: var(--size-control-max); }

/* Results page */
.scroll-page { min-height: 100vh; min-height: 100dvh; padding: var(--space-10) var(--space-6); }
.results { max-width: 600px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--space-10); }
.promo { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); background: var(--color-text); color: #000; border-radius: var(--radius-card); text-align: center; }
.promo__text { font-size: var(--text-sm); font-weight: var(--font-weight-light); }
/* Bouton copier et lien de redirection (SIPTECS-8681) partagent la même typo ;
   `.promo__cta` n'a ni icône ni état « copié », les deux règles suivantes restent
   donc scopées à `.promo__copy`. Pas de `text-transform` : la capitale de la
   maquette vient du libellé saisi en BO, pas d'une règle CSS. */
.promo__copy, .promo__cta { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; font-size: var(--text-sm); font-weight: var(--font-weight-medium); text-decoration: underline; color: #000; cursor: pointer; }
.promo__copy .icon { width: 15px; height: 15px; }
.promo__copy.is-copied { color: #2f6f4f; }
.results__head { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); text-align: center; }
.results__tag { font-size: 17px; font-weight: var(--font-weight-regular); color: var(--color-accent); }
.results__title { font-size: 24px; font-weight: var(--font-weight-regular); line-height: 36px; }
/* Maquette, frame `Step1` : libellé d'étape et description alignés à GAUCHE sur la
   largeur de la tuile, collés (gap 0) — centrés, deux étapes ne s'alignaient plus. */
.results__step { display: flex; flex-direction: column; align-items: stretch; gap: 0; text-align: left; }
/* letter-spacing 0 dans la maquette : l'`--letter-spacing-wide` est réservé aux CTA */
.results__step-label { font-size: 13px; font-weight: var(--font-weight-regular); line-height: 20px; text-transform: uppercase; color: var(--color-accent); }
.results__step-sub { font-size: var(--text-sm); font-weight: var(--font-weight-light); line-height: 26px; }
.results__step > .product-tile, .results__complementary > .product-tile { margin-top: var(--space-3); }
.results__step .product-tile { width: 100%; max-width: 320px; margin-inline: auto; }
/* Séparateurs (SIPTECS-8636, Figma desktop 800:3530 / mobile 756:4013) : 32px
   avant le trait — le gap du flex parent vaut déjà --space-10, la marge négative
   ramène l'écart net à --space-8. `:has(p)` sur les mentions légales : le
   `<footer>` se rend toujours, mais un trait au-dessus d'un bloc vide serait un
   artefact qu'aucune contribution ne justifie (D12). Le trait suit la largeur de la
   section à toutes les largeurs : au-dessus d'une carte plafonnée à 345 il la déborde
   donc dès ~394px, en mobile comme en desktop — voulu. */
.results__complementary {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  text-align: left;
  border-top: 1px solid var(--color-divider);
  margin-top: calc(var(--space-8) - var(--space-10));
  padding-top: var(--space-6);
}
.results__legal:has(p) {
  border-top: 1px solid var(--color-divider);
  padding-top: var(--space-8);
}
.results__legal { display: flex; flex-direction: column; gap: var(--space-3); }

/* Results screen — 2 columns (sticky media + scrollable content), products in a grid */
.screen--results .screen__content { padding: 0; }
/* Contrat avec resize.js (SIPTECS-8649) : il pose `--app-viewport` (hauteur réellement
   disponible chez le visiteur, en px) sur `<html>` ; on la consomme ici, repli `100vh`
   en accès direct. Ne pas la définir ici. `width: 100%` : avec les DEUX axes explicites
   l'`aspect-ratio` de repli (base.css) est ignoré — sinon la largeur en dériverait.
   Hôte muet (`--app-viewport: auto`) : `height: auto`, un axe redevient libre et le
   ratio reprend la main — repli intact. */
.screen--results .screen__media { position: sticky; top: 0; width: 100%; height: var(--app-viewport, 100vh); align-self: start; }
.screen--results .promo { border-radius: 0; }
/* 756 de contenu (la largeur de `Rituel-list-product`) + les deux gouttières : la
   maquette mesure la liste, pas la boîte qui la porte. */
.results-body { display: flex; flex-direction: column; gap: var(--space-10); padding: var(--space-8) var(--space-6); width: 100%; max-width: calc(756px + 2 * var(--space-6)); margin-inline: auto; }
/* Une colonne à la largeur d'une carte, centrée : l'état de base (mobile, tablette).
   Deux cartes côte à côte n'existent que dans le bloc >= 1143px plus bas (D57). */
.results__grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, var(--size-control-max)); justify-content: center; gap: var(--space-6); align-items: start; }
.results__grid .product-tile { max-width: none; }
/* Hors de `.results__grid`, le soin complémentaire ne profite pas de sa piste : libellé
   et tuile plafonnés et centrés ensemble, calés sur le bord gauche (SIPTECS-8636). */
.results__complementary > * { width: 100%; max-width: var(--size-control-max); margin-inline: auto; }
@media (min-width: 769px) {
  /* La colonne de contenu réserve toujours la place d'une carte : c'est la photo,
     piste `minmax(0, 1fr)`, qui cède, jamais la carte ; au-delà, partage 50/50.
     SCOPÉ à cette media query, impérativement : même spécificité (0,1,0) que le `1fr`
     mobile de base.css, elle l'emporterait par l'ordre des feuilles. */
  .screen--results { grid-template-columns: minmax(0, 1fr) minmax(calc(var(--size-control-max) + 2 * var(--space-6)), var(--layout-col-split)); }
  /* La colonne de contenu est le SEUL défilement de l'écran résultat en desktop
     (SIPTECS-8762, D51) ; `scrollbar-gutter: stable` (SIPTECS-8640) réserve la
     gouttière que le rituel déborde ou non.
     `min-height: 0` est OBLIGATOIRE : la taille minimale automatique d'un item de
     grille vaut son contenu et annulerait le plafond. JAMAIS `100vh`/`100dvh` seul
     en plafond : en iframe ces unités valent la hauteur posée au tour PRÉCÉDENT
     (le cliquet de SIPTECS-8649) ; le repli `100vh` ne sert qu'en accès direct, où
     resize.js ne s'exécute pas. */
  .screen--results .screen__content { min-height: 0; max-height: var(--app-viewport, 100vh); overflow-y: auto; scrollbar-gutter: stable; }
  /* Piste FIXE dès qu'une photo est en face : la carte ne rétrécit jamais en desktop.
     Un ascenseur classique (Windows, 15 à 17px — la colonne défile seule, D51) se prend
     sur le padding de `.results-body`, jamais sur la carte. Row-gap 48 (Figma 800:3481). */
  .results__grid { grid-template-columns: var(--size-control-max); row-gap: var(--space-12); }
  /* Le soin complémentaire suit la piste fixe : centrage par `align-self`, un
     centrage par marges auto ne survivant pas à un espace négatif. */
  .results__complementary > * { width: var(--size-control-max); margin-inline: 0; align-self: center; }
  /* Traits bord à bord (desktop seulement) : la marge négative compense le padding
     horizontal de `.results-body`, `padding-inline` restaure la position du contenu
     — l'expansion étant symétrique, l'axe médian ne bouge pas. */
  .results__complementary,
  .results__legal:has(p) { margin-inline: calc(-1 * var(--space-6)); padding-inline: var(--space-6); }
}
/* Deux cartes côte à côte seulement quand la photo garde au moins un tiers de
   l'écran : 1143 = 762 x 3/2, 762 étant la colonne de contenu qui porte deux cartes
   (2 x 345 + gouttière 24 + 2 x 24 de padding). Arbitrage SIPTECS-8640 (D57) : les
   cartes ne rétrécissent jamais, c'est la photo qui cède. */
@media (min-width: 1143px) {
  .screen--results { grid-template-columns: minmax(0, 1fr) minmax(calc(2 * var(--size-control-max) + 3 * var(--space-6)), var(--layout-col-split)); }
  .results__grid { grid-template-columns: repeat(2, var(--size-control-max)); }
  /* Une ligne réservée sous le libellé, comme la maquette (`Information product`
     = 20 + 26) : un sous-titre sur deux lignes ne pousse pas la tuile plus bas que
     sa voisine. Même seuil que le subgrid : sans voisine à aligner, la réserve ne
     serait qu'un vide sous chaque libellé. */
  .results__grid .results__step-sub { min-height: 26px; }
  /* L'impair isolé s'étend sur les deux pistes pour se centrer, à la largeur d'une carte. */
  .results__grid > .results__step:last-child:nth-child(odd) { grid-column: 1 / -1; justify-self: center; width: var(--size-control-max); }
}
/* Alignement des tuiles d'une même rangée (SIPTECS-8649) : un sous-titre ou un nom de
   produit sur deux lignes face à un voisin sur une ligne décalait le départ ou le bas
   des tuiles. Chaque `li.results__step` devient une sous-grille qui PARTAGE les pistes
   de `.results__grid` (libellé / sous-titre / tuile) : chaque étage prend la hauteur du
   plus grand de la rangée. Le libellé (clé administrable D45) est tout-ou-rien par
   locale, les `li` d'une page ont donc toujours le MÊME nombre d'enfants.
   `align-items: stretch` rétablit l'étirement que la règle de base coupe ; dans la
   tuile, `__body` absorbe l'écart et la ligne prix/panier se cale en bas. Sans subgrid,
   le `@supports` ne s'applique pas : on retombe sur la réserve d'une ligne.
   SIPTECS-8640 : en subgrid, le `row-gap: 48px` du parent s'applique entre TOUTES les
   pistes, y compris les jointures internes d'une carte, et la compensation de gouttière
   l'absorbe dans les boîtes des enfants (libellé 20→24, sous-titre 26→48, tuile 26px
   trop bas). D'où `row-gap: 0`, l'écart entre lignes recréé en MARGE sur le premier
   enfant des cartes à partir de la 2e ligne (`nth-child(n+3)`), qui contribue à la
   piste partagée. SCOPÉ au `@supports`, impérativement : sans subgrid le repli garde
   le `row-gap: 48` de la règle plus haut, sinon ses lignes se colleraient. */
@media (min-width: 1143px) {
  @supports (grid-template-rows: subgrid) {
    .results__grid { align-items: stretch; row-gap: 0; }
    .results__grid > .results__step { display: grid; grid-template-rows: subgrid; grid-row: span 3; }
    .results__grid > .results__step:nth-child(n+3) > :first-child { margin-top: var(--space-12); }
    .results__grid .product-tile__body { flex: 1; }
    .results__grid .product-tile__row { margin-top: auto; }
  }
}

/* Quick-buy */
.qb__title { font-size: 32px; font-weight: var(--font-weight-regular); line-height: 42px; }
.qb__strong { font-size: 20px; font-weight: var(--font-weight-medium); line-height: 30px; }
.qb__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.qb-item { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3); border: 1px solid rgba(255, 255, 255, 0.14); border-radius: var(--radius-card); }
.qb-item__media { flex: 0 0 64px; width: 64px; height: 64px; border-radius: var(--radius-card); background: #F6F6F6; }
.qb-item__info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; text-align: left; }
.qb-item__name { font-size: var(--text-sm); font-weight: var(--font-weight-medium); overflow-wrap: break-word; }
.qb-item__instruction { font-size: var(--text-xs); font-weight: var(--font-weight-light); color: var(--color-text-muted); }
.qb-item__buy { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-3); }
.qb-item__price { font-size: var(--text-sm); font-weight: var(--font-weight-medium); white-space: nowrap; }
.qb-item__oos { font-size: var(--text-sm); font-weight: var(--font-weight-medium); color: var(--color-error); white-space: nowrap; }
.qb-add { width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--color-text); background: transparent; color: var(--color-text); font-size: 20px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; transition: background var(--transition-base), color var(--transition-base); }
.qb-add:hover { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-button-text-active); }
.qb-add .icon { width: 20px; height: 20px; }
.qb__complementary { display: flex; flex-direction: column; gap: var(--space-3); text-align: left; }
/* mobile: drop price + add button below the info so the name has full width */
@media (max-width: 480px) {
  .qb-item { flex-wrap: wrap; }
  .qb-item__info { flex-basis: calc(100% - 64px - var(--space-4)); }
  .qb-item__buy { flex-basis: 100%; justify-content: flex-end; }
}
