/* ============================================================================
   BarberSaaS — Global styles (loaded AFTER tailwind.css by Propshaft order)
   Cascade Layers architecture (Evil Martians pattern)
   ============================================================================ */

@layer base, components, utilities, motion;

@layer base {
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("https://rsms.me/inter/font-files/InterVariable.woff2") format("woff2");
  }

  :root {
    --safe-top:    env(safe-area-inset-top);
    --safe-bottom: env(safe-area-inset-bottom);
    --safe-left:   env(safe-area-inset-left);
    --safe-right:  env(safe-area-inset-right);

    /* Alto de la bottom nav del cliente (ícono 20px + label 10px + py-2.5).
       Lo usa el bottom-sheet del buscador para no dejar contenido debajo. */
    --client-nav-h: 3.75rem;
  }

  html {
    scroll-behavior: smooth;
    -webkit-tap-highlight-color: transparent;
    font-feature-settings: "cv11", "ss01", "ss03";
  }

  body {
    font-family: var(--font-sans);
    background: var(--color-ink-950);
    color: var(--color-ink-50);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overscroll-behavior-y: none;
  }

  ::selection {
    background: var(--color-coral-500);
    color: white;
  }

  /* Scrollbar */
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: var(--color-ink-900); }
  ::-webkit-scrollbar-thumb {
    background: var(--color-ink-600);
    border-radius: 10px;
    border: 2px solid var(--color-ink-900);
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--color-coral-600); }

  /* Ocultar el scrollbar (chips horizontales, carruseles estilo Rappi).
     Antes `scrollbar-none` no existía como utility → el scrollbar global de
     10px con thumb coral asomaba bajo los chips. La regla con clase gana al
     `::-webkit-scrollbar` base por especificidad. */
  .scrollbar-none { scrollbar-width: none; -ms-overflow-style: none; }
  .scrollbar-none::-webkit-scrollbar { width: 0; height: 0; display: none; }

  /* Kill native select arrow (we use custom SVG chevrons) */
  select.appearance-none,
  select[style*="appearance"] {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background-image: none;
  }
  select::-ms-expand { display: none; }

  /* Native dropdown panel adopts dark theme for <option> popup (Chrome/Safari/Firefox).
     Without this, mac/iOS render the dropdown with light background on dark UI.
     Mantiene <select> nativo: touch-friendly en mobile, picker del OS en iOS/Android,
     screen readers OK. Closed-state lo siguen pintando las clases Tailwind del input. */
  select {
    color-scheme: dark;
  }

  /* Suppress UA :invalid red-border on selects with appearance-none.
     When a <select required> starts with value="" the browser immediately applies
     :invalid styles (red outline/border) even before the user touches the field.
     This is visually broken on dark UIs where the border lives on a wrapper div.
     We kill outline + box-shadow from the UA stylesheet; the wrapper div manages
     the actual visible border via Tailwind classes + focus-within. */
  select.appearance-none:invalid,
  select.select-no-invalid:invalid {
    outline: none;
    box-shadow: none;
  }

  /* ─── Native time/date inputs on dark bg ────────────────────
     Two pseudo-element systems coexist en WebKit/Blink:
       · Desktop Chrome/Safari/Edge → ::-webkit-datetime-edit-*-field (per-field)
       · iOS Safari mobile          → ::-webkit-date-and-time-value (single span)
     Sin cubrir AMBOS, los dígitos se renderizan en color default oscuro
     sobre nuestro fondo dark y se ven invisibles ("input vacío").
     -webkit-text-fill-color va junto a `color` porque iOS Safari a veces
     prioriza fill-color sobre color en estos controles.
     `color-scheme: dark` instruye al UA a renderizar el sheet/popover
     nativo del picker en modo oscuro (sino sale en light mode por default).
  */
  input[type="time"],
  input[type="date"],
  input[type="datetime-local"] {
    color-scheme: dark;
    color: var(--color-ink-50);
    -webkit-text-fill-color: var(--color-ink-50);
    /* iOS Safari quirk: si font-size < 16px, el browser hace zoom al focus.
       Tailwind text-sm (14px) lo dispara; forzamos 16px aquí sin afectar
       layout porque el input ya tiene h-11 fijo. */
    font-size: 16px;
  }

  /* iOS Safari MOBILE — pseudo-elemento principal donde vive el texto. */
  input[type="time"]::-webkit-date-and-time-value,
  input[type="date"]::-webkit-date-and-time-value,
  input[type="datetime-local"]::-webkit-date-and-time-value {
    color: var(--color-ink-50);
    -webkit-text-fill-color: var(--color-ink-50);
    text-align: left;
    /* Asegura altura para que iOS no clipee el texto verticalmente. */
    min-height: 1.25rem;
  }

  /* Desktop Chrome/Safari/Edge — pseudo-elementos por campo (hour/minute/etc). */
  input[type="time"]::-webkit-datetime-edit,
  input[type="time"]::-webkit-datetime-edit-fields-wrapper,
  input[type="time"]::-webkit-datetime-edit-text,
  input[type="time"]::-webkit-datetime-edit-hour-field,
  input[type="time"]::-webkit-datetime-edit-minute-field,
  input[type="time"]::-webkit-datetime-edit-second-field,
  input[type="time"]::-webkit-datetime-edit-ampm-field,
  input[type="date"]::-webkit-datetime-edit,
  input[type="date"]::-webkit-datetime-edit-fields-wrapper,
  input[type="date"]::-webkit-datetime-edit-text,
  input[type="date"]::-webkit-datetime-edit-month-field,
  input[type="date"]::-webkit-datetime-edit-day-field,
  input[type="date"]::-webkit-datetime-edit-year-field,
  input[type="datetime-local"]::-webkit-datetime-edit,
  input[type="datetime-local"]::-webkit-datetime-edit-fields-wrapper,
  input[type="datetime-local"]::-webkit-datetime-edit-text,
  input[type="datetime-local"]::-webkit-datetime-edit-hour-field,
  input[type="datetime-local"]::-webkit-datetime-edit-minute-field,
  input[type="datetime-local"]::-webkit-datetime-edit-month-field,
  input[type="datetime-local"]::-webkit-datetime-edit-day-field,
  input[type="datetime-local"]::-webkit-datetime-edit-year-field {
    color: var(--color-ink-50);
    -webkit-text-fill-color: var(--color-ink-50);
  }

  /* Placeholder vacío (e.g. "--:--") en gris suave. */
  input[type="time"]:invalid,
  input[type="date"]:invalid,
  input[type="datetime-local"]:invalid {
    color: var(--color-ink-400);
    -webkit-text-fill-color: var(--color-ink-400);
  }

  /* Clock/Calendar icon — `brightness(0) invert(1)` fuerza blanco puro
     (ignora el color SVG nativo, lo pinta blanco completo). Alternativa
     más agresiva que `invert(1)` solo, que dependía del color base
     del icono nativo (a veces gris en iOS). */
  input[type="time"]::-webkit-calendar-picker-indicator,
  input[type="date"]::-webkit-calendar-picker-indicator,
  input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    filter: brightness(0) invert(1);
    opacity: 0.85;
    cursor: pointer;
    padding: 0 2px;
    transition: opacity 0.2s;
  }
  input[type="time"]:hover::-webkit-calendar-picker-indicator,
  input[type="date"]:hover::-webkit-calendar-picker-indicator,
  input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator,
  input[type="time"]:focus::-webkit-calendar-picker-indicator,
  input[type="date"]:focus::-webkit-calendar-picker-indicator,
  input[type="datetime-local"]:focus::-webkit-calendar-picker-indicator {
    opacity: 1;
  }
}

@layer components {
  /* ─── Grain texture overlay ─────────────────────────────────────────── */
  .grain::before {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 1;
    opacity: 0.035;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  /* ─── Aurora / glow bg ──────────────────────────────────────────────── */
  .aurora {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
  }
  .aurora::before,
  .aurora::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    filter: blur(80px);
    opacity: 0.35;
  }
  .aurora::before {
    width: 50rem; height: 50rem;
    background: radial-gradient(circle, var(--color-coral-500), transparent 70%);
    top: -15rem; left: -10rem;
    animation: aurora-drift-a 22s ease-in-out infinite;
  }
  .aurora::after {
    width: 40rem; height: 40rem;
    background: radial-gradient(circle, var(--color-ink-500), transparent 70%);
    bottom: -10rem; right: -10rem;
    animation: aurora-drift-b 28s ease-in-out infinite;
  }

  /* ─── Grid pattern ──────────────────────────────────────────────────── */
  .grid-pattern {
    background-image:
      linear-gradient(rgb(255 255 255 / 0.04) 1px, transparent 1px),
      linear-gradient(90deg, rgb(255 255 255 / 0.04) 1px, transparent 1px);
    background-size: 48px 48px;
    mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%);
  }

  /* ─── Buttons ───────────────────────────────────────────────────────── */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 44px;
    padding: 0.875rem 1.5rem;
    border-radius: 0.875rem;
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1;
    transition: transform 0.2s var(--ease-out-back), box-shadow 0.2s, background 0.2s;
    cursor: pointer;
    white-space: nowrap;
    user-select: none;
  }
  .btn:active { transform: scale(0.97); }

  .btn-primary {
    background: linear-gradient(135deg, var(--color-coral-500), var(--color-coral-600));
    color: white;
    box-shadow: var(--shadow-glow);
  }
  .btn-primary:hover {
    background: linear-gradient(135deg, var(--color-coral-400), var(--color-coral-500));
    box-shadow: 0 0 100px -15px rgb(233 69 96 / 0.7);
  }

  .btn-ghost {
    background: rgb(255 255 255 / 0.06);
    color: var(--color-ink-50);
    backdrop-filter: blur(10px);
    border: 1px solid rgb(255 255 255 / 0.1);
  }
  .btn-ghost:hover {
    background: rgb(255 255 255 / 0.12);
    border-color: rgb(255 255 255 / 0.2);
  }

  /* Acción positiva: completar servicio, confirmar acción exitosa.
     Espejo de .btn-primary pero en emerald — mismo peso visual,
     semántica distinta (éxito vs CTA principal). */
  .btn-success {
    background: linear-gradient(135deg, #10b981, #059669);
    color: white;
    box-shadow: 0 0 80px -20px rgb(16 185 129 / 0.5);
  }
  .btn-success:hover {
    background: linear-gradient(135deg, #34d399, #10b981);
    box-shadow: 0 0 100px -15px rgb(16 185 129 / 0.7);
  }

  /* ─── Mobile menu overlay ──────────────────────────────────────────── */
  .mobile-menu {
    position: fixed;
    top: 4rem;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9998;
    background: #05050b;
    overflow: hidden;
    flex-direction: column;
  }

  /* ─── Google SSO button ─────────────────────────────────────────────── */
  .btn-google {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    height: 3rem;
    min-height: 44px;
    padding: 0 1.25rem;
    border-radius: var(--radius-xl);
    background: white;
    color: #1f1f1f;
    font-weight: 500;
    letter-spacing: -0.01em;
    border: 1px solid rgb(0 0 0 / 0.08);
    box-shadow: 0 2px 6px -2px rgb(0 0 0 / 0.3), inset 0 0 0 1px rgb(255 255 255 / 0.5);
    transition: transform 0.2s var(--ease-out-back), box-shadow 0.2s, background 0.2s;
    cursor: pointer;
  }
  .btn-google:hover {
    background: #f7f7fb;
    box-shadow: 0 8px 24px -6px rgb(0 0 0 / 0.5), inset 0 0 0 1px rgb(255 255 255 / 0.6);
    transform: translateY(-1px);
  }
  .btn-google:active { transform: scale(0.97); }
  .btn-google:focus-visible {
    outline: 2px solid var(--color-coral-400);
    outline-offset: 3px;
  }

  /* ─── Auth card (design system) ─────────────────────────────────────── */
  .auth-card {
    position: relative;
    width: 100%;
    max-width: 28rem;
    padding: 2rem 1.5rem;
    border-radius: var(--radius-3xl);
    background: rgb(255 255 255 / 0.04);
    backdrop-filter: blur(24px);
    border: 1px solid rgb(255 255 255 / 0.08);
    box-shadow: var(--shadow-float);
  }
  @media (min-width: 640px) {
    .auth-card { padding: 2.5rem 2.25rem; }
  }

  /* Subtle coral glow behind the card */
  .auth-card::before {
    content: "";
    position: absolute;
    inset: -1px;
    border-radius: inherit;
    background: linear-gradient(135deg, rgb(233 69 96 / 0.25), transparent 60%);
    z-index: -1;
    filter: blur(30px);
    opacity: 0.6;
    pointer-events: none;
  }

  /* ─── Divider with label ────────────────────────────────────────────── */
  .divider-labeled {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--color-ink-400);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
  }
  .divider-labeled::before,
  .divider-labeled::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgb(255 255 255 / 0.08);
  }

  /* ─── Role option card (radio-styled) ───────────────────────────────── */
  .role-option {
    display: block;
    cursor: pointer;
    padding: 1rem 1.1rem;
    border-radius: var(--radius-2xl);
    border: 1px solid rgb(255 255 255 / 0.08);
    background: rgb(255 255 255 / 0.03);
    transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease-out-back);
  }
  .role-option:hover {
    border-color: rgb(233 69 96 / 0.35);
    background: rgb(255 255 255 / 0.06);
    transform: translateY(-1px);
  }
  .role-option:has(input:checked) {
    border-color: var(--color-coral-500);
    background: rgb(233 69 96 / 0.08);
    box-shadow: 0 0 0 3px rgb(233 69 96 / 0.15);
  }
  .role-option input { position: absolute; opacity: 0; pointer-events: none; }

  /* ─── Glass card ────────────────────────────────────────────────────── */
  .glass {
    background: rgb(255 255 255 / 0.04);
    backdrop-filter: blur(20px);
    border: 1px solid rgb(255 255 255 / 0.08);
    box-shadow: var(--shadow-card);
  }
  .glass-hover {
    transition: background 0.3s, border-color 0.3s, transform 0.3s var(--ease-out-back);
  }
  .glass-hover:hover {
    background: rgb(255 255 255 / 0.08);
    border-color: rgb(233 69 96 / 0.3);
    transform: translateY(-4px);
  }

  /* ─── Badge ─────────────────────────────────────────────────────────── */
  .badge-beta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.75rem;
    border-radius: 999px;
    background: rgb(233 69 96 / 0.12);
    border: 1px solid rgb(233 69 96 / 0.3);
    color: var(--color-coral-300);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
  }
  .badge-beta::before {
    content: "";
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--color-coral-400);
    box-shadow: 0 0 10px var(--color-coral-400);
    animation: pulse-dot 2s ease-in-out infinite;
  }

  /* ─── Display heading ──────────────────────────────────────────────── */
  .display {
    font-family: var(--font-sans);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 0.95;
    background: linear-gradient(180deg, var(--color-ink-50) 0%, var(--color-ink-200) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

  .text-gradient-coral {
    background: linear-gradient(120deg, var(--color-coral-400), var(--color-gold-400));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

  /* Acento dorado para el carril del consumidor (lo distingue del flujo
     B2B en coral). Gold claro → gold profundo. */
  .text-gradient-gold {
    background: linear-gradient(120deg, var(--color-gold-400), var(--color-gold-500));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

  /* ─── Phone mockup ──────────────────────────────────────────────────── */
  .phone {
    position: relative;
    width: 280px;
    aspect-ratio: 9 / 19;
    border-radius: 48px;
    background: linear-gradient(145deg, var(--color-ink-600), var(--color-ink-800));
    padding: 10px;
    box-shadow:
      0 0 0 2px rgb(255 255 255 / 0.06),
      var(--shadow-float),
      inset 0 0 0 1px rgb(255 255 255 / 0.05);
  }
  .phone-screen {
    width: 100%; height: 100%;
    border-radius: 40px;
    background: var(--color-ink-900);
    overflow: hidden;
    position: relative;
  }
  .phone-notch {
    position: absolute;
    top: 14px; left: 50%;
    transform: translateX(-50%);
    width: 100px; height: 28px;
    background: var(--color-ink-950);
    border-radius: 999px;
    z-index: 3;
  }

  /* ─── Ui::DatePickerComponent — Cally theming (ADR-011) ───────────────
     <calendar-date> es un web component "headless"; usamos ::part() y
     CSS custom properties para vestirlo con tokens dark glass + coral.

     IMPORTANTE: Cally tiene 2 custom elements con shadow DOM separado:
       · <calendar-date>  → contiene buttons prev/next + heading
       · <calendar-month> → contiene los buttons de día
     ::part() no penetra entre custom elements, así que tematizamos ambos.

     El <dialog> contenedor recibe glass + backdrop blur. Forzamos
     posicionamiento fixed centrado: cuando el dialog vive dentro de un
     wrapper con contexto de posicionamiento, el centrado nativo de
     showModal() puede no aplicar. */
  .date-picker-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    background: rgb(20 20 28 / 0.95);
    backdrop-filter: blur(20px);
    border: 1px solid rgb(255 255 255 / 0.10);
    border-radius: 1rem;
    color: var(--color-ink-50);
    padding: 0;
    max-width: calc(100vw - 2rem);
    box-shadow: 0 20px 60px -20px rgb(0 0 0 / 0.8);
  }
  .date-picker-dialog::backdrop {
    background: rgb(0 0 0 / 0.6);
    backdrop-filter: blur(4px);
  }

  /* Tokens compartidos por <calendar-date> y <calendar-month>. */
  calendar-date,
  calendar-month {
    --color-accent: var(--color-coral-500);
    --color-text-on-accent: white;
    font-family: ui-sans-serif, system-ui, sans-serif;
    color: var(--color-ink-50);
    display: block;
  }

  /* Buttons prev/next viven en shadow DOM de <calendar-date>. */
  calendar-date::part(button) {
    min-width: 40px;
    min-height: 40px;
    border-radius: 0.5rem;
    background: rgb(255 255 255 / 0.05);
    border: 1px solid rgb(255 255 255 / 0.10);
    color: var(--color-ink-200);
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
  }
  calendar-date::part(button):hover {
    background: rgb(255 255 255 / 0.10);
    color: var(--color-ink-50);
    border-color: rgb(255 255 255 / 0.20);
  }
  calendar-date::part(button disabled) {
    opacity: 0.3;
    cursor: not-allowed;
  }
  calendar-date::part(heading) {
    color: var(--color-ink-50);
    font-weight: 600;
    font-size: 0.875rem;
  }

  /* Buttons de día viven en shadow DOM de <calendar-month>. */
  calendar-month::part(button) {
    min-width: 40px;
    min-height: 40px;
    border-radius: 0.5rem;
    color: var(--color-ink-200);
    font-size: 0.875rem;
    font-weight: 500;
    transition: background-color 0.15s, color 0.15s;
  }
  calendar-month::part(button):hover {
    background: rgb(255 255 255 / 0.08);
    color: var(--color-ink-50);
  }
  calendar-month::part(button selected) {
    background: var(--color-coral-500);
    color: white;
    box-shadow: 0 0 12px -2px rgb(233 69 96 / 0.5);
  }
  calendar-month::part(button today) {
    outline: 1px solid var(--color-coral-400);
    outline-offset: -1px;
    color: var(--color-coral-300);
  }
  calendar-month::part(button selected today) {
    color: white;
    outline-color: white;
  }
  calendar-month::part(button disallowed) {
    color: var(--color-ink-600);
    opacity: 0.35;
    cursor: not-allowed;
  }
  calendar-month::part(button outside) {
    opacity: 0.35;
  }

  /* ─── Ui::ImageUploadComponent — dropzone (ADR-014) ───────────────────
     Wrapper clickeable que aloja preview + placeholder + remove button.
     Estados controlados por Stimulus:
       · default        — bordered dashed, placeholder visible
       · .is-dragging   — drag activo encima, borde coral + bg coral-tint
       · .has-image     — imagen cargada, sin borde dashed visible
     Hover (no drag) ya está cubierto por las clases Tailwind del template;
     aquí van los estados con state machine + transitions transversales. */
  .image-dropzone {
    transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.15s var(--ease-out-back);
    background-color: rgb(255 255 255 / 0.02);
  }
  .image-dropzone:active {
    transform: scale(0.995);
  }
  .image-dropzone.is-dragging {
    /* Override del border-dashed white/15 del placeholder: coral solid */
    background-color: rgb(233 69 96 / 0.08);
    box-shadow: 0 0 0 2px var(--color-coral-500), 0 0 24px -4px rgb(233 69 96 / 0.5);
  }
  .image-dropzone.is-dragging > [data-ui--image-upload-target="placeholder"] {
    border-color: var(--color-coral-500);
    background-color: rgb(233 69 96 / 0.06);
  }
  /* Cuando hay imagen cargada, ocultamos el outline punteado del placeholder
     (que ya no se ve) y aseguramos que el wrapper no muestre el bg sutil. */
  .image-dropzone.has-image {
    background-color: transparent;
  }

  /* ─── Skeleton glass — placeholder shimmering para imagen async ──────
     Úsalo como background mientras la imagen real está cargando (o cuando
     se renderea la card pero el blob de R2 todavía no respondió).
     Combinable con `aspect-square` para mantener layout estable. */
  .skeleton-glass {
    position: relative;
    overflow: hidden;
    background: linear-gradient(
      100deg,
      rgb(255 255 255 / 0.03) 30%,
      rgb(255 255 255 / 0.08) 50%,
      rgb(255 255 255 / 0.03) 70%
    );
    background-size: 200% 100%;
    animation: shimmer 1.6s linear infinite;
  }

  /* ─── Ui::AccountMenuComponent — popover anclado al trigger ────────────
     <dialog> con showModal() (top layer → ESC, backdrop y focus trap nativos).
     El panel NO es un bottom sheet: es un popover que cae desde el avatar
     (trigger, arriba-derecha) en TODOS los breakpoints — como el dropdown que
     existía ANTES del commit 8cbe66c (2026-05-28), que fue el que lo convirtió
     en bottom sheet y lo rompió. Anclar a un punto SIEMPRE visible (arriba)
     evita que el panel caiga en el borde inferior recortado (DevTools/iOS). El
     Stimulus controller setea top/right/max-height inline desde el rect del
     trigger + visualViewport. */
  .account-menu-dialog {
    /* Reset del <dialog>: transparente y fullscreen, sin borde ni padding */
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100dvh;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
  }
  .account-menu-dialog::backdrop {
    background: rgb(0 0 0 / 0.55);
    backdrop-filter: blur(4px);
  }

  /* Panel: popover fixed anclado al trigger en TODOS los breakpoints. El JS
     setea top/right/max-height inline desde el rect del trigger + visualViewport.
     Los valores de abajo son fallback estático; max-height usa svh (el viewport
     más chico, siempre ≤ visible) para no rebasar en iOS si el JS no corrió. Si
     el contenido no cabe, scrollea interno. */
  .account-menu-panel {
    position: fixed;
    top: 4.5rem;
    right: 1rem;
    left: auto;
    bottom: auto;
    width: min(18rem, calc(100vw - 1.5rem));
    max-height: calc(100svh - 5.5rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    background: rgb(20 20 28 / 0.96);
    backdrop-filter: blur(20px);
    border: 1px solid rgb(255 255 255 / 0.10);
    border-radius: 1rem;
    padding: 0.5rem 0 calc(0.5rem + var(--safe-bottom));
    box-shadow: 0 20px 60px -20px rgb(0 0 0 / 0.8);
    animation: account-menu-fade-pop 160ms ease-out;
  }

  @keyframes account-menu-slide-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }

  @keyframes account-menu-fade-pop {
    from { transform: translateY(-8px); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
  }

  @media (min-width: 768px) {
    /* Desktop: sin scrim — los clicks fuera del panel cierran vía clickOutside.
       Mismo popover anclado al trigger, sólo un poco más angosto. */
    .account-menu-dialog::backdrop {
      background: transparent;
      backdrop-filter: none;
    }
    .account-menu-panel {
      width: 16rem;
    }
  }

  /* ─── Ui::TeamMemberFormDialogComponent ─────────────────────────────
     Bottom sheet en mobile, popover centered en md+. Más alto/ancho que
     el account-menu para acomodar el form completo (display name + phone
     + grid de 6 especialidades + status + footer). */
  .team-form-dialog {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100dvh;
    max-width: none;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
  }
  .team-form-dialog::backdrop {
    background: rgb(0 0 0 / 0.65);
    backdrop-filter: blur(4px);
  }
  /* Mobile: bottom sheet casi full-height (deja el header del page visible). */
  .team-form-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: calc(100dvh - 3rem);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    background: rgb(20 20 28 / 0.98);
    backdrop-filter: blur(20px);
    border: 1px solid rgb(255 255 255 / 0.10);
    border-radius: 1.5rem 1.5rem 0 0;
    padding-top: 0.5rem;
    box-shadow: 0 -24px 80px -20px rgb(0 0 0 / 0.85);
    animation: account-menu-slide-up 260ms cubic-bezier(0.32, 0.72, 0, 1);
  }
  @media (min-width: 768px) {
    .team-form-panel {
      left: 50%;
      right: auto;
      bottom: auto;
      top: 50%;
      transform: translate(-50%, -50%);
      width: min(36rem, calc(100% - 2rem));
      max-height: min(48rem, calc(100dvh - 4rem));
      border-radius: 1.5rem;
      box-shadow: 0 24px 80px -20px rgb(0 0 0 / 0.85);
      animation: team-form-pop 200ms ease-out;
    }
    @keyframes team-form-pop {
      from { transform: translate(-50%, -48%) scale(0.97); opacity: 0; }
      to   { transform: translate(-50%, -50%) scale(1);    opacity: 1; }
    }
  }
}

@layer motion {
  /* ─── Scroll-driven reveal (modern CSS) ─────────────────────────────── */
  @supports (animation-timeline: view()) {
    .reveal {
      animation: reveal-up linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 30%;
    }
    .reveal-fade {
      animation: reveal-fade linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }

  /* Scroll reveal via Stimulus IntersectionObserver — todos los browsers */
  .js-reveal {
    opacity: 0;
    transform: translateY(40px) scale(0.97);
    transition: opacity 0.9s var(--ease-out-expo),
                transform 0.9s var(--ease-out-expo);
  }
  .js-reveal.is-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  /* Shake horizontal breve — usado al click en tab disabled ("Con código"). */
  .animate-shake {
    animation: shake-x 320ms cubic-bezier(.36,.07,.19,.97) both;
  }
  @keyframes shake-x {
    10%, 90% { transform: translateX(-1px); }
    20%, 80% { transform: translateX(2px); }
    30%, 50%, 70% { transform: translateX(-4px); }
    40%, 60% { transform: translateX(4px); }
  }

  /* ─── Scroll-driven parallax (desktop only, motion-safe, CSS-only) ─── */
  @media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: scroll()) {

      /* Back layer: aurora drifts DOWN as user scrolls — starts at natural pos */
      .parallax-back {
        animation: parallax-back linear both;
        animation-timeline: scroll(root);
        animation-range: 0 100vh;
        will-change: transform, opacity;
      }

      /* Slow: phone floats UP gently — starts at natural pos */
      .parallax-slow {
        animation: parallax-slow linear both;
        animation-timeline: scroll(root);
        animation-range: 0 100vh;
        will-change: transform;
      }

      /* Float: decorative orbs/glows — starts at natural pos */
      .parallax-float {
        animation: parallax-float linear both;
        animation-timeline: scroll(root);
        animation-range: 0 100vh;
        will-change: transform;
      }

      /* Grid: background pattern scrolls slowly */
      .parallax-grid {
        animation: parallax-grid linear both;
        animation-timeline: scroll(root);
        animation-range: 0 200vh;
        will-change: background-position;
      }
    }
  }

  @keyframes reveal-up {
    from { opacity: 0; transform: translateY(40px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }

  @keyframes reveal-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  @keyframes parallax-back {
    from { transform: translateY(0) scale(1);    opacity: 1;    }
    to   { transform: translateY(180px) scale(1.2); opacity: 0.35; }
  }

  @keyframes parallax-slow {
    from { transform: translateY(0)   scale(1);    }
    to   { transform: translateY(-90px) scale(1.04); }
  }

  @keyframes parallax-float {
    from { transform: translateY(0);   }
    to   { transform: translateY(-60px); }
  }

  @keyframes parallax-grid {
    from { background-position-y: 0;    }
    to   { background-position-y: 300px; }
  }

  @keyframes pulse-dot {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: 0.5; transform: scale(0.85); }
  }

  @keyframes aurora-drift-a {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(4rem, 2rem) scale(1.1); }
  }

  @keyframes aurora-drift-b {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(-3rem, -3rem) scale(0.9); }
  }

  /* ─── Wizard step transitions (enter only, no overlap) ─────── */
  @keyframes wizard-slide-in-right {
    from { transform: translateX(20px); opacity: 0; }
    to   { transform: translateX(0);    opacity: 1; }
  }
  @keyframes wizard-slide-in-left {
    from { transform: translateX(-20px); opacity: 0; }
    to   { transform: translateX(0);     opacity: 1; }
  }

  .wizard-enter-right { animation: wizard-slide-in-right 250ms var(--ease-out-expo) both; }
  .wizard-enter-left  { animation: wizard-slide-in-left  250ms var(--ease-out-expo) both; }

  @keyframes marquee {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }

  @keyframes shimmer {
    0%   { background-position: -200% 0; }
    100% { background-position: 200% 0; }
  }

  /* ─── Real-time slot state transitions ─────────────────────────── */
  @keyframes slot-take {
    0%   { background-color: rgb(233 69 96 / 0.20); }
    40%  { background-color: rgb(233 69 96 / 0.30); }
    100% { background-color: rgb(255 255 255 / 0.03); }
  }

  @keyframes slot-release {
    0%   { background-color: rgb(16 185 129 / 0.15); }
    40%  { background-color: rgb(16 185 129 / 0.25); }
    100% { background-color: rgb(233 69 96 / 0.08); }
  }

  .slot-animating-take {
    animation: slot-take 450ms ease-out forwards;
  }

  .slot-animating-release {
    animation: slot-release 450ms ease-out forwards;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

/* ─── View Transitions API ────────────────────────────────────────────── */
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

/* ─── Descubrimiento del cliente — mapa (ADR-030) ──────────────────────── */
/* Estilos globales: Leaflet inyecta sus clases fuera de nuestras cascade layers. */
.leaflet-container { background: #0a0a14; font-family: inherit; }
.leaflet-control-attribution {
  background: rgba(10, 10, 20, 0.7) !important;
  color: rgb(148 148 168) !important;
  font-size: 9px; backdrop-filter: blur(6px);
}
.leaflet-control-attribution a { color: rgb(233 69 96); }
.leaflet-bar a {
  background: rgba(20, 20, 32, 0.9); color: #f4f4f8;
  border-color: rgba(255, 255, 255, 0.08);
}
.leaflet-bar a:hover { background: rgba(30, 30, 46, 0.95); }

/* Pin de resultado (número, coral con halo). */
.discovery-pin { background: transparent; border: none; }
.discovery-pin__dot {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 9999px;
  background: linear-gradient(135deg, rgb(233 69 96), rgb(200 40 70));
  color: #fff;
  border: 2px solid #0a0a14;
  box-shadow: 0 4px 14px -3px rgba(233, 69, 96, 0.7);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.discovery-pin--active .discovery-pin__dot {
  transform: scale(1.28);
  box-shadow: 0 8px 22px -4px rgba(233, 69, 96, 0.95);
  background: linear-gradient(135deg, rgb(233 69 96), rgb(245 158 66));
}

/* Marcador "tú" — punto que respira. */
.discovery-you { background: transparent; border: none; }
.discovery-you__pulse {
  display: block; width: 16px; height: 16px; border-radius: 9999px;
  background: rgb(56 189 248); border: 3px solid #0a0a14; margin: 3px;
  animation: discovery-pulse 2s infinite;
}
@keyframes discovery-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0.55); }
  70%  { box-shadow: 0 0 0 14px rgba(56, 189, 248, 0); }
  100% { box-shadow: 0 0 0 0 rgba(56, 189, 248, 0); }
}

/* Chips de filtro del buscador (ADR-030).
   Superficie de TINTA translúcida + blur, no de velo blanco: el mapa es oscuro,
   así que un blanco al 5% sin backdrop-filter no es una superficie, es un vidrio
   sucio (el texto competía con las calles). Google usa chips blancos opacos
   porque su mapa es CLARO; el análogo en dark es tinta. El saturate deja pasar
   un fantasma de color del mapa → se ve vivo, no un rectángulo gris muerto. */
.discovery-chip {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.375rem; white-space: nowrap;
  padding: 0 0.875rem; height: 2.5rem; border-radius: 9999px;
  font-size: 0.8125rem; font-weight: 600; letter-spacing: -0.01em;
  background: rgb(10 10 20 / 0.72);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  border: 1px solid rgb(255 255 255 / 0.14);
  color: var(--color-ink-100);
  box-shadow: 0 2px 10px -2px rgb(0 0 0 / 0.55),
              inset 0 1px 0 rgb(255 255 255 / 0.06);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.12s ease;
}
/* Hit-area de 44px sin engordar el chip visualmente (queda en 40px). */
.discovery-chip::after { content: ""; position: absolute; inset: -2px 0; }

/* Activo en coral-600, NO coral-500: blanco sobre coral-500 da 3.80:1 y no pasa
   WCAG AA (mínimo 4.5:1). coral-600 sube a 5.2:1 y el glow mantiene el brillo. */
.discovery-chip.chip-active {
  background: var(--color-coral-600);
  border-color: var(--color-coral-500);
  color: #fff;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  box-shadow: 0 0 18px -4px rgb(233 69 96 / 0.65),
              inset 0 1px 0 rgb(255 255 255 / 0.18);
}

/* Red de seguridad: si algún día los labels crecen (i18n, dynamic type), el
   borde derecho se desvanece en vez de cortar en seco. Inerte si todo cabe. */
.discovery-filters {
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}

/* Control de ubicación (ADR-030) — afijo del buscador, NO un chip: no filtra
   resultados, define el PUNTO de búsqueda. Por eso habla el idioma del input
   (rounded-2xl, h-12) y no el de los filtros (pill, h-10). */
.loc-affix {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.375rem;
  height: 3rem; min-width: 3rem; padding: 0 0.875rem;
  border-radius: 1rem;
  font-size: 0.8125rem; font-weight: 600; white-space: nowrap;
  background: rgb(10 10 20 / 0.72);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  border: 1px solid rgb(255 255 255 / 0.14);
  color: var(--color-coral-300);
  box-shadow: 0 2px 10px -2px rgb(0 0 0 / 0.55);
  transition: background 0.2s ease, width 0.2s ease, color 0.2s ease;
}
/* Con ciudad resuelta colapsa a ícono y le devuelve el ancho al input: el
   "layout shift" es feedback de éxito (ya sabemos dónde está el cliente). */
.loc-affix--set {
  width: 3rem; padding: 0;
  background: var(--color-coral-600);
  border-color: var(--color-coral-500);
  color: #fff;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  box-shadow: 0 0 18px -4px rgb(233 69 96 / 0.65);
}

/* Favoritos (ADR-031) — "heart-pop" al guardar/quitar. */
.fav-pop { animation: fav-pop 0.34s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes fav-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .fav-pop { animation: none; } }

/* Popup de ruta ("Cómo llegar") en tema oscuro. */
.discovery-route-popup .leaflet-popup-content-wrapper,
.discovery-route-popup .leaflet-popup-tip {
  background: rgba(20, 20, 32, 0.96);
  color: #f4f4f8;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 8px 30px -8px rgba(0, 0, 0, 0.7);
}
.discovery-route-popup .leaflet-popup-content { margin: 10px 12px; }
.discovery-route-popup a.leaflet-popup-close-button { color: rgb(148 148 168); }

@media (prefers-reduced-motion: reduce) {
  .discovery-you__pulse { animation: none; }
}

