/*
  PROTOTYPE.CSS — DA brutaliste M&M's (Phase A v3.2)

  Migration en cours : iOS soft → brutaliste M&M's basée sur Figma.
  Tokens --mm-* = la nouvelle DA (extraite du Figma kVGszc34RDGhQFNA9qp0EE).
  Tokens --proto-* = legacy, gardés pour rétro-compat des composants pas
  encore migrés (loop card, transport, etc.). On migrera composant par
  composant dans les phases B → H, puis on virera --proto-*.

  Conventions :
    - Touch targets ≥ 44pt (iOS HIG)
    - Font Supply (OTF dans assets/fonts/) avec fallback SF Pro tant
      que les fichiers ne sont pas déposés
    - Bg principal noir #1e1e1e (brutaliste)
*/

/* ==== Supply font (Apparat) — 3 weights disponibles ==== */
/* POURQUOI font-display:swap : iOS Safari sans .otf disponible affichera
   le fallback immédiatement (pas de FOIT). Quand l'OTF arrive, switch
   transparent vers Supply. */
@font-face {
  font-family: 'Supply';
  font-weight: 200;      /* UltraLight pour les hairlines (BPM, status) */
  font-style: normal;
  font-display: swap;
  src: url('assets/fonts/Supply-UltraLight.otf') format('opentype');
}
@font-face {
  font-family: 'Supply';
  font-weight: 400;      /* Regular body */
  font-style: normal;
  font-display: swap;
  src: url('assets/fonts/Supply-Regular.otf') format('opentype');
}
@font-face {
  font-family: 'Supply';
  font-weight: 700;      /* Bold pour labels boutons + emphasis */
  font-style: normal;
  font-display: swap;
  src: url('assets/fonts/Supply-Bold.otf') format('opentype');
}

/* ==== Reset léger ==== */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
  margin: 0; padding: 0;
  height: 100%;
  font-family: var(--mm-font-body);
  font-size: 15px;
  color: var(--mm-ink);
  /* Phase 8 v3.3 — bg dark fallback (avant chargement caméra) puis transparent
     via override. Le <video> est positionné fixed inset:0 derrière toute l'UI. */
  background: var(--mm-bg);
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
}
button { font-family: inherit; font-size: inherit; }

/* ==== Tokens M&M's brutaliste (Phase A) ==== */
/* Extraits depuis Figma : bg principal #1e1e1e, frame button #d9d9d9,
   rouge actif (PLAY/WRITE) #ff0000, accent jaune #ffed00 (cf plan). */
:root {
  /* Surfaces & inks */
  --mm-bg:        #1e1e1e;   /* fond global écran (dark) */
  --mm-surface:   #ffffff;   /* loop card active + button face white */
  --mm-mute:      #c7c7cc;   /* loop card muette (mid gray) */
  --mm-frame:     #d9d9d9;   /* contour de bouton 60x60 outer (gris clair) */
  --mm-ink:       #1e1e1e;   /* texte sur surfaces claires */
  --mm-ink-dim:   #8e8e93;   /* texte secondaire / labels dim */
  --mm-ink-soft:  rgba(255, 255, 255, 0.45);  /* texte sur fond noir */

  /* Couleurs d'état — Figma brand (cf design tokens kVGszc34RDGhQFNA9qp0EE) */
  --mm-accent:    #ffed00;   /* jaune accent (state actif, hover) */
  --mm-danger:    #ff0000;   /* rouge RÉSERVÉ au record (WRITE) — status-light model */
  --mm-warn:      #ffab00;   /* amber "pause" (PLAY paused) — jaune-ambre, distinct du record */
  --mm-hold:      #ff5e00;   /* orange "hold" (HOLD held) — couleur propre, distincte du pause amber */
  --mm-success:   #51b743;   /* vert PLAY playing / WRITE rec-success (Figma brand) */
  --mm-grey-mid:  #9f9f9f;   /* gris medium boutons désactivés (HOLD, STOP inactifs) */
  --mm-grey-dark: #5f5f5f;   /* gris dark loop card Désactivé (Figma 39:798) */

  /* Typography */
  --mm-font-body: 'Supply', -apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif;
  /* Pas de Supply Mono : on utilise Supply Regular + fallback mono système
     pour les contextes monospace (BPM display, ADSR labels). */
  --mm-font-mono: 'Supply', ui-monospace, 'SF Mono', Menlo, monospace;

  /* Border radii (Figma utilise multiples de 4) */
  --mm-radius-sm:  4px;   /* card outer (LoopCard, BPM card, Slider cards, Scale cards) */
  --mm-radius-md:  8px;   /* (legacy, peu utilisé en v3) */
  --mm-radius-lg:  12px;  /* (legacy) */
  --mm-radius-btn: 16px;  /* forme interne des boutons transport (STOP rouge 56×56, etc.) */

  /* Font sizes — échelle Supply Regular extraite du Figma */
  --mm-text-xs:    10px;  /* loop card label "LOOP D1" */
  --mm-text-sm:    12px;  /* button labels (STOP, CUT OFF, OCTAVE, MAJOR…), text — BPM label */
  --mm-text-md:    15px;  /* body text (défaut) */
  --mm-text-lg:    19px;  /* BPM display value "- 145 +" */

  /* Letter-spacing */
  --mm-tracking-tight: -1.14px; /* BPM display value 19px (cf 47:424) */

  /* Spacing scale (Figma : multiples de 4 + spécifiques composants) */
  --mm-space-1:    2px;   /* padding interne mini (frame autour btn inner) */
  --mm-space-2:    4px;
  --mm-space-3:    8px;
  --mm-space-4:    16px;
  --mm-space-loop: 26px;  /* gap interne LoopCard : EYE+LABEL ↔ slider (cf 39:797) */

  /* Touch targets (génériques) */
  --mm-btn-sq:        60px;  /* transport buttons square (Play, Hold, Stop, Write) */
  --mm-btn-sq-inner:  56px;  /* forme intérieure (red square pour STOP, etc.) */
  --mm-btn-rect-h:    40px;  /* mode buttons (Drum, Synth, View, Share) */

  /* ====== Tokens LEGACY (proto-*) — mapping vers --mm-* pour transition ====== */
  /* À mesure que les phases B → H avancent, on retire ces remappings et on
     migre les composants vers --mm-* directement. */
  --proto-bg:           var(--mm-bg);
  --proto-surface:      var(--mm-surface);
  --proto-surface-2:    var(--mm-mute);
  --proto-ink:          var(--mm-ink);
  --proto-ink-2:        #3a3a3c;
  --proto-ink-3:        var(--mm-ink-dim);
  --proto-separator:    rgba(255, 255, 255, 0.12);
  --proto-accent:       var(--mm-accent);
  --proto-accent-soft:  rgba(255, 237, 0, 0.12);
  --proto-success:      var(--mm-success);
  --proto-danger:       var(--mm-danger);
  --proto-warn:         var(--mm-warn);

  --proto-radius-sm:    var(--mm-radius-sm);
  --proto-radius-md:    var(--mm-radius-md);
  --proto-radius-lg:    var(--mm-radius-lg);
  --proto-radius-pill:  100px;

  --proto-shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.06);
  --proto-shadow-md:    0 2px 6px rgba(0, 0, 0, 0.08);
}

.hidden { display: none !important; }
.inactive-mode { display: none !important; }

/* Indicateur de zoom caméra (pinch). Apparaît brièvement au centre haut. */
.proto-zoom-indicator {
  position: fixed;
  top: 18%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  padding: 6px 14px;
  border-radius: 100px;
  background: rgba(30, 30, 30, 0.6);
  color: var(--mm-frame);
  font-family: var(--mm-font-body);
  font-size: 16px;
  letter-spacing: 0.5px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease-out;
}
.proto-zoom-indicator.visible { opacity: 1; }

/* ==== Splash ==== */
.proto-splash {
  position: fixed; inset: 0;
  background: var(--proto-bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 32px;
  text-align: center;
  gap: 16px;
  z-index: 10;
}
.proto-splash h1 {
  font-size: 28px; font-weight: 700; margin: 0;
  letter-spacing: -0.5px;
}
.proto-splash-sub {
  color: var(--proto-ink-3); font-size: 13px; margin: 0;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.proto-btn-primary {
  background: var(--proto-accent); color: white;
  border: none;
  padding: 14px 32px;
  border-radius: var(--proto-radius-pill);
  font-size: 17px; font-weight: 600;
  min-height: 50px;
  cursor: pointer;
  box-shadow: var(--proto-shadow-md);
}
.proto-btn-primary:active { background: #0062cc; }
.proto-hint {
  color: var(--proto-ink-3); font-size: 13px;
  max-width: 280px;
}
.proto-error {
  color: var(--proto-danger); font-size: 14px;
  max-width: 320px;
}

/* ==== Écran d'accueil DRUM&MS (tokens --mm-*, typo Supply) ====
   Fond TRANSPARENT : l'interface réelle s'affiche en filigrane (30%) derrière
   (cf html.pre-launch plus bas). Nom + baseline + 2 boutons identiques ; choisir
   un mode lance l'app (pas de bouton Start). */
#splash.proto-splash { background: transparent; gap: 0; }
/* Hero = colonne centrée, largeur = contenu (les boutons définissent la largeur). */
.splash-hero { display: flex; flex-direction: column; align-items: center; gap: 12px; }
/* DRUM&MS : mot normal, TAILLE de typo calée (pas d'espacement) pour remplir B→D. */
/* #splash pour battre .proto-splash h1 (sinon font-size écrasée à 28px). */
#splash .splash-title { margin: 0; font-family: var(--mm-font-body); font-size: 34.2px; font-weight: 700;
  text-transform: uppercase; color: var(--mm-frame); line-height: 1; white-space: nowrap; }
/* Baseline : mot normal, TAILLE calée pour remplir la largeur des boutons. */
/* margin-top négatif : remonte légèrement la baseline vers le titre (le gap du
   hero est 12px ; -6px → ~6px sous DRUM&MS). */
.splash-tagline { margin: -6px 0 0; font-family: var(--mm-font-body); font-size: 13.1px;
  letter-spacing: 0; color: var(--mm-ink-dim); white-space: nowrap; }
.splash-launch-row { display: flex; gap: 10px; }
/* Boutons dimensionnés au CONTENU, padding ÉGAL tout autour (12px), petits. */
.splash-launch { -webkit-appearance: none; appearance: none; cursor: pointer;
  background: var(--mm-frame); color: var(--mm-ink); border: none; border-radius: var(--mm-radius-sm);
  padding: 12px; font-family: var(--mm-font-body); font-size: 12px; letter-spacing: 0;
  text-transform: uppercase; }

/* Avant le choix du mode : l'interface réelle est visible en filigrane (30%),
   non-interactive, grille masquée (le teaser ne montre pas la grille de placement). */
html.pre-launch #stage { opacity: 0.3; pointer-events: none; }
html.pre-launch .grid-zone { display: none !important; }

/* ==== Coach marks (tour interactif, tokens --mm-*) ==== */
.cm-root { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
/* Panneaux sombres autour de la cible : bloquent les clics hors-cible. */
.cm-mask { position: fixed; background: rgba(0,0,0,0.72); pointer-events: auto; }
/* Anneau autour de la cible (pulse léger). Ne capte pas les clics. */
.cm-ring { position: fixed; border: 2px solid var(--mm-accent); border-radius: 10px;
  pointer-events: none; box-shadow: 0 0 0 9999px rgba(0,0,0,0); animation: cm-pulse 1.2s ease-in-out infinite; }
@keyframes cm-pulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.06); opacity: 0.65; } }
.cm-bubble { position: fixed; max-width: 280px; box-sizing: border-box; pointer-events: auto; z-index: 61;
  /* Fond translucide (texte reste 100% opaque) → on voit le plan de travail derrière. */
  background: rgba(217, 217, 217, 0.78); color: var(--mm-ink); border-radius: var(--mm-radius-sm);
  padding: 14px 16px; font-family: var(--mm-font-body); }
.cm-bubble h3 { margin: 0 0 6px; font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; }
.cm-bubble p { margin: 0 0 12px; font-size: 14px; line-height: 1.45; }
.cm-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cm-skip { background: none; border: none; padding: 0; cursor: pointer; font-family: var(--mm-font-body);
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--mm-ink-dim); }
.cm-next { background: var(--mm-ink); color: var(--mm-frame); border: none; border-radius: 3px; cursor: pointer;
  padding: 8px 16px; font-family: var(--mm-font-body); font-size: 12px; letter-spacing: 1px; text-transform: uppercase; }
.cm-hint { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--mm-ink-dim); }
/* Marqueurs "place ici" (step 1) : cercle pointillé couleur kick, pulsé. */
.cm-marker { position: fixed; border: 2px dashed #e63946; border-radius: 50%;
  pointer-events: none; z-index: 61; animation: cm-pulse 1.1s ease-in-out infinite; }

/* ==== Mode BEGINNER / PRO ==== */
/* Beginner masque : WRITE, HOLD, loops, œil, et (synth) cutoff+octave.
   HOLD est caché car non compris par les novices (cf test utilisateur) : le
   transport beginner se réduit à PLAY puis STOP, deux gros boutons pleine largeur
   empilés (mêmes dimensions). */
/* Préfixe html.* → spécificité (0,2,1) pour battre, quel que soit l'ordre,
   .proto-synth-bottom.inactive-mode { display:grid!important } (drum mode). */
html.mode-beginner #btn-rec,
html.mode-beginner #btn-hold,
html.mode-beginner .proto-loops,
html.mode-beginner #btn-eye-global,
html.mode-beginner .proto-synth-bottom { display: none !important; }
/* PLAY et STOP : chacun pleine largeur (mêmes dimensions), empilés. */
html.mode-beginner .proto-controls .proto-transport #btn-play,
html.mode-beginner .proto-controls .proto-transport #btn-stop { grid-column: 1 / -1; aspect-ratio: 2 / 1; }

/* Toggle Beginner/Pro (splash + feuille) — même état que les boutons top bar. */
.lvl-toggle { display: inline-flex; gap: 1px; }
.lvl-btn {
  -webkit-appearance: none; appearance: none; cursor: pointer;
  border: 0.5px solid var(--mm-frame); background: var(--mm-frame); opacity: 0.4;
  color: var(--mm-ink); border-radius: var(--mm-radius-sm); padding: 9px 18px;
  font-family: var(--mm-font-body); font-size: 12px; letter-spacing: 1px; text-transform: uppercase;
}
.lvl-btn.active { opacity: 1; }

.proto-gear-icon { display: block; width: 20px; height: 20px; }

/* Feuille réglages (engrenage) — bottom sheet brutaliste. */
.settings-sheet { position: fixed; inset: 0; z-index: 70; background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: flex-end; justify-content: center; }
.settings-sheet.hidden { display: none; }
.settings-panel { width: 100%; max-width: 420px; box-sizing: border-box;
  background: var(--mm-bg); border-top: 1px solid #2a2a2a; border-radius: 12px 12px 0 0;
  padding: 20px 18px calc(env(safe-area-inset-bottom) + 20px);
  display: flex; flex-direction: column; gap: 12px; color: var(--mm-frame); font-family: var(--mm-font-body); }
.settings-panel h2 { margin: 0 0 4px; font-size: 13px; letter-spacing: 2px; text-transform: uppercase; font-weight: 400; }
.settings-row { display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--mm-frame); color: var(--mm-ink); border: none; border-radius: var(--mm-radius-sm);
  padding: 14px 16px; font-family: var(--mm-font-body); font-size: 14px; text-transform: uppercase; letter-spacing: 1px; }
button.settings-row { cursor: pointer; width: 100%; text-align: left; }
.settings-level { background: transparent; color: var(--mm-frame); padding: 4px 2px; }
.settings-level > span { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; }
.settings-close { -webkit-appearance: none; appearance: none; cursor: pointer; background: transparent;
  border: 1px solid var(--mm-frame); color: var(--mm-frame); border-radius: var(--mm-radius-sm);
  padding: 12px 0; margin-top: 4px; font-family: var(--mm-font-body); font-size: 12px; letter-spacing: 1px; text-transform: uppercase; }

/* ==== Stage layout ==== */
.proto-stage {
  height: 100%;
  display: flex; flex-direction: column;
  padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom);
  background: var(--proto-bg);
}

/* ==== Top bar ==== */
/* Phase 6 v3.3 — bar transparente (overlay sur caméra Phase 8). */
.proto-top-bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 12px;
  background: transparent;
  border-bottom: none;
  position: sticky; top: 0; z-index: 5;
}
/* Phase 2 v3.3 — Mode buttons + icon buttons brutaliste Figma.
   Pattern toggle commun pour DRUM / SYNTH / View / Share :
     - Activé   : fill #d9d9d9, text #1e1e1e (high contrast, "lit")
     - Désactivé: transparent + outline 0.5px #d9d9d9, text #d9d9d9 (low contrast)
   Tous en 60×40 rounded 4px.

   NB : les icon-btn (View=👁, Share=↑) gardent leur emoji comme contenu —
   migration vers labels texte "VUE"/"SHARE" se fera en Phase H (restructure HTML).
*/
.proto-icon-btn {
  width: var(--mm-btn-sq);                  /* 60 */
  height: var(--mm-btn-rect-h);             /* 40 */
  padding: 0;
  border: 0.5px solid var(--mm-frame);
  /* État INACTIF = rempli gris clair à opacité réduite (idem loop vide), pas un
     contour. Le bouton ACTIF passe à opacité 1 (cf .active). */
  background: var(--mm-frame);
  opacity: 0.4;
  border-radius: var(--mm-radius-sm);       /* 4 */
  font-family: var(--mm-font-body);
  font-size: 18px;                          /* taille emoji ajustée pour 40px frame */
  color: var(--mm-ink);                     /* texte/icône sombre sur fond clair */
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  line-height: 1;
}
.proto-icon-btn.active { opacity: 1; }      /* actif = plein */
.proto-icon-btn.recording {
  background: var(--mm-danger);             /* rouge */
  color: var(--mm-frame);
  border-color: var(--mm-danger);
  opacity: 1;
  animation: rec-pulse 1s ease-in-out infinite;
}
@keyframes rec-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
/* Bouton SHARE : texte (au lieu d'icône). 12px uppercase comme DRUM/SYNTH. */
.proto-share-btn {
  font-size: var(--mm-text-sm);             /* 12 */
  text-transform: uppercase;
}
/* Icône œil (top bar) : vecteur stroke via currentColor → s'adapte au fond
   (dark sur card claire active, clair sur outline inactif). */
.proto-eye-icon {
  display: block;
  width: 23px;
  height: 16px;
}

/* Mode nav — DRUM / SYNTH toggle (60×40, gap 1px comme Figma x=15,76). */
.proto-mode-nav {
  display: inline-flex;
  gap: 1px;
}
.proto-mode-nav .nav-btn {
  width: var(--mm-btn-sq);                  /* 60 */
  height: var(--mm-btn-rect-h);             /* 40 */
  padding: 0;
  border: 0.5px solid var(--mm-frame);
  /* Inactif = rempli gris clair atténué (idem loop vide) ; sélectionné = plein. */
  background: var(--mm-frame);
  opacity: 0.4;
  border-radius: var(--mm-radius-sm);
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;
  color: var(--mm-ink);                     /* texte sombre sur fond clair */
  text-transform: uppercase;
  cursor: pointer;
  line-height: 1;
}
.proto-mode-nav .nav-btn.active { opacity: 1; }

/* Status (debug) — gros et visible pour faciliter le debug à distance */
.proto-status {
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--proto-ink);
  background: var(--proto-warn);
  text-align: center;
  border-bottom: 1px solid var(--proto-separator);
}
.proto-status:empty::before { content: '—'; opacity: 0.4; }

/* ==== Cam zone — Phase 8 v3.3 ====
   La caméra est extraite du flow normal et positionnée fixed fullscreen
   derrière toute l'UI (z-index 0). La <section> .proto-cam-zone devient
   un "spacer" transparent dans le flex flow qui définit où le tracking
   M&M's a lieu visuellement (grid-zone overlay positionnée dedans). */
.proto-cam-zone {
  position: relative;
  flex: 1 1 0;                          /* prend l'espace restant, rétractable */
  min-height: 0;                        /* autorise le shrink pour que loops restent visibles */
  background: transparent;             /* laisse passer la caméra fixed */
  overflow: visible;
}
.proto-cam-zone #cam,
#cam {
  position: fixed;                      /* fullscreen sous tout l'UI */
  inset: 0;
  width: 100vw;
  height: 100vh;
  object-fit: cover;
  z-index: 0;                           /* sous le stage qui sera z-index 1+ */
  display: block;
  background: var(--mm-bg);             /* fallback dark avant chargement webcam */
}
/* #11 — overlay fullscreen fixed (aligné EXACTEMENT sur la caméra), z-index 1
   = au-dessus de la caméra (z0) mais SOUS l'UI (z2). pointer-events:none. */
.proto-overlay {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
/* Les sections UI passent au-dessus de l'overlay (z2) : cards cliquables +
   opaques par-dessus pulses/playhead. */
.proto-top-bar,
.proto-cam-zone,
.proto-controls,
.proto-loops,
.proto-synth-bottom {
  position: relative;
  z-index: 2;
}

/* Stage : container relatif au-dessus de la caméra, hauteur viewport EXACTE
   (100dvh = dynamic viewport, gère la barre Safari iOS). overflow:hidden +
   cam-zone rétractable → la bande contrôles + loops restent toujours visibles
   en bas, jamais poussées hors écran. */
.proto-stage {
  position: relative;
  z-index: 1;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Status bar masqué en Phase 8 — c'était du debug. Le user peut le réactiver
   via JS si besoin. */
.proto-status {
  display: none;
}

/* #2 — double-tap zoom OFF globalement (touch-action:manipulation autorise
   pan/pinch mais supprime le double-tap-to-zoom iOS). Les composants à swipe
   custom (knob, cycle) overrident en touch-action:none. */
html, body {
  touch-action: manipulation;
}

/* #4 — désactive la sélection de texte sur toute l'UI (le double-tap/tap sur
   un label de loop sélectionnait le texte au lieu d'activer la loop) + désactive
   le menu contextuel iOS (callout) au long-press. */
.proto-stage,
.proto-stage * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  /* touch-action:manipulation sur TOUS les descendants (pas seulement html/body :
     touch-action n'est pas hérité, donc un double-tap sur un <span> label gardait
     touch-action:auto → zoom iOS). Les composants à swipe custom (knob, cycle)
     overrident en touch-action:none plus bas (règles postérieures, ils gagnent). */
  touch-action: manipulation;
}

/* #1 — mode paysage bloqué : message "tournez l'appareil". */
@media (orientation: landscape) and (max-height: 600px) {
  #stage, #splash { display: none !important; }
  body::after {
    content: "↻ Tournez votre appareil en mode portrait";
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 40px;
    background: var(--mm-bg);
    color: var(--mm-frame);
    font-family: var(--mm-font-body);
    font-size: 18px;
    letter-spacing: 0.5px;
  }
}

/* Seq mode guides — héritent du styles.css existant pour le visuel
   minimal (cercles radial, grid linear). On override juste la couleur
   pour qu'elle soit lisible sur fond noir caméra. */
.grid-zone {
  position: absolute; inset: 0; pointer-events: none; z-index: 4;
}
.grid-zone.hidden { display: none; }
.grid-zone-mask { display: none; }
/* #12 — grille = 80% de la cam-zone (l'espace restant au-dessus des boutons),
   centrée. inset 10% partout → 80%×80%. */
.grid-zone-box {
  position: absolute; left: 10%; right: 10%; top: 10%; bottom: 10%;
  border: 1.5px solid rgba(255,255,255,0.55);
  border-radius: 4px;
}
/* Maillage 8 colonnes (steps) × 6 rangées (guide visuel — la hauteur ne change
   pas le son, c'est la couleur qui choisit l'instrument/la note). 2 gradients :
   verticaux (colonnes, 1/8) + horizontaux (rangées, 1/6). Partagé drum + synth. */
.grid-zone-lines {
  position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(
      to right, transparent 0 calc(100%/8 - 1px), rgba(255,255,255,0.18) calc(100%/8 - 1px) calc(100%/8)
    ),
    repeating-linear-gradient(
      to bottom, transparent 0 calc(100%/6 - 1px), rgba(255,255,255,0.18) calc(100%/6 - 1px) calc(100%/6)
    );
}
.radial-rings {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none; z-index: 4;
}
.radial-rings.hidden { display: none; }
.radial-rings-inner {
  width: 90%; aspect-ratio: 1;
  background:
    radial-gradient(circle, transparent 25%, rgba(255,255,255,0.4) 26%, transparent 27%),
    radial-gradient(circle, transparent 50%, rgba(255,255,255,0.4) 51%, transparent 52%),
    radial-gradient(circle, transparent 75%, rgba(255,255,255,0.4) 76%, transparent 77%);
}

/* Œil global OFF = masque les guides. La grille (DOM) est masquée ici ;
   le playhead (canvas) est géré par un guard dans drawPlayheadLinear.
   Les ghosts restent visibles (contrôlés par leur œil per-loop). */
body.eye-global-off .grid-zone { visibility: hidden; }

/* ==== Loop rows (la pièce centrale à valider) ==== */
/* Phase 6 v3.3 — panel transparent (overlay sur caméra Phase 8). */
.proto-loops {
  background: transparent;
  border-top: none;
  padding: 12px 12px 8px;
}
.proto-loops-row-label {
  font-size: 11px; font-weight: 600;
  color: var(--proto-ink-3);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin: 4px 4px 6px;
}
.proto-loops-row-label + .loop-row { margin-bottom: 10px; }
.loop-row {
  display: grid;
  /* minmax(0, 1fr) (et non 1fr) : empêche le contenu (label/slider) de forcer
     les colonnes plus larges que leur part → plus de débordement à droite sur
     iPhone 11 / Android étroits. Les items de grille ont min-width:auto par
     défaut, ce qui les empêche de rétrécir sous la taille de leur contenu. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

/* Phase 5 v3.3 — Loop cards brutaliste Figma.
   Référence : LoopCard/Drum (39:804) + LoopCard/Synth (39:805).
   On utilise les variants Fond=Sombre (cards opaques sur substrate caméra).

   Mapping app → Figma :
     .filled.audio-on    → État=Activé    | bg #d9d9d9 | text #1e1e1e
     .filled.audio-off   → État=Désactivé | bg #5f5f5f | text #d9d9d9
     .filled.editing     → État=Edit      | bg #ff0000 | text #1e1e1e
     .empty              → Ghost=Empty    | bg outline + dim text
     .eye-on / .eye-off  → Ghost=Visible / Masqué (toggle SVG eye open/closed)

   Layout Figma : 90×59 flex column gap 26 — top row [label + eye] / slider.
   En layout actuel le card flex à 1 (grid 4 cols), restera à dimension exacte
   en Phase 8. */
.loop-slot {
  /* POURQUOI position:relative : la <video> caméra est position:fixed z-index:0,
     enfant du stage. Dans l'ordre de peinture CSS, un élément fixed z-index:0
     peint APRÈS le contenu statique en flux → il masquerait les slots statiques.
     position:relative élève les slots au même niveau de peinture que la vidéo,
     et comme ils viennent après dans le DOM, ils peignent au-dessus. */
  position: relative;
  -webkit-appearance: none;
  appearance: none;
  /* touch-action:manipulation → le double-tap (édit loop) ne déclenche plus
     le zoom OS iOS. */
  touch-action: manipulation;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: var(--mm-space-3);                   /* 8 — approx de 26 Figma serré */
  min-height: 59px;
  padding: var(--mm-space-3) var(--mm-space-3);  /* 8 */
  border-radius: var(--mm-radius-sm);       /* 4 brutaliste */
  border: none;
  font-family: var(--mm-font-body);
  cursor: pointer;
}
/* Empty = Figma Ghost=Empty (39:803) : CONTOUR seul (border 0.5px #d9d9d9),
   fond transparent (caméra visible dessous), label "LOOP D1" gris clair en
   HAUT-GAUCHE. Pas d'œil ni de slider. */
.loop-slot.empty {
  /* État vide = card "désactivée" : fond gris clair plein + opacité réduite,
     exactement comme les boutons octave/cutoff désactivés en mode drum
     (background:#d9d9d9 + opacity:0.4). Remplace l'ancien look "contour". */
  background: var(--mm-frame);              /* #d9d9d9 fond plein */
  opacity: 0.4;                            /* card atténuée (idem octave/cutoff off) */
  color: var(--mm-ink);                    /* label sombre, lisible sur le fond gris */
  align-items: flex-start;                  /* label haut-gauche */
  justify-content: flex-start;
  gap: 0;
  padding: 6px 7px;                          /* Figma px-[7] py-[6] */
}
.loop-slot-label {
  font-size: var(--mm-text-xs);             /* 10 (Figma) */
  font-weight: 400;                          /* Supply Regular */
  letter-spacing: 0;
  text-transform: uppercase;
  white-space: nowrap;
}
.loop-slot-hint {
  font-size: 9px;
  font-weight: 400;
  color: rgba(217, 217, 217, 0.55);
}

/* Slot rempli : colonne = [top row (label + œil)] puis [slider volume] */
.loop-slot.filled {
  position: relative;
  /* bg + color appliqués par état (.audio-on / .audio-off / .editing) */
}
/* État=Activé : card claire #d9d9d9, text dark */
.loop-slot.filled.audio-on {
  background: var(--mm-frame);              /* #d9d9d9 */
  color: var(--mm-ink);                     /* #1e1e1e */
}
/* État=Désactivé (mute) : card dark grey, text claire */
.loop-slot.filled.audio-off {
  background: var(--mm-grey-dark);          /* #5f5f5f */
  color: var(--mm-frame);                   /* #d9d9d9 */
}
.loop-slot-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--mm-space-2);                   /* 4 */
}
/* #9 — slider volume identique au cutoff : ligne 1px + thumb rond 8px.
   height 24px = zone tactile, track 1px centré, thumb 8px. */
.loop-slot-volume {
  width: 100%;
  height: 24px;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
}
.loop-slot-volume::-webkit-slider-runnable-track {
  height: 1px;
  background: currentColor;                 /* hérite couleur de la card */
}
.loop-slot-volume::-moz-range-track {
  height: 1px;
  background: currentColor;
}
.loop-slot-volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
  border: none;
  margin-top: -3.5px;                        /* centre sur la ligne 1px */
}
.loop-slot-volume::-moz-range-thumb {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
  border: none;
}
.loop-slot-label-btn {
  flex: 1; min-width: 0;
  border: none;
  background: transparent;
  padding: 0;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-xs);             /* 10 Figma */
  font-weight: 400;                          /* Supply Regular */
  letter-spacing: 0;
  text-transform: uppercase;
  text-align: left;
  color: inherit;                            /* hérite couleur card (.audio-on/off) */
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-radius: var(--mm-radius-sm);
  line-height: 1.1;
}
/* État=Edit : card rouge pleine #ff0000, text dark.
   Override .audio-on/off (priorité ordre source). */
.loop-slot.filled.editing {
  background: var(--mm-danger);             /* #ff0000 */
  color: var(--mm-ink);                     /* #1e1e1e */
  border: none;
  box-shadow: none;                          /* ring rouge legacy supprimé */
}
.loop-slot.filled.editing .loop-slot-label-btn {
  color: var(--mm-ink);
  font-weight: 700;                          /* emphasis sur label en édit */
}
.loop-slot.filled.editing .loop-slot-volume {
  accent-color: var(--mm-ink);              /* slider dark sur card rouge */
}
/* Phase 5 v3.3 — Eye button.
   Référence Figma 39:435 : œil vector 14×10. currentColor hérite du parent
   (.loop-slot) → trait dark sur card claire (.audio-on), trait clair sur
   card dark (.audio-off). */
.loop-slot-eye {
  width: 20px;                              /* touch area légèrement plus grande que vector 14 */
  height: 16px;
  border: none;
  background: transparent;
  border-radius: 0;
  padding: 0;
  color: currentColor;
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.loop-slot-eye svg {
  display: block;
  width: 14px;                              /* Figma exact */
  height: 10px;
}
.loop-slot.eye-on  .loop-slot-eye { opacity: 1; }
.loop-slot.eye-off .loop-slot-eye { opacity: 0.45; }
.loop-slot.filled:active { transform: scale(0.97); }

/* ==== Synth panel ==== */
/* Phase 6 v3.3 — panel transparent (overlay sur caméra Phase 8). */
.proto-synth-panel {
  background: transparent;
  border-top: none;
  padding: 12px;
  display: flex; flex-direction: column; gap: 10px;
}
.proto-row {
  display: flex; gap: 6px; align-items: center;
}
.proto-pill {
  flex: 1;
  background: var(--proto-bg);
  border: none;
  padding: 10px 12px;
  border-radius: var(--proto-radius-md);
  font-size: 13px; font-weight: 600;
  color: var(--proto-ink-2);
  cursor: pointer;
  min-height: 36px;
}
.proto-pill.active {
  background: var(--proto-ink);
  color: var(--proto-surface);
}
.proto-pill-wide { padding: 12px; font-size: 14px; }

.proto-waves .wave-btn { padding: 8px; font-size: 12px; }

/* Sliders SPILL */
.proto-slider {
  flex: 1;
  display: flex; flex-direction: column; gap: 2px;
}
.proto-slider-label {
  font-size: 10px; font-weight: 600;
  color: var(--proto-ink-3); text-transform: uppercase;
  letter-spacing: 0.4px;
}
.proto-slider input[type=range] {
  width: 100%;
}

/* ADSR knobs (en sliders pour le prototype) */
.proto-adsr { gap: 8px; }
.proto-knob {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.proto-knob-label {
  font-size: 11px; font-weight: 700;
  color: var(--proto-ink-2);
}
.proto-knob input[type=range] {
  width: 100%;
}

/* Phase 3 v3.3 — Cutoff + Octave brutaliste Figma.
   Référence : Figma Slider/CutOff (46:298, 170×60 card #d9d9d9) +
   Slider/Octave (48:28, 180×60 card #d9d9d9).
   Layout : 2 cards côte-à-côte, gap 8.
*/
.proto-synth-extra {
  gap: var(--mm-space-3);                   /* 8 */
  align-items: center;
}

/* ---- Cutoff card 170×60 ---- */
.proto-knob-wide {
  position: relative;
  flex: 0 0 170px;
  width: 170px;
  height: 60px;
  background: var(--mm-frame);              /* #d9d9d9 card bg */
  border-radius: var(--mm-radius-sm);       /* 4 */
  display: block;                            /* override .proto-knob flex */
  /* Item 2 : toute la card pilote le slider via un handler pointeur (cf
     melody-app.js). touch-action:none → le drag horizontal n'est pas interprété
     comme scroll/zoom par le navigateur. */
  touch-action: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
}
/* Le label "CUTOFF" va en BAS du card (sous le slider) */
.proto-knob-wide .proto-knob-label {
  position: absolute;
  bottom: 6px;
  left: 0; right: 0;
  text-align: center;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;                          /* Supply Regular */
  color: var(--mm-ink);                     /* #1e1e1e */
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1;
  pointer-events: none;
}
/* Le slider lui-même : ligne 1px #1e1e1e + thumb rond 8px noir.
   Zone tactile = TOUTE la card (inset:0) : le thumb 8px est trop petit à saisir,
   donc un tap/drag n'importe où sur la card pilote le slider (le track 1px reste
   centré verticalement ≈ y30, identique à avant ; le label "CUTOFF" en dessous
   reste visible car l'input est transparent + le label est pointer-events:none). */
.proto-knob-wide input[type=range] {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: pointer;
  /* Item 2 : l'input est purement visuel (track + thumb). Toute l'interaction
     passe par le handler pointeur de la card → l'input ne capte pas les events. */
  pointer-events: none;
}
.proto-knob-wide input[type=range]::-webkit-slider-runnable-track {
  height: 1px;
  background: var(--mm-ink);                /* ligne #1e1e1e 1px */
}
.proto-knob-wide input[type=range]::-moz-range-track {
  height: 1px;
  background: var(--mm-ink);
}
.proto-knob-wide input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--mm-ink);                /* thumb #1e1e1e */
  border: none;
  margin-top: -3.5px;                        /* centre sur ligne 1px */
  cursor: grab;
}
.proto-knob-wide input[type=range]::-moz-range-thumb {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--mm-ink);
  border: none;
  cursor: grab;
}

/* ---- Octave card 180×60 ----
   Note : Figma utilise un slider visuel avec 5 ticks (-2 -1 0 1 2).
   Le HTML actuel reste un stepper +/- (Phase 8 restructure HTML).
   On approxime visuellement : card #d9d9d9, "OCT" + "− value +" centré,
   label "OCTAVE" en bas. */
.proto-octave {
  position: relative;
  flex: 0 0 180px;
  width: 180px;
  height: 60px;
  background: var(--mm-frame);              /* #d9d9d9 */
  border-radius: var(--mm-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--mm-space-3);                   /* 8 */
  padding: 0 var(--mm-space-4);             /* 16 */
}
.proto-octave-label {
  /* "OCT" label devient le sub-label "OCTAVE" en bas (style Figma) */
  position: absolute;
  bottom: 6px;
  left: 0; right: 0;
  text-align: center;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;
  color: var(--mm-ink);
  text-transform: uppercase;
  letter-spacing: 0;
  margin: 0;
  line-height: 1;
  pointer-events: none;
}
/* On masque le texte "OCT" du label original, on affiche "OCTAVE" via ::after */
.proto-octave-label::before { content: "OCTAVE"; }
.proto-octave-label { font-size: 0; }       /* hide original "OCT" text */
.proto-octave-label::before {
  font-size: var(--mm-text-sm);
}

.proto-octave-btn {
  /* #5 — zone tactile élargie 44×44 (glyphe centré, visuel inchangé). */
  min-width: 44px; height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;
  color: var(--mm-ink);
  cursor: pointer;
  line-height: 1;
  touch-action: manipulation;
}
.proto-octave-btn:active { color: var(--mm-grey-mid); }
.proto-octave-value {
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;
  color: var(--mm-ink);
  min-width: 20px;
  text-align: center;
}

/* FX checkboxes */
.proto-fx { gap: 8px; }
.proto-fx-cell {
  flex: 1;
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px;
  background: var(--proto-bg);
  border-radius: var(--proto-radius-sm);
  font-size: 12px;
}
.proto-fx-cell input[type=checkbox] { margin: 0; }
.proto-fx-cell input[type=range] { flex: 1; min-width: 0; }

/* ==== Bande de contrôles — Phase 8 v3.7 (RESPONSIVE, identique sur tt device) ====
   3 colonnes en TIERS ÉGAUX basés sur le viewport (plus de px fixes → plus de
   décalage selon la largeur d'écran iPhone 13 / 14 / Pro / mini / Max).
   --third = largeur d'une colonne = hauteur de la bande (transport carré). */
.proto-controls {
  --third: calc((100vw - 32px) / 3);          /* (100vw - 2×15 marge - 2×1 gap) / 3 */
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 1px;
  padding: var(--mm-space-2) 15px 0;          /* bottom 0 → gap 1px vers les loops */
}
/* Les 3 colonnes partagent la largeur en tiers égaux. */
.proto-controls .proto-transport,
.proto-controls .proto-bpm,
.proto-controls .proto-cycle-stack,
.proto-controls .proto-knob-slot {
  flex: 1 1 0;
  min-width: 0;
}

/* Transport 2×2 — colonnes 1fr, boutons carrés (aspect-ratio) → scalent. */
.proto-controls .proto-transport {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
}
.proto-controls .proto-transport-btn {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}
/* Forme intérieure : cadre 2px responsive (inset au lieu de 56×56 fixe). */
.proto-controls .proto-transport-btn::before {
  top: 2px; left: 2px; right: 2px; bottom: 2px;
  width: auto; height: auto;
}

/* Milieu DRUM (BPM) : hauteur = largeur de colonne (≈ carré, internals en %). */
.proto-controls .proto-bpm {
  height: var(--third);
}
/* Milieu SYNTH (wave + scale empilés) : 2 cards flex remplissant la hauteur. */
.proto-controls .proto-cycle-stack {
  display: flex;
  flex-direction: column;
  gap: 1px;
  height: var(--third);
}
.proto-cycle-stack .cycle-button-mount { width: 100%; flex: 1 1 0; }
.proto-cycle-stack .cycle-button { height: 100%; }

/* Knob slot : carré qui remplit son tiers. */
.proto-controls .proto-knob-slot {
  display: flex;
  align-items: center;
  justify-content: center;
}
.proto-knob-slot .mm-knob {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
}

/* Top bar : nav à gauche, eye+share groupés à droite. */
.proto-top-right {
  display: flex;
  gap: 1px;                                   /* Figma : eye x=256, share x=317 → 1px */
}

/* Synth bottom : CUT OFF + OCTAVE, chacun flex 1 → ensemble = largeur des
   loops (2 cards de ~181, gap 1px, alignées sur la grille loops). */
/* Grid 2 colonnes EXACTEMENT égales (minmax(0,1fr)) → cutoff & octave de même
   taille quel que soit leur contenu (le flex échouait car le padding/min-width
   de l'octave forçait sa colonne plus large). */
.proto-synth-bottom {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  align-items: stretch;
  padding: 1px 15px var(--mm-space-2);        /* top 1px → gap 1px depuis les loops */
}
.proto-synth-bottom .proto-knob-wide,
.proto-synth-bottom .proto-octave {
  width: auto;                                /* override largeur fixe 170/180 */
  min-width: 0;                               /* peut rétrécir sans déborder */
}
/* En mode DRUM, cutoff/octave restent VISIBLES mais DÉSACTIVÉS (au lieu de
   disparaître via .inactive-mode { display:none }). POURQUOI : garder un layout
   identique drum/synth → la grille ne change pas de taille entre les modes, ce
   qui évite aussi de décaler les ghosts de loop d'un mode à l'autre.
   Spécificité (0,2,0)+!important > .inactive-mode (0,1,0)+!important → on gagne. */
.proto-synth-bottom.inactive-mode {
  display: grid !important;
  opacity: 0.4;
  pointer-events: none;
}

/* Loops — 4 colonnes × 2 rangées, cards 90×59 (Figma). Gap 1px.
   #14 : label en haut, slider en bas (space-between) → espace max entre eux. */
.proto-loops {
  padding: 1px 15px 0;                         /* top 1px (gap depuis controls), bottom 0 */
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.proto-loops .loop-row {
  gap: 1px;
  margin-bottom: 0;
}
.proto-loops .loop-slot {
  min-height: 59px;
  min-width: 0;                               /* peut rétrécir sous son contenu */
  overflow: hidden;                            /* coupe tout débordement interne */
  justify-content: space-between;             /* label top / slider bottom */
  gap: var(--mm-space-2);                     /* 4 mini */
  padding: 6px var(--mm-space-3) 8px;         /* + d'air vertical */
}
/* Sous-éléments des loops : ne forcent jamais l'élargissement. */
.proto-loops .loop-slot-top,
.proto-loops .loop-slot-label-btn,
.proto-loops .loop-slot-volume,
.proto-loops .loop-slot-label {
  min-width: 0;
  max-width: 100%;
}
.proto-loops .loop-slot-label-btn,
.proto-loops .loop-slot-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Phase 6 v3.3 — BPM display brutaliste Figma.
   Référence : BPM (47:422) variant Fond=Sombre (47:423). Container 120×123 :
     - Triangle métronome SVG (60×60) en haut, injecté par js/metronome.js
     - Card outlined #d9d9d9 (118×60) en bas (y=63) contenant − value +
   Aiguille animée via requestAnimationFrame, oscille au tempo BPM.
   Style stroke claire (#d9d9d9) car Fond=Sombre = visible sur dark/camera. */
/* RESPONSIVE : width via flex (tiers), height via var(--third). Internals en %. */
.proto-bpm {
  position: relative;
  width: 100%;
  background: transparent;
  border-radius: 0;
  padding: 0;
  display: block;
  color: var(--mm-frame);                   /* strokes triangle + text via inheritance */
}
/* Card valeur outlined : moitié basse du container (top 51% → bottom). */
.proto-bpm::after {
  content: "";
  position: absolute;
  top: 51%;
  bottom: 0;
  left: 1px;
  right: 1px;
  border: 1px solid var(--mm-frame);
  border-radius: var(--mm-radius-sm);
  pointer-events: none;
}
/* SVG triangle métronome — occupe toute la hauteur, centré (82% de large). */
.proto-bpm-triangle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 9%;
  width: 82%;
  height: 100%;
  overflow: visible;                          /* pas de clip du swing */
  pointer-events: none;
  color: inherit;                            /* stroke via currentColor */
}
.proto-bpm-needle {
  /* JS applique transform: rotate() ici. transform-origin = pivot bas-centre
     (coords SVG viewBox, indépendant de la taille rendue). */
  transform-origin: 34.5px 120px;
  will-change: transform;
}
/* Boutons − / + : moitié basse, collés aux bords de la card valeur. */
.proto-bpm-btn {
  position: absolute;
  top: 51%;
  bottom: 0;
  width: 33%;
  padding: 0;
  border: none;
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-lg);             /* 19 */
  font-weight: 400;                          /* Supply Regular */
  color: var(--mm-frame);                   /* light text sur outline card */
  cursor: pointer;
  text-align: center;
  touch-action: manipulation;
}
#btn-bpm-down { left: 1px; }
#btn-bpm-up   { right: 1px; }
.proto-bpm-btn:active { color: var(--mm-grey-mid); }

/* Valeur "145" centrée dans la moitié basse. " BPM" masqué via font-size:0. */
.proto-bpm-value {
  position: absolute;
  top: 51%;
  bottom: 0;
  left: 1px;
  right: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mm-font-body);
  font-size: 0;                              /* masque " BPM" */
  text-align: center;
  pointer-events: none;
  color: inherit;
}
.proto-bpm-value #bpm {
  font-size: var(--mm-text-lg);             /* 19 — re-show la valeur */
  font-weight: 400;
  letter-spacing: var(--mm-tracking-tight); /* -1.14px Figma */
  color: var(--mm-frame);
}

/* .proto-transport grid (2×2) défini Phase 8 plus haut. */

/* Phase 2 v3.3 — Transport buttons brutaliste Figma.
   Chaque bouton = card 60×60 frame #d9d9d9 (Fond Sombre, sur substrate noir)
   + forme intérieure 56×56 (::before) qui change de COULEUR et de RADIUS
   selon le bouton + l'état. Le label texte (Supply Regular 12px #1e1e1e)
   est posé au centre par-dessus la forme intérieure.

   Mapping anatomie Figma :
     - Outer 60×60 #d9d9d9 rounded 4px       = .proto-transport-btn (card frame)
     - Inner 56×56 (top:2 left:2)            = ::before (forme colorée)
     - Label centré                          = button textContent (z-index:1)

   Per-bouton inner radius (Figma) :
     - PLAY  : 3px  (presque carré)
     - HOLD  : 16px (--mm-radius-btn)
     - STOP  : 16px
     - WRITE : 32px (= circle sur 56×56)

   Per-bouton couleurs (STATUS-LIGHT : seul l'état engagé s'allume, idle = neutre,
   rouge réservé au record — comme un séquenceur / transport analogique) :
     - PLAY  : idle #9f9f9f (grey) → playing #51b743 (vert) → paused #ffab00 (amber)
     - HOLD  : idle #9f9f9f (grey) → held #ff5e00 (orange, couleur propre) ; dimmed si audio stopped
     - STOP  : #9f9f9f (grey) en permanence ; dimmed si rien à stopper (PAS de rouge :
               l'état "stopped" est déjà signalé par PLAY qui repasse en idle)
     - WRITE : #ff0000 (rouge = identité record) → rec-success flash #51b743 → denied dimmed
   Au repos (stopped) toute la rangée est grise, seul WRITE est rouge.
*/
.proto-transport-btn {
  /* appearance:none : évite que le chrome natif du <button> Safari masque la
     forme ::before colorée (rendu "transparent" quand actif, bug #7). */
  -webkit-appearance: none;
  appearance: none;
  position: relative;                       /* ancre pour ::before */
  width: var(--mm-btn-sq);                  /* 60 */
  height: var(--mm-btn-sq);                 /* 60 */
  padding: 0;
  border: none;
  border-radius: var(--mm-radius-sm);       /* 4px outer */
  background: var(--mm-frame);              /* #d9d9d9 (Fond Sombre = card claire) */
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12px */
  font-weight: 400;                         /* Supply Regular (Figma utilise Regular) */
  color: var(--mm-ink);                     /* texte sombre #1e1e1e — over inner coloré */
  text-transform: uppercase;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
/* Forme intérieure 56×56 — couleur + radius surchargés par bouton */
.proto-transport-btn::before {
  content: "";
  position: absolute;
  top: var(--mm-space-1);                   /* 2 */
  left: var(--mm-space-1);                  /* 2 */
  width: var(--mm-btn-sq-inner);            /* 56 */
  height: var(--mm-btn-sq-inner);           /* 56 */
  border-radius: var(--mm-radius-btn);      /* default 16, override par bouton */
  background: var(--mm-grey-mid);           /* default inactif, override par bouton */
  z-index: 0;
  pointer-events: none;
}
/* Le label texte doit passer AU-DESSUS de la forme intérieure.
   POURQUOI un <span> wrapper : un nœud texte nu n'est pas un élément → pas
   élevable en z-index, donc masqué derrière ::before (positionné). Le span
   l'élève au-dessus. */
.proto-transport-btn { z-index: 0; }
.proto-transport-btn .proto-btn-label {
  position: relative;
  z-index: 1;
  pointer-events: none;
}
/* touch-action:manipulation → désactive le double-tap-zoom iOS sur les boutons. */
.proto-transport-btn { touch-action: manipulation; }

/* PLAY — inner presque carré (3px). Status-light : idle gris neutre → playing
   vert (running) → paused amber (figé). Plus de rouge (réservé au record). */
.proto-play::before {
  background: var(--mm-grey-mid);
  border-radius: 3px;
}
.proto-play.playing::before { background: var(--mm-success); }
.proto-play.paused::before  { background: var(--mm-warn); }

/* HOLD — inner radius 16px, gris désactivé → orange activé */
.proto-hold::before {
  background: var(--mm-grey-mid);
  border-radius: var(--mm-radius-btn);
}
.proto-hold.active::before  { background: var(--mm-hold); }

/* STOP — gris neutre EN PERMANENCE (status-light). Pas de remplissage rouge :
   le rouge reste réservé au record (WRITE), et l'état "stopped" est déjà signalé
   par PLAY qui repasse en idle. Juste dimmed quand il n'y a rien à stopper.
   (La classe .stopped est encore posée par le JS mais n'a plus d'effet visuel.) */
.proto-stop::before {
  background: var(--mm-grey-mid);
  border-radius: var(--mm-radius-btn);
}
.proto-stop.disabled { opacity: 0.35; pointer-events: none; }

/* WRITE — inner circle (32px = full radius sur 56×56), rouge → flash vert rec-success */
.proto-rec::before {
  background: var(--mm-danger);
  border-radius: 9999px;
}
.proto-rec.rec-success::before {
  background: var(--mm-success);
  transition: background 0.15s ease-out;
}
.proto-rec.rec-denied { opacity: 0.35; }

.proto-transport-btn.disabled { opacity: 0.35; pointer-events: none; }

/* ==== Phase 4 v3.3 — Cycle button component (MMCycleButton) ==== */
/* Référence Figma : Scale (45:232) + Waves (46:309), variant Fond=Sombre.
   Card claire #d9d9d9 sur substrate caméra (overlay). Contenu sombre #1e1e1e
   (texte "BLUES" pour scale, SVG wave pour waves). 4 dots au bas du card
   indiquant la position dans le cycle.

   Dimensions Figma : 118×60 (carrée). En layout actuel les cards flexent à 1
   pour remplir la largeur disponible — la dimension exacte 118 sera enforced
   en Phase 8 (restructure layout). */
.proto-cycle-row {
  display: flex;
  gap: var(--mm-space-3);                    /* 8 */
  padding: var(--mm-space-3) var(--mm-space-4); /* 8 16 */
}
.cycle-button-mount {
  flex: 1 1 0;
  min-width: 0;
}
.cycle-button {
  /* Reset chrome navigateur */
  -webkit-appearance: none; appearance: none;
  border: none; outline: none; margin: 0;
  font: inherit; cursor: pointer;
  /* Card Fond Sombre Figma */
  position: relative;
  width: 100%;
  height: 60px;
  padding: 0;
  background: var(--mm-frame);              /* #d9d9d9 */
  color: var(--mm-ink);                     /* #1e1e1e text + SVG via currentColor */
  border-radius: var(--mm-radius-sm);       /* 4 */
  display: flex;
  align-items: center;
  justify-content: center;
  /* POURQUOI touch-action:none : empêche Safari iOS d'interpréter le swipe
     horizontal comme un swipe navigation arrière/avant. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.cycle-button:active {
  background: var(--mm-mute);               /* #c7c7cc feedback tap */
}
/* Contenu central (texte ou SVG wave) — centré verticalement avec offset
   vers le haut pour laisser de la place aux dots en bas */
.cycle-button-content {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 */
  font-weight: 400;                         /* Supply Regular */
  letter-spacing: 0;
  text-transform: uppercase;
  margin-top: -10px;                         /* offset haut pour les dots en bas */
  line-height: 1;
}
/* Dots indicator — Figma : 7×7 cercles, gap 16, position absolute bottom 8 */
.cycle-button-dots {
  position: absolute;
  bottom: 8px;
  left: 0; right: 0;
  display: flex;
  gap: 9px;                                  /* 16 entre centres = 7 + 9 gap */
  align-items: center;
  justify-content: center;
}
.cycle-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--mm-ink);          /* outline dark sur card claire */
  transition: background 80ms;
}
.cycle-dot.active {
  background: var(--mm-ink);                /* dot rempli dark = position active */
  border-color: var(--mm-ink);
}
/* Canvas de la wave synth (dessinée en code, cf js/wave-anim.js). Remplit la
   fenêtre .cycle-button-content (30px, overflow hidden) ; la résolution interne
   est gérée en JS selon le devicePixelRatio. */
.cycle-wave-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* Fallback SVG statique (si le module wave-anim n'est pas chargé). */
.cycle-wave-svg {
  width: 40px; height: 20px;
  color: inherit;                           /* SVG stroke via currentColor = #1e1e1e */
}

/* ==== Wave card (synth) — conforme Figma 46:350 ====
   La card WAVE est en CONTOUR seul (≠ la card SCALE qui est en fond plein).
   La wave est placée en HAUT et ANIMÉE (scroll horizontal seamless). */
#cycle-wave-synth .cycle-button {
  background: transparent;                   /* contour seul, pas de fond */
  border: 1px solid var(--mm-frame);         /* #d9d9d9 */
  color: var(--mm-frame);                    /* wave + dots clairs sur caméra */
  overflow: visible;                         /* laisse la wave déborder au-dessus */
}
/* Wave CENTRÉE sur la ligne supérieure de la card (à cheval, comme le triangle
   du métronome déborde). Le container est plus grand et centré verticalement
   sur le bord haut (y=0) : top négatif = moitié au-dessus, moitié à l'intérieur.
   overflow:hidden = clip horizontal du scroll. La card garde sa taille. */
#cycle-wave-synth .cycle-button-content {
  position: absolute;
  top: -15px;                                /* moitié de la hauteur → centré sur le bord */
  left: 0;                                    /* bord à bord (plus d'inset → wave continue) */
  right: 0;
  height: 30px;                              /* container agrandi (Figma ~29) */
  margin-top: 0;
  overflow: hidden;                          /* fenêtre de scroll horizontale */
}
/* La wave est dessinée dans un <canvas> animé en RAF (js/wave-anim.js), calé
   sur le BPM. Le canvas remplit la fenêtre .cycle-button-content ci-dessus.
   Aucune animation CSS ici (cf .cycle-wave-canvas). */

/* Nom de la wave active (SINE/TRIANGLE/SQUARE/SAWTOOTH) dans la card wave.
   Même typo que les labels transport (HOLD) : Supply Regular 12px uppercase,
   couleur frame (clair). CENTRÉ verticalement ET horizontalement dans la card,
   exactement comme le texte de PLAY/HOLD (qui sont aussi flex-center dans une
   card de même hauteur et alignée au même niveau) → alignement parfait garanti.
   pointer-events:none → ne bloque pas le tap/swipe de cyclage. */
.cycle-wave-name {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 — identique à HOLD */
  font-weight: 400;                         /* Supply Regular */
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1;
  color: var(--mm-frame);                   /* clair, comme la wave et les dots */
  pointer-events: none;
}

/* Dots clairs sur la card contour (la card SCALE garde ses dots sombres). */
#cycle-wave-synth .cycle-dot {
  border-color: var(--mm-frame);
}
#cycle-wave-synth .cycle-dot.active {
  background: var(--mm-frame);
  border-color: var(--mm-frame);
}

/* ==== Phase 7 v3.3 — MultiKnob/Drum (remplace les sliders TEMP) ==== */
/* Le wrapper .proto-drum-fx-temp garde son nom pour ne pas casser le toggle
   .drum-only / .synth-only existant, mais sa déco TEMP (dashed orange) est
   supprimée — c'est maintenant un simple container pour le multi-knob. */
.proto-drum-fx-temp {
  display: flex;
  justify-content: center;
  padding: 8px;
  background: transparent;
  border: none;
  margin-bottom: 6px;
}
/* Inputs source du multi-knob (pitch/decay/drive/cutoff) : invisibles, juste
   carriers de valeur pour la rétro-compat avec snapshot / applyLoopSettings. */
.mm-hidden-inputs { display: none; }

/* MultiKnob component card 121×121 (cf js/multi-knob.js + Figma 43:3). */
.mm-knob {
  position: relative;
  width: 121px;
  height: 121px;
  background: var(--mm-frame);              /* #d9d9d9 Fond Sombre */
  border-radius: var(--mm-radius-sm);       /* 4 */
  color: var(--mm-ink);                     /* #1e1e1e : stroke + text + needle */
  user-select: none;
  -webkit-user-select: none;
}
.mm-knob-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.mm-knob-needle {
  /* Le JS applique transform:rotate(deg). transform-origin déjà set inline. */
  will-change: transform;
}
.mm-knob-label {
  font-family: var(--mm-font-body);
  font-size: var(--mm-text-sm);             /* 12 — set aussi en attribut SVG */
  font-weight: 400;
  text-transform: uppercase;
  pointer-events: none;
  user-select: none;
}
.proto-drum-fx-row {
  display: flex; flex-direction: column; gap: 2px;
}
.proto-drum-fx-row label {
  font-size: 10px;
  font-family: var(--proto-mono, monospace);
  color: var(--proto-ink-3);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.proto-drum-fx-row input[type="range"] {
  margin: 0;
  width: 100%;
  -webkit-appearance: none;
  height: 20px;
  background: transparent;
}
.proto-drum-fx-row input[type="range"]::-webkit-slider-runnable-track {
  height: 2px; background: var(--proto-ink-3);
}
.proto-drum-fx-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--proto-ink); margin-top: -7px;
}

/* ==== Visualisations cachées (cohérence avec index.html, debug) ==== */
.grid-vis, .melody-vis { display: none; }

/* ==== Share menu (bottom sheet pour export/import) ==== */
.share-menu {
  position: fixed; inset: 0;
  z-index: 1000;
  display: flex; flex-direction: column; justify-content: flex-end;
}
.share-menu.hidden { display: none; }
.share-menu-backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.45);
}
.share-menu-sheet {
  position: relative;
  background: var(--proto-surface);
  border-radius: 16px 16px 0 0;
  padding: 14px 14px calc(14px + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.3);
  animation: shareSlideUp 200ms ease-out;
}
@keyframes shareSlideUp {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
.share-menu-title {
  font-size: 13px; font-weight: 700;
  color: var(--proto-ink-3);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  text-align: center;
  padding: 6px 0 14px;
}
.share-menu-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%;
  padding: 14px 12px;
  background: var(--proto-bg);
  border: none;
  border-radius: var(--proto-radius-md);
  margin-bottom: 6px;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}
.share-menu-row:active { background: var(--proto-separator); }
.share-menu-icon {
  font-size: 24px;
  flex: 0 0 32px;
  text-align: center;
}
.share-menu-text {
  display: flex; flex-direction: column; gap: 2px;
  flex: 1; min-width: 0;
}
.share-menu-label {
  font-size: 15px; font-weight: 600;
  color: var(--proto-ink);
}
.share-menu-desc {
  font-size: 12px;
  color: var(--proto-ink-3);
  line-height: 1.3;
}
.share-menu-cancel {
  width: 100%;
  padding: 14px;
  margin-top: 8px;
  background: transparent;
  border: 1px solid var(--proto-separator);
  border-radius: var(--proto-radius-md);
  font-family: inherit;
  font-size: 15px; font-weight: 600;
  color: var(--proto-ink-2);
  cursor: pointer;
}
.share-menu-cancel:active { background: var(--proto-bg); }
.share-menu-busy {
  margin-top: 4px;
  padding: 8px 12px;
  background: var(--proto-bg);
  border-radius: var(--proto-radius-sm);
  font-size: 12px;
  color: var(--proto-ink-2);
  text-align: center;
}
