/**
 * SmartBlock — styles des illustrations animées.
 *
 * Tout vient des variables CSS du design system. Les valeurs de repli après la
 * virgule ne servent QU'AU cas où ce fichier est collé dans un site qui ne
 * charge pas les tokens (le WordPress actuel, par exemple) : dans le nouveau
 * site, ce sont toujours les tokens qui gagnent.
 */

.sb-anim {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 11;
  font-family: var(--font-body, system-ui, sans-serif);
  color: var(--text-body, #334155);
  transition: opacity var(--dur-slow, 280ms) var(--ease-out, ease);
}

/* Remise à zéro entre deux tours : toutes les transitions et animations sont
   neutralisées le temps d'un rendu, sinon chaque propriété rejouerait sa
   transition en sens inverse et la boucle se lirait comme un rembobinage.
   Il n'y a ni fondu ni écran vide entre deux tours : la boucle reprend
   directement au décor. */
.sb-anim.is-resetting,
.sb-anim.is-resetting * {
  transition: none !important;
  animation: none !important;
}

.sb-anim__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--radius-lg, 14px);
  border: 1px solid var(--border-subtle, #e2e8f0);
  background: var(--surface-card, #fff);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(15, 23, 42, 0.08));
}

/* ---- Curseur : matérialise toute action simulée ---- */
.sb-cursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transition:
    transform 520ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 180ms;
}
.sb-cursor.is-on {
  opacity: 1;
}
/* Chaque clic est marqué au moment où il se produit — sans ce marqueur,
   l'apparition d'une popup semble spontanée. */
.sb-cursor.is-click::after {
  content: '';
  position: absolute;
  left: -9px;
  top: -9px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid var(--brand, #3366ff);
  animation: sb-click 280ms var(--ease-out, ease) forwards;
}
@keyframes sb-click {
  from {
    transform: scale(0.3);
    opacity: 0.9;
  }
  to {
    transform: scale(1);
    opacity: 0;
  }
}

/* ---- Fond de carte ---- */
.sb-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.sb-map__bg {
  fill: var(--slate-100, #f1f5f9);
}
.sb-map__blocks rect {
  fill: var(--slate-200, #e2e8f0);
  opacity: 0.55;
}
.sb-map__streets path {
  stroke: var(--white, #fff);
  stroke-width: 7;
  fill: none;
}
.sb-map__dot {
  fill: var(--brand, #3366ff);
  opacity: 0.42;
  transition:
    opacity var(--dur-base, 180ms),
    r var(--dur-base, 180ms);
}
.sb-map__dot.is-active {
  opacity: 1;
  r: 8;
}
.sb-map__dot.is-promo {
  fill: var(--liquidity, #0d9488);
  opacity: 0.75;
}

/* ---- Cartes flottantes communes ---- */
.fb,
.fp,
.hp {
  position: absolute;
  z-index: 4;
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-md, 10px);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(15, 23, 42, 0.12));
  opacity: 0;
  transform: scale(0.72);
  transition:
    opacity 380ms var(--ease-out, ease),
    transform 380ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.fb.is-open,
.fp.is-open,
.hp.is-open {
  opacity: 1;
  transform: scale(1);
}

/* =================== carte-fiche-bien =================== */
/* Cadre volontairement plus court que son contenu : c'est ce qui crée la
   course de défilement, et le défilement EST le message de cette animation. */
.fb {
  right: 5%;
  top: 9%;
  width: 47%;
  height: 72%;
  overflow: hidden;
  font-size: 10px;
}
.fb__scroll {
  padding: 10px 12px 14px;
  transition: transform 900ms var(--ease-in-out, cubic-bezier(0.65, 0, 0.35, 1));
}
/* Vraie photo d'annonce depuis le 20/07/2026 : le dégradé gris qui tenait la
   place se lisait comme un chargement raté, pas comme un bien. Le fond est
   conservé — il couvre le temps de chargement de l'image. */
.fb__photo {
  display: block;
  width: 100%;
  height: 54px;
  object-fit: cover;
  border-radius: var(--radius-sm, 6px);
  background: linear-gradient(135deg, var(--slate-300, #cbd5e1), var(--slate-200, #e2e8f0));
}
.fb__address {
  margin: 8px 0 2px;
  color: var(--text-muted, #64748b);
  font-size: 9px;
}
/* Le prix est l'ancre de lecture d'un professionnel : la ligne la plus
   lisible de la fiche. */
.fb__price {
  margin: 0 0 8px;
  font-family: var(--font-mono, inherit);
  font-variant-numeric: tabular-nums;
  font-size: 19px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  letter-spacing: var(--tracking-tight, -0.02em);
}
.fb__ratios {
  display: flex;
  gap: 14px;
  margin: 0 0 8px;
}
.fb__ratios dt {
  color: var(--text-faint, #94a3b8);
  font-size: 8px;
}
.fb__ratios dd {
  margin: 1px 0 0;
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
}
.fb__peb {
  margin: 0 0 8px;
  color: var(--text-faint, #94a3b8);
  font-size: 9px;
}
.fb__pebBadge {
  display: inline-block;
  min-width: 15px;
  padding: 1px 4px;
  border-radius: var(--radius-xs, 4px);
  background: var(--caution, #d97706);
  color: var(--white, #fff);
  font-weight: var(--fw-bold, 700);
  text-align: center;
}
/* Description volontairement coupée en cours de phrase. */
.fb__desc {
  margin: 0 0 12px;
  color: var(--text-body, #334155);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fb__chart {
  margin: 0 0 12px;
}
.fb__chart figcaption {
  margin-bottom: 4px;
  color: var(--text-faint, #94a3b8);
  font-size: 8px;
}
.fb__chart svg {
  width: 100%;
  height: auto;
}
.fb__line {
  fill: none;
  stroke: var(--viz-1, #3366ff);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 260;
  stroke-dashoffset: 260;
}
.fb__chart.is-drawn .fb__line {
  transition: stroke-dashoffset 600ms var(--ease-out, ease);
  stroke-dashoffset: 0;
}
.fb__bars rect {
  fill: var(--viz-1, #3366ff);
  opacity: 0.75;
  transform-origin: bottom;
  transform: scaleY(0);
}
.fb__chart.is-drawn .fb__bars rect {
  animation: sb-bar 420ms var(--ease-out, ease) forwards;
}
.fb__chart.is-drawn .fb__bars rect:nth-child(2) {
  animation-delay: 60ms;
}
.fb__chart.is-drawn .fb__bars rect:nth-child(3) {
  animation-delay: 120ms;
}
.fb__chart.is-drawn .fb__bars rect:nth-child(4) {
  animation-delay: 180ms;
}
.fb__chart.is-drawn .fb__bars rect:nth-child(5) {
  animation-delay: 240ms;
}
@keyframes sb-bar {
  to {
    transform: scaleY(1);
  }
}
.fb__pdf {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  padding-top: 8px;
  border-top: 1px solid var(--border-subtle, #e2e8f0);
  color: var(--text-link, #2952e6);
  font-weight: var(--fw-semibold, 600);
}
/* Indicateur de défilement : indispensable, visible dès la lecture haute. */
.fb__progress {
  position: absolute;
  right: 4px;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 2px;
  background: var(--slate-200, #e2e8f0);
}
.fb__progress i {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 2px;
  background: var(--slate-400, #94a3b8);
  transform-origin: top;
  transition: transform 900ms var(--ease-in-out, ease);
}

/* =================== carte-lasso-quartier =================== */
.lasso {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
}
/* Contour OUVERT pendant tout le tracé. */
.lasso__path {
  fill: none;
  stroke: var(--brand, #3366ff);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.lasso__fill {
  fill: var(--brand, #3366ff);
  opacity: 0;
  transition: opacity 220ms var(--ease-out, ease);
}
.lasso__fill.is-on {
  opacity: 0.1;
}
/* Décompte : les points intérieurs se distinguent, les extérieurs reculent —
   mais restent visibles. */
.is-counted .sb-map__dot {
  opacity: 0.16;
}
.is-counted .sb-map__dot.is-in {
  opacity: 1;
  r: 6.5;
}

.lp {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 5;
  width: 38%;
  padding: 14px 14px;
  background: var(--surface-card, #fff);
  border-left: 1px solid var(--border-subtle, #e2e8f0);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(15, 23, 42, 0.12));
  transform: translateX(100%);
  transition: transform 420ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
  font-size: 10px;
}
.lp.is-on {
  transform: translateX(0);
}
.lp__title {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 13px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
/* Fait le lien entre le geste et les chiffres. */
.lp__count {
  margin: 3px 0 14px;
  color: var(--text-muted, #64748b);
}
.lp__count b {
  color: var(--brand, #3366ff);
  font-weight: var(--fw-bold, 700);
}
.lp__stats {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.lp__stats > div {
  opacity: 0;
  transform: translateY(6px);
  transition:
    opacity 260ms,
    transform 260ms var(--ease-out, ease);
}
.lp__stats > div.is-on {
  opacity: 1;
  transform: none;
}
.lp__stats dt {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--text-faint, #94a3b8);
  font-size: 9px;
}
.lp__stats dd {
  margin: 2px 0 0;
  font-size: 15px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  letter-spacing: var(--tracking-tight, -0.02em);
}

/* La carte se réduit pour laisser place au panneau : il ne recouvre JAMAIS la
   zone sélectionnée. */
.is-lasso .sb-map,
.is-lasso .lasso {
  width: 64%;
}

/* =================== tableau-export-excel =================== */
.tx {
  position: absolute;
  inset: 0;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  font-size: 10px;
}
.tx__table {
  width: 100%;
  border-collapse: collapse;
}
.tx__table th {
  padding: 0 6px 6px;
  text-align: left;
  font-size: 8px;
  font-weight: var(--fw-semibold, 600);
  letter-spacing: var(--tracking-caps, 0.08em);
  text-transform: uppercase;
  color: var(--text-faint, #94a3b8);
  border-bottom: 1px solid var(--border-subtle, #e2e8f0);
  white-space: nowrap;
}
.tx__table th svg {
  vertical-align: -2px;
  margin-right: 3px;
}
.tx__table td {
  padding: 5px 6px;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
}
/* Les lignes non cochées restent parfaitement lisibles : les atténuer
   suggérerait leur disparition. */
.tx__addr {
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-medium, 500);
}
.tx__price {
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
}
.tx__photos svg {
  vertical-align: -2px;
  margin-right: 3px;
  color: var(--text-faint, #94a3b8);
}
.tx__check {
  width: 18px;
}
.tx__box {
  display: block;
  width: 12px;
  height: 12px;
  border-radius: var(--radius-xs, 4px);
  border: 1.5px solid var(--border-default, #cbd5e1);
  background: var(--surface-card, #fff);
  transition:
    background var(--dur-fast, 120ms),
    border-color var(--dur-fast, 120ms);
}
[data-row].is-checked .tx__box {
  background: var(--brand, #3366ff);
  border-color: var(--brand, #3366ff);
}
[data-row].is-checked .tx__box::after {
  content: '';
  display: block;
  width: 3px;
  height: 6px;
  margin: 0.5px auto 0;
  border: solid var(--white, #fff);
  border-width: 0 1.8px 1.8px 0;
  transform: rotate(45deg);
}
[data-row].is-checked td {
  background: var(--brand-subtle, #eef3ff);
}
.tx__peb {
  display: inline-block;
  min-width: 14px;
  padding: 1px 4px;
  border-radius: var(--radius-xs, 4px);
  font-size: 8px;
  font-weight: var(--fw-bold, 700);
  text-align: center;
  color: var(--white, #fff);
  background: var(--slate-400, #94a3b8);
}
.tx__peb[data-peb='B'] {
  background: var(--positive, #16a34a);
}
.tx__peb[data-peb='C'] {
  background: #65a30d;
}
.tx__peb[data-peb='D'] {
  background: var(--caution, #d97706);
}
.tx__peb[data-peb='E'] {
  background: #ea580c;
}
.tx__peb[data-peb='F'] {
  background: var(--negative, #dc2626);
}

.tx__foot {
  margin-top: auto;
  padding-top: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.tx__count {
  color: var(--text-muted, #64748b);
  opacity: 0;
  transition: opacity 200ms;
}
.tx__foot.is-active .tx__count {
  opacity: 1;
}
.tx__count b {
  color: var(--brand, #3366ff);
  font-weight: var(--fw-bold, 700);
}
/* Le bouton est en retrait, puis devient actif : c'est la charnière. */
.tx__btn {
  margin-left: auto;
  padding: 6px 12px;
  border: 1px solid var(--border-default, #cbd5e1);
  border-radius: var(--radius-md, 10px);
  background: var(--surface-card, #fff);
  color: var(--text-faint, #94a3b8);
  font: inherit;
  font-weight: var(--fw-semibold, 600);
  transition: all var(--dur-base, 180ms) var(--ease-out, ease);
}
.tx__foot.is-active .tx__btn {
  background: var(--brand, #3366ff);
  border-color: transparent;
  color: var(--text-on-brand, #fff);
  box-shadow: var(--shadow-brand, 0 6px 20px rgba(51, 102, 255, 0.28));
}
.tx__btn.is-pressed {
  transform: scale(0.97);
}
/* Le fichier sort DU BOUTON. */
.tx__file {
  position: absolute;
  right: 14px;
  bottom: 14px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 11px;
  border-radius: var(--radius-md, 10px);
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  box-shadow: var(--shadow-md, 0 4px 12px rgba(15, 23, 42, 0.08));
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-semibold, 600);
  opacity: 0;
  transform: translateY(-26px) scale(0.8);
  transition:
    opacity 380ms,
    transform 380ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.tx__file svg {
  color: var(--positive, #16a34a);
}
.tx__file.is-on {
  opacity: 1;
  transform: none;
}

/* =================== carte-fiche-promotion =================== */
.fp {
  left: 8%;
  top: 8%;
  width: 56%;
  padding: 12px 14px;
  font-size: 10px;
}
.fp__title {
  margin: 0 0 10px;
  font-family: var(--font-display, inherit);
  font-size: 14px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  letter-spacing: var(--tracking-tight, -0.02em);
}
.fp__fields {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.fp__row {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  align-items: center;
  gap: 8px;
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity 200ms,
    transform 200ms var(--ease-out, ease);
}
.fp__row.is-on {
  opacity: 1;
  transform: none;
}
.fp__row svg {
  color: var(--brand, #3366ff);
  opacity: 0.8;
}
.fp__row dt {
  color: var(--text-faint, #94a3b8);
  font-size: 9px;
}
.fp__row dd {
  margin: 0;
  text-align: right;
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-medium, 500);
}
/* « 211 logements » légèrement plus saillant. */
.fp__row[data-row='4'] dd {
  font-weight: var(--fw-bold, 700);
  font-size: 12px;
}
.fp__row[data-row='4'] dd b {
  font-family: var(--font-mono, inherit);
  font-variant-numeric: tabular-nums;
}
/* Les deux dates forment un bloc en bas. */
.fp__row[data-row='5'] {
  margin-top: 5px;
  padding-top: 7px;
  border-top: 1px solid var(--border-subtle, #e2e8f0);
}

/* =================== detail-unites =================== */
.du {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  font-size: 10px;
}
.du__head {
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--border-subtle, #e2e8f0);
}
.du__head h4 {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 14px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
.du__count {
  margin: 2px 0 0;
  color: var(--text-muted, #64748b);
}
.du__count b {
  color: var(--brand, #3366ff);
  font-weight: var(--fw-bold, 700);
  font-size: 12px;
}
/* Le cadre coupe la liste : pas de défilement, la 9e ligne est tronquée. */
.du__body {
  flex: 1;
  overflow: hidden;
  padding: 0 14px;
}
.du table {
  width: 100%;
  border-collapse: collapse;
}
/* Hauteur de ligne calibrée pour que HUIT lignes tiennent dans le cadre et que
   la NEUVIÈME soit coupée par le bas. Cette ligne coupée est l'élément le plus
   important de la composition : elle dit que la liste est interrompue par le
   cadre, pas par la fin des données (8 lignes affichées contre 211 annoncées). */
.du td {
  padding: 12px 4px;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
}
.du [data-row] {
  opacity: 0;
  transition: opacity 260ms var(--ease-out, ease);
}
.du [data-row].is-on {
  opacity: 1;
}
.du__typo {
  width: 26px;
  color: var(--brand, #3366ff);
  opacity: 0.7;
}
.du__id {
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-semibold, 600);
}
.du__beds {
  color: var(--text-muted, #64748b);
}
/* La colonne des prix DOIT être alignée et régulière : un alignement
   approximatif détruit l'impression de base de données. */
.du__price {
  text-align: right;
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
  font-variant-numeric: tabular-nums;
}

/* =================== detail-unites-b =================== */
/* Version B : reprend le tableau réel de l'application (case à cocher,
   étage, typologie, chambres, surface, terrasse, prix, prix/m², badge de
   statut) plutôt qu'une liste stylisée. Le sujet (arbitrage du 30/07/2026) :
   sélectionner ses unités puis exporter, pas « la liste ne s'arrête pas » —
   les lignes sont donc toutes visibles d'emblée, seules les cases à cocher et
   le pied de tableau (compteur, boutons, fichier) sont animés. */
/* container-type: inline-size + cqw (repère : conteneur ≈ 560px, comme
   hero-marche-devoile plus bas) : à 9 colonnes, un tableau en px fixes
   déborde purement et simplement du cadre sur un hero étroit (mobile) — les
   deux dernières colonnes disparaissent sous overflow:hidden sans qu'aucun
   défilement ne le signale. En cqw, chaque colonne rétrécit avec le cadre et
   le tableau ne peut plus déborder, quelle que soit la largeur. */
.du2 {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  font-size: 8px;
  container-type: inline-size;
}
/* Barre de navigateur (arbitrage du 30/07/2026) : sans elle, la capture se
   lit comme un tableur ou un export PDF — la barre rappelle que c'est une
   page WEB, consultée dans le navigateur, pas un fichier. Points monochromes
   (pas le rouge/jaune/vert macOS) : ça reste un rappel discret, pas une
   fenêtre système qui détournerait l'attention du tableau. */
.du2__browser {
  flex: none;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 8px 12px;
  background: var(--slate-50, #f8fafc);
  border-bottom: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-lg, 14px) var(--radius-lg, 14px) 0 0;
}
.du2__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border-default, #cbd5e1);
}
.du2__url {
  margin-left: 6px;
  flex: 1;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-full, 999px);
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  color: var(--text-muted, #64748b);
  font-size: 9px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.du2__url svg {
  flex: none;
}
.du2__head {
  padding: 12px 14px 8px;
  border-bottom: 1px solid var(--border-subtle, #e2e8f0);
}
.du2__head h4 {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 14px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
.du2__count {
  margin: 2px 0 0;
  font-size: 10px;
  color: var(--text-muted, #64748b);
}
.du2__count b {
  color: var(--brand, #3366ff);
  font-weight: var(--fw-bold, 700);
  font-size: 12px;
}
.du2__body {
  flex: 1;
  overflow: hidden;
  padding: 0 14px;
}
.du2 table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1.43cqw;
}
/* Grid complète (verticale + horizontale), comme le tableau réel de
   l'app — une simple ligne de base sous chaque ligne ne suffisait pas à
   faire lire des COLONNES. */
.du2 th,
.du2 td {
  border-right: 1px solid var(--border-subtle, #e2e8f0);
}
.du2 th:last-child,
.du2 td:last-child {
  border-right: none;
}
.du2 th {
  padding: 0 0.89cqw 0.89cqw;
  text-align: left;
  font-size: 1.25cqw;
  font-weight: var(--fw-semibold, 600);
  letter-spacing: var(--tracking-caps, 0.06em);
  text-transform: uppercase;
  color: var(--text-faint, #94a3b8);
  border-bottom: 1px solid var(--border-subtle, #e2e8f0);
  white-space: nowrap;
}
/* Surface, Terrasse, Prix et Prix/m² sont alignés à droite en corps de
   tableau (tabular-nums) : leur en-tête doit suivre, sinon le libellé part
   à gauche pendant que chaque valeur part à droite et les deux ne se
   répondent jamais. */
.du2 th:nth-child(5),
.du2 th:nth-child(6),
.du2 th:nth-child(7),
.du2 th:nth-child(8) {
  text-align: right;
}
/* Lignes fines (arbitrage du 30/07/2026) : le tableau réel de l'app est dense,
   pas aéré. */
.du2 td {
  padding: 1.1cqw 0.89cqw;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
  white-space: nowrap;
}
/* Une ligne sur deux légèrement grisée, comme dans l'app — sans elle, une
   ligne aussi dense (9 colonnes) ne se lit plus horizontalement. */
.du2 tbody tr:nth-child(even) td {
  background: var(--slate-50, #f8fafc);
}
[data-row].is-checked td {
  background: var(--brand-subtle, #eef3ff);
}
.du2__check {
  width: 2.86cqw;
}
/* Case décochée par défaut : le contour seul, comme tableau-export-excel.js.
   C'est le clic simulé qui la remplit — la sélection EST l'animation. */
.du2__box {
  display: block;
  width: 1.96cqw;
  height: 1.96cqw;
  border-radius: var(--radius-xs, 4px);
  border: 1.5px solid var(--border-default, #cbd5e1);
  background: var(--surface-card, #fff);
  transition:
    background var(--dur-fast, 120ms),
    border-color var(--dur-fast, 120ms);
}
[data-row].is-checked .du2__box {
  background: var(--brand, #3366ff);
  border-color: var(--brand, #3366ff);
}
[data-row].is-checked .du2__box::after {
  content: '';
  display: block;
  width: 0.54cqw;
  height: 0.98cqw;
  margin: 0.09cqw auto 0;
  border: solid var(--white, #fff);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg);
}
/* Corps de tableau tout en noir (arbitrage du 30/07/2026) : le gris clair
   utilisé pour distinguer les colonnes secondaires nuisait à la lisibilité.
   Seuls le statut Hors ligne et les en-têtes de colonnes restent en gris —
   ce sont des LIBELLÉS, pas des données. */
.du2__floor,
.du2__beds,
.du2__area,
.du2__terrace,
.du2__pricem2 {
  color: var(--text-strong, #0f172a);
}
.du2__type {
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-medium, 500);
}
.du2__area,
.du2__terrace {
  text-align: right;
}
.du2__price,
.du2__pricem2 {
  text-align: right;
  font-weight: var(--fw-semibold, 600);
  font-variant-numeric: tabular-nums;
}
.du2__status {
  display: inline-flex;
  align-items: center;
  gap: 0.54cqw;
  padding: 0.36cqw 1.07cqw;
  border-radius: var(--radius-full, 999px);
  font-size: 1.43cqw;
  font-weight: var(--fw-semibold, 600);
  white-space: nowrap;
}
/* Icône ✓ / ✕ : le pastille seule ne suffit pas à distinguer les deux statuts
   d'un coup d'œil, surtout pour un daltonien — l'app réelle porte l'icône,
   pas seulement la couleur. */
.du2__status i {
  display: inline-block;
  width: 1.25cqw;
  height: 1.25cqw;
  font-style: normal;
}
.du2__status[data-status='sold'] {
  background: var(--brand-subtle, #eef3ff);
  color: var(--brand, #3366ff);
}
.du2__status[data-status='sold'] i {
  border-bottom: 1.4px solid currentColor;
  border-left: 1.4px solid currentColor;
  width: 1.07cqw;
  height: 0.54cqw;
  margin-top: -0.36cqw;
  transform: rotate(-45deg);
}
.du2__status[data-status='offline'] {
  background: var(--slate-100, #f1f5f9);
  color: var(--text-strong, #0f172a);
}
.du2__status[data-status='offline'] i {
  position: relative;
}
.du2__status[data-status='offline'] i::before,
.du2__status[data-status='offline'] i::after {
  content: '';
  position: absolute;
  top: 0.54cqw;
  left: 0;
  width: 1.25cqw;
  height: 1.4px;
  background: currentColor;
}
.du2__status[data-status='offline'] i::before {
  transform: rotate(45deg);
}
.du2__status[data-status='offline'] i::after {
  transform: rotate(-45deg);
}

/* ---- Pied de tableau : compteur de sélection + export ---- */
.du2__foot {
  position: relative;
  margin-top: auto;
  padding: 1.79cqw 0.89cqw 1.07cqw;
  display: flex;
  align-items: center;
  gap: 1.79cqw;
  font-size: 1.43cqw;
}
.du2__selcount {
  color: var(--text-strong, #0f172a);
  opacity: 0;
  transition: opacity 200ms;
}
.du2__foot.is-active .du2__selcount {
  opacity: 1;
}
.du2__selcount b {
  color: var(--brand, #3366ff);
  font-weight: var(--fw-bold, 700);
}
.du2__btns {
  margin-left: auto;
  display: flex;
  gap: 0.71cqw;
}
/* Boutons noirs, radius faible, texte blanc — pas le bleu de marque animé
   d'avant (arbitrage du 30/07/2026) : c'est le style réel de l'app. Les deux
   formats restent posés côte à côte : le bouton Excel s'anime (pressed +
   fichier), le bouton Rapport Word montre juste que le choix existe. */
.du2__btn {
  padding: 0.71cqw 1.43cqw;
  border: none;
  border-radius: var(--radius-sm, 5px);
  background: var(--slate-900, #0f172a);
  color: var(--white, #fff);
  font: inherit;
  font-weight: var(--fw-semibold, 600);
  transition: all var(--dur-base, 180ms) var(--ease-out, ease);
}
.du2__btn--ghost {
  opacity: 0.5;
}
.du2__foot.is-active .du2__btn--ghost {
  opacity: 1;
}
.du2__btn.is-pressed {
  transform: scale(0.97);
}
/* Le fichier sort DU BOUTON — ancré au pied de tableau lui-même (bottom:100%),
   pas au cadre : il reste toujours au-dessus des boutons, quelle que soit la
   hauteur du tableau au-dessus (nombre de lignes, largeur du cadre). */
.du2__file {
  position: absolute;
  right: 0;
  bottom: calc(100% + 6px);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 11px;
  border-radius: var(--radius-md, 10px);
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  box-shadow: var(--shadow-md, 0 4px 12px rgba(15, 23, 42, 0.08));
  color: var(--text-strong, #0f172a);
  font-size: 10px;
  font-weight: var(--fw-semibold, 600);
  opacity: 0;
  transform: translateY(6px) scale(0.8);
  transition:
    opacity 380ms,
    transform 380ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.du2__file svg {
  color: var(--positive, #16a34a);
}
.du2__file.is-on {
  opacity: 1;
  transform: none;
}

/* =================== historique-prix-bien =================== */
.hp {
  left: 7%;
  top: 10%;
  width: 58%;
  padding: 12px 14px;
  font-size: 10px;
}
/* Photo + identité du bien sur une ligne (20/07/2026) : l'historique de prix
   seul ressemblait à un relevé bancaire. La photo dit qu'on parle d'un bien. */
.hp__head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.hp__photo {
  flex: none;
  width: 46px;
  height: 34px;
  object-fit: cover;
  border-radius: var(--radius-sm, 6px);
  background: var(--slate-200, #e2e8f0);
}
.hp__addr {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 12px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
/* Presque aussi important que les prix. */
.hp__since {
  display: flex;
  align-items: center;
  gap: 5px;
  /* Le retrait sous le bloc est porté par `.hp__head` depuis que la photo
     regroupe adresse et ancienneté : ici il tomberait à l'intérieur du flex. */
  margin: 4px 0 0;
  color: var(--text-muted, #64748b);
  font-weight: var(--fw-semibold, 600);
}
.hp__table {
  width: 100%;
  border-collapse: collapse;
}
.hp__table tr {
  opacity: 0;
  transition: opacity 280ms var(--ease-out, ease);
}
.hp__table tr.is-on {
  opacity: 1;
}
.hp__table td {
  padding: 5px 2px;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
}
.hp__date {
  color: var(--text-faint, #94a3b8);
}
/* Les prix ne défilent PAS en compteur : la vérité de la donnée est dans les
   paliers. Tout est neutre sauf les baisses. */
.hp__price {
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
}
.hp__delta {
  text-align: right;
}
.hp__drop,
.hp__initial {
  opacity: 0;
  transition: opacity 300ms var(--ease-out, ease);
}
.is-deltas .hp__drop,
.is-deltas .hp__initial {
  opacity: 1;
}
.hp__drop {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--negative, #dc2626);
  font-weight: var(--fw-bold, 700);
  font-variant-numeric: tabular-nums;
}
.hp__initial {
  color: var(--text-faint, #94a3b8);
  font-size: 9px;
}
/* Le cumul doit être la ligne la plus lisible en fin d'animation, plus encore
   que le prix actuel. */
.hp__total {
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--border-subtle, #e2e8f0);
  color: var(--text-muted, #64748b);
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity 320ms,
    transform 320ms var(--ease-out, ease);
}
.is-total .hp__total {
  opacity: 1;
  transform: none;
}
.hp__total b {
  font-size: 16px;
  font-weight: var(--fw-extrabold, 800);
  color: var(--negative, #dc2626);
}

/* =================== suivi-projet-email =================== */
.is-mail {
  background: var(--surface-page, #f8fafc);
}
.mb {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 43% 57%;
  font-size: 10px;
}
.mb__list {
  padding: 10px 0 0;
  border-right: 1px solid var(--border-subtle, #e2e8f0);
  background: var(--surface-card, #fff);
  overflow: hidden;
}
.mb__title {
  margin: 0 0 6px;
  padding: 0 12px;
  font-size: 8px;
  letter-spacing: var(--tracking-caps, 0.08em);
  text-transform: uppercase;
  color: var(--text-faint, #94a3b8);
  font-weight: var(--fw-semibold, 600);
}
.mb ul {
  margin: 0;
  padding: 0;
  list-style: none;
}
.mb__item {
  display: flex;
  gap: 7px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
}
.mb__avatar {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--slate-200, #e2e8f0);
}
.mb__avatar.is-brand {
  background: var(--brand, #3366ff);
}
.mb__meta {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.mb__meta b {
  font-size: 9px;
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-semibold, 600);
}
.mb__meta i {
  font-style: normal;
  font-size: 9px;
  color: var(--text-muted, #64748b);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mb__meta u {
  text-decoration: none;
  font-size: 8px;
  color: var(--text-faint, #94a3b8);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* L'email s'insère en haut ; les autres glissent vers le bas. Le décalage est
   l'unique preuve de la nouveauté. */
.mb__new {
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
  opacity: 0;
  transition:
    max-height 520ms var(--ease-out, ease),
    opacity 380ms,
    padding 520ms;
}
.mb__new.is-in {
  max-height: 60px;
  padding-top: 7px;
  padding-bottom: 7px;
  opacity: 1;
}
/* État non lu, levé au moment de l'ouverture. */
.mb__new.is-in {
  border-left: 3px solid var(--brand, #3366ff);
  background: var(--brand-subtle, #eef3ff);
}
.mb__new.is-read {
  background: var(--surface-card, #fff);
}

.ml {
  padding: 12px 14px;
  overflow: hidden;
  opacity: 0;
  transform: translateX(10px);
  transition:
    opacity 320ms,
    transform 320ms var(--ease-out, ease);
}
.ml.is-on {
  opacity: 1;
  transform: none;
}
.ml__block {
  margin-bottom: 9px;
  opacity: 0;
  transform: translateY(5px);
  transition:
    opacity 240ms,
    transform 240ms var(--ease-out, ease);
}
.ml__block.is-on {
  opacity: 1;
  transform: none;
}
/* Les intitulés priment sur leur contenu. */
.ml__block h5 {
  margin: 0 0 3px;
  font-size: 9px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
.ml__body {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 5px;
}
.ml__chip {
  padding: 1px 6px;
  border-radius: var(--radius-full, 999px);
  background: var(--brand-subtle, #eef3ff);
  color: var(--blue-700, #1e3fbf);
  font-size: 8px;
  font-weight: var(--fw-semibold, 600);
}
.ml__line {
  display: block;
  width: 100%;
  font-size: 9px;
  color: var(--text-body, #334155);
}
.ml__line b {
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
/* L'information la plus rare et la plus vendeuse. */
.ml__sold {
  padding: 0 5px;
  border-radius: var(--radius-full, 999px);
  background: var(--negative, #dc2626);
  color: var(--white, #fff);
  font-size: 8px;
  font-weight: var(--fw-bold, 700);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide, 0.04em);
}
.ml__price {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.ml__price b {
  font-size: 15px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
}
.ml__up {
  font-style: normal;
  font-size: 9px;
  font-weight: var(--fw-semibold, 600);
  color: var(--positive, #16a34a);
}

/* =================== radar-collecte-quotidienne =================== */
/* Colonne plutôt que positions absolues : en absolu, le bloc de lecture se
   superposait au bas du radar et devenait illisible. Ici le radar prend la
   place restante et le texte a la sienne, quelle que soit la hauteur. */
.is-radar {
  background: var(--surface-card, #fff);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 16px 16px 18px;
}
.rd {
  flex: 1;
  min-height: 0;
  height: auto;
  width: auto;
  aspect-ratio: 1;
}
/* Fond de territoire : très atténué, juste assez pour dire qu'on balaie un
   lieu. S'il devient lisible, l'œil cherche des rues et quitte le radar. */
.rd__ground path {
  fill: none;
  stroke: var(--slate-200, #e2e8f0);
  stroke-width: 5;
  opacity: 0.5;
}
.rd__rings circle,
.rd__rings line {
  fill: none;
  stroke: var(--border-subtle, #e2e8f0);
  stroke-width: 1.2;
}
.rd__hub {
  fill: var(--brand, #3366ff);
}
.rd__arm {
  stroke: var(--brand, #3366ff);
  stroke-width: 2;
  stroke-linecap: round;
}
@keyframes sb-radar {
  to {
    transform: rotate(360deg);
  }
}

/* Les points restent après leur détection : s'ils s'effaçaient à chaque tour,
   on lirait « surveillance » au lieu de « accumulation ». */
.rd__blip {
  opacity: 0;
  transition: opacity 220ms var(--ease-out, ease);
}
.rd__blip.is-on {
  opacity: 1;
}
.rd__dot {
  fill: var(--brand, #3366ff);
}
.rd__wave {
  fill: none;
  stroke: var(--brand, #3366ff);
  stroke-width: 1.6;
  opacity: 0;
}
.rd__blip.is-on .rd__wave {
  animation: sb-ping 700ms var(--ease-out, ease) forwards;
}
@keyframes sb-ping {
  from {
    r: 3;
    opacity: 0.7;
  }
  to {
    r: 13;
    opacity: 0;
  }
}

.rd__readout {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 6%;
  text-align: center;
  opacity: 0;
  transition: opacity 300ms var(--ease-out, ease);
}
.rd__readout.is-on {
  opacity: 1;
}
.rd__count {
  margin: 0;
  font-size: 11px;
  color: var(--text-muted, #64748b);
}
.rd__count b {
  font-size: 20px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  letter-spacing: var(--tracking-tight, -0.02em);
}
.rd__fresh {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 5px 0 0;
  padding: 2px 9px;
  border-radius: var(--radius-full, 999px);
  background: var(--teal-50, #f0fdfa);
  color: var(--liquidity, #0d9488);
  font-size: 10px;
  font-weight: var(--fw-semibold, 600);
  opacity: 0;
  transition: opacity 280ms var(--ease-out, ease);
}
.rd__fresh.is-on {
  opacity: 1;
}

/* =================== pipeline-comparables =================== */
.pl {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: var(--surface-card, #fff);
}
.pl__eyebrow {
  font-size: 8px;
  font-weight: var(--fw-semibold, 600);
  letter-spacing: var(--tracking-caps, 0.08em);
  text-transform: uppercase;
  fill: var(--text-faint, #94a3b8);
}
.pl__source {
  fill: var(--text-faint, #94a3b8);
}
.pl__label {
  font-size: 11px;
  font-weight: var(--fw-medium, 500);
  fill: var(--text-muted, #64748b);
  dominant-baseline: middle;
}
/* Conduits statiques : discrets, ils dessinent la STRUCTURE du flux. Seul le
   point voyageur, par-dessus, raconte le mouvement. */
.pl__path {
  fill: none;
  stroke: var(--border-subtle, #e2e8f0);
  stroke-width: 1.6;
  stroke-dasharray: 3 4;
}
.pl__dot {
  fill: var(--brand, #3366ff);
  opacity: 0;
  transition:
    transform 550ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 200ms;
}
.pl__dot.is-on {
  opacity: 1;
}
.pl__hub {
  fill: var(--brand, #3366ff);
  transition: transform 260ms var(--ease-out, ease);
  transform-box: fill-box;
  transform-origin: center;
}
.pl__hub.is-pulse {
  transform: scale(1.6);
}
/* Vérification : un point de passage fixe, pas un état qui se construit —
   c'est une étape du flux, pas le sujet de l'animation. Elle clignote au
   passage de chaque point, pour dire « chacun y passe », rien de plus. */
.pl__verify rect {
  fill: var(--brand-subtle, #eef3ff);
  stroke: var(--brand, #3366ff);
  stroke-width: 1.4;
  transition: fill 220ms var(--ease-out, ease);
}
.pl__verify .pl__check {
  stroke: var(--brand, #3366ff);
  transition: stroke 220ms var(--ease-out, ease);
}
.pl__verify.is-pulse rect {
  fill: var(--brand, #3366ff);
}
.pl__verify.is-pulse .pl__check {
  stroke: var(--white, #fff);
}
.pl__panel-bg {
  fill: var(--slate-50, #f8fafc);
  stroke: var(--border-subtle, #e2e8f0);
  stroke-width: 1;
}
.pl__panel-head {
  font-family: var(--font-display, inherit);
  font-size: 12px;
  font-weight: var(--fw-bold, 700);
  fill: var(--text-strong, #0f172a);
}
.pl__panel-rule {
  stroke: var(--border-subtle, #e2e8f0);
  stroke-width: 1;
}
.pl__swatch {
  fill: var(--brand-subtle, #eef3ff);
  opacity: 0;
  transition: opacity 260ms var(--ease-out, ease);
}
.pl__addr,
.pl__price {
  font-size: 10px;
  opacity: 0;
  transform: translateX(6px);
  transform-box: fill-box;
  transition:
    opacity 260ms var(--ease-out, ease),
    transform 260ms var(--ease-out, ease);
}
.pl__addr {
  fill: var(--text-strong, #0f172a);
  font-weight: var(--fw-medium, 500);
}
.pl__price {
  fill: var(--text-strong, #0f172a);
  font-weight: var(--fw-semibold, 600);
}
.pl__swatch.is-on,
.pl__addr.is-on,
.pl__price.is-on {
  opacity: 1;
  transform: none;
}
.pl__fresh {
  font-size: 9px;
  font-weight: var(--fw-semibold, 600);
  fill: var(--liquidity, #0d9488);
  opacity: 0;
  transition: opacity 300ms var(--ease-out, ease);
}
.pl__fresh.is-on {
  opacity: 1;
}

/* =================== convergence-estimations =================== */
.is-converge {
  background: var(--surface-card, #fff);
}
.cv {
  position: absolute;
  inset: 0;
  padding: 26px 22px;
}

.cv__scale {
  position: absolute;
  inset: 26px 22px;
}
.cv__scale i {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--top);
  height: 1px;
  background: var(--slate-100, #f1f5f9);
}

/* Trait d'écart : nomme le problème, visible une seule scène. */
.cv__spread {
  position: absolute;
  left: 26px;
  top: calc(26px + var(--from));
  height: calc(var(--to) - var(--from));
  width: 2px;
  background: var(--caution, #d97706);
  opacity: 0;
  transition: opacity 260ms var(--ease-out, ease);
}
.is-spread .cv__spread {
  opacity: 1;
}
.cv__spread::before,
.cv__spread::after {
  content: '';
  position: absolute;
  left: -4px;
  width: 10px;
  height: 2px;
  background: var(--caution, #d97706);
}
.cv__spread::before {
  top: 0;
}
.cv__spread::after {
  bottom: 0;
}
.cv__spreadLabel {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: 10px;
  color: var(--caution, #d97706);
}
.cv__spreadLabel b {
  font-size: 13px;
  font-weight: var(--fw-bold, 700);
}

/* Ligne de référence. */
.cv__truth {
  position: absolute;
  left: 22px;
  right: 22px;
  top: calc(26px + var(--top));
  border-top: 2px solid var(--brand, #3366ff);
  opacity: 0;
  transform: scaleX(0.4);
  transform-origin: left;
  transition:
    opacity 300ms var(--ease-out, ease),
    transform 420ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.is-truth .cv__truth {
  opacity: 1;
  transform: none;
}
.cv__truthValue {
  position: absolute;
  right: 0;
  top: -22px;
  font-size: 15px;
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  letter-spacing: var(--tracking-tight, -0.02em);
}
/* Justifie l'autorité de la ligne : sans cette mention, le chiffre de
   référence n'est qu'un sixième avis. */
.cv__truthMeta {
  position: absolute;
  right: 0;
  top: 6px;
  font-size: 9px;
  color: var(--text-faint, #94a3b8);
}

.cv__voices {
  position: absolute;
  inset: 26px 22px;
}
.cv__voice {
  position: absolute;
  left: 34px;
  top: var(--shift, var(--top));
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 10px;
  border-radius: var(--radius-full, 999px);
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  box-shadow: var(--shadow-xs, 0 1px 2px rgba(15, 23, 42, 0.06));
  transform: translateY(-50%);
  opacity: 0;
  /* Pas de rebond : les étiquettes s'arrêtent NET sur la ligne. Un rebond
     suggérerait une négociation, or il n'y en a plus. */
  transition:
    opacity 240ms var(--ease-out, ease),
    top 1100ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    left 420ms var(--ease-out, ease);
}
.cv__voice.is-on {
  opacity: 1;
}
.cv__role {
  font-size: 10px;
  font-weight: var(--fw-semibold, 600);
  color: var(--text-body, #334155);
}
.cv__value {
  font-size: 10px;
  color: var(--text-muted, #64748b);
  font-variant-numeric: tabular-nums;
}

/* Fusion : les cinq rôles se regroupent horizontalement. Chacun reste nommé,
   sinon on perd le fait que tout le monde est d'accord. */
.is-merged .cv__voice {
  border-color: transparent;
  box-shadow: none;
  background: transparent;
}
.is-merged .cv__value {
  display: none;
}
.is-merged .cv__voice[data-voice='0'] {
  left: 0;
}
.is-merged .cv__voice[data-voice='1'] {
  left: 96px;
}
.is-merged .cv__voice[data-voice='2'] {
  left: 178px;
}
.is-merged .cv__voice[data-voice='3'] {
  left: 232px;
}
.is-merged .cv__voice[data-voice='4'] {
  left: 330px;
}
.is-merged .cv__role {
  color: var(--brand, #3366ff);
}

/* ================================================================== */
/* hero-carte-3d — visuel du hero de l'accueil                        */
/*                                                                    */
/* Seule animation SANS cadre : ni bordure ni ombre ni fond blanc.     */
/* C'est un décor fondu dans la page, pas un écran de produit.         */
/* ================================================================== */

sb-hero-carte-3d.sb-anim {
  aspect-ratio: 16 / 10;
  overflow: visible;
}

.h3d {
  position: absolute;
  inset: 0;
}

/* Dérive lente : toute la scène bouge d'un bloc, à vitesse constante. Ce n'est
   pas du parallax (aucune couche n'en double une autre) — c'est ce qui évite
   que le hero paraisse gelé entre deux popups. Première chose à retirer si
   elle attire l'œil. */
.h3d__drift {
  position: absolute;
  inset: 0;
  animation: sb-drift 38s linear infinite alternate;
}
@keyframes sb-drift {
  from {
    transform: translate(-1.2%, 0.6%);
  }
  to {
    transform: translate(1.2%, -0.6%);
  }
}

/* Le SVG DOIT écrêter : le cadrage étant resserré à l'intérieur du plan, une
   bonne partie du bâti tombe hors du viewBox et déborderait dans la page.
   Le fondu des bords évite en plus l'arête rectangulaire d'un décor qui n'a
   pas de cadre : la ville se dissout dans le fond. */
.sb-city {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  -webkit-mask-image: radial-gradient(120% 120% at 50% 45%, #000 58%, transparent 100%);
  mask-image: radial-gradient(120% 120% at 50% 45%, #000 58%, transparent 100%);
}

.sb-city__streets path {
  fill: none;
  stroke: var(--white, #fff);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Les façades sont plus foncées que les toits : dans une palette aussi claire,
   c'est le SEUL écart de valeur qui fait exister le volume. Sans lui, la ville
   redevient un plan. */
.sb-city__foot {
  fill: var(--slate-200, #e2e8f0);
}
.sb-city__wall {
  fill: var(--slate-300, #cbd5e1);
}
.sb-city__roof {
  fill: var(--slate-100, #f1f5f9);
  stroke: var(--slate-200, #e2e8f0);
  stroke-width: 0.5;
}

/* Levée de la ville : le seul grand mouvement du tour. Le volume se déplie
   depuis son empreinte au sol. */
.sb-city__solid {
  transform-origin: var(--ox) var(--oy);
  transform: scaleY(0.02);
  opacity: 0;
  transition:
    transform 820ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 260ms linear;
  transition-delay: calc(var(--i) * 20ms);
}
.is-risen .sb-city__solid {
  transform: scaleY(1);
  opacity: 1;
}

/* ---- Points ---- */
.sb-city__pin {
  opacity: 0;
  transition: opacity var(--dur-slow, 280ms) var(--ease-out, ease);
}
.sb-city__pin.is-on {
  opacity: 1;
}

.sb-city__dot {
  fill: var(--brand, #3366ff);
  transition:
    r var(--dur-base, 180ms) var(--ease-out, ease),
    opacity var(--dur-base, 180ms);
}
.sb-city__halo {
  fill: var(--brand, #3366ff);
  opacity: 0.14;
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(0.5);
  transition:
    transform var(--dur-slow, 280ms) var(--ease-out, ease),
    opacity var(--dur-base, 180ms);
}

/* Pendant qu'une popup est ouverte, le point actif reste à pleine intensité et
   les autres reculent. Le regard doit finir sur une seule donnée. */
.is-focused .sb-city__pin.is-on .sb-city__dot {
  opacity: 0.32;
}
.is-focused .sb-city__pin.is-on .sb-city__halo {
  opacity: 0;
}
.sb-city__pin.is-active .sb-city__dot {
  opacity: 1;
  r: 2.9;
}
.sb-city__pin.is-active .sb-city__halo {
  opacity: 0.18;
  transform: scale(1);
}

/* ---- Popups ---- */
.h3d__card {
  position: absolute;
  left: var(--ax);
  top: var(--ay);
  z-index: 4;
  width: 14.5rem;
  margin: 0;
  padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-md, 10px);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(15, 23, 42, 0.12));
  /* La popup naît de SON point et y retourne : elle grandit depuis lui, elle
     ne glisse jamais depuis un bord — sinon le lien point → donnée se perd,
     et c'est tout le message. */
  transform-origin: 0 100%;
  transform: translate(-50%, calc(-100% - 1rem)) scale(0.62);
  opacity: 0;
  pointer-events: none;
  transition:
    transform var(--dur-slow, 280ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity var(--dur-base, 180ms) var(--ease-out, ease);
}
.h3d__card.is-open {
  opacity: 1;
  transform: translate(-50%, calc(-100% - 1rem)) scale(1);
}

/* Ancrage par le coin quand le point est près d'un bord du cadre. */
.h3d__card.is-left {
  transform-origin: 1.75rem 100%;
  transform: translate(-1.75rem, calc(-100% - 1rem)) scale(0.62);
}
.h3d__card.is-left.is-open {
  transform: translate(-1.75rem, calc(-100% - 1rem)) scale(1);
}
.h3d__card.is-right {
  transform-origin: calc(100% - 1.75rem) 100%;
  transform: translate(calc(-100% + 1.75rem), calc(-100% - 1rem)) scale(0.62);
}
.h3d__card.is-right.is-open {
  transform: translate(calc(-100% + 1.75rem), calc(-100% - 1rem)) scale(1);
}
.h3d__card.is-left::after {
  left: 1.75rem;
}
.h3d__card.is-right::after {
  left: auto;
  right: 1.75rem;
}

/* Le trait qui relie la popup à son point. À trois popups sur neuf marqueurs,
   sans lui on ne sait plus laquelle parle de qui. */
.h3d__card::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  width: 1px;
  height: 1rem;
  background: var(--brand, #3366ff);
  opacity: 0.45;
}

/* La popup d'un point porte la photo du bien depuis le 20/07/2026. Elle est en
   tête, pleine largeur : c'est la première chose lue quand la fiche s'ouvre —
   on sait qu'on regarde un bien avant de lire son prix. Les marges négatives
   annulent le `padding` de la carte pour que l'image touche ses bords. */
.h3d__photo {
  display: block;
  width: calc(100% + 2 * var(--space-4, 1rem));
  margin: calc(-1 * var(--space-3, 0.75rem)) calc(-1 * var(--space-4, 1rem))
    var(--space-3, 0.75rem);
  aspect-ratio: 16 / 9;
  height: auto;
  object-fit: cover;
  border-radius: var(--radius-md, 10px) var(--radius-md, 10px) 0 0;
  background: var(--slate-200, #e2e8f0);
}

.h3d__addr {
  margin: 0;
  font-size: var(--text-sm, 0.875rem);
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
  line-height: var(--leading-snug, 1.3);
}
.h3d__addr span {
  display: block;
  font-weight: var(--fw-regular, 400);
  color: var(--text-muted, #64748b);
}

.h3d__price {
  margin: var(--space-2, 0.5rem) 0 0;
  font-family: var(--font-display, inherit);
  font-size: var(--text-xl, 1.25rem);
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  letter-spacing: var(--tracking-tight, -0.01em);
  font-variant-numeric: tabular-nums;
}

.h3d__ratios {
  margin: var(--space-2, 0.5rem) 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  font-size: var(--text-xs, 0.75rem);
  color: var(--text-muted, #64748b);
}
.h3d__ratios b {
  color: var(--text-body, #334155);
  font-variant-numeric: tabular-nums;
}

.h3d__epc {
  margin: var(--space-2, 0.5rem) 0 0;
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  font-size: var(--text-xs, 0.75rem);
  color: var(--text-muted, #64748b);
}
/* Le PEB s'affiche en étiquette de classe : un code visuel que le marché belge
   lit sans effort. */
.h3d__epc b {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4rem;
  height: 1.4rem;
  padding: 0 0.35rem;
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-xs, 0.75rem);
  font-weight: var(--fw-bold, 700);
  color: var(--white, #fff);
  background: var(--caution, #d97706);
}
.h3d__epc b[data-epc='C'] {
  background: var(--positive, #16a34a);
}
.h3d__epc b[data-epc='E'] {
  background: var(--negative, #dc2626);
}

/* ================================================================== */
/* hero-dossier-comparables — hero de la page Experts                  */
/* ================================================================== */

sb-hero-dossier-comparables.sb-anim {
  aspect-ratio: 16 / 11;
  /* Les fiches entrent depuis le hors-champ : rien ne les recadre. */
  overflow: visible;
}

.hdc {
  position: absolute;
  inset: 0;
}

/* La feuille est le livrable de l'expert, pas une capture du produit : aucune
   barre d'outils, aucun onglet. */
.hdc__sheet {
  position: absolute;
  left: 0;
  top: 7%;
  width: 78%;
  margin: 0;
  padding: 14px 16px 12px;
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-lg, 14px);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(15, 23, 42, 0.12));
}

.hdc__head {
  margin: 0;
  font-size: var(--text-2xs, 11px);
  font-weight: var(--fw-bold, 700);
  letter-spacing: var(--tracking-caps, 0.08em);
  text-transform: uppercase;
  color: var(--text-faint, #94a3b8);
}

.hdc__rows {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
}

.hdc__row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 7px 6px;
  border-bottom: 1px solid var(--slate-100, #f1f5f9);
  border-radius: var(--radius-sm, 6px);
  font-size: 10px;
  transition: background var(--dur-slow, 280ms) linear;
}
.hdc__row:last-child {
  border-bottom: 0;
}

/* Ligne non écrite : un trait, pas un texte gris. Une ligne vide doit se voir
   comme une place à remplir. */
.hdc__ghost {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 58%;
  height: 5px;
  border-radius: var(--radius-full, 999px);
  background: var(--slate-100, #f1f5f9);
  transition: opacity var(--dur-base, 180ms) linear;
}
.hdc__row.is-on .hdc__ghost {
  opacity: 0;
}

.hdc__cell {
  opacity: 0;
  transition: opacity var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hdc__row.is-on .hdc__cell {
  opacity: 1;
}

/* La ligne qui vient de s'écrire s'allume, puis rejoint le niveau des autres.
   Sans ce marquage, cinq lignes du même gris se lisent comme un texte qui
   charge — pas comme un dossier qui se construit. */
.hdc__row.is-fresh {
  background: var(--brand-subtle, #eef3ff);
}

.hdc__addr {
  color: var(--text-strong, #0f172a);
  font-weight: var(--fw-medium, 500);
  line-height: var(--leading-snug, 1.25);
}
.hdc__addr span {
  display: block;
  color: var(--text-faint, #94a3b8);
  font-weight: var(--fw-regular, 400);
  font-size: 9px;
}
/* Le prix reste la valeur la plus lisible de la ligne, comme partout ailleurs
   dans le produit. */
.hdc__price {
  font-size: 12px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  font-variant-numeric: tabular-nums;
}
.hdc__m2 {
  min-width: 62px;
  text-align: right;
  color: var(--text-muted, #64748b);
  font-variant-numeric: tabular-nums;
}

/* Un dossier ne vaut pas par son épaisseur mais par le chiffre qu'il produit :
   le pied est le seul élément en bleu de marque en dehors des prix. */
.hdc__foot {
  margin: 12px 0 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 10px;
  color: var(--text-muted, #64748b);
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity var(--dur-slow, 280ms) linear,
    transform var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hdc__foot.is-on {
  opacity: 1;
  transform: none;
}
.hdc__footMedian b {
  margin-left: 4px;
  font-family: var(--font-display, inherit);
  font-size: 15px;
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  letter-spacing: var(--tracking-tight, -0.02em);
  font-variant-numeric: tabular-nums;
}

/* ---- Les fiches en vol ---- */
.hdc__flight {
  position: absolute;
  inset: 0;
}

/* Même famille visuelle que les popups de hero-carte-3d : même carte blanche,
   même prix bleu. Les deux heros doivent se lire comme un seul outil. */
.hdc__card {
  position: absolute;
  right: 0;
  top: calc(19% + var(--slot) * 12.6%);
  display: flex;
  align-items: center;
  gap: 7px;
  width: 40%;
  margin: 0;
  padding: 7px 10px;
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-md, 10px);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(15, 23, 42, 0.12));
  /* La fiche se rétracte VERS sa ligne : l'origine est à gauche, du côté du
     dossier. Si elle s'évanouissait sur elle-même, le lien entre l'annonce et
     le comparable se perdrait — et c'est tout le propos. */
  transform-origin: 0 50%;
  transform: translateX(120%);
  opacity: 0;
  transition:
    transform 380ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 200ms linear;
}
.hdc__card.is-in {
  opacity: 1;
  transform: translateX(4%);
}
.hdc__card.is-absorbed {
  opacity: 0;
  transform: translateX(-32%) scale(0.42);
  transition:
    transform 300ms var(--ease-in-out, ease),
    opacity 240ms linear;
}

/* Chaque comparable qui s'envole porte sa photo depuis le 20/07/2026 : sans
   elle, les fiches volantes étaient des étiquettes de prix. */
.hdc__cardPhoto {
  flex: none;
  width: 34px;
  height: 26px;
  object-fit: cover;
  border-radius: var(--radius-sm, 6px);
  background: var(--slate-200, #e2e8f0);
}
.hdc__cardAddr {
  margin: 0;
  font-size: 10px;
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
  line-height: var(--leading-snug, 1.25);
}
.hdc__cardAddr span {
  display: block;
  font-size: 9px;
  font-weight: var(--fw-regular, 400);
  color: var(--text-muted, #64748b);
}
.hdc__cardPrice {
  margin: 3px 0 0;
  font-family: var(--font-display, inherit);
  font-size: 13px;
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  font-variant-numeric: tabular-nums;
}

/* ================================================================== */
/* hero-marge-projet — hero de la page Promoteurs                      */
/* ================================================================== */

/* Le rapport du cadre EST celui du viewBox (520 × 300) : c'est ce qui permet
   d'ancrer les étiquettes HTML en % sur des faces calculées en SVG, sans
   letterbox ni déformation. */
sb-hero-marge-projet.sb-anim {
  aspect-ratio: 520 / 300;
  overflow: visible;
}

.hmp {
  position: absolute;
  inset: 0;
}

.hmp__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Un niveau se déplie depuis le niveau précédent : c'est un empilement, pas
   cinq images qui se remplacent. Seul grand mouvement de l'animation. */
.hmp__level {
  transform-origin: var(--ox) var(--oy);
  transform: scaleY(0.02);
  opacity: 0;
  transition:
    transform 540ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 180ms linear;
  transition-delay: calc(var(--i) * 200ms);
}
.is-risen .hmp__level {
  transform: scaleY(1);
  opacity: 1;
}

/* Les façades des niveaux s'empilent en une seule paroi continue : c'est le
   contour de chaque étage qui tient lieu de ligne de plancher. Sans lui, les
   quatre étages fondent en un bloc unique et la construction ne se compte plus.

   Le trait est plus FONCÉ que la façade, jamais plus clair : un filet blanc sur
   du gris clair ne survit ni au contraste ni à un écran non-retina — c'était le
   cas de la première version, où les planchers avaient disparu à l'œil.

   `non-scaling-stroke` est indispensable : le SVG est étiré au cadre
   (`preserveAspectRatio="none"`), donc sans lui l'épaisseur du trait ne serait
   pas la même à l'horizontale et à la verticale. */
/* Le nez de dalle : plus foncé que la façade, parce qu'une tranche de béton ne
   prend pas la lumière comme un parement. C'est ce contraste, avec le débord,
   qui fait compter les planchers. */
.hmp__slab .sb-city__wall {
  fill: var(--slate-400, #94a3b8);
}

.hmp__level .sb-city__wall,
.hmp__level .sb-city__roof {
  stroke: var(--slate-400, #94a3b8);
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Le niveau qui vient de se poser est brièvement plus clair, puis rejoint la
   valeur des autres. Sans ce marquage, l'empilement se lit comme un
   redimensionnement. */
/* Le flash porte sur les façades SEULEMENT (enfants directs du niveau) : une
   animation sur `fill` l'emporte sur une déclaration normale, elle repeindrait
   donc le nez de dalle à la couleur de la façade et effacerait les planchers. */
.is-risen .hmp__level > .sb-city__wall {
  animation: sb-level-fresh 700ms var(--ease-out, ease) both;
  animation-delay: calc(var(--i) * 200ms + 260ms);
}
@keyframes sb-level-fresh {
  from {
    fill: var(--blue-100, #e0e8ff);
  }
  to {
    fill: var(--slate-300, #cbd5e1);
  }
}

/* ---- Étiquettes de programmation ---- */
/* Une seule ligne, typologie puis prix. Deux lignes empiétaient sur
   l'étiquette de l'étage du dessus — et deux étiquettes qui se superposent, ce
   n'est plus une programmation, c'est un empilement illisible. */
.hmp__tag {
  position: absolute;
  left: var(--ax);
  top: var(--ay);
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 4px 8px;
  background: var(--surface-card, #fff);
  border: 1px solid var(--border-subtle, #e2e8f0);
  border-radius: var(--radius-sm, 6px);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(15, 23, 42, 0.08));
  font-size: 9px;
  white-space: nowrap;
  opacity: 0;
  /* L'étiquette SORT de la façade qu'elle décrit : elle glisse vers la droite
     depuis le volume, elle n'apparaît pas à sa place. */
  transform: translate(8px, -50%);
  transition:
    opacity var(--dur-slow, 280ms) linear,
    transform var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hmp__tag.is-on {
  opacity: 1;
  transform: translate(26px, -50%);
}
/* Le trait qui relie l'étiquette à son étage. Sans lui, à trois étiquettes sur
   quatre niveaux, on ne sait plus laquelle parle de quel étage. */
.hmp__tag::before {
  content: '';
  position: absolute;
  right: 100%;
  top: 50%;
  width: 24px;
  height: 1px;
  background: var(--border-default, #cbd5e1);
}
.hmp__tag b {
  font-weight: var(--fw-semibold, 600);
  color: var(--text-strong, #0f172a);
}
.hmp__tag i {
  font-style: normal;
  color: var(--text-muted, #64748b);
  font-variant-numeric: tabular-nums;
}

/* En scène finale, seule l'étiquette qui porte le prix de sortie reste à pleine
   intensité : le regard doit finir sur une seule donnée. */
.is-focused .hmp__tag {
  opacity: 0.4;
}
.is-focused .hmp__tag[data-tag='1'] {
  opacity: 1;
}

/* ---- Règle de marché ---- */
.hmp__rule {
  position: absolute;
  left: 2%;
  bottom: 3%;
  width: 74%;
  font-size: 10px;
}
.hmp__ruleHead {
  margin: 0 0 6px;
  font-size: var(--text-2xs, 11px);
  letter-spacing: var(--tracking-caps, 0.08em);
  text-transform: uppercase;
  color: var(--text-faint, #94a3b8);
}
.hmp__track {
  position: relative;
  display: block;
  height: 6px;
  border-radius: var(--radius-full, 999px);
  background: var(--slate-100, #f1f5f9);
  overflow: visible;
}
.hmp__fill {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--slate-200, #e2e8f0);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 700ms var(--ease-out, ease);
}
.is-drawn .hmp__fill {
  transform: scaleX(1);
}
/* La fourchette utile est le SEUL aplat de bleu de la règle ; le reste du
   marché reste gris. C'est ce contraste qui fait exister l'encadrement. */
.hmp__band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--l);
  width: var(--w);
  border-radius: var(--radius-full, 999px);
  background: var(--brand, #3366ff);
  opacity: 0;
  transform: scaleY(0.4);
  transition:
    opacity var(--dur-slow, 280ms) linear,
    transform var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hmp__band.is-on {
  opacity: 0.28;
  transform: scaleY(1);
}

/* Le repère DESCEND sur la règle et s'y cale : c'est le déplacement qui dit
   « il vient se positionner par rapport au marché ». */
.hmp__marker {
  position: absolute;
  left: var(--p);
  bottom: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding-bottom: 8px;
  transform: translate(-50%, -14px);
  opacity: 0;
  transition:
    opacity var(--dur-base, 180ms) linear,
    transform 420ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
}
.hmp__marker.is-set {
  opacity: 1;
  transform: translate(-50%, 0);
}
.hmp__marker::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 2px;
  height: 8px;
  border-radius: 1px;
  background: var(--brand, #3366ff);
  transform: translateX(-50%);
}
.hmp__marker b {
  font-family: var(--font-display, inherit);
  font-size: 15px;
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  letter-spacing: var(--tracking-tight, -0.02em);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hmp__marker em {
  font-style: normal;
  font-size: 9px;
  color: var(--text-muted, #64748b);
  white-space: nowrap;
}

.hmp__scale {
  margin: 6px 0 0;
  display: flex;
  justify-content: space-between;
  font-size: 9px;
  color: var(--text-faint, #94a3b8);
  font-variant-numeric: tabular-nums;
}

/* ================================================================== */
/* hero-marche-devoile — hero de la page Investisseurs                 */
/* ================================================================== */

sb-hero-marche-devoile.sb-anim {
  aspect-ratio: 16 / 11;
  /* La grille déborde de sa colonne : rien ne la recadre. */
  overflow: visible;
}

.hmd {
  position: absolute;
  inset: 0;
  /* La bande de données ci-dessous a des tailles fixes converties en cqw
     (repère : conteneur ≈ 494px) pour rester proportionnelles à la largeur
     des vignettes (en %). Sans ça, sur un hero étroit (mobile), la bande
     garde sa hauteur en pixels alors que la grille rétrécit : les deux
     rangées de vignettes finissent par se chevaucher. */
  container-type: inline-size;
}

/* La grille est un EXTRAIT de marché, pas un catalogue complet — elle déborde
   donc à gauche et à droite de la colonne du hero. Ses bords ne sont pas
   coupés au couteau mais fondus : une arête franche dessinerait un cadre, et
   un cadre referait de ce décor un écran de produit. Le fondu est asymétrique,
   plus court à gauche : de ce côté la grille frôle le texte du hero. */
.hmd__grid {
  position: absolute;
  top: 0;
  bottom: 0;
  left: -12%;
  right: -20%;
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 13%,
    #000 84%,
    transparent 100%
  );
  mask-image: linear-gradient(to right, transparent 0, #000 13%, #000 84%, transparent 100%);
}

/* État masqué : un rectangle gris uni. Pas de flou, pas de cadenas — une donnée
   RETIRÉE, pas une donnée protégée. Rien n'y bouge : c'est l'immobilité de la
   scène 1 qui rend le dévoilement lisible. */
.hmd__tile {
  position: absolute;
  left: var(--x);
  top: var(--y);
  /* Largeur commune, hauteur déduite du contenu : la photo est en 3:2 et la
     bande de données a une hauteur fixe, donc les douze vignettes tombent
     forcément au même gabarit. Fixer une hauteur par vignette, comme avant,
     laissait chaque photo se recadrer différemment. Doit rester synchronisée
     avec `TILE_W` dans hero-marche-devoile.js. */
  --tile-w: 16%;
  width: var(--tile-w);
  margin: 0;
  padding: 0;
  /* La photo va au bord : c'est le rayon de la vignette qui l'arrondit. */
  overflow: hidden;
  border-radius: var(--radius-md, 10px);
  background: var(--slate-200, #e2e8f0);
  border: 1px solid var(--slate-200, #e2e8f0);
  box-shadow: none;
  /* Le front de dévoilement n'est pas un élément qui traverse le cadre : c'est
     ce retard, calculé depuis la position en x de la vignette. */
  transition:
    background var(--dur-slow, 280ms) linear,
    border-color var(--dur-slow, 280ms) linear,
    box-shadow var(--dur-slow, 280ms) linear,
    opacity var(--dur-slow, 280ms) linear;
  transition-delay: var(--d);
}
.is-revealed .hmd__tile {
  background: var(--surface-card, #fff);
  border-color: var(--border-subtle, #e2e8f0);
  box-shadow: var(--shadow-sm, 0 1px 3px rgba(15, 23, 42, 0.08));
}

/* La photo de l'annonce. Elle occupe le haut de la vignette et la bande de
   données reste sur fond blanc en dessous : c'est la mise en page d'une fiche
   de portail, pas une carte postale légendée. Le prix garde donc son bleu de
   marque sur blanc — le poser en blanc sur la photo l'aurait rendu décoratif
   alors qu'il est le sujet de l'animation.

   Elle apparaît AU DÉVOILEMENT, avec le même retard `--d` que le reste : avant,
   la vignette est un rectangle gris. Le marché ne retire pas que le prix. */
.hmd__photo {
  display: block;
  width: 100%;
  /* Ratio imposé, pas déduit : toutes les photos se recadrent pareil, quelle
     que soit la source. C'est ce qui donne aux douze cartes le même gabarit.
     16:9 et non 3:2 — trois rangées de cartes en 3:2 ne tiennent pas dans la
     hauteur du hero. */
  aspect-ratio: 16 / 9;
  height: auto;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--dur-slow, 280ms) linear;
  transition-delay: var(--d);
}
.is-revealed .hmd__photo {
  opacity: 1;
}

/* Repère de l'état masqué, posé dans la bande de données : à la place du prix,
   pas à la place de la photo. */
.hmd__bar {
  position: absolute;
  left: 2.02cqw;
  bottom: 3.04cqw;
  width: 46%;
  height: 1.42cqw;
  border-radius: var(--radius-full, 999px);
  background: var(--slate-300, #cbd5e1);
  transition: opacity var(--dur-base, 180ms) linear;
  transition-delay: var(--d);
}
.is-revealed .hmd__bar {
  opacity: 0;
}

/* La bande de données. Hauteur fixe et respiration franche au-dessus du prix :
   collé sous la photo, il se lisait comme une légende d'image alors qu'il est
   le sujet de l'animation. */
.hmd__data {
  display: flex;
  flex-direction: column;
  gap: 0.4cqw;
  padding: 1.62cqw 1.82cqw 1.82cqw;
  opacity: 0;
  transition: opacity var(--dur-base, 180ms) linear;
  transition-delay: var(--d);
}
.is-revealed .hmd__data {
  opacity: 1;
}
.hmd__price {
  font-family: var(--font-display, inherit);
  font-size: 2.43cqw;
  font-weight: var(--fw-bold, 700);
  color: var(--brand, #3366ff);
  letter-spacing: var(--tracking-tight, -0.02em);
  font-variant-numeric: tabular-nums;
}
.hmd__m2 {
  font-style: normal;
  font-size: 1.82cqw;
  color: var(--text-muted, #64748b);
  font-variant-numeric: tabular-nums;
}

/* La pastille grandit DEPUIS sa vignette : l'information appartient au bien. */
.hmd__pill {
  position: absolute;
  right: 1.42cqw;
  top: 1.42cqw;
  padding: 0.4cqw 1.21cqw;
  border-radius: var(--radius-full, 999px);
  font-size: 1.82cqw;
  font-weight: var(--fw-bold, 700);
  color: var(--white, #fff);
  white-space: nowrap;
  opacity: 0;
  transform: scale(0.4);
  transform-origin: 100% 0;
  transition:
    opacity var(--dur-base, 180ms) linear,
    transform var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hmd__tile.is-pilled .hmd__pill {
  opacity: 1;
  transform: none;
}
.hmd__pill[data-kind='down'] {
  background: var(--negative, #dc2626);
}
.hmd__pill[data-kind='flag'] {
  background: var(--caution, #d97706);
}
.hmd__pill[data-kind='age'] {
  background: var(--slate-500, #64748b);
}

/* En scène finale, le regard doit finir sur ce que le marché cache : les huit
   vignettes sans historique reculent d'un cran. */
.is-focused .hmd__tile {
  opacity: 0.5;
}
.is-focused .hmd__tile.has-pill {
  opacity: 1;
}

/* La transparence n'est pas une posture : elle a un volume. */
.hmd__foot {
  position: absolute;
  left: 2%;
  bottom: 0;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 10px;
  color: var(--text-muted, #64748b);
  opacity: 0;
  transform: translateY(4px);
  transition:
    opacity var(--dur-slow, 280ms) linear,
    transform var(--dur-slow, 280ms) var(--ease-out, ease);
}
.hmd__foot.is-on {
  opacity: 1;
  transform: none;
}
.hmd__foot b {
  font-family: var(--font-display, inherit);
  font-size: 17px;
  font-weight: var(--fw-bold, 700);
  color: var(--text-strong, #0f172a);
  letter-spacing: var(--tracking-tight, -0.02em);
  font-variant-numeric: tabular-nums;
}

/* ================================================================== */
/* hero-macbook-fiches — visuel du hero de l'accueil (11/09/2026)     */
/*                                                                    */
/* Un MacBook de face (image WebP à fond transparent), l'écran de      */
/* Market Explorer reconstitué en HTML dans sa découpe, et trois       */
/* fiches qui sortent de l'écran une à la fois. Sans cadre : le cadre, */
/* c'est le portable.                                                  */
/*                                                                    */
/* Deux échelles cohabitent. L'écran est écrit à 1336 px et réduit par  */
/* `--k` (posé par le JS d'après la largeur mesurée). Les fiches sont   */
/* en `em`, avec un `font-size` racine lui aussi posé par le JS : elles */
/* suivent la largeur du cadre sans qu'aucune valeur ne soit supposée.  */
/* ================================================================== */

sb-hero-macbook-fiches.sb-anim {
  aspect-ratio: 3207 / 1942;
  overflow: visible;
}

.hmf {
  position: absolute;
  inset: 0;
  font-size: 12px; /* recalculé par le JS d'après la largeur du cadre */
}

.hmf__frame {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  pointer-events: none;
  user-select: none;
  filter: drop-shadow(0 30px 40px rgba(5, 10, 40, 0.55));
}

/* La découpe transparente du cadre, positionnée par le JS (pourcentages
   mesurés sur l'image). `overflow: hidden` : l'écran écrête l'UI. */
.hmf__screen {
  position: absolute;
  z-index: 1;
  overflow: hidden;
  background: #fff;
  border-radius: 6px 6px 2px 2px;
}

/* ---- L'écran de l'app, à l'échelle 1 ----
   Les couleurs ci-dessous sont celles de Market Explorer, relevées sur la
   capture : elles sont en dur À DESSEIN, l'écran doit ressembler à l'app et
   non au site. Aucun token du design system ici. */
.hmf__ui {
  position: absolute;
  left: 0;
  top: 0;
  transform: scale(var(--k, 0.3));
  transform-origin: 0 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px;
  line-height: 1.4;
  color: #212529;
  background: #ececec;
  overflow: hidden;
}
.hmf__ui p,
.hmf__ui h1,
.hmf__ui h2 {
  margin: 0;
}

.hmf__m {
  position: absolute;
  left: 0;
  top: 0;
  width: 893px;
  height: 869px;
  overflow: hidden;
}
.hmf__map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hmf__map text {
  font-size: 13px;
  fill: #8a8a8a;
}

/* Le point d'où sort la fiche en cours : une seule pulsation, quand elle
   sort. Le reste du temps, un point comme les autres. */
.hmf__pin.is-active {
  animation: hmf-pulse 900ms ease-out 1;
}
@keyframes hmf-pulse {
  0% {
    r: 5.5;
    opacity: 1;
  }
  45% {
    r: 14;
    opacity: 0.55;
  }
  100% {
    r: 5.5;
    opacity: 1;
  }
}

.hmf__tb {
  position: absolute;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}
.hmf__tb--menu {
  left: 12px;
  top: 10px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hmf__tb--addr {
  left: 60px;
  top: 10px;
  width: 358px;
  height: 40px;
  padding: 0 14px;
  display: flex;
  align-items: center;
  font-size: 16px;
  color: #333;
  white-space: nowrap;
}
/* Décalé à droite de l'encoche du portable, qui couvre le haut de l'écran
   entre 44 % et 56 % de sa largeur. */
.hmf__tb--search {
  left: 756px;
  top: 10px;
  width: 126px;
  height: 36px;
  padding: 0 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  background: #343a40;
  color: #fff;
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
}
.hmf__tb--table {
  left: 12px;
  top: 582px;
  width: 130px;
  height: 38px;
  padding: 0 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: #343a40;
  color: #fff;
  font-size: 16px;
  font-weight: 500;
  white-space: nowrap;
}
.hmf__n {
  background: #fff;
  color: #222;
  font-size: 11px;
  font-weight: 700;
  border-radius: 3px;
  padding: 0 5px;
  line-height: 16px;
}
.hmf__tb--tools {
  left: 12px;
  top: 630px;
  width: 130px;
  font-size: 16px;
}
.hmf__tb--tools div {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 34px;
  padding: 0 12px;
  border-top: 1px solid #e5e5e5;
}
.hmf__tb--tools div:first-child {
  border-top: 0;
}
.hmf__tb--tools i {
  font-style: normal;
  width: 12px;
  font-size: 13px;
  color: #333;
}
.hmf__tb--mini {
  left: 696px;
  top: 676px;
  width: 56px;
  height: 56px;
  overflow: hidden;
  background: #ddd;
}
.hmf__tb--mini svg {
  width: 100%;
  height: 100%;
}
.hmf__tb--legend {
  left: 762px;
  top: 672px;
  width: 110px;
  padding: 6px 0;
  font-size: 15px;
}
.hmf__tb--legend div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 14px;
}
.hmf__tb--legend b {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  display: inline-block;
}

/* Panneau « Market » */
.hmf__panel {
  position: absolute;
  left: 893px;
  top: 0;
  width: 443px;
  height: 869px;
  background: #fff;
  border-left: 1px solid #e0e0e0;
}
.hmf__tabs {
  display: flex;
  align-items: center;
  height: 42px;
  border-bottom: 1px solid #e5e5e5;
  font-size: 18px;
  color: #555;
}
.hmf__tabs span {
  padding: 0 30px 0 8px;
  height: 42px;
  display: flex;
  align-items: center;
}
.hmf__tabs span.is-on {
  color: #212529;
  border-right: 1px solid #e0e0e0;
  padding-left: 10px;
}
.hmf__tabs .hmf__user {
  margin-left: auto;
  margin-right: 6px;
  padding: 0;
}
.hmf__title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 22px 12px 8px 30px;
}
.hmf__title h1 {
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.hmf__title small {
  font-size: 14px;
  color: #333;
}
.hmf__select {
  margin: 2px 12px 0 30px;
  height: 34px;
  border: 1px solid #cfd4da;
  border-radius: 4px;
  display: flex;
  align-items: center;
  padding: 0 10px;
  font-size: 17px;
}
.hmf__x {
  margin-left: auto;
  color: #666;
  font-size: 16px;
}
.hmf__v {
  margin-left: 10px;
  color: #666;
  font-size: 11px;
}
.hmf__links {
  margin: 6px 12px 0 30px;
  background: #f3f3f3;
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 32px;
  font-size: 13.5px;
  color: #1b6ee0;
  text-decoration: underline;
}
.hmf__links span {
  display: flex;
  align-items: center;
  gap: 6px;
}
.hmf__h2 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 34px 12px 0 30px;
}
.hmf__h2 h2 {
  font-size: 22px;
  font-weight: 600;
}
.hmf__acts {
  display: flex;
  gap: 6px;
}
.hmf__acts span {
  width: 38px;
  height: 30px;
  border: 1px solid #1b6ee0;
  border-radius: 4px;
  color: #1b6ee0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 14px;
}
.hmf__acts span:first-child {
  width: 46px;
}
.hmf__sub {
  margin: 14px 0 0 30px;
  color: #1b6ee0;
  font-size: 19px;
  font-weight: 500;
}
.hmf__sum {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  row-gap: 14px;
  margin: 20px 12px 0 30px;
  text-align: center;
  font-size: 14px;
  color: #222;
}
.hmf__sum div {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.hmf__sum svg {
  width: 22px;
  height: 22px;
}
.hmf__desc {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 26px 12px 0 30px;
}
.hmf__desc .hmf__sub {
  margin: 0;
}
.hmf__new {
  background: #f5893a;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 3px;
}
.hmf__tbl {
  margin: 10px 12px 0 30px;
  border-collapse: collapse;
  width: calc(100% - 42px);
  font-size: 14px;
}
.hmf__tbl td {
  padding: 8px 4px;
  border-bottom: 1px solid #e5e5e5;
  vertical-align: top;
  line-height: 1.35;
}
.hmf__tbl td:first-child {
  width: 190px;
  color: #222;
}
.hmf__red {
  color: #e8412f;
}
.hmf__online {
  background: #d5f0dd;
  color: #2a7a3e;
  font-size: 12px;
  padding: 1px 8px;
  border-radius: 10px;
  margin-left: 12px;
  white-space: nowrap;
}
.hmf__stars {
  letter-spacing: 1px;
}
.hmf__chat {
  position: absolute;
  right: 14px;
  bottom: 14px;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  background: #222;
}
.hmf__chat::after {
  content: '';
  position: absolute;
  left: 17px;
  top: 19px;
  width: 20px;
  height: 14px;
  background: #fff;
  border-radius: 3px;
}

/* ---- Les fiches qui sortent de l'écran ----
   La popup de l'app (photo, bloc de couleur, année, statut), agrandie à
   l'échelle du hero. En bleu de marque et non dans le rouge de l'app : sur le
   bloc bleu du hero, le rouge tranchait trop (arbitré le 11/09/2026).
   Fermée, la fiche est posée SUR son point de l'écran ; ouverte, elle glisse
   vers sa place sur le bord du cadre. Elle sort de l'écran et y rentre : elle
   ne vient jamais d'un bord du hero. */
.hmf__fiche {
  position: absolute;
  left: var(--fx);
  top: var(--fy);
  z-index: 3;
  width: 15.8em;
  margin: 0;
  overflow: hidden;
  border-radius: 0.5em;
  background: var(--surface-card, #fff);
  box-shadow:
    0 2em 3.6em -1em rgba(5, 10, 40, 0.6),
    0 0 0 1px rgba(15, 23, 42, 0.08);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.3;
  transform: scale(0.5);
  opacity: 0;
  pointer-events: none;
  transition:
    left 620ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    top 620ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    transform 620ms var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
    opacity 320ms var(--ease-out, ease);
}
.hmf__fiche.is-open {
  left: var(--x);
  top: var(--y);
  transform: scale(1);
  opacity: 1;
}

.hmf__photo {
  display: block;
  width: 100%;
  height: 7.3em;
  object-fit: cover;
  background: var(--slate-200, #e2e8f0);
}
.hmf__body {
  display: flex;
}
.hmf__main {
  width: 9.8em;
  padding: 0.6em 0.7em;
  background: var(--brand, #3366ff);
  color: var(--white, #fff);
  font-size: 0.96em;
}
.hmf__kind {
  font-weight: 500;
}
.hmf__price {
  margin: 0.25em 0;
  font-weight: 700;
  font-size: 1.22em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hmf__ratio {
  font-variant-numeric: tabular-nums;
}
.hmf__side {
  flex: 1;
  position: relative;
  padding: 0.6em 0.7em;
  font-size: 1em;
  color: var(--brand, #3366ff);
}
.hmf__status {
  position: absolute;
  left: 0.7em;
  bottom: 0.6em;
  padding: 0.1em 0.6em;
  border-radius: 1em;
  background: #d5f0dd;
  color: #2a7a3e;
  font-size: 0.875em;
  white-space: nowrap;
}

/* ---- Réduction de mouvement : l'état final est déjà posé par le JS, on
       neutralise seulement les transitions résiduelles. ---- */
@media (prefers-reduced-motion: reduce) {
  .sb-anim,
  .sb-anim * {
    transition: none !important;
    animation: none !important;
  }
  .sb-cursor {
    display: none;
  }
  /* Le radar est la seule animation qui tourne encore pendant sa pause
     finale : ici, le balayage se fige sur une position lisible. */
  .rd__sweep {
    transform: rotate(38deg);
  }
}
