/* Exclivia · components.css — piezas reutilizables.
 * Prefijo exc- en todo para no chocar con Bootstrap (ni con Tailwind el día que
 * el admin cambie de framework: los componentes viven de las variables, no del
 * framework).
 */

/* ── Tarjetas ───────────────────────────────────────────────────────────── */

.exc-card {
  background: var(--exc-surface);
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius-card);
  padding: 1.25rem;
  box-shadow: var(--exc-shadow);
}

.exc-card + .exc-card { margin-top: 1rem; }

.exc-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem; flex-wrap: wrap;
  margin-bottom: 1rem;
}
.exc-card-title { margin: 0; font-size: 1.05rem; font-weight: 600; }
.exc-card-sub   { margin: .15rem 0 0; font-size: .85rem; color: var(--exc-muted); }

.exc-card-accent { border-left: 3px solid var(--exc-accent); }

.exc-card-link {
  display: block; color: inherit;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.exc-card-link:hover {
  transform: translateY(-2px);
  border-color: var(--exc-accent);
  box-shadow: var(--exc-shadow-lg);
  opacity: 1;
}

/* ── Botones ────────────────────────────────────────────────────────────── */

.exc-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .62rem 1.15rem;
  min-height: 44px;               /* objetivo táctil cómodo en celular */
  border: var(--exc-border-width) solid transparent;
  border-radius: var(--exc-radius);
  font: inherit; font-size: .94rem; font-weight: 600;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: background .15s ease, border-color .15s ease, transform .1s ease, opacity .15s ease;
  -webkit-tap-highlight-color: transparent;
}
.exc-btn:active { transform: translateY(1px); }
.exc-btn:disabled, .exc-btn.is-busy { opacity: .6; cursor: not-allowed; }

.exc-btn-primary {
  background: var(--exc-accent);
  color: var(--exc-accent-contrast);
  border-color: var(--exc-accent);
}
.exc-btn-primary:hover { filter: brightness(1.06); opacity: 1; }

.exc-btn-outline {
  background: transparent;
  color: var(--exc-text);
  border-color: var(--exc-border);
}
.exc-btn-outline:hover { border-color: var(--exc-accent); color: var(--exc-accent); opacity: 1; }

.exc-btn-ghost {
  background: transparent; color: var(--exc-text); border-color: transparent;
}
.exc-btn-ghost:hover { background: var(--exc-surface-2); opacity: 1; }

.exc-btn-danger {
  background: var(--exc-danger); color: #fff; border-color: var(--exc-danger);
}

/* De qué color va el icono de cada variante — el mismo que su texto.
   El mecanismo está en «Iconos», en base.css. */
.exc-btn-primary { --exc-ico: var(--exc-accent-contrast); }
.exc-btn-outline,
.exc-btn-ghost   { --exc-ico: var(--exc-text); }
.exc-btn-danger  { --exc-ico: #fff; }
.exc-btn-outline:hover { --exc-ico: var(--exc-accent); }

/* `display: inline-flex` le gana al `hidden` del navegador: sin esta regla,
   esconder un botón desde el JS no lo escondía —seguía ahí, ofreciendo algo que
   el servidor iba a rechazar—. Pasó con el botón de «agregar motivo» al llegar
   al tope y con el enlace de «ver mi cita» cuando no había enlace. */
.exc-btn[hidden] { display: none; }

.exc-btn-sm    { padding: .4rem .8rem; min-height: 36px; font-size: .85rem; }
.exc-btn-lg    { padding: .85rem 1.6rem; min-height: 52px; font-size: 1rem; }
.exc-btn-block { display: flex; width: 100%; }
.exc-btn-icon  { padding: .5rem; min-width: 44px; }

/* Estado cargando: el texto se atenúa y aparece el giro. */
.exc-btn .exc-spinner { display: none; margin-right: 0; }
.exc-btn.is-busy .exc-spinner { display: inline-block; }

/* `inline-block` y su margen van en la BASE, no solo dentro de `.exc-btn`: un
   `<span>` es inline y ahí `width`/`height` no cuentan, así que en cualquier
   otro botón (los de la cocina, los estados de una mesa) el giro salía como
   una RAYITA junto al texto — parecía un cursor escribiendo. */
.exc-spinner {
  display: inline-block; vertical-align: -.15em; margin-right: .45em; flex: none;
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: exc-spin .7s linear infinite;
}
@keyframes exc-spin { to { transform: rotate(360deg); } }

/* Un `<label>` que hace de botón —el campo de archivo vive dentro— no tiene
   `disabled`: se le puede volver a dar clic mientras trabaja y arrancar una
   segunda subida encima de la primera. Esto es lo que lo apaga de verdad.

   No sustituye a la bandera del guion, la acompaña: el teclado sigue pudiendo
   activarlo, y `pointer-events` solo detiene al dedo y al ratón. */
.exc-no-tocar { pointer-events: none; opacity: .75; }

/* ── Formularios ────────────────────────────────────────────────────────── */

/* `display: block` NO es decorativo.
 *
 * `.exc-field` se pone casi siempre en un `<label>`, y un label es `inline`.
 * Dentro de un grid no se nota —el grid convierte a sus hijos en bloque— pero
 * SUELTO, el campo se queda inline y el `width: 100%` de su input o su textarea
 * no tiene contra qué medirse: se encoge al ancho del contenido. Ese era el
 * caso de la descripción de un auto, que salía en una columna estrecha en medio
 * de una fila vacía sin que nada estuviera mal escrito en su vista. */
.exc-field { display: block; margin-bottom: 1rem; }

.exc-label {
  display: block;
  margin-bottom: .35rem;
  font-size: .88rem;
  font-weight: 600;
}

.exc-input, .exc-select, .exc-textarea {
  display: block; width: 100%;
  padding: .68rem .85rem;
  min-height: 46px;
  font: inherit;
  font-size: 16px;               /* menos de 16px hace que iOS acerque el zoom */
  color: var(--exc-text);
  background: var(--exc-bg);
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.exc-input:focus, .exc-select:focus, .exc-textarea:focus {
  outline: none;
  border-color: var(--exc-accent);
  box-shadow: 0 0 0 3px rgba(var(--exc-accent-rgb), .18);
}
.exc-input::placeholder, .exc-textarea::placeholder { color: var(--exc-muted); }
.exc-input:disabled, .exc-select:disabled, .exc-textarea:disabled {
  opacity: .6; cursor: not-allowed;
}
.exc-textarea { min-height: 110px; resize: vertical; }

.exc-select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2.4rem;
}

/* Los renglones del desplegable, pintados a mano.
 *
 * `color-scheme` en la raíz debería bastar —y está puesto—, pero no todos los
 * navegadores lo aplican a la lista que abre un `<select>`: sale con el blanco
 * y el azul del sistema encima de una pantalla oscura. Esto sí lo respetan
 * Chromium y Firefox en Windows y Linux.
 *
 * En macOS ese menú lo dibuja el sistema y estas reglas no le llegan; ahí lo
 * único que funciona es `color-scheme`, que por eso se queda. Las dos juntas
 * cubren los dos casos.
 *
 * Se pintan TODOS los `option`, no solo los de `.exc-select`: en el sitio hay
 * desplegables con `.exc-input` y con clases de módulo, y una lista blanca en
 * uno solo se ve igual de rota. */
select option,
select optgroup {
  background-color: var(--exc-surface);
  color: var(--exc-text);
}

.exc-hint  { margin: .3rem 0 0; font-size: .8rem; color: var(--exc-muted); }
.exc-error { margin: .3rem 0 0; font-size: .8rem; color: var(--exc-danger); }
.exc-input.is-invalid, .exc-select.is-invalid { border-color: var(--exc-danger); }
.exc-input.is-valid { border-color: var(--exc-success); }

/* Casilla y switch */
.exc-check {
  display: flex; align-items: flex-start; gap: .6rem;
  padding: .5rem 0; cursor: pointer;
}
.exc-check input { width: 20px; height: 20px; accent-color: var(--exc-accent); flex: none; margin-top: .1rem; }

/* «Falta un paso: permite la ubicación» — el renglón que acompaña a esa
   casilla en el perfil. Nace OCULTO y lo enseña `cerca.js` solo si el
   permiso del navegador todavía no está dado: pintado siempre, quien ya lo
   concedió vería un botón que no hace nada.

   La regla de `[hidden]` va escrita aquí porque en este proyecto NO hay una
   global: sin ella, el `display: flex` de la caja le gana al atributo del
   navegador y el renglón se pinta para todo el mundo. */
.exc-cerca-permiso {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem .8rem;
  margin: .2rem 0 0;
  font-size: .86rem;
  line-height: 1.45;
}
.exc-cerca-permiso[hidden] { display: none; }
.exc-cerca-permiso > span { min-width: 0; flex: 1 1 18rem; }

.exc-switch { display: inline-flex; align-items: center; gap: .6rem; cursor: pointer; }
.exc-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.exc-switch-track {
  width: 46px; height: 26px; flex: none;
  background: var(--exc-border);
  border-radius: 999px;
  position: relative;
  transition: background .18s ease;
}
.exc-switch-track::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--exc-surface);
  transition: transform .18s ease;
}
.exc-switch input:checked + .exc-switch-track { background: var(--exc-accent); }
.exc-switch input:checked + .exc-switch-track::after { transform: translateX(20px); }
.exc-switch input:focus-visible + .exc-switch-track { outline: 2px solid var(--exc-accent); outline-offset: 2px; }

/* Opciones tocables — en celular se usan mejor que un <select>. */
.exc-choices { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.exc-choice {
  position: relative;
  display: flex; align-items: center; gap: .6rem;
  padding: .8rem .9rem;
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.exc-choice input { position: absolute; opacity: 0; }
.exc-choice.is-selected { border-color: var(--exc-accent); background: rgba(var(--exc-accent-rgb), .08); }

/* Una opción por renglón, para cuando cada una necesita explicarse.
 *
 * La rejilla de arriba reparte el ancho entre las opciones y va bien con
 * etiquetas de una palabra —«Perro», «Gato»—; con un título y una línea debajo,
 * dos columnas dejan cuatro palabras por renglón. Aquí la decisión no es de un
 * vistazo: se lee. */
.exc-choices-block { grid-template-columns: 1fr; }
.exc-choices-block .exc-choice { align-items: flex-start; }

/* `min-width: 0` para que el texto largo se corte dentro del renglón en vez de
   ensanchar la caja. */
.exc-choices-block .exc-choice > span {
  display: flex; flex-direction: column; gap: .15rem; min-width: 0;
}
.exc-choices-block .exc-choice strong {
  display: inline-flex; align-items: center; gap: .4rem;
}
.exc-choices-block .exc-choice small { color: var(--exc-muted); line-height: 1.4; }

/* Color: el navegador pinta un cuadrito feo; se le da forma. */
.exc-color {
  width: 46px; height: 46px; padding: 3px;
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius-sm);
  background: var(--exc-bg);
  cursor: pointer;
}
.exc-color-row { display: flex; align-items: center; gap: .7rem; }
.exc-color-row .exc-input { max-width: 130px; font-family: ui-monospace, monospace; }

/* ── Avisos ─────────────────────────────────────────────────────────────── */

.exc-alert {
  padding: .85rem 1rem;
  border-radius: var(--exc-radius);
  border: 1px solid transparent;
  font-size: .92rem;
  margin-bottom: 1rem;
}
.exc-alert-success { background: rgba(30,158,106,.12); border-color: rgba(30,158,106,.35); color: var(--exc-success); }
.exc-alert-danger  { background: rgba(214,72,63,.12);  border-color: rgba(214,72,63,.35);  color: var(--exc-danger); }
.exc-alert-warning { background: rgba(217,133,24,.12); border-color: rgba(217,133,24,.35); color: var(--exc-warning); }
.exc-alert-info    { background: rgba(47,125,209,.12); border-color: rgba(47,125,209,.35); color: var(--exc-info); }

/* ── Badges ─────────────────────────────────────────────────────────────── */

.exc-badge {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .22rem .6rem;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
}
.exc-badge-accent  { background: rgba(var(--exc-accent-rgb), .16); color: var(--exc-accent); }
.exc-badge-success { background: rgba(30,158,106,.16); color: var(--exc-success); }
.exc-badge-warning { background: rgba(217,133,24,.16); color: var(--exc-warning); }
.exc-badge-danger  { background: rgba(214,72,63,.16);  color: var(--exc-danger); }
.exc-badge-muted   { background: var(--exc-surface-2); color: var(--exc-muted); }

.exc-badge.is-solid   { color: var(--exc-accent-contrast); background: var(--exc-accent); }
.exc-badge.is-outline { background: transparent; border-color: currentColor; }

/* ── La etiqueta de los personajes generados ──────────────────────────────
 *
 * Va pegada al nombre, en todas partes donde sale un nombre. La pinta
 * `Ia::badge()`; aquí solo se le da forma.
 *
 * ── Por qué NO es un `.exc-badge` más ────────────────────────────────────
 *
 * Los badges del sitio son etiquetas de estado —«activo», «pendiente»,
 * «pagado»— y comparten familia porque dicen lo mismo: en qué punto está algo.
 * Esto dice otra cosa: con QUIÉN estás hablando. Si compartiera clase, un día
 * alguien le pondría `is-solid` para que resaltara en una pantalla y la
 * etiqueta cambiaría de aspecto según dónde caiga — y una advertencia que no
 * se ve igual en todos lados deja de leerse como una advertencia.
 *
 * ── Y por qué es GRIS y no del color de acento ───────────────────────────
 *
 * Porque no es un premio. En dorado, al lado del sello de verificación, se
 * leería como un tercer nivel de cuenta —«azul, oro, IA»— que es exactamente
 * lo contrario de lo que dice. Neutra, discreta y siempre igual: informa sin
 * felicitar y sin acusar.
 */
.exc-ia {
  display: inline-flex;
  align-items: center;
  gap: .22rem;
  padding: .12rem .42rem .12rem .3rem;
  border: 1px solid var(--exc-border);
  border-radius: 999px;
  background: var(--exc-surface-2);
  color: var(--exc-muted);
  --exc-ico: currentColor;
  /* `em` y no `rem`: la etiqueta acompaña a un nombre que mide distinto en el
     `h1` del perfil y en el renglón de una tarjeta del muro. Con `rem` había
     que escribir un tamaño por sitio; con `em` se ajusta sola y siempre queda
     en proporción con lo que está etiquetando. */
  font-size: .72em;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.5;
  white-space: nowrap;
  /* Se queda quieta en la línea base del nombre en vez de flotar a media
     altura, que es donde acaba si hereda el `vertical-align` del texto. */
  vertical-align: middle;
  /* El nombre de al lado puede ir en cursiva o con otra caja tipográfica —la
     bio, un título—; la etiqueta no hereda nada de eso: siempre igual. */
  font-style: normal;
  text-transform: none;
}

/* El icono, un pelo más chico que las letras: es el acento, no el mensaje. */
.exc-ia svg { width: 1em; height: 1em; }

/* En una tarjeta del muro, junto a un @usuario ya pequeño, se queda solo la
   chispa: las letras a ese tamaño son tres píxeles borrosos que no se leen, y
   una etiqueta ilegible es peor que ninguna porque ocupa sitio y no avisa.

   Se esconden con la técnica de `exc-sr` y NO con `display: none`. La
   diferencia no es de estilo: `display: none` las saca del árbol de
   accesibilidad, así que quien navega con lector de pantalla se quedaba sin el
   aviso de que quien publica no es una persona — que es justo a quien más
   falta le hace, porque no puede deducirlo de un icono. */
.exc-ia-sm .exc-ia-txt {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.exc-ia-sm      { padding: .12rem .26rem; }

/* ── La chispa, en dorado ────────────────────────────────────────────────────
 *
 * El icono toma el acento del sitio en vez del gris del texto. Sin letras al
 * lado —que es como se lleva cuando el bot elige `icono`— el gris se perdía
 * dentro de la pastilla y la etiqueta dejaba de verse: lo único que queda para
 * avisar es esa forma, así que tiene que destacar.
 *
 * El dorado sale del token, no escrito a mano: el día que se cambie el acento
 * del sitio esta etiqueta se mueve con él. */
.exc-ia {
  --exc-ico: var(--exc-accent);
  color: var(--exc-accent);
  border-color: color-mix(in srgb, var(--exc-accent) 38%, transparent);
  background: color-mix(in srgb, var(--exc-accent) 10%, var(--exc-surface-2));
}

/* El destello.
 *
 * Un brillo que cruza el icono cada pocos segundos, no un parpadeo: lo que
 * parpadea se lee como una alerta y esto no lo es — es una aclaración de qué
 * es la cuenta. Y va LENTO y espaciado a propósito: en un muro con diez
 * tarjetas, diez chispas latiendo a la vez convierten la pantalla en un árbol
 * de navidad y dejan de decir nada.
 *
 * Se anima el `filter`, que la GPU compone sin repintar el texto de al lado.
 * Animar el `color` obligaría a recalcular el renglón entero en cada cuadro. */
@media (prefers-reduced-motion: no-preference) {
  .exc-ia svg { animation: exc-ia-destello 4.5s ease-in-out infinite; }
}

/* Sin el `@media` de arriba no habría nada que lo apagara: quien pidió menos
   movimiento en su sistema lo hizo por algo —mareo, migraña, dificultad para
   leer con algo moviéndose al lado— y una etiqueta decorativa no es razón para
   ignorarlo. Con la consulta, esa persona ve la chispa dorada quieta, que
   avisa exactamente igual. */
@keyframes exc-ia-destello {
  0%, 62%, 100% { filter: none; }
  72%           { filter: drop-shadow(0 0 3px var(--exc-accent)) brightness(1.5); }
  82%           { filter: none; }
}

/* ── La etiqueta de canal ─────────────────────────────────────────────────
 *
 * Misma caja que la de IA y a propósito: las dos contestan la misma pregunta
 * —«¿con quién estoy hablando?»— y con dos aspectos distintos la de canal se
 * leería como un nivel de cuenta y no como una aclaración. Gris, discreta y
 * siempre igual. Ver `Canal::badge()`.
 *
 * Se escriben juntas y no con una clase compartida en el HTML porque el día
 * que una de las dos tenga que cambiar —un fondo propio, un icono relleno—
 * tocar la regla no puede mover la otra sin querer.
 */
.exc-canal {
  display: inline-flex;
  align-items: center;
  gap: .22rem;
  padding: .12rem .42rem .12rem .3rem;
  border: 1px solid var(--exc-border);
  border-radius: 999px;
  background: var(--exc-surface-2);
  color: var(--exc-muted);
  --exc-ico: currentColor;
  font-size: .72em;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.5;
  white-space: nowrap;
  vertical-align: middle;
  font-style: normal;
  text-transform: none;
}

.exc-canal svg { width: 1em; height: 1em; }

/* En una tarjeta del muro la etiqueta ENCOGE pero NO se queda muda, al revés
   que la de IA. Ahí la chispa sola basta porque «IA» son dos letras que casi
   nadie necesita leer entera; aquí la palabra es el mensaje: un negocio y la
   cuenta personal de su dueño se llaman casi igual —«Tacos El Primo» y
   «@elprimo»— y un altavoz suelto no distingue una de otra. */
.exc-canal-sm      { padding: .1rem .3rem .1rem .22rem; font-size: .64em; }
.exc-canal-sm svg  { width: .95em; height: .95em; }

/* ── Avatares ───────────────────────────────────────────────────────────── */

.exc-avatar {
  width: 40px; height: 40px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  background: var(--exc-surface-2);
}
.exc-avatar-initials {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 700;
  color: var(--exc-accent-contrast);
  background: var(--exc-accent);
}
.exc-avatar-sm { width: 30px; height: 30px; font-size: .7rem; }
.exc-avatar-lg { width: 64px; height: 64px; font-size: 1.1rem; }
.exc-avatar-xl { width: 112px; height: 112px; font-size: 1.9rem; }

/* ── Tablas ─────────────────────────────────────────────────────────────── */

/* La tabla se desplaza DENTRO de su caja: la página nunca se mueve de lado. */
.exc-table-wrap {
  overflow-x: auto;
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius-card);
  background: var(--exc-surface);
  -webkit-overflow-scrolling: touch;
}
.exc-table { width: 100%; border-collapse: collapse; font-size: .92rem; }
.exc-table th, .exc-table td {
  padding: .75rem .9rem;
  text-align: left;
  border-bottom: 1px solid var(--exc-border);
  vertical-align: middle;
}
.exc-table th {
  font-size: .76rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--exc-muted);
  background: var(--exc-surface-2);
  white-space: nowrap;
}
.exc-table tbody tr:last-child td { border-bottom: 0; }
.exc-table tbody tr:hover { background: var(--exc-surface-2); }
.exc-table-actions { display: flex; gap: .4rem; justify-content: flex-end; }

/* ── Estado vacío ───────────────────────────────────────────────────────── */

.exc-empty { padding: 2.5rem 1rem; text-align: center; color: var(--exc-muted); }
/* A este tamaño un trazo de 1.6 pesa demasiado: se adelgaza. */
.exc-empty-icon  { font-size: 2.4rem; opacity: .55; margin-bottom: .5rem; --exc-ico-w: 1.25; }
.exc-empty-title { margin: 0 0 .25rem; font-weight: 600; color: var(--exc-text); }
.exc-empty-hint  { margin: 0; font-size: .88rem; }

/* ── Pestañas ───────────────────────────────────────────────────────────── */

.exc-tabs {
  display: flex; gap: .25rem;
  overflow-x: auto;
  border-bottom: 1px solid var(--exc-border);
  margin-bottom: 1.25rem;
  scrollbar-width: none;
}
.exc-tabs::-webkit-scrollbar { display: none; }
.exc-tab {
  padding: .7rem 1rem;
  border: 0; background: transparent;
  color: var(--exc-muted);
  font: inherit; font-size: .9rem; font-weight: 600;
  white-space: nowrap; cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.exc-tab:hover { color: var(--exc-text); }
.exc-tab.is-active { color: var(--exc-accent); border-bottom-color: var(--exc-accent); }
.exc-tabpanel[hidden] { display: none; }

/* ── Avisos flotantes ───────────────────────────────────────────────────── */

.exc-toasts {
  position: fixed;
  /* Por encima de cualquier capa que pueda dispararlos.
   *
   * Estaba en 1080, debajo de las ventanas del sitio (1200) y de las capas a
   * pantalla completa de los módulos: un «Copiado» o un «Historia borrada»
   * lanzado desde dentro de una de ellas se pintaba DETRÁS y no lo veía nadie
   * — la acción parecía no haber hecho nada.
   *
   * Debajo del «¿seguro?» (2000) a propósito: ése interrumpe y tiene que
   * quedarse solo en pantalla. */
  z-index: 1900;
  left: 50%; transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 5rem);
  display: flex; flex-direction: column; gap: .5rem;
  width: min(420px, calc(100vw - 2rem));
  pointer-events: none;
}
@media (min-width: 992px) {
  .exc-toasts { left: auto; right: 1.5rem; transform: none; bottom: 1.5rem; }
}
.exc-toast {
  pointer-events: auto;
  padding: .8rem 1rem;
  border-radius: var(--exc-radius);
  background: var(--exc-surface);
  border: 1px solid var(--exc-border);
  box-shadow: var(--exc-shadow-lg);
  font-size: .9rem;
  animation: exc-toast-in .22s ease;
}
.exc-toast-success { border-left: 3px solid var(--exc-success); }
.exc-toast-danger  { border-left: 3px solid var(--exc-danger); }
.exc-toast-warning { border-left: 3px solid var(--exc-warning); }
.exc-toast-info    { border-left: 3px solid var(--exc-info); }
@keyframes exc-toast-in { from { opacity: 0; transform: translateY(8px); } }

/* ── Barra de progreso ──────────────────────────────────────────────────── */

.exc-progress {
  height: 8px; border-radius: 999px;
  background: var(--exc-surface-2);
  overflow: hidden;
}
.exc-progress-bar {
  /* Nace VACÍA. Sin esta línea un `div` de bloque ocupa el ancho entero de su
     caja, así que toda barra que empiece en cero y la mueva el JS se pinta
     LLENA hasta la primera actualización: el medidor de fuerza del registro
     decía «contraseña fortísima» antes de escribir una letra, y la descarga de
     un modelo 3D arrancaba al 100 %. Quien quiera otro valor inicial lo
     escribe —los tres sitios que lo hacen llevan su `style` o su `.exc-w-*`,
     que van después en esta hoja y ganan por orden. */
  width: 0;
  height: 100%;
  background: var(--exc-accent);
  transition: width .3s ease;
}

/* ── Esqueleto de carga ─────────────────────────────────────────────────── */

.exc-skeleton {
  background: linear-gradient(90deg, var(--exc-surface-2) 25%, var(--exc-border) 50%, var(--exc-surface-2) 75%);
  background-size: 200% 100%;
  animation: exc-shimmer 1.3s infinite;
  border-radius: var(--exc-radius-sm);
}
@keyframes exc-shimmer { to { background-position: -200% 0; } }

/* ── Encabezado de página ───────────────────────────────────────────────── */

.exc-page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.exc-page-title { margin: 0; }
.exc-page-sub   { margin: .25rem 0 0; color: var(--exc-muted); font-size: .92rem; }

/* Varios botones a la derecha del título. En un teléfono el encabezado ya
   envuelve, así que aquí lo que importa es que los botones envuelvan ENTRE
   ELLOS en vez de encogerse hasta cortar su texto. */
.exc-page-acciones {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
}

/* Anchos fijos para demos y barras estáticas — evitan un style="" suelto en
   las vistas. Los porcentajes calculados en tiempo real sí van como atributo,
   porque son un DATO, no una decisión de diseño. */
.exc-w-25 { width: 25%; }
.exc-w-50 { width: 50%; }
.exc-w-70 { width: 70%; }
.exc-w-100 { width: 100%; }

/* ── Pares clave / valor ────────────────────────────────────────────────────
   Vivían sólo en el CSS del admin, pero `.exc-kv` también se usa en el panel
   del usuario («Tu cuenta»), que no carga ese archivo: ahí la etiqueta y el
   valor salían pegados —«Usuario@iigoons»— porque la clase no tenía estilo.
   Al ser un componente compartido, su sitio es este. */
.exc-kv {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; padding: .3rem 0;
  font-size: .88rem;
}
.exc-kv span { color: var(--exc-muted); }

/* ── Selector de iconos ─────────────────────────────────────────────────── */
/* Un solo cajón por pantalla, compartido por todos los campos que lo abren. */

.exc-noscroll { overflow: hidden; }

.exc-icofield { display: inline-flex; }
.exc-icofield-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; flex: none;
  padding: 0;
  background: var(--exc-bg);
  border: var(--exc-border-width) solid var(--exc-border);
  border-radius: var(--exc-radius);
  color: var(--exc-text);
  cursor: pointer;
  transition: border-color .15s ease;
}
.exc-icofield-btn:hover { border-color: var(--exc-accent); }
.exc-icofield-preview { display: inline-flex; font-size: 1.35rem; }

.exc-icopicker {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0;
}
@media (min-width: 640px) {
  .exc-icopicker { align-items: center; padding: 1.5rem; }
}
.exc-icopicker[hidden] { display: none; }

.exc-icopicker-backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, .55);
}

.exc-icopicker-panel {
  position: relative;
  display: flex; flex-direction: column; gap: .9rem;
  width: 100%; max-width: 560px;
  max-height: 88vh;
  padding: 1.1rem;
  background: var(--exc-surface);
  border: var(--exc-border-width) solid var(--exc-border);
  /* En celular sube desde abajo y se pega al borde: el pulgar llega mejor. */
  border-radius: var(--exc-radius-card) var(--exc-radius-card) 0 0;
  box-shadow: var(--exc-shadow-lg);
  animation: exc-ico-in .18s ease;
}
@media (min-width: 640px) {
  .exc-icopicker-panel { border-radius: var(--exc-radius-card); }
}

@keyframes exc-ico-in { from { transform: translateY(14px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .exc-icopicker-panel { animation: none; }
}

.exc-icopicker-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: .75rem;
}
.exc-icopicker-head span { display: block; }

.exc-icopicker-grid {
  display: grid; gap: .4rem;
  grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
  overflow-y: auto;
  min-height: 0;                 /* sin esto el grid no cede y no hay scroll */
  padding: .15rem;
}

.exc-icopicker-item {
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  padding: .6rem .3rem;
  background: transparent;
  border: var(--exc-border-width) solid transparent;
  border-radius: var(--exc-radius);
  color: var(--exc-text);
  font: inherit; font-size: 1.4rem;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.exc-icopicker-item:hover {
  background: var(--exc-surface-2);
  border-color: var(--exc-accent);
}
.exc-icopicker-item[hidden] { display: none; }

.exc-icopicker-name {
  font-size: .62rem; line-height: 1.1;
  color: var(--exc-muted);
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.exc-icopicker-none { margin: 0; text-align: center; color: var(--exc-muted); font-size: .88rem; }

.exc-icopicker-foot {
  display: flex; justify-content: flex-end;
  border-top: var(--exc-border-width) solid var(--exc-border);
  padding-top: .8rem;
}

/* ── El recortador de imágenes y su campo ───────────────────────────────────
 *
 * Vivían en el CSS del admin, que solo se carga en el panel. Desde que el
 * diálogo es un componente compartido —lo usan el admin, el perfil y los
 * logotipos de los negocios— sus estilos tienen que estar donde estén los
 * demás componentes: con el marcado en un sitio y las reglas en otro, el
 * recortador se abre sin estilo y parece roto. */

/* ── Campo de imagen ────────────────────────────────────────────────────── */

.exc-imgfield {
  display: flex; align-items: center; gap: 1rem;
  padding: .8rem;
  border: 1px dashed var(--exc-border);
  border-radius: var(--exc-radius);
}

.exc-imgfield-preview {
  width: 72px; height: 72px; flex: none;
  object-fit: contain;
  background: var(--exc-surface-2);
  border-radius: var(--exc-radius-sm);
  padding: .3rem;
}

.exc-imgfield-empty {
  width: 72px; height: 72px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1.6rem; opacity: .5;
  background: var(--exc-surface-2);
  border-radius: var(--exc-radius-sm);
}

.exc-imgfield-actions { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .35rem; }

.exc-imgfield-actions .exc-input { padding: .45rem; min-height: auto; font-size: .85rem; }

/* ── Recortador de imágenes ────────────────────────────────────────────────
   Compartido por cualquier campo que declare `crop`. El lienzo manda: el
   panel se estira a lo que la pantalla dé, porque encuadrar una foto en una
   ventanita de 300 px es adivinar, no recortar. */

.exc-crop {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
}

.exc-crop[hidden] { display: none; }

.exc-crop-back {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, .72);
}

.exc-crop-panel {
  position: relative;
  width: min(920px, 100%);
  max-height: calc(100dvh - 2rem);
  display: flex; flex-direction: column;
  background: var(--exc-surface);
  border: 1px solid var(--exc-border);
  border-radius: var(--exc-radius);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
  overflow: hidden;
}

.exc-crop-head {
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
  padding: .9rem 1rem;
  border-bottom: 1px solid var(--exc-border);
}

/* El lienzo va sobre fondo neutro oscuro: sobre el color del tema, una foto
   clara y el panel se funden y no se ve dónde termina el recorte. */
.exc-crop-stage {
  flex: 1; min-height: 0;
  background: #14161a;
  padding: .5rem;
}

/* Cropper mide la IMAGEN al arrancar y arma su lienzo de ese tamaño, así que
   el límite va aquí y no en `.cropper-container`: puesto en el contenedor solo
   recortaría un lienzo que ya nació demasiado alto. */
/* `> img` y no `img` a secas: dentro viven también las COPIAS que pinta
   Cropper, y el `margin: 0 auto` las centraba por su cuenta. Con la foto
   alejada —más angosta que el marco— la vista previa salía corrida respecto
   de la foto de abajo (el archivo salía bien; solo mentía la pantalla). */
.exc-crop-stage > img {
  display: block;
  max-width: 100%;
  max-height: 62dvh;
  margin: 0 auto;
}

/* Alejar / acercar / que quepa entera. Fila propia y no dentro del pie: en un
   teléfono el pie ya reparte el ancho entre Cancelar y Usar. */
.exc-crop-zoom {
  display: flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .5rem 1rem 0;
}
.exc-crop-zoom[hidden] { display: none; }

.exc-crop-foot {
  display: flex; align-items: center; gap: .5rem;
  padding: .8rem 1rem;
  border-top: 1px solid var(--exc-border);
}

/* Recorte REDONDO: para los avatares. El archivo que sale sigue siendo
   cuadrado; lo único que cambia es que se encuadra viendo la forma en la que
   la foto se va a ver. `cropper-face` es la superficie que se arrastra y
   `cropper-view-box` el marco: las dos tienen que redondearse o queda un
   círculo dentro de un cuadrado. */
.exc-crop.is-redondo .cropper-view-box,
.exc-crop.is-redondo .cropper-face { border-radius: 50%; }

/* Las guías de Cropper vienen en azul de fábrica: se llevan al acento para
   que el recortador no parezca pegado de otro sitio. */
.exc-crop .cropper-view-box { outline-color: var(--exc-accent); }

.exc-crop .cropper-point,
.exc-crop .cropper-line   { background-color: var(--exc-accent); }

.exc-crop-panel { width: 100%; max-height: 100dvh; height: 100dvh; border-radius: 0; border: 0; }

.exc-crop-stage > img { max-height: 58dvh; }

.exc-crop-foot .exc-btn { flex: 1; }

.exc-crop-head strong { flex: none; }

.exc-crop-size {
  margin-left: auto;
  font-size: .75rem; font-variant-numeric: tabular-nums;
  padding: .15rem .5rem;
  color: var(--exc-muted);
  background: var(--exc-surface-2);
  border-radius: 999px;
}

/* Sin tamaño fijo declarado el JS lo deja vacío, y una píldora vacía se lee
   como que algo no cargó. */
.exc-crop-size:empty { display: none; }

/* «Foto 2 de 4»: quién viene detrás. Se pinta en el acento y no en gris
   porque no es un dato de referencia como el tamaño — es lo que explica por
   qué el diálogo vuelve a abrirse al terminar. */
.exc-crop-paso {
  font-size: .75rem; font-weight: 600; font-variant-numeric: tabular-nums;
  padding: .15rem .5rem;
  color: var(--exc-accent);
  background: var(--exc-accent-soft);
  border-radius: 999px;
}
.exc-crop-paso[hidden] { display: none; }

/* Las proporciones. Van DEBAJO del lienzo y encima de los botones: es una
   decisión sobre lo que se está viendo, no una acción de cerrar. */
.exc-crop-ratios {
  display: flex; gap: .4rem; flex-wrap: wrap;
  padding: .6rem 1rem 0;
}
/* Sin esto el `hidden` no vale nada: un `display` de autor le gana siempre al
   atributo, y la fila saldría vacía en todos los campos del admin. */
.exc-crop-ratios[hidden] { display: none; }

.exc-crop-chip {
  font: inherit; font-size: .78rem; font-weight: 600;
  padding: .3rem .7rem;
  color: var(--exc-muted);
  background: var(--exc-surface-2);
  border: 1px solid var(--exc-border);
  border-radius: 999px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.exc-crop-chip:hover { color: var(--exc-text); }

.exc-crop-chip.is-on {
  color: var(--exc-accent);
  border-color: var(--exc-accent);
  background: var(--exc-accent-soft);
}

/* En un teléfono el recortador ocupa la pantalla entera: los márgenes le
   quitan sitio justo a lo único que importa, que es ver el encuadre. */
@media (max-width: 640px) {
  .exc-crop { padding: 0; }
}

/* ── Los diez colores ───────────────────────────────────────────────────────
 *
 * Paleta CERRADA. Nada de hex libre: un color elegido en modo claro se vuelve
 * ilegible en el oscuro y quien lo eligió no tiene cómo saberlo.
 *
 * Hacen falta las DOS familias de selector porque el color se usa en dos
 * planos distintos:
 *
 *   .exc-cal-<color>       el acento de una RAÍZ (el calendario de esta persona).
 *   .exc-cal-c.is-<color>  el color de UNA cosa: este evento, esta muestra.
 *
 * Están aquí y no en el CSS de un módulo porque los usan varios —el calendario
 * y Signature— y una copia por módulo es exactamente la que un día dirá otro
 * tono. Signature no carga el CSS del calendario: mientras vivieron allá, sus
 * diez muestras salían todas del acento. */

/* El valor de partida. Lo que no diga otra cosa sale del acento del sitio. */
.exc-cal-c { --cal-c: var(--exc-accent); --cal-c-rgb: var(--exc-accent-rgb, 214,162,61); }

.exc-cal-accent, .exc-cal-c.is-accent { --cal-c: var(--exc-accent); --cal-c-rgb: var(--exc-accent-rgb, 214,162,61); }
.exc-cal-rose,   .exc-cal-c.is-rose   { --cal-c: #d1607f; --cal-c-rgb: 209,96,127; }
.exc-cal-coral,  .exc-cal-c.is-coral  { --cal-c: #dd6b4d; --cal-c-rgb: 221,107,77; }
.exc-cal-amber,  .exc-cal-c.is-amber  { --cal-c: #d9922b; --cal-c-rgb: 217,146,43; }
.exc-cal-lime,   .exc-cal-c.is-lime   { --cal-c: #7ba428; --cal-c-rgb: 123,164,40; }
.exc-cal-teal,   .exc-cal-c.is-teal   { --cal-c: #2a9d8f; --cal-c-rgb: 42,157,143; }
.exc-cal-sky,    .exc-cal-c.is-sky    { --cal-c: #2f8fd1; --cal-c-rgb: 47,143,209; }
.exc-cal-indigo, .exc-cal-c.is-indigo { --cal-c: #5b6bd4; --cal-c-rgb: 91,107,212; }
.exc-cal-violet, .exc-cal-c.is-violet { --cal-c: #9061cf; --cal-c-rgb: 144,97,207; }
.exc-cal-slate,  .exc-cal-c.is-slate  { --cal-c: #6b7280; --cal-c-rgb: 107,114,128; }

/* ── Campo con icono dentro, y el ojo de las contraseñas ─────────────────── */

/* Global y no del registro: el acceso y el perfil lo van a querer igual, y dos
   copias del mismo campo acaban separándose. */
.exc-icf { position: relative; display: flex; align-items: center; }

.exc-icf > svg {
  position: absolute;
  left: .8rem;
  /* Sin anclaje vertical, un `absolute` dentro de un contenedor FLEX se coloca
     en su posición estática, que es el borde de ARRIBA: el icono saldría
     pegado al techo del campo en vez de centrado. */
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;   /* el icono no puede robarle el clic al campo */
  color: var(--exc-muted);
}

.exc-icf .exc-input { width: 100%; min-width: 0; padding-left: 2.5rem; }
.exc-icf-pass .exc-input { padding-right: 2.8rem; }

.exc-ojo {
  position: absolute;
  right: .4rem;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--exc-muted);
  cursor: pointer;
}

.exc-ojo:hover { background-color: rgba(127, 127, 127, .16); color: var(--exc-text); }
/* Enseñando la contraseña, el ojo se marca: es un estado que conviene ver de
   reojo antes de que alguien se asome. */
.exc-ojo.is-on { color: var(--exc-accent); }

/* ── El «¿seguro?» del sitio ─────────────────────────────────────────────── */

.exc-conf-velo {
  position: fixed;
  inset: 0;
  z-index: 2000;              /* sobre cualquier hoja: interrumpe a todo */
  background-color: rgba(0, 0, 0, .55);
}

.exc-conf {
  position: fixed;
  z-index: 2001;
  left: 50%;
  /* Anclado por ARRIBA y centrado con `translate`: con `top: 50%` un texto
     largo lo hace crecer hacia los dos lados y en un móvil se sale por el
     borde de abajo, donde están los botones. */
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(26rem, calc(100vw - 2rem));
  max-height: 85dvh;
  overflow-y: auto;
  padding: 1.25rem;
  border: 1px solid var(--exc-border);
  border-radius: 16px;
  background-color: var(--exc-surface);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
}

.exc-conf-t { margin: 0 0 .4rem; font-size: 1.05rem; font-weight: 600; }
.exc-conf-p { margin: 0; font-size: .95rem; line-height: 1.5; }

/* Los datos que hay que REVISAR antes de aceptar.
   Cada uno en su renglón y el valor con letra de máquina: un celular en tipo
   proporcional se lee de corrido y los dígitos repetidos se pierden. Con
   `tabular-nums` cada cifra ocupa lo mismo y el ojo puede contarlas. */
.exc-conf-datos {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .35rem .75rem;
  margin: .85rem 0 0;
  padding: .75rem .85rem;
  border-radius: 12px;
  background-color: var(--exc-surface-2);
}
.exc-conf-datos dt {
  font-size: .78rem;
  color: var(--exc-muted);
  white-space: nowrap;
}
.exc-conf-datos dd {
  margin: 0;
  font-size: .92rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}

/* La casilla del «¿seguro?»: la SEGUNDA decisión, cuando la hay.
   Toda la fila es el objetivo del toque —etiqueta incluida— porque un cuadrito
   de 16 px con el pulgar se falla, y fallarlo aquí significa borrar unas fotos
   que se querían conservar (o al revés). */
.exc-conf-chk {
  display: flex; align-items: flex-start; gap: .6rem;
  margin: .9rem 0 0;
  padding: .7rem .85rem;
  font-size: .88rem; line-height: 1.45;
  border-radius: 12px;
  background-color: var(--exc-surface-2);
  cursor: pointer;
}

.exc-conf-chk input {
  flex: none;
  width: 1.05rem; height: 1.05rem;
  margin: .15rem 0 0;
  accent-color: var(--exc-accent);
  cursor: pointer;
}

.exc-conf-pie {
  display: flex;
  justify-content: flex-end;
  gap: .6rem;
  margin-top: 1.25rem;
}

/* En un teléfono los botones van a lo ancho y el de confirmar ABAJO: es donde
   llega el pulgar, y el de cancelar queda donde no se pulsa sin querer. */
@media (max-width: 400px) {
  .exc-conf-pie { flex-direction: column-reverse; }
  .exc-conf-pie .exc-btn { width: 100%; justify-content: center; }
}

/* Lo destructivo se marca con el borde, no solo con el color del botón: un
   diálogo idéntico para «guardar» y para «borrar» se contesta igual sin leer. */
.exc-conf.is-peligro { border-color: var(--exc-danger); }

/* ═══════════════════════════════════════════════════════════════════════════
   El bloque de alta del formulario de acceso
   (`resources/views/components/acceso.php`)
   ═══════════════════════════════════════════════════════════════════════════

   Va aquí y no en un módulo porque el componente es global: lo pintan la
   pantalla de acceso y la landing del área de negocios.

   Existe para separar DOS trabajos que antes tenían el mismo peso. Debajo del
   botón de entrar había dos enlaces seguidos —«¿olvidaste tu contraseña?» y
   «créala aquí»— y quien llega sin cuenta no viene a recuperar nada: viene a
   crearla. Ahora eso es la segunda acción de la pantalla, con su línea y su
   botón, y recuperar la contraseña se queda donde le toca: pequeño, junto al
   campo que falló. */

.exc-acceso-alta {
  margin-top: 1.5rem;
  padding-top: 1.4rem;
  /* La línea NO llega a los bordes: se desvanece por los dos lados. Una regla
     de punta a punta parte la tarjeta en dos mitades que se leen como dos
     tarjetas pegadas; ésta separa sin cortar. */
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, transparent, var(--exc-border) 22%,
                                var(--exc-border) 78%, transparent) 1;
}

.exc-acceso-alta-t {
  margin: 0 0 .8rem;
  text-align: center;
  font-size: .9rem;
  color: var(--exc-muted);
}

.exc-acceso-alta .exc-btn { justify-content: center; gap: .5rem; }

/* ══ El selector de emojis ════════════════════════════════════════════════
 *
 * Lo pide `View::component('emoji')` y lo mueve `assets/js/emoji.js`. Vive
 * aquí, con los demás componentes compartidos, y no dentro de un módulo:
 * borrar ese módulo se lo llevaría de las otras pantallas que lo usen.
 *
 * Ojo: el panel es `absolute` sobre el documento —lo coloca el JS con la
 * posición del botón, ya sumado el desplazamiento de la página—. Una regla de
 * grupo que le ponga `position: relative` a un ancestro lo movería de sitio,
 * y ganaría por ESPECIFICIDAD, no por orden.
 */

.exc-emoji-campo { position: relative; display: block; }

/* El botón se mete DENTRO del campo, arriba a la derecha. En un textarea el
   texto empieza arriba a la izquierda, así que ahí no tapa nada de lo escrito
   hasta que hay muchas líneas — y entonces ya se está mirando el final. */
.exc-emoji-abrir {
    position: absolute;
    top: .35rem;
    right: .35rem;
    /* 44×44 de área tocable, que es lo que pide un dedo. */
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    font-size: 1.15rem;
    line-height: 1;
    border: 0;
    border-radius: .5rem;
    background: none;
    cursor: pointer;
    opacity: .55;
    transition: opacity .15s ease, background-color .15s ease;
}
.exc-emoji-abrir:hover,
.exc-emoji-abrir[aria-expanded="true"] {
    opacity: 1;
    background: var(--exc-bg-soft, var(--exc-bg));
}

/* El campo deja sitio para el botón. Sin esto, la primera línea se escribe
   por debajo y el texto queda partido de una forma que parece un error. */
.exc-emoji-campo > .exc-textarea,
.exc-emoji-campo > .exc-input { padding-right: 3rem; }

.exc-emoji {
    position: absolute;
    /* Por encima de TODO lo que pueda contener un campo de texto.
     *
     * Estaba en 60, que basta cuando el campo vive en una página normal —así
     * nació, en el editor de Comercio— pero no cuando quien lo abre es una capa
     * a pantalla completa: el compositor de Momentos está en 1110 y el panel
     * quedaba enterrado detrás. Un selector que se abre y no se ve se lee como
     * un botón roto.
     *
     * 1210 lo deja también sobre las ventanas del sitio (1200), que es lo
     * correcto para un desplegable: se abre DESDE ellas. Y por debajo de los
     * avisos flotantes (2000), que interrumpen a todo. */
    z-index: 1210;
    width: min(20rem, calc(100vw - 1rem));
    background: var(--exc-surface);
    border: 1px solid var(--exc-border);
    border-radius: var(--exc-radius, .75rem);
    box-shadow: 0 8px 28px rgb(0 0 0 / .18);
    overflow: hidden;
}
.exc-emoji[hidden] { display: none; }

.exc-emoji-tabs {
    display: flex;
    gap: .15rem;
    padding: .35rem;
    overflow-x: auto;
    scrollbar-width: none;
    border-bottom: 1px solid var(--exc-border);
}
.exc-emoji-tabs::-webkit-scrollbar { display: none; }

.exc-emoji-tab {
    flex: none;
    font: inherit;
    font-size: .72rem;
    padding: .25rem .5rem;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--exc-muted);
    white-space: nowrap;
    cursor: pointer;
}
.exc-emoji-tab.is-on {
    color: var(--exc-accent);
    background: color-mix(in srgb, var(--exc-accent) 12%, transparent);
}

/* Alto fijo: sin él, el grupo de 102 símbolos estiraba el panel hasta salirse
   de la pantalla y el de 49 lo dejaba diminuto — el panel cambiaba de tamaño
   al cambiar de pestaña. */
.exc-emoji-cuerpo {
    max-height: 14rem;
    overflow-y: auto;
    padding: .35rem;
    overscroll-behavior: contain;
}

.exc-emoji-grupo {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(2.1rem, 1fr));
    gap: .1rem;
}
.exc-emoji-grupo[hidden] { display: none; }

.exc-emoji-b {
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    /* El emoji lo dibuja el sistema con SUS colores: aquí `font-size` es lo
       único que se toca. Teñirlo no se puede y no hace falta — que traiga sus
       colores es justo lo que se busca en el contenido. */
    font-size: 1.35rem;
    line-height: 1;
    border: 0;
    border-radius: .35rem;
    background: none;
    cursor: pointer;
    padding: 0;
}
.exc-emoji-b:hover,
.exc-emoji-b:focus-visible { background: var(--exc-bg-soft, var(--exc-bg)); }

@media (prefers-reduced-motion: reduce) {
    .exc-emoji-abrir { transition: none; }
}

/* ══ El sello de verificación ══════════════════════════════════════════════
 *
 * Dos niveles con la MISMA silueta: una roseta de seis lóbulos gordos —un
 * sello de lacre, no la ráfaga de ocho puntas que usan todas las demás—. Lo
 * que cambia entre azul y oro es el acabado, no la figura: dos figuras
 * distintas dirían «dos cosas distintas», y son lo mismo un nivel más arriba.
 *
 * Los trazos van en `Verificado::svg()`. Aquí solo vive el TAMAÑO, y va en
 * `em` a propósito: el sello crece con la letra de su contexto —grande junto
 * al nombre de un perfil, pequeño en una fila de la tabla del admin— sin una
 * regla por sitio.
 */
.exc-sello {
  width: 1.05em; height: 1.05em;
  flex: none;
  vertical-align: -.16em;
  /* Nunca se encoge en un flex apretado: medio sello es un sello roto. */
  min-width: 1.05em;
}

/* Un pelín de sombra para que despegue del fondo. Sin ella, el azul sobre una
   superficie oscura y el oro sobre una clara se funden con lo de detrás. */
.exc-sello-azul { filter: drop-shadow(0 1px 2px rgba(47, 125, 209, .35)); }
.exc-sello-oro  { filter: drop-shadow(0 1px 3px rgba(169, 118, 26, .45)); }

/* Junto a un nombre va pegado a él, no al elemento siguiente: separado por
   igual de los dos parecía un icono suelto entre dos cosas. */
.exc-sello + * { margin-left: .1rem; }

/* ── Soltar un archivo ─────────────────────────────────────────────────────
   Sustituye al control del navegador, que es el único del sitio que no obedece
   al tema, al idioma ni al tamaño de letra —y que en inglés dice «No file
   chosen» aunque la persona tenga Exclivia en español—.

   Es un `<label>`: tocarlo abre el selector sin una línea de JS, así que sin
   guion sigue siendo un campo de archivo que funciona. */
.exc-soltar {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  min-height: 118px;
  padding: 1rem;
  border: 2px dashed var(--exc-border);
  border-radius: 14px;
  background: var(--exc-surface-2);
  color: var(--exc-muted);
  text-align: center;
  cursor: pointer;
  transition: border-color .15s, background-color .15s;
}

.exc-soltar:hover,
.exc-soltar:focus-within { border-color: var(--exc-accent); }

/* Mientras el archivo va encima. El cambio es EVIDENTE a propósito: soltar es
   un gesto a ciegas y hay que ver dónde se va a caer antes de abrir la mano. */
.exc-soltar.is-sobre {
  border-color: var(--exc-accent);
  border-style: solid;
  background-color: rgba(var(--exc-accent-rgb), .1);
}

.exc-soltar-vacio { display: grid; place-items: center; gap: .15rem; }
.exc-soltar-vacio svg { width: 1.7em; height: 1.7em; opacity: .7; }
.exc-soltar-vacio strong { font-size: .92rem; color: var(--exc-text); font-weight: 600; }
.exc-soltar-vacio small  { font-size: .78rem; }

/* Con algo dentro se enseña la foto y se retira la invitación: repetir
   «arrastra aquí» encima de la imagen que ya está puesta confunde sobre si se
   guardó o no. */
.exc-soltar.tiene { padding: .55rem; border-style: solid; }
.exc-soltar.tiene .exc-soltar-vacio { display: none; }

.exc-soltar-prev { margin: 0; width: 100%; }
.exc-soltar-prev img {
  display: block;
  width: 100%;
  max-height: 220px;
  object-fit: contain;
  border-radius: 10px;
}

/* El pie solo aparece con archivo: vacío deja un hueco que descuadra la caja. */
.exc-soltar-pie { display: none; align-items: center; gap: .6rem; width: 100%; }
.exc-soltar.tiene .exc-soltar-pie { display: flex; }

.exc-soltar-pie [data-ex-soltar-nom] {
  flex: 1 1 auto;
  min-width: 0;
  font-size: .78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ══ Gráficas dibujadas en el servidor ══════════════════════════════════════
 *
 * Las pinta `Grafica` (PHP) en SVG. Van en `components.css` —del núcleo— y no
 * en un módulo: el helper es global y el día que otra pantalla dibuje una barra
 * no puede depender de que `canales` esté encendido. Es la misma razón por la
 * que los iconos viven en el núcleo.
 *
 * Los colores salen del TEMA. Ni un hex escrito aquí: una gráfica se ve igual
 * de bien en claro y en noche sin dos juegos de colores que un día se separan.
 */

/* La caja: el dibujo arriba y sus rótulos abajo, como texto de verdad. */
.exc-graf-caja { display: flex; flex-direction: column; gap: .35rem; }

/* El SVG se estira al contenedor: es vectorial, así que a 320 px y a 1400 se
   dibuja igual de nítido sin medir nada en el navegador. El `viewBox` es de
   100×100 y `preserveAspectRatio: none` lo deforma a propósito — de una barra
   lo que importa es su ALTO relativo, no su proporción. Por eso dentro NO puede
   ir texto: se deformaría con él. */
.exc-graf {
  display: block;
  width: 100%;
  height: var(--g-alto, 140px);
}

.exc-graf-barras rect { fill: var(--exc-accent); transition: fill .15s ease; }
.exc-graf-barras rect:hover   { fill: var(--exc-text); }
.exc-graf-barras rect.is-cero { fill: var(--exc-border); }

/* Los rótulos del eje. Fuera del SVG y repartidos: el primero pegado a la
   izquierda y el último a la derecha, que es donde están sus barras. */
.exc-graf-ejes {
  display: flex;
  justify-content: space-between;
  gap: .4rem;
  font-size: .72rem;
  color: var(--exc-muted);
  border-top: 1px solid var(--exc-border);
  padding-top: .3rem;
}
.exc-graf-ejes span { white-space: nowrap; }
/* ── El ranking: barras horizontales con texto de VERDAD ──────────────────── */
.exc-graf-rank {
  list-style: none; margin: 0; padding: 0;
  /* `minmax(0, 1fr)`: una columna implícita `auto` no encoge y un nombre
     largo empujaría la tarjeta entera de lado. */
  display: grid; grid-template-columns: minmax(0, 1fr); gap: .5rem;
}

.exc-graf-rank-fila {
  position: relative;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .7rem;
  border-radius: var(--exc-radius-sm, 8px);
  /* Ancla de la barra de fondo. */
  isolation: isolate;
}

/* La barra va DETRÁS del texto, no al lado: así el renglón no se parte en dos
   columnas y el nombre puede ser tan largo como sea sin encoger la gráfica. */
.exc-graf-rank-barra {
  position: absolute;
  inset: 0 auto 0 0;
  z-index: -1;
  width: var(--g-pct, 0%);
  background: var(--exc-accent);
  opacity: .16;
  border-radius: inherit;
}

.exc-graf-rank-nom {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.exc-graf-rank-n {
  flex: none;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.exc-graf-rank-n small { color: var(--exc-muted); font-weight: 400; margin-left: .25rem; }

/* ── La dona ──────────────────────────────────────────────────────────────── */
.exc-graf-dona { display: block; width: 100%; max-width: 190px; height: auto; }

.exc-graf-dona .g-dona-fondo { stroke: var(--exc-surface-2); }
.exc-graf-dona .g-dona-trozo { transform: rotate(-90deg); transform-origin: 50% 50%; }

.exc-graf-dona .g-dona-n {
  fill: var(--exc-text);
  font-size: 6px; font-weight: 700;
  text-anchor: middle;
}
.exc-graf-dona .g-dona-pie {
  fill: var(--exc-muted);
  font-size: 2.6px;
  text-anchor: middle;
}

/* Seis colores y ni uno más, del tema. Una paleta cerrada, por lo mismo que la
   de las carpetas de la bóveda: un color elegido a ojo se vuelve ilegible en el
   otro modo y quien lo eligió no tiene forma de saberlo. */
.g-dona-trozo.is-c0, .can-est-punto.is-c0 { stroke: #e8b64c; background: #e8b64c; }
.g-dona-trozo.is-c1, .can-est-punto.is-c1 { stroke: #e2679a; background: #e2679a; }
.g-dona-trozo.is-c2, .can-est-punto.is-c2 { stroke: #5aa9e6; background: #5aa9e6; }
.g-dona-trozo.is-c3, .can-est-punto.is-c3 { stroke: #63c69a; background: #63c69a; }
.g-dona-trozo.is-c4, .can-est-punto.is-c4 { stroke: #b58ae0; background: #b58ae0; }
.g-dona-trozo.is-c5, .can-est-punto.is-c5 { stroke: #e08a5a; background: #e08a5a; }

/* ── La cifra grande con su rótulo ─────────────────────────────────────────
 *
 * Vivía en `modules/dashboard_admin/css/admin.css` y la usaba solo el tablero
 * del admin. En cuanto la pantalla de estadísticas de un canal reutilizó las
 * mismas clases salió SIN estilos —el rótulo, el número y el pie en un solo
 * renglón corrido—, que es el fallo de siempre: compartir el nombre de una
 * clase no comparte la hoja. */
.exc-stat { display: flex; flex-direction: column; gap: .15rem; }
.exc-stat-label {
  font-size: .74rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--exc-muted);
}
.exc-stat-value { font-size: 1.9rem; line-height: 1.1; font-weight: 600; }
.exc-stat-foot  { font-size: .8rem; color: var(--exc-muted); }


/* ══════════════════════════════════════════════════════════════════════════
   El recortador de VIDEO — elegir qué trozo se sube
   ══════════════════════════════════════════════════════════════════════════

   Vivía dentro de Momentos (`.mom-trim`) y se mudó aquí entero cuando Secrets
   necesitó el mismo diálogo. La alternativa era copiarlo, y copiar un diálogo
   es tenerlo en dos sitios y arreglarlo en uno — que es exactamente el problema
   que ya dejó una previa mintiendo en este mismo módulo.

   Va en `components.css`, que se carga en todo el sitio, porque el marcado es
   un componente del núcleo (`resources/views/components/videotrim.php`) y una
   capa a pantalla completa no puede depender de que esté cargada la hoja del
   módulo que la abrió.

   El botón de reproducir es SUYO (`.exc-trim-play`) y no el `.mom-play` de la
   tarjeta del muro: aquél se quedó en Momentos porque lo usa también el
   momento, y compartir el nombre sin compartir la hoja es cómo una pantalla
   acaba pintándose sin estilos.
*/
/* ── El recortador de video ───────────────────────────────────────────────── */

/* Sale al elegir un video, SIEMPRE — también con un clip que ya cabe. Va encima
   del compositor, que en un teléfono ya ocupa la pantalla entera; por eso la
   capa es `fixed` y no un bloque dentro del formulario. */
.exc-trim {
  position: fixed;
  inset: 0;
  z-index: 1130;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, .6);
}

.exc-trim-caja {
  width: 100%;
  max-width: 30rem;
  max-height: 94vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: .8rem;
  padding: .8rem;
  background: var(--exc-surface);
  border-radius: var(--exc-radius-card) var(--exc-radius-card) 0 0;
}

.exc-trim-barra-sup {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
}
.exc-trim-barra-sup strong { font-weight: 500; font-size: .95rem; }

.exc-trim-vista {
  position: relative;
  /* Acotada igual que el muro: si aquí se viera un 9:16 completo y en el muro
     recortado, la persona elegiría un encuadre y publicaría otro. */
  aspect-ratio: 4 / 5;
  max-height: 46vh;
  border-radius: var(--exc-radius-sm);
  overflow: hidden;
  background: #000;
}
.exc-trim-vista video { width: 100%; height: 100%; object-fit: contain; display: block; }

/* La tira del tiempo. 44 px de alto: es lo que necesita un pulgar para agarrar
   un asa sin ampliar la pantalla. */
.exc-trim-tira {
  position: relative;
  height: 44px;
  border-radius: var(--exc-radius-sm);
  background: var(--exc-surface-2);
  touch-action: none;   /* o el navegador se lleva el gesto como scroll */
  user-select: none;
}

/* Lo elegido. El resto de la tira queda apagado detrás, así que se lee de un
   vistazo qué se va a publicar y qué se descarta. */
.exc-trim-sel {
  position: absolute;
  top: 0;
  bottom: 0;
  background: rgba(var(--exc-accent-rgb), .22);
  border: 2px solid var(--exc-accent);
  border-radius: var(--exc-radius-sm);
}

.exc-trim-asa {
  position: absolute;
  top: 50%;
  width: 22px;
  height: 100%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  border-radius: var(--exc-radius-sm);
  background: var(--exc-accent);
  cursor: ew-resize;
  touch-action: none;
}
/* La rayita del centro: sin ella el asa se lee como un bloque y no como algo
   que se agarra. */
.exc-trim-asa::after {
  content: '';
  width: 2px;
  height: 45%;
  border-radius: 2px;
  background: var(--exc-accent-contrast);
  opacity: .75;
}
.exc-trim-asa.is-ini { left: 0; }
.exc-trim-asa.is-fin { left: 100%; }
.exc-trim-asa:focus-visible { outline: 2px solid var(--exc-text); outline-offset: 2px; }

.exc-trim-aguja {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--exc-text);
  pointer-events: none;
}

.exc-trim-info {
  margin: 0;
  font-size: .82rem;
  color: var(--exc-muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.exc-trim-info.is-tope { color: var(--exc-warning); }

@media (min-width: 992px) {
  .exc-trim { align-items: center; }
  .exc-trim-caja { border-radius: var(--exc-radius-card); }
}

.exc-trim[hidden] { display: none; }


/* ══ LA BIENVENIDA DE UN NEGOCIO ══════════════════════════════════════════════
 *
 * «Bienvenido a La Parroquia Morales, Gonzalo. Hoy te recomendamos las
 * enchiladas potosinas.» Sale cuando alguien llega a un local que encendió su
 * bienvenida, y la pinta `assets/js/cerca.js`.
 *
 * Vive AQUÍ, con el toast, y no en la hoja de `canales`: esto aparece en
 * cualquier pantalla del sitio —quien llega a un restaurante puede estar
 * mirando su wallet— así que no puede depender de una hoja que esa pantalla
 * no carga. Y decidir en el `<head>` si hace falta obligaría a preguntar por
 * la persona antes de que ninguna vista haya corrido.
 *
 * No es un aviso de seguridad: es un saludo. Se comporta como la bienvenida de
 * una gasolinera y no como un tope — no suena, no vibra, no bloquea y se va
 * sola.
 */
.exc-cerca {
  --cerca-color: var(--exc-accent);

  position: fixed;
  /* ARRIBA y no abajo. Abajo vive la barra del pulgar en el teléfono, y una
     tarjeta que cae encima se cierra sin querer con el primer toque — y de paso
     tapa justo los botones que se usan todos los días. */
  top: calc(env(safe-area-inset-top, 0px) + .75rem);
  left: 50%;
  z-index: 1400;

  width: min(420px, calc(100vw - 1.5rem));
  padding: .85rem .95rem;
  border-radius: var(--exc-radius-card);
  border: var(--exc-border-width) solid var(--exc-border);
  /* El color del negocio SOLO en el borde de arriba. Pintar la tarjeta entera
     de su color la sacaría del tema de quien mira —y un rosa fuerte en modo
     noche deja el texto ilegible—; en una franja de 3 px se reconoce la marca
     sin pelearse con nada. Es la misma decisión que el borde de una carpeta de
     la bóveda. */
  border-top: 3px solid var(--cerca-color);
  background: var(--exc-surface);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .28);

  display: grid;
  gap: .6rem;

  /* Entra desde arriba. `translate` y `opacity` son las dos cosas que el
     navegador anima sin volver a maquetar la página. */
  transform: translate(-50%, -140%);
  opacity: 0;
  transition: transform .26s ease, opacity .26s ease;
}
.exc-cerca.is-dentro { transform: translate(-50%, 0); opacity: 1; }

/* Quien pidió no ver animaciones no las ve: aparece y ya. */
@media (prefers-reduced-motion: reduce) {
  .exc-cerca { transition: none; }
}

/* ── El saludo ─────────────────────────────────────────────────────────────── */

.exc-cerca-top {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
}

.exc-cerca-logo {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--cerca-color);
  background: var(--exc-surface-2);
}

.exc-cerca-txt { flex: 1; min-width: 0; }

.exc-cerca-saludo {
  margin: 0;
  font-size: .98rem;
  font-weight: 600;
  line-height: 1.35;
}
.exc-cerca-suc {
  margin: .15rem 0 0;
  font-size: .8rem;
  color: var(--exc-muted);
}

.exc-cerca-x {
  flex-shrink: 0;
  width: 28px; height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--exc-muted);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
.exc-cerca-x:hover { background: var(--exc-surface-2); color: var(--exc-text); }

/* ── El platillo ───────────────────────────────────────────────────────────── */

.exc-cerca-plato {
  padding: .6rem .7rem;
  border-radius: var(--exc-radius-sm);
  background: var(--exc-surface-2);
}

.exc-cerca-plato-et {
  display: block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--cerca-color);
}

.exc-cerca-plato-nom {
  margin: .15rem 0 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .6rem;
  font-size: .95rem;
  font-weight: 600;
}
.exc-cerca-plato-pr { flex-shrink: 0; font-variant-numeric: tabular-nums; }

.exc-cerca-plato-desc {
  margin: .2rem 0 0;
  font-size: .8rem;
  line-height: 1.4;
  color: var(--exc-muted);
}

/* ── Y la puerta al negocio ────────────────────────────────────────────────── */

.exc-cerca-ir {
  display: block;
  padding: .5rem;
  border-radius: var(--exc-radius-sm);
  background: var(--cerca-color);
  /* Blanco fijo NO: el color lo elige el negocio y puede ser un amarillo sobre
     el que el blanco no se lee. `color-mix` con el negro y el blanco del tema
     no resuelve el contraste real, así que se hereda el texto del sitio sobre
     un fondo del 12 % — se ve de quién es sin apostar a que su color contrasta.
     El color pleno se queda en el borde de arriba, donde no lleva texto. */
  background: color-mix(in srgb, var(--cerca-color) 14%, transparent);
  color: var(--exc-text);
  font-size: .86rem;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}
.exc-cerca-ir:hover { background: color-mix(in srgb, var(--cerca-color) 24%, transparent); }

/* Un restaurante con mesas: «escanea el QR de tu mesa y pide». Va con el
   acento del SITIO, no con el color del negocio: es una función de Exclivia. */
.exc-cerca-mesa { display: grid; gap: .45rem; margin-bottom: .5rem; padding: .7rem .8rem; border-radius: var(--exc-radius-sm);
  border: 1px dashed rgba(var(--exc-accent-rgb), .6); background: rgba(var(--exc-accent-rgb), .1); }
.exc-cerca-mesa p { margin: 0; font-size: .9rem; font-weight: 600; color: var(--exc-text); }
.exc-cerca-mesa-b { display: block; min-height: 44px; padding: .6rem; border-radius: var(--exc-radius-sm); text-align: center;
  background: var(--exc-accent); color: var(--exc-accent-contrast); font-weight: 800; text-decoration: none; }
/* Cualquier botón trabajando —sea o no `.exc-btn`— lo DICE: giro, y el cursor
   de espera. Sin esto un toque en la cocina parecía no haber hecho nada. */
button.is-busy { cursor: progress; }
button.is-busy:not(.exc-btn) { opacity: .85; }

/* ══ El componente de EQUIPO ══════════════════════════════════════════════
 *
 * Vive aquí y no en un módulo porque lo pintan varios —Activa hoy, Host y el
 * POS mañana—: en la hoja de uno, abrirlo desde otro saldría sin estilo.
 * Mismo sitio que el recortador y el selector de emoji, por la misma razón.
 */
.eqc { display: grid; gap: .75rem; grid-template-columns: minmax(0, 1fr); }

.eqc-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
}
.eqc-t { margin: 0; font-size: 1.05rem; font-weight: 600; }
.eqc-p { margin: .15rem 0 0; font-size: .85rem; color: var(--exc-muted); max-width: 54ch; }
.eqc-nota { margin: 0; font-size: .78rem; color: var(--exc-muted); }

.eqc-lista { display: grid; gap: .5rem; grid-template-columns: minmax(0, 1fr); }

.eqc-p {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .6rem .75rem;
  background: var(--exc-surface);
  border: 1px solid var(--exc-border);
  border-radius: var(--exc-radius);
}
.eqc-av { width: 2.5rem; height: 2.5rem; flex: none; }
.eqc-p-t { display: grid; min-width: 0; flex: 1; }
.eqc-p-t strong { font-size: .92rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eqc-p-t small { font-size: .78rem; color: var(--exc-muted); }
/* Quien todavía no tiene cuenta no puede entrar a la app, y se dice: callarlo
   deja al dueño esperando a que esa persona aparezca. */
.eqc-sin { font-style: normal; color: var(--exc-warning); }

.eqc-rol { display: inline-flex; }
.eqc-rol .exc-select { min-width: 8.5rem; }

.eqc-x {
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--exc-muted);
  cursor: pointer;
}
.eqc-x:hover { background: var(--exc-surface-2); color: var(--exc-danger); }

.eqc-dlg { border: 0; padding: 0; background: transparent; width: 100%; max-width: min(30rem, 94vw); }
.eqc-dlg::backdrop { background: rgba(0, 0, 0, .5); }
.eqc-dlg-caja {
  display: grid;
  gap: .75rem;
  padding: 1.25rem;
  background: var(--exc-surface);
  border: 1px solid var(--exc-border);
  border-radius: var(--exc-radius-card);
  box-shadow: var(--exc-shadow-lg);
  max-height: 86dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.eqc-dlg-t { margin: 0; font-size: 1.1rem; font-weight: 600; }
.eqc-dlg-p { margin: -.4rem 0 .25rem; font-size: .85rem; color: var(--exc-muted); }
.eqc-dlg-fila { display: grid; grid-template-columns: minmax(0, 1fr); gap: .75rem; }
@media (min-width: 520px) { .eqc-dlg-fila { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.eqc-dlg-pie { display: flex; justify-content: flex-end; }

.eqc-hallado {
  display: grid;
  gap: .1rem;
  width: 100%;
  text-align: left;
  padding: .6rem .75rem;
  margin-top: .35rem;
  background: var(--exc-surface-2);
  border: 1px solid var(--exc-border);
  border-radius: var(--exc-radius);
  cursor: pointer;
  color: var(--exc-text);
}
.eqc-hallado:hover:not(:disabled) { border-color: var(--exc-accent); }
.eqc-hallado:disabled { opacity: .55; cursor: default; }
.eqc-hallado strong { font-size: .9rem; font-weight: 600; }
.eqc-hallado small, .eqc-hallado em { font-size: .78rem; color: var(--exc-muted); font-style: normal; }
.eqc-nada { margin: .5rem 0 0; font-size: .85rem; color: var(--exc-muted); }

.eqc-nueva { margin-top: .5rem; }
.eqc-nueva summary { cursor: pointer; font-size: .88rem; color: var(--exc-muted); }
.eqc-nueva[open] summary { margin-bottom: .75rem; }
.eqc-nueva form { display: grid; gap: .75rem; }

/* La segunda cara del componente: la pantalla central del negocio. Mismo
   renglón —misma identidad— y otras acciones: en qué herramientas está, su
   ficha y la baja del negocio entero. */
.eqc-p.is-baja { opacity: .55; }
.eqc-baja { font-style: normal; color: var(--exc-muted); }

.eqc-apps { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .35rem; }
.eqc-chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .25rem .6rem;
  min-height: 2rem;
  border: 1px solid var(--exc-border);
  border-radius: 2rem;
  background: var(--exc-surface-2);
  color: var(--exc-muted);
  font-size: .76rem;
  cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.eqc-chip:hover { border-color: var(--exc-accent); }
/* Dentro se marca con FONDO y no solo con color: una pastilla encendida que
   solo cambia de tono no se distingue de una apagada en una pantalla con sol
   encima — y aquí lo que dice es si esa persona entra o no a la agenda. */
.eqc-chip.is-on {
  background: var(--exc-accent);
  border-color: var(--exc-accent);
  color: var(--exc-accent-contrast);
  font-weight: 600;
}

.eqc-acc { display: flex; gap: .25rem; flex: none; }
