/* =========================================================================
   admin/assets/css/app.css — el diseno de la aplicacion, en un solo archivo.

   Lo cargan las cinco pantallas a traves de pages/_shell.php. Las paginas de
   autenticacion no: esas van con login.css sobre admin-core.css, y no
   comparten ni tokens ni componentes con esto.

   Como esta ordenado, de arriba abajo:
     1. tokens          color, tipografia, espacio, altura de control, tiempos
     2. armazon         .app .nav .main .topbar .content
     3. componentes     botones, campos, paneles, pastillas, tabla, tarjetas
     4. capas           cajon, modal, visor de fotos, avisos
     5. respuestas      un bloque por ancho, y uno por puntero grueso

   Reglas que no se ven leyendo una sola linea:

   - Solo hay dos alturas de control, --control-h y --control-h-sm. Un boton,
     un campo y un chip que comparten fila miden lo mismo o el ojo lo nota.
   - El espacio sale de la escala de 4. Un numero suelto en un margen es un
     error, no una decision.
   - El modo oscuro se declara dos veces —bajo prefers-color-scheme y bajo
     [data-theme="dark"]— pero solo en los bloques de tokens. Ninguna regla
     de componente puede tener su version oscura por su cuenta: si la tiene,
     quien no haya tocado el conmutador no la vera nunca.
   - Todo color de texto pasa 4.5:1 sobre su fondo real. Antes de aclarar un
     gris, medirlo.
   ========================================================================= */

:root {
  /* Primitivo. El color a secas, sin decir para que sirve.
     Hace falta porque --bg-card y el blanco de primer plano valen lo mismo
     HOY pero no son lo mismo: --bg-card se vuelve oscuro con el tema y el
     otro no puede moverse, porque la superficie sobre la que va tampoco se
     mueve. Apuntando los dos aqui, comparten origen sin quedar atados. */
  --white: #ffffff;

  --bg-page:      #f3f4f6;
  --bg-card:      var(--white);
  --bg-card-soft: #f9fafb;
  --border-subtle:#e5e7eb;
  --border-control:#888f9c;
  --accent:       #2563eb;
  --accent-soft:  rgba(37, 99, 235, 0.08);
  --danger:       #dc2626;
  --danger-soft:  rgba(220, 38, 38, 0.08);
  /* --danger y --success valen para rellenos y bordes, pero como texto sobre
     su propio fondo suave se quedaban en 4.28 y 4.38, por debajo del 4.5.
     La tinta es el mismo tono un paso mas oscuro, solo para texto. */
  --danger-ink:   #c81e1e;
  --success:      #15803d;
  --success-soft: rgba(21, 128, 61, 0.10);
  --success-ink:  #137539;
  --warning:      #854d0e;
  --warning-soft: rgba(133, 77, 14, 0.10);
  --text-main:    #111827;
  --text-muted:   #666d79;

  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 18px;
  --shadow-soft:   0 18px 35px rgba(15, 23, 42, 0.08);
  --shadow-subtle: 0 10px 25px rgba(15, 23, 42, 0.04);
  /* La perilla de un interruptor (.switch): una sombra corta y nitida que la
     despega de la pista. Las otras dos son para tarjetas y no le sirven. */
  --shadow-knob: 0 1px 2px rgba(15, 23, 42, 0.25);

  /* ---- profundidad en vez de trazos (9-sep-2026) ----------------------
     El armazon se dibujaba con lineas de 1px lisas: una debajo del logo, otra
     debajo del titulo, otra en cada borde de tarjeta y otra en cada fila de
     tabla. Cuatro trazos del mismo grosor y el mismo gris, todos gritando lo
     mismo, y ninguno diciendo que hay delante y que hay detras. Ademas
     delataban cualquier desajuste: las dos primeras no caian a la misma
     altura, y con una linea recta eso se ve a un metro.

     El relevo es una superficie y una sombra:
       --hairline     el trazo, cuando de verdad hace falta, casi invisible
       --shadow-hair  separa dos superficies pegadas (barra / contenido)
       --shadow-card  levanta una tarjeta de la pagina

     Se quedan en rgba y no en un hex: asi funcionan sobre cualquier fondo sin
     tener que declararlos otra vez por cada superficie. */
  --hairline:      rgba(15, 23, 42, 0.07);
  --shadow-hair:   0 1px 2px rgba(15, 23, 42, 0.05);
  --shadow-card:   0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05);
  /* La separacion del panel lateral: un trazo casi invisible y una sombra
     ancha hacia el contenido. Es sombra y no borde porque el borde vertical a
     lo alto de toda la pantalla es la linea mas larga de la interfaz y la que
     menos informacion lleva. */
  --shadow-rail:   1px 0 0 var(--hairline), 4px 0 16px rgba(15, 23, 42, 0.04);

  /* La altura que COMPARTEN la cabecera del panel lateral y la barra de
     titulo. Es un token y no dos numeros porque su unico trabajo es que las
     dos separaciones caigan en el mismo eje: escritas aparte, se separan a la
     primera vez que alguien cambia un tamano de letra. Ver DESIGN-RULES §2. */
  --shell-head-h:  76px;
  --radius-sm:     8px;
  /* El aro de foco, en un solo sitio. Estaba escrito 9 veces entre las dos
     hojas: nueve oportunidades de que una se quede distinta y teclear se
     sienta de dos maneras en la misma app. */
  --ring: 0 0 0 3px var(--accent-soft);
  /* El aro de "este es el elegido" separado de su forma por un hueco del
     color de la tarjeta (el estado actual en Change status). */
  --ring-pick: 0 0 0 2px var(--bg-card), 0 0 0 4px var(--accent);
  /* El velo detras de un cajon o de un modal. */
  --scrim: rgba(15, 23, 42, .42);
  /* Texto encima de una superficie que NO cambia con el tema: la etiqueta
     sobre la miniatura va sobre un velo oscuro fijo, asi que su blanco
     tampoco puede moverse. No es --bg-card: ese es el fondo de una tarjeta y
     en oscuro se vuelve oscuro, que dejaria texto oscuro sobre velo oscuro. */
  --on-overlay: var(--white);
  --tap-h: 44px;

  --space-1: 4px; --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px;

  /* Dos alturas de control, y solo dos. Antes convivian 32, 34, 38 y 40 px:
     un boton junto a un campo no alineaba nunca, y en la fila de filtros se
     mezclaban 32 con 34, que es la clase de descuadre que se ve sin saber
     que se esta viendo. */
  --control-h:    40px;
  --control-h-sm: 34px;

  --dur-fast: 120ms; --dur-base: 200ms; --dur-slow: 320ms;
  --ease: cubic-bezier(.2, 0, .2, 1);
  --ease-out: cubic-bezier(0, 0, .2, 1);
  /* Para lo que entra en pantalla. Arranca rapido y frena largo: se lee como
     algo que llega y se posa, no como algo que se desliza a velocidad
     constante. Es lo que hace que el movimiento se sienta vivo sin rebotar. */
  --ease-enter: cubic-bezier(.22, 1, .36, 1);

  /* Tercer nivel de texto: etiquetas de los KPI, cabeceras de la tabla, el
     nombre de la company bajo cada store. Estaba en #9aa1ad, que daba 2.6:1
     sobre blanco, muy por debajo del 4.5:1 que exige el texto normal.

     Medido contra los tres fondos claros —#fff, --bg-card-soft y --bg-page—
     el mas exigente es --bg-page, y contra el no hay ningun gris mas claro
     que este que llegue a 4.5. Por eso vale lo mismo que --text-muted: el
     tercer nivel no se hace con color, se hace con tamano, mayusculas y
     espaciado, que es justo como ya estan escritas esas etiquetas.
     No aclarar este valor. */
  /* Vale lo mismo que --text-muted y ahora lo DICE en vez de repetir el
     hex: si algun dia se aclara el apagado, este va detras solo. Escribir el
     mismo valor dos veces es tener dos colores para el ojo de quien mira la
     paleta y dos sitios que tocar para cambiar uno. */
  --text-soft:  var(--text-muted);
  --accent-ink: #1d4ed8;
  --sidebar-w:  236px;
  --navbar-h:   60px;

  /* Pastillas de estado. El color dice DONDE ESTA EL CASO, no cual de los
     siete estados es; el nombre ya dice eso. Seis significados:

       open   esperando a la plataforma            Submitted
       wait   esperando a QUIEN MIRA, hay que hacer algo   Needs photo
       live   abierto y peleandose                 In dispute
       ok     ganado, el dinero vuelve             Approved
       bad    perdido, el dinero no vuelve         Denied
       done   fuera de la mesa, nada se mueve      Closed y No action

     Closed y No action comparten color a proposito: al ojo significan lo
     mismo —esto ya no pide nada—, y la etiqueta es la que los separa. Lo que
     NO puede repetirse es live y bad, que es como estaba: una dispute en
     curso se veia igual que una perdida, y son la diferencia entre revisarla
     hoy o no volver a mirarla.

     Los valores salen de la capa semantica, no de hex nuevos: si algun dia se
     mueve --danger, la pastilla de Denied va detras sola. */
  --pill-open-bg: rgba(51, 65, 85, 0.10);  --pill-open-fg: #334155;
  --pill-wait-bg: var(--warning-soft);     --pill-wait-fg: var(--warning);
  --pill-live-bg: var(--accent-soft);      --pill-live-fg: var(--accent-ink);
  --pill-ok-bg:   var(--success-soft);     --pill-ok-fg:   var(--success-ink);
  --pill-bad-bg:  var(--danger-soft);      --pill-bad-fg:  var(--danger-ink);
  --pill-done-bg: var(--bg-card-soft);     --pill-done-fg: var(--text-muted); --pill-done-bd: var(--border-subtle);
  /* La escalada de Needs photo es UNA rampa, no tres colores sueltos: el
     mismo ambar sube de intensidad y solo al final cambia a rojo. Antes
     arrancaba en azul y saltaba a ambar, que se leia como otro estado. */
  --pill-a15-bg: rgba(133, 77, 14, 0.18);  --pill-a15-fg: var(--warning);    --pill-a15-bd: rgba(133, 77, 14, 0.45);
  --pill-a30-bg: rgba(220, 38, 38, 0.14);  --pill-a30-fg: var(--danger-ink); --pill-a30-bd: rgba(220, 38, 38, 0.40);

  /* ---- Capa 3: componentes del Overview ----------------------------------
     Estos tokens NO guardan un color. Dicen QUE PAPEL juega un color de la
     capa semantica dentro de una pieza concreta, que es la unica razon por la
     que merece la pena que existan.

     Se declaran UNA SOLA VEZ, aqui, y no se repiten en los bloques oscuros:
     var(--success) ya sabe cambiar con el tema, asi que copiarlos abajo seria
     crear dos sitios donde equivocarse para no ganar nada.

     Y ninguno inventa un significado nuevo. El encargo pedia "que los colores
     representen importancia"; lo que hay debajo es la misma regla que ya
     gobierna las pastillas —el color dice DONDE ESTA algo, no cuanto importa—
     aplicada al dinero. Un cobro tiene etapas igual que una dispute, y son
     las etapas las que se colorean. Ver docs/DESIGN-RULES.md, "Color en el
     Overview". */

  /* Los cuatro resultados de dinero del Dashboard (15-sep-2026). Ver
     docs/DESIGN-RULES.md "Los colores de las graficas".
     Antes salian de las pastillas —su tinta, diluida al 46%—, y la tinta de
     una pastilla esta OSCURECIDA para leerse como texto de 13px: diluida, el
     ambar de "Needs attention" salia cafe y el conjunto se veia apagado,
     nada que ver con la referencia. Una barra no es texto: su color tiene que
     ser el tono de verdad, suave. Por eso son valores propios y no alias.
     Denied es GRIS, no rojo: un intento de recuperacion que no salio no es
     una perdida nueva, y el rojo se queda para lo que si pide accion. La
     pastilla Denied de la lista de disputes no cambia. */
  --chart-ok:     #6bb888;
  --chart-live:   #8fb1f2;
  --chart-wait:   #f2b84b;
  --chart-denied: #c9ced6;
  /* El punto de una leyenda es mas pequeno que una barra: un paso mas de
     color para que no se lea como una mancha palida junto al texto. */
  --chart-ok-dot:     #2f9e5b;
  --chart-live-dot:   #3b6fe0;
  --chart-wait-dot:   #eea92c;
  --chart-denied-dot: #a3aab5;
  --chart-track:  #eef0f3;
  --chart-grid:   rgba(15, 23, 42, 0.09);
  /* Top Disputes (el donut de causas). Una causa es una CATEGORIA, no un
     resultado de dinero, asi que no toma ninguno de los cuatro de arriba:
     violeta, con la luz y la saturacion de --chart-live y un matiz que no usa
     ninguna otra grafica. Ver DESIGN-RULES, "El pastel". */
  --chart-cause:  #9e8df2;

  /* El libro de cobro. Un cargo pasa por las mismas etapas que una dispute:
       none      no hay fee configurado -> no pide nada       (como CLOSED)
       accruing  recuperado y aun sin facturar -> en curso    (como DISPUTE)
       awaiting  facturado y sin pagar -> esperando a que paguen (como SUBMITTED)
       settled   cobrado                                      (como APPROVED)
     "Inactiva" reusa `none`: una company apagada tampoco pide nada. Se
     distingue por la fila atenuada y por la palabra, no por el color. */
  --ledger-none:     var(--pill-done-fg);
  --ledger-accruing: var(--pill-live-fg);
  --ledger-tosend:   var(--pill-wait-fg);
  --ledger-awaiting: var(--pill-open-fg);
  --ledger-settled:  var(--pill-ok-fg);

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Modo oscuro: los mismos papeles, invertidos. El azul se aclara para
   mantener contraste sobre fondo oscuro; el resto conserva su significado. */
:root:not([data-theme="light"]) {
  @media (prefers-color-scheme: dark) {
    --bg-page: #0f1115; --bg-card: #171a21; --bg-card-soft: #1d212a;
    --border-subtle: #262b36; --border-control: #4a5160;
    --text-main: #e8eaee; --text-muted: #9aa2b1; --text-soft: #8a94a5;
    --accent: #60a5fa; --accent-soft: rgba(96,165,250,.12); --accent-ink: #93c5fd;
    --danger: #f87171; --danger-soft: rgba(248,113,113,.12); --danger-ink: #f87171;
    --success: #4ade80; --success-soft: rgba(74,222,128,.12); --success-ink: #4ade80;
    --warning: #fbbf24; --warning-soft: rgba(251,191,36,.12);
    --shadow-soft: 0 18px 35px rgba(0,0,0,.5); --shadow-subtle: 0 10px 25px rgba(0,0,0,.3); --shadow-knob: 0 1px 2px rgba(0,0,0,.5);
  /* En oscuro una sombra negra no separa nada: el fondo ya es oscuro. El
     trazo sube a luz y la sombra se refuerza, que es como se lee la
     profundidad cuando no hay blanco debajo. */
  --hairline: rgba(255,255,255,.08);
  --shadow-hair: 0 1px 2px rgba(0,0,0,.45);
  --shadow-card: 0 1px 2px rgba(0,0,0,.35), 0 8px 24px rgba(0,0,0,.30);
  --shadow-rail: 1px 0 0 var(--hairline), 4px 0 16px rgba(0,0,0,.35);
  /* Sobre un relleno de acento o de peligro, en oscuro el relleno es claro,
     asi que el texto encima tiene que ser oscuro: en blanco daba 2.5:1. */
  --on-accent: #0f1115; --on-danger: #0f1115;
  --danger-hover: #ef4444; --shimmer: rgba(255,255,255,.06);
  /* Pastillas de estado. Antes solo existian bajo [data-theme="dark"], asi que
     quien tenia el sistema en oscuro y no habia tocado el conmutador las veia
     en claro sobre tarjeta oscura: 1.5:1. */
  --pill-open-bg: rgba(148,163,184,.16); --pill-open-fg: #cbd5e1;
  --pill-wait-bg: rgba(251,191,36,.16);  --pill-wait-fg: #fcd34d;
  --pill-live-bg: rgba(96,165,250,.16);  --pill-live-fg: #93c5fd;
  --pill-ok-bg:   rgba(74,222,128,.14);  --pill-ok-fg:   #86efac;
  --pill-bad-bg:  rgba(248,113,113,.16); --pill-bad-fg:  #fca5a5;
  --pill-done-bg: var(--bg-card-soft);   --pill-done-fg: var(--text-muted); --pill-done-bd: var(--border-subtle);
  --pill-a15-bg: rgba(251,191,36,.22);   --pill-a15-fg: #fcd34d; --pill-a15-bd: rgba(251,191,36,.55);
  --pill-a30-bg: rgba(248,113,113,.22);  --pill-a30-fg: #fca5a5; --pill-a30-bd: rgba(248,113,113,.55);
  /* Graficas en oscuro: los mismos cuatro tonos, bajados de luz para que
     una barra no brille mas que el texto que la explica. */
  --chart-ok: #4f9f71; --chart-live: #6189d6; --chart-wait: #d6a03f; --chart-denied: #3e4452;
  --chart-ok-dot: #62c28a; --chart-live-dot: #7fa6f5; --chart-wait-dot: #f3bf55; --chart-denied-dot: #8a93a2;
  --chart-track: #232833; --chart-grid: rgba(255,255,255,.08);
  --chart-cause: #7e6cda;
  }
}
:root[data-theme="dark"] {
  --bg-page: #0f1115; --bg-card: #171a21; --bg-card-soft: #1d212a;
  --border-subtle: #262b36; --border-control: #4a5160;
  --text-main: #e8eaee; --text-muted: #9aa2b1; --text-soft: #8a94a5;
  --accent: #60a5fa; --accent-soft: rgba(96,165,250,.12); --accent-ink: #93c5fd;
  --danger: #f87171; --danger-soft: rgba(248,113,113,.12); --danger-ink: #f87171;
  --success: #4ade80; --success-soft: rgba(74,222,128,.12); --success-ink: #4ade80;
  --warning: #fbbf24; --warning-soft: rgba(251,191,36,.12);
  --shadow-soft: 0 18px 35px rgba(0,0,0,.5); --shadow-subtle: 0 10px 25px rgba(0,0,0,.3); --shadow-knob: 0 1px 2px rgba(0,0,0,.5);
  /* En oscuro una sombra negra no separa nada: el fondo ya es oscuro. El
     trazo sube a luz y la sombra se refuerza, que es como se lee la
     profundidad cuando no hay blanco debajo. */
  --hairline: rgba(255,255,255,.08);
  --shadow-hair: 0 1px 2px rgba(0,0,0,.45);
  --shadow-card: 0 1px 2px rgba(0,0,0,.35), 0 8px 24px rgba(0,0,0,.30);
  --shadow-rail: 1px 0 0 var(--hairline), 4px 0 16px rgba(0,0,0,.35);
  /* Sobre un relleno de acento o de peligro, en oscuro el relleno es claro,
     asi que el texto encima tiene que ser oscuro: en blanco daba 2.5:1. */
  --on-accent: #0f1115; --on-danger: #0f1115;
  --danger-hover: #ef4444; --shimmer: rgba(255,255,255,.06);
  /* Pastillas de estado. Antes solo existian bajo [data-theme="dark"], asi que
     quien tenia el sistema en oscuro y no habia tocado el conmutador las veia
     en claro sobre tarjeta oscura: 1.5:1. */
  --pill-open-bg: rgba(148,163,184,.16); --pill-open-fg: #cbd5e1;
  --pill-wait-bg: rgba(251,191,36,.16);  --pill-wait-fg: #fcd34d;
  --pill-live-bg: rgba(96,165,250,.16);  --pill-live-fg: #93c5fd;
  --pill-ok-bg:   rgba(74,222,128,.14);  --pill-ok-fg:   #86efac;
  --pill-bad-bg:  rgba(248,113,113,.16); --pill-bad-fg:  #fca5a5;
  --pill-done-bg: var(--bg-card-soft);   --pill-done-fg: var(--text-muted); --pill-done-bd: var(--border-subtle);
  --pill-a15-bg: rgba(251,191,36,.22);   --pill-a15-fg: #fcd34d; --pill-a15-bd: rgba(251,191,36,.55);
  --pill-a30-bg: rgba(248,113,113,.22);  --pill-a30-fg: #fca5a5; --pill-a30-bd: rgba(248,113,113,.55);
  --chart-ok: #4f9f71; --chart-live: #6189d6; --chart-wait: #d6a03f; --chart-denied: #3e4452;
  --chart-ok-dot: #62c28a; --chart-live-dot: #7fa6f5; --chart-wait-dot: #f3bf55; --chart-denied-dot: #8a93a2;
  --chart-track: #232833; --chart-grid: rgba(255,255,255,.08);
  --chart-cause: #7e6cda;
}

* { box-sizing: border-box; }
/* Con el cajon abierto se bloquea el scroll del body. Sin reservar el hueco
   de la barra, al bloquearlo desaparece y todo se corre unos pixeles a la
   derecha — otro saltito, este al abrir el detalle. */
html { scrollbar-gutter: stable; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg-page); color: var(--text-main);
  font-family: var(--font); font-size: 14px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
button, input, textarea, select { font: inherit; color: inherit; }
a { color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* El halo del toque: iOS y Android pintan un rectangulo azul o gris encima
   de lo que se toca. Con el pulsado propio de cada cosa (A7) eran dos
   respuestas al mismo dedo, y la del navegador, la mas tosca. Se quita, y lo
   que no tiene un pulsado propio se atenua mientras se pulsa, como un boton
   del sistema. El aro del teclado no cambia: eso es :focus-visible. */
html { -webkit-tap-highlight-color: transparent; }
:where(a[href], button, [role="button"], summary):not(:where(.btn, .dcard, .co-card, .hm-new, .sale-next-link, :disabled)):active { opacity: .6; }
[hidden] { display: none !important; }

/* ==================================================================== SHELL */
.app { display: flex; flex-direction: column; min-height: 100dvh; }

/* ---- navegacion ----
   UN solo menu para todos los tamaños: en escritorio, la barra lateral fija
   (bloque >=1024 mas abajo); por debajo, el mismo <nav> como cajon a la
   izquierda que abre el boton de la barra de titulo (.nav-toggle, ui.js).
   Hasta el 4-oct-2026 era una barra fija abajo, un hueco por entrada: con
   siete entradas no cabian y Performance y Review Management se quedaban
   fuera.

   El cajon entra moviendo `left` y no con transform A PROPOSITO: las chapas
   de "Viewing as" y de staging viven dentro del <nav> y en el telefono van
   fijas arriba de la pantalla. Un transform en el <nav> las haria fijas al
   cajon, y con el cajon cerrado desaparecerian. Por lo mismo el cajon
   cerrado se esconde con `visibility` (fuera del tabulador y del lector de
   pantalla) y las chapas la vuelven a pedir. */
.nav {
  position: fixed; top: 0; bottom: 0; z-index: 45;
  left: calc(-1 * min(var(--sidebar-w), 86vw) - 24px);
  width: min(var(--sidebar-w), 86vw);
  display: flex; flex-direction: column; align-items: stretch;
  background: var(--bg-card); box-shadow: var(--shadow-soft);
  overflow-y: auto; overscroll-behavior: contain;
  padding-bottom: env(safe-area-inset-bottom);
  visibility: hidden;
  transition: left var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}
.app.nav-open .nav {
  left: 0; visibility: visible;
  transition: left var(--dur-base) var(--ease-enter), visibility 0s;
}
.nav .env-flag, .nav .impersonate-flag { visibility: visible; }
.nav-scrim { position: fixed; inset: 0; z-index: 44; background: var(--scrim); animation: fade var(--dur-base) var(--ease); }
.nav-toggle { flex: 0 0 auto; margin-left: -6px; color: var(--text-main); }
.nav-toggle svg { width: 20px; height: 20px; }

/* MISMA ALTURA que la barra de titulo, y por eso es un token compartido.
   Antes cada una se ganaba su alto con su propio padding y su propio
   contenido: 67px aqui, 83px enfrente. Las dos lineas de separacion caian
   con 16px de desfase justo en el punto donde la vista entra a la pantalla.
   Con --shell-head-h las dos empiezan y acaban a la vez, y ya no hace falta
   que ninguna de las dos dibuje una linea para disimularlo. */
.nav-brand { display: flex; align-items: center; gap: var(--space-3); min-height: var(--shell-head-h); padding: 0 var(--space-4); text-decoration: none; color: inherit; flex: 0 0 auto; }
.nav-mark { width: 32px; height: 32px; border-radius: 9px; background: var(--accent); color: var(--on-accent, var(--bg-card)); display: grid; place-items: center; font-weight: 700; font-size: 12.5px; flex: 0 0 auto; }
.nav-brand b { font-size: 14px; font-weight: 700; display: block; }
.nav-brand small { font-size: 11.5px; color: var(--text-soft); }
.nav-list { display: flex; padding: 10px var(--space-2); flex-direction: column; gap: 2px; flex: 0 0 auto; }
.nav-item {
  display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: 10px;
  padding: 9px 10px; min-height: 40px; border: 0; border-radius: var(--radius-md); background: transparent; cursor: pointer;
  font-size: 13.5px; font-weight: 600; color: var(--text-muted); text-decoration: none; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav-item svg { width: 21px; height: 21px; flex: 0 0 auto; opacity: .7; }
@media (hover: hover) {
  .nav-item:hover { background: var(--bg-card-soft); color: var(--text-main); }
  .nav-item:hover svg { opacity: 1; }
}
/* El item activo, con TRES senales y NINGUNA franja (regla A1).
   Tenia solo el fondo suave, que a un metro de la pantalla es un rectangulo
   un poco mas azul entre otros cuatro. Llego a llevar una barra de acento de
   3px a la izquierda y se quito: una franja pegada a un lado es un adorno
   que no dice nada que no diga ya el propio item, y en esta app no se usa en
   ningun sitio.
   Las tres senales que quedan no dependen del color: FONDO teñido, TEXTO en
   negrita —que cambia la forma de la palabra— e ICONO a plena opacidad
   frente al 70% de los inactivos. */
.nav-item[aria-current="page"] { position: relative; background: var(--accent-soft); color: var(--accent-ink); font-weight: 700; }
.nav-item[aria-current="page"] svg { opacity: 1; }
.nav-item .badge-n { display: inline-block; margin-left: auto; font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; background: var(--bg-card-soft); color: var(--text-muted); padding: 1px 7px; border-radius: 99px; border: 1px solid var(--border-subtle); }
.nav-item[aria-current="page"] .badge-n { background: var(--accent); color: var(--on-accent, var(--bg-card)); border-color: var(--accent); }
.nav-foot { display: block; margin-top: auto; padding: var(--space-3); border-top: 1px solid var(--border-subtle); }
.nav-user { display: flex; align-items: center; gap: 9px; width: 100%; background: transparent; border: 0; cursor: pointer; padding: 6px; border-radius: var(--radius-md); text-align: left; }
@media (hover: hover) { .nav-user:hover { background: var(--bg-card-soft); } }
.nav-user .who { min-width: 0; }
.nav-user b { display: block; font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav-user span { font-size: 11.5px; color: var(--text-soft); }

.main { display: flex; flex-direction: column; min-width: 0; flex: 1; }

/* ---- barra superior ---- */
/* La barra de titulo NO lleva una linea debajo: lleva una sombra.
   Las dos separaciones del armazon —la de debajo del logo y la de debajo del
   titulo— eran dos bordes de 1px lisos a alturas distintas, y una linea recta
   delata un desajuste de 16px desde el otro lado de la habitacion. Ahora la
   jerarquia la hace la superficie (la barra es --bg-card sobre un contenido
   --bg-page) y la sombra solo la despega. Ver DESIGN-RULES §2, reglas SH1-SH3. */
.topbar {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-card); box-shadow: var(--shadow-hair);
  position: sticky; top: 0; z-index: 30;
  transition: transform var(--dur-base) var(--ease-enter);
}
/* En el telefono y la tableta la barra se aparta al bajar por la pagina y
   vuelve en cuanto se sube un poco (ui.js pone .is-head-away), como la barra
   de direcciones del propio navegador (4-oct-2026). Fija entera —titulo,
   New dispute y busqueda— se comia con la de abajo una cuarta parte de la
   pantalla; quitarla del todo dejaba la accion principal a un viaje hasta
   arriba. Solo transform: no mueve nada de sitio. En escritorio no aplica:
   ahi quien se desplaza es .content y la barra no se mueve. */
@media (max-width: 1023px) {
  .app.is-head-away .topbar { transform: translateY(calc(-100% - 6px)); }
}
.title-wrap { margin-right: auto; min-width: 0; }
.topbar h1 { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.015em; }
.topbar-actions { display: flex; align-items: center; gap: var(--space-3); flex: 0 0 auto; }
/* En el telefono el avatar no se cae a otra linea. Era el ultimo de la fila,
   asi que cuando las acciones no cabian junto al titulo, era el quien bajaba
   —solo, a la izquierda—. Ahora el titulo cede (se corta con puntos) y, si
   hay dos acciones o mas, ellas bajan a su propia linea, a la derecha; el
   avatar se queda arriba. Con una sola, cabe todo en la primera. */
@media (max-width: 1023px) {
  .title-wrap { flex: 1 1 0; }
  .topbar h1 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .topbar-actions:has(> :nth-child(2)) { order: 2; flex: 1 0 100%; justify-content: flex-end; }
}
.topbar-sub { display: none; }
.search { position: relative; order: 3; flex: 1 0 100%; min-width: 0; }
.search .ico { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--text-soft); pointer-events: none; }
.search .ico svg { width: 100%; height: 100%; display: block; }
.search input {
  width: 100%; height: var(--control-h); padding: 0 var(--space-3) 0 var(--space-8);
  border: 1px solid var(--border-control); border-radius: var(--radius-md);
  background: var(--bg-card-soft); font-size: 13.5px;
}
.search input::placeholder { color: var(--text-soft); }
.search input:focus { outline: none; border-color: var(--accent); background: var(--bg-card); box-shadow: var(--ring); }
.avatar {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; cursor: pointer;
  display: grid; place-items: center; background: var(--accent-soft); color: var(--accent-ink);
  font-size: 12px; font-weight: 700; border: 1px solid var(--border-subtle);
}
.content { flex: 1; padding: var(--space-4); }

/* ---- botones ---- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--control-h); padding: 0 var(--space-4);
  border-radius: var(--radius-md); border: 1px solid var(--border-control);
  background: var(--bg-card); color: var(--text-main);
  font-size: 13.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease-enter);
}
.btn svg { width: 16px; height: 16px; flex: 0 0 auto; }
/* El hover no toca a un boton deshabilitado: aclararle el fondo lo hace
   parecer pulsable justo cuando no lo es. */
.btn:hover:not([disabled]) { background: var(--bg-card-soft); }
/* Pulsado (A7): se encoge un pelo en vez de bajar 1px. Encogerse se lee
   como "te he oido" en cualquier tamano de boton; un pixel hacia abajo casi
   no se ve en uno grande. */
.btn:active { transform: scale(.97); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent, var(--on-overlay)); }
.btn-primary:hover:not([disabled]) { background: var(--accent-ink); border-color: var(--accent-ink); }
.btn-danger { background: var(--danger); border-color: var(--danger); color: var(--on-danger, var(--on-overlay)); }
/* Sin esta regla el boton rojo heredaba el fondo claro de .btn:hover y se
   quedaba con texto blanco sobre blanco: desaparecia al pasar el raton, justo
   el boton que mas hay que leer antes de pulsarlo. */
.btn-danger:hover:not([disabled]) {
  background: var(--danger-hover, #b91c1c); border-color: var(--danger-hover, #b91c1c);
  color: var(--on-danger, var(--bg-card));
}
.btn-danger-outline { color: var(--danger); border-color: var(--danger); background: transparent; }
.btn-danger-outline:hover:not([disabled]) { background: var(--danger-soft); }
.btn-ghost:hover:not([disabled]) { background: var(--bg-card-soft); border-color: var(--border-subtle); }
.btn-ghost { border-color: transparent; background: transparent; color: var(--text-muted); }
.btn-sm { height: var(--control-h-sm); padding: 0 var(--space-3); font-size: 12.5px; }
.btn-icon { width: var(--control-h); padding: 0; }
.btn-icon.btn-sm { width: var(--control-h-sm); }
@media (pointer: coarse) {
  .btn, .nav-item, .tab, .status-opt { min-height: var(--tap-h); }
  .btn-sm { min-height: var(--control-h); }
}

/* ---- campos ---- */
/* El formulario respira en la escala de 4: 20px entre campos, 8px entre la
   etiqueta y su caja. Estaba en 16 y 5, y ese 5 no era ningun paso de la
   escala, solo un numero. */
.field { margin-bottom: var(--space-5); }
/* `.lbl` es la misma etiqueta para un grupo que NO tiene un solo control al
   que apuntar: un radiogroup se etiqueta con aria-labelledby, y un <label>
   suelto sin `for` es peor que un <span> — promete un destino que no existe
   (regla F1). Se pinta igual porque es lo mismo para el ojo. */
/* HIJO DIRECTO, y esto arregla un bug de meses.
   Estaba escrito `.field label`, que alcanza a CUALQUIER <label> dentro del
   campo — incluidas las casillas de .perm-check, .check y .pick, que son
   componentes con su propio `display: flex` y su propio `gap`. Y le ganaba:
   `.field label` pesa (0,1,1) contra el (0,1,0) de la clase. Resultado:
   `display: block` aplastaba el flex y el `gap` dejaba de existir, asi que el
   cuadrito de cada plataforma en "Edit company" salia PEGADO a su texto.
   En el CSS la regla del componente se leia perfecta.
   Con `>` la generica se queda donde le toca —la etiqueta del campo, que
   siempre es hija directa— y cada componente manda dentro de si mismo. Es la
   regla R9 de tools/design-check.js, que existia y no miraba esta hoja. */
.field > label, .field > .lbl { display: block; font-size: 12.5px; font-weight: 600; color: var(--text-muted); margin-bottom: var(--space-2); }
.field .req { color: var(--danger); }
.input, .textarea, .select {
  width: 100%; border: 1px solid var(--border-control); border-radius: var(--radius-md);
  background: var(--bg-card); color: var(--text-main); font-size: 13.5px;
}
.input, .select { height: var(--control-h); padding: 0 var(--space-3); }
.textarea { padding: 10px var(--space-3); resize: vertical; min-height: 96px; line-height: 1.55; }
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.select {
  -webkit-appearance: none; appearance: none; padding-right: 30px; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.5' stroke-linecap='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center;
}
.hint { font-size: 12px; color: var(--text-soft); margin-top: var(--space-2); }

/* Las casillas solo estaban dimensionadas dentro de .matrix. Fuera de ahi
   salian al tamano por defecto del navegador, que es distinto en cada uno y
   en ninguno llega a ser comodo de acertar con el dedo. */
input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px; margin: 0;
  accent-color: var(--accent); cursor: pointer; flex: 0 0 auto;
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled { cursor: not-allowed; opacity: .45; }
/* Una casilla con su texto al lado: alineados por arriba, no por el centro,
   para que una etiqueta de dos lineas no descoloque la casilla. */
.check {
  display: flex; align-items: flex-start; gap: var(--space-2);
  font-size: 13.5px; line-height: 1.45; cursor: pointer;
}
.check input { margin-top: 1px; }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0 var(--space-4); }

/* ---- resumen ---- */
.kpis { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); margin-bottom: var(--space-4); }
/* En movil, una cifra impar no se queda sola en media fila: ocupa la fila. */
@media (max-width: 639px) { .kpis > .kpi:last-child:nth-child(odd) { grid-column: 1 / -1; } }
/* El desglose de una cifra: la tarjeta y su definicion lado a lado. */
.kpi-summary { display: flex; flex-direction: column; gap: var(--space-3); }
@media (min-width: 640px) {
  .kpi-summary { flex-direction: row; align-items: center; gap: var(--space-4); }
  .kpi-summary .kpi { flex: 0 0 220px; }
  .kpi-summary .set-where { flex: 1 1 auto; }
}
.kpi { background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3); box-shadow: var(--shadow-subtle); }
.kpi-label { font-size: 11.5px; font-weight: 700; color: var(--text-soft); text-transform: uppercase; letter-spacing: .05em; }
.kpi-value { font-size: 24px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.2; }
.kpi-meta { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.kpi-meta.risk { color: var(--warning); font-weight: 600; }
/* El boton de "You owe" (14-sep-2026): mismo .btn de siempre, solo que
   dentro de una tarjeta que ya no lleva una tercera linea de texto debajo
   del numero -esa se fue a title/aria-label-, asi que el hueco que deja
   es exactamente el que ocupa este boton. */
.kpi-cta { margin-top: var(--space-2); width: fit-content; }

/* ---- panel ---- */
/* La tarjeta se levanta de la pagina; no se recorta con un trazo.
   Antes tenia un borde de 1px del mismo gris que la linea de la barra, la de
   la tabla y la de cada fila: cuatro trazos identicos compitiendo por decir
   cosas distintas. Ahora el borde es un pelo casi invisible que solo define
   el limite, y quien dice "esto esta encima" es la sombra. §2, regla SH2. */
.panel { background: var(--bg-card); border: 1px solid var(--hairline); border-radius: var(--radius-md); box-shadow: var(--shadow-card); overflow: hidden; }
/* El KPI "You owe" salta aqui con #ledger cuando hay mas de una company por
   pagar. Sin esto la barra superior -sticky hasta escritorio- tapaba el
   titulo del panel nada mas llegar. */
#ledger { scroll-margin-top: calc(var(--shell-head-h) + var(--space-3)); }
/* La separacion entre bloques de una pagina.
   Estaba escrita solo como .panel + .panel, asi que en cuanto un bloque dejo
   de ser un .panel suelto —el Overview mete un .two-col entre medias— la
   separacion desaparecia y ese hueco quedaba a la mitad que los demas. La
   separacion es del HUECO entre dos bloques, no de un tipo de bloque. */
.panel + .panel,
.two-col + .panel,
.panel + .two-col,
.two-col + .two-col { margin-top: var(--space-4); }
.panel-head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--hairline); }
.panel-head h2 { margin: 0; font-size: 14px; font-weight: 700; }
.panel-head .spacer { margin-left: auto; }
.panel-body { padding: var(--space-4); }

/* ---- filtros ---- */
.toolbar { display: flex; flex-direction: column; gap: var(--space-3); margin-bottom: var(--space-4); }
.toolbar-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* AIRE. Los seis pixeles de separacion venian de cuando estas pastillas eran
   rectangulos; con el radio a 999px no basta, porque las puntas redondas se
   comen el hueco y tres pastillas seguidas se leen como una mancha. Ocho, que
   ademas es un paso de la escala (--space-2) y no un numero suelto. */
/* LA FILA DE PASTILLAS SEPARA POR SI SOLA.
   En Disputes iba dentro de `.toolbar`, que le pone 16px por debajo; en Users
   cuelga directamente del <main> y no tenia NADA: las pastillas quedaban
   pegadas al panel de la lista. Medido: 12px de aire en Disputes contra 0 en
   Users, con la misma pastilla.
   La separacion se la queda la fila, que es quien la necesita mande quien la
   envuelva; y donde ya hay envoltorio, se anula para no sumar dos veces. */
.filter-scroll {
  display: flex; gap: var(--space-2); overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; padding-bottom: 2px; margin-bottom: var(--space-4);
  /* Se desliza de lado con iman y sin arrastrar la pagina arriba y abajo
     (skill mobile-native: una fila que se mueve de lado lo dice). */
  scroll-snap-type: x proximity; touch-action: pan-x pan-y;
}
.filter-scroll > * { scroll-snap-align: start; }
.toolbar .filter-scroll { margin-bottom: 0; }
.filter-scroll::-webkit-scrollbar { display: none; }
/* Y AIRE DENTRO. Doce a los lados en una pastilla de 34px con las puntas
   totalmente redondeadas deja el texto tocando la curva: la parte util
   empieza 17px adentro, asi que lo que se ve no son 12 de margen sino casi
   ninguno. Dieciseis (--space-4) es lo que hace que respire.
   El contador se separa un paso mas de su etiqueta: son dos datos, no una
   palabra con un numero pegado. */
.tab {
  border: 1px solid var(--border-control); background: var(--bg-card);
  border-radius: 999px; padding: 0 var(--space-4); height: var(--control-h-sm); cursor: pointer;
  font-size: 12.5px; font-weight: 600; color: var(--text-muted);
  white-space: nowrap; display: inline-flex; align-items: center; gap: var(--space-2);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.tab:hover { background: var(--bg-card-soft); }
/* El chip ELEGIDO se tiñe; no se rellena (4-oct-2026). Relleno era el mismo
   azul solido que New dispute, y filtrar una lista y crear algo no pueden
   parecer la misma accion: el unico azul solido de una pantalla es su accion
   principal. Tinte, borde y texto del acento: sigue viendose elegido sin
   competir. Ver DESIGN-RULES, "Cuatro papeles". */
.tab[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); }
.tab[aria-pressed="true"] .n { opacity: 1; }
/* Filters: el primer chip de la fila en el telefono. Texto en tinta y no en
   gris: es un control, no una opcion mas de la lista. */
.tab-filters { color: var(--text-main); }
.tab-filters svg { width: 15px; height: 15px; flex: 0 0 auto; }
.tab-filters .filters-count { margin-left: 0; }
.tab .n { font-variant-numeric: tabular-nums; opacity: .75; font-weight: 700; }

/* ---- estados: los colores de admin/assets/css/disputes.css ---- */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px 3px 8px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; white-space: nowrap;
  border: 1px solid transparent;
}
.pill i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
/* Un estado, un significado. La tabla de arriba manda; aqui solo se aplica.
   Sin valor de repuesto detras del token: el token esta declarado en :root y
   en los dos bloques oscuros, asi que un fallback solo serviria para tapar el
   dia en que alguien borre la declaracion — y ese dia hay que enterarse. */
.pill.submitted { background: var(--pill-open-bg); color: var(--pill-open-fg); }
.pill.photo     { background: var(--pill-wait-bg); color: var(--pill-wait-fg); }
.pill.dispute   { background: var(--pill-live-bg); color: var(--pill-live-fg); }
.pill.approved  { background: var(--pill-ok-bg);   color: var(--pill-ok-fg); }
.pill.denied    { background: var(--pill-bad-bg);  color: var(--pill-bad-fg); }
.pill.closed,
.pill.noaction  { background: var(--pill-done-bg); color: var(--pill-done-fg); border-color: var(--pill-done-bd); }
/* Denied, en las tarjetas de resumen del Dashboard (14-sep-2026): pizarra
   neutra, no el rojo de .pill.denied de arriba. Esa sigue roja a proposito
   -es la de la lista de disputes, y ahi no cambia nada-; esta es la version
   "resultado, no alarma" que pide el Dashboard. Toma los colores de
   Submitted (texto pizarra sobre fondo pizarra al 10%), no los de Closed: el
   gris apagado de Closed no llegaba al peso de las otras tres pastillas de
   la lista y parecia deshabilitada. Mismo nombre que su barra, .f-denied. */
.pill.f-denied  { background: var(--pill-open-bg); color: var(--pill-open-fg); }
/* Needs photo, segun lo que lleve esperando. Los dias salen de Ajustes. */
.pill.age15 { background: var(--pill-a15-bg); color: var(--pill-a15-fg); border-color: var(--pill-a15-bd); }
.pill.age30 { background: var(--pill-a30-bg); color: var(--pill-a30-fg); border-color: var(--pill-a30-bd); }

/* ---- tarjetas (movil) ---- */
.cards { display: flex; flex-direction: column; gap: var(--space-2); }
/* Una lista de tarjetas DENTRO de una tarjeta (Users en movil) no lleva una
   segunda caja por fila: doble borde y doble margen le comian 24px de ancho y
   la lista media 1506px para trece personas. Filas planas con un pelo. */
.cards--flat { gap: 0; }
.cards--flat .dcard { border: 0; border-radius: 0; box-shadow: none; border-bottom: 1px solid var(--hairline); }
.cards--flat .dcard:last-child { border-bottom: 0; }
.cards--flat .dcard-foot { grid-column: 2; grid-row: 2; justify-content: flex-end; margin-top: 0; }
/* La tarjeta ya no es un boton: lleva dentro el control de estado, y un
   boton dentro de otro no es HTML valido. El area que abre el detalle es
   .dcard-hit, que la cubre entera por debajo del contenido. */
.dcard {
  position: relative;
  width: 100%; text-align: left; cursor: pointer;
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  padding: var(--space-3); box-shadow: var(--shadow-subtle);
  display: grid; grid-template-columns: 1fr auto; gap: 4px var(--space-3);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease-enter);
}
.dcard:hover { border-color: var(--border-control); }
.dcard:active { transform: scale(.98); }
.dcard.is-open { border-color: var(--accent); box-shadow: var(--ring); }
.dcard-top { grid-column: 1; font-size: 14.5px; font-weight: 700; }
.dcard-amt { grid-column: 2; grid-row: 1; text-align: right; font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
.dcard-sub { grid-column: 1; font-size: 12.5px; color: var(--text-muted); }
.dcard-foot { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--space-2); margin-top: 6px; flex-wrap: wrap; }
.dcard-date { margin-left: auto; font-size: 12px; color: var(--text-soft); font-variant-numeric: tabular-nums; }

/* El area que abre el detalle. Va debajo de todo y sin nada dentro: existe
   para recibir el toque y para ser la parada de tabulador de la tarjeta. */
.dcard-hit {
  position: absolute; inset: 0; z-index: 0;
  border: 0; background: transparent; padding: 0; cursor: pointer;
  border-radius: inherit;
}
.dcard-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* El contenido no intercepta el toque, asi el clic llega al area de abajo.
   Lo unico que si lo intercepta es el control de estado. */
.dcard > *:not(.dcard-hit) { position: relative; z-index: 1; pointer-events: none; }
.dcard .pill-btn { pointer-events: auto; }

/* La pastilla cuando ademas se puede cambiar. Se parece a la pastilla, no a
   un boton: lo que cambia es que responde. */
.pill-btn {
  display: inline-flex; align-items: center; gap: 2px;
  border: 0; background: transparent; padding: 0; cursor: pointer;
  border-radius: 999px; color: var(--text-soft);
}
.pill-btn svg { width: 13px; height: 13px; flex: 0 0 auto; transition: transform var(--dur-fast) var(--ease); }
.pill-btn:hover svg { transform: translateY(1px); }
.pill-btn:hover .pill { box-shadow: var(--ring); }
.pill-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (pointer: coarse) { .pill-btn { min-height: var(--tap-h); } }

/* ---- tabla ---- */
.tablewrap { display: none; overflow-x: auto; overscroll-behavior-x: contain; }
table.data { width: 100%; border-collapse: collapse; }
table.data th {
  text-align: left; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-soft); padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--hairline); background: var(--bg-card-soft);
}
table.data th.num { text-align: right; }
/* La linea entre filas es un pelo, no un trazo. Con cuarenta filas, cuarenta
   lineas del mismo gris que el borde de la tarjeta convierten una lista en
   una rejilla — y lo que se lee en una tabla son las filas, no las lineas. */
table.data td { padding: 10px var(--space-3); border-bottom: 1px solid var(--hairline); vertical-align: middle; }
table.data td, table.data th { white-space: nowrap; }
table.data tbody tr { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
/* Sin esto, entre el toque y el cajon no pasaba nada visible: en un movil
   lento se siente como si el toque no hubiera entrado. */
table.data tbody tr:active { background: var(--bg-card-soft); }
table.data tbody tr:hover { background: var(--bg-card-soft); }
table.data tbody tr.is-open { background: var(--accent-soft); }
.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.t-strong { font-weight: 700; }
.t-muted { color: var(--text-muted); font-size: 13px; }
.t-mono { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--text-muted); }
.cell-store { display: flex; flex-direction: column; line-height: 1.3; max-width: 210px; }
.cell-store b, .cell-store span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-store b { font-weight: 600; font-size: 13px; }
.cell-store span { font-size: 11.5px; color: var(--text-soft); }
/* La regla de arriba volvia bloque TAMBIEN a la etiqueta TEST, que se
   estiraba a todo el ancho de la columna como una caja punteada. */
.cell-store .badge { display: inline-block; margin-left: 5px; vertical-align: 1px; overflow: visible; }
.cell-person { display: block; line-height: 1.35; }
.cell-person b { font-weight: 700; font-size: 13.5px; }
.cell-person .mail { display: block; font-size: 11.5px; color: var(--text-soft); }
/* Con seis columnas la tabla no cabe en tablet: el cliente sale de la fila,
   que esta a un toque en el detalle, antes que dejar el estado cortado.
   Cause (14-sep-2026) sigue el mismo criterio: es un dato mas de
   clasificacion, no imprescindible para escanear la lista; en tablet se
   abre el detalle para verla. */
.col-cust, .col-cause { display: none; }

.listfoot { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border-subtle); font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; flex-wrap: wrap; }
.listfoot .spacer { margin-left: auto; }

.empty { padding: 44px var(--space-5); text-align: center; }
/* Solo el dibujo del estado vacio, hijo directo. Con `.empty svg` a secas
   el "+" de un boton dentro del estado vacio salia a 34px y en gris. */
.empty > svg { width: 34px; height: 34px; color: var(--text-soft); margin-bottom: var(--space-2); }
.empty h3 { margin: 0 0 4px; font-size: 15px; font-weight: 700; }
.empty p { margin: 0 0 var(--space-3); font-size: 13px; color: var(--text-muted); }

/* ---- esqueleto de carga ----------------------------------------------
   Regla: el hueco tiene la forma y la altura de lo que va a ocuparlo. Las
   cuatro cifras y los chips de estado aparecian de la nada al llegar los
   datos y empujaban la lista hacia abajo; ese era el salto incomodo. Ahora
   el sitio ya esta reservado desde el primer pintado, sin esperar al JS. */
.skeleton { display: flex; flex-direction: column; gap: var(--space-2); }

.sk-row, .sk-line { position: relative; overflow: hidden; background: var(--bg-card-soft); }
.sk-row::after, .sk-line::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--shimmer, rgba(15,23,42,.06)), transparent);
  animation: shimmer 1.25s linear infinite;
}
@keyframes shimmer { from { transform: translateX(-100%) } to { transform: translateX(100%) } }

/* Una fila de la lista: alta como una tarjeta en movil, como una fila de
   tabla en cuanto hay tabla. */
.sk-row { height: 86px; border-radius: var(--radius-md); background: var(--bg-card); border: 1px solid var(--border-subtle); }

.sk-line { display: block; border-radius: 5px; }
/* Las alturas son las de las tres lineas reales (11.5px, 24px y 12.5px con
   su interlineado), no unas parecidas: si no coinciden, el hueco encoge o
   crece al llegar el dato y vuelve el salto. */
.kpi.is-loading .sk-label { height: 17px; width: 62%; }
.kpi.is-loading .sk-value { height: 29px; width: 44%; }
.kpi.is-loading .sk-meta  { height: 19px; width: 78%; }

.tab.is-loading { border-color: var(--border-subtle); background: var(--bg-card); pointer-events: none; }
.tab.is-loading .sk-line { height: 12px; width: 58px; }

/* ---- cajon de detalle ---- */
.scrim { position: fixed; inset: 0; z-index: 50; background: var(--scrim); animation: fade var(--dur-base) var(--ease); }
.drawer {
  position: fixed; z-index: 51; background: var(--bg-card);
  display: flex; flex-direction: column;
  inset: auto 0 0 0; max-height: 92dvh;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0; box-shadow: var(--shadow-soft);
  animation: sheetUp var(--dur-slow) var(--ease-enter);
  transition: transform var(--dur-base) var(--ease-out);
}
@keyframes fade { from { opacity: 0 } }
/* Un cajon que ya esta en pantalla y solo cambia por dentro NO vuelve a
   entrar. Lo pone drawer.replace(): sin esto, el cajon se deslizaba una vez
   con el esqueleto y otra con el contenido, y eso se ve como un salto. */
.drawer.is-settled { animation: none; }
@keyframes sheetUp { from { transform: translateY(24px); opacity: 0 } }
@keyframes slideIn { from { transform: translateX(28px); opacity: 0 } }
@keyframes popIn { from { transform: translateY(10px) scale(.98); opacity: 0 } }

/* El asa de arrastre. La barra se ve igual de fina, pero lo que atiende al
   dedo son los 24px de alto del contenedor: 4px no se agarran.
   touch-action: none porque esto es un tirador, no algo que se desplace; sin
   ello el navegador intenta desplazar a la vez que se arrastra. */
.drawer-grab {
  flex: 0 0 auto; height: 24px; display: grid; place-items: center;
  cursor: grab; touch-action: none;
}
.drawer-grab::before {
  content: ""; width: 40px; height: 4px; border-radius: 99px;
  background: var(--border-control); opacity: .55;
}
.drawer-grab:active { cursor: grabbing; }
.drawer-head { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4) var(--space-4) var(--space-3); border-bottom: 1px solid var(--border-subtle); flex: 0 0 auto; }
.drawer-head h2 { margin: 0 0 2px; font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
.drawer-head .sub { font-size: 12.5px; color: var(--text-muted); }
.drawer-amt { margin-left: auto; text-align: right; }
.drawer-amt b { display: block; font-size: 21px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
/* overscroll-behavior es lo que impide que, al llegar al final del cajon,
   el gesto se lo quede la pagina de detras. Sin esto el dedo movia el fondo
   en vez de la caja y parecia que la caja no se deslizaba.
   -webkit-overflow-scrolling hace falta para el desplazamiento con inercia
   en las versiones viejas de iOS. */
.drawer-body {
  flex: 1 1 auto; min-height: 0; padding: var(--space-4);
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.drawer-foot { display: flex; gap: var(--space-2); padding: var(--space-3) var(--space-4); padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom)); border-top: 1px solid var(--border-subtle); background: var(--bg-card-soft); flex: 0 0 auto; }
.drawer-foot .btn { flex: 1; }

.deflist { display: grid; grid-template-columns: auto 1fr; gap: 7px var(--space-4); margin: 0 0 var(--space-4); font-size: 13px; }
.deflist dt { color: var(--text-soft); font-weight: 600; }
.deflist dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.sectitle { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--text-soft); margin: 0 0 var(--space-2); display: flex; align-items: center; gap: var(--space-2); }
.sectitle .spacer { margin-left: auto; }
.sectitle .plain { text-transform: none; letter-spacing: 0; font-weight: 600; font-size: 11.5px; }

.photo-slot { border: 1.5px dashed var(--border-control); border-radius: var(--radius-md); padding: 22px var(--space-3); text-align: center; background: var(--bg-card-soft); margin-bottom: var(--space-4); }
.photo-slot svg { width: 26px; height: 26px; color: var(--text-soft); }
.photo-slot p { margin: 6px 0 10px; font-size: 12.5px; color: var(--text-muted); }
.photo-thumb {
  position: relative; display: block; width: 100%; padding: 0; border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md); overflow: hidden; margin-bottom: var(--space-4);
  background: var(--bg-card-soft); cursor: zoom-in;
}
/* min-height + el texto alternativo escondido: si el archivo no esta —se
   borro, o la red se corto a media carga— el navegador encogia la caja a la
   altura del texto alternativo y lo escupia al lado de la etiqueta, con el
   bloque entero descuadrado. Asi el hueco mantiene su sitio y solo se ve
   vacio. */
.photo-thumb img {
  display: block; width: 100%; min-height: 120px; max-height: 260px;
  object-fit: cover; color: transparent; font-size: 0;
}
.photo-thumb .tag { position: absolute; left: 8px; bottom: 8px; background: rgba(15,23,42,.72); color: var(--on-overlay); font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 6px; }

/* ---- linea de tiempo con notas ---- */
.timeline { list-style: none; margin: 0 0 var(--space-3); padding: 0; }
.tl-item { display: grid; grid-template-columns: 26px 1fr; gap: var(--space-2); position: relative; padding-bottom: var(--space-3); }
.tl-item::before { content: ""; position: absolute; left: 12px; top: 28px; bottom: 0; width: 1.5px; background: var(--border-subtle); }
.tl-item:last-child::before { display: none; }
.tl-dot { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-card-soft); border: 1px solid var(--border-subtle); color: var(--text-muted); }
.tl-dot svg { width: 14px; height: 14px; }
.tl-dot.email { background: var(--accent-soft); color: var(--accent-ink); border-color: transparent; }
.tl-dot.photo { background: var(--success-soft); color: var(--success-ink); border-color: transparent; }
.tl-dot.note  { background: var(--warning-soft); color: var(--warning); border-color: transparent; }
.tl-body { min-width: 0; }
.tl-body b { display: block; font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
.tl-body time { font-size: 11.5px; color: var(--text-soft); font-variant-numeric: tabular-nums; }
/* Sin franja (regla A1): el tono de fondo ya dice que es una nota, y el radio
   entero la separa de la linea de tiempo mejor que una barra de 3px que
   ademas dejaba la caja con dos esquinas rectas y dos redondas. */
.tl-note {
  margin-top: 5px; padding: var(--space-2) var(--space-3);
  background: var(--warning-soft);
  border-radius: var(--radius-md);
  font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere;
}

.note-box { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-card-soft); padding: var(--space-3); }
.note-box .textarea { min-height: 68px; background: var(--bg-card); }
.note-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); flex-wrap: wrap; }
.note-actions .hint { margin: 0; margin-right: auto; }

/* ---- grafica mensual (Disputes per month) ----------------------------- */
/* Eje de dinero a la izquierda, lineas guia discontinuas, columnas apiladas
   y la cifra de lo recuperado encima: la referencia "Recovery activity".
   Marcas, lineas y barras se colocan en % del MISMO tope (ui_escala() en
   overview.php), asi que la escala de las tres coincide por construccion.
   Sustituye a .chart/.chart-col/.chart-stack, que se borraron: nada mas los
   pintaba (regla 5.5). */
.mchart { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: 210px auto;
          column-gap: 10px; padding-top: 22px; }
.mchart-y { position: relative; min-width: 34px; }
.mchart-y span { position: absolute; right: 0; transform: translateY(50%); font-size: 11.5px;
                 color: var(--text-soft); font-variant-numeric: tabular-nums; white-space: nowrap; line-height: 1; }
.mchart-plot { position: relative; }
.mchart-line { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed var(--chart-grid); }
.mchart-line.is-base { border-top: 1px solid var(--chart-grid); }
.mchart-cols, .mchart-x { display: flex; gap: 4%; padding: 0 2%; }
.mchart-cols { position: absolute; inset: 0; }
.mchart-col { position: relative; flex: 1 1 0; min-width: 0; display: flex; align-items: flex-end;
              justify-content: center; border-radius: var(--radius-sm); outline-offset: 3px; }
.mchart-bar {
  width: 100%; max-width: 76px; height: var(--h); min-height: 3px;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 2px;
  border-radius: 3px 3px 0 0; overflow: hidden;
  transition: filter var(--dur-fast) var(--ease);
  /* Entra creciendo desde su base, no aparece ya hecha (DESIGN-RULES "Las
     barras entran, no aparecen"). El alto real es --h desde el primer render. */
  transform-origin: bottom; animation: chart-grow-y var(--dur-slow) var(--ease-enter);
}
.mchart-bar .seg { display: block; width: 100%; flex: 0 1 auto; min-height: 2px; }
.mchart-col:hover .mchart-bar, .mchart-col:focus-visible .mchart-bar { filter: brightness(.95); }
.mchart-n { position: absolute; left: 50%; bottom: calc(var(--h) + 6px); transform: translateX(-50%);
            font-size: 12.5px; font-weight: 700; color: var(--text-main); font-variant-numeric: tabular-nums;
            white-space: nowrap; pointer-events: none; }
.mchart-x { padding-top: 8px; }
.mchart-x span { flex: 1 1 0; min-width: 0; text-align: center; font-size: 11.5px; color: var(--text-soft);
                 font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 639px) {
  .mchart { grid-template-rows: 180px auto; column-gap: 6px; }
  .mchart-cols, .mchart-x { gap: 3%; padding: 0 1%; }
  .mchart-n { font-size: 10.5px; }
  .mchart-x span, .mchart-y span { font-size: 10.5px; }
}

/* La nota de pie de una grafica: el icono en su circulo y el texto al lado,
   separados de la grafica por un pelo. */
.chart-note { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-4);
              padding-top: var(--space-4); border-top: 1px solid var(--hairline); }
.chart-note-ico { flex: 0 0 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
                  background: var(--bg-card-soft); color: var(--text-muted); }
.chart-note-ico svg { width: 17px; height: 17px; }
.chart-note p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); }
.chart-note b { color: var(--text-main); }

.bars { display: flex; flex-direction: column; gap: 12px; }
.bar-row { display: grid; grid-template-columns: 1fr auto; gap: 6px var(--space-3); align-items: baseline; }
.bar-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-val { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.bar-track { display: block; grid-column: 1 / -1; height: 8px; border-radius: 99px; background: var(--chart-track); overflow: hidden; }
.bar-fill {
  display: block; height: 100%; border-radius: 99px; background: var(--accent);
  /* Misma entrada que .chart-stack, en horizontal: crece desde el borde
     izquierdo del carril en vez de aparecer ya en su ancho final. El
     `style="width:"` inline sigue siendo el dato real desde el primer
     render; solo se anima como llega a ocuparlo. */
  transform-origin: left; animation: chart-grow-x var(--dur-slow) var(--ease-enter);
}
/* Las dos entradas de "una gráfica crece, no aparece" (DESIGN-RULES). El
   `to` implicito es el propio elemento sin transformar -- el mismo patron
   terso que ya usan sheetUp/toastIn mas abajo. */
@keyframes chart-grow-x { from { transform: scaleX(0) } }
@keyframes chart-grow-y { from { transform: scaleY(0) } }
/* La misma entrada, para lo que no tiene un solo eje: el donut aparece en el
   centro en vez de crecer desde un borde. Desde .96 y no desde 0: nada en el
   mundo sale de la nada, y un anillo que nace de un punto arrastra su cifra
   central de un tamano ilegible (Emil Kowalski, "never scale(0)"). */
@keyframes chart-grow { from { opacity: 0; transform: scale(.96) } }

/* ---- donut (Top Disputes) ---- */
/* Primer grafico que no es una barra: docs/DESIGN-RULES.md, #### El pastel.
   Es un anillo SVG dibujado con stroke-dasharray -tantos circulos
   concentricos como causas, cada uno un tramo de la misma circunferencia-
   en vez de un <path> con arcos: el calculo es una regla de tres, no
   trigonometria, y ese mismo numero (round($len,2) en overview.php)
   gobierna donde empieza el siguiente tramo. */
.donut-row { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: center; }
.donut-wrap { position: relative; flex: 0 0 auto; width: 168px; height: 168px; transform-origin: center; animation: chart-grow var(--dur-slow) var(--ease-enter); }
.donut { width: 100%; height: 100%; }
.donut-track { fill: none; stroke: var(--chart-track); stroke-width: 22; }
.donut-seg { fill: none; stroke: var(--chart-cause); stroke-width: 22; stroke-linecap: butt; }
/* Un halo del color de la tarjeta, no --on-accent: el tramo detras de cada
   numero va del violeta entero a uno mezclado al 45% con la tarjeta, o al
   gris de "Not classified", asi que un solo color de texto no lee bien
   contra todos. El
   halo hace que el numero se lea igual sea cual sea el tramo que tenga
   detras, en vez de adivinar un color que sirva para todos. */
.donut-num {
  font-size: 11px; font-weight: 700; fill: var(--text-main); font-variant-numeric: tabular-nums;
  paint-order: stroke fill; stroke: var(--bg-card); stroke-width: 3px; stroke-linejoin: round;
}
.donut-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; }
.donut-center b { font-size: 22px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.donut-center span { font-size: 11px; color: var(--text-soft); }
.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; flex: 1 1 220px; min-width: 200px; }
.donut-legend li { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.donut-legend i { width: 10px; height: 10px; border-radius: 3px; background: var(--chart-cause); flex: 0 0 auto; }
.donut-legend .lbl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.donut-legend .n { color: var(--text-muted); font-variant-numeric: tabular-nums; font-weight: 600; }
/* El porcentaje, en su propia columna de ancho fijo para que las cifras
   queden alineadas una debajo de otra. */
.donut-legend .pct { color: var(--text-soft); font-variant-numeric: tabular-nums; font-size: 12px; min-width: 3.2em; text-align: right; }
/* El nombre parte en dos lineas antes que cortarse (PR-6). Con la columna del
   porcentaje, a 375px "Cancelled by Support" se quedaba en "Cancelled b…" al
   lado del donut. La muestra se alinea con la primera linea. */
.panel-causes .donut-legend li { align-items: flex-start; }
.panel-causes .donut-legend i { margin-top: 5px; }
.panel-causes .donut-legend .lbl { white-space: normal; overflow: visible; overflow-wrap: break-word; line-height: 1.5; }
.panel-causes .donut-legend .n, .panel-causes .donut-legend .pct { line-height: 1.5; }

/* ==========================================================================
   LAS NOTAS QUE EXPLICAN — y que cada uno puede retirar
   --------------------------------------------------------------------------
   El texto que explica un modulo vive al pie de SU tarjeta, que es donde ya
   vivia el de "Disputes per month": ahi se lee sin buscarlo. Lo pinta el
   servidor (lib/Hints.php) solo para quien todavia no lo ha retirado.

   HIJA DE LA TARJETA, NO DEL CUERPO. Se intento al reves —pegarla a los
   bordes con margenes negativos del tamano del padding del cuerpo— y el
   libro de cobro lo rompio: su cuerpo lleva padding-bottom 0 porque dentro
   hay una tabla, asi que la nota se salia 15px por debajo de su tarjeta.
   Una nota no puede depender del relleno de su vecino.
   ========================================================================== */
.mod-note {
  display: flex; align-items: flex-start; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--hairline);
  background: var(--bg-card-soft);
  font-size: 12.5px; line-height: 1.55; color: var(--text-muted);
}
.mod-note > svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 1px; color: var(--accent); }
/* El texto es lo elastico y nunca baja de 200px: sin esto, en el telefono el
   boton le dejaba una columna de cuatro letras. */
.mod-note p { margin: 0; flex: 1 1 200px; min-width: 0; }
.mod-note p b { color: var(--text-main); font-weight: 600; }
.mod-note-acts { margin-left: auto; flex: 0 0 auto; }
@media (max-width: 559px) {
  .mod-note { gap: var(--space-2); }
  .mod-note-acts { margin-left: 0; width: 100%; display: flex; justify-content: flex-end; }
}
/* La de un campo no cruza la tarjeta: se queda con su campo. */
.mod-note--field {
  margin-top: var(--space-2); border: 1px solid var(--hairline);
  border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3);
}

/* LLEGA, no estaba ahi desde siempre: se anima cuando su modulo entra en
   pantalla. Cuatro pixeles y un cuarto de segundo, lo justo para que el ojo
   la registre como algo nuevo. prefers-reduced-motion la deja quieta por la
   regla global de mas abajo. */
@keyframes modNoteIn { from { opacity: 0; transform: translateY(-4px) } }
.mod-note.is-new { animation: modNoteIn var(--dur-mid) var(--ease-enter); }
/* ESPERANDO SU TURNO. Sin esto la nota ya estaba puesta al llegar abajo
   —el servidor la manda con la pagina— y la "llegada" no se veia: el modulo
   aparecia con su nota hecha. La pone invisible el JS, solo a las que
   todavia no se han visto, asi que sin JS se ven todas. El hueco SI se
   reserva: aparecer empujando el resto de la pagina hacia abajo es peor que
   no aparecer. */
.mod-note--espera { opacity: 0; }

/* Y cuando se devuelve con la (i): dos segundos de fondo encendido, porque
   en un telefono la nota queda fuera de la pantalla y nadie se entera de que
   ha vuelto. El desplazamiento lo hace ui.js. */
@keyframes modNoteBack {
  0%, 70% { background: var(--accent-soft); border-color: var(--accent); }
  100% { background: var(--bg-card-soft); border-color: var(--hairline); }
}
.mod-note.is-back { animation: modNoteBack 2.2s var(--ease); }
/* Las dos a la vez cuando vuelve con la (i): entra Y se enciende. Escrito
   aparte porque las dos reglas usan el atajo `animation` y la ultima le
   pisaba la entrada a la otra. */
.mod-note.is-new.is-back {
  animation: modNoteIn var(--dur-mid) var(--ease-enter), modNoteBack 2.2s var(--ease);
}

/* La puerta de vuelta, en la cabecera del modulo. */
.mod-i {
  width: 20px; height: 20px; flex: 0 0 auto; padding: 0;
  border: 0; background: none; cursor: pointer; border-radius: 99px;
  color: var(--text-soft); display: inline-flex; align-items: center; justify-content: center;
}
.mod-i svg { width: 15px; height: 15px; }
.mod-i:hover { color: var(--accent-ink); background: var(--accent-soft); }
.mod-i:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- companies ---- */
/* Una company en la lista. Se abre en un cajon, como una dispute: antes
   se desplegaba hacia abajo y empujaba todo lo que venia despues. */
.co-card {
  display: flex; align-items: center; gap: var(--space-3); width: 100%;
  padding: var(--space-3) var(--space-4); text-align: left; cursor: pointer;
  background: transparent; border: 0; border-bottom: 1px solid var(--border-subtle);
  transition: background var(--dur-fast) var(--ease);
}
.co-card:last-child { border-bottom: 0; }
.co-card:hover { background: var(--bg-card-soft); }
.co-card:active { background: var(--bg-card-soft); }
.co-card.is-open { background: var(--accent-soft); }
.co-txt { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.co-card .co-chev { transform: rotate(-90deg); }
.co:last-child { border-bottom: 0; }
/* La marca no lleva un color por company: un tono distinto por cada una seria
   color sin trabajo que hacer, y las iniciales ya las distinguen. Lo unico que
   dice el color aqui es si la company esta activa. */
.co-mark.is-off { background: var(--bg-card-soft); color: var(--text-muted); box-shadow: inset 0 0 0 1px var(--border-subtle); }
.co-mark { width: 34px; height: 34px; flex: 0 0 auto; border-radius: 9px; display: grid; place-items: center; background: var(--accent-soft); color: var(--accent-ink); font-weight: 700; font-size: 12.5px; }
.co-name { font-size: 14.5px; font-weight: 700; display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.co-sub { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.co-chev { margin-left: auto; color: var(--text-soft); transition: transform var(--dur-base) var(--ease); flex: 0 0 auto; }
/* Dentro del cajon ya no hace falta sangrarlo: el cajon es el contenedor.
   El :not([hidden]) no sobra: un "display: block" a secas gana contra el
   display:none que trae el atributo hidden del navegador, y las stores se
   verian siempre por mucho que el boton las cierre. */
.co-stores:not([hidden]) { display: block; }
.st-row { padding: 9px 0; border-top: 1px solid var(--hairline); font-size: 13px; }
.st-row .st-n { color: var(--text-muted); font-size: 12.5px; }
.co-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); flex-wrap: wrap; }

.badge { font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 5px; display: inline-block; }
.badge.off { background: var(--bg-card-soft); color: var(--text-muted); border: 1px solid var(--border-subtle); }
.badge.role-admin   { background: var(--accent-soft); color: var(--accent-ink); }
.badge.role-manager { background: var(--success-soft); color: var(--success-ink); }
.badge.role-user    { background: var(--bg-card-soft); color: var(--text-muted); border: 1px solid var(--border-subtle); }
/* El personal de JB comparte el color de su equivalente de cliente —hacen el
   mismo trabajo y pesan lo mismo— y se distingue por el borde: es de casa, no
   del restaurante. La diferencia no se fia solo del color: el rotulo ya dice
   "JB" delante. */
.badge.role-jb_disputes   { background: var(--bg-card-soft); color: var(--text-main); border: 1px dashed var(--accent); }
.badge.role-jb_management { background: var(--success-soft); color: var(--success-ink); border: 1px dashed var(--success); }
.badge.pending { background: var(--warning-soft); color: var(--warning); }
/* BANCO DE PRUEBAS. Dice "esto no es real", y por eso es la unica etiqueta de
   la app escrita en mayusculas y con borde discontinuo: tiene que cortar la
   lectura de una lista de datos reales, que es exactamente lo que no hacia
   cuando la unica pista era como se llamaba la company.
   Ambar y no rojo: no es un error, es un aviso de que ese renglon no cuenta.
   Ver migracion 013 y docs/DESIGN-RULES.md. */
.badge.test {
  background: var(--warning-soft); color: var(--warning);
  border: 1px dashed var(--warning); letter-spacing: .06em;
}
/* "Active" tambien es un badge. Antes era texto plano gris mientras los otros
   dos estados si lo eran, asi que la columna se leia a dos alturas y el estado
   normal parecia una nota al margen en vez de un estado. Un estado binario se
   enseña igual en los dos casos. */
.badge.ok { background: var(--success-soft); color: var(--success-ink); }

/* ---- matriz de permisos ---- */
.matrix-wrap { overflow-x: auto; overscroll-behavior-x: contain; margin: 0 calc(var(--space-4) * -1); padding: 0 var(--space-4); }
.matrix { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.matrix th { text-align: left; font-weight: 700; color: var(--text-soft); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; padding: var(--space-2) 7px; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.matrix th.rot { text-align: center; }
.matrix td { padding: 9px 7px; border-bottom: 1px solid var(--hairline); }
.matrix td:first-child, .matrix th:first-child { padding-left: 10px; }
.matrix td:first-child b { display: block; max-width: 132px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.matrix td.c { text-align: center; }
/* Una fecha no se parte en dos lineas: "2026-09-" arriba y "07" abajo hacia
   cada fila del desglose 58px en vez de 40. */
.matrix td.t-mono { white-space: nowrap; }
.preset-row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--space-3); }

.note-info {
  display: flex; gap: 9px; align-items: flex-start;
  padding: var(--space-3); border-radius: var(--radius-md);
  font-size: 12.5px; line-height: 1.45; margin-bottom: var(--space-4);
  background: var(--accent-soft); color: var(--accent-ink);
}
.note-info.warn   { background: var(--warning-soft); color: var(--warning); }
.note-info.danger { background: var(--danger-soft); color: var(--danger-ink); }
.note-info svg { width: 17px; height: 17px; flex: 0 0 auto; margin-top: 1px; }
.note-info b { font-weight: 700; }

/* Pegar un pedido en "New dispute". El panel vive dentro de un .note-info
   —sin icono: el titulo ya dice que es—; lo de aqui es solo su orden
   interno y las marcas por campo. */
.parser-body { flex: 1 1 auto; min-width: 0; }
.parser-title { display: block; margin-bottom: var(--space-2); font-weight: 700; }
.parser-body .textarea { width: 100%; background: var(--bg-card); color: var(--text-main); }
.parser-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); align-items: center; }
.parser-actions .select-xs { max-width: 100%; }
.parser-result {
  display: flex; flex-wrap: wrap; gap: 6px var(--space-2); align-items: center;
  margin-bottom: var(--space-2); color: var(--text-main);
}
.parser-hint { flex-basis: 100%; color: var(--text-soft); }
/* La marca va pegada a la etiqueta del campo, sin empujar el asterisco. */
.parser-badge { margin-left: 6px; vertical-align: 1px; }
.parser-note { margin-top: 4px; }
/* Las stores candidatas: un enlace con forma de boton, del tamaño del texto
   que lo rodea. Es un <button> porque ELIGE, no navega. */
.parser-pick {
  font: inherit; color: var(--accent-ink); background: var(--accent-soft);
  border: 0; border-radius: 5px; padding: 1px 6px; margin: 2px 4px 0 0; cursor: pointer;
}
.parser-pick:hover { text-decoration: underline; }
.parser-pick:focus-visible { outline: none; box-shadow: var(--ring); }

/* ---------------------------------------------------------------------
   Settings -> Disputes -> Platform parsers: una fila por plataforma.
   --------------------------------------------------------------------- */
.set-parsers { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.set-parsers li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px var(--space-3); align-items: center;
  padding: var(--space-3) 0; border-top: 1px solid var(--hairline);
}
.set-parsers li:first-child { border-top: 0; padding-top: 0; }
.set-parsers .btn { grid-row: 1 / span 2; grid-column: 2; }
.set-parser-meta { font-size: 12.5px; color: var(--text-muted); }

/* ---------------------------------------------------------------------
   Parser Studio (pages/parser.php), en el orden en que se usa: el estado
   arriba, revisar una pagina, las paginas de prueba y, plegado, lo tecnico.
   --------------------------------------------------------------------- */
.pst-platforms { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
.pst-platforms .btn.is-on { background: var(--accent-soft); color: var(--accent-ink); border-color: transparent; }
.pst-status { margin-bottom: var(--space-4); font-size: 13.5px; align-items: center; }
.pst-status > div { flex: 1 1 auto; min-width: 0; }
.pst-status.is-ok { background: var(--success-soft); color: var(--success-ink); }
.pst-status .btn { margin-left: var(--space-2); }
.pst-sec { margin-bottom: var(--space-4); }
.pst-sec .panel-head h2 { margin: 0; font-size: 15px; }
.pst-sec .textarea { width: 100%; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px; }
.pst-steps { margin: 0 0 var(--space-3); padding-left: 1.3em; font-size: 13px; color: var(--text-muted); line-height: 1.6; }
.pst-steps b { color: var(--text-main); }

/* Un enlace que es un boton: acciones secundarias sin ruido. */
.pst-link { font: inherit; font-size: 12.5px; color: var(--accent-ink); background: none; border: 0; padding: 4px 0; cursor: pointer; }
.pst-link:hover { text-decoration: underline; }
.pst-link:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 4px; }

/* El resultado: un campo por fila, en palabras. */
.pst-detect { display: flex; flex-wrap: wrap; gap: 6px var(--space-2); align-items: center; margin: var(--space-4) 0 var(--space-2); font-size: 13px; }
.pst-fields { list-style: none; margin: 0 0 var(--space-2); padding: 0; border-top: 1px solid var(--hairline); }
.pst-field {
  display: grid; grid-template-columns: 130px minmax(0, 1fr) auto auto; gap: 4px var(--space-3); align-items: center;
  padding: 10px 0; border-bottom: 1px solid var(--hairline); font-size: 13.5px;
}
.pst-f-name { font-weight: 600; color: var(--text-main); }
.pst-f-val { overflow-wrap: anywhere; font-weight: 600; }
.pst-f-state .badge { white-space: nowrap; }
.pst-f-act { text-align: right; }
.pst-f-why, .pst-f-tech { grid-column: 2 / -1; margin: 0; font-size: 12.5px; color: var(--text-soft); }
.pst-f-tech code { font-size: 11.5px; }
@media (max-width: 640px) {
  .pst-field { grid-template-columns: minmax(0, 1fr) auto; }
  .pst-f-name { grid-column: 1 / -1; font-size: 12px; color: var(--text-soft); text-transform: uppercase; letter-spacing: .04em; }
  .pst-f-act { grid-column: 1 / -1; text-align: left; }
  .pst-f-why, .pst-f-tech { grid-column: 1 / -1; }
}

/* Enseñar: la pagina tal como se leyo, para señalar el valor. */
.pst-teach { margin-top: var(--space-3); }
.pst-teach-card { border: 1px solid var(--accent); border-radius: var(--radius-md); padding: var(--space-4); background: var(--bg-card); }
.pst-teach-title { margin: 0 0 var(--space-3); font-size: 13.5px; }
.pst-page {
  margin: 0; padding: var(--space-2) var(--space-2) var(--space-2) 2.8em; max-height: 320px; overflow: auto;
  background: var(--bg-card-soft); border-radius: var(--radius-sm, 6px);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12.5px;
}
.pst-page li { padding: 1px 0; }
.pst-page li::marker { color: var(--text-soft); }
.pst-line { overflow-wrap: anywhere; }
.pst-page .pst-line::selection { background: var(--accent); color: var(--on-overlay); }
.pst-proposal { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--hairline); font-size: 13.5px; }
.pst-prop-lead { margin: 0 0 4px; }
.pst-prop-rule { margin: 0 0 var(--space-2); font-weight: 600; color: var(--text-main); overflow-wrap: anywhere; }
.pst-checks { list-style: none; margin: 0 0 var(--space-2); padding: 0; font-size: 12.5px; }
.pst-checks li { padding: 2px 0 2px 20px; position: relative; }
.pst-checks li::before { position: absolute; left: 0; font-weight: 700; }
.pst-checks li.is-ok { color: var(--success-ink); }
.pst-checks li.is-ok::before { content: "✓"; }
.pst-checks li.is-bad { color: var(--warning); }
.pst-checks li.is-bad::before { content: "!"; }
.pst-others { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.pst-others li { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-3); }
.pst-cause-map { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; font-weight: 600; }

/* Guardar como prueba. */
.pst-save { margin-top: var(--space-4); }
.pst-save .hint { margin-top: 6px; }
.pst-save-card { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-4); }
.pst-save-card h3 { margin: 0; font-size: 14px; }
.pst-exp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0 var(--space-3); margin-top: var(--space-3); }
.pst-anon { align-items: flex-start; margin: var(--space-2) 0; font-size: 13px; }
.pst-anon input { margin-top: 3px; }

/* Las paginas de prueba. */
.pst-samples { list-style: none; margin: 0; padding: 0; }
.pst-samples li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 4px var(--space-3); align-items: center;
  padding: 10px 0; border-top: 1px solid var(--hairline); font-size: 13.5px;
}
.pst-samples li:first-child { border-top: 0; padding-top: 0; }
.pst-s-name { overflow-wrap: anywhere; }
.pst-s-state .hint { margin-top: 2px; }
.pst-s-act { white-space: nowrap; }
@media (max-width: 640px) {
  .pst-samples li { grid-template-columns: minmax(0, 1fr) auto; }
  .pst-s-state { grid-column: 1 / -1; grid-row: 2; }
}

/* Advanced: plegado, y dentro todo en palabras salvo los patrones. */
.pst-advanced > summary {
  list-style: none; cursor: pointer; display: flex; flex-wrap: wrap; gap: 2px var(--space-2); align-items: baseline;
  padding: var(--space-3) var(--space-4); font-size: 13.5px;
}
.pst-advanced > summary::-webkit-details-marker { display: none; }
.pst-advanced > summary::before { content: "▸"; color: var(--text-soft); }
.pst-advanced[open] > summary::before { content: "▾"; }
.pst-advanced[open] > summary { border-bottom: 1px solid var(--hairline); }
.pst-advanced > summary .t-muted { font-size: 12.5px; }
.pst-adv-block + .pst-adv-block { margin-top: var(--space-5, 24px); padding-top: var(--space-4); border-top: 1px solid var(--hairline); }
.pst-adv-block h3 { margin: 0 0 4px; font-size: 14px; }
.pst-adv-block h4 { margin: var(--space-3) 0 4px; font-size: 13px; }
.pst-adv-block > .btn { margin-top: var(--space-2); }
.pst-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: var(--space-2) 0; }
.pst-chip {
  font: inherit; font-size: 12.5px; color: var(--text-main); background: var(--bg-card-soft);
  border: 1px solid var(--border-subtle); border-radius: 999px; padding: 3px 10px; cursor: pointer;
}
.pst-chip small { color: var(--text-soft); font-size: 11px; }
.pst-chip:hover { border-color: var(--accent); }
.pst-chip:focus-visible { outline: none; box-shadow: var(--ring); }
.pst-chip.is-off { opacity: .55; text-decoration: line-through; }
.pst-chip code, .pst-list code, .pst-f-why code, .pst-sec .hint code { font-size: 11.5px; background: var(--bg-card-soft); padding: 1px 4px; border-radius: 4px; overflow-wrap: anywhere; }
.pst-shared { font-size: 12.5px; color: var(--text-muted); margin: var(--space-2) 0; }
.pst-list { list-style: none; margin: 0; padding: 0; }
.pst-list li {
  display: flex; flex-wrap: wrap; gap: 4px var(--space-3); justify-content: space-between; align-items: baseline;
  padding: 6px 0; border-top: 1px solid var(--hairline); font-size: 13px;
}
.pst-list li:first-child { border-top: 0; }
.pst-list li.is-off > span { opacity: .6; }
.pst-list li > span { min-width: 0; overflow-wrap: anywhere; }
.pst-cause-row { display: grid; grid-template-columns: 170px minmax(0, 1fr); gap: 4px var(--space-3); align-items: center; padding: 4px 0; font-size: 13px; }
.pst-cause-row .pst-chips { margin: 0; }
@media (max-width: 640px) { .pst-cause-row { grid-template-columns: 1fr; } }

/* El editor. */
.pst-err { margin-top: var(--space-2); }
.pst-err ul { margin: 6px 0; padding-left: 1.2em; }
.pst-adv summary { cursor: pointer; font-size: 13px; color: var(--text-muted); margin-bottom: var(--space-2); }
.pst-on { margin-top: var(--space-2); }

/* En movil el cajon se cierra arrastrandolo por el asa, asi que la X no
   aporta y quita sitio a la cabecera. En escritorio no hay gesto: ahi se
   queda, y es la unica forma visible de cerrar aparte de Escape. */
@media (max-width: 1023px) {
  .drawer .js-x { display: none; }
}

/* ---- modales ---- */
.modal { position: fixed; inset: 0; z-index: 60; display: grid; grid-template-rows: minmax(0, 1fr); place-items: center; padding: var(--space-4); }
.modal-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, .5); animation: fade var(--dur-base) var(--ease); }
.modal-card {
  position: relative; z-index: 1; width: 100%; max-width: 460px; max-height: 100%;
  display: flex; flex-direction: column;
  background: var(--bg-card); border-radius: var(--radius-lg); box-shadow: var(--shadow-soft);
  animation: popIn var(--dur-base) var(--ease-enter);
}
.modal-card.wide { max-width: 740px; }
.modal-head { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4) var(--space-4) var(--space-3); border-bottom: 1px solid var(--border-subtle); flex: 0 0 auto; }
.modal-head h2 { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
.modal-head p { margin: 2px 0 0; font-size: 12.5px; color: var(--text-muted); }
.modal-head .spacer { margin-left: auto; }
.modal-body {
  padding: var(--space-4); flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.modal-foot { display: flex; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border-subtle); background: var(--bg-card-soft); border-radius: 0 0 var(--radius-lg) var(--radius-lg); flex-wrap: wrap; flex: 0 0 auto; }
.modal-foot .spacer { margin-left: auto; }

.mail-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-4); }
.summary { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); overflow: hidden; }
.summary div { display: flex; gap: var(--space-3); padding: 8px var(--space-3); font-size: 12.5px; border-bottom: 1px solid var(--border-subtle); }
.summary div:last-child { border-bottom: 0; }
.summary strong { flex: 0 0 100px; color: var(--text-soft); font-weight: 600; }
.summary span { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

/* Cambiar el estado: cada opcion ES su pastilla, mas grande para el dedo.
   Era un rectangulo con borde alrededor de la pastilla —una forma dentro de
   otra forma— y se veia como una caja vacia con algo dentro (5-oct-2026).
   El estado actual lleva un aro del acento separado de la pastilla; pasar
   por encima la contornea en su propio color. */
.status-menu { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-3); }
.status-opt { display: inline-flex; align-items: center; padding: 0; border: 0; border-radius: 999px; background: transparent; cursor: pointer; }
.status-opt .pill { padding: 9px 16px 9px 13px; font-size: 13.5px; gap: 7px; transition: box-shadow var(--dur-fast) var(--ease); }
.status-opt .pill i { width: 7px; height: 7px; }
@media (hover: hover) { .status-opt:hover .pill { outline: 1.5px solid currentColor; outline-offset: -1.5px; } }
.status-opt:focus-visible { outline: none; }
.status-opt[aria-pressed="true"] .pill,
.status-opt:focus-visible .pill { box-shadow: var(--ring-pick); }
.status-opt:disabled { cursor: progress; opacity: .6; }

.impact { list-style: none; margin: 0 0 var(--space-4); padding: 0; border: 1px solid var(--danger); border-radius: var(--radius-md); overflow: hidden; }
.impact li { display: flex; padding: 9px var(--space-3); font-size: 13px; border-bottom: 1px solid var(--border-subtle); }
.impact li:last-child { border-bottom: 0; }
.impact li span { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }

.dev-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); }
.dev-row:last-of-type { border-bottom: 0; }
.dev-ico { width: 34px; height: 34px; border-radius: 9px; background: var(--bg-card-soft); border: 1px solid var(--border-subtle); display: grid; place-items: center; color: var(--text-muted); flex: 0 0 auto; }
.dev-ico svg { width: 17px; height: 17px; }
.dev-info { min-width: 0; flex: 1; }
.dev-info b { display: block; font-size: 13.5px; font-weight: 600; }
.dev-info span { font-size: 12px; color: var(--text-soft); font-variant-numeric: tabular-nums; }
.dev-now { font-size: 11px; font-weight: 700; color: var(--success-ink); background: var(--success-soft); padding: 2px 7px; border-radius: 5px; white-space: nowrap; }

/* ---- foto a pantalla completa ---- */
.lightbox { position: fixed; inset: 0; z-index: 70; background: rgba(15,23,42,.9); display: grid; place-items: center; padding: var(--space-4); animation: fade var(--dur-base) var(--ease); }
.lightbox img { max-width: 100%; max-height: 92dvh; border-radius: var(--radius-md); }
.lightbox .btn { position: absolute; top: var(--space-4); right: var(--space-4); }


/* ================================ TABLET ≥640 ============================ */
@media (min-width: 640px) {
  .content { padding: var(--space-5) 22px; }
  /* Tantas columnas como cifras, no siempre cuatro: Companies tiene tres y un
     cliente sin cobros ve tres en el Dashboard, y quedaba un cuarto de fila
     vacio a la derecha. */
  .kpis { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--space-3); }
  .cards { display: none; }
  .tablewrap { display: block; }
  .sk-row { height: 45px; }
  .topbar { padding: 13px 22px; flex-wrap: nowrap; }
  .topbar h1 { font-size: 19px; }
  .search { order: 0; flex: 1 1 auto; max-width: 320px; }
  /* Dos filas: los estados arriba, enteros y sin esconderse; los
     desplegables debajo. Nada de repartirse el ancho. */
  .toolbar { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .filter-scroll { overflow-x: visible; flex-wrap: wrap; }
  .toolbar-row { flex-wrap: wrap; }
  /* --dur-base y no --dur-slow: abrir una dispute es lo que mas veces se hace
     en el dia, y lo que se repite decenas de veces tiene que llegar ya. */
  .drawer { inset: 0 0 0 auto; width: 430px; max-height: none; border-radius: 0; border-left: 1px solid var(--border-subtle); animation: slideIn var(--dur-base) var(--ease-enter); }
  .drawer-grab { display: none; }
  .field-row { grid-template-columns: 1fr 1fr; }
  .mail-grid { grid-template-columns: 1.15fr 1fr; }
  .co-stores { padding-left: 62px; }
}

/* ============================== ESCRITORIO ≥1024 ========================= */
@media (min-width: 1024px) {
  .app { flex-direction: row; height: 100dvh; overflow: hidden; }
  .nav {
    position: static; flex: 0 0 var(--sidebar-w); width: var(--sidebar-w);
    left: auto; visibility: visible; transition: none; overflow: visible;
    /* El panel lateral se separa por SUPERFICIE (--bg-card sobre --bg-page) y
       una sombra a su derecha, no por un trazo. Un borde vertical de 1px a lo
       alto de la pantalla es la linea mas larga de la interfaz y la que menos
       dice. */
    box-shadow: var(--shadow-rail);
    padding-bottom: 0;
  }
  .nav-toggle, .nav-scrim { display: none; }

  .main { flex: 1 1 auto; min-width: 0; overflow: hidden; }
  .topbar { min-height: var(--shell-head-h); padding: var(--space-3) var(--space-6); position: static; }
  .topbar h1 { font-size: 21px; letter-spacing: -.02em; }
  .topbar-sub { display: block; font-size: 12.5px; color: var(--text-soft); margin-top: 1px; }
  .search { max-width: 300px; }
  /* El circulo de arriba sobra en escritorio: la cuenta ya esta abajo a la
     izquierda. Esta regla decia `.avatar` a secas y se llevaba tambien el de
     la barra lateral, que es justo el que va con el nombre: quedaba un nombre
     suelto, sin cara, en una app que trata con personas. */
  .topbar .avatar { display: none; }
  .nav-user .avatar { width: 32px; height: 32px; cursor: inherit; }
  /* 24px entre la barra de titulo y el primer bloque, no 22.
     El 22 no era ningun paso de la escala: era un numero. Y el hueco tenia
     que ser el de "entre secciones de una vista" (--space-6), porque eso es
     exactamente lo que separa aqui — el titulo de la vista de su contenido.
     Ver §3 quater, S1 y S3. */
  .content { padding: var(--space-6); overflow: auto; overscroll-behavior: contain; }
  .kpi { padding: var(--space-4); }
  .kpi-value { font-size: 27px; }
  .kpi.is-loading .sk-value { height: 33px; }
  table.data th { position: sticky; top: 0; z-index: 1; }
  .drawer { width: 470px; }
  /* El panel de accesos lleva una company con todas sus stores dentro; en
     470px los chips de cada store se partian en dos lineas y el bloque de
     la company se veia igual que una store mas. */
  .drawer-wide { width: min(680px, 92vw); }
  .two-col { display: grid; grid-template-columns: minmax(0,1.3fr) minmax(340px,1fr); gap: var(--space-3); align-items: start; }
  .two-col .panel + .panel { margin-top: 0; }
  /* El mes ocupa las dos filas de la izquierda y las tarjetas de la derecha
     marcan el alto: la segunda fila (1fr) se lleva lo que sobre, asi que las
     tres acaban en la misma linea sea cual sea el dato. */
  .dash-grid { grid-template-rows: auto 1fr; align-items: stretch; }
  .dash-grid > .panel-month { grid-row: 1 / span 2; }
}
/* Customer y Cause desde 1280px, no desde 1024: entre 1024 y 1279 la tabla
   de Disputes media 923px en un hueco de 708 y se desplazaba de lado 215px.
   Sin esas dos columnas cabe entera; en el cajon siguen estando. */
@media (min-width: 1280px) {
  .col-cust, .col-cause { display: table-cell; }
}
/* Hasta 1440px la plataforma va bajo el numero de orden y no a su lado: al
   lado ensanchaba la columna Order de 153 a 227px sin anadir nada. Con
   Customer y Cause visibles, a 1280 esos 74px sacaban la tabla 9px de su
   panel (973 en 964) con los numeros de GrubHub de quince cifras. */
@media (max-width: 1439px) {
  table.data td .plat { display: block; width: max-content; margin: 3px 0 0; }
}
/* Tablet: By status y Top Disputes lado a lado bajo el mes, a la misma altura. */
@media (min-width: 640px) and (max-width: 1023px) {
  .dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); align-items: stretch; }
  .dash-grid > .panel-month { grid-column: 1 / -1; }
  .dash-grid .panel + .panel { margin-top: 0; }
}
/* Las tarjetas de la rejilla se estiran: su cuerpo tambien, para que lo que
   crece (la grafica, el donut) crezca dentro y no deje un hueco al pie. */
.panel-month, .panel-causes { display: flex; flex-direction: column; }
.panel-month > .panel-body, .panel-causes > .panel-body { flex: 1 1 auto; display: flex; flex-direction: column; }
.panel-month .mchart { flex: 1 1 auto; grid-template-rows: minmax(210px, 1fr) auto; }
.panel-causes .donut-row { flex: 1 1 auto; flex-wrap: nowrap; gap: var(--space-4); }
.panel-causes .donut-wrap { width: 132px; height: 132px; }
.panel-causes .donut-legend { flex: 1 1 auto; min-width: 0; }
.panel-causes .donut-center b { font-size: 19px; }
/* En una tarjeta estrecha (tablet, movil) el donut cede 20px a la leyenda:
   con 132px, "Not classified" y "Incorrect Items" partian en dos lineas de
   70px al lado de sus cifras. Lo decide el ancho de la tarjeta, no el de la
   ventana: a 1024px la tarjeta tambien es estrecha. */
.panel-causes { container-type: inline-size; }
@container (max-width: 380px) {
  .panel-causes .donut-row { gap: var(--space-3); }
  .panel-causes .donut-wrap { width: 112px; height: 112px; }
  .panel-causes .donut-center b { font-size: 17px; }
}
@media (max-width: 639px) { .panel-month .mchart { grid-template-rows: minmax(180px, 1fr) auto; } }

/* Quien pide menos movimiento no quiere que la interfaz se quede sin
   estados: quiere que no se desplace. Se dejan en un instante en vez de
   quitarlas, que es lo recomendado, porque asi el estado final sigue siendo
   el que pinta la animacion y nada se queda a medias. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- avisos (toasts) -------------------------------------------------
   notifications.js pinta .toast-container y .toast desde hace tiempo, pero
   sus estilos vivian en admin-core.css. Al dejar de cargarla, cada aviso
   —"Dispute created", "Email sent", y tambien los errores— se disparaba
   invisible: la app respondia y no se veia. Aqui vuelven, con los tokens
   de ahora. */
/* ARRIBA Y AL CENTRO.
   Estaba arriba a la derecha, que es justo donde no esta la vista despues de
   pulsar un boton —esos viven al centro o a la izquierda de su tarjeta—. El
   centro es el unico sitio al que se llega sin buscar. */
.toast-container {
  position: fixed; top: var(--space-5); left: 50%; transform: translateX(-50%);
  z-index: 10001;
  display: flex; flex-direction: column; align-items: center; gap: var(--space-2);
  width: min(440px, calc(100vw - var(--space-8)));
  pointer-events: none;
}

/* EL COLOR VA EN LA SUPERFICIE, no solo en el simbolo.
   El aviso era blanco sobre paneles blancos: lo unico que lo separaba del
   fondo era un borde de un pixel, asi que aparecia y desaparecia sin que
   nadie lo viera. Ahora el tono de fondo y la barra lateral dicen que fue —
   los mismos colores con los que ya se dice todo lo demas en esta app
   (§3, el color dice consecuencia).
   El texto se queda en --text-main: sobre los tonos -soft pasa de 14:1, y
   teñir tambien la letra es lo que hace ilegible un aviso. */
.toast {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%;
  background: var(--bg-card); color: var(--text-main);
  /* SIN FRANJA DE ACENTO (regla A1). El color va en la superficie entera y en
     el simbolo; el borde se tiñe del mismo tono y separa del fondo sin
     convertir el aviso en una pestaña con lengüeta. */
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md); padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-soft); pointer-events: auto;
  min-height: var(--tap-h); font-size: 13.5px; line-height: 1.45;
  animation: toastIn var(--dur-base) var(--ease-enter);
  /* Se arrastra para quitarlo. `pan-y` le deja el gesto vertical al navegador
     y se queda con el horizontal: sin esto, arrastrar de lado tambien hace
     rodar la lista de detras. */
  touch-action: pan-y; user-select: none;
}
/* Mientras el dedo o el raton lo llevan no hay transicion —el aviso va pegado
   al puntero— y al soltar sin llegar al umbral vuelve a su sitio. */
.toast.is-drag { transition: none; cursor: grabbing; }
.toast.vuelve  { transition: transform var(--dur-base) var(--ease); }
/* El tinte va SOBRE una base opaca, no en lugar de ella.
   Los tokens -soft son rgba con un 10% de alfa: puestos como unico fondo, el
   aviso se transparenta y se lee el texto de la pagina por debajo. Dos capas
   —el tinte encima de --bg-card— dan el mismo color y tapan. */
.toast.success { background: linear-gradient(var(--success-soft), var(--success-soft)), var(--bg-card); border-color: var(--success); }
.toast.error   { background: linear-gradient(var(--danger-soft),  var(--danger-soft)),  var(--bg-card); border-color: var(--danger); }
.toast.info    { background: linear-gradient(var(--accent-soft),  var(--accent-soft)),  var(--bg-card); border-color: var(--accent); }

/* El icono es un SVG del sistema, no el caracter '✓' de la tipografia de cada
   equipo: asi la marca es la misma en Windows, en un Mac y en un telefono. */
.toast-ico { flex: 0 0 auto; width: 18px; height: 18px; display: block; }
.toast-ico svg { width: 100%; height: 100%; display: block; }
.toast.success .toast-ico { color: var(--success-ink); }
.toast.error   .toast-ico { color: var(--danger-ink); }
.toast.info    .toast-ico { color: var(--accent-ink); }

.toast-message { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* El contador del aviso repetido. No es decoracion: dice que la accion ocurrio
   mas de una vez, que es informacion distinta de que ocurrio. */
.toast-n {
  flex: 0 0 auto; font-size: 11.5px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--text-muted);
  background: var(--bg-card); border-radius: 99px; padding: 1px 7px;
}

.toast-close {
  background: none; border: 0; cursor: pointer; color: var(--text-soft);
  padding: 4px; line-height: 1; flex-shrink: 0; width: 24px; height: 24px;
  display: grid; place-items: center;
  transition: color var(--dur-fast) var(--ease);
}
.toast-close svg { width: 14px; height: 14px; display: block; }
/* Undo (y cualquier accion de un aviso): texto en el color de la accion, con
   la altura tactil entera. Va antes de la X y no se arrastra con el aviso. */
.toast-act {
  flex: 0 0 auto; min-height: 32px; padding: 0 var(--space-2); margin-right: calc(var(--space-1) * -1);
  border: 0; background: none; cursor: pointer; border-radius: var(--radius-md);
  color: var(--accent-ink); font: inherit; font-weight: 700;
}
@media (hover: hover) and (pointer: fine) { .toast-act:hover { background: var(--accent-soft); } }
@media (pointer: coarse) { .toast-act { min-height: var(--tap-h); } }
.toast-close:hover { color: var(--text-main); }

/* La SALIDA es mas lenta que la entrada, a proposito: entrar tiene que llamar
   la atencion y salir tiene que dar tiempo a terminar la frase. Y se va hacia
   arriba, de donde vino. */
@keyframes toastIn { from { transform: translateY(-12px); opacity: 0 } }
.toast.slide-out { animation: toastOut var(--dur-slow) var(--ease) forwards; }
@keyframes toastOut { to { transform: translateY(-12px); opacity: 0 } }

/* Con el raton encima el reloj se para (lo hace notifications.js). Aqui solo
   se dice que se esta mirando. */
.toast-container.is-held .toast { box-shadow: var(--shadow-soft), 0 0 0 1px var(--border-control); }

@media (max-width: 639px) {
  .toast-container {
    left: var(--space-3); right: var(--space-3); transform: none;
    top: calc(var(--space-3) + env(safe-area-inset-top));
    width: auto;
  }
}

/* ---- barra de progreso para lo que tarda -----------------------------
   Subir una foto y mandar un correo pasan por la red y por un relay SMTP:
   pueden tardar segundos. Sin senal, parece que no paso nada y se vuelve a
   pulsar. */
.progress {
  position: fixed; left: 0; right: 0; top: 0; z-index: 10000;
  height: 3px; background: var(--accent-soft);
}
.progress span {
  display: block; height: 100%; width: 30%; background: var(--accent);
  animation: progressSlide 1.1s var(--ease) infinite;
}
@keyframes progressSlide {
  from { transform: translateX(-100%) }
  to   { transform: translateX(400%) }
}

/* ==========================================================================
   AJUSTES — secciones (docs/DESIGN-RULES.md, "Ajustes")
   --------------------------------------------------------------------------
   Cada pestaña es una lista de secciones. Una seccion dice que es y donde se
   nota —su titulo y una o dos frases— y al lado lleva su formulario, en una
   tarjeta con su propio boton de guardar al pie.

   Antes cada tarjeta ocupaba los 966px del contenido, abria con una caja azul
   explicandose y dejaba dos campos cortos a la izquierda y 500px vacios a la
   derecha. Con la explicacion fuera de la tarjeta, la tarjeta solo lleva
   campos y mide lo que ellos piden (PR-1, PR-7).

   A partir de 1200px, titulo a la izquierda y tarjeta a la derecha; por
   debajo, uno encima del otro. Entre secciones --space-6 y una linea: son
   temas distintos (S2, S3).
   ========================================================================== */
.set-sec { padding-block: var(--space-6); border-top: 1px solid var(--hairline); }
.set-sec:first-of-type { border-top: 0; padding-top: var(--space-2); }
.set-sec-head h2 { margin: 0; font-size: 15px; font-weight: 700; color: var(--text-main); }
.set-sec-head p { margin: var(--space-1) 0 0; font-size: 13px; line-height: 1.5; color: var(--text-muted); max-width: 62ch; }
.set-sec-head p + p { margin-top: var(--space-2); }
.set-sec-head b { font-weight: 600; color: var(--text-main); }
.set-sec > .set-card { margin-top: var(--space-3); max-width: 760px; }
@media (min-width: 1200px) {
  .set-sec { display: grid; grid-template-columns: 232px minmax(0, 760px); gap: var(--space-6); align-items: start; }
  .set-sec > .set-card { margin-top: 0; }
}

/* Un formulario de una columna no pasa de 640px (PR-7). Una fila cuyo
   ultimo campo es corto (.input--txt) marca su ancho y el largo se queda
   con el resto, asi los dos acaban donde acaban los campos de abajo. */
.set-card .panel-body > .field,
.set-card .panel-body > .field-row { max-width: 640px; }
@media (min-width: 640px) {
  .set-card .field-row:has(> .field:last-child .input--txt) { grid-template-columns: minmax(0, 1fr) auto; }
}

.set-add { margin-top: var(--space-3); }


/* Cifras cortas en fila (dias, importes): cada una en su columna de 168px
   con su unidad al lado, no una por linea ni repartidas por 700px. */
.set-nums { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: var(--space-4); max-width: 640px; }
.set-nums .field { margin-bottom: 0; }
.set-nums .hint { margin-top: var(--space-1); }
/* Que paneles del Dashboard ve cada rol: dos columnas -admin, manager- que
   se apilan en movil, igual que .set-nums pero con checkboxes en vez de
   numeros. */
.dash-panels-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-5); max-width: 640px; }
.dash-panels-grid .field-inline { margin-bottom: var(--space-3); }
.input-unit { display: inline-flex; align-items: center; gap: var(--space-2); }
.input-unit > span { font-size: 13px; color: var(--text-muted); }

/* Una seccion que no guarda nada aqui, solo lleva a otra pantalla. */
.set-link { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); }
.set-link p { margin: 0; flex: 1 1 260px; font-size: 13px; color: var(--text-muted); }


/* La fila del Dashboard en el telefono: el chip Filters y, al lado, lo que
   se esta mirando ("Example Diner · Sep 1 → Oct 4", FB6). */
.dash-chips { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.dash-sum { min-width: 0; font-size: 13px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- filtros -----------------------------------------------------------
   Los mismos campos en dos sitios: en linea cuando hay ancho, y dentro de una
   hoja en el telefono, donde no caben al lado de los chips. */
.filter-fields { display: none; gap: var(--space-2); flex-wrap: wrap; }
.filter-fields .select, .filter-fields .input { height: var(--control-h-sm); width: auto; font-size: 13px; }
.filter-fields .date-f { max-width: 148px; }
.filters-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; margin-left: 6px;
  border-radius: 999px; background: var(--accent); color: var(--on-accent, var(--bg-card));
  font-size: .72rem; font-weight: 700;
}
.filter-sheet { display: flex; flex-direction: column; gap: var(--space-3); }
.filter-sheet .select, .filter-sheet .input { width: 100%; height: var(--control-h); }

/* La plataforma, pegada al numero de pedido: es lo que lo identifica. */
.plat {
  display: inline-block; margin-left: 7px; padding: 1px 6px;
  border-radius: 5px; background: var(--bg-card-soft); border: 1px solid var(--border-subtle);
  color: var(--text-muted); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em; vertical-align: 1px;
}

@media (min-width: 640px) {
  .tab-filters, .dash-chips { display: none !important; }
  .filter-fields { display: flex; }
}

/* ---- accesos ----------------------------------------------------------
   Tres granos: un preset para toda la company, uno por store, y las casillas
   sueltas escondidas tras "Details" para el caso raro. La matriz de antes
   obligaba a leer una tabla de seis columnas para entender algo. */
.perm-co { border: 1px solid var(--border-subtle); border-radius: var(--radius-md); margin-bottom: var(--space-3); overflow: hidden; }
.perm-co-head {
  display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4); background: var(--bg-card-soft);
  border-bottom: 1px solid var(--border-subtle);
}
.perm-co-head h3 { margin: 0; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.perm-co-head::before {
  content: "Company"; font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-soft);
  position: absolute; top: 6px; left: var(--space-4);
}
.perm-co-head { position: relative; padding-top: 22px; }
/* UN ATAJO, NO UN TITULO.
   Iba en azul, mayusculas y 700 sobre una franja teñida, asi que pesaba mas
   que el nombre de la company que tiene justo encima y se leia como el titulo
   de la seccion. Y no lo es: es un atajo para poner el mismo rol en todas de
   una vez, que es lo que menos se usa de esta caja. Ahora se lee como lo que
   es —una etiqueta apagada al lado de sus botones— y el color se lo queda el
   nombre de la company. */
.perm-all {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-2) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--hairline);
}
.perm-all-label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
/* Las stores van sangradas y con una linea a la izquierda: el sangrado es
   lo que dice "esto cuelga de la company de arriba". Sin el, la fila de la
   company y las de sus stores se leen como la misma lista y parece que
   solo se puede dar acceso por company. */
/* Lo que separa "toda la company" de "store por store". Es un boton, no un
   rotulo: las stores no se despliegan solas, porque una company con diez
   abiertas de golpe son cincuenta chips antes de haber decidido nada. */
.perm-sub, .co-toggle {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  margin: var(--space-3) 0 0; padding: var(--space-2) var(--space-4);
  background: transparent; border: 0; cursor: pointer; text-align: left;
  font: inherit; font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-soft);
  transition: color var(--dur-fast) var(--ease);
}
.co-toggle { margin: 0 0 var(--space-2); padding-left: 0; padding-right: 0; }
.perm-sub:hover, .co-toggle:hover { color: var(--accent-ink); }
.perm-sub:focus-visible, .co-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-md); }
/* La flecha dice si esta abierto sin tener que leer nada. */
.perm-sub::after, .co-toggle::after {
  content: ""; margin-left: auto; flex: 0 0 auto;
  width: 7px; height: 7px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform var(--dur-base) var(--ease);
}
.perm-sub[aria-expanded="true"]::after, .co-toggle[aria-expanded="true"]::after { transform: rotate(45deg); }
.perm-sub .t-muted, .co-toggle .t-muted { font-variant-numeric: tabular-nums; }

.perm-store {
  padding: var(--space-3) var(--space-4) var(--space-3) var(--space-6);
  border-bottom: 1px solid var(--border-subtle);
  position: relative;
}
.perm-store::before {
  content: ""; position: absolute; left: var(--space-4); top: 0; bottom: 0;
  width: 2px; background: var(--border-subtle); border-radius: 2px;
}
.perm-store:hover::before { background: var(--accent); }
.perm-store:last-child { border-bottom: 0; }
.perm-store-head { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); flex-wrap: wrap; }
.perm-store-head b { font-size: 13.5px; font-weight: 600; }
.perm-detail { margin-left: auto; }
.perm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-2); margin-top: var(--space-3); }
.perm-check {
  display: flex; align-items: center; gap: var(--space-2); cursor: pointer;
  padding: 7px 9px; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm, 8px);
  font-size: 12.5px; background: var(--bg-card-soft);
}
.perm-check input { width: 16px; height: 16px; accent-color: var(--accent); flex: 0 0 auto; }
.perm-check.is-locked { opacity: .5; cursor: not-allowed; }
.perm-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-3); }


/* El rol vive con las acciones de cuenta, no con los permisos: cambiarlo
   cambia por donde se otorga el acceso, no solo cuanto. */
/* Quien es la cuenta: nombre, correo y el boton que los cambia. No es una
   "zona" como Disable o Delete —no avisa de nada, solo edita— asi que va sin
   caja: una fila, el dato a la izquierda y la accion a la derecha. */
.perm-id { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); }
.perm-id > div { flex: 1 1 200px; min-width: 0; }
.perm-id b { display: block; font-size: 14px; font-weight: 700; }
.perm-id span { display: block; font-size: 12.5px; color: var(--text-muted); overflow-wrap: anywhere; }
.perm-id .btn { flex: 0 0 auto; }

.perm-role { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-2); }
.perm-role label { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.perm-role .select { width: auto; height: var(--control-h-sm); }

/* El alias y su boton de guardar, en una linea. El campo se lleva el hueco
   que sobre; el boton mide lo que mide. */
.alias-row { display: flex; gap: var(--space-2); align-items: center; }
.alias-row .input { flex: 1 1 auto; min-width: 0; }
.alias-row .btn { flex: 0 0 auto; }

/* ---- editor de la portada ----------------------------------------
   El lienzo es la pagina de verdad dentro de un iframe: mismo HTML, misma
   hoja de estilo. Los controles van FUERA —una barra arriba y otra
   flotante sobre el bloque elegido— porque meter formularios entre los
   bloques es lo que hacia que el editor no se pareciera a la pagina. */

.hm-bar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-3) 0 var(--space-3);
  background: var(--bg-page);
}
.hm-bar .spacer { margin-left: auto; }
.hm-bar-t { display: flex; align-items: baseline; gap: var(--space-2); }
.hm-bar-t b { font-size: 15px; font-weight: 700; }
.hm-bar-t span { font-size: 12.5px; }

/* Escritorio o telefono: la misma pagina, otro ancho. */
.hm-view { display: inline-flex; background: var(--bg-card-soft); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 2px; }
.hm-view button {
  border: 0; background: transparent; cursor: pointer;
  padding: 0 var(--space-3); height: 30px; border-radius: 8px;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-muted);
}
.hm-view button.is-on { background: var(--bg-card); color: var(--text-main); box-shadow: var(--shadow-subtle); }
.hm-view button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.hm-stage { background: var(--bg-card-soft); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: var(--space-4); }
.hm-frame { position: relative; margin: 0 auto; max-width: 100%; transition: max-width var(--dur-base) var(--ease); }
.hm-frame.phone { max-width: 390px; }
.hm-frame iframe {
  display: block; width: 100%; border: 0; background: var(--bg-card);
  border-radius: var(--radius-md); box-shadow: var(--shadow-subtle);
}

/* La barra del bloque elegido. Va pegada por encima de el y no se sale
   del lienzo aunque el bloque este arriba del todo. */
.hm-tools {
  position: absolute; left: 0; right: 0; z-index: 2;
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-2); border-radius: var(--radius-md);
  background: var(--bg-card); border: 1px solid var(--accent);
  box-shadow: var(--shadow-soft);
}
.hm-tools-k { font-size: 12.5px; font-weight: 700; padding-left: var(--space-2); white-space: nowrap; }
.hm-tools-hint { flex-basis: 100%; padding: 0 var(--space-2) var(--space-1); font-size: 12px; line-height: 1.4; color: var(--text-muted); }
.hm-tools [data-t="up"] svg { transform: rotate(180deg); }
.select-xs { height: 30px; font-size: 12px; padding: 0 26px 0 var(--space-2); background-position: right 7px center; width: auto; }

/* La paleta. Los nombres, nada mas: la descripcion va en el title y en el
   cajon al abrirlo. Antes cada ficha llevaba dos parrafos y ocupaba mas
   que la propia pagina. */
.hm-pal { margin-top: var(--space-5); }
.hm-pal-t { font-size: 13px; color: var(--text-muted); margin: 0 0 var(--space-3); }
.hb-group { font-size: 11.5px; font-weight: 700; color: var(--text-soft); text-transform: uppercase; letter-spacing: .06em; margin: var(--space-4) 0 var(--space-2); }
.hb-group:first-of-type { margin-top: 0; }
.hm-add { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: var(--space-2); }
.hm-new {
  cursor: grab; padding: var(--space-2); text-align: left;
  display: flex; flex-direction: column; gap: var(--space-2);
  background: var(--bg-card); border: 1px solid var(--border-control);
  border-radius: var(--radius-md);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.hm-new b { font-size: 12.5px; font-weight: 600; }

/* La miniatura: no es un icono, es la forma que va a tener el bloque en la
   pagina. Con solo el nombre hay que anadirlo para saber que es. */
.hm-mini {
  display: flex; flex-direction: column; gap: 3px; justify-content: center;
  height: 52px; padding: var(--space-2);
  background: var(--bg-card-soft); border-radius: 7px; overflow: hidden;
}
.hm-mini i, .hm-mini b, .hm-mini u {
  display: block; height: 4px; border-radius: 2px;
  background: var(--border-control); opacity: .55; text-decoration: none;
}
.hm-mini .t { height: 7px; opacity: .8; }
.hm-mini .c { margin-left: auto; margin-right: auto; }
.hm-mini .w20 { width: 20%; } .hm-mini .w30 { width: 30%; } .hm-mini .w40 { width: 40%; }
.hm-mini .w45 { width: 45%; } .hm-mini .w50 { width: 50%; } .hm-mini .w55 { width: 55%; }
.hm-mini .w60 { width: 60%; } .hm-mini .w65 { width: 65%; } .hm-mini .w70 { width: 70%; }
.hm-mini .w80 { width: 80%; } .hm-mini .w85 { width: 85%; } .hm-mini .w90 { width: 90%; }
/* El boton, el unico trozo con el color de accion: en la pagina tambien es
   lo unico que lo lleva. */
.hm-mini .b { width: 34%; height: 9px; border-radius: 99px; background: var(--accent); opacity: .75; }
.hm-mini .row { display: flex; align-items: center; gap: 5px; height: auto; background: none; opacity: 1; }
.hm-mini .row u { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; opacity: .7; }
.hm-mini .row u.num { border-radius: 3px; background: var(--accent); opacity: .6; }
.hm-mini .cols { display: flex; gap: 5px; height: auto; background: none; opacity: 1; }
.hm-mini .cols .n { flex: 1; height: 20px; border-radius: 3px; }
.hm-mini .q { height: 10px; opacity: .45; }
.hm-mini .img { height: 30px; border-radius: 4px; opacity: .45; }
.hm-mini .side { display: flex; gap: 5px; height: auto; background: none; opacity: 1; }
.hm-mini .side .half { width: 45%; height: 30px; }
.hm-mini .side .halfx { flex: 1; height: 30px; opacity: .3; border-radius: 3px; }
.hm-mini .hr { height: 2px; opacity: .8; }
/* Los campos del formulario: cajas, no lineas. Es lo que lo distingue de un
   bloque de texto de un vistazo. */
.hm-mini .box { height: 10px; border-radius: 3px; opacity: .4; }
.hm-mini .cols .box { flex: 1; }
.hm-new:hover { border-color: var(--accent); background: var(--accent-soft); }
.hm-new:active { cursor: grabbing; }
.hm-new:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Las secciones que pueden salir en el menu. Cada una tiene su interruptor,
   direccion estable y etiqueta publica: la direccion sigue sirviendo aunque
   la seccion no se muestre temporalmente en la barra. */
.hm-secs { list-style: none; margin: 0 0 var(--space-5); padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.hm-secs li { display: grid; grid-template-columns: minmax(0, .85fr) minmax(110px, .65fr) minmax(0, 1fr); gap: var(--space-2); align-items: end; }
.hm-secs .check { margin: 0; }
.hm-sec-k { font-size: 13px; font-weight: 600; }
.hm-secs .field { margin: 0; }
.hm-secs .field > label { font-size: 11px; margin-bottom: 3px; }
.hm-secs .input { height: var(--control-h-sm); font-size: 13px; }
.hm-secs .input:disabled { opacity: .5; cursor: not-allowed; }
/* La fila de filtro de una lista, cuando el modulo tiene uno solo y no se
   gana una fila de pestañas. Va encima del contador, con el mismo aire. */
.list-filter { margin: 0 0 var(--space-3); }
.list-filter .check { margin: 0; font-size: 13px; }

/* El contador de la lista (contrato de modulo, M1). Va justo encima de la
   lista, en los tres modulos y con la misma pinta: si Disputes lo enseña de
   una forma y Users de otra, deja de ser el mismo dato. */
.list-count {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: 12.5px; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.list-count b { color: var(--text-main); font-weight: 700; }
/* Quitar los filtros es una accion reversible y de apoyo: enlace, no boton.
   Un boton aqui competiria con el primario de crear, que es el que manda en
   esta pantalla (regla B1). */
.list-clear {
  border: 0; background: none; padding: 0; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 600; color: var(--accent-ink);
  text-decoration: underline; text-underline-offset: 2px;
}
.list-clear:hover { color: var(--accent); }
.list-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-md); }

/* Campo con X para vaciarlo. El aspa es icon('close'), la misma con la que se
   cierran cajones y modales: dos dibujos distintos para "quitar esto" son dos
   cosas que aprender.

   El cuadro visible mide --control-h-sm, pero el AREA DE TOQUE se estira con
   padding hasta el alto entero del campo. La regla de los 44px tactiles no se
   puede cumplir a la letra dentro de un control de 40, y la salida no es
   encoger el objetivo sino separarlo: la X nunca es el unico camino —siempre
   se puede seleccionar y borrar— asi que es un atajo, no una funcion. */
.input-clear { position: relative; display: block; }
.input-clear > .input { padding-right: 38px; }
.input-x {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: var(--control-h-sm); height: var(--control-h-sm);
  display: grid; place-items: center;
  border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--text-muted); border-radius: var(--radius-md);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.input-x svg { width: 15px; height: 15px; }
.input-x:hover { color: var(--text-main); background: var(--bg-card-soft); }
.input-x:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; color: var(--text-main); }

.input-prefix { display: flex; align-items: center; border: 1px solid var(--border-control); border-radius: var(--radius-sm); background: var(--bg-card); overflow: hidden; }
.input-prefix span { padding: 0 0 0 9px; color: var(--text-soft); font-size: 13px; }
.input-prefix .input { min-width: 0; border: 0; border-radius: 0; padding-left: 3px; background: transparent; }
.input-prefix .input:focus { box-shadow: none; }

@media (max-width: 620px) { .hm-secs li { grid-template-columns: 1fr; align-items: stretch; } }

.hm-img { display: flex; gap: var(--space-3); align-items: flex-start; }
.hm-img img { width: 120px; height: 90px; object-fit: cover; border-radius: var(--radius-md); border: 1px solid var(--border-subtle); flex: 0 0 auto; }
.hm-img-none { width: 120px; height: 90px; flex: 0 0 auto; display: grid; place-items: center; border: 1.5px dashed var(--border-control); border-radius: var(--radius-md); background: var(--bg-card-soft); color: var(--text-soft); }
.hm-img-none svg { width: 24px; height: 24px; }
.hm-img-do { min-width: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: flex-start; }

@media (max-width: 639px) {
  .hm-stage { padding: var(--space-2); }
  .hm-bar { gap: var(--space-2) var(--space-2); }
  .hm-img { flex-direction: column; }
  .hm-img img, .hm-img-none { width: 100%; height: 140px; }
}
/* "Ver" no es un permiso mas: es la puerta. Se pinta aparte y, cuando otra
   casilla la enciende sola, se marca como concedida en vez de bloqueada a
   secas — la diferencia entre "no puedes" y "ya la tienes". */
.perm-check.perm-view { grid-column: 1 / -1; background: var(--bg-card); }
.perm-check.is-implied { opacity: 1; background: var(--accent-soft); border-color: transparent; color: var(--accent-ink); cursor: default; }
.perm-check.is-implied input { opacity: .75; }
.perm-check.is-implied span::after { content: ' · granted automatically'; font-weight: 600; opacity: .8; }

/* ---- quien alcanza una company --------------------------------------
   La lista de stores dice que hay; esto dice quien entra. */
.co-access { margin-top: var(--space-4); padding-top: var(--space-3); border-top: 1px solid var(--border-subtle); }
.co-access h4 { margin: 0 0 var(--space-2); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--text-soft); }
.acc-list { display: flex; flex-direction: column; gap: 6px; }
/* REJILLA, no flex, en cuanto hay sitio.
   Con flex cada fila se rompia donde le tocaba segun lo largo que fuera el
   nombre: uno de veinte letras empujaba su Remove a una segunda linea y las
   tres filas dejaban de compartir bordes. Una rejilla les da las mismas
   columnas a todas —nombre, rol, alcance, acciones— y el nombre es el unico
   que se estira. */
.acc-row { display: flex; align-items: center; gap: var(--space-2); font-size: 13px; flex-wrap: wrap; }
@media (min-width: 640px) {
  .acc-row {
    display: grid; align-items: center;
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    column-gap: var(--space-3);
  }
  /* El panel de stores se sale de la rejilla y ocupa el ancho entero. */
  .acc-row .acc-stores { grid-column: 1 / -1; }
  .acc-row.is-admin { grid-template-columns: minmax(0, 1fr) auto auto; }
}
.acc-row .acc-name { min-width: 0; overflow-wrap: anywhere; }
/* En el telefono la fila se parte y "Remove" caia justo al lado del alcance,
   asi que lo destructivo quedaba pegado a un dato — regla B2. Se va al borde
   opuesto: el hueco del medio es la separacion. */
@media (max-width: 639px) {
  .acc-row .acc-out { margin-left: auto; }
}
.acc-name { font-weight: 600; }
.acc-reach { margin-left: auto; font-size: 12px; color: var(--text-muted); }

/* LO DECIDE EL ANCHO DE LA LISTA, NO EL DE LA VENTANA.
   La rejilla de cuatro columnas de arriba se encendia a partir de 640px de
   VENTANA, pero esta lista vive en un cajon de 400px: a 1280 el nombre se
   quedaba con lo que dejaban el rol, "All 5 stores, and any new one" y
   Remove — doce pixeles — y un nombre de catorce letras se leia letra a
   letra, de arriba abajo (16-sep-2026).
   En una lista estrecha la fila va en dos lineas: quien es y Quitar arriba,
   su rol y su alcance debajo. */
.acc-list { container: acceso / inline-size; }
@container acceso (max-width: 560px) {
  .acc-row {
    display: grid; align-items: center;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "name name out" "role scope scope";
    column-gap: var(--space-3); row-gap: var(--space-1);
  }
  .acc-row > .acc-name { grid-area: name; }
  .acc-row > .field-inline, .acc-row > .badge { grid-area: role; justify-self: start; }
  .acc-row > .acc-scope, .acc-row > .acc-reach { grid-area: scope; justify-self: start; margin-left: 0; min-width: 0; text-align: left; }
  .acc-row > .acc-out { grid-area: out; margin-left: 0; }
  .acc-row > .acc-stores { grid-column: 1 / -1; }
  /* Un admin no tiene nada que cambiar: nombre, insignia y alcance caben en
     una linea y ahi se quedan. */
  .acc-row.is-admin { display: flex; }
  .acc-row.is-admin > .acc-name { flex: 1 1 auto; }
  .acc-row.is-admin > .acc-reach { margin-left: 0; }
  /* Con .acc-list delante: las reglas de 1024px mas abajo tienen el mismo
     peso y, por venir despues, ponian tres columnas de 110px en el cajon. */
  .acc-list .acc-store-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container acceso (max-width: 340px) {
  .acc-list .acc-store-grid { grid-template-columns: minmax(0, 1fr); }
}
.acc-invite-row { margin-top: var(--space-3); }

/* ---- invitar a alguien (invite.js) ---- */
/* "Where they work" es la mitad del formulario, no un campo mas: una linea
   y su titulo lo separan de quien es la persona (S2). */
.inv-where { margin-top: var(--space-5); padding-top: var(--space-4); border-top: 1px solid var(--hairline); }
.inv-where .sectitle { margin-bottom: var(--space-3); }
/* Un valor que no se elige porque solo puede ser ese (FL5): se lee como un
   dato, no como un campo desactivado. */
.inv-fixed { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-1) 0 0; font-size: 14px; font-weight: 600; color: var(--text-main); }
.inv-stores { margin-top: var(--space-2); }
.inv-all { margin-left: var(--space-2); font-size: 12px; }
.st-acts .btn { flex: 0 0 auto; }
.js-del-store { color: var(--danger); }
.js-del-store:hover { background: var(--danger-soft); }

/* ---- metricas de una persona --------------------------------------- */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: var(--space-2); margin-bottom: var(--space-3); }
.metric { background: var(--bg-card-soft); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); }
.metric b { display: block; font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; }
.metric span { font-size: 11px; color: var(--text-soft); text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.metric.is-zero { opacity: .55; }

/* ---- foto: el rectangulo es el boton ---------------------------------
   Antes habia un modal en medio que solo servia para pedir otro clic. El
   propio rectangulo es un <label> atado al input, asi que un toque abre la
   camara; y en escritorio acepta que le suelten el archivo encima. */
.photo-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; text-align: center; cursor: pointer;
  padding: 30px var(--space-4); margin-bottom: var(--space-4);
  border: 1.5px dashed var(--border-control); border-radius: var(--radius-md);
  background: var(--bg-card-soft);
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.photo-drop:hover { border-color: var(--accent); background: var(--accent-soft); }
.photo-drop.is-over { border-color: var(--accent); background: var(--accent-soft); border-style: solid; }
.photo-drop svg { width: 28px; height: 28px; color: var(--text-soft); }
.photo-drop b { font-size: 13.5px; font-weight: 600; }
.photo-drop span { font-size: 12px; color: var(--text-soft); }

/* Lo que se ve tras elegir: la foto, no su nombre. */
.photo-preview { margin-bottom: var(--space-4); }
.photo-preview img {
  display: block; width: 100%; max-height: 260px; object-fit: cover;
  border-radius: var(--radius-md); border: 1px solid var(--border-subtle);
}
.photo-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-2) 0 var(--space-4); }
.photo-actions .btn { cursor: pointer; }
.photo-preview .photo-actions { margin-bottom: 0; }

/* ---- rango de fechas -------------------------------------------------
   Dos campos sueltos uno al lado del otro no dicen que sean un rango: podian
   ser dos filtros distintos. Van juntos, etiquetados y con la flecha en medio.
   Cada uno limita al otro con min/max, asi que no se puede pedir un "desde"
   posterior al "hasta".
   Con JS (siempre, salvo que falle) la caja se vuelve UN boton que abre el
   calendario de rango (UI.rangos, .rp-pop): los dos campos se esconden pero
   siguen guardando el valor. Sin JS quedan los dos campos de siempre. */
.date-range {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0 var(--space-2) 0 var(--space-3); height: var(--control-h-sm);
  border: 1px solid var(--border-control); border-radius: var(--radius-md);
  background: var(--bg-card);
}
.date-range-label { font-size: 12px; font-weight: 600; color: var(--text-soft); white-space: nowrap; }
.date-range-sep { color: var(--text-soft); font-size: 13px; }
.date-range .input.date-f {
  height: 28px; border: 0; background: transparent; padding: 0;
  width: auto; min-width: 108px; font-size: 12.5px;
}
.date-range .input.date-f:focus { outline: none; box-shadow: none; }
.date-range:focus-within { border-color: var(--accent); box-shadow: var(--ring); }

/* La caja con el calendario: el boton la ocupa entera, como un filtro mas. */
.date-range.is-picker { padding: 0; }
.date-range.is-picker > .date-f,
.date-range.is-picker > .date-range-label,
.date-range.is-picker > .date-range-sep { display: none; }
.rp-trigger {
  flex: 1 1 auto; align-self: stretch; display: flex; align-items: center;
  padding: 0 var(--space-3); border: 0; border-radius: inherit; background: transparent;
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--text-main);
  text-align: left; white-space: nowrap; cursor: pointer;
}
.rp-trigger.is-empty { color: var(--text-muted); font-weight: 500; }
.rp-trigger:focus-visible { outline: none; }
.rp-trigger[aria-invalid="true"] { color: var(--danger-ink); }
.date-range:has(.rp-trigger[aria-invalid="true"]) { border-color: var(--danger); }

/* El calendario. Misma superficie que las listas que se despliegan
   (.cbx-list): tarjeta, pelo y sombra suave. El rango elegido se TIÑE, no se
   rellena —el unico azul solido de una pantalla es su accion principal
   (R17)—: franja --accent-soft y, en los dos extremos, un aro del acento. */
.rp-pop {
  position: fixed; z-index: 10003;
  width: min(304px, calc(100vw - var(--space-4)));
  padding: var(--space-3);
  background: var(--bg-card); color: var(--text-main);
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  box-shadow: var(--shadow-soft);
  animation: helpIn var(--dur-fast) var(--ease-enter, var(--ease));
}
.rp-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-1); }
.rp-title { font-size: 13.5px; font-weight: 700; }
.rp-prev svg { transform: rotate(180deg); }
.rp-week, .rp-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.rp-week span { padding: 6px 0 4px; text-align: center; font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-soft); }
.rp-grid { row-gap: 2px; }
.rp-day {
  height: 38px; padding: 0; border: 0; border-radius: var(--radius-sm); background: transparent;
  font: inherit; font-size: 13px; font-variant-numeric: tabular-nums; color: var(--text-main); cursor: pointer;
}
@media (hover: hover) { .rp-day:not(.is-in):hover { background: var(--bg-card-soft); } }
.rp-day:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rp-day.is-out { color: var(--text-soft); }
.rp-day.is-today { font-weight: 800; color: var(--accent-ink); }
.rp-day.is-in { background: var(--accent-soft); color: var(--accent-ink); border-radius: 0; }
.rp-day.is-start { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.rp-day.is-end { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.rp-day.is-start.is-end { border-radius: var(--radius-sm); }
.rp-day.is-start, .rp-day.is-end { box-shadow: inset 0 0 0 1.5px var(--accent); font-weight: 700; }
/* El pie: lo elegido, entero y en letra, y debajo los botones. Nada se
   aplica hasta Apply, asi que lo que se va a poner tiene que leerse aqui. */
.rp-foot {
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--hairline);
}
.rp-sel { display: flex; flex-direction: column; gap: 1px; min-height: 34px; justify-content: center; }
.rp-sel b { font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.rp-hint { font-size: 12px; color: var(--text-muted); }
.rp-actions { display: flex; align-items: center; gap: var(--space-2); }
.rp-actions .spacer { margin-left: auto; }
@media (pointer: coarse) { .rp-day { height: 42px; } }

@media (max-width: 639px) {
  /* En la hoja del movil cada campo ocupa su linea: ahi si hay sitio. */
  .filter-sheet .date-range { display: flex; width: 100%; height: auto; padding: var(--space-2) var(--space-3); flex-wrap: wrap; }
  .filter-sheet .date-range .input.date-f { flex: 1; min-width: 120px; height: 32px; }
  .filter-sheet .date-range.is-picker { padding: 0; height: var(--control-h); }
}

/* ---- ajustes: que hace cada bloque y donde se ve ---------------------
   Cuatro cajas sueltas obligaban a adivinar a que afectaba cada una. Cada
   bloque abre diciendo en que pantalla se nota lo que se cambia. */

.set-where {
  margin: 0 0 var(--space-3); padding: var(--space-2) var(--space-3);
  background: var(--accent-soft); color: var(--accent-ink);
  border-radius: var(--radius-md); font-size: 12.5px; line-height: 1.45;
}
.set-where b { font-weight: 700; }

/* ---- correcciones de la auditoria (28-ago-2026) ----------------------- */

/* La fila de una store metia nombre, direccion y tres botones en una linea.
   En el telefono el nombre acababa en 57px —ilegible— y la fila desbordaba.
   Ahora el nombre manda: ocupa su linea entera y los botones bajan. */
/* ==========================================================================
   LA FILA DE UNA STORE — dos lineas, no una que se rompe donde quiera
   --------------------------------------------------------------------------
   Seis cosas en un renglon: nombre, etiqueta de inactiva, direccion, Edit,
   Deactivate y borrar. En un cajon de 470px eso no cabe, y las dos maneras de
   forzarlo son malas:

     - Sin envolver, el unico elastico era el nombre, asi que se comia a si
       mismo: seis stores de una misma company, que empiezan igual, quedaban
       recortadas al mismo prefijo. Seis etiquetas que no distinguen nada.
     - Envolviendo —lo que se probo el 9-sep-2026 y salio en la captura del
       cliente— los seis trozos caen donde toque y cada fila se rompe por un
       sitio distinto: una con "Deactivate" solo en la segunda linea, la
       siguiente con la direccion separada de su nombre. Peor que el recorte:
       el recorte al menos era regular.

   La forma correcta no es envolver, es decir DONDE va cada cosa. Rejilla de
   dos filas y dos columnas —identidad a la izquierda en dos lineas, acciones
   a la derecha— que siempre se rompe por el mismo sitio. Es lo unico que se
   le pide a una lista de cinco elementos.
   ========================================================================== */
.st-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name acts" "addr acts";
  align-items: center;
  column-gap: var(--space-3);
  row-gap: 2px;
}
.st-id { grid-area: name; display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.st-row .st-name {
  font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* La direccion baja a la segunda linea y a segundo nivel: es con lo que se
   desempata un nombre repetido, no lo que se lee primero. */
.st-row .st-n {
  grid-area: addr; margin-left: 0; text-align: left; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.st-acts { grid-area: acts; display: flex; align-items: center; gap: var(--space-2); }

@media (max-width: 639px) {
  /* En el telefono no hay columna derecha que valga: 375px menos el padding
     no da para nombre y tres botones. Todo en una columna, en el orden en que
     se lee. */
  .st-row { grid-template-columns: 1fr; grid-template-areas: "name" "addr" "acts"; row-gap: var(--space-2); }
}

/* Los campos tambien son objetivos tactiles. La regla de 44px solo cubria
   botones y chips, asi que un <input> o un <select> se quedaba en 28px justo
   donde mas cuesta acertar. */
@media (pointer: coarse) {
  .input, .select, .textarea, input[type="date"], input[type="file"] { min-height: var(--tap-h); }
  .date-range { height: auto; padding-top: 4px; padding-bottom: 4px; }
  .date-range .input.date-f { min-height: 38px; }
  .date-range.is-picker { height: var(--tap-h); padding: 0; }
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; }
}

/* La chapa de entorno. Solo se pinta fuera de produccion (ver _shell.php).
   Ambar y no roja: no es un error, es un aviso de donde estas. */
.env-flag {
  margin: 0 var(--space-4) var(--space-3);
  padding: 7px var(--space-3);
  border-radius: var(--radius-md);
  /* El tinte ambar SOBRE la superficie, no a secas: --warning-soft es un 10%
     de opacidad, y en el telefono la chapa va fija encima de la pagina. A
     secas, la lista se veia pasar por detras del texto. */
  background: linear-gradient(var(--warning-soft), var(--warning-soft)), var(--bg-card);
  color: var(--warning);
  border: 1px solid var(--warning);
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  display: flex; flex-direction: column; gap: 1px;
}
.env-flag span { font-weight: 500; letter-spacing: 0; font-size: 10.5px; opacity: .85; }
@media (max-width: 899px) {
  /* En movil el menu es un cajon cerrado: la chapa se pone arriba del todo,
     cruzada, para que se vea antes de tocar nada. */
  .env-flag { position: fixed; top: 0; left: 0; right: 0; z-index: 60; margin: 0;
              border-radius: 0; flex-direction: row; gap: var(--space-2); justify-content: center; }
  /* El hueco es PARA la chapa, asi que solo existe si la chapa existe.
     Estaba escrito como `.app { padding-top: 30px }` a secas, y la chapa solo
     se pinta fuera de produccion (_shell.php mira JBA_ENV). Resultado: en el
     telefono, en el sitio real, 30px de nada encima de la barra de titulo —
     un hueco que no separaba nada de nada. */
  .app:has(.env-flag) { padding-top: 30px; }
  .app:has(.env-flag) .nav { top: 30px; }
}

/* La chapa de "ver como" (14-sep-2026). Mismo lenguaje que .env-flag arriba
   -ambar, no roja: no es un error, es donde estas- porque son la misma
   clase de aviso: no algo que paso en el CONTENIDO, sino en que contexto se
   esta mirando. A diferencia de .env-flag, sale en produccion a proposito
   -es justo ahi donde hace falta- y lleva un boton: salir de este modo no
   puede depender de encontrar el camino de vuelta a Users. */
.impersonate-flag {
  margin: 0 var(--space-4) var(--space-3);
  padding: 7px var(--space-3);
  border-radius: var(--radius-md);
  /* Opaca por lo mismo que .env-flag: fija arriba en el telefono, con el
     tinte a secas se transparentaba la lista (la demo la usa tambien). */
  background: linear-gradient(var(--warning-soft), var(--warning-soft)), var(--bg-card);
  color: var(--warning);
  border: 1px solid var(--warning);
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  display: flex; align-items: center; flex-wrap: wrap; gap: 2px var(--space-2);
}
.impersonate-flag span { font-weight: 500; letter-spacing: 0; font-size: 10.5px; opacity: .85; flex: 1 1 auto; }
.impersonate-exit {
  flex: 0 0 auto; display: inline-block; background: transparent; border: 1px solid var(--warning); color: var(--warning);
  font: inherit; font-weight: 700; padding: 3px 10px; border-radius: 99px; cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.impersonate-exit:hover { background: var(--warning-soft); }
@media (max-width: 899px) {
  /* Mismo motivo que .env-flag: en movil el menu es un cajon cerrado, asi
     que esta chapa se cruza arriba del todo para que se vea antes de tocar
     nada -y sobre todo, para que el boton de Salir este siempre a mano. */
  /* UNA linea de alto fijo. Con flex-wrap partia en dos (59px: el nombre y
     la frase arriba, Exit debajo) y el hueco reservado era de 30: la chapa
     tapaba el boton "New dispute" de la barra de titulo, y al desplazar, la
     barra —sticky a top:0— se metia debajo. La frase secundaria se recorta;
     "Viewing as" y Exit no. */
  .impersonate-flag { position: fixed; top: 0; left: 0; right: 0; z-index: 61; margin: 0;
                       border-radius: 0; justify-content: center; flex-wrap: nowrap;
                       height: 36px; padding-block: 0; }
  .impersonate-flag b { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .impersonate-flag span { flex: 0 0 auto; max-width: 125px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .impersonate-exit { padding-block: 2px; }
  /* Si las dos chapas coinciden (poco probable: la de entorno no sale en
     produccion, que es donde vive esta app) no se tapan: la segunda baja el
     alto de la primera, y el hueco de arriba crece para las dos. */
  .env-flag + .impersonate-flag { top: 30px; }
  .app:has(.env-flag):has(.impersonate-flag) { padding-top: 66px; }
  .app:has(.impersonate-flag):not(:has(.env-flag)) { padding-top: 36px; }
  .app:has(.impersonate-flag) .topbar { top: 36px; }
  .app:has(.env-flag):has(.impersonate-flag) .topbar { top: 66px; }
  .app:has(.impersonate-flag):not(:has(.env-flag)) .nav { top: 36px; }
  .app:has(.env-flag):has(.impersonate-flag) .nav { top: 66px; }
}


/* ==========================================================================
   AJUSTES: LEER PRIMERO, EDITAR A PROPOSITO (4-oct-2026, AJ-7 a AJ-12)
   --------------------------------------------------------------------------
   La pestaña Disputes de Settings medía 4,3 pantallas de iPhone: cada nombre
   en una caja con borde, cada fila con su papelera, cada seccion con su
   boton de guardar. Ahora cada cosa es una FILA con su valor, tocarla abre
   una hoja (modal) con solo esa cosa, y los interruptores guardan solos.
   ========================================================================== */

/* La tarjeta de una seccion que es una lista: las filas llegan al borde,
   con su separador, como una lista de ajustes de un telefono. */
.set-card--list > .panel-body { padding: 0; }
.set-list { display: flex; flex-direction: column; }
.set-item {
  display: flex; align-items: center; gap: var(--space-3);
  width: 100%; min-height: 52px; padding: 10px var(--space-4);
  background: transparent; border: 0; border-top: 1px solid var(--hairline);
  color: var(--text-main); font: inherit; text-align: left; text-decoration: none; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease);
}
.set-list > .set-item:first-child { border-top: 0; }
@media (hover: hover) and (pointer: fine) { .set-item:hover { background: var(--bg-card-soft); } }
.set-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-md); }
.set-item-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.set-item-main b { font-size: 14px; font-weight: 600; overflow-wrap: anywhere; }
.set-item-main > span { font-size: 12.5px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.set-item-chev { flex: 0 0 auto; display: grid; color: var(--text-soft); }
.set-item-chev svg { width: 16px; height: 16px; }
/* Añadir es la ultima fila de su lista (AJ-10), en el color de la accion. */
.set-item-add { color: var(--accent-ink); }
.set-item-add > svg { width: 16px; height: 16px; flex: 0 0 auto; }
.set-item-add b { font-weight: 600; }
/* El punto del tramo del Dashboard en el que cuenta cada estado: el mismo
   que en las graficas (.f-ok, .f-live...). Gris si no cuenta en ninguno. */
.set-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.set-dot:not(.f-ok):not(.f-live):not(.f-wait):not(.f-denied) { background: var(--border-control); }

/* Una seccion con algo a la vista antes de su lista: el modo de cobro. Test
   / Live no se esconde en una hoja: es la decision mas seria de Settings. */
.set-mode { display: grid; gap: var(--space-2); padding: var(--space-4); border-bottom: 1px solid var(--hairline); }
.set-mode .note-info { margin: 0 0 var(--space-2); }
/* Dashboard panels: dos listas, admin y manager, cada una con su titulo. */
.set-list-group .sectitle { margin: 0; padding: var(--space-3) var(--space-4) var(--space-1); }

/* El interruptor: encender o apagar guarda al momento, con Undo en el aviso
   (AJ-9). Una casilla de verdad con role="switch": se anuncia, se enfoca y
   se mueve con la barra espaciadora sin una linea de JavaScript. */
/* `input.switch` y no `.switch`: la regla general de las casillas
   (input[type="checkbox"]) pesa mas y las dejaba en 16px. */
input.switch {
  -webkit-appearance: none; appearance: none; margin: 0; flex: 0 0 auto; cursor: pointer;
  width: 40px; height: 24px; border-radius: 999px; position: relative;
  background: var(--border-control);
  transition: background-color var(--dur-fast) var(--ease);
}
input.switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%;
  background: var(--bg-card); box-shadow: var(--shadow-knob);
  transition: transform var(--dur-fast) var(--ease-enter);
}
input.switch:checked { background: var(--accent); }
input.switch:checked::after { transform: translateX(16px); }
input.switch:disabled { opacity: .6; cursor: progress; }
input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-switch { cursor: pointer; }
/* Dentro de una hoja: el interruptor con su nombre y lo que significa. */
.set-toggle {
  display: flex; align-items: center; gap: var(--space-3); padding-block: var(--space-3);
  border-top: 1px solid var(--hairline); cursor: pointer;
}
.set-toggle > span { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.set-toggle b { font-size: 14px; font-weight: 600; }
.set-toggle > span > span { font-size: 12.5px; color: var(--text-muted); }

/* La hoja: Delete a la izquierda y aparte, en el color del peligro pero sin
   relleno (B2: lo destructivo no toca a lo primario). */
.set-sheet .field:last-child { margin-bottom: 0; }
.set-del { color: var(--danger-ink); }
.set-del[disabled] { color: var(--text-soft); }
/* Tocar fuera con algo escrito no cierra: el Save da un aviso de un pulso. */
.btn.is-nudge { animation: set-nudge var(--dur-slow) var(--ease-enter); }
@keyframes set-nudge { 35% { transform: scale(1.06); } }

/* ESCRITORIO: las pestañas son un control segmentado, el mismo de Billing.
   Pista gris y la seccion abierta en blanco: pestañas que se ven como
   pestañas y no como filtros, y sin subrayado, que seria una franja (A1). */
.set-nav {
  display: inline-flex; flex-wrap: wrap; gap: var(--space-1); padding: var(--space-1);
  margin-bottom: var(--space-4);
  background: var(--bg-card-soft); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.set-nav button {
  border: 0; background: transparent; font: inherit; cursor: pointer; white-space: nowrap;
  min-height: var(--control-h-sm); padding: 0 var(--space-3); border-radius: var(--radius-md);
  font-size: 13px; font-weight: 600; color: var(--text-muted);
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .set-nav button:hover { color: var(--text-main); } }
.set-nav button[aria-selected="true"] {
  background: var(--bg-card); color: var(--text-main); font-weight: 700; box-shadow: var(--shadow-subtle);
}
@media (pointer: coarse) { .set-nav button { min-height: var(--tap-h); } }

/* TELEFONO: la lista de secciones, y en cada seccion una salida clara. */
.set-menu .set-list {
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-subtle); overflow: hidden;
}
.set-back {
  display: inline-flex; align-items: center; gap: 2px; min-height: var(--tap-h);
  margin-left: -6px; padding: 0 6px; border: 0; background: none; cursor: pointer;
  color: var(--accent-ink); font: inherit; font-size: 14px; font-weight: 600;
}
.set-back svg { width: 18px; height: 18px; transform: rotate(180deg); }
.set-title-m { margin: 0 0 var(--space-3); font-size: 20px; font-weight: 700; letter-spacing: -.015em; }

/* ---- Overview: grafica de dinero y libro de cobro ----------------------- */

/* ==========================================================================
   f-* : QUE SIGNIFICA un trozo de color, en un solo sitio
   --------------------------------------------------------------------------
   Estas clases dicen "recuperado", "en revision", "necesita atencion",
   "denied"... y su color lo declaraba CADA componente por su cuenta: una vez
   para .bar-fill (las barras de By status, Disputes per month, Where it
   comes from y Busiest stores) y otra para .seg-legend (los puntos de la
   leyenda). Una clase que parece hacer algo y no lo hace es la trampa de la
   regla R9.

   Ahora el color vive en la clase. Quien la use, la usa. Los componentes solo
   ponen forma —tamano, radio, borde—, nunca el color otra vez.

   Cuatro, desde el 14-sep-2026: Recovered, In review, Needs attention y
   Denied. Nunca van en un elemento con TEXTO: la clase pinta el fondo, y el
   15-sep eso dejo los porcentajes de "Where it comes from" como cuadros de
   color con el numero dentro del mismo color, invisible.

   Dos pesos: el punto de una leyenda o de un tooltip toma el tono -dot, un
   paso mas firme; la superficie grande -un tramo de barra- toma el tono
   suave. Selector mas especifico para la superficie, gana sin pelear con el
   orden. */
.f-ok     { background: var(--chart-ok-dot); }
.f-live   { background: var(--chart-live-dot); }
.f-wait   { background: var(--chart-wait-dot); }
.f-denied { background: var(--chart-denied-dot); }
.bar-fill.f-ok,     .seg.f-ok,     .split-bar .f-ok     { background: var(--chart-ok); }
.bar-fill.f-live,   .seg.f-live,   .split-bar .f-live   { background: var(--chart-live); }
.bar-fill.f-wait,   .seg.f-wait,   .split-bar .f-wait   { background: var(--chart-wait); }
.bar-fill.f-denied, .seg.f-denied, .split-bar .f-denied { background: var(--chart-denied); }

/* El libro de cobro reusa .matrix, que ya es LA tabla de esta aplicacion.
   Lo unico que anade son columnas de dinero y el desglose por store. */
.matrix td.n, .matrix th.n { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.matrix td.n b { font-weight: 700; }
/* Un dato que todavia no existe se escribe con una raya, no con $0.00: cero
   dolares es una cifra y esto es una ausencia. */
.matrix .none { color: var(--text-soft); }
.matrix tr.is-off td { opacity: .55; }
.matrix tr.co-line { cursor: pointer; }
.matrix tr.co-line:hover td { background: var(--bg-card-soft); }
.matrix tr.co-line td:first-child { font-weight: 600; }
.matrix .caret { display: inline-block; width: 12px; color: var(--text-soft); font-size: 10px;
                 transition: transform var(--dur-fast) var(--ease); }
.matrix .row-open[aria-expanded="true"] .caret { transform: rotate(90deg); }
/* El boton que abre una company. Se ve como texto porque ES el nombre de
   la company: un boton con marco dentro de cada celda convertiria la tabla
   en una rejilla de botones y no en una tabla. */
.matrix .row-open { display: block; width: 100%; text-align: left; background: none;
  border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; }
.matrix .row-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-md); }
@media (prefers-reduced-motion: reduce) { .matrix .caret { transition: none; } }
.matrix tr.drill td { background: var(--bg-card-soft); padding-left: var(--space-6);
                      color: var(--text-muted); font-size: 12px; }
.matrix .sub { color: var(--text-soft); font-weight: 400; }

/* El pie de una tabla que explica de donde sale una columna. */
.tbl-note { font-size: 12px; color: var(--text-soft); line-height: 1.55; margin: var(--space-3) 0 0; }

/* Las tarjetas de store del Company viewer: la misma pieza que un KPI, en
   rejilla propia, para no inventar una tarjeta nueva que se vea parecida. */
.store-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-2); }
@media (min-width: 620px) { .store-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Las pastillas del libro de cobro. Reusan .pill —misma pieza, mismo tamano,
   mismo punto— y solo cambian de que capa semantica beben. No hay una pastilla
   "de facturacion" distinta de una "de dispute": es la misma pastilla diciendo
   en que etapa esta otra cosa. */
.pill.ledger-none     { background: var(--pill-done-bg); color: var(--ledger-none);     border-color: var(--pill-done-bd); }
.pill.ledger-accruing { background: var(--pill-live-bg); color: var(--ledger-accruing); }
.pill.ledger-tosend   { background: var(--pill-wait-bg); color: var(--ledger-tosend); }
.pill.ledger-awaiting { background: var(--pill-open-bg); color: var(--ledger-awaiting); }
.pill.ledger-settled  { background: var(--pill-ok-bg);   color: var(--ledger-settled); }

@media (pointer: coarse) {
  /* Una fila que se abre al tocarla es un control, y un control con el dedo
     mide 44px. Con el padding de tabla se quedaba en 35 y abrir la company
     equivocada era cuestion de tiempo. */
  .matrix tr.co-line td { padding-top: 13px; padding-bottom: 13px; }
}

/* ---- cobrar: marcar stores y crear la factura -------------------------- */
/* La casilla y el nombre son un solo blanco: en el telefono acertarle a un
   cuadro de 13px es una loteria, y el nombre esta justo al lado sin hacer
   nada. */
.pick { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer;
        min-height: var(--tap-h); }
.pick input { width: 16px; height: 16px; flex: 0 0 auto; accent-color: var(--accent); cursor: pointer; }
.pick span { font-weight: 600; color: var(--text-main); }
.pick:hover span { color: var(--accent-ink); }

/* La barra aparece solo cuando hay algo marcado: un pie fijo diciendo
   "nothing selected" es ruido permanente para un momento que dura segundos. */
.pick-bar {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-3); padding: var(--space-3);
  background: var(--accent-soft); border: 1px solid var(--accent);
  border-radius: var(--radius-md); flex-wrap: wrap;
}
.pick-count { font-size: 13px; font-weight: 600; color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.pick-bar .spacer { margin-left: auto; }

/* Los botones de una factura, en la celda de etapa. En una tabla estrecha se
   apilan bajo la pastilla en vez de empujar la columna. */
.inv-acts { display: inline-flex; gap: var(--space-2); flex-wrap: wrap; margin-left: var(--space-2); }
/* Sin sitio, las acciones van en su propia linea, pero las dos juntas: con
   flex-wrap se apilaban tambien entre ellas y cada fila del libro media
   128px en tablet y 148 en movil (pastilla / Charge all / Details).
   Lo decide el ancho del panel, no el de la ventana: a 1024px la barra
   lateral deja el libro en 708px y la etapa en linea pedia 726. */
#ledger > .panel-body { container: ledger / inline-size; }
@container ledger (max-width: 759px) { .inv-acts { display: flex; flex-wrap: nowrap; margin: var(--space-2) 0 0; } }
@media (max-width: 639px) { #ledger tr.co-line td { padding-top: 10px; padding-bottom: 10px; } }

/* La base de una cifra de dinero, bajo ella. En su propia linea porque al lado
   se lee como parte del importe: "$20.00 on $100.00" pasa por un solo numero. */
.matrix td.n .sub { display: block; font-size: 11px; margin-top: 1px; }

/* ---- permisos de un manager: la company ES la fila --------------------- */
/* La misma pieza sirve para "store por store" y para "company entera", pero
   en el segundo caso el .perm-store de dentro no es una cosa aparte: es el
   control de la company que ya nombra la cabecera. Sin esto se veia una
   tarjeta dentro de otra, con el nombre escrito dos veces y una barra lateral
   que separaba algo de nada. */
.perm-co.is-company .perm-store { padding: 0 var(--space-4) var(--space-4); border-bottom: 0; }
.perm-co.is-company .perm-store::before { display: none; }
.perm-co.is-company .perm-co-head { padding-bottom: var(--space-2); }
/* Details sube a la cabecera, donde ya esta el nombre al que se refiere. */
.perm-co.is-company .perm-co-head .perm-detail { margin-left: auto; }
/* Ocho companies seguidas son mucha pantalla: se aprieta el hueco entre ellas
   y la cabecera pierde el relleno extra que solo hacia falta con la franja. */
.perm-co.is-company { margin-bottom: var(--space-2); }
.perm-co.is-company .perm-grid { margin-top: var(--space-3); }

/* ---- barra partida por estado (Busiest stores) ------------------------- */
/* Un solo carril con varios tramos. Entre tramo y tramo, dos pixeles del
   fondo de la tarjeta: se separan sin un borde que corte. */
.bar-track.seg { display: flex; gap: 2px; background: var(--bg-card); }
.bar-track.seg .bar-fill { border-radius: 0; min-width: 3px; }
.bar-track.seg .bar-fill:first-child { border-radius: 99px 0 0 99px; }
.bar-track.seg .bar-fill:last-child { border-radius: 0 99px 99px 0; }
.bar-track.seg .bar-fill:only-child { border-radius: 99px; }
.bar-track.seg, .split-bar { cursor: default; outline-offset: 3px; }

.seg-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-3);
              font-size: 12.5px; color: var(--text-muted); }
.seg-legend--end { justify-content: flex-end; }
/* A la derecha solo si la tarjeta tiene sitio: al lado de By status, en
   escritorio estrecho, la cuarta entrada caia sola en otra linea. */
.mchart-panel { container-type: inline-size; }
@container (max-width: 520px) { .seg-legend--end { justify-content: flex-start; } }
.seg-legend span { display: inline-flex; align-items: center; gap: 7px; }
.seg-legend i { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
@media (max-width: 639px) { .seg-legend--end { justify-content: flex-start; } }

/* ---- By status ---------------------------------------------------------- */
/* Cuatro filas separadas por un pelo, pastilla a la izquierda, cifra a la
   derecha y la barra debajo: la referencia "By status". La fila conserva
   class="bar-row" y "bar-name" tal cual -R14b de design-check.js cuenta esos
   dos atributos-, y el estilo entra por el contenedor. */
/* Busiest stores en movil: nombre arriba y cifra debajo. En la misma linea la
   cifra se quedaba 192px y el nombre de la store 105 de los 280 que pide.
   Con la linea entera para el, el nombre parte en dos antes que cortarse:
   a 375px la company de tres stores seguia perdiendo sus ultimas letras. */
@media (max-width: 639px) {
  .store-list .bar-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .store-list .bar-name { white-space: normal; }
  .store-list .bar-track { margin-top: 5px; }
}

.status-list { gap: 0; }
.status-list .bar-row { align-items: center; gap: 10px var(--space-3); padding: 13px 0;
                        border-bottom: 1px solid var(--hairline); }
.status-list .bar-row:first-child { padding-top: 0; }
.status-list .bar-row:last-child { border-bottom: 0; padding-bottom: 2px; }
.status-list .pill { font-size: 12.5px; padding: 5px 13px 5px 11px; gap: 7px; }
.status-list .bar-val { font-size: 13px; }

/* ---- Where it comes from ------------------------------------------------ */
/* La referencia de la que sale todo lo demas: tabla limpia, nombre con sus
   disputes debajo, importes en negrita, y una barra partida de 12px con los
   porcentajes debajo de cada tramo y una leyenda que dice que significa cada
   color. El conteo bajo el nombre se quito para todo el mundo (17-sep-2026)
   y volvio el mismo dia, pero no para admin: quien ya tiene la fila entera,
   el libro de cobro y el resto del Overview no necesita otro numero mas
   (ver el `if (!Authz::isAdmin())` en overview.php). Para manager y el
   resto, sigue diciendo cuantos casos hay detras de la barra partida. */
.platforms th { padding-top: 4px; padding-bottom: 10px; }
.platforms td { padding-top: 15px; padding-bottom: 15px; vertical-align: middle; }
.platforms .plat-name { display: block; font-size: 14px; font-weight: 600; color: var(--text-main); }
.platforms .plat-n { display: block; margin-top: 2px; font-size: 12.5px; color: var(--text-muted); }
.platforms td.money { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.platforms .split { width: 42%; }
.split-bar { display: flex; gap: 2px; height: 12px; border-radius: 99px; overflow: hidden; }
.split-bar > span { flex: 1 1 0; min-width: 3px;
                    transform-origin: left; animation: chart-grow-x var(--dur-slow) var(--ease-enter); }
/* Cada porcentaje en una caja que crece como su tramo (flex-grow = su parte)
   pero nunca por debajo de su propio texto: cae bajo el final de su tramo y
   dos tramos pequenos seguidos no se pisan. */
.split-pct { display: flex; margin-top: 7px; font-size: 12px; color: var(--text-muted);
             font-variant-numeric: tabular-nums; line-height: 1; }
.split-pct span { flex: 1 1 0; min-width: max-content; padding-left: 8px; text-align: right; }
.split-pct span:first-child { padding-left: 0; text-align: left; }
.split-legend { list-style: none; margin: var(--space-2) 0 0; padding: var(--space-4) 0 0;
                border-top: 1px solid var(--hairline);
                display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-3) var(--space-4); }
.split-legend li { display: flex; align-items: flex-start; gap: 9px; min-width: 0; }
.split-legend i { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; margin-top: 4px; }
.split-legend b { display: block; font-size: 13px; font-weight: 700; color: var(--text-main); }
.split-legend span { display: block; margin-top: 2px; font-size: 12.5px; line-height: 1.4; color: var(--text-muted); }
@media (max-width: 899px) { .split-legend { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* En movil las columnas no caben: cada plataforma se apila -nombre y cifras
   arriba, la barra a todo el ancho debajo- en vez de mandar la tabla de lado.
   Cada cifra pone su nombre encima, porque la cabecera ya no se ve. */
@media (max-width: 639px) {
  .platforms, .platforms tbody, .platforms tr, .platforms td { display: block; width: auto; }
  .platforms thead { display: none; }
  .platforms tr { display: flex; flex-wrap: wrap; align-items: flex-end; column-gap: var(--space-4);
                  padding: 14px 0; border-bottom: 1px solid var(--hairline); }
  .platforms tr:last-child { border-bottom: 0; }
  .platforms td, .platforms td:first-child { padding: 0; border: 0; }
  .platforms td.plat-cell { flex: 1 1 auto; min-width: 0; }
  .platforms td.money::before { content: attr(data-label); display: block; margin-bottom: 2px;
                                font-size: 10px; font-weight: 700; letter-spacing: .04em;
                                text-transform: uppercase; color: var(--text-soft); }
  .platforms td.split { flex: 1 0 100%; width: auto; margin-top: 12px; }
}

/* ---- tooltip de las graficas -------------------------------------------- */
/* Uno solo para toda la pagina, en overview.js. Sale al pasar el raton, al
   enfocar con Tab y al tocar en movil. Los importes vienen ya formateados del
   servidor (ui_money), igual que el resto de la pagina. */
.chart-tip { position: fixed; z-index: 70; pointer-events: none; min-width: 200px; max-width: 280px;
             padding: 10px 12px; border-radius: var(--radius-md); background: var(--bg-card);
             color: var(--text-main); border: 1px solid var(--border-subtle); box-shadow: var(--shadow-card);
             font-size: 12.5px; }
.chart-tip b { display: block; font-size: 13px; }
.chart-tip small { display: block; margin-top: 1px; font-size: 11.5px; color: var(--text-muted); }
.chart-tip-rows { margin-top: 8px; display: grid; gap: 5px; }
.chart-tip-row { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; align-items: center; gap: 8px; }
.chart-tip-row i { width: 8px; height: 8px; border-radius: 50%; }
.chart-tip-row em { font-style: normal; font-weight: 600; font-variant-numeric: tabular-nums; padding-left: var(--space-3); }

/* ---- el borde de fila deja de recortar la esquina de un input ---------- */
/* Con border-collapse: collapse los bordes se pintan CENTRADOS sobre la linea
   de la rejilla: medio pixel del divisor cae dentro de la celda de abajo y se
   come la esquina redondeada del input que empieza justo ahi. Se veia en las
   siete filas de Dispute statuses.

   Con `separate` y espaciado cero el aspecto es identico —el divisor sigue
   siendo un borde de celda— pero cada borde vive dentro de su propia celda y
   ya no invade a la vecina.

   `table.data` (Disputes y Users) no lo necesita: no lleva campos dentro de
   las celdas, asi que no hay ninguna esquina que recortar. */
.matrix { border-collapse: separate; border-spacing: 0; }

/* Regla B2 en una fila de tabla: lo destructivo no toca a lo primario.
   En un modal el patron es .modal-footer-spread; aqui, donde no hay pie que
   repartir, el equivalente es un hueco explicito. Y la celda no cambia de
   alto por llevar botones, que era lo que descuadraba la columna STAGE
   respecto al resto de la tabla. */
.acts-gap { display: inline-block; width: var(--space-4); flex: 0 0 auto; }
.matrix td .inv-acts { vertical-align: middle; }
.matrix td:has(.inv-acts) { white-space: nowrap; }

/* ==========================================================================
   ANCHO DE CAMPO — el tamano de la caja avisa cuanto se espera dentro
   --------------------------------------------------------------------------
   Todos los inputs de Ajustes median lo mismo: "Maximum wait" (dos digitos) y
   "Domains allowed to send" (un dominio) ocupaban la columna entera. Un campo
   ancho promete texto largo, y cuando lo que cabe son dos cifras esa promesa
   es una pregunta ("me falta algo?").

   La regla, escrita tambien en docs/DESIGN-RULES.md, "Formularios" (F5):
     - dato de longitud CONOCIDA y corta (dias, porcentaje, importe) -> --num
     - dato de longitud LIBRE (correo, dominio, URL, nombre) -> columna entera
   No hay tercer tamano a proposito: dos anchos se distinguen de un vistazo,
   cuatro no.

   112px = 7 digitos comodos con la tipografia tabular del panel, mas el
   padding lateral. Y `max-width:100%` para que a 375px nunca desborde.
   ========================================================================== */
.input--num { width: 112px; max-width: 100%; }
/* TERCER ancho, y hubo que anadirlo. La regla decia "dos anchos y solo dos":
   angosto para cifras, columna entera para lo demas. Eso metia en el mismo
   saco un correo —que puede medir 60 caracteres— y el nombre de una
   plataforma, que mide ocho. En Ajustes se veia: "DoorDash" y "UBEREATS" en
   cajas de 900px, con la etiqueta perdida a la izquierda de un campo vacio.
   260px = ~30 caracteres, que es mas de lo que mide cualquier nombre corto,
   codigo o rotulo de estado de este proyecto. */
.input--txt { width: 260px; max-width: 100%; }
/* Con `type=number` el navegador anade sus flechas; sin sitio para ellas el
   texto se recorta. Ancho suficiente contado ya, pero se deja explicito. */
.input--num[type="number"] { width: 112px; }

/* ==========================================================================
   MARCAR TODAS LAS STORES DE UNA COMPANY
   --------------------------------------------------------------------------
   La fila de "todas" no es una store: va antes que ellas y se distingue con
   el fondo suave de la tarjeta, no con un color propio. Un color nuevo aqui
   solo diria "soy distinta", que es justo lo que ya dice su posicion.
   ========================================================================== */
.matrix tr.drill.is-all > td { background: var(--bg-card-soft); }
.drill-all { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* El importe que el cliente decide pagar. Alineado a la derecha porque es
   dinero, y con el simbolo fuera del campo: dentro obligaria a limpiarlo
   antes de escribir. */
.money-in { display: flex; align-items: center; gap: var(--space-2); }
.money-in .cur { font-size: 15px; font-weight: 700; color: var(--text-muted); }
.money-in .input { width: 140px; text-align: right; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   CONMUTADOR DE DOS ESTADOS — para lo que no puede cambiar sin querer
   --------------------------------------------------------------------------
   Un <select> nativo con el foco puesto CAMBIA DE VALOR con la rueda del
   raton en Chrome. Para elegir una plataforma da igual; para elegir entre
   "test" y "live" no: un scroll distraido sobre el control enfocado pone la
   facturacion en dinero real sin un solo clic.

   Dos radios pintados como un segmentado. Un radio no reacciona a la rueda,
   se mueve con las flechas del teclado como debe, y anuncia el grupo entero
   a un lector de pantalla. La marca de seleccion NO es solo el color de
   fondo: `aria-checked` lo dice y el peso de la letra lo refuerza, porque el
   color solo nunca lleva el significado (§3, R14b).
   ========================================================================== */
.seg-ctl {
  display: inline-flex; gap: var(--space-1); padding: var(--space-1);
  background: var(--bg-card-soft); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md); flex-wrap: wrap;
}
.seg-ctl label {
  display: flex; align-items: center; gap: var(--space-2); cursor: pointer;
  padding: 0 var(--space-3); min-height: var(--control-h-sm);
  border-radius: var(--radius-md); font-size: 13px; font-weight: 600;
  color: var(--text-muted); white-space: nowrap;
}
.seg-ctl input { width: 15px; height: 15px; flex: 0 0 auto; }
.seg-ctl label:hover { color: var(--text-main); }
.seg-ctl label:has(input:checked) {
  background: var(--bg-card); color: var(--text-main); font-weight: 700;
  box-shadow: var(--shadow-subtle);
}
/* Que se paga por elegir "live": el segmento encendido se tine de peligro.
   El texto ya lo dice; esto lo hace visible desde el otro lado de la mesa. */
.seg-ctl label.is-hot:has(input:checked) { color: var(--danger-ink); }
.seg-ctl label:focus-within { box-shadow: var(--ring); }
@media (pointer: coarse) { .seg-ctl label { min-height: var(--tap-h); } }

/* ==========================================================================
   ERROR JUNTO AL CAMPO — regla F3
   --------------------------------------------------------------------------
   Un toast global delante de un formulario de siete campos dice QUE falla
   pero no DONDE. Esto va debajo del campo que falla, con el mismo tamano que
   .hint para no gritar, y en rojo porque no es una pista: es un rechazo.

   El color NO lleva el mensaje solo: el texto dice el problema y el campo
   queda con aria-invalid, que es lo que lee un lector de pantalla.
   ========================================================================== */
.field-err {
  margin-top: var(--space-2); font-size: 12px; font-weight: 600;
  color: var(--danger-ink); line-height: 1.45;
}
.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger); }

/* ==========================================================================
   CHIPS — un dato corto que se repite, dicho de un vistazo
   --------------------------------------------------------------------------
   Las plataformas de una company vivian solo dentro del cajon, asi que la
   pregunta mas frecuente de esa pantalla —¿con quien trabaja cada una?—
   costaba abrir las ocho. Aqui van en la fila.

   No es una pastilla de estado: una pastilla dice EN QUE PUNTO esta algo y
   por eso lleva color con significado. Esto es una etiqueta, y por eso es
   gris. Meter color aqui gastaria el unico recurso que la pantalla reserva
   para lo que urge.
   ========================================================================== */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: var(--space-1); }
.chip {
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 2px var(--space-2); border-radius: 99px;
  background: var(--bg-card-soft); color: var(--text-muted);
  border: 1px solid var(--border-subtle); white-space: nowrap;
}
/* "Sin ninguna" es la ausencia del dato, no un valor: se escribe mas apagada
   y en cursiva para que no se lea como una plataforma mas llamada asi. */
.chip.is-none { font-style: italic; font-weight: 600; border-style: dashed; }

/* ==========================================================================
   COMBOBOX — el campo que busca de verdad (UI.combobox en assets/js/ui.js)
   --------------------------------------------------------------------------
   La lista va colgada del <body>, no del campo: dentro de un modal el
   contenido tiene su propio scroll y la recortaria justo al abrirla. Por eso
   se posiciona con `fixed` y se recoloca al hacer scroll o cambiar de tamano.

   La flecha la pintamos nosotros, y solo cuando hay bastantes opciones. La
   del <input list> nativo la dibuja el navegador —no se puede colocar, de ahi
   que saliera desalineada— y ademas prometia un desplegable que en realidad
   no filtraba nada.
   ========================================================================== */
.cbx { position: relative; }
.cbx-arrow {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%) rotate(90deg);
  width: 14px; height: 14px; color: var(--text-soft); pointer-events: none;
}
.cbx-arrow svg { width: 100%; height: 100%; display: block; }
/* Con flecha, el texto no pasa por debajo de ella. Sin flecha no se reserva
   sitio: un hueco en blanco a la derecha de un campo corto se lee como un
   control que falta. */
.cbx:has(.cbx-arrow) .input { padding-right: 30px; }
/* La X de limpiar y la flecha no pueden ocupar el mismo sitio. */
.cbx:has(.cbx-arrow) .input-x { right: 28px; }

.cbx-list {
  position: fixed; z-index: 80; margin: 0; padding: var(--space-1); list-style: none;
  max-height: 260px; overflow-y: auto; overscroll-behavior: contain;
  background: var(--bg-card); border: 1px solid var(--hairline);
  border-radius: var(--radius-md); box-shadow: var(--shadow-soft);
}
.cbx-opt {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm);
  font-size: 13.5px; cursor: pointer; color: var(--text-main);
}
.cbx-opt .cbx-lb { font-weight: 600; }
.cbx-opt .cbx-sub { font-size: 12px; color: var(--text-muted); margin-left: auto; }
/* Resaltado por teclado y por raton, el MISMO. Dos resaltados distintos para
   la misma cosa obligan a mirar dos sitios. */
.cbx-opt:hover, .cbx-opt.is-on { background: var(--accent-soft); color: var(--accent-ink); }
/* Una opcion inactiva se ofrece igual —hay que poder encontrarla— pero lo
   dice con palabras, no solo apagando el color. */
.cbx-opt.is-off .cbx-lb::after { content: ' · inactive'; font-weight: 500; color: var(--text-soft); }
.cbx-empty { padding: var(--space-3); font-size: 12.5px; color: var(--text-muted); }
/* Con el dedo, una opcion de 36px se falla. Regla L5: ningun control
   interactivo por debajo de --tap-h. */
@media (pointer: coarse) { .cbx-opt { min-height: var(--tap-h); } }

/* ---- el combobox montado sobre un <select> de filtro -------------------- */
/* El <select> sigue ahi, escondido: es quien guarda el valor y quien viaja en
   el formulario. Lo que se ve es el campo de al lado.
   Ancho fijo y no `auto`: con `width:auto` un <input> mide por su atributo
   `size` y los tres filtros salian de tres anchos distintos. */
.cbx-flt { width: 190px; }
.toolbar-row .cbx, .filter-fields .cbx { flex: 0 0 auto; }
.toolbar-row .cbx-flt .input, .filter-fields .cbx-flt .input {
  height: var(--control-h-sm); width: 100%; font-size: 13px;
}
/* En la hoja del telefono ocupan la fila entera, como el resto de sus campos. */
.filter-sheet .cbx-flt { width: 100%; }
.filter-sheet .cbx-flt .input { height: var(--control-h); }

/* Y EN EL DASHBOARD, que no tiene hoja: sus filtros viven en la propia
   pagina. Con 190px fijos y una fila que envuelve, en 375px quedaban dos
   campos cortos y un rango de fechas de 316: tres anchos distintos, ninguno
   llegando al borde, escalonados como si estuvieran mal alineados. Apilados a
   ancho completo son lo mismo que cualquier otro formulario de la app. */
@media (max-width: 639px) {
  /* Los campos del Dashboard, detras del chip Filters, que abre la misma
     hoja que en Disputes (overview.js pone .is-folded). La primera cifra se
     ve sin bajar. */
  .dash-filters.is-folded > .toolbar-row { display: none; }
  /* En Disputes la fila de los campos solo existe en escritorio: en el
     telefono estan en la hoja, y la fila vacia dejaba un hueco. */
  .filters-inline { display: none; }
  .toolbar-row { flex-direction: column; align-items: stretch; }
  .toolbar-row .cbx, .toolbar-row .cbx-flt, .toolbar-row .date-range { width: 100%; flex: 0 0 auto; }
  .toolbar-row .cbx-flt .input { height: var(--control-h); }
  .toolbar-row .date-range { justify-content: space-between; }
}

/* ==========================================================================
   FICHAS DE CORREO — el buzon de disputas de cada plataforma
   --------------------------------------------------------------------------
   Varias direcciones en un campo. La coma cierra una ficha; una ficha se
   quita de un clic y se REABRE de otro, que es lo que un campo de texto con
   "a@a.com, b@b.com, c@c.com" no deja hacer: para borrar la del medio hay que
   contar comas.

   La caja imita a .input —mismo borde, mismo radio, mismo aro de foco— porque
   es un campo: si se viera como otra cosa, nadie escribiria dentro.
   ========================================================================== */
.tags {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1);
  min-height: var(--control-h); padding: 4px 6px;
  border: 1px solid var(--border-control); border-radius: var(--radius-md);
  background: var(--bg-card); cursor: text;
}
.tags:focus-within { border-color: var(--accent); box-shadow: var(--ring); }
.tag {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--accent-soft); color: var(--accent-ink);
  border-radius: var(--radius-sm); font-size: 12.5px; font-weight: 600;
  max-width: 100%;
}
.tag-txt {
  border: 0; background: none; color: inherit; font: inherit; cursor: pointer;
  padding: 3px 2px 3px var(--space-2); border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tag-txt:hover { text-decoration: underline; }
.tag-x {
  border: 0; background: none; color: inherit; cursor: pointer;
  font-size: 15px; line-height: 1; padding: 3px var(--space-2) 3px 2px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; opacity: .65;
}
.tag-x:hover { opacity: 1; background: var(--danger-soft); color: var(--danger-ink); }
/* Una direccion que no cuadra se marca AQUI, al escribirla, no al guardar:
   el servidor la rechaza igual, pero para entonces ya se perdio el sitio. */
.tag.is-bad { background: var(--danger-soft); color: var(--danger-ink); }
.tag-in {
  flex: 1 1 12ch; min-width: 12ch;
  border: 0; background: none; outline: none; padding: 4px 2px; font-size: 13.5px;
  color: var(--text-main);
}
.tag-in::placeholder { color: var(--text-soft); }
@media (pointer: coarse) { .tag-txt, .tag-x { min-height: 32px; } }

/* ---- la X de limpiar, a su tamano ------------------------------------- */
/* Era un cuadrado de 34px dentro de un campo de 34px de alto: tocaba el borde
   de arriba y el de abajo, y en los filtros —que usan el alto pequeno— se
   salia. Y con el combobox habia ademas una flecha al lado, asi que hacian
   falta dos huecos y solo se reservaba uno: el texto pasaba por debajo. */
.input-x { width: 26px; height: 26px; right: 5px; }
.input-x svg { width: 14px; height: 14px; }
.input-clear > .input { padding-right: 34px; }
.cbx:has(.cbx-arrow) .input { padding-right: 54px; }
.cbx:has(.cbx-arrow) .input-x { right: 26px; }

/* ---- una cifra que se puede abrir --------------------------------------- */
/* Un KPI que enlaza a su desglose tiene que PARECER que se puede pulsar, o el
   desglose no existe para quien no lo intente. Y tiene que seguir leyendose
   como una cifra, no como un boton: la senal es el cursor, un realce al pasar
   y una flecha discreta, no un relleno de color. */
a.kpi.is-link { display: block; text-decoration: none; color: inherit; position: relative;
                transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease); }
a.kpi.is-link::after {
  content: "›"; position: absolute; top: var(--space-3); right: var(--space-3);
  font-size: 17px; line-height: 1; color: var(--text-soft); opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
a.kpi.is-link:hover { border-color: var(--accent); box-shadow: var(--shadow-card); }
a.kpi.is-link:hover::after, a.kpi.is-link:focus-visible::after { opacity: 1; }


/* El codigo de un rol, en la tabla de nombres de Ajustes. No es un campo:
   no se puede editar, y por eso no se pinta como uno. Monoespaciado porque
   es un identificador, y apagado porque lo que se lee es la columna de al
   lado. */
.mono-code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12.5px; color: var(--text-muted);
  display: flex; align-items: center; min-height: var(--control-h);
}

/* ==========================================================================
   EL "?" QUE EXPLICA — la ayuda, donde se toma la decision
   --------------------------------------------------------------------------
   Un desplegable que dice "Staff" y "Manager" no explica nada: quien reparte
   accesos por primera vez tiene que adivinar, y quien lo hace por decima vez
   sigue sin estar seguro de si un Manager puede borrar. Este "?" pone la
   lista de permisos a un toque del sitio donde se elige.

   Tamaño: 18px de circulo dentro de un area pulsable de 28. Mas pequeño y
   en un telefono no se acierta; mas grande y compite con la etiqueta a la
   que acompaña, que es lo que de verdad hay que leer primero.

   El globo NO es una tarjeta mas: no lleva radio grande ni borde marcado.
   Es una nota que aparece encima de todo y se va sola. Por eso sombra y un
   trazo casi invisible, nada mas (§ profundidad en vez de trazos).
   ========================================================================== */
.help-dot {
  display: inline-grid; place-items: center;
  width: 28px; height: 28px; margin: 0 0 0 2px; padding: 0;
  background: none; border: 0; cursor: help;
  font: inherit; font-size: 11px; font-weight: 700; line-height: 1;
  color: var(--text-muted);
  vertical-align: middle;
}
/* El circulo se dibuja aparte del area pulsable: el dedo necesita 28px y el
   ojo no necesita ver una diana de 28px al lado de cada etiqueta. */
.help-dot-i {
  display: grid; place-items: center; width: 18px; height: 18px;
  border: 1px solid var(--border-control); border-radius: 99px;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.help-dot:hover, .help-dot[aria-expanded="true"] { color: var(--accent-ink, var(--accent)); }
.help-dot:hover .help-dot-i, .help-dot[aria-expanded="true"] .help-dot-i {
  background: var(--accent-soft); border-color: var(--accent);
}
.help-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 99px; }

/* En las filas que ya son flex el "?" es un hijo mas y el gap del contenedor
   lo separa demasiado de lo que explica: un icono de ayuda tiene que leerse
   pegado a su control, no como un cuarto elemento de la fila. */
.acc-row > .help-dot, .acc-add > .help-dot,
.perm-role > .help-dot, .preset-row > .help-dot { margin-left: -6px; }
.drawer-amt .help-dot { margin-left: 0; }

/* Un control y su "?" en la misma linea dentro de un campo de formulario. */
.field-inline { display: flex; align-items: center; gap: var(--space-2); }
.field-inline > .select { flex: 1 1 auto; min-width: 0; }

.help-pop {
  position: fixed; z-index: 10002;
  width: min(300px, calc(100vw - var(--space-6)));
  background: var(--bg-card); color: var(--text-main);
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
  padding: var(--space-3) var(--space-4);
  font-size: 12.5px; line-height: 1.5;
  /* Once permisos y una frase encima llegan a 340px de alto: en un telefono
     tumbado eso es la pantalla entera. Se corta y se hace rodar por dentro —
     el que abre el globo sabe que hay mas porque lo ve cortado. */
  max-height: min(62vh, 420px); overflow-y: auto; overscroll-behavior: contain;
  animation: helpIn var(--dur-fast) var(--ease-enter, var(--ease));
}
@keyframes helpIn { from { transform: translateY(-4px); opacity: 0 } }
.help-pop b { display: block; font-size: 13px; color: var(--text-main); margin-bottom: 2px; }
.help-pop p { margin: 0 0 var(--space-2); color: var(--text-muted); }
/* La lista de lo que SI puede, en dos columnas si caben: son frases cortas y
   en una sola columna el globo se vuelve una tira larguisima. */
.help-pop ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
.help-pop li { color: var(--text-main); padding-left: 14px; position: relative; }
.help-pop li::before {
  content: ''; position: absolute; left: 3px; top: 7px;
  width: 5px; height: 5px; border-radius: 99px; background: var(--success);
}
/* Lo que NO puede se dice tambien: la mitad de las dudas sobre un rol son
   sobre lo que no alcanza, y callarlo obliga a probar. */
.help-pop li.no { color: var(--text-muted); }
.help-pop li.no::before { background: var(--text-soft); opacity: .55; }
.help-pop .help-foot {
  display: block; margin-top: var(--space-2); padding-top: var(--space-2);
  border-top: 1px solid var(--hairline);
  font-size: 11.5px; color: var(--text-muted);
}

/* ==========================================================================
   ZONA DE PELIGRO — regla B2, en su forma mas fuerte
   --------------------------------------------------------------------------
   "Delete permanently" vivia en la misma fila y con el mismo espaciado que
   "Deactivate", que es justo la accion con la que se confunde: las dos
   quitan la company de en medio, pero una se deshace y la otra destruye el
   historial entero.

   Un hueco no basta cuando las dos palabras se parecen tanto. Esto le da
   contenedor propio, borde de peligro y una frase que dice que hace y cual
   es la alternativa. La regla B2 dice "nunca pegado"; aqui hace falta ademas
   "nunca parecido".
   ========================================================================== */
/* La hermana tranquila de .danger-zone: mismo esqueleto, sin la alarma.
   Para lo que es grave pero se deshace —desactivar una cuenta—. Compartir la
   caja roja con lo que destruye es lo contrario de D10: en vez de
   distinguirlas, las iguala. */
.soft-zone {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--bg-card-soft);
}
.soft-zone > div { flex: 1 1 260px; min-width: 0; }
.soft-zone > div > b { display: block; font-size: 13.5px; color: var(--text-main); }
.soft-zone > div > span { display: block; font-size: 12.5px; color: var(--text-muted); line-height: 1.5; margin-top: 2px; }
.soft-zone .btn { flex: 0 0 auto; }

.danger-zone {
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger); border-radius: var(--radius-md);
  background: var(--danger-soft);
}
.danger-zone > div { flex: 1 1 260px; min-width: 0; }
/* HIJO DIRECTO. Escrito como `.danger-zone b` alcanzaba tambien al <b> de
   dentro del parrafo —el que nombra "Deactivate"— y lo sacaba a su propia
   linea y en rojo, como si fuera un segundo titulo. Es la misma trampa que
   `.field label` con las casillas: una regla generica pisando a un elemento
   que solo queria ir en negrita. */
.danger-zone > div > b { display: block; font-size: 13.5px; color: var(--danger-ink); }
.danger-zone > div > span { display: block; font-size: 12.5px; color: var(--text-muted); line-height: 1.5; margin-top: 2px; }
.danger-zone > div > span b { color: inherit; font-weight: 600; }
.danger-zone .btn { flex: 0 0 auto; }

/* Un hueco que se puede arreglar. No es una raya muerta: es la unica cosa de
   la fila que se puede pulsar, y por eso se pinta como un enlace de accion y
   no como un dato. Discreto —no compite con las cifras— pero visiblemente
   distinto de un "—". */
.fix-me {
  font-size: 12.5px; font-weight: 600; color: var(--accent);
  text-decoration: none; border-bottom: 1px dashed currentColor;
}
.fix-me:hover, .fix-me:focus-visible { color: var(--accent-ink); border-bottom-style: solid; }
/* La variante boton del enlace-arreglo: mismo aspecto, elemento correcto.
   Dentro de un cajon lo que abre un formulario es un <button>, no un <a>. */
button.fix-me { background: none; border: 0; border-bottom: 1px dashed currentColor;
                padding: 0; cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 600; }


/* ==========================================================================
   EL CATALOGO DE ROLES — Settings › People
   --------------------------------------------------------------------------
   Una lista de filas que se abren en su sitio. Ni modal ni pantalla aparte:
   un nombre y once casillas es un formulario corto, y el modal ademas
   obligaria a abrir un segundo modal para confirmar el alcance, que es lo
   que prohibe M4.

   La fila lleva cuatro datos y en este orden: NOMBRE (lo que se busca),
   cuantos permisos (el tamaño), A CUANTA GENTE ALCANZA (lo que hay que saber
   antes de tocarlo) y el boton. El alcance va en su color: "nobody yet" es
   informacion tranquila y "9 people" es una advertencia.
   ========================================================================== */
.role-list { display: flex; flex-direction: column; gap: var(--space-2); }
.role-row {
  border: 1px solid var(--hairline); border-radius: var(--radius-md);
  background: var(--bg-card); overflow: hidden;
  transition: box-shadow var(--dur-fast) var(--ease);
}
.role-row.is-open { box-shadow: var(--shadow-card); }
.role-line {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4); min-height: var(--tap-h);
}
.role-name { font-size: 14px; font-weight: 700; }
.role-count { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* El alcance empuja al boton al borde y se queda pegado a el: es la cifra
   que hay que leer justo antes de pulsarlo. */
.role-reach {
  margin-left: auto; font-size: 12.5px; font-weight: 600;
  color: var(--warning); font-variant-numeric: tabular-nums;
}
.role-reach.is-none { color: var(--text-soft); font-weight: 500; }
.role-row.is-reserved { background: var(--bg-card-soft); }

.role-edit { border-top: 1px solid var(--hairline); }
.role-form { padding: var(--space-4); }
/* La rejilla de permisos reusa .perm-check, que ya existe y ya es accesible.
   Dos columnas en cuanto hay sitio: once casillas en una sola son una tira
   larguisima que obliga a rodar para ver la ultima. */
.role-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-2); margin: var(--space-3) 0; }
.role-form-foot {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
}
@media (min-width: 640px) {
  .role-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .role-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* En el telefono la fila se apila y el alcance deja de estar pegado al boton:
   pierde el `margin-left: auto` para no quedarse solo en una linea. */
@media (max-width: 639px) {
  /* EL NOMBRE MANDA Y OCUPA SU LINEA; debajo, sus dos datos; y "Edit" a la
     derecha, con su tamaño. Estaba a ancho completo y centrado, asi que
     quedaba flotando lejos de la fila a la que pertenece y con un agujero en
     medio — y cuatro botones grandes seguidos pesan mas que el contenido.
     Es una accion secundaria repetida, no la accion de la pantalla. */
  .role-line { align-items: center; row-gap: var(--space-2); padding: var(--space-3); }
  .role-name { flex: 1 1 100%; }
  .role-reach { margin-left: 0; }
  .role-line .btn { margin-left: auto; }
  /* En escritorio el .spacer manda a Delete al otro extremo. Aqui no hay
     extremo: los tres botones se reparten 275px y lo destructivo acaba a
     dieciseis pixeles de Cancel, que es "pegado" para lo que pide B2. El
     spacer pasa a ocupar la fila entera y parte la linea, asi que Delete se
     queda solo abajo y con aire propio. */
  .role-form-foot { flex-wrap: wrap; }
  .role-form-foot .btn { flex: 1 1 auto; }
  /* EL SEPARADOR ES EL SEPARADOR, no el boton.
     Le puse el filete y el radio al propio .btn-danger-outline, que YA tiene
     su borde rojo: salio una caja con dos esquinas cuadradas y dos redondas,
     y un trazo gris cruzandole por arriba. La raya la pinta el .spacer, que
     es lo que de verdad separa las dos lineas; el boton se queda como
     cualquier otro boton. */
  .role-form-foot .spacer {
    flex: 0 0 100%; height: 0; margin-top: var(--space-3);
    border-top: 1px solid var(--hairline);
  }
  .role-form-foot .btn-danger-outline { flex: 1 1 100%; margin-top: var(--space-3); }
}

/* ==========================================================================
   DAR ACCESO A ALGO QUE TODAVIA NO TIENE
   --------------------------------------------------------------------------
   La lista de arriba es lo CONCEDIDO. Esto es la puerta para traer una
   company o una store nueva, y va visualmente aparte de las filas de rol
   —fondo tenue y separado por arriba— porque es una accion, no un dato mas
   de la lista. Antes esa puerta no existia: se enseñaban las cuarenta
   companies del alcance con "No access" puesto, y elegir era buscar.
   ========================================================================== */
.perm-give {
  margin-top: var(--space-4); padding: var(--space-3) var(--space-4);
  background: var(--bg-card-soft); border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
}
.perm-give > label {
  display: block; font-size: 12.5px; font-weight: 600;
  color: var(--text-muted); margin-bottom: var(--space-2);
}
.perm-give-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.perm-give-row .select { flex: 1 1 200px; min-width: 0; height: var(--control-h-sm); font-size: 12.5px; }
.perm-give .hint { margin: var(--space-2) 0 0; }
/* Dentro de una company es la misma pieza en pequeño: sin marco propio, para
   que no parezca otra company. */
.perm-give-sub { margin-top: var(--space-2); background: none; border: 0; padding: 0 var(--space-1, 0); }

/* ==========================================================================
   PEOPLE ON THIS COMPANY — repartir accesos desde la company
   --------------------------------------------------------------------------
   La fila es identidad + rol + acciones. Se apila en el telefono porque un
   <select> y un boton al lado de un nombre largo no caben en 375px sin que el
   nombre se coma a si mismo, que es el mismo error que ya se corrigio en la
   fila de store.
   ========================================================================== */
.acc-row .acc-role { height: var(--control-h-sm); width: auto; font-size: 12.5px; }
/* El alcance es un BOTON con aspecto de dato: dice "All 3 stores" y se puede
   abrir. Pintarlo como un boton de verdad lo pondria al nivel de Remove, que
   es la accion de la fila; pintarlo como texto plano escondería que se toca.
   La flecha es la afordancia (regla I2). */
.acc-scope {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: 4px 2px; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--text-muted);
  border-bottom: 1px dashed var(--border-control);
}
.acc-scope svg { width: 13px; height: 13px; transition: transform var(--dur-fast) var(--ease); }
.acc-scope[aria-expanded="true"] svg { transform: rotate(90deg); }
.acc-scope:hover, .acc-scope:focus-visible { color: var(--accent-ink); border-bottom-color: var(--accent); }
/* Acotado a unas pocas: se dice con palabras en la propia etiqueta, y ademas
   con peso, que no depende del color (§5.6). */
.acc-scope.is-partial { color: var(--warning); font-weight: 600; }
.acc-row.is-admin { color: var(--text-muted); }

/* El detalle por store ocupa la fila entera, debajo. */
.acc-stores {
  flex: 1 1 100%; margin-top: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md);
  background: var(--bg-card-soft); border: 1px solid var(--hairline);
}
.acc-store-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-2); }
.acc-store-foot { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
@media (min-width: 640px) { .acc-store-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .acc-store-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.acc-row .acc-out { color: var(--danger-ink); }
.acc-row .acc-out:hover { background: var(--danger-soft); }
.acc-add {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
}
/* 150 y no 200: en el cajon de 400px, con el rol, su "?" y Add al lado,
   200 mandaba el boton Add solo a una segunda linea. */
.acc-add .cbx { flex: 1 1 150px; min-width: 0; }
.acc-add .input { height: var(--control-h-sm); width: 100%; font-size: 13px; }
.acc-add .select { height: var(--control-h-sm); width: auto; font-size: 12.5px; }
@media (max-width: 639px) {
  .acc-add { flex-direction: column; align-items: stretch; }
  /* `flex: 1 1 200px` mide el eje PRINCIPAL, y al apilarse el principal pasa a
     ser el vertical: los 200px de ancho se convertian en 200px de ALTO y
     dejaban un agujero en blanco entre el buscador y el rol, con la flecha del
     desplegable flotando sola en medio. Aqui la base se suelta. */
  .acc-add .cbx { flex: 0 0 auto; }
  .acc-add .select, .acc-add .btn, .acc-add .field-inline { width: 100%; }
}

/* ==========================================================================
   PAGINAS DE VENTA — Performance y Review Management
   --------------------------------------------------------------------------
   Venden dos servicios que todavia no existen (DESIGN-RULES V1-V6). Segunda
   version, 4-oct-2026: la primera era correcta pero de plantilla —tarjetas
   blancas, iconos pequeños, chips y listas, la misma composicion dos veces—.
   Ahora cada pagina tiene su pieza, sacada del trabajo real de JB:

   - Performance: hojas de reporte de las plataformas y la hoja de JB que
     responde tres preguntas, con la ultima bajo una doble linea, como el
     total de una cuenta.
   - Review Management: un solo ticket con su talon troquelado. La parte
     grande es de JB; el talon, del propietario, con sus decisiones como
     etiquetas colgantes.

   Mismo sistema que el panel: tokens, Inter (--font), azul y neutros.

   EL MOVIMIENTO cuenta cada historia una vez y se queda quieto (skill
   `animate` de Emil Kowalski: proposito «explicacion», animacion CSS, solo
   transform, opacity y clip-path, curva de salida fuerte —--ease-enter—,
   escalonado de 30-80ms). Solo se anima la ilustracion: el texto se lee
   desde el primer fotograma. Nada pasa de --dur-slow por pieza (A3) y la
   secuencia entera acaba antes de 800ms. Lo que al cargar queda fuera de
   la pantalla espera a aparecer (sales.js, .is-pending). Con movimiento
   reducido, el estado final directamente.
   ========================================================================== */
.sale {
  display: grid; gap: calc(var(--space-8) * 1.5);
  max-width: 1120px; padding-block: var(--space-6) var(--space-8);
}
/* «Coming Soon» grande y en el color de la marca; la promesa, justo debajo:
   que viene, que resuelve y que hace JB, en el mismo vistazo. */
.sale-soon {
  margin: 0; font-size: clamp(44px, 6.8vw, 84px); line-height: .92;
  font-weight: 800; letter-spacing: -.045em; color: var(--accent);
}
.sale-title {
  margin: 0; font-size: clamp(24px, 3vw, 34px); line-height: 1.15;
  font-weight: 700; letter-spacing: -.025em; color: var(--text-main); text-wrap: balance;
}
.sale-lead { margin: 0; font-size: 17px; line-height: 1.6; color: var(--text-main); max-width: 52ch; }
.sale-foot { margin: 0; max-width: 72ch; font-size: 13px; line-height: 1.6; color: var(--text-muted); }

/* El paso a la otra pagina: un enlace de texto, no otra tarjeta. */
.sale-next { display: flex; }
.sale-next-link {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--tap-h);
  font-size: 14px; color: var(--text-muted); text-decoration: none;
  transition: transform var(--dur-fast) var(--ease-enter);
}
.sale-next-link b { font-weight: 600; color: var(--accent-ink); }
.sale-next-link svg { width: 16px; height: 16px; color: var(--accent-ink); transition: transform var(--dur-fast) var(--ease-enter); }
@media (hover: hover) and (pointer: fine) {
  .sale-next-link:hover b { text-decoration: underline; text-underline-offset: 3px; }
  .sale-next-link:hover svg { transform: translateX(3px); }
}
.sale-next-link:active { transform: scale(.98); }
.sale-next-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ==================================================== PERFORMANCE ====== */
/* Asimetrico: el mensaje a la izquierda, el proceso a la derecha, con mas
   peso. Por debajo de 1200px, uno encima del otro: a dos columnas, la
   ilustracion no cabia a un tamaño legible. */
.perf-hero { display: grid; gap: var(--space-8); align-items: center; }
@media (min-width: 1200px) {
  .perf-hero { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--space-8); }
}
.perf-copy { display: grid; gap: var(--space-4); max-width: 560px; }
/* En la columna del hero, «Coming Soon» cabe en una linea: partido en dos
   pesaba mas que el servicio. */
@media (min-width: 1200px) { .sale--perf .sale-soon { font-size: clamp(56px, 5vw, 76px); white-space: nowrap; } }

/* La mesa: dos hojas de reporte de las plataformas, tal como llegan —
   torcidas, apiladas, cada una con su nombre y sus categorias reales— y la
   hoja de JB, derecha y ordenada, montada encima. El contraste entre las
   dos es el servicio. La colocacion va en `translate`/`rotate` sueltos y la
   animacion en `transform`: asi se suman y no se pisan. */
.desk { margin: 0; display: grid; align-items: center; max-width: 640px; }
@media (min-width: 640px) { .desk { grid-template-columns: minmax(0, .95fr) minmax(0, 1.15fr); } }
.desk-reports { display: grid; justify-items: start; }
.desk-report {
  grid-area: 1 / 1; width: 70%; padding: 12px 14px 10px;
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: 6px;
  box-shadow: var(--shadow-card);
}
.desk-report--a { rotate: -4deg; }
.desk-report--b { translate: 40% 34px; rotate: 3deg; }
.desk-report-head {
  margin: 0 0 6px; padding-bottom: 7px; border-bottom: 1px dashed var(--border-control);
  display: grid; gap: 1px;
}
.desk-report-head b { font-size: 13px; font-weight: 700; color: var(--text-main); }
.desk-report-head span { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
.desk-report-lines { list-style: none; margin: 0; padding: 0; }
.desk-report-lines li {
  font-size: 12px; line-height: 1.25; color: var(--text-muted);
  padding-block: 4px; border-bottom: 1px solid var(--hairline);
}
.desk-report-lines li:last-child { border-bottom: 0; }
.desk-step { margin: 0; font-size: 13px; font-weight: 600; color: var(--accent-ink); }
.desk-reports .desk-step { margin-top: calc(34px + var(--space-3)); }
@media (max-width: 639px) {
  /* En el telefono las hojas se achican para que la de JB quede a la vista
     sin bajar demasiado; las categorias se siguen leyendo. */
  .desk-report { width: 62%; padding: 10px 12px 8px; }
  .desk-report--b { translate: 54% 26px; }
  .desk-report-lines li { font-size: 11.5px; padding-block: 3px; }
  .desk-reports .desk-step { margin-top: calc(26px + var(--space-3)); }
}

.desk-sheet {
  position: relative; z-index: 1; display: grid; gap: var(--space-3);
  padding: var(--space-5); margin-top: calc(var(--space-6) * -1);
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
}
@media (min-width: 640px) { .desk-sheet { margin: 0 0 0 -14%; } }
.desk-by {
  display: flex; align-items: center; gap: var(--space-2); margin: 0;
  font-size: 11.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}
.desk-mark {
  display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px;
  background: var(--accent); color: var(--on-accent, var(--bg-card));
  font-size: 10px; font-weight: 800; letter-spacing: -.02em;
}
.desk-qs { margin: 0; }
.desk-q { padding-block: 11px; border-top: 1px solid var(--hairline); }
.desk-q dt { font-size: 16px; font-weight: 700; letter-spacing: -.01em; color: var(--text-main); }
.desk-q dd { margin: 3px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--text-muted); }
/* La respuesta, bajo una doble linea: el total de la cuenta. */
.desk-q.is-total { margin-top: 2px; border-top: 3px double var(--border-control); padding-bottom: 0; }
.desk-q.is-total dd { font-size: 15px; font-weight: 700; color: var(--accent-ink); }

/* Tres grupos como columnas de un periodico: una raya arriba, un titulo y
   una frase. Sin iconos ni tarjetas. */
.perf-groups { display: grid; gap: var(--space-6); }
@media (min-width: 760px) { .perf-groups { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-8); } }
.perf-group { padding-top: var(--space-3); border-top: 2px solid var(--text-main); }
.perf-group h3 { margin: 0 0 6px; font-size: 16px; font-weight: 700; letter-spacing: -.01em; color: var(--text-main); }
.perf-group p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--text-muted); }

/* Movimiento: los reportes caen sobre la mesa, la hoja de JB sale de ellos
   y sus tres respuestas se asientan en orden. Acaba en 740ms. */
@keyframes desk-drop   { from { opacity: 0; transform: translateY(-16px); } }
@keyframes desk-gather { from { opacity: 0; transform: translateX(-28px) scale(.97); } }
@keyframes desk-settle { from { opacity: 0; transform: translateY(6px); } }
@keyframes desk-fade   { from { opacity: 0; } }
.desk .desk-report--a { animation: desk-drop var(--dur-slow) var(--ease-enter) both; }
.desk .desk-report--b { animation: desk-drop var(--dur-slow) var(--ease-enter) 70ms both; }
.desk .desk-reports .desk-step { animation: desk-fade var(--dur-base) var(--ease-enter) 140ms both; }
.desk .desk-sheet { transform-origin: left center; animation: desk-gather var(--dur-slow) var(--ease-enter) 200ms both; }
.desk .desk-q { animation: desk-settle 240ms var(--ease-enter) both; }
.desk .desk-q:nth-child(1) { animation-delay: 380ms; }
.desk .desk-q:nth-child(2) { animation-delay: 440ms; }
.desk .desk-q:nth-child(3) { animation-delay: 500ms; }
.desk .desk-sheet > .desk-step { animation: desk-fade var(--dur-base) var(--ease-enter) 540ms both; }

/* ============================================== REVIEW MANAGEMENT ====== */
.rev-hero { display: grid; gap: var(--space-4); max-width: 760px; }

/* El ticket: una sola pieza. La parte de JB, a lo ancho, con renglones como
   una comanda; el troquel; y el talon del propietario, tintado. El troquel
   es el limite entre lo que hace JB y lo que solo decide el dueño. */
.pact {
  margin: 0; display: grid; overflow: hidden;
  background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-xl);
  box-shadow: var(--shadow-soft);
}
@media (min-width: 900px) { .pact { grid-template-columns: minmax(0, 1.35fr) 20px minmax(0, 1fr); } }
.pact-jb, .pact-you { display: grid; gap: var(--space-2); align-content: start; padding: var(--space-6); }
.pact-you { background: var(--accent-soft); }
.pact-who { margin: 0; font-size: 11.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted); }
.pact-you .pact-who { color: var(--accent-ink); }
.pact h3 { margin: 0 0 var(--space-2); font-size: clamp(20px, 2.2vw, 26px); line-height: 1.2; font-weight: 700; letter-spacing: -.02em; color: var(--text-main); }
.pact-lines { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }
.pact-lines li { padding-block: 12px; border-bottom: 1px solid var(--hairline); font-size: 16px; line-height: 1.4; color: var(--text-main); }
/* Las decisiones del propietario, como etiquetas colgantes: dicen QUE
   decide, sin un valor y sin nada que se pueda tocar. */
.pact-tags { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 10px 8px; }
.pact-tags li {
  position: relative; padding: 7px 14px 7px 24px;
  font-size: 14px; font-weight: 600; color: var(--accent-ink); background: var(--bg-card);
  clip-path: polygon(11px 0, 100% 0, 100% 100%, 11px 100%, 0 50%);
}
.pact-tags li::before {
  content: ""; position: absolute; left: 10px; top: 50%; width: 5px; height: 5px; margin-top: -2.5px;
  border-radius: 50%; background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--border-control);
}
/* El troquel: una linea de puntos entre dos muescas del color del fondo. */
.pact-tear {
  position: relative; min-height: 20px;
  background:
    radial-gradient(circle at 50% 0, var(--bg-page) 9px, transparent 9.5px),
    radial-gradient(circle at 50% 100%, var(--bg-page) 9px, transparent 9.5px);
}
.pact-tear::before {
  content: ""; position: absolute; top: 16px; bottom: 16px; left: 9px;
  border-left: 2px dashed var(--border-control);
}
@media (max-width: 899px) {
  .pact-tear {
    background:
      radial-gradient(circle at 0 50%, var(--bg-page) 9px, transparent 9.5px),
      radial-gradient(circle at 100% 50%, var(--bg-page) 9px, transparent 9.5px);
  }
  .pact-tear::before { top: 9px; bottom: auto; left: 16px; right: 16px; border-left: 0; border-top: 2px dashed var(--border-control); }
}
.pact-cap {
  grid-column: 1 / -1; padding: 10px var(--space-6) 12px;
  border-top: 1px solid var(--hairline); font-size: 12.5px; color: var(--text-muted);
}

/* Lo comercial, una sola vez y en una franja: la tarifa paga el servicio;
   los rewards siguen otro presupuesto y las reglas del propietario. */
.rev-terms {
  display: grid; gap: 4px var(--space-8); padding-block: var(--space-4);
  border-block: 1px solid var(--border-subtle);
}
@media (min-width: 900px) { .rev-terms { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); align-items: baseline; } }
.rev-terms h2 { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.01em; color: var(--text-main); }
.rev-terms p { margin: 0; font-size: 15px; line-height: 1.55; color: var(--text-muted); max-width: 62ch; }

/* Movimiento: primero lo que hace JB; despues se marca el troquel; despues
   llega el talon del propietario, con sus decisiones juntas. Acaba en 740ms. */
@keyframes pact-in      { from { opacity: 0; transform: translateY(10px); } }
@keyframes pact-stub    { from { opacity: 0; transform: translateX(18px); } }
@keyframes pact-stub-up { from { opacity: 0; transform: translateY(12px); } }
@keyframes pact-cut-y   { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }
@keyframes pact-cut-x   { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
.pact .pact-jb { animation: pact-in var(--dur-slow) var(--ease-enter) both; }
.pact .pact-tear::before { animation: pact-cut-y 240ms var(--ease-enter) 260ms both; }
.pact .pact-you { animation: pact-stub var(--dur-slow) var(--ease-enter) 420ms both; }
@media (max-width: 899px) {
  .pact .pact-tear::before { animation-name: pact-cut-x; }
  .pact .pact-you { animation-name: pact-stub-up; }
}

/* ---- lo que espera a aparecer (sales.js) ---------------------------------
   Invisible y FUERA de la pantalla: nadie lo ve esperar. Al quitarse la
   clase, cada pieza recupera su animacion y entra desde el principio. */
.is-pending .desk-report, .is-pending .desk-sheet, .is-pending .desk-q, .is-pending .desk-step,
.is-pending .pact-jb, .is-pending .pact-you, .is-pending .pact-tear::before {
  animation: none; opacity: 0;
}

/* ---- pasar de una a otra -------------------------------------------------
   View Transitions entre documentos, del propio navegador (las dos paginas
   la piden con 'viewTransition' en ui_head). La barra de navegacion lleva su
   propio nombre, asi que se queda quieta; lo demas se funde. La salida, mas
   corta que la entrada. */
.app:has(.sale) .nav { view-transition-name: app-nav; }
::view-transition-old(root) { animation: sale-vt-out 140ms var(--ease) both; }
::view-transition-new(root) { animation: sale-vt-in var(--dur-base) var(--ease-enter) both; }
@keyframes sale-vt-out { to { opacity: 0; } }
@keyframes sale-vt-in { from { opacity: 0; } }

/* Menos movimiento: el estado final directamente. El bloque general de
   arriba deja las duraciones en .01ms pero no toca los retrasos: sin esto,
   las piezas escalonadas aparecerian de golpe, pero tarde. */
@media (prefers-reduced-motion: reduce) {
  .sale *, .sale *::before, .sale *::after { animation-delay: 0s !important; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ==================================================================
   PERFORMANCE (admin) — captura de reportes de plataforma
   ------------------------------------------------------------------
   La hoja tiene la forma de la de JB: una etiqueta y su cifra por fila,
   con cortes entre bloques y Total Fees como fila calculada. Las celdas
   no cambian nunca de tamaño: el resultado de una cuenta sale en un globo
   (.cell-pop) mientras se escribe, y en la celda al salir de ella.
   ================================================================== */
/* La lista es la de Disputes (6-oct-2026): tarjetas en el telefono, tabla
   en el escritorio y un cajon al pulsar. Bajo cada cifra de dinero, su % de
   Sales. */
table.data.rep-list .sub { display: block; font-size: 11.5px; font-weight: 500; color: var(--text-soft); }
/* Lo que se quedo la plataforma: el tono de "espera", el mismo del pastel. */
.pill.fees { background: var(--pill-wait-bg); color: var(--pill-wait-fg); }

/* La hoja en el cajon: la misma, para leer, sin celdas. */
.rep-sheet.is-read .rep-row { grid-template-columns: minmax(0, 1fr) auto 4.25rem; padding: 7px var(--space-3); }
.rep-sheet.is-read .rep-val { padding-right: 0; font-weight: 600; }
.rep-sheet.is-read .rep-row.is-band .rep-val { font-weight: 700; }

.rep-modal { max-width: 620px; }
.rep-modal .date-range { width: 100%; }
.rep-modal .date-range.is-picker { height: var(--control-h); }
.rep-modal .date-range .input.date-f { flex: 1 1 0; min-width: 0; }
.rep-sheet { border: 1px solid var(--hairline); border-radius: var(--radius-sm); overflow: hidden; }
.rep-block + .rep-block { border-top: 1px solid var(--hairline); }
.rep-row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) 4.25rem; align-items: center;
  gap: 2px var(--space-3); padding: 6px var(--space-3);
}
.rep-lbl { font-size: 13px; font-weight: 600; color: var(--text-main); }
.rep-row .input { height: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.rep-row .field-err { grid-column: 2 / -1; margin: 0; }
/* El % de Sales, la tercera columna de la hoja de JB. */
.rep-pct { text-align: right; font-size: 12px; color: var(--text-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.rep-val { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; padding-right: var(--space-3); }
/* Las tres filas que resumen —lo vendido, lo que se quedo la plataforma, lo
   que llego— van teñidas, como en la hoja. Paid Out cierra con la doble
   linea de un total, la misma de la ilustracion de la pagina de venta. */
.rep-row.is-band { background: var(--accent-soft); }
.rep-row.is-band .rep-lbl { font-weight: 700; }
.rep-row.is-band .rep-pct { color: var(--accent-ink); font-weight: 600; }
.rep-row.is-total { border-top: 3px double var(--border-control); }
.rep-row.is-total .rep-lbl, .rep-row.is-total .rep-val { font-size: 14px; font-weight: 800; color: var(--accent-ink); }
.rep-block + .rep-block:has(> .is-total) { border-top: 0; }

/* El globo del resultado: la misma superficie que el globo del "?"
   (.help-pop), mas pequeño. Encima de la celda, alineado a su borde
   derecho, que es donde acaban los numeros. No recibe el raton: es una
   lectura, no un control. */
.cell-pop {
  position: fixed; z-index: 10002;
  max-width: min(280px, calc(100vw - var(--space-6)));
  background: var(--bg-card); color: var(--text-main);
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-card);
  padding: var(--space-2) var(--space-3);
  font-size: 12px; line-height: 1.45; text-align: right;
  pointer-events: none;
  animation: helpIn var(--dur-fast) var(--ease-enter, var(--ease));
}
.cell-pop b { display: block; font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
.cell-pop-keys { display: block; margin-top: 1px; font-size: 11.5px; color: var(--text-soft); }
.cell-pop-keys:empty { display: none; }
.cell-pop.is-bad b { font-size: 12.5px; font-weight: 600; color: var(--danger-ink); }

/* En el telefono sigue siendo una hoja: etiqueta y cifra en la misma fila. */
@media (max-width: 639px) {
  .rep-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr) 3.6rem; gap: 2px var(--space-2); padding: 6px var(--space-2) 6px var(--space-3); }
  .rep-lbl { font-size: 12.5px; }
  .rep-pct { font-size: 11.5px; }
  .rep-val { padding-right: var(--space-2); }
}

/* ==================================================================
   SUPPORT REQUESTS (14-oct-2026)
   ------------------------------------------------------------------
   La lista y el cajon son los de Disputes; aqui solo lo propio. Las
   pastillas reusan las capas --pill-* con el mismo significado: espera a
   alguien (wait), en marcha (live), bien (ok), mal (bad), terminado (done).
   Response Required lleva borde: es la unica que pide algo a quien mira.
   ================================================================== */
.pill.sr-new  { background: var(--pill-open-bg); color: var(--pill-open-fg); }
.pill.sr-open { background: var(--pill-live-bg); color: var(--pill-live-fg); }
.pill.sr-wait { background: var(--pill-wait-bg); color: var(--pill-wait-fg); }
.pill.sr-resp { background: var(--pill-a15-bg); color: var(--pill-a15-fg); border-color: var(--pill-a15-bd); }
.pill.sr-ok   { background: var(--pill-ok-bg);   color: var(--pill-ok-fg); }
.pill.sr-bad  { background: var(--pill-bad-bg);  color: var(--pill-bad-fg); }
.pill.sr-done { background: var(--pill-done-bg); color: var(--pill-done-fg); border-color: var(--pill-done-bd); }

/* La urgencia no es una etapa: va como texto con su punto, no como pastilla,
   para que en la fila no compitan tres pastillas iguales. */
.sr-urg { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; white-space: nowrap; }
.sr-urg i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.sr-urg.sr-done { color: var(--text-soft); }
.sr-urg.sr-open { color: var(--pill-wait-fg); }
.sr-urg.sr-bad  { color: var(--pill-bad-fg); }

/* "Needs your response": la marca de lo que espera a quien mira. */
.sr-needs { font-size: 11.5px; font-weight: 700; color: var(--pill-a15-fg); white-space: nowrap; }
.tab.tab-needs { border-color: var(--pill-a15-bd); color: var(--pill-a15-fg); }
.tab.tab-needs[aria-pressed="true"] { background: var(--pill-a15-bg); }
/* Sin franja lateral (regla A1): el aviso va en la superficie de la fila. */
table.data.sr-list tbody tr.needs-response:not(.is-open) { background: var(--pill-wait-bg); }
.dcard.sr-card.needs-response { border-color: var(--pill-a15-bd); }

table.data.sr-list .sr-title b { display: block; font-weight: 600; overflow-wrap: anywhere; }
table.data.sr-list .sub { display: block; font-size: 11.5px; font-weight: 500; color: var(--text-soft); }
.sr-hours { font-size: 13px; color: var(--text-muted); }
.sr-foot { row-gap: 6px; }

/* El cajon. */
.sr-head-pills { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.sr-desc {
  margin-bottom: var(--space-4); padding: var(--space-3);
  background: var(--bg-card-soft); border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
  font-size: 13.5px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere;
}
.sr-approve {
  margin-bottom: var(--space-4); padding: var(--space-3);
  border: 1px solid var(--accent); border-radius: var(--radius-md); background: var(--accent-soft);
}
.sr-approve p { margin: 0 0 var(--space-2); font-size: 13px; }
.sr-approve-acts { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.sr-billing {
  margin: 0 0 var(--space-4); padding: var(--space-3);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-card-soft);
  font-size: 13px;
}
.sr-billing .lbl { color: var(--text-muted); font-weight: 600; margin-right: var(--space-2); }
.sr-billing-calc { margin-top: 4px; font-variant-numeric: tabular-nums; color: var(--text-muted); }
.sr-billing .hint { margin: var(--space-2) 0 0; }
.sr-billing.is-warn { border-color: var(--pill-a15-bd); background: var(--warning-soft); }
.sr-billing.is-ok b { color: var(--success-ink); }

/* La conversacion: las respuestas del cliente con el tono de las notas; las
   de JB, con el del acento, para leer de un vistazo quien dijo que. */
.sr-timeline .tl-item.is-jb .tl-note { background: var(--accent-soft); }
.sr-timeline .tl-item.is-jb .tl-dot { background: var(--accent-soft); color: var(--accent-ink); border-color: transparent; }
.sr-effect { margin-top: 2px; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* Store y Urgency son columnas propias solo donde caben (como Customer y
   Cause en Disputes). Mas estrecho, van bajo el titulo de la solicitud: la
   tabla no se desplaza de lado y ningun dato del Excel se pierde. */
.sr-col-store, .sr-col-urg, .sr-col-date { display: none; }
.sr-sub-urg .sr-urg { font-size: 11.5px; }
@media (min-width: 1024px) {
  .sr-col-urg, .sr-col-date { display: table-cell; }
  .sr-sub-urg, .sr-sub-date { display: none; }
}
@media (min-width: 1280px) { .sr-col-store { display: table-cell; } .sr-sub-store { display: none; } }
/* El titulo es texto libre: se parte en lineas en vez de empujar la tabla. */
table.data.sr-list td.sr-title, table.data.sr-list td.sr-title .sub { white-space: normal; min-width: 160px; }
.sr-change { margin-left: var(--space-2); }
/* Support Requests: de donde sale la tarifa, y la lista de precios de Ajustes. */
.sr-billing-from { margin-top: 2px; font-size: 12px; color: var(--text-soft); }
.sr-billing .btn { margin-top: var(--space-2); }
.sp-store { padding-top: var(--space-2); border-top: 1px solid var(--hairline); }
.sp-store > b { display: block; font-size: 13px; margin-bottom: var(--space-2); }

/* Texto solo para lectores de pantalla: no se ve, se lee. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ==================================================================
   PERFORMANCE — A DONDE SE VA EL DINERO (14-oct-2026)
   ------------------------------------------------------------------
   Resumen (Sales, Paid Out, Net deductions, y la barra de dos partes
   cuando cuadra como reparto), lo que se desconto y lo que volvio. Las
   barras de concepto salen todas del mismo punto y miden el % de Sales:
   una pequeña es pequeña y su cifra va al lado. Cada concepto tiene su
   color, el mismo en la vista general y en el cajon.
   ================================================================== */
.wf-dot { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; flex: 0 0 auto; }
.pm-bar.is-offers,   .wf-dot.is-offers   { background: var(--chart-wait); }
.pm-bar.is-ads,      .wf-dot.is-ads      { background: var(--chart-cause); }
.pm-bar.is-comm,     .wf-dot.is-comm     { background: var(--chart-live-dot); }
.pm-bar.is-after-fees, .wf-dot.is-after-fees, .pm-reference-bar.is-after-fees { background: var(--chart-ok-dot); }
.pm-bar.is-shortfall, .wf-dot.is-shortfall { background: var(--chart-denied-dot); }
.pm-bar.is-refunds,  .wf-dot.is-refunds  { background: var(--chart-denied-dot); }
.pm-bar.is-recovered,.wf-dot.is-recovered{ background: var(--chart-ok); }
.pm-bar.is-misc,     .wf-dot.is-misc     { background: var(--chart-denied); }

.perf-mix { padding: var(--space-4); margin-bottom: var(--space-3); }
.perf-mix .pm { margin-bottom: 0; }
.pm { margin: 0 0 var(--space-4); }
.pm-head { margin-bottom: var(--space-3); }
.pm-title { margin: 0; font-size: 15px; font-weight: 700; }
.pm-scope { margin: 2px 0 0; font-size: 12.5px; color: var(--text-muted); }
.pm-k { display: block; font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-soft); }
.drawer-amt .pm-k { text-align: right; }
.pm-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); }
.pm-sum.is-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pm-rows { margin-top: var(--space-3); }
.request-access { grid-column: 1 / -1; }
.request-stores > .perm-check { margin-bottom: var(--space-3); }
.comment-edited { color: var(--text-soft); font-size: 11.5px; margin-right: var(--space-2); }
.sr-delete { margin: var(--space-3) 0; }
.pm-big { display: block; font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.02em; margin-top: 2px; }
.pm.is-compact .pm-big { font-size: 18px; }
.pm-big.is-paid { color: var(--success-ink); }
.pm-sub { display: block; font-size: 12px; color: var(--text-soft); font-variant-numeric: tabular-nums; }
.pm-split { display: flex; height: 18px; margin-top: var(--space-3); border-radius: 6px; overflow: hidden; background: var(--chart-track); }
.pm-split > span { display: block; height: 100%; }
.pm-comparison { margin-top: var(--space-4); }
.pm-reference { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 4px var(--space-3); font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.pm-comparison .pm-split { margin-top: 6px; height: 22px; }
.pm-split .pm-bar { border-radius: 0; }
.pm-reference-bars > div { display: grid; grid-template-columns: 5.5rem minmax(0,1fr) auto; gap: var(--space-2); align-items: center; margin: 6px 0; font-size: 12px; font-variant-numeric: tabular-nums; }
.pm-reference-bar { display: block; height: 100%; background: var(--text-soft); }
.pm-reference-bar.is-credit { background: repeating-linear-gradient(135deg,var(--text-soft),var(--text-soft) 3px,transparent 3px,transparent 6px); }
.cbx-multi { border: 1px solid var(--border-control); border-radius: var(--radius-sm); background: var(--bg-card); }
.cbx-multi:focus-within { border-color: var(--accent); box-shadow: var(--ring); }
.cbx-multi .input { border: 0; box-shadow: none; background: transparent; }
.cbx-multi > .cbx-arrow { top: 20px; }
.cbx-values { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; padding: 0 8px 8px; }
.cbx-values[hidden] { display: none; }
.cbx-token { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px 6px; border-radius: 5px; background: var(--bg-card-soft); font-size: 12px; overflow-wrap: anywhere; }
.cbx-token button { border: 0; padding: 2px; background: transparent; color: var(--text-muted); display: inline-flex; cursor: pointer; }
.cbx-token svg { width: 14px; height: 14px; }
.cbx-clear-all { padding: 4px; border: 0; background: transparent; color: var(--text-muted); font: inherit; font-size: 12px; cursor: pointer; }
.cbx-count { font-size: 12px; color: var(--text-muted); }
.cbx-list[aria-multiselectable] .cbx-opt { display: grid; grid-template-columns: 18px minmax(0,1fr); gap: 2px 8px; align-items: center; }
.cbx-list[aria-multiselectable] .cbx-sub { grid-column: 2; }
.cbx-list[aria-multiselectable] input { margin: 0; width: 16px; height: 16px; accent-color: var(--accent); pointer-events: none; }
.cbx-list .cbx-all { border-bottom: 1px solid var(--border-subtle); font-weight: 600; }
.sr-store-name, .sr-stores > span { display: block; }
.sr-stores { max-height: 180px; overflow-y: auto; }
.sr-stores summary { cursor: pointer; }
.pm-split .is-paid, .pm-legend i.is-paid { background: var(--chart-ok-dot); }
.pm-split .is-ded,  .pm-legend i.is-ded  { background: var(--chart-denied-dot); }
.pm-legend { display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2); margin-top: 6px; font-size: 12.5px; font-variant-numeric: tabular-nums; }
.pm-legend span { display: inline-flex; align-items: center; gap: 6px; }
.pm-legend i { width: 9px; height: 9px; border-radius: 2px; }
.pm-signed { margin: var(--space-3) 0 0; font-size: 13px; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--bg-card-soft); font-variant-numeric: tabular-nums; }

.pm-h { margin: var(--space-5) 0 var(--space-2); font-size: 13.5px; font-weight: 700; }
.pm-h small { display: block; font-size: 12px; font-weight: 500; color: var(--text-soft); margin-top: 1px; }
.pm-row { display: grid; grid-template-columns: minmax(0, 13rem) minmax(0, 1fr) 6rem 4rem; gap: var(--space-3); align-items: center; padding: 6px 0; font-size: 13px; }
.pm-row.is-zero { color: var(--text-soft); }
.pm-row.is-remainder { margin-top: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--hairline); }
.pm.is-compact .pm-row { grid-template-columns: minmax(0, 1fr) auto 3.4rem; grid-template-areas: "lbl amt pct" "bar bar bar"; gap: 4px var(--space-2); }
.pm.is-compact .pm-lbl { grid-area: lbl; }
.pm.is-compact .pm-amt { grid-area: amt; }
.pm.is-compact .pm-pct { grid-area: pct; }
.pm.is-compact .pm-track { grid-area: bar; }
.pm-lbl { display: block; min-width: 0; line-height: 1.35; }
.pm-lbl .wf-dot { vertical-align: 0; }
.pm-tag { display: block; width: fit-content; margin: 3px 0 0 15px; padding: 1px 6px; font-size: 11px; font-weight: 600; color: var(--text-muted);
  border: 1px solid var(--border-subtle); border-radius: 4px; }
.pm-track { display: block; height: 10px; border-radius: 4px; background: var(--chart-track); overflow: hidden; }
.pm-bar { display: block; height: 100%; border-radius: 4px; }
.pm-amt { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pm-pct { text-align: right; font-size: 12px; color: var(--text-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pm-empty { margin: 0; font-size: 13px; color: var(--text-soft); }
.pm-zeros { border-top: 1px dashed var(--border-subtle); margin-top: 4px; }
.pm-totals { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px var(--space-3); margin: var(--space-2) 0 0;
  padding-top: var(--space-2); border-top: 1px solid var(--hairline); font-size: 13px; }
.pm-totals dt { font-weight: 600; }
.pm-totals dt small { display: block; font-weight: 500; font-size: 11.5px; color: var(--text-soft); }
.pm-totals dd { margin: 0; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pm-totals dd span { font-weight: 500; font-size: 12px; color: var(--text-soft); margin-left: 6px; }
.pm-eq { margin: var(--space-4) 0 0; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--bg-card-soft);
  font-size: 13px; font-variant-numeric: tabular-nums; line-height: 1.5; }
.pm-gap { display: flex; gap: 6px; align-items: center; margin: var(--space-2) 0 0; font-size: 12.5px; color: var(--danger-ink); }
.pm-gap svg { width: 15px; height: 15px; }
.pm-foot { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2); margin-top: var(--space-3); }
.pm-help { font-size: 12.5px; color: var(--text-muted); }
.pm-help summary { cursor: pointer; font-weight: 600; }
.pm-help ul { margin: var(--space-2) 0 0; padding-left: 1.1rem; line-height: 1.5; }

@media (max-width: 760px) {
  .pm-sum { grid-template-columns: 1fr 1fr; }
  .pm-sum:not(.is-two) > div:first-child { grid-column: 1 / -1; }
  .pm-row { grid-template-columns: minmax(0, 1fr) auto 3.4rem; grid-template-areas: "lbl amt pct" "bar bar bar"; gap: 4px var(--space-2); }
  .pm-lbl { grid-area: lbl; }
  .pm-amt { grid-area: amt; }
  .pm-pct { grid-area: pct; }
  .pm-track { grid-area: bar; height: 8px; }
}

/* El desglose de Marketing, plegable dentro del cajon. */
.mk { margin-top: var(--space-4); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); background: var(--bg-card); }
.mk > summary { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3); cursor: pointer;
  font-size: 13px; font-weight: 700; list-style: none; }
.mk > summary::-webkit-details-marker { display: none; }
.mk > summary::after { content: ""; margin-left: auto; width: 8px; height: 8px; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: rotate(45deg); transition: transform var(--dur-fast) var(--ease); }
.mk[open] > summary::after { transform: rotate(225deg); }
.mk-body { padding: 0 var(--space-3) var(--space-3); }
.pill.mk-none { background: var(--pill-done-bg); color: var(--pill-done-fg); border-color: var(--pill-done-bd); }
.pill.mk-ok   { background: var(--pill-ok-bg);   color: var(--pill-ok-fg); }
.pill.mk-wait { background: var(--pill-wait-bg); color: var(--pill-wait-fg); }
.pill.mk-bad  { background: var(--pill-bad-bg);  color: var(--pill-bad-fg); }
.mk-compare { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-2); margin: 0 0 var(--space-3); }
.mk-compare > div { padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--bg-card-soft); }
.mk-compare span { display: block; font-size: 11.5px; color: var(--text-soft); font-weight: 600; }
.mk-compare b { display: block; font-size: 15px; font-variant-numeric: tabular-nums; }
.mk-compare small { display: block; font-size: 11px; color: var(--text-soft); }
.mk-compare .is-diff { background: var(--pill-wait-bg); }
.mk-compare .is-diff b { color: var(--pill-wait-fg); }
.mk-items { width: 100%; border-collapse: collapse; font-size: 12.5px; margin-bottom: var(--space-3); }
.mk-items th { text-align: left; font-weight: 500; padding: 5px 0; }
.mk-items th small { display: block; color: var(--text-soft); }
.mk-items td { text-align: right; font-variant-numeric: tabular-nums; padding: 5px 0; }
.mk-items tbody tr + tr, .mk-items tfoot tr { border-top: 1px solid var(--hairline); }
.mk-items tfoot th, .mk-items tfoot td { font-weight: 700; }
.mk-note { font-size: 12.5px; margin: 0 0 var(--space-3); }
.mk-prop { padding: var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--accent); background: var(--accent-soft); margin-bottom: var(--space-3); font-size: 13px; }
.mk-prop p { margin: 0 0 var(--space-2); }
.mk-prop ul { margin: 0 0 var(--space-2); padding-left: 1.1rem; font-variant-numeric: tabular-nums; }
.mk-adj { padding: var(--space-3); border-radius: var(--radius-md); background: var(--bg-card-soft); margin-bottom: var(--space-3); font-size: 12.5px; }
.mk-adj b { display: block; }
.mk-adj span { color: var(--text-soft); }
.mk-adj p { margin: var(--space-1) 0 0; }
.mk-acts { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.mk-hist { margin-bottom: var(--space-3); font-size: 12px; }
.mk-hist summary { cursor: pointer; color: var(--text-muted); font-weight: 600; }
.rep-row.is-adj .rep-lbl { font-weight: 500; font-size: 12px; padding-left: var(--space-3); color: var(--text-muted); }
.mk-lines { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-2); }
.mk-line { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1.4fr) auto; gap: var(--space-2); align-items: center; }
.mk-line .field-err { grid-column: 1 / -1; margin: 0; }
.mk-live { margin-top: var(--space-3); }
@media (max-width: 639px) {
  .mk-line { grid-template-columns: minmax(0, 1fr) 6.5rem auto; }
  .mk-line [data-k="notes"] { grid-column: 1 / 3; grid-row: 2; }
}
}

/* Una solicitud sin aprobar se ve como tal en la lista y en el cajon: la
   etiqueta lleva borde punteado, como lo que todavia no esta confirmado. */
.sr-unapproved { display: inline-block; font-size: 11.5px; font-weight: 700; color: var(--pill-wait-fg);
  border: 1px dashed var(--pill-a15-bd); border-radius: 99px; padding: 0 8px; white-space: nowrap; }
.note-info.sr-unapproved-box { background: var(--pill-wait-bg); color: var(--pill-wait-fg); }
