/* 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;
}


/* ── Add-to-cart cross-sell modal (panier/add → complementary products) ── */
/* treact's .treact-modal is width:500px → only ~2 cards fit and the modal gets
   very tall. On wider viewports widen THIS modal so the grid can go to 3 columns.
   (Guarded by min-width so phones keep treact's responsive width.) */
@media (min-width: 700px) {
  #modal-compl .treact-modal { width: 720px; }   /* capped by treact's max-width:90% */
}
.compl-modal { min-width: 300px; }
.compl-main {
  display: flex; gap: 12px; align-items: center;
  padding: 4px 4px 14px; border-bottom: 1px solid #eee;
}
.compl-main__info { line-height: 1.3; }
.compl-title { margin: 14px 0 8px; font-weight: 700; color: #1d2939; }
/* Recommended products now use the canonical .associe tile (product_tile8) so the cross-sell
   matches the tiles used everywhere else on the site. The tile is a fixed 168px — wrap and
   centre them in the modal; they drop to fewer per row as it narrows. */
.compl-tiles { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.compl-added { color: #138a3e; font-weight: 700; font-size: 13px; margin-bottom: 2px; }
.compl-actions {
  margin-top: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: flex-end;
  align-items: center;
}
.compl-actions .btn-link {
  background: none; border: none; color: #667085;
  font-weight: 700; cursor: pointer; padding: 8px 4px; margin-right: auto;
}
.compl-actions .btn-link:hover { color: #1d2939; }
.compl-actions .btn-outline {
  background: #fff; border: 1px solid #138a3e; color: #138a3e;
  border-radius: 8px; padding: 9px 14px; font-weight: 700; cursor: pointer;
}
.compl-actions .btn-outline:hover { background: #f3f8f1; }


/* ── Cart-view modal (panier/show): thumb · info · qty stepper · PU · subtotal · remove ── */
@media (min-width: 700px) {
  #modal-panier .treact-modal { width: 640px; }   /* room for the line layout (capped by max-width:90%) */
}
.cart-modal { min-width: 300px; }
.cart-modal__head { font-size: 13px; color: #555; margin-bottom: 10px; }
.cart-modal__note { margin-top: 12px; font-size: 12px; color: #888; }
.cart-row__pu s { display: block; font-size: 12px; color: #667085; font-weight: 400; }   /* list price struck through above the net (#667085 = 5:1 on white; #98a2b3 was 2.6:1) */
.cart-row__stock { margin-top: 3px; font-size: 12px; }
.cart-stockpct { display: inline-flex; align-items: center; gap: 4px; color: #B54708; font-weight: 700; white-space: nowrap; }
.cart-stockpct .icon { width: 14px; height: 14px; }
.cart-stockpct.is-full { color: var(--green-cart-dark, #2f7d22); }
/* Cart actions: [Sauvegarder comme devis] (secondary) · [Commander] (primary, green = buy) */
.tnl-tot__row--free b { color: var(--green-cart-dark, #2f7d22); }
.tnl-tot__hint { display: flex; align-items: center; gap: 8px; margin-top: 8px; padding: 8px 10px; background: #fff8e6; color: #7a4b00; font-size: 13px; line-height: 1.4; }
.tnl-tot__hint .icon, .tnl-portrule .icon { width: 18px; height: 18px; flex: none; }
.tnl-portrule { display: flex; align-items: center; gap: 8px; margin: 0 0 var(--space-3); }
/* Franco de port progress (lib/panier.php panier_franco_html) — amber = "almost there", green = reached */
.cart-franco { position: relative; display: flex; align-items: center; gap: 10px; margin-top: var(--space-3); padding: 10px 12px 14px;
  background: #fff8e6; color: #7a4b00; font-size: 14px; line-height: 1.4; }
.cart-franco .icon { width: 20px; height: 20px; flex: none; }
.cart-franco small { color: #9a6a1c; white-space: nowrap; }
.cart-franco__bar { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; background: rgba(122,75,0,.15); }
.cart-franco__bar::after { content: ''; position: absolute; inset: 0 auto 0 0; width: var(--p); background: #F39200; transition: width .35s cubic-bezier(.22,1,.36,1); }
.cart-franco.is-ok { background: #ECF7E8; color: #2f7d22; padding-bottom: 10px; }
.cart-modal:has(.cart-empty) .cart-franco { display: none; }
.cart-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-3); }
.cart-actions button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 16px;
  font: inherit; font-weight: 700; font-size: 15px; cursor: pointer; border-radius: 0; }
.cart-actions button .icon { width: 18px; height: 18px; }
.cart-actions__save { background: #fff; color: #344054; border: 1px solid #d0d5dd; }
.cart-actions__save:hover { background: #f2f4f7; }
.cart-actions__order { background: var(--green-cart, #4AAA35); color: #fff; border: 0; }
.cart-actions__order:hover { filter: brightness(.95); }
.cart-modal:has(.cart-empty) .cart-actions { display: none; }   /* last line removed → nothing to order */
@media (max-width: 960px) { .cart-actions button { flex: 1 1 auto; } }

.cart-rows { display: flex; flex-direction: column; }
.cart-row {
  display: grid;
  grid-template-columns: 56px minmax(110px, 1fr) auto auto auto 30px;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid #f0f0f0;
}
.cart-row__img { width: 56px; height: 56px; display: flex; align-items: center; justify-content: center; }
.cart-row__img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cart-row__info { min-width: 0; }
.cart-row__ref  { font-weight: 700; font-size: 13px; }
.cart-row__desc { font-size: 12px; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cart-row__pu   { font-size: 12px; color: #777; text-align: right; white-space: nowrap; }
.cart-row__sub  { font-weight: 700; text-align: right; white-space: nowrap; min-width: 64px; }
.cart-row__del  {
  width: 28px; height: 28px; border: 0; background: none; cursor: pointer;
  color: #b0b0b0; font-size: 22px; line-height: 1; border-radius: 6px;
}
.cart-row__del:hover { color: #c0392b; background: #fbeceb; }

/* compact stepper inside a cart row */
.cart-row .qty-stepper { height: 36px; }
.cart-row .qty-minus, .cart-row .qty-plus { width: 32px; font-size: 16px; }
.cart-row .qtyinput { width: 40px; font-size: 14px; }

/* Desktop: share ONE set of column tracks across all rows via subgrid, so the
   qty / PU / subtotal columns line up perfectly even though their content widths
   differ row to row (each row was previously its own grid → ragged columns).
   Scoped to >560px so the mobile area-layout below is untouched; falls back to the
   per-row grid above where subgrid is unsupported. */
@media (min-width: 561px) {
  @supports (grid-template-columns: subgrid) {
    .cart-rows {
      display: grid;
      grid-template-columns: 56px minmax(110px, 1fr) auto auto auto 30px;
      column-gap: 10px;
    }
    .cart-row {
      grid-column: 1 / -1;
      grid-template-columns: subgrid;
      gap: 0 10px;
    }
  }
}

/* devis section-title pseudo-line (__WARKTITRE__): a full-width heading row that
   breaks out of the subgrid tracks (spans all columns as one cell). */
.cart-row--title {
  grid-column: 1 / -1;
  grid-template-columns: none !important;
  display: block;
  padding: var(--space-2) 0 var(--space-0);
  font-weight: 800; color: var(--brand); border-bottom: 0;
}

/* ── Devis line "options" mini add-to-cart dropdown (devis_options_widget) ──
   Reuses the .split-button/.dropdown-menu component (js/javascript.js openMenu),
   overriding only what differs: a light link-style toggle (not the heavy green
   split half) and a roomy, scrollable, wrapping menu for the option cards. */
.dropdown-toggle.opt-toggle {
  background: none; border: 0; box-shadow: none;
  color: #144A8E; font-weight: 700; font-size: .78em;
  padding: var(--space-0) 0;
}
.dropdown-toggle.opt-toggle:hover { background: none; box-shadow: none; color: #0f2f5e; }

.dropdown-menu.opt-menu {
  white-space: normal;                 /* override .dropdown-menu's nowrap */
  left: 0; right: auto;                /* drop into the content area, not 300px left over the sidebar */
  width: min(300px, calc(100vw - 2 * var(--space-3)));   /* never wider than the viewport on a phone */
  min-width: 0;
  max-height: 60vh; overflow-y: auto;
  padding: 0;                          /* title bar bleeds edge-to-edge; inner blocks pad themselves */
}
/* gradient "title bar" — gives the popover an OS-window feel (brand gradient) */
/* Shared popover/card "title bar" — the OS-window look (brand gradient header).
   Reused by the options dropdown, the devis action & help cards, etc. */
.cardbar {
  background: var(--brand-gradient);
  color: #fff; font-weight: 700; font-size: 13px;
  padding: var(--space-1) var(--space-2);
}
.opt-menu .opt-panel { display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-1) var(--space-2); }
.opt-row {
  display: grid; grid-template-columns: 40px 1fr auto;
  align-items: center; gap: var(--space-1);
  padding: var(--space-1) 0; border-bottom: 1px solid #eee;   /* hairline separator, not a panel outline */
}
.opt-row__img { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; }
.opt-row__img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.opt-row__info { min-width: 0; }
.opt-row__ref   { font-weight: 700; font-size: 12px; }
.opt-row__price { color: #138a3e; font-weight: 700; font-size: .9em; }
.opt-row .qty-stepper { height: 36px; }
.opt-row .qty-minus, .opt-row .qty-plus { width: 30px; font-size: 16px; }
.opt-row .qtyinput { width: 38px; font-size: 14px; }
.opt-menu .opt-commit { list-style: none; margin: 0; padding: 0; }
/* add-to-cart action → GREEN (house convention: matches the product page "Ajouter au Panier") */
.opt-menu .opt-commit li {
  background: var(--green-cart); color: #fff;
  text-align: center; font-weight: 700; padding: var(--space-2);
}
.opt-menu .opt-commit li:hover { background: var(--green-cart-dark); color: #fff; }

/* ── Product puncher — drill-down catalog modal to add products to a devis ──
   Categories grid → product rows (each a .productcard so recalcAll() can filter it).
   House rules: square, var(--space-*), var(--shadow-panel); add buttons = green. */
.puncher-launch {                                   /* trigger button atop the Produits island */
  display: flex; align-items: center; justify-content: center; gap: var(--space-1);
  width: 100%; margin-bottom: var(--space-2); padding: var(--space-2);
  background: #f3f8f1; border: 1px dashed var(--green-cart); color: var(--green-cart-dark);
  font-weight: 700; font-size: 14px; cursor: pointer;
}
.puncher-launch:hover { background: #e7efe4; }

@media (min-width: 700px) { #modal-puncher .treact-modal { width: min(860px, 94vw); } }
/* Let the modal body be the SINGLE scroll area (bounded by the modal's max-height:95vh) instead of
   a nested #puncher-body max-height:56vh, which on shorter screens extended past the clipped modal
   and masked the last rows. Drop the modal's default 20px padding — the puncher manages its own. */
#modal-puncher .treact-modal-body { padding: 0; }
.puncher { display: flex; flex-direction: column; }
/* The crumb + search bar stays put while the results scroll under it (it lives OUTSIDE the scrolled
   content now, so it survives the body swaps too). */
.puncher__bar { position: sticky; top: 0; z-index: 3; background: #fff; display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; padding: var(--space-2); border-bottom: 1px solid #eee; }
.puncher-crumb { flex: 1 1 220px; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); font-size: 13px; }
.puncher-crumb__item { background: none; border: 0; padding: 0; cursor: pointer; color: #144A8E; font-weight: 700; font-size: 13px; }
.puncher-crumb__item--current { color: #1d2939; cursor: default; }
.puncher-crumb__sep { color: #bbb; }   /* slash separator, matching the site breadcrumbs */
/* Search field mirrors the header search: magnifier on the left, bold (placeholder) text. */
.puncher__searchbox { position: relative; display: flex; }
.puncher__searchico { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); display: flex; color: #888; pointer-events: none; }
.puncher__searchico .icon { width: 18px; height: 18px; }
.puncher__search { padding: var(--space-1) var(--space-2); border: 1px solid #d0d5dd; min-width: 200px; background: #fff; font-weight: bold; }
.puncher__searchbox .puncher__search { padding-left: 36px; }   /* clear the icon (beats input[type=search] specificity) */
#puncher-body { padding-bottom: var(--space-2); }   /* breathing room under the last row (modal body scrolls) */

/* "Search from this point" scope toggle (segmented control: this branch vs whole catalog). */
.puncher-scope { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); padding: var(--space-2); border-bottom: 1px solid #eee; font-size: 13px; }
.puncher-scope__lbl { display: inline-flex; align-items: center; gap: 4px; color: #667085; margin-right: var(--space-1); }
.puncher-scope__lbl .icon { width: 14px; height: 14px; }
.puncher-scope__opt { appearance: none; -webkit-appearance: none; border: 1px solid #d0d5dd; background: #fff; color: #344054; padding: 3px var(--space-2); cursor: pointer; font-size: 12px; font-weight: 700; line-height: 1.4; }
.puncher-scope__opt:hover { background: #f6f7f9; }
/* Keep a real 1px border on the active pill (not transparent) so both segments stay the same
   size/aligned and no native button bevel shows through. Brand border ties it to the gradient. */
.puncher-scope__opt.is-active { background: var(--brand-gradient); color: #fff; border-color: var(--brand); }

.puncher-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-2); padding: var(--space-2); }
/* Tiles mirror the megamenu cards: gradient label bar on top, white picture area below. */
.puncher-tile { display: grid; grid-template-rows: auto 1fr; min-height: 116px; background: #fff; box-shadow: var(--shadow-panel); border: 0; padding: 0; cursor: pointer; text-align: center; overflow: hidden; }
.puncher-tile:hover { box-shadow: 0 2px 9px rgba(0,0,0,.20); }
.puncher-tile__label { background: var(--brand-gradient); color: #fff; font-weight: 700; font-size: 13px; line-height: 1.2; padding: var(--space-1) var(--space-2); }
.puncher-tile__img { display: flex; align-items: center; justify-content: center; padding: var(--space-2); }
.puncher-tile__img img { max-height: 72px; max-width: 100%; object-fit: contain; }

.pfilter { font-size: 13px; cursor: pointer; white-space: nowrap; }
.pfilter--brand img { max-height: 20px; max-width: 60px; vertical-align: middle; }

/* Results layout: the facet column is ALWAYS the fixed-width left column (collapsed = just the
   "Filtres" toggle, open = toggle + facets below it) — so the toggle belongs to that column and
   never jumps from full-width to the side when opened. Reuses the category/search filter engine
   (scoped puncherRecalc), light-themed for the white modal. */
.puncher-results { display: grid; grid-template-columns: 200px 1fr; gap: var(--space-2); align-items: start; padding: var(--space-2); }
@media (max-width: 700px) {
  .puncher-results { grid-template-columns: 1fr; }   /* stack: the Filtres toggle (full width) above the rows */
}
.puncher-rows-col { min-width: 0; }                     /* let the rows shrink/scroll, not overflow */
.puncher-filtres { font-size: 12px; color: #1d2939; }   /* NOT .filtres (that's white-on-dark for the category sidebar) */

/* "Filtres" toggle (the panel header) + active-filter hint badge */
.puncher-filtres__toggle {
  display: flex; align-items: center; gap: var(--space-1); width: 100%;
  padding: var(--space-1) var(--space-2);
  background: #f4f6f8; border: 1px solid #d0d5dd; cursor: pointer;
  font-size: 13px; font-weight: 700; color: #1d2939;
}
.puncher-filtres__toggle:hover { border-color: var(--brand); }
.puncher-filtres__toggle.is-active { border-color: var(--brand); background: #eef3fb; }
.puncher-filtres__toggle .icon { width: 16px; height: 16px; }
.puncher-filtres__hint {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px;
  background: #cbd2da; color: #fff; font-size: 11px; font-weight: 700;   /* neutral grey when 0 (not a color swatch) */
}
.puncher-filtres__hint.is-active { background: var(--brand); }            /* brand-colored only when filters are active */
.puncher-filtres__chev { margin-left: auto; display: flex; transition: transform .15s ease; }
.puncher-results.filters-open .puncher-filtres__chev { transform: rotate(180deg); }
.puncher-filtres__body { margin-top: var(--space-1); }
/* Compact, light group headers (override the dark #41434E .secondary sidebar look — too heavy in
   the white modal). Tight padding so the panel scans fast. */
.puncher-filtres__body .secondary { background: #eef1f4; color: #344054; }
.puncher-filtres__body .secondarytitle { font-size: 12px; padding: 3px var(--space-1); margin: 0; }
.puncher-filtres__body .titrefiltre { margin: 0; }
.puncher-filtres__body .filtre { margin-bottom: 2px; }
.puncher-filtres__body .filtrecontent { margin-left: var(--space-2); font-size: 12px; }
.puncher-filtres__body .facet-brandgrid { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; padding-left: var(--space-2); }

.puncher-rows { display: flex; flex-direction: column; padding: 0 var(--space-2); }
.puncher-row { display: grid; grid-template-columns: 48px 1fr auto; gap: var(--space-2); align-items: center; padding: var(--space-2) 0; border-bottom: 1px solid #f0f0f0; }
.puncher-row__img { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; }
.puncher-row__img img { max-width: 100%; max-height: 100%; object-fit: contain; }
.puncher-row__info { min-width: 0; }
.puncher-row__ref { font-weight: 700; font-size: 13px; }
.puncher-row__desc { font-size: 12px; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.puncher-row__meta { font-size: 12px; color: #777; margin-top: 2px; }
/* ── .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); }

.puncher-row__add { display: flex; align-items: center; gap: var(--space-1); }
/* Match the devis (.cart-row) stepper exactly — without these the input falls back to the
   wide 56px default + 44px buttons, so it looked stretched next to the compact devis one. */
.puncher-row__add .qty-stepper { height: 36px; }
.puncher-row__add .qtyinput { width: 40px; font-size: 14px; }
.puncher-row__add .qty-minus, .puncher-row__add .qty-plus { width: 32px; font-size: 16px; }
.puncher-row__add .cta { width: auto; padding: var(--space-1) var(--space-2); }

.pbadge { display: inline-block; font-size: 10px; font-weight: 700; padding: 1px var(--space-0); margin-left: var(--space-0); }
.pbadge--deja { color: #8a6d3b; background: #fdf3e6; }
.pbadge--devis { color: #138a3e; background: #f3f8f1; }
.puncher-empty, .puncher-note { padding: var(--space-3); text-align: center; color: #888; font-size: 13px; }
/* Section headers in the puncher search results (Catégories / Produits). */
.puncher-section { padding: var(--space-2) var(--space-2) 4px; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: #b08a5a; }

@media (max-width: 560px) {
  .puncher-row { grid-template-columns: 40px 1fr; }
  .puncher-row__add { grid-column: 1 / -1; justify-content: flex-end; }
}

.cart-total { margin-top: 12px; text-align: right; font-size: 15px; color: #555; }
.cart-total b { font-size: 18px; color: #1d2939; margin-left: 6px; }
.cart-empty { padding: 28px 8px; text-align: center; color: #888; }

/* stack to a compact 3-column layout on narrow screens */
@media (max-width: 560px) {
  .cart-row {
    grid-template-columns: 48px 1fr auto;
    grid-template-areas:
      "img info del"
      "img qty  sub"
      "img qty  pu";
    column-gap: 8px; row-gap: 2px;
  }
  .cart-row__img  { grid-area: img; width: 48px; height: 48px; }
  .cart-row__info { grid-area: info; }
  .cart-row__qty  { grid-area: qty; }
  .cart-row__sub  { grid-area: sub; align-self: center; }
  .cart-row__del  { grid-area: del; justify-self: end; }
  /* Unit price kept on phones (the client's net — the point of the cart — with the list price struck through), as
     a small line under the subtotal: "687,50 € 323,13 € / u". */
  .cart-row__pu   { grid-area: pu; justify-self: end; font-size: 12px; }
  .cart-row__pu s { display: inline; margin-right: 6px; }
  .cart-row__pu::after { content: ' / u'; color: #667085; font-weight: 400; }
}

/* ── 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;
}

/* 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; }

/* ── 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_image() 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; }

/* 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; }

/* ── Commande rapide / quick-order pad (handlers/commande/quickorder*) ── */
#modal-quickorder .treact-modal { width: 560px; }   /* capped by treact's max-width:90% */

.quickorder-bar { display: flex; justify-content: flex-end; padding: var(--space-2) 0; }

.qo-intro { margin: 0 0 var(--space-2); color: #555; }
.qo-input {
  width: 100%; box-sizing: border-box; resize: vertical;
  padding: var(--space-2); border: 1px solid #c3ccd2; background: #fff;
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .95em; line-height: 1.5;
}
.qo-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(0,0,17,.08); }

.qo-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }

/* Green action button — the add-to-cart/devis convention (var(--green-cart)). */
.qo-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;
}
.qo-btn:hover { background: var(--green-cart-dark); }
.qo-btn .icon { width: 1.1em; height: 1.1em; }
.qo-btn--lg { padding: var(--space-2) var(--space-4); }

.qo-result { margin-top: var(--space-3); }
.qo-empty { color: #888; font-style: italic; margin: 0; }

.qo-table { width: 100%; border-collapse: collapse; font-size: .92em; }
.qo-table td { padding: var(--space-1) var(--space-1); border-bottom: 1px solid #f0f0f0; vertical-align: middle; }
.qo-ico { width: 1.5em; }
.qo-row--ok .qo-ico { color: #1b7a1b; }
.qo-row--ko .qo-ico { color: #aa1b1b; }
.qo-ref { font-weight: 700; }
.qo-desc { display: block; margin-top: 2px; font-weight: 400; font-size: .88em; color: #777; }
.qo-qte { text-align: right; white-space: nowrap; font-weight: 700; }
.qo-price { text-align: right; white-space: nowrap; }
.qo-notfound { color: #aa1b1b; font-style: italic; }

.qo-done { color: #1b4a1b; font-weight: 700; display: flex; align-items: center; gap: var(--space-1); }
.qo-done .icon { width: 1.2em; height: 1.2em; }
.qo-warn { margin-top: var(--space-2); color: #8a5a00; }
/* quick-order recognised-line thumbnail */
.qo-thumb { width: 44px; }
.qo-thumb img { display: block; max-width: 40px; max-height: 40px; }
.qo-stock { white-space: nowrap; font-size: .9em; }
/* not-found → punch-in (open the puncher pre-searched) */
.qo-punch {
  display: inline-flex; align-items: center; gap: var(--space-0);
  background: none; border: 1px solid #c3ccd2; color: #555;
  padding: 1px var(--space-1); font-size: .85em; cursor: pointer; vertical-align: middle;
}
.qo-punch:hover { border-color: var(--brand); color: var(--brand); }
.qo-punch .icon { width: 1em; height: 1em; }
/* quick-order: persistent banner shown once a punch-in replaced a reference */
.qo-warnbanner {
  display: flex; align-items: center; gap: var(--space-1);
  margin: var(--space-1) 0 var(--space-2);
  padding: var(--space-1) var(--space-2);
  background: #fff7e6; border: 1px solid #e3c878; color: #8a5a00; font-size: .9em;
}
.qo-warnbanner .icon { width: 1.2em; height: 1.2em; flex: 0 0 auto; }

/* ─────────────────────────── order tunnel (commande wizard) ─────────────────────────── */
@media (min-width: 700px) { #modal-commande .treact-modal { width: 640px; } }
.tnl { display: flex; flex-direction: column; }

/* stepper */
.tnl-steps { display: flex; align-items: center; padding: var(--space-2) var(--space-3); background: #f6f7f9; border-bottom: 1px solid #e6e8eb; }
.tnl-step { display: flex; align-items: center; gap: var(--space-1); white-space: nowrap; color: #98a2b3; font-size: 13px; font-weight: 700; }
.tnl-step:not(:last-child) { flex: 1 1 auto; }
.tnl-step:not(:last-child)::after { content: ''; flex: 1 1 auto; height: 2px; background: #dcdfe4; margin: 0 var(--space-2); min-width: 14px; }
.tnl-step__num { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; flex: 0 0 auto; background: #dcdfe4; color: #fff; font-size: 12px; }
.tnl-step__num .icon { width: 14px; height: 14px; }
.tnl-step.is-active { color: #1d2939; }
.tnl-step.is-active .tnl-step__num { background: var(--green-cart); }
.tnl-step.is-done { color: #475467; }
.tnl-step.is-done .tnl-step__num { background: var(--green-cart-dark); }
.tnl-step.is-done:not(:last-child)::after { background: var(--green-cart); }
@media (max-width: 520px) { .tnl-step__lbl { display: none; } .tnl-step.is-active .tnl-step__lbl { display: inline; } }   /* phones: only the CURRENT step is named ("② Livraison") */

/* panes / fields */
.tnl-pane { padding: var(--space-3); max-height: 58vh; overflow-y: auto; }
.tnl-field { margin-bottom: var(--space-2); }
.tnl-field > label { display: block; font-weight: 700; font-size: 13px; margin-bottom: var(--space-1); color: #344054; }
.tnl-opt { font-weight: 400; color: #98a2b3; }
.tnl-input { width: 100%; padding: var(--space-1) var(--space-2); border: 1px solid #d0d5dd; font-size: 14px; font-family: inherit; box-sizing: border-box; }
.tnl-input:focus { outline: none; border-color: var(--green-cart); }
textarea.tnl-input { resize: vertical; }
.tnl-row { display: flex; gap: var(--space-2); }
.tnl-row .tnl-field { flex: 1 1 0; min-width: 0; }
@media (max-width: 520px) { .tnl-row { flex-direction: column; gap: 0; } }
.tnl-terms { display: flex; align-items: center; gap: var(--space-1); padding: var(--space-2); background: #f6f7f9; color: #344054; font-weight: 700; }   /* read-only info (single carrier, payment terms): neutral, so it doesn't look like a choice to tap */
.tnl-terms .icon { color: var(--brand); }
.tnl-terms .icon { width: 1.1em; height: 1.1em; flex: 0 0 auto; }
.tnl-help { font-size: .85em; color: #667085; margin-top: var(--space-1); }

/* recap lines + totals */
.tnl-line { display: grid; grid-template-columns: 40px 1fr auto auto; align-items: center; gap: var(--space-2); padding: var(--space-1) 0; border-bottom: 1px solid #f0f1f3; }
.tnl-line__img { width: 40px; height: 40px; display: grid; place-items: center; }
.tnl-line__img img { max-width: 40px; max-height: 40px; }
.tnl-line__ref { display: flex; flex-direction: column; min-width: 0; font-weight: 700; font-size: 13px; }
.tnl-line__desc { font-weight: 400; color: #667085; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tnl-line__qty { color: #475467; font-size: 13px; }
.tnl-line__sub { font-weight: 700; font-size: 13px; white-space: nowrap; }
.tnl-line--title { display: block; border: 0; padding: var(--space-2) 0 var(--space-1); color: #1d2939; }
.tnl-tot { margin: var(--space-2) 0 0 auto; max-width: 320px; }
.tnl-tot__row { display: flex; justify-content: space-between; gap: var(--space-3); padding: 2px 0; font-size: 14px; color: #475467; }
.tnl-tot__row--ttc { margin-top: var(--space-1); padding-top: var(--space-1); border-top: 1px solid #e6e8eb; font-size: 16px; color: #1d2939; }

/* validation summary */
.tnl-summary { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.tnl-sum { flex: 1 1 220px; border: 1px solid #e6e8eb; padding: var(--space-2); background: #fff; }
.tnl-sum__h { display: flex; align-items: center; gap: var(--space-1); font-weight: 700; font-size: 13px; color: var(--green-cart-dark); margin-bottom: var(--space-1); }
.tnl-sum__h .icon { width: 1.1em; height: 1.1em; }
.tnl-sum__addr { font-size: 13px; color: #344054; line-height: 1.35; }
.tnl-sum__meta { font-size: 12px; color: #667085; margin-top: var(--space-1); }
.tnl-sum__note { font-size: 12px; color: #475467; margin-top: var(--space-1); font-style: italic; }
.tnl-cgv { margin-top: var(--space-2); font-size: .85em; color: #667085; }
.tnl-cgv a { color: inherit; text-decoration: underline; }
.tnl-stock { display: flex; align-items: center; gap: 8px; margin-top: var(--space-2); padding: 8px 10px; background: #fff8e6; color: #7a4b00; font-size: 13px; line-height: 1.4; }
.tnl-stock .icon { width: 18px; height: 18px; flex: none; }
.tnl-portrule.is-free { color: var(--green-cart-dark, #2f7d22); }
.tnl-nav button { white-space: nowrap; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
/* Phones (app sheet + mobile website): the tunnel sheet keeps ONE height across steps (it jumped 332→192→299→147 px),
   the step content scrolls and the Précédent/Continuer/Valider bar stays pinned at the bottom. Follows the virtual
   keyboard like every app sheet (--kb, js/app-mode.js). */
@media (max-width: 960px) {
  #modal-commande .treact-modal { height: calc((100dvh - var(--kb, 0px)) * .9); }
  #modal-commande .treact-modal-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding: 0; overflow: hidden; }
  #modal-commande .tnl, #modal-commande #tunnel-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  #modal-commande .tnl-pane { flex: 1 1 auto; min-height: 0; max-height: none; }
  #modal-commande .tnl-nav { flex: none; padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom)); }
  #modal-commande .tnl-nav .primary-button { flex: 1 1 auto; }
  #modal-commande .tnl-done { margin: auto 0; }
}

/* nav bar */
.tnl-nav { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-top: 1px solid #e6e8eb; background: #fafbfc; }
.tnl-nav .icon { width: 1em; height: 1em; vertical-align: -2px; }

/* CB payment (Monetico hosted page embedded — level 2) */
.tnl-pay { display: flex; flex-direction: column; }
.tnl-pay__frame { width: 100%; height: 520px; border: 0; display: block; background: #fafbfc; }
.tnl-pay__note { display: flex; align-items: center; gap: var(--space-1); padding: var(--space-2) var(--space-3); border-top: 1px solid #e6e8eb; background: #f3f8f1; color: var(--green-cart-dark); font-size: 12px; }
.tnl-pay__note .icon { width: 1.1em; height: 1.1em; flex: 0 0 auto; }

/* confirmation */
.tnl-done { text-align: center; padding: var(--space-4) var(--space-3); }
.tnl-done__icon { color: var(--green-cart); line-height: 0; }
.tnl-done__icon .icon { width: 56px; height: 56px; }
.tnl-done__title { margin: var(--space-2) 0 var(--space-1); font-size: 20px; }
.tnl-done__msg { color: #475467; font-size: 14px; max-width: 420px; margin: 0 auto var(--space-3); line-height: 1.4; }
.tnl-done__actions { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; }


/* ── 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 */
}
/* 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; }


/* Rename-offer pencil next to the devis heading (Espace Client, compte.devi.php). */
.devis-head__title { display: flex; align-items: center; gap: 6px; }
.devis-rename {
	flex: 0 0 auto;
	display: inline-flex; align-items: center; justify-content: center;
	width: 28px; height: 28px; padding: 0;
	background: transparent; border: 0; cursor: pointer; color: #98a2b3;
	transition: color .15s ease, background .15s ease;
}
.devis-rename:hover { color: var(--brand); background: rgba(0, 0, 0, .05); }
.devis-rename .icon { width: 16px; height: 16px; }


/* 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; }

/* ── Espace Client ▸ RMA cards (vues/compte/compte.rma.php) ────────────────────────────────────
   Desktop: a 3-column grid of vertical tiles (numsav band → origin affaire → product image →
   ref/desc → N/S → status). css.mobile.php re-flows each card horizontally on phones. Global
   sheet on purpose (always $rand-busted; per-view compte.css can be served stale by turbonav). */
.rma-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin-top: 12px; }
.rma-card { display: flex; flex-direction: column; min-width: 0; }
.rma-card__head { padding: 8px 12px; font-size: 13px; font-weight: 700; }
.rma-card__body { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; }
.rma-card__aff { padding: 4px 6px; background: #333; color: #fff; font-size: 12px; font-variant: small-caps; overflow-wrap: anywhere; }
.rma-card__img { height: 80px; display: flex; align-items: center; justify-content: center; }
.rma-card__ref { font-weight: 700; }
.rma-card__desc { font-size: 12px; color: #475467; max-height: 3em; overflow: hidden; }
.rma-card__sn { font-size: 12px; color: #475467; overflow-wrap: anywhere; }
.rma-card__status:empty { display: none; }
.rma-status { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; font-size: 12px; font-weight: 700; color: #fff; }
.rma-status .icon { width: 14px; height: 14px; }
.rma-status--ok { background: #2e7d32; }
.rma-status--ko { background: #b3261e; }
.rma-card__staff { background: #ccc; font-size: 10px; overflow-x: auto; }
.rma-empty { padding: 12px; margin-top: 12px; color: #98a2b3; font-style: italic; }
@media (max-width: 960px) { .rma-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ── .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; }

/* Outils header dropdown — reuses .acct__menu (hover/focus reveal, gradient frame); the grid sits
   flush inside the frame, so drop its own outer border. */
#outilstile .outils-menu { min-width: 320px; }
.outils-menu .tile-grid { border: 0; }

/* /outils landing + tool pages */
.outils-page { padding-bottom: var(--space-4, 32px); }
.outils-page h1 .icon { width: 1em; height: 1em; vertical-align: -0.12em; }
.outils-intro { margin: 12px 0; color: #fff; }
.outils-page > .tile-grid { max-width: 760px; }
.outils-page > .tile-grid .tile-grid__item { min-height: 140px; padding: 22px 16px; font-size: 15px; justify-content: flex-start; }   /* top-align: icons + titles line up across a row whatever the description length */
.outils-page > .tile-grid .tile-grid__item .icon { width: 34px; height: 34px; }
.outils-tool { max-width: 760px; margin-top: 12px; padding: var(--space-3); display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.outils-tool__desc { margin: 0; color: #1d2939; }
.outils-tool__soon { margin: 0; color: #667085; font-style: italic; display: flex; align-items: center; gap: 6px; }
@media (max-width: 520px) { .outils-page > .tile-grid .tile-grid__desc { display: none; } }

/* ── Outils ▸ Calculateur disque dur (vues/outils/outil.disque-dur.php + outils.js) ──────────────
   Desktop: inputs left, sticky result panel right. Phone: one column, result after the inputs.
   Square, token-spaced; borders = control outlines only. */
.tool { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: var(--space-3); align-items: start; margin-top: 12px; max-width: 1100px; }
.tool__main { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.tool-card__body { padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.tool-f { display: flex; flex-direction: column; gap: 4px; font-size: 13px; font-weight: 600; color: #344054; min-width: 0; }
.tool-f small { font-weight: 400; color: #667085; }
.tool-f input[type=number], .tool-f select {
  width: 100%; box-sizing: border-box; padding: 8px 10px; font: inherit; font-weight: 400; font-size: 14px;
  border: 1px solid #c3ccd2; background: #fff; color: #1d2939;
}
.tool-f input:focus, .tool-f select:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand); }   /* visible square brand ring (was an ~invisible 8% shadow) */
.tool-f input[type=range] { width: 100%; accent-color: var(--brand); }

/* A camera group = one compound row: qty · résolution · compression · ips · débit, subtotal below. */
.cam-groups { display: flex; flex-direction: column; gap: var(--space-2); }
.cam-group {
  position: relative; display: grid; gap: var(--space-2); align-items: end;
  grid-template-columns: 80px minmax(0, 1.3fr) minmax(0, 1.3fr) 100px 120px;
  padding: var(--space-2); padding-right: 44px; background: #f6f7f9;
}
.cam-group__sub { grid-column: 1 / -1; font-size: 12px; color: #667085; }
.cam-group__del {
  position: absolute; top: 6px; right: 6px; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; color: #98a2b3; cursor: pointer;
}
.cam-group__del:hover { color: #b3261e; background: #fbe9e7; }
.cam-group__del[hidden] { display: none; }   /* author display:flex would otherwise beat the [hidden] UA rule (last group can't be removed) */
.cam-group__del .icon { width: 16px; height: 16px; }
.cam-add { align-self: flex-start; }
.tool-hint { margin: 0; font-size: 12px; font-weight: 400; color: #667085; }
.tool-2col { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.tool-f--wide { grid-column: 1 / -1; }

.tool-result { position: sticky; top: var(--space-3); }
.tool-kpi { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 13px; color: #475467; }
.tool-kpi b { color: #1d2939; font-size: 15px; white-space: nowrap; }
.tool-kpi--main { flex-direction: column; align-items: flex-start; padding-bottom: var(--space-2); border-bottom: 1px solid #eaecf0; }
.tool-kpi--main b { font-size: 2rem; line-height: 1.1; color: var(--brand); }
.hdd-sugg__title { font-size: 13px; font-weight: 700; color: #344054; }
.hdd-sugg__title small { font-weight: 400; color: #667085; }
.hdd-sugg ul { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.hdd-sugg li { display: flex; justify-content: space-between; padding: 6px 8px; background: #f6f7f9; font-size: 13px; }
.hdd-sugg li span { color: #667085; }
.tool-result .cta { justify-content: center; }

@media (max-width: 900px) {
  .tool { grid-template-columns: 1fr; }
  .tool-result { position: static; }
}
@media (max-width: 640px) {
  /* Group row → 2 columns: qty + ips on one line, the selects full width, débit full width. */
  .cam-group { grid-template-columns: 1fr 1fr; grid-auto-flow: row dense; }   /* dense: ips back-fills next to Caméras */
  .cam-group .tool-f:nth-child(2), .cam-group .tool-f:nth-child(3), .cam-group .tool-f--rate { grid-column: 1 / -1; }
  .tool-2col { grid-template-columns: 1fr; }
}

/* ── .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); }

/* Disk calculator ▸ matching products (house blocproduit cards, shown by outils.js when their
   capacity is suggested). Spans the calculator's full width; same card grid as the category pages. */
.hdd-prods-wrap { margin-top: var(--space-3); }   /* sibling AFTER the .tool grid (outside the sticky panel's range) */
.hdd-prods { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-3); }

/* ── 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) */

/* ── UI/UX critique quick wins (Outils) ── */
/* #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; }
/* An odd number of tiles: the last one spans the row, so no empty grey cell shows through the gap. */
.tile-grid__item:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* #1 not-yet-built tools: a muted, non-link tile with a square "Bientôt" tag. */
.tile-grid__item.is-soon { color: #98a2b3; cursor: default; }
.tile-grid__item.is-soon:hover { background: #fff; }
.tile-grid__item.is-soon .icon { color: #c3ccd2; }
.tile-grid__soon { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 8px; background: #eaecf0; color: #667085; }
/* #4 in-page jump (ghost) + small link to the full range; green stays for add-to-cart. */
.hdd-jump { width: 100%; justify-content: center; white-space: nowrap; }
.hdd-jump[hidden] { display: none; }
.hdd-alllink { align-self: center; font-size: 13px; color: var(--brand); }
.hdd-prods-wrap:focus { outline: none; }   /* programmatic focus target after the jump */
/* #7 iOS zooms into any field under 16px — keep calculator fields at 16px on phones. */
@media (max-width: 640px) { .tool-f input, .tool-f select { font-size: 16px; } }

/* ── Outils ▸ Calculateur de bande passante (vues/outils/outil.bande-passante.php + outils.js) ──
   Two result blocks (réseau local / accès à distance): figure · usage bar · verdict. Status colours
   = the house status palette (same hues as the stock badges): ok green · warn amber · ko red. */
.bp-block { display: flex; flex-direction: column; gap: 6px; padding-bottom: var(--space-2); border-bottom: 1px solid #eaecf0; }
.bp-block .tool-kpi--main { border-bottom: 0; padding-bottom: 0; }
.bp-block .tool-kpi--main small { font-weight: 400; color: #667085; }
.bp-bar { height: 8px; background: #eaecf0; overflow: hidden; }
.bp-bar span { display: block; height: 100%; width: 0; background: #c3ccd2; transition: width .2s ease; }
.bp-bar span.is-ok   { background: #3d7a00; }
.bp-bar span.is-warn { background: #8a6a00; }
.bp-bar span.is-ko   { background: #b3261e; }
@media (prefers-reduced-motion: reduce) { .bp-bar span { transition: none; } }
.bp-verdict { font-size: 13px; font-weight: 600; color: #667085; min-height: 1.2em; }
.bp-verdict.is-ok   { color: #3d7a00; }
.bp-verdict.is-warn { color: #8a6a00; }
.bp-verdict.is-ko   { color: #b3261e; }
.bp-advice { margin: 0; padding: 8px 10px; font-size: 13px; background: #fdf3e6; color: #6b4a00; }

/* ── Outils ▸ sticky mobile result bar (lib/outils.php outils_sticky_html + outils.js watchResult) ──
   Only ≤900px, where the result panel stacks below the inputs. Pinned to the bottom edge, square,
   white + panel shadow; sits under the cookie banner (9999). Hidden (JS) while the panel is visible. */
.tool-sticky { display: none; }
@media (max-width: 900px) {
  .tool-sticky:not([hidden]) {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 900;
    display: flex; align-items: center; gap: 10px;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
    background: #fff; box-shadow: 0 -4px 16px rgba(0,0,0,.18);
  }
  .tool-wrap { padding-bottom: 64px; }                 /* the last input is never hidden behind the bar */
}
.tool-sticky__text { flex: 1 1 auto; min-width: 0; font-weight: 800; color: var(--brand); font-size: 15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tool-sticky__mark { flex: 0 0 auto; width: 10px; height: 10px; display: none; }
.tool-sticky__mark.is-ok   { display: block; background: #3d7a00; }
.tool-sticky__mark.is-warn { display: block; background: #8a6a00; }
.tool-sticky__mark.is-ko   { display: block; background: #b3261e; }
.tool-sticky__btn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; min-height: 44px; padding: 0 12px;
  border: 1px solid #c3ccd2; background: #fff; color: #1d2939; font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
.tool-sticky__btn .icon { width: 16px; height: 16px; }
.tool-sticky__btn:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.tool-result:focus { outline: none; }             /* programmatic focus target after the jump */

/* Outils ▸ "Réinitialiser" — a quiet text button at the foot of the result panel (inputs persist). */
.tool-reset { align-self: center; display: inline-flex; align-items: center; gap: 4px; min-height: 36px; padding: 0 8px;
  border: 0; background: none; color: #667085; font: inherit; font-size: 13px; cursor: pointer; }
.tool-reset:hover { color: #1d2939; text-decoration: underline; }
.tool-reset:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.tool-reset .icon { width: 14px; height: 14px; }

/* ── Outils ▸ Câblage RJ45 (vues/outils/outil.cablage-rj45.php) — diagram + French pin table + rules.
   Colour chips are square (house rule); "blanc / X" conductors = diagonal white/colour stripes. ── */
.rj45 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-3); align-items: start; margin-top: 12px; max-width: 1100px; }
.rj45-fig { margin: 0; }                                  /* cardbar bleeds edge-to-edge; padding lives on the body */
.rj45-fig__body { padding: var(--space-2); }
.rj45-fig img { display: block; width: 100%; height: auto; }
.rj45-fig figcaption { padding-top: var(--space-1); font-size: 13px; color: #475467; text-align: center; }
.rj45-side { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.rj45-table { width: 100%; border-collapse: collapse; font-size: 14px; color: #1d2939; }
.rj45-table th, .rj45-table td { padding: 7px 12px; text-align: left; border-bottom: 1px solid #eaecf0; }
.rj45-table thead th { font-size: 12px; font-weight: 700; color: #667085; text-transform: uppercase; letter-spacing: .03em; }
.rj45-table tbody th { width: 3.5em; font-weight: 800; color: var(--brand); }
.rj45-table td:nth-child(3) { width: 3.5em; text-align: center; }
.rj45-table td:last-child { color: #475467; font-size: 13px; }
.rj45-chip { display: inline-block; width: 22px; height: 12px; margin-right: 8px; vertical-align: -1px; border: 1px solid rgba(0,0,0,.25); }
.rj45-chip--orange { background: #f07d1a; }
.rj45-chip--green  { background: #2f9e44; }
.rj45-chip--blue   { background: #2b54c4; }
.rj45-chip--brown  { background: #7a4a1e; }
.rj45-chip--w-orange { background: repeating-linear-gradient(135deg, #fff 0 4px, #f07d1a 4px 8px); }
.rj45-chip--w-green  { background: repeating-linear-gradient(135deg, #fff 0 4px, #2f9e44 4px 8px); }
.rj45-chip--w-blue   { background: repeating-linear-gradient(135deg, #fff 0 4px, #2b54c4 4px 8px); }
.rj45-chip--w-brown  { background: repeating-linear-gradient(135deg, #fff 0 4px, #7a4a1e 4px 8px); }
.rj45-note { padding: 10px 12px 12px; }
.rj45-rules { margin: 0; padding: var(--space-3) var(--space-3) var(--space-2) 2.2em; display: flex; flex-direction: column; gap: 8px; font-size: 14px; color: #1d2939; line-height: 1.45; }
.rj45-cta { width: auto; margin: 0 var(--space-3) var(--space-3); }   /* .cta is width:100% — with side margins that overflowed the card */
@media (max-width: 900px) { .rj45 { grid-template-columns: 1fr; } }
@media (max-width: 520px) { .rj45-table th, .rj45-table td { padding: 7px 6px; } .rj45-table td:last-child { font-size: 12px; } }

/* ── Outils: center each page's block on wide screens (it hugged the left edge). The intro line takes
   the SAME max-width + auto margins so its left edge lines up with the block below it; the gradient
   title band stays full width like every other page. Landing grid = 760px, tools = 1100px. ── */
/* page_head8 variants (desktop): the title block matches the width of the content under it */
.pagehead--tool > .pagetitle { max-width: 1100px; margin-left: auto; margin-right: auto; box-sizing: border-box; }
.pagehead--narrow > .pagetitle { max-width: 760px; margin-left: auto; margin-right: auto; box-sizing: border-box; }
.outils-page > .tool-wrap, .outils-page > .rj45, .outils-page > .tile-grid, .outils-page > .outils-tool { margin-left: auto; margin-right: auto; }
/* Tool root = .tool-wrap[data-tool]: the .tool grid (inputs + sticky result) THEN the tool's full-width
   extras (matching products, mobile bar). Keeping those OUT of the grid bounds the sticky panel — as a
   grid item it stuck across the whole grid and slid over the product cards. */
.tool-wrap { max-width: 1100px; }

/* ── Outils ▸ Calculateur de champ de vision (outil.champ-de-vision.php + outils.js fov*) ──
   Two HiDPI canvases (sized to their CSS width in JS), a DORI legend, the DORI reach list.
   DORI chips use the same light fills as the drawing: I red · R orange · O yellow · D green; dead = transparent. */
.fov-canvas-wrap { padding: var(--space-2); }
.fov-canvas { display: block; width: 100%; height: 260px; background: #fff; }
.fov-legend { list-style: none; margin: 0; padding: 0 var(--space-3) var(--space-3); display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 13px; color: #344054; }
.fov-legend li { display: inline-flex; align-items: center; gap: 6px; }
.fov-legend small { color: #667085; }
.fov-chip { display: inline-block; width: 16px; height: 12px; flex: 0 0 auto; border: 1px solid rgba(0,0,0,.2); }
.fov-chip--i { background: rgba(120, 196,  96, .38); }
.fov-chip--r { background: rgba(250, 214,  60, .42); }
.fov-chip--o { background: rgba(247, 150,  60, .38); }
.fov-chip--d { background: rgba(239,  99,  88, .38); }
.fov-chip--dead { background: repeating-linear-gradient(135deg, #fff 0 3px, #c3ccd2 3px 5px); }
.fov-dori { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.fov-dori li { display: flex; align-items: center; gap: 8px; padding: 5px 8px; background: #f6f7f9; font-size: 13px; color: #344054; }
.fov-dori li b { margin-left: auto; color: #1d2939; white-space: nowrap; }
.fov-class { padding: 6px 10px; font-size: 13px; font-weight: 700; color: #1d2939; }
.fov-class--i { background: rgba(120, 196,  96, .25); }
.fov-class--r { background: rgba(250, 214,  60, .3); }
.fov-class--o { background: rgba(247, 150,  60, .25); }
.fov-class--d { background: rgba(239,  99,  88, .25); }
.fov-class--none { background: #eaecf0; color: #475467; font-weight: 600; }
.fov-lensalt { margin-top: -6px; text-align: right; font-size: 12px; color: #667085; }
.fov-lensalt:empty { display: none; }

/* Segmented control (radio group) — house square style, active = brand gradient (same look as
   .puncher-scope__opt.is-active). Markup: .seg[role=radiogroup] > label.seg__opt > input[type=radio] + span.
   The radio stays in the DOM (keyboard arrows + form semantics), visually replaced by its span. */
.seg { display: flex; }
.seg__opt { position: relative; flex: 1 1 0; margin: 0; cursor: pointer; }
.seg__opt input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.seg__opt span { display: flex; align-items: center; justify-content: center; gap: 6px; height: 100%; padding: 6px var(--space-2); border: 1px solid #d0d5dd; background: #fff; color: #344054; font-size: 13px; font-weight: 700; white-space: nowrap; }
.seg__opt + .seg__opt span { border-left-width: 0; }
.seg__opt span .icon { width: 16px; height: 16px; }
.seg__opt:hover span { background: #f6f7f9; }
.seg__opt input:checked + span { background: var(--brand-gradient); color: #fff; border-color: var(--brand); }
.seg__opt input:focus-visible + span { outline: 2px solid var(--brand); outline-offset: 2px; position: relative; z-index: 1; }
.fov-target { margin: var(--space-2) var(--space-2) 0; }

/* FOV angle slider: wide on the LEFT, zoom on the RIGHT (130° → 5°, like a zoom rocker) — the value
   stays the angle, the track is just mirrored. */
.fov-angle { direction: rtl; }
.fov-angle__ends { display: flex; justify-content: space-between; font-weight: 400; color: #667085; }

/* 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); }

/* Outils ▸ PDF export: the result-panel button + the title modal (handlers/outils/pdf.php). */
.tool-pdfbtn { margin-top: var(--space-3); }
.toolpdf { padding: var(--space-3); min-width: min(360px, 80vw); display: flex; flex-direction: column; gap: var(--space-2); }
.toolpdf input[type=text] { height: 40px; padding: 8px 10px; border: 1px solid #d0d5dd; font-size: 15px; width: 100%; box-sizing: border-box; }
.toolpdf input[type=text]:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand); }
.toolpdf__actions { display: flex; justify-content: flex-end; }
.toolpdf__actions .cta { width: auto; padding: var(--space-2) var(--space-3); }

/* Fresnel tool legend chips — same fills as the canvas (outils.js fzDraw): radio zone in blue (its own
   function colour), obstructions in amber (outer 40 %, tolerated) / red (60 % core). */
.fz-chip--zone  { background: rgba(66, 133, 244, .16); }
.fz-chip--sixty { background: rgba(66, 133, 244, .30); }
.fz-chip--edge  { background: rgba(247, 176, 60, .55); }   /* ground in the outer 40 %: tolerated */
.fz-chip--hit   { background: rgba(239, 99, 88, .7); }    /* ground in the 60 % core: a real problem */

/* Fresnel obstacles — one compound row per obstacle (type · hauteur · distance), its verdict line below;
   same row chrome as the camera groups (grey fill, ✕ top-right). */
.fz-obs {
  position: relative; display: grid; gap: var(--space-2); align-items: end;
  grid-template-columns: minmax(0, 1fr) 120px 160px;
  padding: var(--space-2); padding-right: 44px; background: #f6f7f9;
}
.fz-obs__sub { grid-column: 1 / -1; font-size: 12px; font-weight: 600; color: #667085; }
.fz-obs__sub.is-ok   { color: #3d7a00; }
.fz-obs__sub.is-warn { color: #8a6a00; }
.fz-obs__sub.is-ko   { color: #b3261e; }
.fz-obs-empty { margin: 0; }
.fz-obs-empty[hidden] { display: none; }
.fz-chip--tree { background: #a9cf97; }
.fz-chip--bldg { background: #c9ced6; }
@media (max-width: 640px) {
  .fz-obs { grid-template-columns: 1fr 1fr; }
  .fz-obs .tool-f:first-child { grid-column: 1 / -1; }
}

/* Générateur de mot de passe — the password itself: big monospace, breaks anywhere (no word boundaries),
   selectable in one go; seed field with an eye toggle inside it. */
.pw-out { display: block; padding: var(--space-3) var(--space-2); background: #f6f7f9; border: 1px solid #d0d5dd;
  font: 700 20px/1.35 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: #1d2939;
  word-break: break-all; user-select: all; text-align: center; letter-spacing: .5px; }
.pw-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); margin: var(--space-2) 0 var(--space-3); }
.pw-actions .cta[hidden] { display: none; }
.pw-actions:has([data-pw-new][hidden]) .cta { grid-column: 1 / -1; }   /* seed mode: no "Nouveau" → Copier spans */
.pw-mode, .pw-comp { font-size: 12px; color: #667085; margin-top: 6px; }
.pw-opt { font-weight: 400; opacity: .85; }
.pw-seedbox { position: relative; display: block; }
.pw-seedbox input { width: 100%; box-sizing: border-box; padding-right: 44px; height: 40px; border: 1px solid #d0d5dd; padding-left: 10px; font-size: 15px; }
.pw-seedbox input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand); }
.pw-eye { position: absolute; top: 0; right: 0; width: 40px; height: 40px; border: 0; background: none; color: #667085; cursor: pointer;
  display: flex; align-items: center; justify-content: center; }
.pw-eye:hover { color: #1d2939; }
.pw-eye .icon { width: 18px; height: 18px; }

/* Password generator ▸ list mode: mode switch above the options; equipment counts; the list under the tool
   (one card per category, rows = label · password · copy). */
.pw-modeseg { margin-bottom: 0; }   /* .tool__main already spaces its children */
.pw-cats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
.pw-cats .tool-f span .icon { width: 15px; height: 15px; vertical-align: -2px; }
@media (max-width: 640px) { .pw-cats { grid-template-columns: 1fr; } }
.pw-listwrap { margin-top: var(--space-3); margin-bottom: var(--space-4); }   /* breathing room before the footer */
.pw-sectitle { margin-top: 0; }   /* "Paramètres" heads the left column, level with the result panel's top */
.pw-listwrap:focus { outline: none; }
.pw-listwrap > h2 { margin-bottom: var(--space-3); }   /* the heading mustn't sit on the category cards */
.pw-lists { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-3); align-items: start; }
.pw-rows { list-style: none; margin: 0; padding: var(--space-1) 0; }
.pw-rows li { display: grid; grid-template-columns: 64px minmax(0, 1fr) 34px; align-items: center; gap: var(--space-2); padding: 3px var(--space-3); }
.pw-rows li:nth-child(even) { background: #f6f7f9; }
.pw-rows__label { font-size: 12px; font-weight: 700; color: #667085; }
.pw-rows__pw { font: 600 14px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: #1d2939; word-break: break-all; user-select: all; background: none; padding: 0; }
.pw-rows__copy { width: 30px; height: 30px; border: 0; background: none; color: #98a2b3; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.pw-rows__copy:hover { color: #1d2939; background: #eaecf0; }
.pw-rows__copy.is-done { color: #3d7a00; }
.pw-rows__copy .icon { width: 16px; height: 16px; }
.pw-actions .pw-csv { grid-column: 1 / -1; }

/* 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; }

/* ── Comparateur (lib/compare.php · js/compare.js · vues/comparer) ─────────────────────────────── */
/* Card toggle: a small square chip on the product photo (a <button>, so the card click-through ignores it).
   Off = quiet outline; on = brand fill. */
.cmp-toggle--card { position: absolute; top: 70px; left: 8px;   /* photo's top-LEFT: the brand logo sits top-right */
   z-index: 2; width: 32px; height: 32px; padding: 0;
  display: flex; align-items: center; justify-content: center; border: 1px solid #d0d5dd; background: rgba(255,255,255,.92);
  color: #667085; cursor: pointer; transition: background .15s, color .15s, border-color .15s; }
.cmp-toggle--card:hover { color: #1d2939; border-color: #98a2b3; }
.cmp-toggle--card .icon { width: 17px; height: 17px; }
.cmp-toggle.is-on.cmp-toggle--card { background: var(--brand-gradient); border-color: var(--brand); color: #fff; }
.cmp-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* 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__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 .share-btn { width: auto; }
.share-btn[hidden] { display: none; }   /* compact secondary action, not a full-width bar */
.cmp-toggle--page.is-on { border-color: var(--brand); color: var(--brand); }

/* Tray: fixed bottom bar, shell level (survives turbonav). */
/* House dark grey (the grey gradient's darkest stop), like the site chrome — a white bar read as a bolted-on plugin. */
.cmp-tray { position: fixed; left: 0; right: 0; bottom: 0; z-index: 1100; background: var(--grey-gradient-end); box-shadow: 0 -4px 18px rgba(0,0,0,.3);
  border-top: 3px solid transparent; border-image: var(--brand-gradient) 1; }
.cmp-tray[hidden] { display: none; }
.cmp-tray__inner { max-width: 1240px; margin: 0 auto; padding: var(--space-1) var(--space-3); display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.cmp-tray__title { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: #fff; white-space: nowrap; }
.cmp-tray__title small { font-weight: 600; color: rgba(255,255,255,.6); }
.cmp-tray__list { list-style: none; margin: 0; padding: 0; display: flex; gap: var(--space-1); flex: 1 1 auto; min-width: 0; }
.cmp-tray__item { position: relative; display: flex; align-items: center; gap: 6px; height: 48px; padding: 4px 30px 4px 4px;
  background: rgba(255,255,255,.08); min-width: 0; flex: 0 1 190px; }
.cmp-tray__item img { width: 40px; height: 40px; object-fit: contain; background: #fff; flex: 0 0 auto; }
.cmp-tray__ref { font-size: 12px; font-weight: 700; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmp-tray__item--empty { background: none; border: 1px dashed rgba(255,255,255,.22); padding: 0; }
.cmp-tray__rm { position: absolute; top: 2px; right: 2px; width: 26px; height: 26px; border: 0; background: none; color: rgba(255,255,255,.55); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.cmp-tray__rm:hover { color: #fff; background: rgba(217,45,32,.85); }
.cmp-tray__rm .icon { width: 14px; height: 14px; }
.cmp-tray__actions { display: flex; align-items: center; gap: var(--space-2); white-space: nowrap; }
.cmp-tray__go { width: auto; padding: var(--space-1) var(--space-3); }
.cmp-tray__hint { font-size: 13px; color: rgba(255,255,255,.65); }
.cmp-tray__clear { border: 0; background: none; color: rgba(255,255,255,.75); font-size: 13px; text-decoration: underline; cursor: pointer; }
.cmp-tray__note { flex-basis: 100%; font-size: 12px; font-weight: 600; color: #fdb022; }
body.has-cmp-tray footer { padding-bottom: var(--cmp-tray-h, 72px); }   /* the tray never covers the footer's last line */
body.has-cmp-tray #filters-toggle { bottom: calc(14px + var(--cmp-tray-h, 0px)); }   /* mobile category "Filtres" button rides above it */
@media (max-width: 700px) {
  .cmp-tray__inner { gap: var(--space-1); padding: 6px var(--space-2); }
  .cmp-tray__title { font-size: 13px; }
  .cmp-tray__list { order: 3; flex-basis: 100%; }
  .cmp-tray__item { flex: 1 1 0; padding-right: 26px; }
  .cmp-tray__item img { width: 32px; height: 32px; }
  .cmp-tray__ref { display: none; }                      /* 4 thumbnails on a phone: the photo says enough */
  .cmp-tray__actions { margin-left: auto; }
}


/* Comparison page */
.cmp-page { margin-bottom: var(--space-4); }
.cmp-page > h1 { display: flex; align-items: center; gap: 10px; margin-bottom: var(--space-3); }
.cmp-empty { padding: var(--space-3); max-width: 640px; }
.cmp-empty p { margin: 0 0 var(--space-3); line-height: 1.6; }
.cmp-empty__ico .icon { width: 16px; height: 16px; vertical-align: -3px; }
.cmp-empty .cta { width: auto; display: inline-flex; padding: var(--space-2) var(--space-3); }
.cmp-toolbar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-2); color: #fff; font-size: 14px; }
.cmp-diffonly { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; cursor: pointer; }
.cmp-diffonly input { width: 18px; height: 18px; accent-color: var(--brand); }
.cmp-stats { opacity: .9; }
.cmp-clear { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; border: 0; background: none; color: #fff; font-size: 13px; cursor: pointer; text-decoration: underline; }
.cmp-clear .icon { width: 15px; height: 15px; }
.cmp-scroll { overflow-x: auto; background: #fff; box-shadow: var(--shadow-panel); }
.cmp-table { border-collapse: collapse; width: 100%; min-width: calc(180px + var(--cmp-n) * 220px); table-layout: fixed; font-size: 14px; color: #1d2939; }
.cmp-table th, .cmp-table td { padding: 10px 12px; border-bottom: 1px solid #eaecf0; text-align: left; vertical-align: top; }
.cmp-table tbody th { width: 180px; font-weight: 700; color: #475467; background: #f6f7f9; }
.cmp-table thead .cmp-corner { width: 180px; }
.cmp-table tbody th, .cmp-table .cmp-corner { position: sticky; left: 0; z-index: 1; }   /* labels stay put while scrolling sideways */
.cmp-table .cmp-corner { background: #fff; }
.cmp-table tbody tr.is-diff td { background: #fff8e6; }                               /* differing values: soft amber */
.cmp-table tbody tr.is-diff th { box-shadow: inset 3px 0 0 #f0b429; }
.cmp-table.is-diffonly tbody tr:not(.is-diff) { display: none; }
.cmp-na { color: #98a2b3; }
.cmp-prod { position: relative; font-weight: 400; vertical-align: top; }
.cmp-prod > * { display: block; }
.cmp-prod__rm { position: absolute; top: 6px; right: 6px; width: 30px; height: 30px; border: 0; background: none; color: #98a2b3; cursor: pointer; display: flex !important; align-items: center; justify-content: center; }
.cmp-prod__rm:hover { color: #b3261e; background: #fbe9e7; }
.cmp-prod .row-preview { position: absolute; top: 6px; left: 6px; width: 30px; height: 30px; display: flex !important; align-items: center; justify-content: center; }
.cmp-prod .row-preview:hover { background: #f2f4f7; }
.cmp-prod .row-preview .icon { width: 18px; height: 18px; }
.cmp-prod__img { height: 110px; display: flex !important; align-items: center; justify-content: center; margin: 10px 0 8px; }   /* fixed box: refs line up across columns; 10px top: the photo no longer touches the table frame */
.cmp-prod__img img { max-width: min(128px, 100%); max-height: 110px; width: auto !important; height: auto !important; object-fit: contain; }   /* petite_image() sets width/height INLINE → override both or the photo stretches */
.cmp-prod__ref { font-weight: 800; font-size: 15px; color: #1d2939; text-decoration: none; }
.cmp-prod__ref:hover { color: var(--brand); }
.cmp-prod__desc { font-size: 12px; color: #667085; line-height: 1.4; margin: 4px 0 8px; min-height: 3.4em; }
.cmp-prod__price { font-size: 18px; font-weight: 800; color: var(--brand); }
.cmp-prod__stock { margin: 6px 0 8px; font-size: 12px; }
/* Qty + "Ajouter au panier" on ONE line: compact stepper, the add button takes the rest. The column's own width
   decides (container query — 2 products = roomy, 3-4 = narrow): below 360px they stack again, the stepper then
   filling the column like the button (± at the edges, the input in the middle). */
.cmp-prod__buy { display: flex !important; flex-wrap: wrap; align-items: stretch; gap: 6px; container-type: inline-size; }
.cmp-prod__buy .qty-stepper { flex: 0 0 auto; height: auto; }   /* stretches to the add button's height (same line) */
.cmp-prod__buy .split-button { flex: 1 1 auto; min-width: 0; }
@container (max-width: 360px) {   /* stepper ~146 + gap + the button's natural ~190 → below this they can't share a line */
  .cmp-prod__buy > * { flex-basis: 100%; }
  .cmp-prod__buy .qty-stepper { display: flex; width: 100%; height: 44px; }   /* own row: back to the 44px touch height */
  .cmp-prod__buy .qty-stepper input { flex: 1 1 auto; width: auto; min-width: 0; text-align: center; }
}

/* (after the base .cmp-table rules — same specificity, so the later block must be the phone one) */
@media (max-width: 640px) {   /* comparison table on a phone: narrower label + product columns (2 fit ~side by side) */
  .cmp-table { min-width: calc(96px + var(--cmp-n) * 124px); font-size: 13px; }   /* 2 products fit a 390px screen; 3-4 scroll sideways */
  .cmp-table tbody th, .cmp-table thead .cmp-corner { width: 96px; }
  .cmp-table th, .cmp-table td { padding: 8px; }
  .cmp-prod__img { height: 80px; }
  .cmp-prod__img img { max-width: min(100%, 120px); max-height: 80px; }
  .cmp-prod__desc { display: none !important; }
  .cmp-toolbar { color: #fff; gap: var(--space-2); }
  .cmp-clear { margin-left: 0; }
  .cmp-prod__buy .qty-stepper { display: none; }   /* too wide for a 124px column; its input (value 1) still rides the add */
  .cmp-prod__buy .split-button__main { font-size: 12px; padding: 8px 4px; }
  /* a REAL sideways scroller: product columns snap next to the sticky label column, a fade on the right says
     "more columns" (js/javascript.js toggles .is-more-end), the add button is icon-only (no 3-line label) */
  .cmp-scroll { scroll-snap-type: x mandatory; scroll-padding-left: 96px; overscroll-behavior-x: contain; scrollbar-width: none; }
  .cmp-scroll::-webkit-scrollbar { display: none; }
  .cmp-table thead th.cmp-prod { scroll-snap-align: start; }
  /* exactly 2 WHOLE product columns beside the labels at any phone width (no sliver of a 3rd): (scroller − label) / 2.
     The scroller is the screen minus the 8px gutters in the app (16px), minus .content's 20px padding on the website. */
  .cmp-page { --cmp-col: calc((100vw - 40px - 96px) / 2); }
  .app-mode .cmp-page { --cmp-col: calc((100vw - 16px - 96px) / 2); }
  .cmp-table { min-width: calc(96px + var(--cmp-n) * var(--cmp-col)) !important; }
  .cmp-table thead th.cmp-prod { width: var(--cmp-col); }
  .cmp-scroll { --edge-max: 28px; --edge-a: .25; }   /* fade: see .edge-fade (animated) */
  .cmp-prod__buy .split-button__label { display: none; }
  .cmp-prod__buy .split-button__main { display: flex; align-items: center; justify-content: center; }
}

/* ── Site edit mode (GC8-gated, lib/siteedit.php) ─────────────────────────────────────────────────────────
   Deliberately loud (warning amber, the "you are changing the live catalogue" signal), never mistaken for
   customer UI. Bar = shell level; panel = product page; card link + attribute table on listings. */
.siteedit-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 6px 16px; background: #fdf1d0; color: #5c4400;
  border-bottom: 2px solid #f0b429; font-size: 13px; }
.siteedit-bar .icon { width: 16px; height: 16px; }
.siteedit-bar__lock { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; border: 1px solid #c8961c; background: #fff; color: #5c4400;
  font-size: 12px; font-weight: 700; padding: 4px 10px; cursor: pointer; }
.siteedit-bar__lock:hover { background: #fff8e6; }
.siteedit-panel { margin: var(--space-3) 0; box-shadow: 0 0 0 2px #f0b429, var(--shadow-panel); }
.siteedit-panel__bar { background: linear-gradient(90deg, #b7791f, #f0b429); display: flex; align-items: center; gap: 8px; }
.siteedit-panel__bar small { font-weight: 400; opacity: .9; }
.siteedit-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.siteedit-table th, .siteedit-table td { padding: 7px 12px; border-bottom: 1px solid #eaecf0; text-align: left; vertical-align: middle; }
.siteedit-table th { width: 32%; color: #475467; font-weight: 700; background: #f6f7f9; }
.siteedit-actions { width: 76px; white-space: nowrap; text-align: right; }
.siteedit-ico { width: 30px; height: 30px; border: 0; background: none; color: #98a2b3; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
.siteedit-ico:hover { color: #1d2939; background: #eaecf0; }
.siteedit-ico--rm:hover { color: #b3261e; background: #fbe9e7; }
.siteedit-ico .icon { width: 16px; height: 16px; }
.siteedit-edit, .siteedit-add { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.siteedit-add { padding: 12px; background: #fffbf0; }
.siteedit-edit input, .siteedit-add input, .siteedit-add select { height: 36px; border: 1px solid #d0d5dd; padding: 0 10px; font-size: 14px; min-width: 0; }
.siteedit-add select { flex: 1 1 200px; } .siteedit-add input { flex: 1 1 160px; } .siteedit-edit input { flex: 1 1 220px; }
.siteedit-btn { height: 36px; display: inline-flex; align-items: center; gap: 6px; padding: 0 14px; border: 1px solid #d0d5dd; background: #fff; color: #344054; font-weight: 700; font-size: 13px; cursor: pointer; }
.siteedit-btn--ok { background: #b7791f; border-color: #b7791f; color: #fff; }
.siteedit-btn--ok:hover { background: #975a16; }
.siteedit-btn .icon { width: 15px; height: 15px; }
.siteedit-hint { margin: 0; padding: 0 12px 12px; font-size: 12px; color: #667085; background: #fffbf0; border-bottom: 1px solid #f3e3b5; }
.siteedit-empty { color: #667085; font-style: italic; }
.siteedit-cardlink { display: flex; align-items: center; gap: 6px; padding: 5px 8px; background: #fdf1d0; color: #5c4400; font-size: 12px; font-weight: 700; text-decoration: none; }
.siteedit-cardlink .icon { width: 14px; height: 14px; }
.selektorPreview { width: 100%; border-collapse: collapse; font-size: 10px; background: #fffbf0; }
.selektorPreview td { padding: 2px 6px; border-bottom: 1px solid #f3e3b5; color: #5c4400; }

/* Value suggestions (site_edit_suggestions + js/siteedit.js): the VALUE, its product count as a quiet badge. */
.siteedit-combo { position: relative; display: flex; flex: 1 1 160px; min-width: 0; }
.siteedit-edit .siteedit-combo { flex-basis: 220px; }
.siteedit-combo input { width: 100%; }
.siteedit-sugg { position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 50; max-height: 260px; overflow-y: auto;
  background: #fff; box-shadow: 0 6px 20px rgba(0,0,0,.18); border: 1px solid #d0d5dd; }
.siteedit-sugg[hidden] { display: none; }
.siteedit-sugg__opt { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 10px; border: 0; background: #fff;
  color: #1d2939; font-size: 14px; text-align: left; cursor: pointer; }
.siteedit-sugg__opt[hidden] { display: none; }
.siteedit-sugg__opt span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.siteedit-sugg__opt small { flex: 0 0 auto; font-size: 11px; font-weight: 700; color: #667085; background: #f2f4f7; padding: 1px 7px; }
.siteedit-sugg__opt:hover, .siteedit-sugg__opt.is-active { background: #fff8e6; }

/* Comparison page ▸ edit mode: "+" in empty cells → inline picker (handlers/siteedit/cmpcell). */
.cmp-add { width: 24px; height: 24px; margin-left: 4px; padding: 0; border: 1px dashed #c8961c; background: #fffbf0; color: #975a16;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; }
.cmp-add:hover { background: #fdf1d0; border-style: solid; }
.cmp-add .icon { width: 14px; height: 14px; }
.cmp-pick { display: flex; flex-direction: column; gap: 6px; padding: 6px; margin: -4px; background: #fffbf0; box-shadow: 0 0 0 2px #f0b429; }
.cmp-chip { display: inline-flex; align-items: center; gap: 4px; align-self: flex-start; max-width: 100%; padding: 4px 9px; border: 1px solid #c8961c;
  background: #fff; color: #5c4400; font-size: 13px; font-weight: 700; cursor: pointer; text-align: left; }
.cmp-chip:hover { background: #fdf1d0; }
.cmp-chip .icon { width: 12px; height: 12px; flex: 0 0 auto; }
.cmp-pick .siteedit-combo { flex: 0 0 auto; flex-direction: column; }   /* in this COLUMN flex the product page's 160px basis became 160px of HEIGHT */
.cmp-pick .siteedit-combo input { height: 32px; border: 1px solid #d0d5dd; padding: 0 8px; font-size: 13px; }
.cmp-pick .siteedit-sugg { position: static; max-height: 180px; box-shadow: none; }   /* inline: the table's scroll box would clip a floating list */
.cmp-pick__actions { display: flex; gap: 6px; }
.cmp-pick__actions .siteedit-btn { height: 30px; padding: 0 10px; }

/* Outils ▸ customers-only gate (lib/outils.php outils_gate_html) */
.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; } }

/* Mon compte ▸ "Vos outils techniques" (the tools are a customers-only perk) */
.dashboard__tools { background: #fff; box-shadow: var(--shadow-panel); margin-top: var(--space-3); }
.dashboard__tools > .h2 small { font-size: 13px; font-weight: 400; opacity: .9; margin-left: 6px; }
.dashboard__tools .tile-grid { box-shadow: none; }

/* ── 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; }
@media (max-width: 960px) {
  .tiles-pane { padding: 12px; }
  #associescontent, #variantescontent { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  #associescontent .associe, #variantescontent .associe { width: 100%; }
}

/* ── Mon compte ▸ Factures (vues/compte/compte.factures.php). Summary tiles, status segments, rows = PDF links.
   Status hues: amber = to pay, red = overdue, green = paid, slate = credit note — always icon + label, never colour alone.
   The status filter is CSS-only (:has on the radios); !important beats the inline display jQuery's quick-filter fades set. */
.fact-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
.fact-kpi { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2) var(--space-3); border-top: 3px solid #d0d5dd; }
.fact-kpi__lbl { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #667085; }
.fact-kpi__val { font-size: 22px; font-weight: 800; color: #101828; font-variant-numeric: tabular-nums; }
.fact-kpi.is-due  { border-top-color: #dc8a0b; }
.fact-kpi.is-late { border-top-color: #d92d20; }
.fact-kpi.is-late .fact-kpi__val { color: #b42318; }
.fact-bar { flex-wrap: wrap; gap: var(--space-2); }
.fact-seg { flex: 0 1 auto; }
.fact-empty { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3); }
.fact-empty > .icon { width: 28px; height: 28px; color: #98a2b3; }
a.fact-row { color: inherit; text-decoration: none; cursor: pointer; margin-bottom: var(--space-1); transition: box-shadow .15s; }
a.fact-row:hover { box-shadow: 0 4px 14px rgba(0, 0, 0, .12); }
a.fact-row:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.fact-row .la-glance .icon { width: 18px; height: 18px; color: #98a2b3; }
.fact-row .la-amt { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.fact-row .la-amt .detail { font-weight: 400; }
.fact-row.is-avoir .la-amt { color: #475467; }
.fact-st { display: inline-flex; align-items: center; gap: 5px; padding: 3px var(--space-1); font-size: 12px; font-weight: 700; white-space: nowrap; }
.fact-st .icon { width: 14px; height: 14px; }
.fact-st--paid  { background: #ecfdf3; color: #027a48; }
.fact-st--due   { background: #fffaeb; color: #b54708; }
.fact-st--late  { background: #fef3f2; color: #b42318; }
.fact-st--avoir { background: #f2f4f7; color: #344054; }
.fact-pdf { display: inline-flex; align-items: center; gap: 5px; font-weight: 700; color: var(--brand); }
.fact-pdf .icon { width: 16px; height: 16px; }
.fact:has(#fact-f-due:checked)   .fact-row:not(.is-due),
.fact:has(#fact-f-paid:checked)  .fact-row:not(.is-paid),
.fact:has(#fact-f-avoir:checked) .fact-row:not(.is-avoir) { display: none !important; }
@media (max-width: 960px) {
  .fact-sum { grid-template-columns: 1fr 1fr; }
  .fact-sum .fact-kpi:last-child { display: none; }
  .fact-kpi { padding: var(--space-1) var(--space-2); }
  .fact-kpi__val { font-size: 18px; }
  .fact-seg { width: 100%; }
  .account-list .fact-row { grid-template-areas: "id amt" "main main" "brands brands"; }
  .account-list .fact-row .la-act { display: none; }
}

/* 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 { grid-template-rows: 2fr 1fr; }
@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 */
}

/* Site edit ▸ masked product (lib/siteedit.php site_edit_selektor_panel): mask badge + completion gauge, missing
   fields as inline inputs, predefined choices as chips, and the CSS-only "À renseigner" filter (radio + :has). */
.se-mask { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; }
.se-mask .icon { width: 15px; height: 15px; }
.se-gauge { display: inline-block; width: 64px; height: 6px; background: rgba(255,255,255,.35); }
.se-gauge i { display: block; height: 100%; background: #fff; }
.se-filter { padding: 8px 12px; background: #fffbf0; border-bottom: 1px solid #f3e3b5; }
.se-filter .seg__opt { flex: 0 0 auto; }
.se-filter .seg__opt input:checked + span { background: #b7791f; border-color: #b7791f; }
.se-masktable tr.is-missing > th { background: #fdf1d0; color: #5c4400; box-shadow: inset 3px 0 0 #f0b429; }
.se-masktable tr.is-missing input { border-style: dashed; }
.se-masktable .siteedit-edit .siteedit-btn { padding: 0 10px; }
.se-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.se-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border: 1px solid #d0d5dd; background: #fff; color: #344054;
  font-size: 12px; font-weight: 600; cursor: pointer; }
.se-chip:hover { border-color: #b7791f; }
.se-chip.is-on { background: #b7791f; border-color: #b7791f; color: #fff; }
.se-chip .icon { width: 12px; height: 12px; }
.se-sep td { padding: 6px 12px; background: #f2f4f7; color: #667085; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
.se-done { display: none; }
.se-done td { padding: 14px 12px; color: #027a48; font-weight: 700; }
.se-done .icon { width: 16px; height: 16px; vertical-align: -3px; }
#siteedit-selektor:has(#se-f-miss:checked) .se-masktable tr.se-row:not(.is-missing):not(.is-just),
#siteedit-selektor:has(#se-f-miss:checked) .se-masktable tr.se-sep { display: none; }
#siteedit-selektor:has(#se-f-miss:checked):not(:has(tr.is-missing)) .se-done { display: table-row; }

/* ── Encours (GC8/lib/encours.data.php gc8_encours_bar_html — same markup in GC8) — Mon compte dashboard + Factures.
   [ Disponible | Utilisé | En retard ]: the Factures status hues (green paid / amber due / red late), legend carries
   the amounts, square like everything css8. */
.encours { margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3); background: #fff; box-shadow: var(--shadow-panel); }
.encours__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-2); margin-bottom: var(--space-1); }
.encours__head b { font-size: 15px; color: #101828; }
.encours__limit { font-size: 13px; color: #667085; }
.encours__over { margin-left: auto; padding: 2px 8px; font-size: 12px; font-weight: 700; color: #fff; background: #d92d20; }
.encours__bar { display: flex; height: 14px; background: #eaecf0; overflow: hidden; }
.encours__seg { display: block; height: 100%; min-width: 2px; }
.encours__seg + .encours__seg { box-shadow: -2px 0 0 #fff; }
.encours__seg--dispo, .encours__k--dispo i { background: #12b76a; }
.encours__seg--used,  .encours__k--used i  { background: #f79009; }
.encours__seg--late,  .encours__k--late i  { background: #d92d20; }
.encours__legend { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin-top: var(--space-1); font-size: 13px; color: #475467; }
.encours__k { display: inline-grid; grid-template-columns: 10px auto; column-gap: 6px; align-items: start; }   /* ■ | label, sub-line under the label */
.encours__k i { display: block; width: 10px; height: 10px; margin-top: .3em; }
.encours__k b { color: #101828; font-variant-numeric: tabular-nums; }
.encours__k small { grid-column: 2; font-size: 12px; color: #667085; }
.encours--over .encours__k--late b { color: #b42318; }

/* ── 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; }
}

#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 — the app's one-column feed only */
/* 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; }
/* Logout confirm (handlers/deconnexion/confirm.php — app ☰ menu) */
#modal-logout .treact-modal { width: 400px; }
.logout-confirm__txt { margin: 0 0 var(--space-4); line-height: 1.5; color: #344054; }
.logout-confirm__actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
#modal-logout .logout-confirm__actions button { width: auto; height: 46px; display: flex; align-items: center; justify-content: center; gap: 8px;
  font: inherit; font-weight: 700; font-size: 15px; border-radius: 0; cursor: pointer; }
#modal-logout .logout-confirm__cancel { background: #fff; color: #344054; border: 1px solid #d0d5dd; }
#modal-logout .logout-confirm__cancel:hover { background: #f2f4f7; color: #1d2939; }
.logout-confirm__ok { background: #D92D20; color: #fff; border: 0; }
.logout-confirm__ok:hover { background: #B42318; }
.logout-confirm__ok .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 + tr > * { border-top: 1px solid #eaecf0; }
.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; }
@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 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; }
  .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__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: 96px; max-height: 30px; }
.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__all { grid-column: 1 / -1; padding: 8px 10px; font-size: 12px; font-weight: 700; color: var(--brand); text-decoration: 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 */
.typegrid { display: grid; grid-template-columns: repeat(auto-fill, 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; }
@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); }
  /* App catalogue ROOT: Catégories | Marques tabs; Marques = the A–Z list (address book) on the dark overlay */
  .app-mode #menu .menutabs { display: grid; grid-template-columns: 1fr 1fr; margin: 0 0 16px; background: rgba(255,255,255,.08); }
  .app-mode body.menu-panel-open #menu .menutabs { display: none; }
  .app-mode #menu.show-allbrands .menuroot { display: none; }
  .app-mode #menu.show-allbrands .menuallbrands { display: block; }
  .app-mode body.menu-panel-open #menu .menuallbrands { display: none; }
  .app-mode #menu .brandaz { background: none; box-shadow: none; }
  .app-mode #menu .brandaz__letter { top: -20px;   /* = 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); }
  .app-mode #menu .brandaz__row { grid-template-columns: 64px 1fr auto; padding: 8px 2px; color: #fff; border-top: 1px solid rgba(255,255,255,.06); }
  .app-mode #menu .brandaz__row:active { background: rgba(255,255,255,.08); }
  .app-mode #menu .brandaz__logo { background: #fff; height: 36px; padding: 0 4px; }
  .app-mode #menu .brandaz__logo img { max-width: 56px; max-height: 28px; }
  .app-mode #menu .brandaz__n { color: rgba(255,255,255,.55); }
  /* App catalogue: "Par gamme | Par marque" switch in each category panel */
  .app-mode #menu .menuswitch { display: grid; grid-template-columns: 1fr 1fr; gap: 0; margin: 0 0 12px; background: rgba(255,255,255,.08); }
  .app-mode #menu .menuswitch__btn { padding: 10px; border: 0; background: none; color: rgba(255,255,255,.7); font-weight: 700; font-size: 14px; }
  .app-mode #menu .menuswitch__btn.is-on { background: var(--brand-gradient); color: #fff; }
  .app-mode #menu .menupanel > .grid:not(.menuproductgrid) { display: none !important; }   /* brands/services block … */
  .app-mode #menu .menupanelwrapper.show-marques .menuproductgrid { display: none !important; }
  .app-mode #menu .menupanelwrapper.show-marques .menupanel > .grid:not(.menuproductgrid) { display: block !important; margin: 0; }   /* … shown in marques view */
  .app-mode #menu .menupanelwrapper.show-marques .menuservices { display: none !important; }
  .app-mode #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; }
  .app-mode #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; }
  .app-mode #menu .menubrand__logo { height: 36px; justify-content: center; }
  .app-mode #menu .menubrand__logo img { max-width: 100%; max-height: 36px; }
  .app-mode #menu .menubrand__name { max-width: 100%; }
}

/* ── 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;
  }
  .app-mode #search_result div:has(> .search_result_li_produit) { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }   /* 2 cols (minmax(180px) inline fits 1) */
  .app-mode #search_result .search_result_li_produit .search_result_image { height: 60px !important; }
  .app-mode #search_result .search_result_li_produit .search_result_image img { max-height: 60px; width: auto; height: auto; }

  #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; }
  :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 on the same rhythm: shell on the 8px gutters, 8px under the path bar and between its blocks
     (nav card / greeting / tiles / chart / recent); the "Bonjour" bar sits flush on its tiles as their header. */
  .app-mode .content.compte-shell { gap: var(--app-gap); }
  .app-mode .dashboard__hello { margin-bottom: 0; }
  .app-mode .dashboard__quick, .app-mode .dashboard__hero { margin-bottom: var(--app-gap); }
  .app-mode .dashboard__hero { gap: var(--app-gap); }
  /* Outils (landing + each tool): same rhythm — 8px gutters, 8px between path bar / title / tiles or tool. */
  /* 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; padding: 10px 12px 4px; font-size: 15px; line-height: 1.3; align-self: start;   /* content-box: the cap must not include the padding */
    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 .newsage {
    display: flex; align-items: center; gap: 10px; margin: 6px 0 -2px; padding: 0 2px;
    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;
  }
  :root .blocproduitmarque { height: 18px; padding-top: 4px; padding-right: 6px; }
  :root .blocproduitmarqueimg { max-width: 38px; max-height: 16px; }
  :root .containerimageblocproduit { height: 104px; padding: 0 6px; }
  :root .containerimageblocproduit img {   /* petite_image() 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 { top: 38px; left: 6px; width: 28px; height: 28px; }
  :root .cmp-toggle--card .icon { width: 15px; height: 15px; }
  :root .blocproduit > div[style^="height:28px"]:not(.blocvariante) { height: 6px !important; }   /* the EMPTY variants spacer only (the real row has the same inline style) */
  /* 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 !important; display: flex !important; justify-content: center; gap: 6px; padding: 4px 6px 2px; }
  :root .blocvariante > div { font-size: 0 !important; line-height: 0 !important; }
  :root .blocvariante img { width: 26px !important; height: 26px !important; object-fit: contain; }
  :root .blockstockprix { grid-template-columns: 1fr auto !important; align-items: center; padding: 0 6px 2px; gap: 4px; }
  :root .blockstockprix > div:first-child { justify-content: start !important; font-size: 11px !important; 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. !important beats
     breadcrumbs8()'s inline margin/font-size. */
  .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 !important; line-height: 1 !important; margin: 0 !important; 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 app sizes: the ref title like .pagetitle (21px), panel bars 16px, compact description. */
  .app-mode #photosproduit > h1 { font-size: 21px; line-height: 1.2; padding: 11px 14px; margin: 0; }
  .app-mode .produitpage .h2.gradient:not(h1) { font-size: 16px; line-height: 1.25; padding: 9px 14px; }
  .app-mode #caracteristiques .bold[style*='line-height'] { line-height: 1.35 !important; margin-bottom: 6px; }
  .app-mode #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 on the logo (js/app-mode.js): squish per tap, number pops over it, GO = green + full spin */
  .appmenu__head img.is-egg { animation: eggTap .32s cubic-bezier(.22,1,.36,1); }
  .appmenu__head img.is-egg-go { animation: eggSpin .65s cubic-bezier(.22,1,.36,1); }
  @keyframes eggTap { 30% { transform: scale(.82) rotate(-8deg); } 65% { transform: scale(1.12) rotate(4deg); } }
  @keyframes eggSpin { 0% { transform: scale(1); } 40% { transform: scale(1.25) rotate(200deg); } 100% { transform: scale(1) rotate(360deg); } }
  .appmenu__egg {
    position: absolute; z-index: 2; transform: translate(-50%, -50%); pointer-events: none; opacity: 0;
    min-width: 34px; height: 34px; padding: 0 6px; box-sizing: border-box; display: flex; align-items: center; justify-content: center;
    background: #fff; color: var(--brand); font-weight: 900; font-size: 20px; line-height: 1; box-shadow: 0 4px 14px rgba(0,0,0,.35);
  }
  .appmenu__egg.is-on { animation: eggNum .9s cubic-bezier(.22,1,.36,1) forwards; }
  .appmenu__egg.is-go { background: var(--green-cart, #4AAA35); color: #fff; font-size: 17px; letter-spacing: .04em; }
  @keyframes eggNum {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(.4); }   /* hatches ON the logo: the head is the screen's top edge, */
    25% { opacity: 1; transform: translate(-50%, -50%) scale(1.25); }  /* there is no room above it */
    70% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(.85); }
  }
  .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 in the app:
     full width, rising from the bottom over the tab bar, ≤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) and the sheet shrinks to the
     space left, so a focused field is never hidden ("Nouveau devis"). 0 when the WebView itself resizes. */
  .app-mode .treact-modal-overlay { align-items: flex-end; bottom: var(--kb, 0px); transition: opacity .3s ease, bottom .18s ease; }
  .app-mode .treact-modal-overlay .treact-modal {
    width: 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;
  }
  .app-mode .treact-modal-overlay.show .treact-modal { transform: none; }
  .app-mode .treact-modal-header { flex: none; }
  .app-mode .treact-modal-body { padding: 14px; overscroll-behavior: contain; }

  /* Compare tray = ONE compact row on the tab bar: [⇄ 2/4] [4 square thumbs, × on each] [Comparer] [Vider].
     Bottom padding clears the LSCF bump rising into it. The Filtres pill always rides ABOVE the tray
     (--cmp-tray-h is published by js/compare.js; this rule out-ranks the app pill rules incl. landscape). */
  .app-mode .cmp-tray__inner { flex-wrap: nowrap; gap: 8px; padding: 8px 8px 18px; }
  .app-mode .cmp-tray__title { font-size: 0; gap: 4px; }
  .app-mode .cmp-tray__title .icon { width: 20px; height: 20px; }
  .app-mode .cmp-tray__title small { font-size: 12px; }
  .app-mode .cmp-tray__title { order: 1; } .app-mode .cmp-tray__list { order: 2; } .app-mode .cmp-tray__actions { order: 3; }
  .app-mode .cmp-tray__list { flex: 0 1 auto; gap: 6px; overflow-x: auto; padding-top: 6px; margin-top: -6px; scrollbar-width: none; }   /* narrow phones: thumbs scroll, never overlap the buttons */
  .app-mode .cmp-tray__item--empty { display: none; }   /* the "2/4" count says how many are left */
  .app-mode .cmp-tray__clear { font-size: 12px; }
  .app-mode .cmp-tray__item { width: 40px; height: 40px; padding: 2px; flex: none; justify-content: center; }
  .app-mode .cmp-tray__item img { width: 36px; height: 36px; }
  .app-mode .cmp-tray__ref { display: none; }
  .app-mode .cmp-tray__rm { top: -6px; right: -6px; width: 20px; height: 20px; background: #41434E; color: #fff; border-radius: 50%; }
  .app-mode .cmp-tray__rm .icon { width: 12px; height: 12px; }
  .app-mode .cmp-tray__actions { margin-left: auto; gap: 8px; }
  .app-mode .cmp-tray__go { padding: 9px 10px; font-size: 13px; }
  .app-mode .cmp-tray__hint { font-size: 11.5px; white-space: normal; max-width: 90px; line-height: 1.2; }
  .app-mode .cmp-tray__note { display: none; }
  .app-mode body.has-cmp-tray #filters-toggle { bottom: calc(var(--appbar-h) + var(--cmp-tray-h, 0px) + 12px) !important; }

  /* Mon compte KPIs (Devis / Commandes / Factures à régler): ONE row of 3 compact tiles instead of 3 stacked panels.
     .dashboard prefix out-ranks the per-view compte.css (loaded after this sheet). */
  .dashboard .dashboard__kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--app-gap); }
  .dashboard .kpi { padding: 10px 8px; text-align: center; min-width: 0; }
  .dashboard .kpi__n { font-size: 1.6rem; }
  .dashboard .kpi__l { font-size: 11.5px; line-height: 1.25; }
  .dashboard .kpi__sub { font-size: 11.5px; }
  .dashboard .kpi--alert { border-left: 0; border-top: 3px solid #c0392b; }

  /* 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); }

  .app-mode .treact-modal-overlay .treact-modal { width: min(640px, 100%) !important; max-height: 94dvh; }   /* landscape: a centred sheet */
  .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 { width: 36px; height: 36px; }
  .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; }
}
@media (min-width: 961px) {
  .brandaz { background: none; box-shadow: none; }
  .brandaz__group { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
  .brandaz__letter { grid-column: 1 / -1; position: static; background: none; color: #fff; font-size: 20px; padding: 2px 0; }
  .brandaz__row { grid-template-columns: 1fr; justify-items: center; text-align: center; gap: 4px; padding: 14px 10px 10px;
    background: #fff; box-shadow: var(--shadow-panel); border-top: 0; }
  .brandaz__row:hover { box-shadow: 0 6px 18px rgba(0,0,0,.14); background: #fff; }
  .brandaz__logo { height: 48px; }
  .brandaz__logo img { max-width: 150px; max-height: 44px; }
  .brandaz__name { font-size: 14px; max-width: 100%; }
}

/* ── 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)); }
}
@media (max-width: 960px) and (orientation: landscape) and (max-height: 500px) {
  .app-mode body:has(#ajtpanierdevis) { padding-bottom: calc(56px + 16px + env(safe-area-inset-bottom)); }
}

/* 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); }
