/* ============================================================
   RESET / GLOBAL
============================================================ */

/* border-box comme sur le site et dans Galerie/ — ce fichier ne l'avait pas.
   Sans lui, `width:100%` + padding déborde du parent (l'en-tête poussait le
   logo client hors de la pastille) et `height:100%` + padding dépasse en bas
   (les cartes des panneaux mangeaient leur marge inférieure). */
*, *::before, *::after { box-sizing: border-box; }

* {
  -webkit-user-drag: none;
  user-select: none;
  font-family: "Manrope", sans-serif;
  font-optical-sizing: auto;
  font-style: normal;
}

html {
  overflow: hidden;
  /* Le reset Starting Point pose `scrollbar-gutter: stable` sur html. La
     visionneuse ne défile jamais (overflow:hidden) : la gouttière ne protège
     d'aucun saut, elle retire juste ~15 px à droite. Sur un navigateur à
     ascenseur classique, tout — maquette comprise — s'en trouve décalé d'un
     seul côté. */
  scrollbar-gutter: auto;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

.material-symbols-sharp {
  font-variation-settings:
  'FILL' 0,
  'wght' 400,
  'GRAD' 0,
  'opsz' 24
}

body {
  margin: 0;
  overflow: hidden;
  /* Pas de fond derrière l'image principale : les rendus sont détourés
     (WebP avec alpha), la plaque #ddd9da qui les cernait a sauté. */
  background: transparent;
  /* touch-action:none n'est PAS posé sur body : il bloquerait le défilement
     des panneaux. Le geste de rotation est borné aux surfaces de rendu
     (#render-root et al., voir plus bas). */
  overscroll-behavior: none;
}


/* L'ancien #topbar (logo / nom / logo) est remplacé par l'en-tête partagé —
   voir packages/ui-header/header.css et packages/ui-header/header.js. */

/* ============================================================
   VERRE LIQUIDE
   Le vocabulaire de l'en-tête partagé, factorisé pour que les barres
   d'outils et les panneaux en soient les déclinaisons : plaque claire
   floutée, filet, coin arrondi, ombre portée — et, posés dessus, des
   boutons sombres pleins.
============================================================ */

:root {
  /* Même recette que l'en-tête (cf. --header-bg-scrolled / --header-blur
     dans brand.css) pour que les trois plaques se lisent comme un seul
     matériau. */
  --glass-bg: rgba(250, 250, 250, 0.62);
  --glass-blur: blur(20px) saturate(180%);
  --glass-border: rgba(17, 17, 17, 0.10);
  --glass-shadow: 0 12px 34px rgba(17, 17, 17, 0.14);
  --glass-radius: 5px;
  --glass-pad: 8px;

  --btn-size: 44px;
  --btn-radius: 5px;
  --btn-ink: #111111;
  --btn-ink-hover: #393a44;
  --btn-on-ink: #ffffff;

  /* Marge des panneaux détachés (villa à droite, filtres/scènes à gauche) */
  --panel-inset: 12px;

  /* Largeur de la carte d'un panneau latéral, et gouttière qui la sépare des
     boutons une fois ceux-ci décalés. */
  --side-panel-w: 320px;
  --side-gap: 10px;
}

.glass {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow);
}

/* ============================================================
   MODALE GÉNÉRIQUE (Présentation)
============================================================ */

.lucid-modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(6px);
  display: none;
  z-index: 999;
}

.lucid-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.lucid-modal-card {
  position: relative;
  width: min(90vw, 640px);
  max-height: min(80vh, 720px);
  overflow-y: auto;
  padding: 40px;
  border-radius: 5px;
  background: #fff;
  color: #111;
}

.lucid-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 5px;

  background: rgba(0,0,0,0.06);
  color: #111;
  border: none;
}

.lucid-modal-title {
  margin: 0 0 18px;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 0.02em;
}

.lucid-modal-body {
  font-size: 15px;
  line-height: 1.7;
  color: #4c5166;
}

.lucid-modal-body p { margin: 0 0 14px; }
.lucid-modal-body p:last-child { margin-bottom: 0; }

/* ---- Barre latérale ----
   Plaque de verre verticale. À l'ouverture d'un panneau elle glisse vers la
   droite pour lui laisser la place, au lieu de le laisser la recouvrir. */
#side-controls {
	position: absolute;
	left: var(--panel-inset);
	top: 50%;

	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: var(--glass-pad);

	z-index: 60;
	pointer-events: auto;

	transform: translateY(-50%);
	transition: transform 0.35s cubic-bezier(.16,.84,.44,1);
}

/* Panneau ouvert : on pousse d'une largeur de panneau + la gouttière */
#side-controls.is-pushed {
	transform: translateY(-50%) translateX(calc(var(--side-panel-w) + var(--side-gap)));
}

.side-btn,
.ui-btn {
	width: var(--btn-size);
	height: var(--btn-size);

	border-radius: var(--btn-radius);
	border: none;

	background: var(--btn-ink);
	color: var(--btn-on-ink);

	display: flex;
	align-items: center;
	justify-content: center;

	cursor: pointer;
	transition: background-color 0.2s, color 0.2s, transform 0.15s;
}

.side-btn:active,
.ui-btn:active { transform: scale(0.94); }

/* Barre du bas : de simples pictogrammes posés sur le verre, sans plaque —
   la barre elle-même suffit à les cadrer. */
.ui-btn {
	background: none;
	color: var(--btn-ink);
}

.ui-btn:hover {
	background: none;
	color: var(--btn-ink-hover);
}

.ui-btn:disabled {
	opacity: 0.32;
	cursor: default;
}

.ui-btn:disabled:hover {
	color: var(--btn-ink);
}

/* Onglets latéraux : plaque claire au repos, plein noir une fois leur
   panneau ouvert — c'est l'onglet actif qui est le plus appuyé. */
.side-btn {
	background: rgba(255, 255, 255, 0.72);
	color: var(--btn-ink);
	box-shadow: inset 0 0 0 1px rgba(17, 17, 17, 0.08);
}

.side-btn:hover { background: rgba(255, 255, 255, 0.94); }

.side-btn.active {
	background: var(--btn-ink);
	color: var(--btn-on-ink);
	box-shadow: none;
}

.side-btn.active:hover { background: var(--btn-ink-hover); }

.side-btn .material-symbols-sharp,
.ui-btn .material-symbols-sharp { font-size: 22px; }

/* ---- Panneaux latéraux (filtres / scènes) ----
   Symétriques du panneau villa : carte sombre pleine hauteur, détachée des
   bords, qui glisse depuis l'arête gauche. Seule différence voulue avec la
   droite — les boutons qui l'ouvrent se décalent pour lui laisser la place
   (#side-controls.is-pushed). */
.side-panel {
	position: fixed;
	left: 0;
	top: 0;
	height: 100dvh;
	width: calc(var(--panel-inset) + var(--side-panel-w));

	/* Miroir de la marge du panneau villa (12px 12px 12px 0) */
	padding: var(--panel-inset) 0 var(--panel-inset) var(--panel-inset);

	transform: translateX(-100%);
	transition: transform 0.35s cubic-bezier(.16,.84,.44,1);

	z-index: 150;
	color: white;
}

.side-panel.active { transform: translateX(0); }

.side-panel-card {
	height: 100%;
	padding: 22px;

	background: rgba(15, 15, 16, 0.92);
	backdrop-filter: var(--glass-blur);
	-webkit-backdrop-filter: var(--glass-blur);
	border: 1px solid rgba(255, 255, 255, 0.08);
	border-radius: var(--glass-radius);
	box-shadow: var(--glass-shadow);

	display: flex;
	flex-direction: column;
	gap: 18px;

	overflow-y: auto;
}

/* Le panneau de filtres est rendu par packages/ui-forms (DynamicFilterPanel).
   Override SP UI classes pour le fond sombre des panneaux en verre. */
#panel-filter .label,
#panel-filter .field-title,
#panel-filter .field-description {
  color: rgba(255, 255, 255, 0.85);
}

#panel-filter .input,
#panel-filter .select {
  background-color: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.16);
  color: white;
  transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease);
}

#panel-filter .input:hover,
#panel-filter .select:hover {
  border-color: rgba(255, 255, 255, 0.28);
}

#panel-filter .input:focus,
#panel-filter .select:focus {
  border-color: rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.08);
}

/* Sélecteur (type select) : chevron de marque — celui de SP UI est gris
   moyen, trop pâle sur le verre — et liste d'options sombre. Sans ces
   règles, les <option> natifs sortent en blanc jusqu'au survol. */
#panel-filter .select,
#panel-filter input[type="date"] {
  color-scheme: dark;
}

#panel-filter .select {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.65)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m6 9 6 6 6-6'/%3e%3c/svg%3e");
}

#panel-filter .select option {
  background-color: #1b1b1e;
  color: white;
}

#panel-filter .select option:hover,
#panel-filter .select option:checked {
  background-color: #393a44;
  color: white;
}

#panel-filter .input::placeholder {
  color: rgba(255, 255, 255, 0.35);
}

#panel-filter .checkbox {
  border-color: rgba(255, 255, 255, 0.25);
  accent-color: white;
}

#panel-filter .btn {
  background: rgba(255, 255, 255, 0.95);
  color: #111;
  border-color: transparent;
}

#panel-filter .btn:hover {
  background: white;
}

#panel-filter .text-muted-foreground {
  color: rgba(255, 255, 255, 0.55);
}

#panel-filter .text-foreground {
  color: white;
}

#panel-filter .slider {
  accent-color: white;
}

/* Le composant slider-range de starting-point colore le segment sélectionné
   et le contour du curseur avec --primary (#111 côté design system) — sur
   le fond vitre sombre du panneau de filtres, ça se fond dans le noir.
   On surcharge les jetons localement plutôt que le composant lui-même. */
#panel-filter .slider-range {
  --primary: white;
  --muted: rgba(255, 255, 255, 0.25);
}

/* Multi-select déroulant (type multiselect du schéma) — même verre sombre
   que le reste du panneau, avec ses états de survol et de déploiement. */
#panel-filter .gf-multi-select {
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.06);
  overflow: hidden;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}

#panel-filter .gf-multi-select:hover {
  border-color: rgba(255, 255, 255, 0.28);
}

#panel-filter .gf-multi-select[open] {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.35);
}

#panel-filter .gf-multi-select-summary {
  cursor: pointer;
  user-select: none;
  padding: 7px 10px;
  font-size: 0.8rem;
  color: white;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: background-color 0.15s var(--ease);
}

#panel-filter .gf-multi-select-summary:hover {
  background: rgba(255, 255, 255, 0.06);
}

#panel-filter .gf-multi-select-summary::-webkit-details-marker {
  display: none;
}

#panel-filter .gf-multi-select-summary::after {
  content: "\25BE";
  color: rgba(255, 255, 255, 0.55);
  transition: transform 0.2s var(--ease);
}

#panel-filter .gf-multi-select[open] .gf-multi-select-summary::after {
  transform: rotate(180deg);
}

#panel-filter .gf-multi-select-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(15, 15, 16, 0.4);
  color: rgba(255, 255, 255, 0.85);
}

#panel-filter .gf-multi-select-list label {
  padding: 4px 6px;
  border-radius: 4px;
  transition: background-color 0.15s var(--ease);
}

#panel-filter .gf-multi-select-list label:hover {
  background: rgba(255, 255, 255, 0.07);
}

#panel-filter .border-border {
  border-color: rgba(255, 255, 255, 0.12);
}

/* Close button inside side panel headers */
.panel-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 4px;
  border: none;
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.panel-close-btn:hover {
  background: rgba(255, 255, 255, 0.18);
  color: white;
}

.panel-close-btn .material-symbols-sharp {
  font-size: 18px;
}

/* L'ancienne flèche .side-arrow a disparu avec la refonte des boutons :
   c'est le déplacement de la barre qui signale le panneau ouvert. */

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: white;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.02em;
}

.panel-content {
	color: white;
	font-size: 13px;
}

/* Contenu réutilisable des panneaux : on restaure la sélection de texte que
   le reset global `user-select:none` interdit — on copie une référence, un
   prix, une description ; les contrôles, eux, restent non sélectionnables. */
.panel-data dd,
.panel-description,
.panel-content,
.side-panel-card,
.lucid-modal-body {
  user-select: text;
  -webkit-user-select: text;
}

.hidden {
	display: none;
}

/* ---- Lignes libellé → valeur ----
   .panel-row est l'ancienne forme, encore posée en dur par endroits ; les
   données d'unité passent désormais par .panel-data (plus bas), qui réunit
   champs globaux et propriétés dynamiques dans une seule liste. */

.panel-row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 16px;
}

/* ---- Curseurs doubles pour les filtres numériques (gf-filter-dual-range) ----
   Remplaçant les champs min/max de ui-forms quand des plages de données
   existent. Reprend le thème du panneau de filtres. */
#panel-filter .gf-filter-range-values {
	display: flex;
	justify-content: space-between;
	font-size: 12px;
	color: rgba(255, 255, 255, 0.6);
	margin-bottom: 4px;
}

#panel-filter .gf-filter-dual-range {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 2px;
}

#panel-filter .gf-filter-dual-range input[type="range"] {
	width: 100%;
	height: 4px;
	background: rgba(255, 255, 255, 0.2);
	border-radius: 2px;
	outline: none;
	-webkit-appearance: none;
	appearance: none;
}

#panel-filter .gf-filter-dual-range input[type="range"]::-webkit-slider-thumb {
	-webkit-appearance: none;
	appearance: none;
	width: 14px;
	height: 14px;
	border-radius: 50%;
	background: white;
	cursor: pointer;
}

/* Fiche équipement dans le panneau villa */
.panel-icon {
	text-align: center;
	margin-bottom: 0.6rem;
}

.panel-icon .material-symbols-sharp {
	font-size: 2.4rem;
	color: rgba(255, 255, 255, 0.7);
}

.panel-description {
	font-size: 13px;
	color: rgba(255, 255, 255, 0.7);
	line-height: 1.5;
	margin: 0 0 0.8rem;
}

/* ---- Scènes ----
   Plus de pictogramme : chaque scène se présente par son propre rendu,
   flouté, avec son nom posé dessus. */
.scenes-list {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 10px;
	margin-top: 12px;
}

.scene-item {
	position: relative;
	aspect-ratio: 4 / 3;

	border-radius: 5px;
	overflow: hidden;

	display: flex;
	align-items: flex-end;

	cursor: pointer;
	transition: transform 0.2s;
}

.scene-preview {
	position: absolute;
	inset: 0;
	z-index: 0;

	background-image: var(--preview, linear-gradient(150deg, #3a3b45, #17171b));
	background-size: cover;
	background-position: center;

	filter: blur(9px) saturate(130%);
	transform: scale(1.3);
	transition: filter 0.3s, transform 0.3s;
}

.scene-item::after {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	background: linear-gradient(to top, rgba(0,0,0,0.72), rgba(0,0,0,0.18));
	box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
	border-radius: inherit;
	transition: box-shadow 0.2s;
}

.scene-item:hover { transform: translateY(-2px); }
.scene-item:hover .scene-preview { filter: blur(5px) saturate(140%); transform: scale(1.22); }

.scene-item.active::after {
	box-shadow: inset 0 0 0 2px #ffffff;
}

.scene-label {
	position: relative;
	z-index: 2;

	padding: 10px;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	line-height: 1.2;
	color: white;
}

/* ============================================================
   MAIN LAYERS (CRITICAL FOR ALIGNMENT)
============================================================ */


/* Turntable image */
#viewer {
  z-index: 1;
  display: flex;
  cursor: grab;
}

#viewer:active {
  cursor: grabbing;
}

#frame {
  pointer-events: none;
  display: block;
}

/* Tant qu'aucune frame n'est posée, l'<img> reste sans source : le navigateur
   dessine alors sa vignette d'image cassée, un rectangle bordé en plein milieu
   de la maquette. Rien à montrer, rien à afficher. */
#frame:not([src]) { display: none; }

#frame-canvas {
  /* Même boîte que l'<img> : posé en absolu sur #viewer, il ne dépend pas de
     la taille de l'image masquée ni du flux flex — il remplit exactement le
     rendu, sans écrasement ni effondrement. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Three.js canvas */
#three-container {
  z-index: 2;
}
/* `fixed`, pas `absolute` : un élément fixe est accroché à la fenêtre et ne
   compte pas dans le débordement du document. En absolu, une boîte de rendu
   plus large que l'écran élargissait le document, donc la fenêtre de mise en
   page, donc la boîte au calcul suivant — un cliquet qui ne s'arrêtait plus
   (mesuré : 1389 px sur un écran de 375). */
#render-root {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#loading-screen {
  position: fixed;
  inset: 0;

  display: flex;
  align-items: center;
  justify-content: center;

  /* L'écran de chargement garde une plaque opaque : il masque la scène
     pendant le préchargement, il ne peut pas être transparent. */
  background: #ddd9da;

  z-index: 9999;

  transition: opacity 0.35s ease;
  backdrop-filter: blur(4px);
  will-change: opacity;
}

#loading-screen.fade-out {
  opacity: 0;
  pointer-events: none;
}

.loader {
  width: 42px;
  height: 42px;

  border-radius: 50%;

  border: 2px solid rgba(0,0,0,0.12);
  border-top-color: rgba(0,0,0,0.75);

  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* both layers fill SAME box */
#viewer,
#three-container {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* image fills container exactly */
#frame {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
#three-container canvas {
  position: absolute;
  inset: 0;
}

body.is-align-mode #ui,
body.is-align-mode #villa-panel,
body.is-align-mode #side-controls,
body.is-align-mode #hotspots,
body.is-align-mode #tooltip,
body.is-align-mode .site-header,
body.is-align-mode .side-panel,
body.is-align-mode .bottom-center,
body.is-align-mode #loading-screen { display: none !important; }

/* ---- Surfaces de rotation vs défilement (tactile) ----
   Le geste « tourner le turntable » est un drag partout sur le rendu : ces
   surfaces gardent touch-action:none pour que le navigateur ne l'intercepte
   pas comme un pan. En revanche les panneaux/zonnes défilants reprennent le
   pan vertical, et les boutons un appui immédiat (pas d'attente du double-tap
   zoom). */
#render-root,
#viewer,
#three-container,
#frame,
#frame-canvas,
#three-container canvas,
#hotspots {
  touch-action: none;
}

.panel-card,
.side-panel-card,
.gf-multi-select-list,
.lucid-modal-card,
.gp-body,
.gp-lightbox-info {
  touch-action: pan-y;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

button,
a,
[role="button"],
.hotspot,
.scene-item,
.panel-tile,
.panel-scene-btn,
.gp-tile,
.nav-toggle {
  touch-action: manipulation;
}

/* ============================================================
   UI LAYER
============================================================ */

#ui {
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}

#ui * {
  pointer-events: auto;
}

/* ---- Barre du bas ----
   Même plaque de verre que la barre latérale et que l'en-tête ; les boutons
   sombres y sont posés. (Le gabarit de .ui-btn est mutualisé avec .side-btn
   plus haut.) */
.bottom-center {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);

  display: flex;
  gap: 8px;
  padding: var(--glass-pad);
}

/* ============================================================
   HOTSPOTS
============================================================ */

#hotspots {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	pointer-events: none;
	z-index: 10;
}

/* La position vient d'un `transform` posé par scene/hotspots.js (le décalage
   -50%/-130% y est intégré), pas de `left`/`top` : écrire left/top à chaque
   rAF relayoute la page sous les 18 repères, et c'est ce que l'on sentait
   comme des à-coups pendant la rotation. Le transform reste au compositeur. */
.hotspot {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -130%);
  will-change: transform;
  pointer-events: auto;
  cursor: pointer;
  transition: opacity var(--model-transition-duration, 0.2s) var(--model-transition-timing, ease);
}

/* --render-scale est posé par SceneViewer.resizeRenderer() : sur une boîte de
   rendu réduite (portrait), les repères rétrécissent avec la maquette au lieu
   de la recouvrir. */
.hotspot-label {
  width: calc(42px * var(--render-scale, 1));
  height: calc(42px * var(--render-scale, 1));
  border-radius: 5px;

  display: flex;
  align-items: center;
  justify-content: center;

  background: rgba(0,0,0,0.92);
  color: white;
}

.hotspot-label .material-symbols-sharp {
  font-size: calc(22px * var(--render-scale, 1));
}

.hotspot-label::after {
  content: "";
  position: absolute;
  bottom: -6px;
  left: 50%;
  transform: translateX(-50%);

  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 6px solid rgba(0,0,0,0.92);
}

/* ============================================================
   TOOLTIP
============================================================ */

#tooltip {
  position: fixed;
  transform: translate(12px, -120%);
  padding: 10px 14px;
  border-radius: 5px;

  background: rgba(0,0,0,0.78);
  color: white;

  font-size: calc(14px * var(--render-scale, 1));
  white-space: nowrap;

  pointer-events: none;
  z-index: 30;

  opacity: 0;
  transition: opacity var(--model-transition-duration, 0.15s) var(--model-transition-timing, ease), transform var(--model-transition-duration, 0.15s) var(--model-transition-timing, ease);
}

#tooltip.visible {
  opacity: 1;
  transform: translate(12px, -140%);
}

/* ============================================================
   PANEL
============================================================ */

#villa-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100dvh;
  width: min(432px, 92vw);

  /* Marge demandée : la carte flotte, détachée des trois bords qu'elle
     touche. En padding sur l'enveloppe plutôt qu'en margin sur la carte,
     pour que translateX(100%) la sorte toujours entièrement de l'écran. */
  padding: var(--panel-inset) var(--panel-inset) var(--panel-inset) 0;

  transform: translateX(100%);
  transition: transform 0.35s cubic-bezier(.16,.84,.44,1);

  /* Au-dessus de l'en-tête partagé (z-index 100), comme il l'était de
     l'ancien #topbar : sinon la barre recouvre le haut du panneau et son
     bouton de fermeture. */
  z-index: 150;
}

/* #villa-type / #villa-title étaient déclarés ici ET plus bas, avec des
   propriétés partiellement différentes. Les deux blocs sont fusionnés dans
   celui de la section « Typography », près du reste de l'en-tête du panneau. */
#panel-close {
  position: absolute;
  /* Calé sur la carte (.panel-card est en position:relative), pas sur
     l'enveloppe : sinon la marge de 12px le collait au coin arrondi. */
  top: 20px;
  right: 20px;

  width: 36px;
  height: 36px;

  border-radius: 5px;
  border: none;

  background: rgba(255,255,255,0.08);
  color: white;

  display: flex;
  align-items: center;
  justify-content: center;

  cursor: pointer;
  transition: 0.2s;
}

#panel-close:hover {
  background: rgba(255,255,255,0.15);
}

#panel-close .material-symbols-rounded {
  font-size: 20px;
}
.panel-row {
  font-size: 14px;
  letter-spacing: 0.2px;
}

#villa-panel.active {
  transform: translateX(0);
}

.panel-card {
  position: relative;
  height: 100%;
  background: #0f0f10;
  color: white;
  padding: 28px;

  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow);
  overflow-y: auto;

  display: flex;
  flex-direction: column;
  gap: 26px;
}

/* ---- Header — logo à gauche, intitulés à sa droite ----
   Empilé et centré, le bloc de titre prenait toute la largeur de la carte et
   venait donc sous le bouton de fermeture (absolu, 36px à 20px du bord). Il
   passe à droite du logo, aligné à gauche, et le padding réserve la colonne
   du bouton pour qu'aucun texte ne puisse plus l'atteindre. */
.panel-card .panel-header {
  display: flex;
  align-items: center;
  gap: 14px;
  text-align: left;
  /* Le bouton est calé sur la CARTE (20px du bord, 36px de large), l'en-tête
     vit lui à l'intérieur de ses 28px de padding : le bouton n'empiète donc
     que de 20 + 36 - 28 = 28px sur cette boîte. 40px laissent 12px d'air. */
  padding-right: 40px;
  min-height: 36px;
}

/* Le logo tenait 70px de haut en pleine largeur ; à côté du titre il est
   borné dans les deux sens — les logos clients arrivent en PNG de rapports
   de forme très variables (même contrainte que .client-logo de l'en-tête). */
.panel-logo {
  height: 56px;
  width: auto;
  max-width: 130px;
  object-fit: contain;
  flex-shrink: 0;
  margin-bottom: 0;
}

/* Posé par setupTopbar() depuis les données du projet : sans source, l'image
   ne doit pas réserver de place à gauche du titre. */
.panel-logo:not([src]),
.panel-logo[src=""] {
  display: none;
}

.panel-title {
  min-width: 0;
}

/* Typography */
#villa-type {
  font-size: 24px;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: 0.5px;
  color: white;
  /* .panel-header met tout son contenu en capitales — pertinent pour les
     en-têtes de panneaux latéraux (« Filters », « Scenes »), pas pour un nom
     de typologie saisi dans le CMS, qui se lit mieux tel qu'il a été écrit. */
  text-transform: none;
  /* Les noms de typologie viennent du CMS : longueur non bornée. */
  overflow-wrap: anywhere;
}

#villa-title {
  font-size: 12px;
  opacity: 0.5;
  letter-spacing: 1px;
  margin-bottom: 4px;
}

/* Content — plus de filets : c'est l'espacement qui porte la hiérarchie.
   Le libellé s'efface, la valeur reste pleine, et l'écart entre lignes fait
   le travail que faisait la bordure. */
.panel-content {
  display: flex;
  flex-direction: column;
  gap: 18px;
  font-size: 15px;
}

.panel-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
}

.panel-row > span:first-child {
  color: rgba(255,255,255,0.45);
  font-size: 13px;
  letter-spacing: 0.02em;
}

.panel-row > span:last-child {
  font-weight: 500;
  text-align: right;
}

/* ---- Données de l'unité — une seule liste ----
   Les champs globaux (Status, Prix) et les propriétés dynamiques du projet
   partagent désormais la même grille, au lieu de deux <dl> au rendu différent.
   Même partition visuelle que .panel-row, qu'elles remplacent : le libellé
   s'efface à gauche, la valeur reste pleine à droite.

   Les styles de grille que ui-forms pose en ligne sont neutralisés dans
   renderDynamicFields() — sans quoi ils l'emporteraient sur ces règles. */
.panel-data {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 14px 16px;
  margin: 0;
  font-size: 15px;
}

/* Bat `.text-muted-foreground`/`.font-semibold` que ui-forms pose sur ses dt
   (0,1,1 contre 0,1,0) : dans ce panneau, le libellé est la mention faible. */
.panel-data dt {
  color: rgba(255, 255, 255, 0.45);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.02em;
}

.panel-data dd {
  margin: 0;
  font-weight: 500;
  text-align: right;
}

/* Filet discret entre les champs globaux et les propriétés du projet — posé
   seulement s'il y a bien quelque chose en dessous (cf. prependGlobalRows). */
.panel-data dt.is-group-end,
.panel-data dd.is-group-end {
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.panel-description {
  font-size: 13px;
  line-height: 1.5;
  opacity: 0.7;
}
#villa-vr {
    cursor: pointer;
}
/* Status colors */
.status-row span:last-child {
    font-weight: 700;
}

.status-green {
    color: #00c853;
}

.status-yellow {
    color: #ffd600;
}

.status-red {
    color: #ff3d00;
}

/* ---- Tuiles d'action (VR, plan) ----
   Carré à l'image des tuiles de la galerie : un aperçu flouté en fond, le
   pictogramme et le libellé posés dessus. Poser --preview (url(...)) pour
   utiliser un vrai visuel ; sans lui, un dégradé de marque tient la place. */
/* Rangée centrée, posée juste sous les attributs — pas repoussée en bas de
   carte : la tuile se lit comme la suite de la fiche, pas comme un pied. */
.panel-actions {
  display: flex;
  gap: 12px;
  flex-wrap: nowrap;
  width: 100%;
  /* Une seule tuile (fiche sans plan) se pose au centre au lieu d'être
     collée à gauche. */
  justify-content: center;
  /* S'ajoute au gap de 26px de .panel-card : les tuiles sont des actions, pas
     la suite de la liste d'attributs — il leur faut plus d'air que l'écart
     entre deux blocs voisins. */
  margin-top: 14px;
}

.panel-tile {
  position: relative;
  /* Les tuiles se partagent la rangée : deux remplissent la ligne (2 × 50 %
     − 6px de gouttière), trois s'équilibrent à ~33 %, et une tuile seule est
     plafonnée à la moitié de la rangée au lieu de s'étirer — avec
     aspect-ratio 1/1, s'étirer donnait un carré énorme. */
  flex: 1 1 0;
  max-width: calc(50% - 6px);
  min-width: 0;
  aspect-ratio: 1 / 1;

  border: none;
  border-radius: 5px;
  overflow: hidden;
  padding: 0;

  background: none;
  color: white;
  cursor: pointer;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;

  transition: transform 0.2s;
}

/* Aperçu flouté. L'agrandissement évite que le flou laisse voir les bords. */
.panel-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;

  background-image: var(--preview, linear-gradient(150deg, #3a3b45, #17171b));
  background-size: cover;
  background-position: center;

  filter: blur(12px) saturate(130%);
  transform: scale(1.25);
  transition: filter 0.3s, transform 0.3s;
}

/* Voile pour garder le texte lisible quel que soit l'aperçu */
.panel-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(0, 0, 0, 0.34);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10);
  border-radius: inherit;
}

.panel-tile:hover { transform: translateY(-2px); }
.panel-tile:hover::before { filter: blur(7px) saturate(140%); transform: scale(1.18); }

.panel-tile .material-symbols-sharp,
.panel-tile .panel-tile-label {
  position: relative;
  z-index: 2;
}

.panel-tile .material-symbols-sharp { font-size: 30px; }

.panel-tile-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  opacity: 0.9;
}

/* ============================================================
   MAP MODAL
============================================================ */

#map-modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: blur(6px);
  display: none;
  z-index: 999;
}

#map-modal.active {
  display: flex;
  align-items: center;
  justify-content: center;
}

.map-content {
  width: min(90vw, 900px);
  height: min(70vh, 600px);
  border-radius: 5px;
  overflow: hidden;
  background: #000;
}

.map-content iframe {
  width: 100%;
  height: 100%;
  border: none;
}

.map-external {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);

  display: none;
  align-items: center;
  gap: 6px;

  padding: 8px 14px;
  border-radius: 5px;

  background: rgba(0, 0, 0, 0.72);
  color: white;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
}

.map-external:not([style*="none"]) { display: inline-flex; }

.map-external .material-symbols-sharp { font-size: 18px; }

.map-close {
  position: absolute;
  top: 10px;
  right: 10px;

  width: 40px;
  height: 40px;
  border-radius: 5px;

  background: rgba(0,0,0,0.7);
  color: white;
  border: none;
}

/* ---- Panel: Group elements ---- */

.panel-sep {
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  margin: 0 0 2px;
}

.panel-hint {
  color: rgba(255, 255, 255, 0.35);
  font-size: 13px;
  font-style: italic;
  margin: 0;
}

.panel-section-label {
  color: rgba(255, 255, 255, 0.45);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.panel-scene-link {
  margin-top: 4px;
}

.panel-scene-btn {
  width: 100%;
  padding: 10px 16px;
  border-radius: 5px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}

.panel-scene-btn:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.24);
}

/* ============================================================
   MOBILE / TACTILE
   ============================================================ */

/* iOS zoome la page à la prise de focus d'un input sous 16px : on fixe la
   taille des champs de filtres sur les pointeurs grossiers. */
@media (pointer: coarse) {
  #panel-filter .input,
  #panel-filter .select,
  #panel-filter input[type="date"],
  #panel-filter input[type="number"],
  #panel-filter .gf-multi-select-summary {
    font-size: 16px;
  }
}

/* Pas de :hover persistant sur tactile : un premier appui laisserait les
   boutons « survolés » en place. On restaure l'état de repos ; c'est l'état
   :active (déjà posé sur .side-btn/.ui-btn) qui répond au doigt. */
@media (hover: none) {
  .ui-btn:hover { background: none; color: var(--btn-ink); }
  .side-btn:hover { background: rgba(255, 255, 255, 0.72); }
  .side-btn.active:hover { background: var(--btn-ink); }

  #panel-filter .input:hover,
  #panel-filter .select:hover { border-color: rgba(255, 255, 255, 0.16); }
  #panel-filter .gf-multi-select-summary:hover { background: transparent; }
  #panel-filter .gf-multi-select-list label:hover { background: transparent; }

  .panel-close-btn:hover {
    background: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.7);
  }

  .scene-item:hover { transform: none; }
  .scene-item:hover .scene-preview { filter: blur(9px) saturate(130%); transform: scale(1.3); }

  .panel-tile:hover { transform: none; }
  .panel-tile:hover::before { filter: blur(12px) saturate(130%); transform: scale(1.25); }

  .panel-scene-btn:hover {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.12);
  }
}

/* Tactile : le pincement remplace les deux boutons de zoom (voir
   initPinchZoom), qui prenaient deux cibles de 44 px dans une barre qui
   déborde déjà sur un téléphone. Au doigt, personne ne cherche un bouton
   de zoom sur une maquette. */
@media (hover: none) and (pointer: coarse) {
  #zoom-in,
  #zoom-out { display: none; }
}

/* --- Disposition téléphone (portrait) ---
   Les panneaux détachés passent en feuille basse plein écran, la barre du bas
   se compacte, et les onglets filtres/scènes se regroupent au-dessus d'elle. */
@media (max-width: 640px) {
  /* Panneau villa → feuille basse */
  #villa-panel {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 80dvh;
    max-height: 80dvh;
    padding: 0;
    transform: translateY(100%);
  }
  #villa-panel.active { transform: translateY(0); }

  .panel-card {
    border-radius: 12px 12px 0 0;
    padding: 20px 20px calc(24px + env(safe-area-inset-bottom, 0px));
  }

  /* Panneaux filtres/scènes → feuille basse */
  .side-panel {
    left: 0;
    right: 0;
    top: auto;
    bottom: 0;
    width: 100%;
    height: 80dvh;
    max-height: 80dvh;
    padding: 0;
    transform: translateY(100%);
  }
  .side-panel.active { transform: translateY(0); }

  .side-panel-card {
    border-radius: 12px 12px 0 0;
    padding: 18px 20px calc(24px + env(safe-area-inset-bottom, 0px));
  }

  /* Onglets filtres/scènes : regroupés au-dessus de la barre du bas */
  #side-controls {
    top: auto;
    bottom: calc(78px + env(safe-area-inset-bottom, 0px));
    left: 50%;
    transform: translateX(-50%);
    flex-direction: row;
    gap: 8px;
  }
  #side-controls.is-pushed { transform: translateX(-50%); }

  /* Barre du bas : boutons compacts, défilement si trop nombreux */
  .bottom-center {
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    gap: 6px;
    max-width: calc(100vw - 16px);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .bottom-center::-webkit-scrollbar { display: none; }

  /* 44px : la cible tactile confortable. À 38px, deux boutons voisins de la
     barre du bas se touchaient sous le pouce. */
  .ui-btn,
  .side-btn {
    width: 44px;
    height: 44px;
  }
  .side-btn .material-symbols-sharp,
  .ui-btn .material-symbols-sharp { font-size: 20px; }

  .panel-title { font-size: 20px; }

  /* Téléphone : deux rangées de contrôles se superposent en bas (barre +
     onglets filtres/scènes). Le repère se pose au-dessus des deux. */
  #first-run-hint {
    bottom: calc(148px + env(safe-area-inset-bottom, 0px));
    font-size: 12px;
  }
}
/* ============================================================
   BARRE DU HAUT ESCAMOTABLE
   Bouton #toggle-header : la maquette seule, sans chrome. La barre glisse
   vers le haut plutôt que de disparaître d'un coup — on doit comprendre
   où elle est partie, et comment la rappeler.
   ============================================================ */

.site-header {
  transition: transform .28s var(--model-transition-timing, ease),
              opacity .28s var(--model-transition-timing, ease);
}

body.is-header-hidden .site-header {
  transform: translateY(calc(-100% - 24px));
  opacity: 0;
  pointer-events: none;
}

/* Barre du bas, même traitement : elle sort par le bas. #side-controls reste
   en place — c'est lui qui porte le bouton pour la rappeler. */
.bottom-center {
  transition: transform .28s var(--model-transition-timing, ease),
              opacity .28s var(--model-transition-timing, ease);
}

body.is-bottombar-hidden .bottom-center {
  transform: translateX(-50%) translateY(calc(100% + 24px));
  opacity: 0;
  pointer-events: none;
}

/* ============================================================
   REPÈRE DE PREMIER USAGE
   Une pastille, le temps d'un regard. Elle ne bloque rien (pointer-events
   none) et s'efface au premier geste : voir initFirstRunHint().
   ============================================================ */

#first-run-hint {
  position: fixed;
  left: 50%;
  bottom: calc(86px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%) translateY(6px);

  display: flex;
  align-items: center;
  gap: 8px;
  max-width: calc(100vw - 32px);
  padding: 10px 16px;

  border-radius: 999px;
  background: rgba(17, 17, 17, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: #fff;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.3;
  text-align: left;

  opacity: 0;
  pointer-events: none;
  z-index: 40;
  transition: opacity .4s ease, transform .4s ease;
}

#first-run-hint[hidden] { display: none; }
#first-run-hint.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }

#first-run-hint .material-symbols-sharp {
  font-size: 20px;
  flex: none;
  animation: hint-swipe 1.8s ease-in-out infinite;
}

@keyframes hint-swipe {
  0%, 100% { transform: translateX(-3px); }
  50% { transform: translateX(3px); }
}

/* Le repère est une invitation, pas une animation de fond : sur un système
   qui demande moins de mouvement, la pastille reste, la main s'arrête. */
@media (prefers-reduced-motion: reduce) {
  #first-run-hint .material-symbols-sharp { animation: none; }
  .site-header,
  .bottom-center { transition: none; }
}
