/* ============================================================================
   Juicy — Dashboard Design System · BUBBLE (menu · box)
   ----------------------------------------------------------------------------
   Figma: "Contextual Menus" (1:3767). Bubble Menu Item (1:3771) 2 modos ×
   Is Last · Bubble Box (1:3810) × Display Footer.

   UN SHELL, DOS CONTENIDOS. En main.css eran DOS componentes copiados línea a
   línea: `.bubble_menu` y `.bubble_box` declaraban lo MISMO (absolute, bottom
   -8, right 0, blanco, radio 5, sombra, borde superior de 3, el pico, el
   transform, el z-index, el estado oculto y la transición) y cada uno repetía
   además su `::before`, su `.active`, su `.open_upward` y su
   `.open_upward.active`. ~110 líneas para decir dos veces lo mismo.

   La prueba de que el modelo estaba mal la dejó escrita el propio producto:
   la caja de notificaciones se declara `class="bubble_menu bubble_box"` (las
   DOS) y main.css tenía que ANULARLE el padding/uppercase que `.bubble_menu >
   *` le imponía a sus hijos — "titulo/lista/boton no son items de menu", decía
   el comentario. Aquí el item es OPT-IN (`.bubble__item`), así que no hay nada
   que anular: quien no es item, no se pinta como item.

     <div class="bubble_wrapper d-inline-block">          <!-- ancla; lo lee el JS -->
       <span class="icon-chip bubble_trigger">…</span>    <!-- lo lee el JS -->

       <nav class="bubble bubble--menu close_on_leave">
         <a class="bubble__item"><i class="fi fi-rr-pencil"></i><span>Editar</span></a>
         <a class="bubble__item tone-error"><i class="fi fi-rr-trash"></i><span>Eliminar</span></a>
       </nav>

       <div class="bubble bubble--box">
         <div class="bubble__header"><span class="bubble__title">Filtrar</span></div>
         <div class="bubble__body">…</div>
       </div>
     </div>

   `.bubble_wrapper`, `.bubble_trigger`, `.close_on_leave`, `.active` y
   `.open_upward` son CONTRATO CON EL JS (main.js abre/cierra y decide hacia
   dónde). Conservan su nombre a propósito: comportamiento y estilo dejan de
   compartir clase — el mismo criterio que en los campos (ds/field.css).
   ============================================================================ */

/* ---- Shell ---------------------------------------------------------------- */

/* El ancla. Su NOMBRE es contrato con el JS, pero su comportamiento es de este
   componente: sin un ancestro posicionado, el shell (absolute) se iría a la
   esquina de la página. Por eso vive aquí y no en main.css. */
.bubble_wrapper {
  position: relative;
}

.bubble {
  /* Dónde apunta el pico: distancia del borde DERECHO al centro del trigger.
     Ver nota 1 — no es decoración, es geometría, y NO es "el ancho del
     trigger": el wrapper no siempre lo abraza. Default = el chip de fila. */
  --bubble-beak-center: 17.5px;

  position: absolute;
  bottom: -8px;
  right: 0;
  z-index: 99;

  background: var(--surface-page);
  border-radius: var(--radius-md);
  border-top: 3px solid var(--action-neutral-lv1-default);
  box-shadow: var(--shadow-md);          /* nota 2 */

  transform: translateY(calc(100% + 20px));
  opacity: 0;
  visibility: hidden;
  /* Solo transform/opacity/visibility. NO `all`: si se anima top/bottom la caja
     viaja entre anclas al abrir hacia arriba, pasa bajo el cursor y dispara
     mouseleave (auto-cierre). Lo aprendió main.css a golpes; se conserva. */
  transition: transform .3s, opacity .3s, visibility .3s;
}

/* El pico. 16 de ancho (Figma) y 10 de alto, con su base METIDA en el borde de
   3 para que no quede costura. Su posición sale de `--bubble-trigger`, no del
   Figma — ver nota 1. */
.bubble::before {
  content: '';
  position: absolute;
  top: -10px;
  right: calc(var(--bubble-beak-center) - 8px);
  border-style: solid;
  border-width: 0 8px 8px;
  border-color: transparent transparent var(--action-neutral-lv1-default) transparent;
}

/* El trigger de los toolbars es un icon-chip, no el chip de fila de 35. Se
   resuelve solo, sin tocar el markup: el pico se recentra por sí mismo. */
.bubble_wrapper:has(> .icon-chip) .bubble     { --bubble-beak-center: calc(var(--control-md) / 2); }
.bubble_wrapper:has(> .icon-chip--sm) .bubble { --bubble-beak-center: calc(var(--control-sm) / 2); }

/* El disparador puede ser un `<button>` y no solo el `<span>` del dashboard PHP.
   Hace falta porque un span no se alcanza con Tab: el menú de acciones de una fila
   —donde vive CANCELAR una venta— era inaccesible sin ratón.

   OJO CON LO QUE NO SE RESETEA, que me costó una regresión: aquí NO va
   `font-size` ni `line-height`. Este selector (0,1,1) le gana a cualquier clase
   sola, y `.header_account_data` —que también es un `bubble_trigger`— declara
   `font-size: 12px`: un `inherit` aquí le subía el nombre del usuario a 16px.
   Y no hacen falta: el disparador solo contiene un icono, cuyo tamaño lo da el
   `font-size` de su propio `::before` en `.icon-chip`. La diferencia de 0.33px
   que queda entre un span y un button no pinta un solo píxel. */
button.bubble_trigger {
  font-family: inherit;
  cursor: pointer;
  /* Por simetría con `.bubble__item`: cierra el último diff medido entre el `<span>` de PHP
     y el `<button>` de React. Hoy no se ve porque `.icon-chip` ya declara fondo y `border: 0`,
     pero dejarlo dependiendo de otro componente es cómo reaparece al cambiar aquel. */
  appearance: none;
  /* Y estas dos por lo mismo: el UA le da al `<button>` `font-weight: 400` y
     `text-align: center`, que el `<span>` de PHP no sufre porque hereda del contexto. Medido con
     el espejo en el disparador de la burbuja de filtros (PHP 500/start · React 400/center).
     Van DESPUÉS del aviso de arriba a propósito: `font-size` y `line-height` siguen fuera. */
  font-weight: inherit;
  text-align: inherit;
}

/* CON FILTROS PUESTOS el disparador se enciende. Es la única señal de que la tabla NO está
   enseñando todo, y por eso no es decoración: sin ella, quien vuelve a una pantalla filtrada lee
   los totales como si fueran los de la tienda entera.

   Vivía en `main.css` —el CSS legacy que el front nuevo NO importa a propósito—, así que allá el
   chip salía apagado aunque la clase estuviera puesta. Es el cuarto caso de lo mismo (después de
   la X del lightbox, los iconos y `.text-red`), y se cierra igual: la regla se MUEVE, no se copia.

   Los tokens son los semánticos que corresponden, no los alias legacy: `--gw-400` era
   `--action-secondary-default` con otro nombre (lo dice `main.css` en su propia nota) y `#FFF`
   era `--action-secondary-text`. Mismo valor computado (#9366DD sobre blanco), medido antes y
   después en la pantalla de PHP. */
.applied_filters .bubble_trigger {
  background: var(--action-secondary-default);
  color: var(--action-secondary-text);
}

.active .bubble {
  transform: translateY(100%);
  opacity: 1;
  visibility: visible;
}

/* Abre hacia arriba cuando no hay espacio abajo (la clase la pone main.js). */
.open_upward .bubble {
  bottom: auto;
  top: -8px;
  border-top: none;
  border-bottom: 3px solid var(--action-neutral-lv1-default);
  transform: translateY(calc(-100% - 20px));
}

.open_upward .bubble::before {
  top: auto;
  bottom: -7px;
  border-width: 10px 8px 0;
  border-color: var(--action-neutral-lv1-default) transparent transparent transparent;
}

/* Abierto Y hacia arriba. El estado oculto de `.open_upward .bubble` lo deja 20px más
   arriba (`calc(-100% - 20px)`); esto lo baja a su sitio. El compuesto `.open_upward.active`
   es OBLIGATORIO: sin él este `.active .bubble` pisa por orden al de arriba —misma
   especificidad— y TODOS los menús abren hacia arriba, también los que caben abajo. Eso
   sacaba de pantalla el menú de las primeras filas. */
.open_upward.active .bubble {
  transform: translateY(-100%);
}

/* ---- Menu ----------------------------------------------------------------- */

.bubble--menu {
  display: flex;
  flex-direction: column;
  min-width: 200px;                      /* nota 3 */
}

/* El item es OPT-IN: el shell no pinta a sus hijos (ver cabecera). */
.bubble__item {
  /* AGNÓSTICO DE LA ETIQUETA que lo hospeda. El dashboard de PHP monta cada item como
     `<span>`/`<a>`; el front nuevo lo monta como `<button>`, que es lo correcto para algo
     que dispara una acción. A un `<button>` la hoja del NAVEGADOR le da fondo `buttonface`,
     marco `2px outset` y `line-height: normal`, y esta regla no declaraba ninguna de las
     tres: el mismo componente salía como una caja gris con marco en React y limpio en PHP.
     Medido con getComputedStyle en los dos mundos, no deducido.

     `.btn` e `.icon-chip` ya llevan esta neutralización por el mismo motivo; este item es
     el que se quedó fuera. No es una regla huérfana en `main.css` —ahí no hay nada de
     esto— así que no hay nada que mover: es un hueco de cobertura del propio componente.

     EL ORDEN IMPORTA: `border: 0` va ANTES del `border-bottom` de abajo, que es el
     separador fino entre items. Al revés lo borraría. */
  appearance: none;
  background: transparent;
  border: 0;
  /* `inherit`, NO `normal`: hereda el 1.3 sin unidad que ya viene de la tabla, que es lo
     que PHP computa (14.3px sobre 11px). Con `normal` el que se movería sería PHP. */
  line-height: inherit;

  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 42px;                      /* Figma: los 4 items miden 42 */
  padding: 10px;                         /* nota 4 */
  border-bottom: var(--stroke-thin) solid var(--border-light);  /* nota 5 */

  color: var(--tone-line);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background .3s;
}

/* Sin tono, la tinta es el texto normal. En :where() para que cualquier
   `.tone-*` lo gane sin pelear especificidad. `--tone-line` y no `--tone-fill`
   porque esto es TINTA, no relleno: es lo que ya hacen `.btn--ghost` y
   `.btn--outline`. Con `tone-error` el item sale rojo — el mismo #F04438 que
   daba `.text-red`, pero diciendo el ROL en vez del color. */
:where(.bubble__item) {
  --tone-line: var(--text-primary);
}

.bubble__item:hover {
  background: var(--surface-sunken-light);
}

.bubble__item:last-child {
  border-bottom: none;
}

/* El icono toma el color del item (currentColor ← `--tone-line`) y va en una
   CAJA EXPLÍCITA de 16×16 (--icon-sm): es el patrón de icon-chip y badge, y aquí
   además es obligatorio. Ver nota 6. */
.bubble__item > i,
.bubble__item > svg {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--icon-sm);
  height: var(--icon-sm);
  font-size: var(--icon-sm);
  line-height: 1;
  fill: currentColor;
}

/* ---- Box ------------------------------------------------------------------ */

/* Los botones DENTRO de un bubble box son chrome neutro (filtros, "Filtrar",
   acciones del popover), no CTAs — se pintan en `tone-neutral-lv1` sin tocar el
   markup. Setea solo `--tone-fill` (+fg/line/soft); `--tone-hover` lo deriva el
   `:where(..., .btn)` de tone.css. Especificidad 0,2,0 → gana a cualquier
   `.tone-*` (0,1,0) que traiga el botón (p.ej. el "Filtrar" default primary). */
.bubble--box .btn {
  --tone-fill: var(--action-neutral-lv1-default);
  --tone-fg:   var(--action-neutral-lv1-text);
  --tone-line: var(--tone-fill);
  --tone-soft: var(--action-neutral-lv1-soft);
  --tone-on-soft: var(--tone-fill);
}

/* El ancho es una VARIABLE, no un número: la caja de notificaciones mide 360 y
   `.notifications_box` (0,1,0) EMPATA con `.bubble--box` (0,1,0) — como esta
   hoja carga después de main.css, ganaría el 400 y la caja engordaría. Es la
   misma trampa de especificidad que ya mordió en 0.7.x con `.button` vs
   `.tone-error`. Con la variable no hay pelea: quien quiera otro ancho la
   redefine. Lo cazó la foto antes/después, no la revisión. */
.bubble--box {
  width: var(--bubble-box-width, 400px); /* Figma: 405. Ver nota 7 */
}

.bubble__header {
  padding: 20px 15px;
  border-bottom: var(--stroke-thin) solid var(--border-default);  /* nota 8 */
}
/* "Quitar Filtros". TERCERA vez que aparece el mismo hueco, y por eso va con nombre y apellidos:
   el dashboard de PHP lo monta como `<a href>` a un URL sin filtros, y el front nuevo como
   `<button>` (allá la navegación la lleva el router; un enlace duro recargaría el SPA entero).
   Esta regla solo declaraba el tamaño, así que en React salía **el botón gris del sistema**:
   fondo #EFEFEF, marco `2px outset`, texto negro y centrado. Medido en los dos mundos.

   Lo que el `<a>` tiene NO se lo da ninguna regla suya: lo HEREDA de `.bubble__header-actions`
   (peso 500, interlineado 16px, alineado a la derecha). Por eso aquí va `inherit` y no un valor
   copiado — el `<a>` ya heredaba, así que esto no puede mover la pantalla de PHP.

   EL COLOR es el caso de siempre: en PHP lo pone `.text-red` de `main.css`, que el front NO
   importa. Se declara aquí, y con `--red-500` y NO con `--error-500`: ese segundo es un ALIAS que
   `main.css` define (`--error-500: var(--red-500)`), así que en el front no existe y la
   declaración entera se cae —silenciosamente, heredando el gris del texto—. Es la misma trampa
   que dejó invisibles las fichas del selectize. El valor es el mismo #F04438, y en PHP no cambia
   nada porque aquel `.text-red` lleva `!important`.

   Igual que `.bubble__item` y `button.bubble_trigger`, más arriba. Cuando aparezca el cuarto, el
   patrón ya está: el componente del DS no puede depender de qué etiqueta lo hospeda. */
.remove-filters-link{
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  font-family: inherit;
  font-weight: inherit;
  line-height: inherit;
  text-align: inherit;
  color: var(--red-500);
  cursor: pointer;

  font-size: 12px;
}
.bubble__title {
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
}

.bubble__body {
  padding: 20px 15px;
}

/* La banda del pie. En Figma es `Display Footer=Yes`; en el producto la pinta
   el `.form_footer` que ya trae el formulario, y por eso sale del body con
   márgenes negativos. */
.bubble__footer,
.bubble__body .form_footer {
  margin: 20px -15px -20px;
  padding: 15px 20px;
  background: var(--surface-sunken-light);   /* nota 9 */
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}

/* ============================================================================
   NOTAS

   1. EL PICO NO SE TRANSCRIBE DEL FIGMA — y por poco lo hago. Figma lo pone a
      `right: 15` → su centro cae a 23 del borde derecho. Medido en el producto:
      el trigger de fila mide 35, su centro cae a 17.5, y el pico de main.css
      (`right: 10` + 8 de media base) caía a 18. Coincidían.
      O sea: el 10 no era un número feo que el DS venía a corregir — era
      `35/2 - 8`, derivado de a quién apunta. El 23 del Figma correspondería a
      un trigger de 46px que en este producto no existe.

      LOS TRES TRIGGERS, MEDIDOS (y no, no bastaba con dos):
        chip de fila   35  · wrapper 35, lo abraza          → centro 17.5
        icon-chip      40  · wrapper 40, lo abraza          → centro 20
        campana        18  · wrapper 40 (SLOT que la centra) → centro 20
      La primera versión de esta variable se llamaba `--bubble-trigger` y valía
      "el ancho del trigger": funcionaba en los dos primeros y MENTÍA en el
      tercero, porque `#dashboard_header_actions > *` hace slots de 40×40 con el
      contenido centrado — la campana mide 18 pero su centro cae a 20. Lo cazó
      medir los tres; con dos habría escrito otra generalización falsa (§3.4 de
      la retro). De ahí el nombre actual: lo que el pico necesita saber es DÓNDE
      apuntar, no cuánto mide nadie.
      El dato bonito, para quien venga: en los tres casos el centro del trigger
      cae en el centro del WRAPPER. Un día el pico podría colgar del wrapper
      (`left:50%`) y esta variable sobraría; hoy no se toca, porque el pico
      tendría que perseguir el ancla del shell (`bottom:-8`) desde fuera.

      (Sí se transcribe lo que el Figma sí especifica: el pico mide 16×10, no
      16×8 — y a 10 su base entra en el borde de 3, sin costura. Con los 8 de
      main.css quedaba 1px de aire entre el pico y la caja.)

   2. LA SOMBRA: main.css usaba `0 0 5px rgba(0,0,0,.2)` — un resplandor
      simétrico que no es de nadie. `--shadow-md` es la del sistema para lo que
      flota sobre contenido (misma decisión que el toast en F2a).

   3. `min-width: 200` es del producto, no del Figma. El Figma dibuja el menú a
      220, pero sus variantes de item miden 1216 de ancho (estiradas a la
      tarjeta del styleguide): el item es FLUIDO y el 220 es el ancho de una
      instancia de demo, no una medida del sistema. El menú real crece con su
      texto más largo ("IMPRIMIR ETIQUETA"). Se conserva el 200.

   4. PADDING/GAP DEL ITEM: SIN RESOLVER, y no se toca a ojo. Medido del render
      del Figma a escala 1:1: padding-left ~17, gap ~13, el texto arranca a ~51
      del borde. El producto usa 10/10 → texto a 34. La diferencia es real pero
      NO se puede despejar desde una imagen: no sé cuánto inset trae el glifo
      dentro de su caja (padding 15 + icono 24 + gap 12 y padding 16 + icono 20
      + gap 15 dan los dos el mismo 51 medido), y el MCP no expone el autolayout
      de un symbol. Se conserva el 10/10 del producto, que es medible y funciona.
      Pregunta abierta para el dueño: ¿el item lleva 15 de padding?

   5. EL SEPARADOR SÍ CAMBIA: `border/light` (#F0F3F6) es una VARIABLE ligada en
      el Figma, y el producto usaba `neutral-100` (#F4F7F9) — un paso más claro.
      Cuando el Figma liga una variable, el Figma manda (a diferencia de la
      nota 4, donde no hay variable que leer).

   6. EL ICONO DEL ITEM: 14px en el producto, ~20 en el Figma (medido 1:1 en dos
      nodos distintos: el item suelto y el menú ensamblado). 20 = `--icon-md`,
      el mismo del Button y el Icon Button, así que además cierra el sistema.
      Es el cambio visible de esta fase.
      Y necesita CAJA EXPLÍCITA (20×20) — ES LA ÚNICA DEL DS QUE LA LLEVA, y por
      una razón concreta: los iconos son uicons, que pintan el glifo en un
      `::before` CON SU PROPIO `line-height` (1.5em → 30px a 20px de fuente), y
      ese 30 manda el alto del `<i>` aunque le pongas `line-height:1`. Como el
      alto de ESTA fila lo dicta su contenido, la fila se iba a 51 (medido, no
      deducido). Con caja + `display:flex` en el propio `<i>` (para que el
      ::before se centre dentro), la fila vuelve a los 42 del Figma.
      En el resto del DS la caja NO va: a un icono de tipografía la caja lo
      DESCENTRA (+4.5 medido en un chip), y solo se salva aquí porque el flex la
      recentra. El icon-chip no la necesita porque su alto es fijo: el line-box
      de 30 le desborda por dentro y nadie se entera. La regla y las mediciones,
      en foundation.css.

   7. `width: 400` del producto vs 405 del Figma. Medido en el render: la caja
      da 399±2 — o sea que el 405 del nodo incluye algo de aire. Se conserva 400.

   8. EL BORDE DEL HEADER: aquí el Figma NO manda, porque no tiene qué mandar.
      Pinta #F0F1F1, que no es ningún primitivo del sistema: es uno de los
      FILLS DESPRENDIDOS ya conocidos (el mismo #F0F1F1 aparece suelto en
      Widgets). El producto usa `border/default` (#E5EAEF), que sí es el token
      de un borde. Se conserva el producto y se anota el bug allá.

   9. EL PIE: Figma lo pinta #F0F3F6 (neutral/150), que NO tiene token de
      superficie — la escala `surface/sunken` salta de 100 (#F4F7F9) a 200
      (#E5EAEF). El producto usa `surface/sunken-light`, que es el token
      correcto para una banda hundida, a un paso de rampa del Figma (ΔE
      imperceptible). Se conserva el token; falta decidir allá si el pie debe
      ser sunken-light o hace falta un `surface/sunken-mid`.

  10. `.bubble_menu.small` (120px) MURIÓ: cero usos. `.medium` (180px) tiene uno
      solo, en el POS (`gw_POS.php:81`), y se queda en main.css como override
      legacy: el Figma no define tamaños de menú. Muere con F2g.
   ============================================================================ */
