/* Shared UI components used across views — the product-detail buy box AND the
   add-to-cart confirmation modal, which can now be triggered from anywhere
   (product page, category listing, …). This lives in a GLOBAL sheet, not a
   per-view file, precisely because the modal is no longer product-page-only.
   (The buy-box LAYOUT itself stays in vues/produit/produit.css — it's detail-
   page-specific; only the reusable pieces live here.) */

/* ── Quantity stepper (markup from qtyStepper(); behaviour in js/javascript.js) ── */
.qty-stepper {
  display: inline-flex;
  align-items: stretch;
  height: 44px;                                 /* WCAG 2.5.5 touch target */
  background: #fff;
  border: 1px solid #d0d5dd;
  /* square, to match the buy button + the rest of css8 (no border-radius unless asked) */
  overflow: hidden;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.qty-stepper:focus-within {
  border-color: var(--green-cart);               /* matches the green "Ajouter au Panier" */
  box-shadow: 0 0 0 3px rgba(74, 170, 53, .15);
}

.qty-minus,
.qty-plus {
  width: 44px;
  border: none;
  background: #fff;
  color: #667085;
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;                   /* no double-tap zoom on the buttons */
  transition: background .12s ease, color .12s ease;
}
.qty-minus:hover,
.qty-plus:hover { background: #f3f8f1; color: var(--green-cart-dark); }
.qty-minus:active,
.qty-plus:active { background: #e7efe4; }
.qty-minus:focus-visible,
.qty-plus:focus-visible { outline: 2px solid var(--green-cart); outline-offset: -2px; }
.qty-minus[disabled],
.qty-plus[disabled] { color: #cbd2da; cursor: not-allowed; background: #fff; }
.qty-minus { border-right: 1px solid #eceff3; }
.qty-plus  { border-left:  1px solid #eceff3; }

.qtyinput {
  width: 56px;
  text-align: center;
  border: none;
  outline: none;
  background: transparent;
  font-size: 15px;
  font-weight: 700;
  color: #1d2939;
  -moz-appearance: textfield;
  appearance: textfield;
}
.qtyinput::-webkit-inner-spin-button,
.qtyinput::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

/* In-stock / on-order indicator under the stepper */
.qty-stockinfo {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.3;
  color: #667085;
}
.qty-stockinfo { display: flex; align-items: center; gap: 5px; }
.qty-stockinfo .icon { flex: 0 0 auto; width: 14px; height: 14px; }   /* sprite 'package' (was a 📦 emoji) */
.qty-stockinfo.over { color: #B38C00; }          /* amber when part of the qty is on backorder */
.qty-stockinfo:not(.has-msg) { display: none; }  /* nothing shown unless there's an availability split */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}


/* → cart / quote / puncher styles: css/components.client.css.php (customer-only) */
/* ── .cta button family ─────────────────────────────────────────────────────
   Shared commerce-button styles. These MUST be global: the puncher (puncher.php)
   and the quick-preview modal (preview.php) both use .cta/.cta--primary and open
   on the product page and category listing — pages whose per-view CSS is NOT
   compte.css. Previously these lived in vues/compte/compte.css, so the puncher's
   "Ajouter" button rendered unstyled (no green CTA) everywhere except /compte. */
.cta {
  display: flex; align-items: center; justify-content: center;
  width: 100%; padding: var(--space-2);
  font-size: 14px; font-weight: 700; cursor: pointer; text-decoration: none;
  border: 1px solid transparent;
  transition: background .15s ease, box-shadow .15s ease, transform .1s ease;
}
/* Green fill = the commerce action. Base look (everywhere, incl. dense grids): a subtle green
   GRADIENT + glossy inset highlight for depth — calm, no glow. The elevated "hero" glow is the
   opt-in .btn-hero modifier below, reserved for singular CTAs. */
.cta--primary { background: linear-gradient(180deg, #57bd41, var(--green-cart)); color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22); }
.cta--primary:hover { background: linear-gradient(180deg, #5fc748, var(--green-cart-dark));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .26), 0 4px 14px rgba(0, 0, 0, .18); transform: translateY(-1px); }
.cta--outline { background: #fff; border-color: #d0d5dd; color: #1d2939; }
.cta--outline:hover { background: #f6f7f9; }
.cta--ghost { background: none; color: #144A8E; }
.cta--ghost:hover { background: #eef3fb; }

/* Product-page split CTA: the main "Ajouter au panier" + a dropdown toggle, both built on the
   shared green .cta--primary (unified with the devis "Commander" button — ditches the old
   .primary-button look). The two halves butt together as one control, with a hairline seam. */
.split-button .cta { width: auto; }                          /* override .cta width:100% inside the split */
.split-button__main { flex: 1 1 auto; }
.split-button .dropdown-toggle.cta {
  flex: 0 0 auto;
  padding-left: var(--space-2); padding-right: var(--space-2);
  border-left: 1px solid rgba(255, 255, 255, .35);           /* seam between main + toggle */
}
/* The halves join, so don't lift/shadow them independently on hover (that would misalign the seam) —
   keep just the .cta--primary background darken. */
.split-button .cta:hover { transform: none; box-shadow: none; }
.split-button .dropdown-toggle.cta .icon { width: 16px; height: 16px; }

/* ── Hero button modifier ────────────────────────────────────────────────────
   The elevated treatment for SINGULAR primary CTAs (product buybox, cart checkout, configurator
   finish/commit). Composes on ANY green fill (.cta--primary OR .primary-button) — adds a green
   glow + lift + focus ring, nothing else. Deliberately kept OFF dense grids (category tiles,
   puncher rows): many glowing buttons at once read as noise, not emphasis. */
.btn-hero { box-shadow: 0 3px 10px rgba(52, 129, 35, .40), inset 0 1px 0 rgba(255, 255, 255, .28);
  transition: transform .12s ease, box-shadow .12s ease, filter .12s ease; }
.btn-hero:hover { transform: translateY(-1px); filter: brightness(1.03);
  box-shadow: 0 6px 18px rgba(52, 129, 35, .55), inset 0 1px 0 rgba(255, 255, 255, .32); }
.btn-hero:active { transform: translateY(0); box-shadow: 0 3px 10px rgba(52, 129, 35, .40); }
.btn-hero:focus-visible { outline: none; box-shadow: 0 0 0 3px #fff, 0 0 0 5px var(--green-cart); }

/* The buybox's main add-to-cart IS a singular CTA → hero the whole split control (so the seam
   stays aligned we glow the container, not the halves). Scoped to .buybox → grids/preview calm. */
.buybox__actions .split-button { box-shadow: 0 3px 10px rgba(52, 129, 35, .38);
  transition: transform .12s ease, box-shadow .12s ease; }
.buybox__actions .split-button:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(52, 129, 35, .52); }

/* → customer-only: css/components.client.css.php */

/* → customer-only: css/components.client.css.php */

/* ── Login overlay (#loginscreen, rendered by compte.php; toggled by openLogin()/closeLogin()) ──
   Fullscreen overlay on the house grey gradient, with the logo and a centred white card. Deliberately STATIC:
   it only fades its own opacity in — the form never moves or resizes, so the browser's password-
   manager autofill dropdown anchors cleanly (the old partialModal tween made it glitch). Square,
   token-spaced; a border = control outline only (inputs/button). */
.loginoverlay {
  position: fixed; inset: 0; z-index: 200000;          /* above the fixed mobile header (100000) */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3); padding: var(--space-3);
  background: var(--grey-gradient);                      /* house grey gradient (tokens.css.php) */
  overflow-y: auto;                                      /* short phones / keyboard up: scroll, don't clip */
  animation: loginoverlay-in .15s ease-out;
}
.loginoverlay[hidden] { display: none; }
@keyframes loginoverlay-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .loginoverlay { animation: none; } }
body.login-open { overflow: hidden; }                    /* no page scroll behind the overlay */
/* html keeps `scrollbar-gutter: stable` (css.php) so modals don't shift the page; that reserved strip
   sits OUTSIDE the fixed overlay and showed the grey page background down the right edge. Paint the
   canvas in the gradient's end colour while the overlay is open so it blends into the overlay. */
html:has(body.login-open) { background: var(--grey-gradient-end); }

.loginoverlay__logo { width: 160px; max-width: 50vw; height: auto; flex: 0 0 auto; }
.loginoverlay__close {
  position: absolute; top: var(--space-2); right: var(--space-2);
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  border: 0; background: rgba(255,255,255,.18); color: #fff; cursor: pointer;
}
.loginoverlay__close:hover { background: rgba(255,255,255,.3); }
.loginoverlay__close .icon { width: 22px; height: 22px; }

.loginbox {
  width: 100%; max-width: 420px; flex: 0 0 auto;
  background: #fff; color: #1d2939;
  box-shadow: 0 12px 40px rgba(0,0,0,.28);
}
.loginbox__title {
  margin: 0; padding: var(--space-2) var(--space-3);
  background: var(--brand-gradient); color: #fff; font-size: 1.25rem;
}

.loginform {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3);
}

.loginfield { display: grid; gap: var(--space-1); }
.loginfield > label,
.logincheck { user-select: none; }
.loginfield > label { font-weight: 700; }

.loginform input[type=email],
.loginform input[type=text],
.loginform input[type=password] {
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-2);
  border: 1px solid #c3ccd2;
  background: #fff;
}
.loginform input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(0, 0, 17, .08);
}

/* Password field + eye toggle share one box; the toggle sits inside the outline. */
.loginpw { position: relative; display: block; }
.loginpw input { padding-right: 44px; }
.loginpw-toggle {
  position: absolute;
  top: 0; right: 0; height: 100%;
  width: 40px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  user-select: none;
}

.loginrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.logincheck { display: inline-flex; align-items: center; gap: var(--space-1); cursor: pointer; }
.loginforgot { color: var(--brand); }

/* Primary action — full-width, brand gradient, square. Hover/active/disabled
   states so the loading lock reads clearly. */
.loginsubmit {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  background: var(--brand-gradient);
  color: #fff;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: filter .15s ease, opacity .15s ease;
}
.loginsubmit:hover { filter: brightness(1.08); }
.loginsubmit:active { filter: brightness(.94); }
.loginsubmit:disabled { opacity: .6; cursor: progress; }

/* ALTCHA widget — square it off and match the input outline (css8 has no radii).
   The widget exposes these CSS custom properties for theming. */
.loginaltcha {
  --altcha-border-radius: 0;
  --altcha-border-width: 1px;
  --altcha-color-border: #c3ccd2;
  --altcha-max-width: 100%;
  display: block;
}

/* "Pas encore de compte ?" under the Connexion button (compte.php) → /inscription (the sign-up funnel). */
.loginsignup { margin: 0; text-align: center; font-size: 14px; color: #475467; }
.loginsignup a { color: var(--brand); font-weight: 700; white-space: nowrap; }
.loginsignup .icon { width: 15px; height: 15px; vertical-align: -2px; }
/* Result banners (success / error). */
.loginmsg { padding: var(--space-2); color: #fff; }
.loginmsg--ok  { background: #1b4a1b; }
.loginmsg--err { background: #aa1b1b; }
.loginmsg .icon { width: 1.1em; height: 1.1em; vertical-align: -0.2em; fill: none; stroke: currentColor; }

/* While a request is in flight: dim + block the fields, show a wait cursor. */
.loginform.is-loading input,
.loginform.is-loading .loginpw-toggle {
  pointer-events: none;
  opacity: .6;
}
.loginform.is-loading { cursor: progress; }

/* ── Mot de passe oublié (vues/mot-de-passe-oublie) ──────────────────────────────────────────────────────
   One white card (centred, 760px like its pagehead--narrow title): the form/state panel + a quiet side column
   (how it works / good to know / phone help). Reuses the login form parts (.loginform/.loginfield/.loginpw/
   .loginsubmit/.loginaltcha) so both screens stay one design. */
.pwr__card, .payl__card {
  max-width: 760px; margin: 0 auto; box-sizing: border-box;
  display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  background: #fff; box-shadow: var(--shadow-panel);
}
.pwr__main { min-width: 0; }
.pwr__main .loginform { padding: var(--space-4); }
.pwr__panel[hidden] { display: none; }
.pwr__panel:focus { outline: none; }
.pwr__lead { margin: 0; line-height: 1.5; color: #344054; }
.pwr__hint { margin: 0; font-size: 13px; line-height: 1.45; color: #667085; }
.pwr__link {
  justify-self: center; display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-1); border: 0; background: none; color: var(--brand); font: inherit; cursor: pointer;
}
.pwr__link:hover { text-decoration: underline; }
.pwr__link .icon { width: 16px; height: 16px; }
.loginsubmit .icon { width: 18px; height: 18px; vertical-align: -3px; margin-right: 4px; }

/* the account the new password is for */
.pwr__account { display: flex; gap: var(--space-2); align-items: center; margin: 0; padding: var(--space-2); background: #f5f7fa; word-break: break-all; }
.pwr__account > .icon { flex: 0 0 auto; width: 22px; height: 22px; opacity: .6; }
.pwr__account small { color: #667085; }

/* error / success line under a form */
.pwr__msg:empty { display: none; }
.pwr__note { display: flex; gap: var(--space-1); align-items: flex-start; margin: 0; padding: var(--space-2); line-height: 1.4; }
.pwr__note .icon { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
.pwr__note--err { background: #fef3f2; color: #b42318; }
.pwr__note--ok  { background: #ecfdf3; color: #067647; }
.pwr__note a { color: inherit; font-weight: 700; }

/* Règlement par carte (vues/reglement) — the same card + side column as the password page (shared rules above) */
.payl__main { min-width: 0; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.payl__doc { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2); }
.payl__who, .payl__kind { display: flex; gap: var(--space-1); align-items: flex-start; }
.payl__who .icon, .payl__kind .icon { flex: 0 0 auto; width: 20px; height: 20px; margin-top: 1px; color: var(--brand); }
.payl__who span { display: flex; flex-direction: column; }
.payl__who small { color: #667085; }
.payl__kind { font-weight: 700; color: #344054; }
.payl__amounts { display: flex; flex-direction: column; }
.payl__row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); padding: var(--space-1) 0; border-bottom: 1px solid #eef0f3; color: #475467; }
.payl__row small { display: block; font-size: 12px; color: #98a2b3; }
.payl__row b { white-space: nowrap; font-variant-numeric: tabular-nums; color: #1d2939; }
.payl__row.is-total { border-bottom: 0; padding-top: var(--space-2); font-size: 1.15rem; color: #1d2939; }
.payl__state { display: flex; gap: var(--space-2); align-items: flex-start; padding: var(--space-2); background: #f5f7fa; color: #475467; line-height: 1.4; }
.payl__state > .icon { flex: 0 0 auto; width: 22px; height: 22px; margin-top: 1px; color: var(--brand); }
.payl__state > div { display: flex; flex-direction: column; gap: 2px; }
.payl__state b { color: #1d2939; }
.payl__state.is-ok { background: #ecfdf3; } .payl__state.is-ok > .icon { color: #067647; }
.payl__state.is-ko { background: #fef3f2; } .payl__state.is-ko > .icon { color: #b42318; }
.payl__pay { margin: 0; }
.payl__btn { width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1); }
.payl__btn .icon { width: 18px; height: 18px; }

/* "this link no longer works" banner, above the request form */
.pwr__alert { display: flex; gap: var(--space-2); align-items: flex-start; margin: var(--space-4) var(--space-4) 0; padding: var(--space-2) var(--space-3); background: #fffaeb; color: #93370d; border-left: 4px solid #f79009; }
.pwr__alert p { margin: 0; line-height: 1.45; }
.pwr__alert .icon { flex: 0 0 auto; width: 20px; height: 20px; margin-top: 1px; }

/* strength meter: 4 segments, coloured up to the level */
.pwr-strength { display: grid; gap: 6px; margin-top: calc(-1 * var(--space-2)); }   /* hugs the field above */
.pwr-meter { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.pwr-meter i { height: 6px; background: #e4e7ec; transition: background-color .2s ease; }
.pwr-meter[data-level="1"] i:nth-child(-n+1) { background: #d92d20; }
.pwr-meter[data-level="2"] i:nth-child(-n+2) { background: #f79009; }
.pwr-meter[data-level="3"] i:nth-child(-n+3) { background: var(--green-cart); }
.pwr-meter[data-level="4"] i { background: var(--green-cart-dark); }
.pwr-meter__label { margin: 0; font-size: 13px; color: #667085; }

.pwr-rules { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; font-size: 14px; color: #667085; }
.pwr-rules li { display: flex; align-items: center; gap: var(--space-1); transition: color .2s ease; }
.pwr-rules .icon { width: 16px; height: 16px; opacity: .35; transition: opacity .2s ease; }
.pwr-rules li.is-ok { color: var(--green-cart-dark); }
.pwr-rules li.is-ok .icon { opacity: 1; }

/* sent / done states */
.pwr__sent, .pwr__done { display: grid; gap: var(--space-3); padding: var(--space-4); justify-items: start; }
.pwr__sent[hidden], .pwr__done[hidden] { display: none; }
.pwr__sent h2, .pwr__done h2 { margin: 0; font-size: 1.35rem; }
.pwr__sent p, .pwr__done p { margin: 0; line-height: 1.5; }
.pwr__badge { display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; background: var(--brand-gradient); color: #fff; }
.pwr__badge .icon { width: 28px; height: 28px; }
.pwr__badge--ok { background: var(--green-cart); }
.pwr__sent:not([hidden]) .pwr__badge, .pwr__done:not([hidden]) .pwr__badge { animation: pwr-pop .35s cubic-bezier(.3, 1.5, .5, 1) both; }
@keyframes pwr-pop { from { transform: scale(.6); opacity: 0; } to { transform: none; opacity: 1; } }
.pwr__tips { margin: 0; padding-left: 1.2em; display: grid; gap: 4px; font-size: 14px; color: #475467; line-height: 1.45; }
.pwr__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); width: 100%; }
.pwr__btn { width: auto; display: inline-block; text-decoration: none; text-align: center; }
.pwr__btn2 {
  display: inline-flex; align-items: center; gap: var(--space-1); padding: var(--space-2) var(--space-3);
  border: 1px solid #c3ccd2; background: #fff; color: #1d2939; font: inherit; font-weight: 700; cursor: pointer;
}
.pwr__btn2 .icon { width: 16px; height: 16px; }
.pwr__btn2:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); }
.pwr__btn2:disabled { opacity: .55; cursor: default; font-variant-numeric: tabular-nums; }

/* side column */
.pwr__side, .payl__side { padding: var(--space-4) var(--space-3); background: #f5f7fa; font-size: 14px; color: #475467; line-height: 1.45; display: flex; flex-direction: column; gap: var(--space-3); }
.pwr__side h2, .payl__side h2 { display: flex; align-items: center; gap: var(--space-1); margin: 0; font-size: 1rem; color: #1d2939; }
.pwr__side h2 .icon, .payl__side h2 .icon { width: 20px; height: 20px; color: var(--brand); }
.pwr__side ul, .payl__side ul { margin: 0; padding-left: 1.2em; display: grid; gap: var(--space-1); }
.pwr__steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); counter-reset: pwr; }
.pwr__steps li { display: grid; grid-template-columns: 28px 1fr; column-gap: var(--space-2); counter-increment: pwr; }
.pwr__steps li::before { content: counter(pwr); grid-row: span 2; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; background: var(--brand-gradient); color: #fff; font-weight: 700; }
.pwr__steps b { color: #1d2939; }
.pwr__help, .payl__help { display: flex; gap: var(--space-1); align-items: flex-start; margin: auto 0 0; padding-top: var(--space-3); border-top: 1px solid #e4e7ec; }
.pwr__help .icon, .payl__help .icon { flex: 0 0 auto; width: 16px; height: 16px; margin-top: 2px; }
.pwr__help a, .payl__help a { color: var(--brand); white-space: nowrap; }

@media (max-width: 760px) {
  .pwr__card, .payl__card { grid-template-columns: 1fr; }
  .pwr__main .loginform, .pwr__sent, .pwr__done { padding: var(--space-3); }
  .pwr__alert { margin: var(--space-3) var(--space-3) 0; }
  .pwr__side, .payl__side { padding: var(--space-3); }
  .payl__main { padding: var(--space-3); }
  .pwr__btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .pwr__badge { animation: none !important; }
}

/* ── Product quick-preview modal (handlers/produit/preview.php) ──────────────
   Opened by the eye icon on devis lines; image + key info + link to full fiche. */
#modal-preview .treact-modal { width: 620px; }   /* capped by treact's max-width:90% */

.preview { display: grid; grid-template-columns: 240px 1fr; gap: var(--space-3); align-items: start; }
/* petite_image8() hard-codes an inline width/height in px → !important to make it
   fill (not overflow) its grid column, so the column gutter is actually visible. */
.preview__media img { width: 100% !important; height: auto !important; display: block; border: 1px solid #e3e3e3; }
.preview__brand { margin-bottom: var(--space-1); }
.preview__brandlogo { max-height: 28px; max-width: 130px; width: auto; height: auto; display: block; }
.preview__ref { font-weight: 700; font-size: 1.1em; margin-bottom: var(--space-2); }
.preview__features { margin: 0 0 var(--space-2); padding-left: var(--space-3); }
.preview__features li { margin-bottom: var(--space-0); line-height: 1.4; }
.preview__designation { color: #333; font-size: .92em; line-height: 1.5; margin: 0 0 var(--space-2); }
.preview__meta { margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid #eee; display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.preview__price { font-size: 1.15em; }
.preview__full { display: inline-flex; align-items: center; gap: var(--space-1); margin-top: var(--space-3); font-weight: 600; }

@media (max-width: 560px) {
  .preview { grid-template-columns: 1fr; }
  .preview__media { max-width: 220px; margin: 0 auto; }
}

/* Eye affordance next to each devis line's reference. */
.row-preview { background: none; border: none; padding: 0 var(--space-0); cursor: pointer; color: #9aa3ab; vertical-align: middle; line-height: 0; }
.row-preview:hover { color: var(--brand); }
.row-preview .icon { width: 1.15em; height: 1.15em; }

/* ── Product-preview tabs (lib/preview.php: Description / Téléchargements / Associés) ── */
.preview-tabs { margin-top: var(--space-3); }
.preview-tabbar { display: flex; gap: var(--space-1); border-bottom: 1px solid #e0e0e0; }
.preview-tab {
  background: none; border: none; border-bottom: 2px solid transparent;
  margin-bottom: -1px; padding: var(--space-1) var(--space-2);
  font: inherit; font-weight: 600; font-size: .95em; color: #667085; cursor: pointer;
}
.preview-tab:hover { color: #1d2939; }
.preview-tab.active { color: var(--brand); border-bottom-color: var(--brand); }

.preview-tabpanel { padding-top: var(--space-3); max-height: 320px; overflow-y: auto; }
.preview-empty { color: #888; font-style: italic; margin: 0; }

/* Téléchargements */
.preview-dls { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.preview-dl { width: 116px; text-align: center; line-height: 1.2; }
.preview-dl a { text-decoration: none; color: inherit; }
.preview-dl__thumb { height: 110px; max-width: 100%; }
.preview-dl__name { font-size: 12px; word-break: break-word; }
.preview-dl__note { font-size: 11px; color: #717171; }

/* Associés */
.preview-assoc { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-2); }
.preview-assoc__card { display: block; border: 1px solid #eee; text-decoration: none; color: inherit; }
.preview-assoc__card:hover { border-color: #cfd4da; text-decoration: none; }   /* kill the global a:hover underline — these are tiles, not links */
.preview-assoc__ref { line-height: 28px; font-weight: 700; text-align: center; }
.preview-assoc__img { padding: var(--space-2); text-align: center; }
.preview-assoc__desc { padding: var(--space-1) var(--space-2) var(--space-2); font-size: .85em; line-height: 1.3; }
.preview-assoc__note { grid-column: 1 / -1; color: #555; font-size: .9em; }
.preview-assoc__sub { grid-column: 1 / -1; font-weight: 700; font-size: .9em; color: #1f2937; padding-top: var(--space-1); border-bottom: 2px solid #eceef2; }

/* Context-aware CTA in the product preview (lib/preview.php → preview_cta_html).
   Green = the add-to-cart/devis convention (var(--green-cart)). */
.preview-cta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) 0; }
.preview-cta__btn {
  display: inline-flex; align-items: center; gap: var(--space-1);
  background: var(--green-cart); color: #fff; border: none;
  padding: var(--space-2) var(--space-3); font-weight: 700; cursor: pointer;
}
.preview-cta__btn:hover { background: var(--green-cart-dark); }
.preview-cta__btn .icon { width: 1.1em; height: 1.1em; }

/* → quick order + order tunnel: css/components.client.css.php (customer-only) */
/* ── Category browsing tiles (vues/categorie/categorie.0|1|2.php) — the catalogue's three levels. App/phone
   overrides live in the app section below (:root .cat2grid / .cat3grid / .cat3tile…). ── */
/* level 0 (/produits): one tile per family = photo · its ranges as icon rows (hover = preview swap) */
.cat1grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(370px, 1fr)); grid-gap: 20px; }
.cat1tile { min-height: 400px; grid-template-rows: 40px auto; display: grid; }
.cat1tile__body { display: grid; gap: 0; }
.cat1tile__cols { grid-template-columns: 1fr 1fr; }
.cat1tile__photo { max-width: 80%; }
.cat1tile__subs { padding: 5px; display: flex; flex-direction: column; gap: 5px; }
.catsub { height: 40px; padding-top: 5px; padding-bottom: 5px; display: grid; font-size: .8em; }
.catsub__row { display: grid; grid-template-columns: 40px auto; align-items: center; }
.catsub__ico { vertical-align: middle; }
/* level 1 (a family): one tile per range = photo | its sub-ranges */
.cat2grid { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.cat2tile { min-height: 300px; display: grid; grid-template-rows: 40px auto; }
.cat2tile__body { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 10px; font-size: .8em; margin-right: 10px; }
.cat2tile__photo { display: grid; height: 100%; align-content: center; border-right: 1px solid #eee; padding-right: 10px; }
.cat2tile__photo > div { align-self: center; }
.cat2tile__photo img { max-width: 100%; }
.cat2tile__links { padding-left: 5px; align-self: center; }
.cat3link { display: grid; grid-template-columns: 40px auto; align-items: center; margin-top: 1px; height: 40px; }
.cat3link__ico { justify-self: center; }
.cat3link__name { align-self: center; }
/* level 2 (a range): one tile per sub-range = title · photo · its level-4 chips */
.cat3page { padding-top: 20px; }
.cat3grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-rows: 1fr; grid-gap: 40px; }
.cat3tile { grid-template-rows: 40px 200px auto; }
.cat3tile__img { align-self: center; height: 150px; display: grid; }
.imagecat3 { max-width: 128px; max-height: 128px; }
.cat3tile__sub {
	background-color: #41434e; color: #fff; line-height: 200%;
	display: flex; align-items: flex-start; align-content: flex-start; flex-wrap: wrap; gap: 10px; font-size: .8em; justify-content: left;
}
.cat3tile__sub > div { background-color: #595C6E; padding: 5px; }

/* ── Reusable product tile (product_tile8 in fonctions.php) ─────────────────────────────────────
   Clickable image + ref bar + description, eye quick-preview (top-right), and an add row (mini qty
   stepper + [+|v] panier/devis split) on its OWN line at the bottom. Global so the tile drops onto
   any page; the CALLER supplies the grid container (e.g. #associescontent). The description row is
   1fr so, when the container stretches cards to equal height, the add row is pinned to the bottom
   and every tile's controls line up — no subgrid needed. */
.associe {
	position: relative; width: 168px; max-width: 100%; font-size: 12px;
	border: 1px solid #eee; background: #fff;
	display: grid; grid-template-rows: 28px 80px 1fr auto auto;   /* ref · img · desc(1fr) · meta · add */
}
/* rows: ref bar (on .gradient) · centred thumbnail · description (also the info-only variant card, produit.variantes.php) */
.associe__ref { line-height: 28px; font-weight: bold; }
.associe__img { margin: 10px; align-self: center; justify-self: center; }
.associe__desc { padding: 10px; line-height: 1; }
/* whole-tile click-through; the global a.fullclicker is an empty stub, so scope it here (.associe
   is position:relative and has no interactive children the overlay would swallow). */
.associe > a.fullclicker { position: absolute; inset: 0; z-index: 2; }
/* compact stock | price line, just above the add row */
.associe__meta {
	display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 6px;
	padding: 3px 8px; font-size: 11px; line-height: 1.25;
}
.associe__meta .associe__stock { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.associe__meta .associe__price { font-weight: 700; white-space: nowrap; }
/* eye preview: top-right of the ref bar, above the fullclicker overlay (z-index 2) */
.associe .row-preview {
	position: absolute; top: 0; right: 0; z-index: 3;
	display: inline-flex; align-items: center; justify-content: center;
	width: 28px; height: 28px; padding: 0;
	background: transparent; color: #fff; border: 0; cursor: pointer;
}
.associe .row-preview:hover { background: rgba(255, 255, 255, .2); }
.associe .row-preview .icon { width: 16px; height: 16px; }
/* add row: own bottom line; relative + z-index keep its controls clickable above the overlay */
.associe .row-addrow {
	position: relative; z-index: 3;
	display: flex; align-items: stretch; justify-content: center; gap: 4px;
	padding: 6px 4px; border-top: 1px solid #eee; background: #fff;
}
/* compact qty stepper — override the 44px touch-target default for the tile */
.associe .row-addrow .qty-stepper { height: 24px; }
.associe .row-addrow .qty-minus,
.associe .row-addrow .qty-plus { width: 18px; font-size: 14px; }
.associe .row-addrow .qtyinput { width: 24px; font-size: 11px; }
/* mini [+|v] split */
.associe .row-addsplit__btn {
	display: inline-flex; align-items: center; justify-content: center;
	height: 24px; width: 26px; padding: 0; border: 0; cursor: pointer;
	background: var(--green-cart); color: #fff;
}
.associe .row-addsplit__btn.dropdown-toggle { width: 18px; border-left: 1px solid rgba(255, 255, 255, .4); }
.associe .row-addsplit__btn:hover { background: var(--green-cart-dark); box-shadow: none; }
.associe .row-addsplit__btn .icon { width: 14px; height: 14px; }
.associe .row-addsplit__btn.dropdown-toggle .icon { width: 12px; height: 12px; }


/* → customer-only: css/components.client.css.php */


/* One-line add controls on a listing card (blocproduit): [qty stepper] [Ajouter au Panier] [▼],
   all the same height. align-items:stretch makes the split-button match the stepper's height; the
   stepper's value feeds the add/devis buttons via data-collect. */
.cardadd { display: flex; align-items: stretch; gap: 4px; margin: 4px 0; }
.cardadd__qty { flex: 0 0 auto; }
.cardadd__qty .qty-stepper { height: 30px; }
.cardadd__qty .qty-minus,
.cardadd__qty .qty-plus { width: 22px; font-size: 15px; }
.cardadd__qty .qtyinput { width: 30px; font-size: 12px; }
.cardadd__split { flex: 1 1 auto; min-width: 0; }                          /* take the rest of the row */
.cardadd__main { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cardadd__more { flex: 0 0 auto; }

/* → RMA cards: css/components.client.css.php (customer-only) */
/* ── .tile-grid — the house 2×2 hairline tile grid (same look as the Mon compte quick access on
   phones): tiles butt together, the 1px divider is the container background showing through a
   1px grid gap, one matching outer border. Icon on top, label underneath. Used by the Outils
   dropdown + /outils landing (lib/outils.php outils_tiles_html()). ── */
.tile-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: #d0d5dd; border: 1px solid #d0d5dd;
}
.tile-grid__item {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; min-height: 92px; padding: 18px 10px; box-sizing: border-box;
  background: #fff; color: #1d2939; text-align: center; text-decoration: none;
  font-size: 14px; font-weight: 600; line-height: 1.25;
}
.tile-grid__item:hover, .tile-grid__item:focus-visible { background: #f6f7f9; color: #1d2939; }
.tile-grid__item .icon { width: 26px; height: 26px; color: var(--brand); }
.tile-grid__desc { font-size: 12px; font-weight: 400; color: #667085; }

/* → customer-only: css/components.client.css.php */

/* → Outils disque dur: css/components.client.css.php (customer-only) */
/* ── .account-card — the shared white panel (white fill + panel shadow, no outline). Was defined in
   the per-view compte.css; promoted here because it's reused outside the account (Outils pages). ── */
.account-card { background: #fff; box-shadow: var(--shadow-panel); }
.account-panel { padding: var(--space-3); }   /* the padded white body under a section band (Mon compte views) */
/* → customer-only: css/components.client.css.php */
/* ── Stock indicator badge (fonctions.php stock_badge(), used by stock2025) — lucide icon + label,
   colour per state (same palette as the former emoji spans). ── */
.stock-ind { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; line-height: 1.2; }
.stock-ind .icon { width: 1.15em; height: 1.15em; flex: 0 0 auto; }
.stock-ind--ok   { color: #3d7a00; }   /* was #5bb300 (~2.7:1) — darkened for WCAG AA on white */
.stock-ind--repl { color: #935C13; }
.stock-ind--ko   { color: #333; }
.stock-ind--wait { color: #8a6a00; }   /* was #B38C00 (~3.2:1) */

/* → Outils + comparator: css/components.client.css.php (customer-only) */
/* ── Public rules that were interleaved with customer-only sections (moved back here on the 2026-11-12 split) ── */
.cfg-figure__img { max-width: 100%; }   /* configurator images (lib/configurateur.render.php); an author style may add to it */
.pfilter { font-size: 13px; cursor: pointer; white-space: nowrap; }
.pfilter--brand img { max-height: 20px; max-width: 60px; vertical-align: middle; }
/* Brand-coloured action (non-commerce: green stays reserved for add-to-cart). */
.cta--brand { background: var(--brand-gradient); color: #fff; border-color: var(--brand); }
.cta--brand:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, .18); }
/* #6 keyboard focus: a visible square brand ring on tiles (background tint alone was invisible). */
.tile-grid__item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.pagehead--narrow > .pagetitle { max-width: 760px; margin-left: auto; margin-right: auto; box-sizing: border-box; }
/* Article / news titles became real <h1>s (SEO): keep the old bar look — no UA heading margins/size. */
h1.titrearticle { margin: 20px 0 0; font-size: 20px; }
h1.titrenew { margin: 0; }
/* Product page: a full-width outline button under the buy box. */
#caracteristiques { display: flex; flex-direction: column; }   /* specs panel: the Comparer action pinned to its bottom edge */
.caract__body { padding: 10px; }
/* product texts (lib/siteedit.php site_text_html — same markup in and out of site edit mode) */
.prod-descourte { line-height: 2em; }
.prod-name { display: none; }   /* phones only: the name under the ref title (vues/produit/produit.images.php) */
.prod-designation { line-height: 1.8em; font-size: 90%; color: #333; }
.prod-ribbon { color: #fff; }   /* fallback; background + text colour (by contrast) inline, couleur_style8() */
/* info blocks (produits.hints, hints8_html): 2 a row under the photo, as on the old site; colours inline */
.prod-hints { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin: 4px 0 var(--space-1); }
.prod-hints__b { padding: 6px var(--space-1); font-weight: 700; font-size: 13px; text-align: center; line-height: 1.25; }
/* photo column header: brand logo · attribute icon */
#photosproduit { min-width: 0; margin-bottom: 20px; }
#photosproduit > h1 { margin: 0; }   /* the ref title (h1.h2.gradient): drop normalize's 0.67em so only the crumbs' gap sits above the card */
.prod-logos { width: 100%; display: flex; flex-direction: row; justify-content: space-around; align-items: center; padding-top: 10px; margin-bottom: 0; }
.prod-logo--wide { width: 100px; }
.prod-logo--tall { height: 40px; }
.prod-attrico { height: 20px; }
/* Téléchargements tab (vues/produit/produit.telechargements.php): one centred tile per file */
#contenutelechargement { display: flex; flex-wrap: wrap; background-color: #fff; }
.dl-item { line-height: 110%; margin: 20px; text-align: center; }
.dl-item__thumb { height: 128px; }
.dl-item__thumb--pdf { border: 1px solid #ccc; }
.dl-item__name { font-size: 12px; }
.dl-item__note { font-size: 11px; color: #717171; }
.caract__actions { margin-top: auto; padding: 0 10px 10px; display: flex; flex-wrap: wrap; gap: var(--space-1); }   /* Comparer + Partager side by side */
.caract__actions .cmp-toggle, .caract__actions .fav-toggle, .caract__actions .share-btn { width: auto; }
.share-btn[hidden] { display: none; }   /* compact secondary action, not a full-width bar */

/* → site edit mode: css/siteedit.css.php (edit mode only) */
/* Outils ▸ customers-only gate (lib/outils.php outils_gate_html) — also the Comparateur / La pause gate pages. The two page
   wrappers keep their bottom air here (public: the gate renders inside them for visitors). */
.outils-page { padding-bottom: var(--space-4, 32px); }
.cmp-page { margin-bottom: var(--space-4); }
.outils-gate { display: flex; gap: var(--space-3); align-items: flex-start; max-width: 720px; margin: 0 auto var(--space-3); padding: var(--space-3); }
.outils-gate__ico { flex: 0 0 auto; width: 52px; height: 52px; display: flex; align-items: center; justify-content: center; background: var(--brand-gradient); color: #fff; }
.outils-gate__ico .icon { width: 26px; height: 26px; }
.outils-gate h2 { margin: 0 0 6px; font-size: 20px; color: #1d2939; }
.outils-gate p { margin: 0 0 var(--space-2); color: #475467; }
.outils-gate__actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.outils-gate__actions .cta { width: auto; padding: var(--space-2) var(--space-3); }
.outils-gate__tel { display: inline-flex; align-items: center; gap: 6px; color: #475467; font-size: 14px; text-decoration: none; }
.outils-gate__tel:hover { color: var(--brand); }
.outils-gate__tel .icon { width: 16px; height: 16px; }
@media (max-width: 640px) { .outils-gate { flex-direction: column; } }


/* ── Login transitions (javascript.js closeLogin/loginSuccess + index.php #loginreturn) ───────────────────────
   Opening stays STATIC (autofill anchors on the inputs). Dismiss: swift fade + slight sink. Success: the form
   collapses away in the card while a green check draws itself → the page reloads under the overlay → the fresh
   page opens under #loginreturn, the SAME compact card, which lifts away. Reduced motion: all skipped (JS too). */
.loginoverlay.is-closing { animation: loginoverlay-out .18s ease-in forwards; }
.loginoverlay.is-closing .loginbox { animation: loginbox-sink .18s ease-in forwards; }
@keyframes loginoverlay-out { to { opacity: 0; } }
@keyframes loginbox-sink { to { transform: translateY(8px) scale(.97); } }

.loginoverlay.is-success .loginoverlay__close { visibility: hidden; }
.loginoverlay.is-success .loginform { animation: loginform-collapse .38s ease forwards; overflow: hidden; }
@keyframes loginform-collapse {
  0%   { opacity: 1; max-height: 720px; }
  40%  { opacity: 0; }
  100% { opacity: 0; max-height: 0; padding-top: 0; padding-bottom: 0; }
}
.loginsuccess { padding: 26px 20px 24px; text-align: center; }
.loginoverlay .loginsuccess { animation: loginsuccess-in .3s .22s ease-out both; }
@keyframes loginsuccess-in { from { opacity: 0; transform: translateY(6px); } }
.loginsuccess__check { width: 64px; height: 64px; display: block; margin: 0 auto 12px; fill: none; stroke: #3d7a00; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.loginsuccess__check circle { stroke-dasharray: 151; stroke-dashoffset: 0; }
.loginsuccess__check path { stroke-dasharray: 36; stroke-dashoffset: 0; stroke-width: 3.5; }
.loginoverlay .loginsuccess__check circle { animation: login-draw .5s .3s ease-out both; --len: 151; }
.loginoverlay .loginsuccess__check path { animation: login-draw .3s .72s ease-out both; --len: 36; }
@keyframes login-draw { from { stroke-dashoffset: var(--len); } to { stroke-dashoffset: 0; } }
.loginsuccess__hello { font-size: 20px; font-weight: 800; color: #1d2939; }
.loginsuccess__sub { margin-top: 4px; font-size: 13px; color: #667085; }

.loginreturn { position: fixed; inset: 0; z-index: 200001; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-3); padding: var(--space-3); background: var(--grey-gradient); }
.loginreturn[hidden] { display: none; }
.loginreturn .loginbox__title { margin: 0; padding: var(--space-2) var(--space-3); background: var(--brand-gradient); color: #fff; font-size: 1.25rem; font-weight: 700; }
.loginreturn.is-leaving { animation: loginoverlay-out .42s ease-in forwards; }
.loginreturn.is-leaving .loginbox { animation: loginbox-lift .42s ease-in forwards; }
@keyframes loginbox-lift { to { transform: translateY(-14px) scale(.98); } }
html.login-returning, html.login-returning body { overflow: hidden; }

#compteutilisateur.is-welcome { animation: acct-welcome 1.4s ease-out; }
@keyframes acct-welcome {
  0%   { box-shadow: 0 0 0 0 rgba(255,255,255,.75); }
  45%  { box-shadow: 0 0 0 12px rgba(255,255,255,0); }
  50%  { box-shadow: 0 0 0 0 rgba(255,255,255,.55); }
  100% { box-shadow: 0 0 0 12px rgba(255,255,255,0); }
}
@media (prefers-reduced-motion: reduce) {
  .loginoverlay.is-closing, .loginoverlay.is-closing .loginbox, .loginoverlay.is-success .loginform,
  .loginoverlay .loginsuccess, .loginoverlay .loginsuccess__check circle, .loginoverlay .loginsuccess__check path,
  .loginreturn.is-leaving, #compteutilisateur.is-welcome { animation: none; }
}

/* Cookies page ▸ audience-measurement opt-out switch (js/analytics.js) */
.matomo-optout { display: inline-flex; align-items: center; gap: 10px; margin-top: var(--space-1); font-weight: 700; cursor: pointer; }
.matomo-optout input { width: 20px; height: 20px; accent-color: var(--brand); }

/* Product page ▸ "Produits conseillés" / variants tile grids. On phones the old `50fr 50fr` columns couldn't shrink
   below the tile's fixed 168px (fr tracks keep their min-content) → 2×168 + gap overflowed the ~310px pane. Columns
   are now shrinkable and the tile fills its column; the pane pads less. Global (not produit.css): per-view CSS can
   be stale after a turbonav swap. */
.tiles-pane { padding: 20px; }
/* "Produits conseillés": GROUPS ("--" subtitle + its tiles, vues/produit/produit.associes.php) flow side by side and
   wrap — each group as wide as its tiles, so small groups share a row instead of each leaving a full row empty.
   Tiles in a row stretch to equal height so their add rows line up. */
#associescontent { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--space-4) var(--space-3); }
#associescontent .assoc-group { flex: 0 1 auto; min-width: 0; max-width: 100%; }
#associescontent .assoc-group__tiles { display: flex; flex-wrap: wrap; gap: 10px; }
#associescontent .associe-sub { margin: 0 0 var(--space-2); padding: 0 0 var(--space-1); font-size: 14px; font-weight: 700; color: #1f2937;
  background: var(--brand-gradient) left bottom / 100% 3px no-repeat; }   /* the site's brand gradient as the group's underline */
#associescontent .assoc-note { flex: 1 1 100%; contain: inline-size; }   /* a comment takes its group's width, never widens it (wrapper has no padding) */
@media (max-width: 960px) {
  .tiles-pane { padding: 12px; }
  #variantescontent { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  #associescontent .assoc-group { flex: 1 1 100%; }                 /* one group per row, its tiles two-up */
  #associescontent .assoc-group__tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #associescontent .assoc-note { grid-column: 1 / -1; }
  #associescontent .associe, #variantescontent .associe { width: 100%; }
}

/* → Factures + encours: css/components.client.css.php (customer-only) */
/* Product page: Caractéristiques + Stock & Commande column. Side by side with the photos (desktop) it shares the
   photo column's height 2:1; stacked on narrow screens there's nothing to match → each box sizes to its content
   (2fr/1fr there forced Caractéristiques to twice the cart box's height = a tall empty panel). */
#caracteristiquespanier { margin-bottom: 20px; display: grid; gap: 20px; grid-template-rows: 2fr 1fr; }
#ajtpanierdevis { grid-template-rows: 50px auto; }
@media (max-width: 960px) { #caracteristiquespanier { grid-template-rows: auto auto; } }

/* Category/search mobile filters: "Filtres" pill ⇄ bottom-sheet container transform (vues/categorie/categorie.js
   setFilters). While morphing, the sheet sits in place with no slide (the clip-path does the motion); the veil is
   the pill's gradient + label laid over the sheet, fading out as it grows (in as it shrinks back). */
@media only screen and (max-width: 960px) {
  body.filters-morphing #filtrespage { transition: none; transform: translateY(0); }
  body.filters-morphing #filters-toggle { visibility: hidden; }
  body.filters-measure #filters-toggle { display: inline-flex; }
  #filters-toggle { transition: transform .14s cubic-bezier(.3, 1.6, .6, 1); }   /* press squash: springs back with a hint of overshoot */
  #filters-toggle:active { transform: scale(.93); transition-duration: .08s; }
  .filters-veil { position: absolute; left: 0; right: 0; z-index: 5; background: var(--brand-gradient); pointer-events: none; }
  .filters-veil__label { position: absolute; display: flex; align-items: center; justify-content: center; gap: 6px;
                         color: #fff; white-space: nowrap; }   /* font copied from the pill in JS */
}

/* ── Agenda (lib/agenda.php, vues/agenda) — calendar + upcoming cards + event page; one hue per event type (--ag). ── */
.ag-page { margin-bottom: var(--space-4); }
.ag-cols { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--space-3); align-items: start; }
.ag-cal, .ag-up, .ag-mine { padding: var(--space-3); }
.ag-mine { margin-bottom: var(--space-3); border-top: 3px solid #12b76a; }
.ag-up h2, .ag-mine h2 { display: flex; align-items: center; gap: 8px; margin: 0 0 var(--space-2); font-size: 17px; }
.ag-mine h2 .icon { width: 18px; height: 18px; color: #12b76a; }
.ag-month__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-2); font-size: 17px; }
.ag-nav { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border: 1px solid #d0d5dd; color: #344054; }
.ag-nav:hover { border-color: var(--brand); color: var(--brand); }
.ag-nav .icon { width: 18px; height: 18px; }
.ag-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px; background: #eaecf0; border: 1px solid #eaecf0; }
.ag-dow { padding: 6px; background: #f9fafb; font-size: 11px; font-weight: 700; text-transform: uppercase; color: #667085; text-align: center; }
.ag-day { min-height: 84px; padding: 4px; background: #fff; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ag-day i { font-style: normal; font-size: 12px; color: #667085; }
.ag-day.is-out { background: #fafafa; }
.ag-day.is-today i { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; background: var(--brand-gradient); color: #fff; font-weight: 700; }
.ag-chip { display: block; padding: 2px 5px; font-size: 11px; font-weight: 600; line-height: 1.3; color: #101828; text-decoration: none;
  background: color-mix(in srgb, var(--ag) 12%, #fff); border-left: 3px solid var(--ag); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-chip:hover { background: color-mix(in srgb, var(--ag) 22%, #fff); }
.ag-chip .icon { width: 11px; height: 11px; margin-right: 2px; vertical-align: -1px; color: #027a48; }
.ag-cards { display: flex; flex-direction: column; gap: var(--space-1); }
.ag-card { display: grid; grid-template-columns: 52px minmax(0, 1fr) auto; gap: var(--space-2); align-items: center; padding: var(--space-1) var(--space-2);
  border-left: 4px solid var(--ag); background: #fff; box-shadow: var(--shadow-panel); color: inherit; text-decoration: none; }
.ag-card:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, .12); }
.ag-card__date { display: flex; flex-direction: column; align-items: center; font-size: 12px; color: #667085; }
.ag-card__date b { font-size: 22px; line-height: 1; color: #101828; }
.ag-card__main { display: flex; flex-direction: column; min-width: 0; }
.ag-card__main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-card__main small { color: #667085; }
.ag-card__type { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ag); }
.ag-card__side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; text-align: right; }
.ag-card__side small { color: #667085; white-space: nowrap; }
.ag-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; font-size: 11px; font-weight: 700; background: #f2f4f7; color: #344054; white-space: nowrap; }
.ag-badge .icon { width: 12px; height: 12px; }
.ag-badge--open { background: #ecfdf3; color: #027a48; }
.ag-badge--in   { background: #027a48; color: #fff; }
.ag-badge--full { background: #fef3f2; color: #b42318; }
.ag-note { margin: var(--space-2) 0 0; font-size: 13px; color: #667085; }
.ag-note .icon { width: 14px; height: 14px; vertical-align: -2px; }
/* event page */
.ag-event { margin-bottom: var(--space-4); }
.ag-event__title { display: flex; flex-direction: column; gap: 2px; border-left: 6px solid var(--ag); }
.ag-event__title h1 { margin: 0; font-size: inherit; }
.ag-event__type { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; opacity: .9; }
.ag-event__body { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--space-3); align-items: start; margin-top: var(--space-3); }
.ag-event__main { padding: var(--space-3); }
.ag-event__img { display: block; max-width: 100%; margin-bottom: var(--space-3); }
.ag-kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); margin: 0 0 var(--space-3); }
.ag-kv dt { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: #475467; }
.ag-kv dt .icon { width: 16px; height: 16px; color: var(--ag); }
.ag-kv dd { margin: 0; }
.ag-desc { line-height: 1.6; }
.ag-event__side { padding: var(--space-3); position: sticky; top: var(--space-3); }
.ag-act { display: flex; flex-direction: column; gap: var(--space-2); }
.ag-places { font-size: 20px; font-weight: 800; color: #027a48; }
.ag-places small { font-size: 13px; font-weight: 400; color: #667085; }
.ag-places.is-full { color: #b42318; }
.ag-booked { display: flex; align-items: center; gap: 8px; padding: var(--space-2); background: #ecfdf3; color: #027a48; font-weight: 700; }
.ag-booked .icon { width: 20px; height: 20px; }
.ag-closed { padding: var(--space-2); background: #f2f4f7; color: #475467; font-weight: 700; text-align: center; }
.ag-cancel { align-self: center; border: 0; background: none; color: #b42318; font-size: 13px; text-decoration: underline; cursor: pointer; }
.ag-act .cta { width: 100%; justify-content: center; }
@media (max-width: 960px) {
  .ag-cols, .ag-event__body { grid-template-columns: 1fr; }
  .ag-day { min-height: 44px; }
  .ag-chip { font-size: 0; height: 6px; padding: 0; border-left-width: 0; background: var(--ag); }   /* phone: a coloured tick per event; the list below carries the details */
  .ag-chip .icon { display: none; }
  .ag-event__side { position: static; order: -1; }
  .ag-card { grid-template-columns: 44px minmax(0, 1fr); }
  .ag-card__side { grid-column: 2; flex-direction: row; align-items: center; justify-content: flex-start; }
}
.ag-desc h3 { font-size: 16px; margin: var(--space-2) 0 var(--space-1); }
.ag-desc ul { margin: var(--space-1) 0; padding-left: 20px; }
.ag-desc p { margin: 0 0 var(--space-1); }

/* Affaire stage badge — colours keyed off compte_stage_label() classes (Mon compte overview, /recherche serial hits). */
.stage-badge { display: inline-block; text-align: center; padding: var(--space-0) var(--space-1); font-size: 11px; font-weight: 700; color: #fff; white-space: nowrap; }
.stage-devis { background:#6b7280; } .stage-order { background:#2a7b9b; }
.stage-ship { background:#b8860b; } .stage-invoice { background:#138a3e; } .stage-other { background:#999; }

/* ── App bottom tab bar (header.php #appbar, Capacitor app only) ──────────────────────────────────────────
   Phone/tablet widths only; wider screens keep the desktop header. In the app the header's burger + tiles
   give way to the bar, and the search bar pins to the top (safe-area aware). --appbar-h is what every other
   bottom-pinned element (cookie banner, compare tray, tool bar, filters pill) lifts itself by. */
/* Page title block (page_title8): the name as a sized <h1> + a smaller context line — replaces "Name — Context"
   h1s that fell back to the UA's 2em and wrapped into a heavy slab on phones. */
.pagetitle { margin: var(--space-3) 0 var(--space-4); padding: 14px 18px 15px; }
.pagetitle__main {
  margin: 0; font-size: clamp(22px, 1.2vw + 16px, 30px); line-height: 1.15; font-weight: 800;
  letter-spacing: -.01em; overflow-wrap: anywhere;
}
.pagetitle__main .icon { width: .85em; height: .85em; vertical-align: -.08em; margin-right: 2px; }
.pagetitle__sub { margin: 4px 0 0; font-size: 14px; font-weight: 600; line-height: 1.3; opacity: .85; }
@media (max-width: 960px) {
  .pagetitle { padding: 11px 14px 12px; margin: var(--space-2) 0 var(--space-3); }
  .pagetitle__main { font-size: 21px; }
  .pagetitle__sub { font-size: 13px; }
}
/* Utilities: drop an element's top / bottom margin (!important: they must beat page sheets loaded later, e.g.
   vues/categorie/categorie.css). On a page_head8() wrapper (variant arg) nobottommargin reaches its .pagetitle. */
.notopmargin { margin-top: 0 !important; }
.nobottommargin, .pagehead.nobottommargin > .pagetitle { margin-bottom: 0 !important; }

#appbar { display: none; }

/* App: no WebView tap highlight (Android paints a translucent blue box over the whole tapped element — a full
   product card, since the card is one big link). Replaced by our own press feedback on the big tap targets. */
.app-mode, .app-mode * { -webkit-tap-highlight-color: transparent; }
/* App: whole-card overlay links (a.fullclicker — the news rows are covered edge to edge by one) and images are not
   draggable: on Android a press-and-move that starts on a draggable link/image can become a link DRAG instead of a
   page scroll ("scrolling from a news row doesn't start"). No long-press link callout either. */
.app-mode a.fullclicker, .app-mode img { -webkit-user-drag: none; -webkit-touch-callout: none; }
.app-mode .fullclicker, .app-mode .navdiv, .app-mode .account-card { transition: filter .12s ease, transform .12s ease; }
/* :active bubbles to ancestors, so pressing a CONTROL inside a card (Ajouter au Panier, qty stepper, compare) must
   not press the whole card. */
.app-mode :is(.fullclicker, .navdiv, .account-card):active:not(:has(:is(button, input, select, label, .split-button, a:not(.fullclicker)):active)) {
  filter: brightness(.94); transform: scale(.99);
}
.catico { display: none; }
.crumb__ico { display: none; }
.newsexcerpt { display: none; }
.newsage { display: none; }       /* news age separators: app feed (below) + website grid (next rule) show them */
/* Website news grid: the age separators ("Ce mois-ci", "Il y a 2 à 3 mois", …) span the whole row — a small-caps label
   + hairline on the slate page background, the same shape as the app's feed (:root .newsage below). The 20px grid gap
   sits above AND below the row, so the bottom margin pulls the cards back up: 20px / label / 12px / cards. */
#news > .newsage {
  display: flex; align-items: center; gap: var(--space-3); grid-column: 1 / -1;
  margin: var(--space-2) 0 calc(-1 * var(--space-2)); padding: 0 2px;
  font-size: 13px; font-weight: 800; line-height: 1.2; text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,.8);
}
#news > .newsage::after { content: ''; flex: 1; height: 1px; background: rgba(255,255,255,.22); }
#news > .newsage:first-child { margin-top: 0; }
/* Showroom mode (app menu switch, html.showroom set from localStorage before first paint — index.php): EVERY catalogue
   price is hidden, to browse in front of an end client. Cards keep their layout (visibility), inline ones collapse. */
.showroom .blocproduitprix, .showroom .buybox__price, .showroom .associe__price, .showroom .cmp-prod__price,
.showroom .preview__price, .showroom .qo-price { visibility: hidden; }
.showroom .search_result_tarif, .showroom .puncher-row__price { display: none; }
.searchbar__menu, #appmenu, #appmenu-backdrop { display: none; }
/* Confirm sheet — ONE shared look for every "are you sure?" modal: text + [Annuler | red action] (or an inline
   preview's [Fermer | green cta]). Used by handlers/deconnexion/confirm.php, handlers/devis/supprimer_form.php,
   handlers/devis/apercu.php. Replaced the Zebra_Dialog confirms on 2026-11-13. */
#modal-logout .treact-modal, #modal-suppr-devis .treact-modal { width: 400px; }
#modal-apercu-devis .treact-modal { width: 640px; }
.confirm-sheet__txt { margin: 0 0 var(--space-4); line-height: 1.5; color: #344054; }
.confirm-sheet__actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.confirm-sheet__actions button, .confirm-sheet__actions .cta { width: auto; height: 46px; display: flex; align-items: center; justify-content: center; gap: 8px;
  font: inherit; font-size: 15px; font-weight: 700; border-radius: 0; cursor: pointer; }
.confirm-sheet__cancel { background: #fff; color: #344054; border: 1px solid #d0d5dd; }
.confirm-sheet__cancel:hover { background: #f2f4f7; color: #1d2939; }
.confirm-sheet__ok { background: #D92D20; color: #fff; border: 0; }   /* the destructive confirm = the one red button */
.confirm-sheet__ok:hover { background: #B42318; }
.confirm-sheet__ok .icon, .confirm-sheet__actions .cta .icon { width: 18px; height: 18px; }
.app-mode .compte-logout { display: none; }   /* the app's ONLY Déconnexion is the ☰ menu's pinned row */
/* The floating Filtres pill (z 99988) must never sit on top of an open modal (treact overlay z 10000). */
body:has(.treact-modal-overlay) #filters-toggle { display: none !important; }
/* Company info pages (/qui-sommes-nous, /contact — lib/infopages.php): the tenant's legacy HTML in a white panel. */
.infopage { padding: 16px 20px 20px; margin-bottom: var(--space-4); box-shadow: var(--shadow-panel); line-height: 1.55; color: #1d2939; }
.infopage > :first-child, .infopage > :first-child > :first-child { margin-top: 0; }
.infopage h2 { font-size: 17px; color: var(--brand); margin: 22px 0 8px; }
.infopage h3 { font-size: 15px; margin: 16px 0 6px; }
.infopage p { margin: 0 0 10px; }
.infopage table { width: 100%; border-collapse: collapse; margin: 4px 0 8px; font-size: 14px; }
.infopage th, .infopage td { padding: 7px 10px; border-top: 1px solid #eaecf0; vertical-align: top; text-align: left; }
.infopage th { width: 40%; color: #475467; font-weight: 600; }
.infopage a { color: var(--brand); font-weight: 600; }
.infopage img { max-width: 100%; height: auto; }
.infopage .text_center { text-align: center; }   /* news text preview — the app's news rows only (blocnews) */   /* breadcrumb root's grid icon — app path bar only (website keeps the word "Produits") */   /* category pictogram (lib/menu.php cat1_icon_html) — app catalogue only */
/* Product spec sheet ("Caractéristiques" tab, lib/tableau.php): label | value rows with hairlines, subtitles as
   small brand-coloured section rows, lists, a check for value-less features, badge strip, footnotes. */
.spec-pane { padding: 4px 0; }
.spec { width: 100%; border-collapse: collapse; font-size: 14px; line-height: 1.4; }
.spec th[scope='row'] { width: 40%; text-align: left; font-weight: 600; color: #475467; padding: 9px 12px; vertical-align: top; }
.spec td { padding: 9px 12px; color: #1d2939; vertical-align: top; overflow-wrap: anywhere; }
.spec tr.spec__alt > * { background: #f8f9fb; }   /* alternating shading guides the eye along a row (lib/tableau.php, per section) */
.spec__sub th { background: #f5f6f8; color: var(--brand); font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; text-align: left; padding: 12px 12px 7px; }
.spec__note td { color: #344054; }
.spec__foot td { font-size: 12px; color: #667085; font-style: italic; }
.spec__flag .icon { width: 18px; height: 18px; color: var(--green-cart); vertical-align: -3px; }
.spec__list, .spec__bullets { margin: 0; padding-left: 18px; }
.spec__intro { margin-bottom: 3px; }
.spec__list li + li, .spec__bullets li + li { margin-top: 3px; }
.spec__badgerow { display: flex; flex-wrap: wrap; gap: 12px 20px; padding: 4px 0; }
.spec__badge { display: flex; align-items: center; gap: 10px; margin: 0; flex: 1 1 240px; }
.spec__badge img { width: 56px; height: 56px; object-fit: contain; flex: none; }
.spec__badge figcaption b { display: block; font-size: 14px; color: #1d2939; }
.spec__badge figcaption span { display: block; font-size: 12px; color: #667085; line-height: 1.35; }
/* Wide screens: a long sheet (≥ 8 rows, lib/tableau.php adds .spec--2col) flows into TWO columns — half the height,
   no long empty band between a label and its value. Each row is a 2-cell grid that never splits across columns;
   subtitles, notes, badge strips and footnotes span both columns. */
@media (min-width: 1100px) {
  .spec.spec--2col, .spec--2col tbody { display: block; }
  .spec--2col tbody { columns: 2; column-gap: var(--space-4); column-rule: 1px solid #eaecf0; }
  .spec--2col tr { display: grid; grid-template-columns: minmax(120px, 38%) 1fr; break-inside: avoid; }
  .spec--2col th[scope='row'] { width: auto; }
  .spec--2col tr.spec__sub, .spec--2col tr.spec__note, .spec--2col tr.spec__badges, .spec--2col tr.spec__foot { display: block; column-span: all; }
  .spec--2col tr.spec__sub > *, .spec--2col tr.spec__note > *, .spec--2col tr.spec__badges > *, .spec--2col tr.spec__foot > * { display: block; }
}
@media (max-width: 600px) {
  .spec { font-size: 13px; }
  .spec th[scope='row'] { width: 42%; padding: 8px 10px; }
  .spec td { padding: 8px 10px; }
}

/* Product FAQ ("FAQ" tab, lib/faq.php): one native <details> per question — hairline-separated rows, chevron turns
   when open; answers indented under the question text. */
.faq { padding: 4px 0; font-size: 14px; line-height: 1.5; }
.faq__item + .faq__item { border-top: 1px solid #eaecf0; }
.faq__q { display: flex; align-items: flex-start; gap: 10px; padding: 12px; cursor: pointer; list-style: none; font-weight: 700; color: #1d2939; }
.faq__q::-webkit-details-marker { display: none; }
.faq__q:hover { color: var(--brand); }
.faq__q .icon { flex: none; width: 18px; height: 18px; margin-top: 1px; color: var(--brand); transition: transform .2s ease; transform: rotate(-90deg); }
.faq__item[open] > .faq__q .icon { transform: none; }
.faq__a { padding: 0 12px 14px 40px; color: #344054; overflow-wrap: anywhere; }
.faq__a--free { padding-left: 12px; }
.faq__a p { margin: 0; }
.faq__a p + p, .faq__a p + ol, .faq__a p + ul, .faq__a ol + p, .faq__a ul + p { margin-top: 6px; }
.faq__a ol, .faq__a ul { margin: 0; padding-left: 20px; }
.faq__a li + li { margin-top: 3px; }
.faq__a a { color: var(--brand); text-decoration: underline; }
.faq__img { display: block; max-width: 100%; height: auto; margin: 8px 0; }
@media (prefers-reduced-motion: reduce) { .faq__q .icon { transition: none; } }
@media (max-width: 600px) {
  .faq { font-size: 13px; }
  .faq__q { padding: 10px; }
  .faq__a { padding: 0 10px 12px 38px; }
}

/* Product tabs (producttabs8(), vues/produit/produit.js flips .is-active) — FOLDER tabs: the active tab is the same
   white surface as its panel (+ the brand-gradient cap), lifted 3px above the grey inactive ones, so "which tab is
   open" reads from the shape itself whatever the number of tabs. The white ::after patch hides the panel's shadow
   under the active tab → tab and content join seamlessly. */
.ptabs { margin-top: var(--space-3); }
.ptabs__bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 2px; }
.ptabs__tab {
  appearance: none; cursor: pointer; border: 0; margin: 0; position: relative;
  padding: var(--space-2) var(--space-3);
  background: #eceef2; color: #475467;
  font-size: 14px; font-weight: 700; line-height: 1;
  transition: background-color .15s ease, color .15s ease;
}
.ptabs__tab:hover { background: #e0e3e9; color: #1d2939; }
.ptabs__tab:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.ptabs__tab.is-active {
  z-index: 1; padding-top: calc(var(--space-2) + 3px);   /* the lift: taller, so it rises above its neighbours */
  background: var(--brand-gradient) top / 100% 3px no-repeat, #fff;
  color: var(--brand); box-shadow: var(--shadow-panel); cursor: default;
}
.ptabs__tab.is-active::after { content: ''; position: absolute; left: 0; right: 0; bottom: -4px; height: 4px; background: #fff; }   /* seam cover */
.ptabs__panels { position: relative; background: #fff; box-shadow: var(--shadow-panel); }
.ptabs__panel { display: none; }
.ptabs__panel.is-active { display: block; }
@media (prefers-reduced-motion: reduce) { .ptabs__tab { transition: none; } }

/* Product tabs on phones/tablets (website ≤960 AND the app): ONE line, never wrapped — tabs share the row when they
   fit (segmented look), otherwise the strip scrolls sideways with a fade on the side that has more (js/javascript.js
   toggles .is-more-start/.is-more-end and scrolls the active tab into view). 2-class selectors beat produit.css. */
@media (max-width: 960px) {
  .ptabs .ptabs__bar {
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; scroll-snap-type: x proximity;
  }
  .ptabs .ptabs__bar::-webkit-scrollbar { display: none; }
  /* the scroller clips overflow → give the active tab's seam cover room INSIDE the strip (pulled back by the margin) */
  .ptabs .ptabs__bar { position: relative; z-index: 1; padding-bottom: 4px; margin-bottom: -4px; }
  .ptabs .ptabs__tab { flex: 1 0 auto; white-space: nowrap; padding: 11px 10px; font-size: 12.5px; scroll-snap-align: start; }   /* 3 tabs fit a 390px phone; 4 scroll */
  .ptabs .ptabs__tab.is-active { padding-top: 14px; }   /* keep the folder lift (+3px) */
  .ptabs .ptabs__bar { --edge-max: 36px; --edge-a: 0; }   /* fade: see .edge-fade (animated) */
}

/* ── BRANDS (lib/brands.php) ─────────────────────────────────────────────────────────────────────────────────── */
/* Mega menu "Par Marque": brand tiles = LOGO ONLY (user: the logos are clear enough; name = aria-label + tooltip). */
.menubrands.gridcenter { justify-items: stretch; align-content: start; }   /* legacy gridcenter centred the tiles → uneven widths */
.menubrands .menubrand { box-sizing: border-box; width: 100%; display: grid; grid-template-columns: 1fr auto; grid-template-rows: 26px auto; align-items: center; gap: 0 6px;
  padding: 6px 10px; color: #1d2939; text-decoration: none; line-height: 1.2; border-right: 1px solid #eee; border-bottom: 1px solid #eee; }
.menubrands .menubrand:hover { background: #f6f7f9; }
.menubrand__logo { grid-column: 1 / -1; display: flex; align-items: center; }
.menubrand__logo img { max-width: 80px; max-height: 24px; }
.menubrand__name { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.menubrand__n { font-size: 11px; color: #667085; }
.menubrands .menubrand__name, .menubrands .menubrand__n { display: none !important; }
.menubrands .menubrand { grid-template-rows: 36px; justify-items: center; padding: 8px 10px; }
.menubrands .menubrand__logo img { max-width: min(96px, 100%); max-height: 30px; }
/* Tiles may shrink below their logo (narrow desktop): equal 0-min columns + a logo that scales — never spills out. */
.menubrands.g1fr1fr { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.menubrands .menubrand, .menubrand__logo { min-width: 0; }
.menubrand__logo { justify-content: center; }
.menuswitch, .menutabs, .menuallbrands { display: none; }   /* app catalogue only */
/* Quick text filter ("Filtre rapide") in the facet panel: useful on desktop (the list beside it updates live), but
   wherever the panel is a SHEET covering the list (app + responsive website ≤960px) typing seemed to do nothing.
   Hidden there — an accepted regression (user, 2026-10-03; may go on desktop too). The header search is the text filter. */
@media (max-width: 960px) { #filtrespage div:has(> #filtrerapide) { display: none; } }
.menubrands .menubrands__foot { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; gap: var(--space-1); line-height: 1.2; white-space: nowrap; }   /* the menu's 32px line-height made both wrap */
.menubrands .menubrands__all { margin-left: auto; padding: 8px 10px; font-size: 12px; font-weight: 700; color: var(--brand); text-decoration: none; }
/* "+ N marques" (lib/menu.php): the tiles past the cap stay folded until asked for; the app lists them all. */
.menubrands .menubrands__more { padding: 8px 10px; border: 0; background: none; font: inherit; font-size: 12px; font-weight: 700; color: #475467; cursor: pointer; }
.menubrands .menubrands__more:hover { color: var(--brand); }
body:not(.app-mode) .menubrands:not(.is-open) .menubrand--more { display: none; }
.menupanel > .menuproductgrid { align-content: start; }   /* rows keep their height when the brand column is the taller one (unfolded) */
/* Mega menu "Nos Services" (lib/menu.php menu_services8): the category's configurators + the shared services, as
   compact icon rows on the dark panel. Overrides the legacy .menuservices box (css.php: 200 % line-height). */
.menupanel__extras .menuservices { display: flex; flex-direction: column; align-self: start; gap: 2px; margin: var(--space-3) 0 0 var(--space-3); padding: 0 0 0 var(--space-3); line-height: 1.25; }
.menuservices .menuservices__h { margin: 0 0 var(--space-0); padding: 0 var(--space-1); font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: #a4a8b2; }
.menuservices .menuservice + .menuservices__h { margin-top: var(--space-2); }
.menuservices .menuservice { display: flex; align-items: center; gap: var(--space-1); padding: var(--space-1); color: #e4e6eb; font-size: 13px; text-decoration: none; }
.menuservices .menuservice:hover, .menuservices .menuservice:focus-visible { background: rgba(255,255,255,.08); color: #fff; }
.menuservices .menuservice svg { flex: none; width: 18px; height: 18px; color: var(--brand-2); }
.menuservices .menuservice span { min-width: 0; }
.menuservices .menuservice small { display: block; margin-top: 2px; font-size: 11.5px; color: #a4a8b2; }
.app-mode #menu .menubrands__more { display: none; }
/* A–Z brand list (lib/brands.php brands_az_html) — /marques page; restyled dark inside the app catalogue below */
.brandaz { background: #fff; box-shadow: var(--shadow-panel); }
.brandaz__letter { position: sticky; top: 0; z-index: 1; margin: 0; padding: 6px 14px; background: #f2f4f7; color: var(--brand); font-size: 13px; font-weight: 800; }
.brandaz__row { display: grid; grid-template-columns: 72px 1fr auto; align-items: center; gap: 12px; padding: 8px 14px; color: #1d2939; text-decoration: none; border-top: 1px solid #eaecf0; }
.brandaz__row:hover { background: #f9fafb; }
.brandaz__letter + .brandaz__row { border-top: 0; }
.brandaz__logo { display: flex; align-items: center; justify-content: center; height: 32px; }
.brandaz__logo img { max-width: 72px; max-height: 30px; }
.brandaz__name { font-weight: 600; font-size: 15px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brandaz__n { font-size: 12px; color: #667085; }
/* Brand page */
.marque-head { display: flex; align-items: center; gap: var(--space-3); padding: 12px 16px; margin-bottom: var(--space-3); box-shadow: var(--shadow-panel); flex-wrap: wrap; }
.marque-head__logo { max-width: 140px; max-height: 48px; flex: none; }
.marque-types { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }
.marque-type { padding: 6px 10px; background: #f2f4f7; color: #1d2939; font-size: 13px; font-weight: 600; text-decoration: none; white-space: nowrap; }
.marque-type b { color: #667085; font-weight: 700; margin-left: 2px; }
.marque-type:hover { background: #e4e7ec; }
.marque-head__hint { color: #475467; font-weight: 600; }
/* Type chooser (big brands): same language as the range tiles (.cat3tile) — gradient header holding the type name,
   white photo panel below with the product count tucked grey in its corner; "Tout voir" last.
   auto-FIT (not auto-fill): a brand with few types spreads its tiles over the full width instead of leaving empty columns */
.typegrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--space-2); }
.typetile { display: flex; flex-direction: column; min-width: 0; background: #fff; box-shadow: var(--shadow-panel); text-decoration: none; }
.typetile:hover { box-shadow: 0 6px 18px rgba(0,0,0,.14); }
.typetile__name { padding: 10px 12px; color: #fff; font-weight: 700; font-size: 15px; line-height: 1.25;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.typetile__img { position: relative; display: flex; align-items: center; justify-content: center; height: 124px; padding: 10px 12px; }
.typetile__img img { width: auto !important; height: auto !important; max-width: 100%; max-height: 96px; object-fit: contain; }
.typetile__img .icon { width: 44px; height: 44px; color: var(--brand); }
.typetile__n { position: absolute; right: 8px; bottom: 6px; font-size: 12px; font-weight: 700; color: #98a2b3; }
.typetile--all .typetile__img { background: #fdf3ee; }
.marque-back { display: inline-flex; align-items: center; gap: 4px; margin-bottom: 10px; color: #fff; font-weight: 700; text-decoration: none; }
.marque-back .icon { width: 18px; height: 18px; }
.marque-group { margin-bottom: var(--space-4); scroll-margin-top: 80px; }
.marque-group__title { margin: 0 0 10px; padding: 10px 14px; background: var(--brand-gradient); color: #fff; font-size: 17px; }
.marque-group__title small { font-size: 13px; opacity: .85; margin-left: 4px; }
.marque-group .listeproduits { margin-top: 0; }
.marque-group:not(:has(.productcard:not(.filteredout))) { display: none; }   /* every card of the type filtered out → no orphan heading */
@media (max-width: 960px) {
  /* app/mobile: the type chips = ONE scrolling row; groups on the 8px rhythm */
  .marque-head { padding: 10px; gap: 10px; margin-bottom: 0; }
  .marque-head__logo { max-width: 90px; max-height: 32px; }
  .marque-types { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .marque-types::-webkit-scrollbar { display: none; }
  :root .marque-page { display: flex; flex-direction: column; gap: var(--app-gap); }
  :root .marque-group { margin: 0; scroll-margin-top: calc(var(--appsearch-h) + 8px); }
  :root .typegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--app-gap); }
  :root .typetile__name { padding: 7px 8px; font-size: 13px; }
  :root .typetile__img { height: 96px; padding: 4px; }
  :root .typetile__img img { max-height: 88px; }
  :root .marque-list .marque-back { margin: 0 0 var(--app-gap); }
  :root .marque-group__title { margin: 0 0 var(--app-gap); font-size: 15px; padding: 9px 12px; }
  :root .brandaz__letter { top: 60px; }   /* responsive website: stick under the fixed #headercontainer (css.mobile.php) */
  .app-mode .brandaz__letter { top: var(--appsearch-h); }
  /* Catalogue ROOT of the drawer (app AND website ≤960 since 2026-11-12 — the website drawer showed the desktop panel
     squeezed: tiles, then the brand grid, then services, no switch): Catégories | Marques tabs; Marques = the A–Z list
     (address book) on the dark overlay. :root #menu outranks the website rules (css.mobile.php, css.php). */
  :root #menu .menutabs { display: grid; grid-template-columns: 1fr 1fr; margin: 0 0 16px; background: rgba(255,255,255,.08); }
  body.menu-panel-open #menu .menutabs { display: none; }
  :root #menu.show-allbrands .menuroot { display: none; }
  :root #menu.show-allbrands .menuallbrands { display: block; }
  body.menu-panel-open #menu .menuallbrands { display: none; }
  :root #menu .brandaz { background: none; box-shadow: none; }
  :root #menu .brandaz__letter { top: -20px;   /* app: = the overlay's padding-top: stick flush at its top edge */ background: #261e19; color: #FFCE54; padding: 8px 2px; border-bottom: 1px solid rgba(255,255,255,.12); }
  :root #menu .brandaz__row { grid-template-columns: 64px 1fr auto; padding: 8px 2px; color: #fff; border-top: 1px solid rgba(255,255,255,.06); }
  :root #menu .brandaz__row:active { background: rgba(255,255,255,.08); }
  :root #menu .brandaz__logo { background: #fff; height: 36px; padding: 0 4px; }
  :root #menu .brandaz__logo img { max-width: 56px; max-height: 28px; }
  :root #menu .brandaz__n { color: rgba(255,255,255,.55); }
  /* App catalogue: "Par gamme | Par marque" switch in each category panel */
  :root #menu .menuswitch { display: grid; grid-template-columns: 1fr 1fr; gap: 0; margin: 0 0 12px; background: rgba(255,255,255,.08); }
  :root #menu .menuswitch__btn { padding: 10px; border: 0; background: none; color: rgba(255,255,255,.7); font-weight: 700; font-size: 14px; }
  :root #menu .menuswitch__btn.is-on { background: var(--brand-gradient); color: #fff; }
  :root #menu .menupanel > .grid:not(.menuproductgrid) { display: none !important; }   /* brands/services block … */
  :root #menu .menupanelwrapper.show-marques .menuproductgrid { display: none !important; }
  :root #menu .menupanelwrapper.show-marques .menupanel > .grid:not(.menuproductgrid) { display: block !important; margin: 0; }   /* … shown in marques view */
  :root #menu .menupanelwrapper.show-marques .menuservices { display: none !important; }
  :root #menu .menubrands { display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); justify-items: stretch; gap: 8px; margin: 0; padding: 0; border: 0; background: none !important; line-height: 1.2; }
  :root #menu .menubrands .menubrand { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 14px 8px; min-height: 64px; width: 100%; box-sizing: border-box; background: #fff; border: 0; text-align: center; }
  :root #menu .menubrand__logo { height: 36px; justify-content: center; }
  :root #menu .menubrand__logo img { max-width: 100%; max-height: 36px; }
  :root #menu .menubrand__name { max-width: 100%; }
  :root:not(.app-mode) #menu .brandaz__letter { top: 0; }   /* website drawer: the drawer itself scrolls (padding-top under the fixed bar) */
  :root:not(.app-mode) #menu .menubrands .menubrand--more { display: flex; }   /* the switch's brand view lists them all, like the app */
  :root:not(.app-mode) #menu .menubrands__more { display: none; }             /* … so no "+ N marques" (website drawer; the app rule is above the block) */
  :root:not(.app-mode) #menu .menuallbrands { padding: 0 14px; }               /* same gutters as the panels (.menupanelwrapper) */
}

/* ── PRICES IN RED — one token everywhere a price is shown (cards, product page, preview, related, comparator, search,
   quick order, cart lines/subtotals/total, header cart preview). New price element? use color: var(--price). ── */
.blocproduitprix, .buybox__price, .associe__meta .associe__price, .cmp-prod__price, .preview__price, .qo-price,
.puncher-row__price b, #search_result .search_result_tarif, .cart-row__pu, .cart-row__sub, .cart-total b, .cartprev-row__sub { color: var(--price); }

/* Product price in "Stock & Commande", first in the buybox row: same red/bold as the listing cards (.blocproduitprix).
   Global sheet (always ?rand-busted), not produit.css, which turbonav injects without the buster. */
.buybox__price { flex: 0 0 auto; font-size: 1.6rem; font-weight: 800; line-height: 1; white-space: nowrap; }
@media (max-width: 960px) {
  /* --app-gap = THE app rhythm: side gutters, gaps between cards, and the vertical gaps search bar → path bar →
     title → content all use it, so the screen reads as one grid. */
  .app-mode { --appbar-h: calc(58px + env(safe-area-inset-bottom)); --appsearch-h: calc(62px + env(safe-area-inset-top)); --app-gap: 8px; }
  .app-mode body { padding-top: var(--appsearch-h); padding-bottom: var(--appbar-h); }
  .app-mode #main { display: none; }                         /* burger, logo, Compte/Panier/Outils tiles → #appbar */
  .app-mode .searchbar {                                     /* fixed, not sticky: it lives inside the short <header> */
    position: fixed; top: 0; left: 0; right: 0; z-index: 9998; margin-top: 0;   /* its results drop over the catalogue (9997) */
    box-sizing: border-box; height: var(--appsearch-h);
    padding: calc(9px + env(safe-area-inset-top)) 0 9px;
    background: var(--brand-gradient);
  }
  /* keep it pinned while the results dropdown is open: the website spotlight rule (css.php
     `body.search-open .searchbar {position:relative}`) out-ranks the rule above and dropped the bar back into the
     flow, below the reserved --appsearch-h → an empty band on top. */
  .app-mode body.search-open .searchbar { position: fixed; z-index: 9998; }
  /* Search results: FULL WIDTH on the 8px gutters (not the field's width, which starts after the ☰) right under the
     field, down to the tab bar — its product grid (minmax(180px) inline in lib/search.php) then shows 2 columns
     of mini tiles on a phone, 4 in landscape. */
  .app-mode #search_result {
    position: fixed; left: calc(var(--rail-w, 0px) + 8px); right: 8px; top: calc(var(--appsearch-h) - 13px);   /* flush under the field */
    max-height: calc(100dvh - var(--appsearch-h) - var(--appbar-h) + 9px); box-sizing: border-box; padding: 8px;
  }
  :root #search_result div:has(> .search_result_li_produit) { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }   /* 2 cols (minmax(180px) inline fits 1); website ≤960 too since 2026-11-12 */
  :root #search_result .search_result_li_produit .search_result_image { height: 60px !important; }
  :root #search_result .search_result_li_produit .search_result_image img { max-height: 60px; width: auto; height: auto; }
  /* Website: the dropdown stays absolute under the in-flow field (css.php) but runs down to the bottom of the visible
     viewport (was max-height 70vh: ~150px of page showed under it, and it lost more to the keyboard). The field's bottom
     sits ~108px under the top on a phone; --kb = keyboard height (js/app-mode.js, 0 on desktop browsers). */
  :root:not(.app-mode) #search_result { max-height: calc(100dvh - var(--kb, 0px) - 116px); padding: 8px; }

  #appbar {
    display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; align-items: end;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 9998;   /* app layer order: see #site-header note below */
    height: var(--appbar-h); padding: 0 4px env(safe-area-inset-bottom);
    background: #261e19; box-shadow: 0 -4px 18px rgba(0,0,0,.3);
  }
  #appbar > a, #appbar > button {
    position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    height: 58px; padding: 0; border: 0; background: none; cursor: pointer;
    color: rgba(255,255,255,.62); font-family: inherit; font-size: 11.5px; font-weight: 600; line-height: 1; text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }
  #appbar .icon { width: 24px; height: 24px; }
  #appbar > .is-active { color: #fff; }
  #appbar > .is-active::before {                              /* active marker: brand bar on the top edge */
    content: ''; position: absolute; top: 0; left: 22%; right: 22%; height: 3px; background: var(--brand);
  }
  #appbar > a:active, #appbar > button:active { opacity: .6; }
  #appbar > :focus-visible { outline: 2px solid var(--brand); outline-offset: -4px; }

  /* Centre tab = home: the tenant's mark in a round bump of the bar itself (same fill as the bar — the PWA icon's
     own background is #261e19 too — and no shadow), so the bar seems to swell around it. Round on purpose
     (user's call), an exception to css8's square rule. */
  #appbar .appbar__home { justify-content: flex-end; padding-bottom: 10px; }   /* label baseline = the centred tabs' */
  #appbar .appbar__home img {
    width: 50px; height: 50px; margin: 0 0 -2px; display: block; box-sizing: border-box;   /* a gentle bump: rises ~14px above the bar (was 26) — the rise = logo + gap + label stacked from the bottom */
    padding: 4px; border-radius: 50%; background: #261e19;
  }
  #appbar .appbar__home.is-active::before { display: none; }  /* the raised mark is its own marker */

  .appbar__badge {
    position: absolute; top: 6px; left: calc(50% + 6px); min-width: 18px; height: 18px; padding: 0 5px;
    display: flex; align-items: center; justify-content: center; box-sizing: border-box;
    background: var(--green-cart); color: #fff; font-size: 11px; font-weight: 700; line-height: 1;
  }
  .appbar__badge[data-zero] { display: none; }
  /* Connected = a green status dot on the Compte icon (same cue as the website header tile's green corner). */
  #appbar .appbar__ico { position: relative; display: flex; }
  #appbar .appbar__online {
    position: absolute; right: -3px; bottom: -1px; width: 10px; height: 10px; box-sizing: content-box;
    background: var(--green-cart); border: 2px solid #261e19; border-radius: 50%;
  }

  /* Catalogue (Catalogue tab → #navtoggle → body.nav-open): FULLSCREEN between the search bar and the tab bar,
     fading in, instead of the website's side drawer. Top categories = a tile grid of pictograms on their colour;
     tapping one drills into its panel (existing showMenuPanel), whose title is the back button. */
  .app-mode #nav-backdrop { display: none; }
  .app-mode #menu.content {
    top: var(--appsearch-h); bottom: var(--appbar-h); left: 0; right: 0; width: auto; height: auto;
    padding: 20px 16px 28px; box-shadow: none; background: #261e19; z-index: 9997;   /* just under the tab bar (its bump overlaps) */
    opacity: 0; visibility: hidden; transform: translateY(10px);
    transition: opacity .2s ease, transform .2s ease, visibility 0s linear .2s;
  }
  .app-mode body.nav-open #menu.content {
    opacity: 1; visibility: visible; transform: none;
    transition: opacity .2s ease, transform .2s ease, visibility 0s;
  }
  .app-mode #menu .menuroot { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px 10px; }
  .app-mode body.menu-panel-open #menu .menuroot { display: none; }
  .app-mode #menu #nosproduits { display: none; }   /* website cascade flyout — the tile grid replaces it */
  .app-mode #menu .menuitem1 {
    display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 8px;
    min-height: 0; height: auto; padding: 0; border: 0; background: none; box-shadow: none;
    font-size: 13px; font-weight: 600; line-height: 1.2; text-align: center; color: #fff;
    -webkit-tap-highlight-color: transparent;
  }
  .app-mode #menu .menuitem1:active .catico { transform: scale(.94); }
  .app-mode #menu .catico {
    display: flex; align-items: center; justify-content: center; flex: none;
    width: 68px; height: 68px; background: var(--cat, var(--brand)); color: var(--cat-ink, #fff);
    transition: transform .12s ease;
  }
  .app-mode #menu .catico .icon { width: 34px; height: 34px; }
  .app-mode #menu .catico img { max-width: 70%; max-height: 70%; }
  .app-mode #menu .catico b { font-size: 28px; }
  /* drill-down panel: its title (‹ back) carries the category's pictogram */
  .app-mode #menu .menupanelwrapper { padding: 0; background: none; }
  .app-mode #menu .menutitrepanel { gap: 12px; padding: 0 0 14px; }
  .app-mode #menu .menutitrepanel .catico { width: 40px; height: 40px; }
  .app-mode #menu .menutitrepanel .catico .icon { width: 22px; height: 22px; }

  /* Product lists (categorie / recherche): a DENSE app grid — 2 columns on phones, 3 from 600px — with a compact
     blocproduit card: one-line ref, smaller photo, stock|price on one row, add row = [– 1 +][cart][▼] (label hidden,
     aria-label kept), 2-line description. The website keeps its roomy cards. */
  /* categorie.1 (top category): its sub-category grid follows the title directly inside the same .content → give it
     the app gap, and pull it to the 8px gutters like the title above (inline gap:20px → !important). */
  :root .cat2grid { gap: var(--app-gap) !important; }
  /* Family page range tiles on phones (2026-11-10): the range photo was half the tile (155 × 246 px of white around a
     90 px picture) and squeezed the sub-range names into 110 px → a 96px photo column, the names get the width. */
  :root .cat2tile { min-height: 0; }
  :root .cat2tile__body { grid-template-columns: 96px minmax(0, 1fr); gap: 8px; margin-right: 0; font-size: 13px; }
  :root .cat2tile__photo { padding-right: 6px; }
  :root .cat2tile__photo img { max-height: 96px; width: auto; }
  :root .cat2tile__links { padding-left: 0; }
  :root .cat3link { height: auto; min-height: 36px; grid-template-columns: 36px minmax(0, 1fr); column-gap: 6px; }
  :root .cat3link__ico img { max-width: 32px; max-height: 32px; }
  :root .cat3grid { gap: var(--app-gap) !important; grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  /* Range page sub-category tiles → mini tiles (an overview, like the product grid): 2-line title, 88px photo,
     the level-4 names as ONE ellipsised "a · b · c" line (they're labels, not links). Inline styles → !important. */
  :root .cat3tile { grid-template-rows: auto auto auto !important; position: relative; min-width: 0; }
  :root .cat3tile__title {
    padding: 7px 8px !important; font-size: 13px; line-height: 1.25; min-height: calc(2 * 1.25em);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  }
  :root .cat3tile__img { height: 96px !important; padding: 4px !important; place-items: center; }
  :root .cat3tile__img img { max-width: 100% !important; max-height: 88px !important; }
  :root .cat3tile__sub {
    display: block !important; padding: 6px 8px !important; font-size: 11px !important; line-height: 1.3 !important;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-height: 1.3em;
  }
  :root .cat3tile__sub > div { display: inline; padding: 0 !important; background: none !important; }
  :root .cat3tile__sub > div + div::before { content: ' · '; opacity: .6; }
  .app-mode #menu + .gradient { display: none; }                                       /* menu.php's 1px demarcation line */
  /* → Mon compte rhythm / Déconnexion / Outils rhythm: css/components.client.css.php */
  /* Actualités (home): same rhythm — 8px gutters, 8px between path bar / title / cards and between cards. */
  :root #news { gap: var(--app-gap); grid-template-columns: minmax(0, 1fr); }
  /* News feed = horizontal media rows: [108px square thumbnail | title (3 lines max) + hashtags]. The photo is
     cropped to fill (object-fit: cover); the whole row stays the card's click target (fullclicker). */
  :root .blocknews {
    /* Text column only in the grid; the thumbnail is pinned absolutely in a 108px left strip (as a grid item spanning
       the rows, Chrome sized the row from it → ~34px of dead space under the text). */
    display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto;
    grid-template-areas: "title" "excerpt" "tags"; align-content: start; padding-left: 108px !important;
    min-height: 108px; position: relative; transform: none !important;
  }
  :root .blocknews .newsimagecontainer { position: absolute; left: 0; top: 0; bottom: 0; width: 108px; height: auto; min-height: 0; padding: 0; display: block; background: #fff; }
  /* ABSOLUTE: an in-flow photo sized the row by its natural height (portrait → 147px rows with empty text space);
     out of flow it just covers whatever height the TEXT gives the row (min 108px). */
  :root .blocknews .imagenews { position: absolute; inset: 0; display: block; width: 100% !important; height: 100% !important; object-fit: cover; }
  :root .blocknews .titrenews {
    grid-area: title; height: auto; max-height: calc(3 * 1.3em); box-sizing: content-box; margin: 0 0 5px; padding: 10px 12px 0; font-size: 15px; line-height: 1.3; align-self: start;   /* content-box: the cap must not include the padding; gap below as MARGIN (with padding, the clipped next line peeked through it) */
    display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  }
  :root .blocknews:has(.newsexcerpt) .titrenews { -webkit-line-clamp: 2; max-height: calc(2 * 1.3em); }   /* title 2 lines + preview 3 lines */
  :root .blocknews .newsexcerpt {
    grid-area: excerpt; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
    margin: 0 0 9px; padding: 0 12px; font-size: 12.5px; line-height: 1.35; color: rgba(255,255,255,.78); align-self: start;   /* spacing as MARGIN: overflow clips at the padding edge (a 4th line peeked) */
    height: calc(3 * 1.35em);   /* GRID GOTCHA: Chrome sizes the track from the UNclamped text (even with max-height) → a FIXED height */
  }
  :root .blocknews .newshashtagcontainer { grid-area: tags; margin: 0 12px 8px; font-size: 11px; }
  :root .blocknews .newshashtagcontainer:empty { display: none; }
  /* Age separators between news rows: small caps label + hairline, like a native list section header. */
  :root #news > .newsage {   /* #news: outranks the website rule (#news > .newsage, negative bottom margin sized for the 20px desktop gap) */
    display: flex; align-items: center; gap: 10px; margin: 14px 0 4px; padding: 0 2px; line-height: 1.2;   /* with the 8px grid gap: 22px above the label, 12px below — a section header, not a caption glued to the first card */
    font-size: 11.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,.72);
  }
  :root .newsage::after { content: ''; flex: 1; height: 1px; background: rgba(255,255,255,.18); }
  :root #news > .newsage:first-child { margin-top: 0; }
  :root .blocknews > a.fullclicker { position: absolute; inset: 0; z-index: 1; }   /* out of the grid flow (it made an empty extra row) = full-row tap target */
  :root .blocknews .newshashtagcontainer { position: relative; z-index: 2; }        /* hashtag links stay tappable above it */
  .app-mode .tool { gap: var(--app-gap); }
  .app-mode .tool-wrap > .tool:first-child { margin-top: 0; }   /* .tool's own 12px top margin doubled the gap */
  :root .listeproduits { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--app-gap); margin-top: 0; }
  :root .productcard { position: relative; min-width: 0; }
  :root .titreblocproduit {
    display: block; height: auto; padding: 7px 8px; font-size: 13px; line-height: 1.25;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* Equal cards per row (2026-11-10): a card without a variants strip, or with a 1-line text, used to end above its
     neighbour (ragged rows). The card is a flex column: the PHOTO box takes the slack, the text tile stays at the bottom. */
  :root .productcard { display: flex; flex-direction: column; }
  :root .productcard > .blocproduit { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
  /* Compare / favourite get their OWN row, shared with the brand logo ([⇄][☆] … [brand]) — they sat on the photo. */
  :root .blocproduitmarque { height: 36px; padding: 0 6px 0 0; display: flex; align-items: center; justify-content: flex-end; }
  :root .blocproduitmarqueimg { max-width: 38px; max-height: 16px; }
  :root .containerimageblocproduit { flex: 1 0 auto; height: auto; min-height: 104px; padding: 0 6px; }
  :root .containerimageblocproduit img {   /* petite_image8() writes width/height INLINE → override both, or the photo stretches */
    width: auto !important; height: auto !important; max-width: 100% !important; max-height: 100px !important; object-fit: contain;
  }
  :root .cmp-toggle--card, :root .fav-toggle--card { top: 31px; left: 6px; width: 28px; height: 28px; }   /* the brand row (title ≈ 30px) */
  :root .fav-toggle--card { left: 46px; }
  :root .cmp-toggle--card .icon, :root .fav-toggle--card .icon { width: 15px; height: 15px; }
  :root .blocvariante-spacer { height: 6px; }   /* the EMPTY variants spacer */
  /* Variants in a ~180px card: a row of swatch thumbnails; the colour names don't fit 3-across → they become each
     swatch's tooltip / accessible name (js/app-mode.js), the text itself is hidden by font-size 0. */
  :root .blocvariante { height: auto; display: flex; justify-content: center; gap: 6px; padding: 4px 6px 2px; }
  :root .blocvariante > div { font-size: 0; line-height: 0; }
  :root .blocvariante img { width: 26px !important; height: 26px !important; object-fit: contain; }
  :root .blockstockprix { grid-template-columns: 1fr auto; align-items: center; padding: 0 6px 2px; gap: 4px; }
  :root .blockstockprix__stock { justify-content: start; font-size: 11px; line-height: 1.2; }
  :root .blocproduitprix { height: auto; padding-right: 0; font-size: 15px; white-space: nowrap; }
  :root .cardadd { gap: 3px; margin: 0; padding: 4px 4px 4px; background: #fff; }   /* padding, not margin: a margin let the card's grey show as a band under the price */
  :root .cardadd__label { display: none; }
  :root .cardadd__main { padding-left: 0; padding-right: 0; display: flex; align-items: center; justify-content: center; }
  :root .productdescription {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    box-sizing: content-box; height: calc(2 * 1.3em); padding: 6px 8px 0; font-size: 11.5px; line-height: 1.3;
    border-bottom: 6px solid #41434E;   /* = .secondary bg. Bottom spacing as a BORDER, not padding: overflow clips at the
                                           padding edge, and descourte's extra HTML blocks (after the clamped one) peeked
                                           through the bottom padding. */
  }

  /* Breadcrumbs → an app path bar: ONE line of chips, scrolled sideways (js/app-mode.js scrolls it to the end so
     the current level is in view), › chevrons instead of "/", current page = filled brand chip. Home + "Produits"
     drop out (the LSCF / Catalogue tabs are those), and so do the hover sibling dropdowns (touch + a scroller).
     The "/" separators are bare text nodes → font-size:0 on the nav, size restored on the chips. */
  .app-mode nav.breadcrumbs {
    display: flex; align-items: center; gap: 0; flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    font-size: 0; line-height: 1; margin: 0; padding: 0;
  }
  .app-mode nav.breadcrumbs::-webkit-scrollbar { display: none; }
  /* one level = the title below says it — unless it's a product trail: its root chip (catalogue) is the way back up. */
  .app-mode nav.breadcrumbs[data-crumbs='0'], .app-mode nav.breadcrumbs[data-crumbs='1']:not([data-root]) { display: none; }
  .app-mode nav.breadcrumbs > a:first-child,                              /* home icon */
  .app-mode nav.breadcrumbs .crumb-dd__menu { display: none; }
  .app-mode nav.breadcrumbs .crumb { display: flex; align-items: center; flex: none; font-size: 13px; }
  .app-mode nav.breadcrumbs .crumb + .crumb::before {
    content: '\203A'; padding: 0 6px; font-size: 18px; line-height: 1; color: rgba(255,255,255,.55); transform: translateY(-2px);
  }
  /* product root = a square grid-icon chip (same icon as the Catalogue tab); tapping it opens the catalogue (app-mode.js) */
  .app-mode nav.breadcrumbs .crumb-root .crumb__txt { display: none; }
  .app-mode nav.breadcrumbs .crumb-root .crumb__ico { display: flex; }
  .app-mode nav.breadcrumbs .crumb.crumb-root > a { padding: 0 9px; display: flex; align-items: center; }   /* 2 classes: beats the later generic chip rule (display:block) */
  .app-mode nav.breadcrumbs .crumb-root .icon { width: 17px; height: 17px; }
  /* chips: fixed 30px, and capped at 60vw with "…" — a long current page (a news title) otherwise filled the bar and
     scrolled its parent levels off-screen, only to repeat the title right below */
  .app-mode nav.breadcrumbs .crumb > a { height: 30px; line-height: 30px; box-sizing: border-box; display: block; max-width: 60vw; overflow: hidden; text-overflow: ellipsis; }
  .app-mode nav.breadcrumbs .crumb > a {
    display: block; padding: 0 11px; white-space: nowrap; text-decoration: none;   /* vertical centring = the 30px line-height */
    background: rgba(255,255,255,.12); color: #fff; font-weight: 600;
  }
  .app-mode nav.breadcrumbs .crumb > a:active { background: rgba(255,255,255,.22); }
  .app-mode nav.breadcrumbs .crumb > a[aria-current='page'] { background: var(--brand-gradient); }

  /* Title block flush with the product grid / path bar (8px gutters) instead of .content's 20px inset. */

  /* Product page: 8px rhythm, and "Stock & Commande" becomes a BUY DOCK pinned on the tab bar, in the bar's colour so
     the two read as one bottom dock: [price / stock] · (LSCF bump) · [– 1 +][cart][▼]. The bump rises 26px into the
     dock, so a 68px column is kept EMPTY exactly under it (--bx below); compact controls fit the 153px half on a
     390px phone (a smaller step below 370px). */
  .app-mode { --buydock-h: 60px; }
  .app-mode #photosproduit { margin-bottom: var(--app-gap) !important; }
  .app-mode #caracteristiquespanier { gap: var(--app-gap) !important; margin-bottom: var(--app-gap) !important; }
  /* Product page headings at phone sizes — app AND mobile website (2026-11-10): the ref title like .pagetitle (21px),
     panel bars 16px, compact description; the name shows under the ref (.prod-name) instead of heading the specs panel. */
  #photosproduit > h1 { font-size: 21px; line-height: 1.2; padding: 11px 14px; margin: 0; }
  .produitpage .h2.gradient:not(h1) { font-size: 16px; line-height: 1.25; padding: 9px 14px; }
  #photosproduit .prod-name { display: block; margin: 0; padding: 10px 14px 0; background: #fff; font-weight: 700; font-size: 15px; line-height: 1.35; color: #1d2939; }
  #caracteristiques .prod-descourte { display: none; }
  #caracteristiques .features li { line-height: 1.4; }
  .app-mode .produitpage .ptabs { margin-top: var(--app-gap); }
  .app-mode body:has(#ajtpanierdevis) { padding-bottom: calc(var(--appbar-h) + var(--buydock-h)); }
  .app-mode #ajtpanierdevis {
    position: fixed; left: 0; right: 0; bottom: var(--appbar-h); z-index: 9997; height: var(--buydock-h);
    display: block !important; background: #261e19 !important; box-shadow: 0 -4px 14px rgba(0,0,0,.28);
  }
  .app-mode #ajtpanierdevis > .h2 { display: none; }                         /* "Stock & Commande" */
  .app-mode #ajtpanierdevis .buybox {
    height: 100%; box-sizing: border-box; padding: 0 8px; gap: 0;
    /* the empty column sits UNDER the bump: --bx = the home tab's centre (5 tabs → 50vw; 4 tabs, logged out → 62.5vw) */
    display: grid; grid-template-columns: minmax(0, calc(var(--bx, 50vw) - 42px)) 68px minmax(0, 1fr); grid-template-rows: auto auto;
    grid-template-areas: "price . buy" "stock . buy"; align-content: center; align-items: center;
  }
  /* 4 tabs: the bump is right of centre → the right half is too narrow for the buy controls → swap the halves. */
  .app-mode body:has(#appbar > :last-child:nth-child(4)) { --bx: 62.5vw; }
  .app-mode body:has(#appbar > :last-child:nth-child(4)) #ajtpanierdevis .buybox { grid-template-areas: "buy . price" "buy . stock"; }
  .app-mode #ajtpanierdevis .buybox__price { display: block; grid-area: price; color: var(--price-on-dark); font-size: 17px; font-weight: 800; line-height: 1.15; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .app-mode #ajtpanierdevis .buybox__stock { grid-area: stock; font-size: 11px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  .app-mode #ajtpanierdevis .buybox__stock .icon { width: 13px; height: 13px; }
  .app-mode #ajtpanierdevis .buybox__qty,
  .app-mode #ajtpanierdevis .buybox__actions { grid-area: buy; margin: 0; }
  .app-mode #ajtpanierdevis .buybox__qty { justify-self: end; margin-right: 70px; }      /* sits left of the split (64px + 6px gap) */
  .app-mode #ajtpanierdevis .buybox__actions { justify-self: end; }
  .app-mode #ajtpanierdevis .qty-stockinfo { display: none; }
  .app-mode #ajtpanierdevis .qty-stepper { height: 40px; }
  .app-mode #ajtpanierdevis .qty-minus, .app-mode #ajtpanierdevis .qty-plus { width: 26px; font-size: 17px; }
  .app-mode #ajtpanierdevis .qty-stepper input { width: 30px; font-size: 14px; }
  .app-mode #ajtpanierdevis .split-button { box-shadow: none; }
  .app-mode #ajtpanierdevis .split-button .cta { height: 40px; padding: 0; }
  .app-mode #ajtpanierdevis .split-button__main { width: 40px; }
  .app-mode #ajtpanierdevis .split-button .dropdown-toggle { width: 24px; }
  .app-mode #ajtpanierdevis .split-button__label { display: none; }
  .app-mode #ajtpanierdevis .dropdown-menu { top: auto; bottom: calc(100% + 6px); }   /* no room below: open upward */
  .app-mode body:has(#ajtpanierdevis) .cmp-tray,
  .app-mode body:has(#ajtpanierdevis) #cookie-banner { bottom: calc(var(--appbar-h) + var(--buydock-h)); }

  /* No website footer in the app (agency/contact/CGV/cookies block on every page = web chrome, not an app screen). */
  .app-mode footer[role='contentinfo'] { display: none; }

  /* ☰ App side menu (lib/appmenu.php): button left of the search field; a left drawer over everything (incl. the tab
     bar, like a native drawer), dark like the catalogue. Opened/closed by js/app-mode.js (body.appmenu-open). */
  .app-mode .searchbar .content { display: flex; align-items: center; gap: 8px; padding-left: 8px !important; padding-right: 8px !important; max-width: none; margin: 0; }   /* ☰ and the field's right edge on the 8px gutters, like the titles below */
  .app-mode .searchbar__field { flex: 1; min-width: 0; }
  .app-mode .searchbar__menu {
    display: flex; align-items: center; justify-content: center; position: relative; flex: none;
    width: 44px; height: 40px; border: 0; background: rgba(0,0,0,.18); color: #fff; cursor: pointer;   /* height = the search field */
  }
  .app-mode .searchbar__menu .icon { width: 24px; height: 24px; }
  .app-mode .searchbar__menu:active { background: rgba(0,0,0,.3); }
  .searchbar__menudot { display: none; position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; background: #FFCE54; }
  .showroom .searchbar__menudot { display: block; }                          /* showroom on = visible cue on the ☰ */
  .app-mode #appmenu {
    display: flex; flex-direction: column; position: fixed; top: 0; bottom: 0; left: 0; z-index: 10003;
    width: min(86vw, 360px); background: #261e19; color: #fff; box-shadow: 4px 0 24px rgba(0,0,0,.45);
    transform: translateX(-102%); transition: transform .26s cubic-bezier(.22,1,.36,1); visibility: hidden;
    padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);
  }
  .app-mode body.appmenu-open #appmenu { transform: none; visibility: visible; }
  .app-mode #appmenu-backdrop {
    display: block; position: fixed; inset: 0; z-index: 10002; background: rgba(0,0,0,.5);
    opacity: 0; visibility: hidden; transition: opacity .26s ease, visibility 0s linear .26s;
  }
  .app-mode body.appmenu-open #appmenu-backdrop { opacity: 1; visibility: visible; transition: opacity .26s ease; }
  .app-mode body.appmenu-open { overflow: hidden; }
  .app-mode body.appmenu-open #filters-toggle { display: none; }   /* the Filtres pill (99988) would float over the drawer */
  .appmenu__head { display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--brand-gradient); }
  .appmenu__head { position: relative; }
  .appmenu__head img { width: 40px; height: 40px; flex: none; }
  /* → easter-egg countdown: css/components.client.css.php */
  .appmenu__head > div { flex: 1; min-width: 0; line-height: 1.25; }
  .appmenu__head b { display: block; font-size: 14px; }
  .appmenu__head small { display: block; font-size: 12px; opacity: .9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .appmenu__close { flex: none; width: 36px; height: 36px; border: 0; background: rgba(0,0,0,.18); color: #fff; display: flex; align-items: center; justify-content: center; }
  .appmenu__close .icon { width: 20px; height: 20px; }
  .appmenu__body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding-bottom: 16px; }
  .appmenu__sect {
    margin: 16px 16px 4px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em; color: rgba(255,255,255,.5);
  }
  .appmenu__row {
    display: flex; align-items: center; gap: 14px; width: 100%; box-sizing: border-box; padding: 11px 16px; min-height: 48px;
    border: 0; background: none; color: #fff; text-align: left; text-decoration: none; font: inherit; cursor: pointer;
  }
  .appmenu__row:active { background: rgba(255,255,255,.08); }
  .appmenu__row > .icon { width: 20px; height: 20px; flex: none; color: rgba(255,255,255,.75); }
  .appmenu__txt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; font-size: 15px; }
  .appmenu__txt small { font-size: 12px; color: rgba(255,255,255,.55); overflow-wrap: anywhere; }
  .appmenu__logout, .appmenu__logout > .icon { color: #ff9d8f; }
  .appmenu__logout { flex: none; min-height: 56px; border-top: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.22); }   /* pinned footer of the drawer (sibling of the scrolling body) */
  /* showroom switch */
  .appmenu__switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
  .appmenu__toggle { flex: none; width: 42px; height: 24px; background: rgba(255,255,255,.22); position: relative; transition: background .18s ease; }
  .appmenu__toggle::after { content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; background: #fff; transition: transform .18s ease; }
  .appmenu__switch input:checked + .appmenu__toggle { background: var(--green-cart); }
  .appmenu__switch input:checked + .appmenu__toggle::after { transform: translateX(18px); }
  .appmenu__switch input:focus-visible + .appmenu__toggle { outline: 2px solid var(--brand); outline-offset: 2px; }

  .app-mode .infopage { padding: 14px 14px 18px; }
  /* Agenda (landing + event): 8px gutters + 8px gaps; their first block follows the (possibly hidden) path bar. */
  .app-mode .ag-event__title + .ag-event__body { margin-top: var(--app-gap); }   /* the event's own title → its body */

  /* Articles list in the app = a feed like the news: rows [96px thumb | title (2 lines) · summary (2 lines) · author]. */
  :root .gridarticles { grid-template-columns: minmax(0, 1fr) !important; gap: var(--app-gap) !important; }
  /* thumb pinned ABSOLUTE in a 96px left strip (an in-grid image sizes the row — news-feed gotcha); text = 1 column */
  :root .blocarticle { display: grid !important; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto;
    grid-template-areas: "title" "resume" "infos"; align-content: start; position: relative; min-height: 96px; padding-left: 96px !important; box-sizing: border-box; }
  :root .blocarticle .titrearticle { grid-area: title; background: none !important; color: var(--brand) !important; font-size: 15px; line-height: 1.3;
    padding: 9px 12px 2px; margin: 0; width: auto; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; max-height: calc(2 * 1.3em); box-sizing: content-box; }   /* width:auto — the site rule's 100% + content-box padding overflowed */
  :root .blocarticle .innerarticle { display: contents; }
  :root .blocarticle .imagecontainer { position: absolute; left: 0; top: 0; bottom: 0; width: 96px; height: auto; }
  :root .blocarticle .imagearticle { width: 100%; height: 100%; border-radius: 0; object-fit: cover; }
  :root .blocarticle .resumehash { grid-area: resume; display: block; padding: 0 12px; }
  :root .blocarticle .resume { margin: 0; font-size: 12.5px; line-height: 1.35; color: #475467; padding: 0;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: calc(2 * 1.35em); }
  :root .blocarticle .hashs { display: none; }
  :root .blocarticle .infosarticle { grid-area: infos; justify-content: flex-start; gap: 8px; padding: 4px 12px 9px; font-size: 11.5px; color: #667085; }
  :root .blocarticle .datepage:empty { display: none; }

  /* Opened news (vues/new): 8px gutters + 8px gaps path bar → title → article (inline padding-top:20px → !important). */
  .app-mode .newpage .contenunew { gap: 0; }
  .app-mode .newpage .imagenew { padding: 12px 12px 0; }
  .app-mode .newpage .textenew { padding: 14px; }

  /* ═══ PAGE SKELETON — the app's layout CONTRACT (design language, 2026-10-03) ═══════════════════════════════════
     Every page = [path bar] [page head: page_head8()] [body…] as DIRECT children of <main>. <main> owns the 8px
     gutters and the 8px gaps between those blocks (flex column; a hidden path bar is display:none → no gap). Blocks
     bring NO outer margins, no side padding, and their first child no top margin — so no view positions its own
     header, and a new page is right by construction. Guard: ~/css8-tools/rhythm_audit.sh (must stay all ✓). */
  .app-mode main.page-main { display: flex; flex-direction: column; gap: var(--app-gap); padding: var(--app-gap) 8px 0; box-sizing: border-box; }
  .app-mode main.page-main > * { margin: 0 !important; min-width: 0; }
  .app-mode main.page-main .content { padding-left: 0 !important; padding-right: 0 !important; max-width: none; }
  .app-mode main.page-main > .content, .app-mode main.page-main > article { padding-top: 0 !important; }
  .app-mode main.page-main > * > :first-child { margin-top: 0 !important; }
  .app-mode main.page-main .pagehead > .pagetitle { margin: 0 !important; }

  /* treact modals (cart, add-to-cart confirmation, quote pickers, quick order…) = native BOTTOM SHEETS on phones —
     the app AND the mobile website (user 2026-11-10; was app-only): full width (a centred 640 px sheet on tablets),
     rising from the bottom, ≤90% of the screen, title bar pinned, content scrolls. The per-modal desktop widths
     (#modal-panier 640px…) are overridden. */
  /* Virtual keyboard: the overlay's bottom edge rides on top of it (--kb, js/app-mode.js — runs on the website too,
     from the visual viewport) and the sheet shrinks to the space left, so a focused field is never hidden ("Nouveau
     devis"). 0 when the browser/WebView itself resizes. */
  /* `body` prefix = out-ranks treact.min.css (same selectors, loaded later). */
  body .treact-modal-overlay { align-items: flex-end; bottom: var(--kb, 0px); transition: opacity .3s ease, bottom .18s ease; }
  body .treact-modal-overlay .treact-modal {
    width: min(640px, 100%) !important; max-width: 100% !important; max-height: calc((100dvh - var(--kb, 0px)) * .9); transform: translateY(100%);
    transition: transform .28s cubic-bezier(.22,1,.36,1); padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box;
  }
  body .treact-modal-overlay.show .treact-modal { transform: none; }
  body .treact-modal-header { flex: none; }
  body .treact-modal-body { padding: 14px; overscroll-behavior: contain; }

  /* → compare tray: css/components.client.css.php */
  /* → Mon compte KPIs: css/components.client.css.php */
  /* Lift the other bottom-pinned UI above the bar. */
  .app-mode #cookie-banner,
  .app-mode .cmp-tray,
  .app-mode .tool-sticky:not([hidden]) { bottom: var(--appbar-h); }
  .app-mode .tool-sticky:not([hidden]) { padding-bottom: 10px; }
  .app-mode #filters-toggle { bottom: calc(var(--appbar-h) + 14px); }
  /* Filter sheet = a native bottom sheet: from the screen edge (fully off-screen when closed — parking it on the
     bar's top edge let its title peek over the bar); open, it + its backdrop (99990/99989) cover the tab bar. */
  .app-mode #filtrespage { padding-bottom: env(safe-area-inset-bottom); }
  .app-mode body.nav-open #filters-toggle { display: none; }   /* never float over the open catalogue */

  /* App layer order. #site-header is position:relative; z-index:1000 → a stacking context that TRAPPED its fixed
     children (tab bar, catalogue, search bar) at 1000, under root-level layers (Filtres pill 99988, sheet 99990)
     → the sheet's title painted over the bar. In app mode #main is hidden, so the header needs no layer: its
     children stack at root, slotted on purpose: login 200000 > open filter sheet 99990 > treact modals 10000 >
     cookie 9999 > TAB BAR 9998 = SEARCH BAR (+ results, drop over the catalogue) > CATALOGUE 9997. */
  .app-mode #site-header { z-index: auto; }
}

/* Content layout shared by the app AND the responsive website (≤960): the --app-gap rhythm token exists for both. */
@media (max-width: 960px) { :root { --app-gap: 8px; } }

/* App product grid: 3 columns on large phones in landscape / small tablets (still the ≤960 app layout). */
@media (min-width: 600px) and (max-width: 960px) {
  :root .listeproduits { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  :root .cat3grid { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
}

/* Buy dock on the narrowest phones (≤370px: halves of ~138px): one size step smaller so [– 1 +][cart][▼] still fit. */
@media (max-width: 370px) {
  .app-mode #ajtpanierdevis .qty-minus, .app-mode #ajtpanierdevis .qty-plus { width: 22px; }
  .app-mode #ajtpanierdevis .qty-stepper input { width: 26px; }
  .app-mode #ajtpanierdevis .split-button__main { width: 34px; }
  .app-mode #ajtpanierdevis .split-button .dropdown-toggle { width: 22px; }
  .app-mode #ajtpanierdevis .buybox__qty { margin-right: 60px; }
  .app-mode #ajtpanierdevis .buybox__price { font-size: 15px; }
}

/* ═══ App, LANDSCAPE PHONE ═══════════════════════════════════════════════════════════════════════════════════════
   Short + wide (a phone on its side: ~390px tall). Vertical space is the scarce resource, so all chrome moves out of
   it: the bottom tab bar becomes a left NAV RAIL (logo inline, no bump), the search bar slims down and hides while
   scrolling down (js/app-mode.js → html.searchbar-hidden), grids use the width. --appbar-h → 0 lets every
   bottom-pinned element settle on the bottom edge; --rail-w shifts the left-pinned ones. Notch insets are on the
   SIDES in landscape → env(safe-area-inset-left/right). Placed last: overrides the portrait app rules above. */
@media (max-width: 960px) and (orientation: landscape) and (max-height: 500px) {
  .app-mode {
    --rail-w: calc(76px + env(safe-area-inset-left)); --appbar-h: 0px;
    --appsearch-h: calc(52px + env(safe-area-inset-top));
  }
  .app-mode body { padding-left: var(--rail-w); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); }

  /* nav rail */
  .app-mode #appbar {
    top: 0; bottom: 0; right: auto; width: var(--rail-w); height: auto;
    grid-auto-flow: row; grid-auto-rows: 1fr; grid-auto-columns: auto; align-items: center;
    padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom) env(safe-area-inset-left);
    box-shadow: 4px 0 18px rgba(0,0,0,.3);
  }
  .app-mode #appbar > a, .app-mode #appbar > button { height: 100%; min-height: 0; gap: 2px; font-size: 10.5px; }
  .app-mode #appbar .icon { width: 22px; height: 22px; }
  .app-mode #appbar > .is-active::before { top: 22%; bottom: 22%; left: 0; right: auto; width: 3px; height: auto; }
  .app-mode #appbar .appbar__home { justify-content: center; padding-bottom: 0; }
  .app-mode #appbar .appbar__home img { width: 34px; height: 34px; margin: 0; padding: 0; border-radius: 0; background: none; }
  .appbar__badge { top: 4px; }

  /* search bar: beside the rail, slimmer, hides on scroll-down */
  .app-mode .searchbar, .app-mode body.search-open .searchbar {
    left: var(--rail-w); padding-top: calc(6px + env(safe-area-inset-top)); padding-bottom: 6px;
    transition: transform .22s ease;
  }
  .app-mode.searchbar-hidden .searchbar:not(:focus-within) { transform: translateY(-100%); }
  .app-mode .searchbar__input { padding-top: 9px; padding-bottom: 9px; }
  .app-mode .searchbar__menu { height: 38px; }

  /* overlays beside the rail */
  .app-mode #menu.content { left: var(--rail-w); }
  .app-mode #menu .menuroot { grid-template-columns: repeat(6, 1fr); gap: 14px 8px; }
  .app-mode #menu .catico { width: 56px; height: 56px; }
  .app-mode #menu .catico .icon { width: 28px; height: 28px; }

  /* grids use the width */
  :root .listeproduits, :root .cat3grid, :root .cat2grid { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)) !important; }
  :root #news { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  :root #news > .newsage { grid-column: 1 / -1; }

  /* product buy dock: bottom edge beside the rail, no bump to dodge → [price/stock | controls] */
  .app-mode #ajtpanierdevis { left: var(--rail-w); right: env(safe-area-inset-right); bottom: 0; height: 56px; --buydock-h: 56px; }
  .app-mode #ajtpanierdevis .buybox, .app-mode body:has(#appbar > :last-child:nth-child(4)) #ajtpanierdevis .buybox {
    grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "price buy" "stock buy"; padding: 0 12px;
  }
  .app-mode #ajtpanierdevis .buybox__qty { margin-right: 70px; }
  .app-mode body:has(#ajtpanierdevis) { padding-bottom: calc(56px + env(safe-area-inset-bottom)); }

  /* product page: photo | characteristics SIDE BY SIDE (single column = one giant photo per screen) */
  .app-mode .produitpage .productmain { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important; gap: var(--app-gap) !important; align-items: start; }
  .app-mode .produitpage #photosproduit { margin-bottom: 0 !important; }

  /* Filters: a RIGHT SIDE PANEL instead of the bottom sheet (which covered the whole short screen) — full height,
     ~45% wide, slides in from the right; the grid stays visible on the left. Square edges, notch inset on the right. */
  .app-mode #filtrespage {
    top: 0; bottom: 0; left: auto; right: 0; width: min(400px, 45vw); max-height: none; height: 100dvh;
    border-radius: 0; transform: translateX(100%); box-shadow: -8px 0 24px rgba(0,0,0,.45);
    padding-right: env(safe-area-inset-right); padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);
  }
  .app-mode body.filters-open #filtrespage { transform: translateX(0); }

  body .treact-modal-overlay .treact-modal { max-height: 94dvh; }   /* landscape: the centred 640 px sheet may use more height */
  .app-mode #search_result { top: calc(var(--appsearch-h) - 8px); }
  .app-mode #search_result div:has(> .search_result_li_produit) { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)) !important; }

  /* other left/bottom-pinned UI */
  .app-mode #cookie-banner, .app-mode .cmp-tray, .app-mode .tool-sticky:not([hidden]) { left: var(--rail-w); }
  .app-mode #filters-toggle { bottom: 14px; }
  .app-mode body:has(#ajtpanierdevis) #filters-toggle { bottom: 70px; }
}

/* ── Animated overflow fades (tab strips, comparison table). A mask can't transition from "none", so the mask is
   ALWAYS on and what animates is the WIDTH of each fading edge: registered custom properties (@property) are
   interpolable. js/javascript.js toggles .is-more-start / .is-more-end; 0px = no fade. --edge-max / --edge-a
   (alpha at the very edge) are set per component above. ── */
@property --edge-s { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --edge-e { syntax: '<length>'; inherits: false; initial-value: 0px; }
@media (max-width: 960px) {
  .ptabs .ptabs__bar, .cmp-scroll, .compte-nav .compte-menu, .marque-types {
    --edge-s: 0px; --edge-e: 0px;
    -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,var(--edge-a, 0)) 0, #000 var(--edge-s), #000 calc(100% - var(--edge-e)), rgba(0,0,0,var(--edge-a, 0)) 100%);
            mask-image: linear-gradient(to right, rgba(0,0,0,var(--edge-a, 0)) 0, #000 var(--edge-s), #000 calc(100% - var(--edge-e)), rgba(0,0,0,var(--edge-a, 0)) 100%);
    transition: --edge-s .35s ease, --edge-e .35s ease;
  }
  .ptabs .ptabs__bar.is-more-start, .cmp-scroll.is-more-start, .compte-nav .compte-menu.is-more-start { --edge-s: var(--edge-max); }
  .ptabs .ptabs__bar.is-more-end,   .cmp-scroll.is-more-end,   .compte-nav .compte-menu.is-more-end   { --edge-e: var(--edge-max); }
  .marque-types { --edge-max: 36px; --edge-a: 0; }
  .marque-types.is-more-start { --edge-s: var(--edge-max); } .marque-types.is-more-end { --edge-e: var(--edge-max); }
  .compte-nav .compte-menu { --edge-max: 36px; --edge-a: .15; scrollbar-width: none; }   /* Mon compte: RMA / Adresses off-screen right */
  /* 3.6 items per width → the 4th always PEEKS in (half its icon) under the fade, every label fits: a cut-off item is the cue that the
     row scrolls (a row that happens to end on an item boundary looks complete). */
  .compte-nav .compte-menu .compte-menu__item { flex: 0 0 calc(100% / 3.6); min-width: 0; box-sizing: border-box; }
  .compte-nav .compte-menu .compte-menu__label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .compte-nav .compte-menu::-webkit-scrollbar { display: none; }
  .cmp-scroll.is-more-start { --edge-s: 0px; }   /* the comparison's left side is the sticky label column: never fade it */
}
@media (prefers-reduced-motion: reduce) { .ptabs .ptabs__bar, .cmp-scroll, .compte-nav .compte-menu { transition: none; } }

/* ── Polish (app) ─────────────────────────────────────────────────────────────────────────────────────────────── */
/* 1. Tab bar: ONE sliding ink marker (js/app-mode.js .appbar__ink, positioned on the active tab) replaces the
      per-tab ::before — it glides from tab to tab (top edge in portrait, left edge on the landscape rail). */
.appbar__ink { display: none; }
@media (max-width: 960px) {
  .app-mode #appbar > .is-active::before { display: none; }
  .app-mode .appbar__ink {
    display: block; position: absolute; top: 0; left: 0; width: 0; height: 3px; background: var(--brand); pointer-events: none;
    transition: transform .32s cubic-bezier(.22,1,.36,1), width .32s cubic-bezier(.22,1,.36,1), height .32s cubic-bezier(.22,1,.36,1), opacity .2s ease;
  }
  .app-mode .appbar__ink.is-off { opacity: 0; }
  /* 2. Cart badge: a short pop when the count goes up */
  .app-mode .appbar__badge.is-bump { animation: badgeBump .5s cubic-bezier(.22,1,.36,1); }
}
@keyframes badgeBump { 0% { transform: scale(1); } 35% { transform: scale(1.45); } 100% { transform: scale(1); } }
/* 3. Search bar: its brand gradient turns a little as the bar slides back in (landscape hide-on-scroll) — @property
      makes the ANGLE interpolable, so the colours flow instead of jumping. */
@property --sb-angle { syntax: '<angle>'; inherits: false; initial-value: 90deg; }
@media (max-width: 960px) {
  .app-mode .searchbar { --sb-angle: 90deg; background: linear-gradient(var(--sb-angle), var(--brand), var(--brand-2)); transition: transform .22s ease, --sb-angle .7s ease; }
  .app-mode.searchbar-hidden .searchbar { --sb-angle: 150deg; }
}
@media (prefers-reduced-motion: reduce) { .app-mode .appbar__ink, .app-mode .searchbar { transition: none; } .app-mode .appbar__badge.is-bump { animation: none; } }

/* ── Touch targets on phones (UI/UX review 2026-10-04): card controls ≥ 36 px tall, cart steppers 40 px, remove + modal
   close 44 px. Last in the file so it wins over the ≤960 content-layout rules above. The product page's buy DOCK keeps
   its tighter controls on purpose (26×40, ≥ the WCAG 24 px minimum): it is fitted around the tab bar's LSCF bump. ── */
@media (max-width: 960px) {
  :root .cardadd__qty .qty-stepper { height: 36px; }
  :root .cardadd__qty .qty-minus, :root .cardadd__qty .qty-plus { width: 28px; font-size: 16px; }
  :root .cardadd__qty .qtyinput { width: 30px; }
  :root .cardadd .split-button, :root .cardadd__main, :root .cardadd__more { min-height: 36px; }
  :root .cmp-toggle--card, :root .fav-toggle--card { width: 36px; height: 36px; }   /* side by side in the brand row (top/left above) */
  .cart-row .qty-stepper { height: 40px; }
  .cart-row .qty-minus, .cart-row .qty-plus { width: 40px; }
  .cart-row__del { width: 44px; height: 44px; }
  .treact-modal-header .treact-modal-close { width: 44px; height: 44px; }   /* beats treact.min.css (loaded later) by specificity */
}

/* ── Mobile WEBSITE product page: sticky buy bar (UI/UX review 2026-10-04 — the price and "Ajouter au panier" first
   showed ~1 100 px down). A simpler sibling of the app's buy DOCK (which works around the tab bar's LSCF bump): fixed at
   the bottom, price + stock left, qty + add right, 44 px controls; the dropdown opens upward. ── */
@media (max-width: 960px) {
  html:not(.app-mode) body:has(#ajtpanierdevis) { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
  html:not(.app-mode) #ajtpanierdevis {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 9990; height: calc(64px + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box;
    display: block !important; background: #261e19 !important; box-shadow: 0 -4px 14px rgba(0,0,0,.28);
  }
  html:not(.app-mode) #ajtpanierdevis > .h2 { display: none; }
  html:not(.app-mode) #ajtpanierdevis .buybox {
    height: 64px; box-sizing: border-box; padding: 0 12px; margin: 0; gap: 0 10px;
    display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto;
    grid-template-areas: "price buy" "stock buy"; align-content: center; align-items: center;
  }
  html:not(.app-mode) #ajtpanierdevis .buybox__price { grid-area: price; color: var(--price-on-dark); font-size: 18px; line-height: 1.15; overflow: hidden; text-overflow: ellipsis; }
  html:not(.app-mode) #ajtpanierdevis .buybox__stock { grid-area: stock; font-size: 11px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  html:not(.app-mode) #ajtpanierdevis .buybox__stock .icon { width: 13px; height: 13px; }
  html:not(.app-mode) #ajtpanierdevis .buybox__qty, html:not(.app-mode) #ajtpanierdevis .buybox__actions { grid-area: buy; margin: 0; }
  html:not(.app-mode) #ajtpanierdevis .buybox__qty { justify-self: end; margin-right: 86px; }   /* left of the split (44 + 34 + 8 gap) */
  html:not(.app-mode) #ajtpanierdevis .buybox__actions { justify-self: end; }
  html:not(.app-mode) #ajtpanierdevis .qty-stockinfo { display: none; }
  html:not(.app-mode) #ajtpanierdevis .qty-stepper { height: 44px; }
  html:not(.app-mode) #ajtpanierdevis .qty-minus, html:not(.app-mode) #ajtpanierdevis .qty-plus { width: 34px; font-size: 18px; }
  html:not(.app-mode) #ajtpanierdevis .qty-stepper input { width: 34px; font-size: 15px; }
  html:not(.app-mode) #ajtpanierdevis .split-button { box-shadow: none; }
  html:not(.app-mode) #ajtpanierdevis .split-button .cta { height: 44px; padding: 0; }
  html:not(.app-mode) #ajtpanierdevis .split-button__main { width: 44px; }
  html:not(.app-mode) #ajtpanierdevis .split-button .dropdown-toggle { width: 34px; }
  html:not(.app-mode) #ajtpanierdevis .split-button__label { display: none; }
  html:not(.app-mode) #ajtpanierdevis .dropdown-menu { top: auto; bottom: calc(100% + 6px); }
}

/* ── Brand TYPE chooser on phones (UI/UX review 2026-10-04: 2 big columns made Jablotron's 20 types a 1 891 px page):
   3-column MINI tiles — overview density, like the range mini tiles. Header 2 lines max, 64 px photo. ── */
@media (max-width: 960px) {
  :root .typegrid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--app-gap, 8px); }
  :root .typetile__name { padding: 6px 7px; font-size: 12px; line-height: 1.25; white-space: normal; height: calc(2 * 1.25em);
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; box-sizing: content-box; }
  :root .typetile__img { height: 72px; padding: 4px; }
  :root .typetile__img img { max-height: 64px; }
  :root .typetile__img .icon { width: 30px; height: 30px; }
  :root .typetile__n { right: 5px; bottom: 3px; font-size: 11px; }
}

/* ── Brands A–Z (/marques): a jump rail + on DESKTOP a logo-tile grid per letter (UI/UX review 2026-10-04: one 1 265 px
   wide list with the count far from the name). Phones keep the address-book rows; the app catalogue hides the rail. ── */
.brandaz__rail { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 var(--space-2); }
.brandaz__rail a { min-width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; background: #fff;
  color: var(--brand); font-weight: 800; text-decoration: none; box-shadow: var(--shadow-panel); }
.brandaz__rail a:hover { background: var(--brand); color: #fff; }
.brandaz__group { scroll-margin-top: 70px; }
#menu .brandaz__rail { display: none; }
@media (max-width: 960px) {
  .brandaz__rail { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .brandaz__rail a { flex: none; min-width: 40px; height: 40px; }
  .brandaz__group { scroll-margin-top: 120px; }
}
/* Tiles on the /marques PAGE at every width (`main` scope: the app catalogue's dark A–Z list inside #menu keeps its
   address-book rows). Desktop: auto-fill 180 px; phones: 2 per row (user 2026-10-14). */
main .brandaz { background: none; box-shadow: none; }
main .brandaz__group { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
main .brandaz__letter { grid-column: 1 / -1; position: static; background: none; color: #fff; font-size: 20px; padding: 2px 0; }
/* Tile = brand-gradient title bar [name | count chip] over a white logo stage (user 2026-10-14: logo + bare name +
   number stacked in one white box read oddly). Same markup as the rows: grid areas re-place the spans and the bar
   is the row's ::before, spanning the first grid row behind name + count. */
main .brandaz__row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name n" "logo logo"; gap: 0; padding: 0;
  background: #fff; box-shadow: var(--shadow-panel); border-top: 0; transition: box-shadow .15s, transform .15s; }
main .brandaz__row::before { content: ''; grid-area: 1 / 1 / 2 / 3; align-self: stretch; background: var(--brand-gradient); }   /* stretch: the row centres its items */
main .brandaz__row:hover { box-shadow: 0 6px 18px rgba(0,0,0,.18); background: #fff; transform: translateY(-2px); }
main .brandaz__name { grid-area: name; position: relative; padding: 8px 4px 8px 12px; color: #fff; font-size: 14px; font-weight: 700; }
main .brandaz__n { grid-area: n; position: relative; align-self: center; margin-right: 10px; padding: 1px 7px;
  background: rgba(38,30,25,.38); color: #fff; font-size: 12px; font-weight: 700; }
main .brandaz__logo { grid-area: logo; height: 84px; padding: 12px; }
main .brandaz__logo img { max-width: 150px; max-height: 56px; transition: transform .15s; }
main .brandaz__row:hover .brandaz__logo img { transform: scale(1.04); }
@media (max-width: 960px) {
  main .brandaz__group { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2); margin-bottom: var(--space-2); }
  .app-mode main .brandaz__group { gap: var(--app-gap); }
  main .brandaz__letter { font-size: 17px; padding: 4px 0 0; }
  main .brandaz__name { padding: 7px 4px 7px 10px; font-size: 13px; }
  main .brandaz__n { margin-right: 8px; padding: 1px 6px; font-size: 11px; }
  main .brandaz__logo { height: 72px; padding: 10px; }
  main .brandaz__logo img { max-width: 100%; max-height: 48px; }
  main .brandaz__row:hover { transform: none; }             /* touch: no hover lift (sticky :hover after a tap) */
  main .brandaz__row:active { box-shadow: 0 2px 8px rgba(0,0,0,.2); }
}
@media (prefers-reduced-motion: reduce) { main .brandaz__row, main .brandaz__logo img { transition: none; } main .brandaz__row:hover { transform: none; } main .brandaz__row:hover .brandaz__logo img { transform: none; } }

/* ── App lists: the LAST row must be able to scroll clear of the floating Filtres pill (+ the compare tray when open)
   and the tab bar (user screenshot 2026-10-05: last tile's add-to-cart, the pill and the bar fought). Reserve = pill
   (~52 px) + its 14 px offset + air ≈ 84 px, on top of the tab bar (0 in landscape, where it's a rail). ── */
@media (max-width: 960px) {
  .app-mode body:has(#filters-toggle) { padding-bottom: calc(var(--appbar-h) + 84px); }
  .app-mode body.has-cmp-tray:has(#filters-toggle) { padding-bottom: calc(var(--appbar-h) + var(--cmp-tray-h, 0px) + 84px); }
}

/* ── Product page: the end of the tabs gets AIR above the fixed buy bar (user screenshot 2026-10-05: the
   Téléchargements panel ran flush into it). Reserve = bar + 16 px (2 × the 8 px rhythm), for the three bar variants:
   app portrait (tab bar + dock), app landscape (dock only, beside the rail), mobile website (fixed bottom bar). ── */
@media (max-width: 960px) {
  .app-mode body:has(#ajtpanierdevis) { padding-bottom: calc(var(--appbar-h) + var(--buydock-h) + 16px); }
  html:not(.app-mode) body:has(#ajtpanierdevis) { padding-bottom: calc(64px + 16px + env(safe-area-inset-bottom)); }
}
  /* → cart-row touch targets: css/components.client.css.php */

/* CGV modal over the order tunnel (handlers/commande/cgv.php): a readable legal text, the tunnel stays underneath. */
@media (min-width: 700px) { #modal-cgv .treact-modal { width: min(760px, 92vw); } }
.cgv-modal { font-size: 14px; line-height: 1.6; color: #344054; }
.cgv-modal h1 { font-size: 20px; margin: 0 0 var(--space-2); }
.cgv-modal h2, .cgv-modal h3 { font-size: 16px; margin: var(--space-3) 0 var(--space-1); }
.cgv-modal img { max-width: 100%; height: auto; }
.cgv-modal p { margin: 0 0 var(--space-2); }


/* ── Site footer (footer.php) ─────────────────────────────────────────────────────────────────────────
   ONE grid, each column with its own heading; phone: Agence full width, the two link lists side by side. */
.sitefoot { margin-top: var(--space-5); background: var(--foot-bg); color: var(--foot-text); font-size: 14px; line-height: 1.5; }
.sitefoot a { color: inherit; text-decoration: none; }
.sitefoot a:hover { color: #fff; text-decoration: underline; }
.sitefoot a:focus-visible { outline: 2px solid var(--brand-2); outline-offset: 2px; }
.sitefoot .icon { width: 16px; height: 16px; flex: none; color: var(--foot-muted); }
.sitefoot__grid { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--space-4); padding-block: var(--space-4); }
.sitefoot__h { margin: 0 0 var(--space-2); font-size: 13px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: #fff; }
.sitefoot__agencebody { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-start; }
.sitefoot__contact { flex: 1 1 200px; display: flex; flex-direction: column; gap: var(--space-1); }
.sitefoot__addr { font-style: normal; }
.sitefoot__tel { display: inline-flex; align-items: center; gap: var(--space-1); font-weight: 700; min-height: 32px; }
.sitefoot__hours, .sitefoot__note { margin: 0; color: var(--foot-muted); }
.sitefoot__hours b { color: var(--foot-text); }
.sitefoot__note a { color: var(--foot-text); text-decoration: underline; }
.sitefoot__map { flex: none; display: flex; flex-direction: column; gap: var(--space-0); width: 160px; }
.sitefoot__map img { display: block; width: 160px; height: auto; }
.sitefoot__map span { display: inline-flex; align-items: center; gap: var(--space-0); color: var(--foot-muted); }
.sitefoot__links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.sitefoot__links a { display: flex; align-items: center; gap: var(--space-1); min-height: 36px; }
.sitefoot__partners { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-block: var(--space-3); border-top: 1px solid rgba(255,255,255,.1); }
.sitefoot__plabel { color: var(--foot-muted); font-size: 13px; margin-right: var(--space-1); }
.sitefoot__partners ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.sitefoot__partners li { display: flex; align-items: center; }
.sitefoot__partners img { display: block; max-height: 40px; width: auto; height: auto; }   /* logos as supplied, on the dark surface (Monetico's is a dark-bg version) */
.sitefoot__nw { white-space: nowrap; }
.sitefoot__bar { background: var(--foot-bar); color: var(--foot-muted); font-size: 12px; }
.sitefoot__barin { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--space-1) var(--space-3); padding-block: var(--space-2); }
.sitefoot__legal { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0 var(--space-3); }
.sitefoot__legal li { margin: 0; padding: 0; }
.sitefoot__legal a { display: inline-flex; align-items: center; min-height: 32px; }
@media (max-width: 760px) {
  .sitefoot__grid { grid-template-columns: 1fr 1fr; gap: var(--space-3); padding-block: var(--space-3); }
  .sitefoot__agence { grid-column: 1 / -1; }
  .sitefoot__map, .sitefoot__map img { width: 120px; }
  .sitefoot__partners ul { gap: var(--space-2); }
  .sitefoot__partners img { max-height: 32px; }
}

/* → Offre personnalisée: css/components.client.css.php (customer-only) */

