/* ==========================================================================
   Finde · Componentes
   Cada bloque tiene un solo trabajo. Los estados se expresan con modificadores,
   nunca con estilos sueltos en la pantalla.
   ========================================================================== */

/* --- Iconos ---------------------------------------------------------------
   Los <i data-icon="..."> se reemplazan por SVG en runtime (js/icons.js).
   ------------------------------------------------------------------------ */
[data-icon] { display: inline-flex; flex: none; }
[data-icon] svg { display: block; }


/* ==========================================================================
   Botones
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0 var(--space-6);
  border-radius: var(--radius-pill);
  font-family: var(--font-text);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  letter-spacing: -0.01em;
  transition: transform var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn:active { transform: scale(.98); }
.btn--block { display: flex; width: 100%; }

.btn--primary {
  background: var(--coral-600);
  color: #fff;
  box-shadow: var(--shadow-accent);
}
.btn--primary:active { background: var(--accent-pressed); transform: scale(0.98); }

.btn--secondary {
  background: var(--coral-50);
  color: var(--coral-600);
  border: var(--border-width) solid var(--coral-100);
}

.btn--outline {
  background: var(--bg-surface);
  color: var(--coral-600);
  border: var(--border-width) solid var(--coral-100);
}

.btn--ghost { background: none; color: var(--text-muted); box-shadow: none; }
/* D-146 · Jerson: "esta app es para celular, no tiene sentido el estado
   hover" — en touch no hay cursor que "quede encima" del botón, pero varios
   navegadores móviles SÍ disparan :hover al tocar y lo dejan pegado hasta el
   siguiente toque (un botón que se queda con el color de "encima" aunque el
   dedo ya se fue). `(hover: hover)` solo es verdadero en dispositivos con un
   puntero que de verdad puede posarse sin tocar — mouse o trackpad — nunca
   en un teléfono. Se agrupan acá en vez de dejarlos sueltos junto a cada
   regla para no repetir el mismo media query una decena de veces. */
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover  { background: var(--accent-pressed); }
  .btn--secondary:hover { background: var(--coral-100); }
  .btn--outline:hover { background: var(--coral-50); }
  .btn--ghost:hover { color: var(--text-title); }
}

.btn[disabled], .btn--disabled {
  background: var(--ink-50);
  color: var(--ink-400);
  border-color: transparent;
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: none;
}

/* Botón circular de icono: back, compartir, cerrar. */
.icon-btn {
  width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  color: var(--ink-900);
  box-shadow: var(--shadow-card);
  transition: background-color var(--dur-fast) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .icon-btn:hover { background: var(--ink-50); } } /* D-146 */
.icon-btn--flat { box-shadow: none; background: var(--bg-sunken); }
/* D-141 · el mic del buscador mientras graba — mismo acento coral que el resto de la app. */
.icon-btn--grabando { background: var(--coral-600); color: #fff; animation: icon-btn-pulso 1.4s var(--ease) infinite; }
@keyframes icon-btn-pulso { 0%, 100% { box-shadow: var(--shadow-card), 0 0 0 0 rgba(254,92,71,.45); } 50% { box-shadow: var(--shadow-card), 0 0 0 8px rgba(254,92,71,0); } }
@media (prefers-reduced-motion: reduce) { .icon-btn--grabando { animation: none; } }
.icon-btn--onImage { background: rgba(255,255,255,.92); backdrop-filter: blur(6px); }

/* Enlace de texto. Azul = informativo (D-07). */
.link {
  font-weight: var(--weight-bold);
  color: var(--highlight);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.link--accent { color: var(--accent); text-decoration: none; }
.link--money  { color: var(--money); text-decoration: none; }


/* ==========================================================================
   Encabezados de pantalla
   ========================================================================== */

.appbar {
  flex: none;
  display: grid;
  grid-template-columns: var(--tap-min) 1fr var(--tap-min);
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--gutter) var(--space-4);
}
.appbar__title {
  grid-column: 2;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  color: var(--text-title);
}
.appbar__action {
  grid-column: 3;
  justify-self: end;
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--accent);
}
.appbar--plain { padding-bottom: var(--space-2); }

/* Encabezado con progreso: back · "Paso N de 4" · Omitir + barra */
.stepbar { flex: none; padding: var(--space-2) var(--gutter) var(--space-5); }
.stepbar__row {
  display: grid;
  grid-template-columns: var(--tap-min) 1fr auto;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.stepbar__label {
  text-align: center;
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--highlight);
}
.stepbar__skip {
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
  color: var(--text-muted);
}
/* D-44 · "Onboarding 1": puntos en vez de barra. El actual se estira en
   píldora con resorte; los hechos quedan llenos a medio tono. */
.stepbar__puntos { display: flex; justify-content: center; gap: 6px; height: 8px; }
.stepbar__puntos i { width: 8px; height: 8px; border-radius: var(--radius-pill); background: var(--ink-100); transition: width 560ms var(--spring-suave, var(--ease)), background-color 260ms var(--ease); }
.stepbar__puntos i.hecho { background: color-mix(in srgb, var(--highlight) 45%, var(--ink-100)); }
.stepbar__puntos i.actual { width: 32px; background: var(--highlight); }
.stepbar__row { overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
  .stepbar__puntos i { transition: none; }
}


/* ==========================================================================
   Campos de formulario
   ========================================================================== */

.field { display: block; }
.field__label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--text-title);
}
.field__optional { font-weight: var(--weight-regular); color: var(--text-muted); }
.field__help { margin-top: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.field__hint { margin-top: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }

.input {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--control-h);
  padding: 0 var(--space-4);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-md);
  color: var(--text-title);
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.input:focus-within {
  border-color: var(--coral-600);
  box-shadow: 0 0 0 3px var(--coral-50);
}
.input__icon { color: var(--ink-400); }
.input__suffix { color: var(--text-muted); font-size: var(--text-md); }
.input input, .input select {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  outline: none;
  font-size: var(--text-md);
  color: var(--text-title);
}
.input input::placeholder { color: var(--text-placeholder); }
.input select { appearance: none; cursor: pointer; }
.input--select .input__icon:last-child { color: var(--ink-500); }

/* Código de verificación */
.otp { display: flex; gap: var(--space-2); }
.otp__box {
  flex: 1;
  height: 56px;
  display: grid;
  place-items: center;
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-md);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semi);
  color: var(--text-title);
}
.otp__box--active { border-color: var(--coral-600); border-width: var(--border-width-selected); }
.otp__box--empty::before { content: "–"; color: var(--ink-200); }

/* Casilla de verificación */
.checkbox { display: flex; gap: var(--space-3); align-items: flex-start; cursor: pointer; }
.checkbox__box {
  flex: none;
  width: 22px; height: 22px;
  margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 7px;
  border: var(--border-width-selected) solid var(--border-strong);
  background: var(--bg-surface);
  color: transparent;
  transition: all var(--dur-fast) var(--ease);
}
.checkbox--on .checkbox__box {
  background: var(--green-600);
  border-color: var(--green-600);
  color: #fff;
}
.checkbox__label { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-title); }
.checkbox__note { margin-top: 2px; font-size: var(--text-xs); color: var(--text-muted); }

/* Fila de selección única (métodos de pago) */
.radio-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-4);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-md);
  text-align: left;
}
.radio-row--on { border-color: var(--ink-900); border-width: var(--border-width-selected); padding: 15px; }
.radio-row__dot {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  border: var(--border-width-selected) solid var(--border-strong);
  display: grid; place-items: center;
}
.radio-row--on .radio-row__dot { border-color: var(--ink-900); }
.radio-row--on .radio-row__dot::after {
  content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--ink-900);
}
.radio-row__main { flex: 1; min-width: 0; }
.radio-row__title { font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-title); }
.radio-row__sub { font-size: var(--text-xs); color: var(--text-muted); }
.radio-row__end { display: flex; align-items: center; gap: var(--space-2); }


/* ==========================================================================
   Selección · un solo patrón para todo (D-06)
   ========================================================================== */

/* Feedback de selección (D-16): un pequeño "pop" de escala al marcarse,
   además del cambio de borde/fondo/color que ya tenía cada --on. Aplica a
   cualquier patrón de selección de la app, sin importar en qué archivo esté
   definido el resto de su estilo (cat-tile--on y person--on viven en sus
   propias pantallas). prefers-reduced-motion lo apaga (ver base.css). */
.chip--on, .cat-tile--on, .tile--on, .person--on, .alt-item--on, .radio-row--on {
  animation: select-pop var(--dur-fast) var(--ease);
}
@keyframes select-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}

/* Chip: filtros de una línea (Hoy / Mañana / $$ / ~30 min) */
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 1px;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--text-body);
  text-align: center;
  transition: all var(--dur-fast) var(--ease);
}
.chip__sub { font-size: var(--text-2xs); font-weight: var(--weight-regular); color: var(--text-muted); }
@media (hover: hover) and (pointer: fine) { .chip:hover { border-color: var(--border-strong); } } /* D-146 */
.chip--on {
  background: var(--coral-50);
  border-color: var(--coral-600);
  border-width: var(--border-width-selected);
  color: var(--coral-700);
  padding: 7px 15px;
}
.chip--on .chip__sub { color: var(--coral-600); }

/* Tile: categorías con icono. Intereses y filtros comparten componente.
   Horizontal y en dos columnas: con 8 categorías (D-05) la rejilla queda
   exacta —4 filas llenas, sin huecos— y ningún rótulo se parte. La versión
   vertical de 3 columnas dejaba dos tiles huérfanos y truncaba
   "Entretenimiento". */
.tile {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  min-height: 62px;
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-align: left;
  transition: all var(--dur-fast) var(--ease);
}
.tile__icon {
  flex: none;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--ink-900);
  transition: all var(--dur-fast) var(--ease);
}
.tile__label {
  min-width: 0;
  font-size: var(--text-sm); font-weight: var(--weight-bold);
  color: var(--text-title); line-height: var(--leading-tight);
  overflow-wrap: break-word;
}
@media (hover: hover) and (pointer: fine) { .tile:hover { border-color: var(--border-strong); } } /* D-146 */
.tile--on {
  background: var(--coral-50);
  border-color: var(--accent);
  border-width: var(--border-width-selected);
  padding: 11px;
}
.tile--on .tile__icon { background: var(--accent); color: #fff; }
.tile--on .tile__label { color: var(--coral-700); }

.tile-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
.tile-grid--1 { grid-template-columns: 1fr; }

/* Dos formas de agrupar, y la diferencia importa:
   · chip-row  → opciones con nombre propio (Hoy, Mañana…). Píldora, ancho natural.
   · chip-grid → una escala (presupuesto, distancia). Columnas iguales, porque
                 un paso de la escala no vale más que otro. */
.chip-row  { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2); }

/* Caja de escala. La píldora no servía acá: con dos líneas de texto el radio
   completo la deformaba en óvalo y el contenido quedaba flotando en el medio.
   Rectángulo redondeado, altura fija y una sola línea por dato. */
.chip-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 56px;
  padding: var(--space-2) 6px;
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  text-align: center;
  transition: all var(--dur-fast) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .chip-box:hover { border-color: var(--border-strong); } } /* D-146 */
.chip-box__value {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--text-title);
  line-height: 1;
  white-space: nowrap;
}
.chip-box__sub {
  font-size: var(--text-2xs);
  color: var(--text-muted);
  line-height: 1;
  white-space: nowrap;
}
.chip-box--on {
  background: var(--coral-50);
  border-color: var(--accent);
  border-width: var(--border-width-selected);
  padding: 7px 5px;
}
.chip-box--on .chip-box__value { color: var(--coral-700); }
.chip-box--on .chip-box__sub   { color: var(--coral-600); }
.chip-box--compact { min-height: 46px; }

/* Rótulo del grupo: saca la unidad de adentro de cada caja. */
.scale-label {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-title);
}
.scale-label__unit { font-size: var(--text-xs); font-weight: var(--weight-regular); color: var(--text-muted); }


/* ==========================================================================
   Avatares (D-03)
   ========================================================================== */

.avatar {
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: #fff;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  letter-spacing: 0.01em;
  background: var(--green-600);
  user-select: none;
}
.avatar--sm { width: 34px; height: 34px; font-size: var(--text-xs); }
.avatar--md { width: 44px; height: 44px; font-size: var(--text-sm); }
.avatar--lg { width: 52px; height: 52px; font-size: var(--text-md); }
/* No hay una "paleta de avatares": son colores del sistema. El coral queda
   fuera a propósito —un avatar coral se leería como algo tocable (D-07)—. */
.avatar--c1 { background: var(--green-600); }
.avatar--c2 { background: var(--blue-600); }
.avatar--c3 { background: var(--amber-600); }
.avatar--c4 { background: var(--rose-600); }
.avatar--c5 { background: var(--olive-600); }
.avatar--ring { box-shadow: 0 0 0 2px var(--bg-app), 0 0 0 4px var(--coral-600); }

/* Persona en columna: avatar + nombre + edad */
.person { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); width: 60px; }
.person__name { font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-title); }
.person__age  { font-size: var(--text-2xs); color: var(--text-muted); margin-top: -6px; }
.person-row { display: flex; gap: var(--space-3); }


/* ==========================================================================
   Listas
   ========================================================================== */

.list-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-align: left;
  transition: all var(--dur-fast) var(--ease);
}
.list-item--on { border-color: var(--coral-600); border-width: var(--border-width-selected); padding: 11px 15px; }
.list-item__main { flex: 1; min-width: 0; }
.list-item__title {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-title);
}
.list-item__sub { font-size: var(--text-xs); color: var(--text-muted); }
.list-item__end { flex: none; color: var(--ink-400); }

/* Alt-item: ítem de alternativa en popup de cambio (logo pequeño, sin foto) */
.alt-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  text-align: left;
  transition: all var(--dur-fast) var(--ease);
  cursor: pointer;
}
.alt-item--on { border-color: var(--coral-600); border-width: var(--border-width-selected); padding: 11px 15px; }
.alt-item__logo {
  flex: none;
  width: 36px; height: 36px;
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--ink-500);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: var(--weight-bold);
  display: grid;
  place-items: center;
  letter-spacing: -0.01em;
}
.alt-item__main { flex: 1; min-width: 0; }
.alt-item__name {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-title);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.alt-item__detail { font-size: var(--text-xs); color: var(--text-muted); margin-top: 1px; }
.alt-item__meta {
  display: flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); color: var(--text-muted);
  margin-top: 3px;
}
.alt-item__price {
  flex: none;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--text-title);
  white-space: nowrap;
}


/* ==========================================================================
   Badges
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: 1.3;
  white-space: nowrap;
}
/* Los tres significados que en los mockups compartían un solo estilo.
   Todos viven en la familia del azul —son etiquetas, no acciones— y se
   distinguen por contenido, no por color: el de persona lleva su avatar. */
.badge--info    { background: var(--blue-600);  color: #fff; }        /* novedad */
.badge--person  { background: var(--blue-600);  color: #fff; padding-left: 4px; }
.badge--neutral { background: rgba(255,255,255,.92); color: var(--ink-900); backdrop-filter: blur(6px); }
.badge__avatar {
  width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--font-display); font-size: 9px; font-weight: var(--weight-bold);
  color: #fff;
}
.badge--money   { background: var(--green-50);  color: var(--green-700); }
.badge--soft    { background: var(--bg-sunken); color: var(--ink-900); }
.badge--tag     { background: var(--blue-50);   color: var(--blue-700); }


/* ==========================================================================
   Banners
   ========================================================================== */

.banner {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}
.banner--info   { background: var(--highlight-soft);   color: var(--blue-700); }
.banner--accent { background: var(--accent-soft); color: var(--coral-700); }
.banner--money  { background: var(--money-soft);  color: var(--green-700); }
.banner__icon { flex: none; margin-top: 1px; }

/* Nota al pie con icono, sin fondo */
.note { display: flex; gap: var(--space-2); align-items: center; font-size: var(--text-xs); color: var(--text-muted); }


/* ==========================================================================
   Tarjeta de plan (D-02)
   Tres bloques con jerarquía explícita, no cuatro líneas flotando a la misma
   distancia:
     1 · imagen        qué es, de un vistazo
     2 · cuerpo        título y resumen — lo que se lee
     3 · pie           los hechos: dónde, cuánto tarda, cuánto cuesta
   El pie se separa con una línea fina y sus dos columnas tienen dos líneas
   cada una, así se alinean solas en vez de pelearse por la línea de base.
   ========================================================================== */

.plan-card {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: transform var(--dur-base) var(--ease), box-shadow var(--dur-base) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .plan-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-raised); } } /* D-146 */

/* 2.7:1 es la proporción de las tarjetas de los mockups, no 16:9. */
.plan-card__media { position: relative; aspect-ratio: 2.7 / 1; background: var(--ink-50); }
.plan-card__media img { width: 100%; height: 100%; object-fit: cover; }
.plan-card__badges {
  position: absolute; top: var(--space-3); left: var(--space-3);
  display: flex; gap: var(--space-2);
}

/* --- 2 · cuerpo --------------------------------------------------------- */
.plan-card__body { padding: var(--space-4) var(--space-5) var(--space-3); }
.plan-card__title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: var(--weight-bold);
  color: var(--text-title);
  letter-spacing: var(--tracking-tight);
  line-height: 1.18;
  text-wrap: balance;
}
/* Dos líneas como máximo: a la tercera el resumen deja de ser un resumen. */
.plan-card__desc {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--text-body); /* D-71: de qué trata el plan se lee, no se adivina */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- 3 · pie ------------------------------------------------------------ */
.plan-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin: 0 var(--space-5);
  padding: var(--space-3) 0 var(--space-4);
  border-top: var(--border-width) solid var(--border-subtle);
}
.plan-card__facts { min-width: 0; }
/* Dónde y cuánto cuesta llegar son dos preguntas distintas: dos líneas. */
.plan-card__where {
  display: flex; align-items: center; gap: 5px;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-title);
  line-height: 1.3;
}
.plan-card__where i { color: var(--text-muted); }
.plan-card__logistics {
  display: block;
  margin-top: 2px;
  padding-left: 19px;               /* alineado con el texto, no con el pin */
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.3;
}

/* Precio del hogar. Nunca aparece sin su etiqueta (D-02).
   D-07 original lo ponía en azul (dato, no acción); revisado a coral para
   la tarjeta de plan a pedido explícito de producto — ver decisiones.md. */
.price { flex: none; text-align: right; }
.price__amount {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--accent);
  line-height: 1.1;
  white-space: nowrap;
}
.price__label {
  display: block;
  margin-top: 1px;
  font-size: var(--text-2xs);
  color: var(--text-muted);
  line-height: 1.2;
  white-space: nowrap;
}
.price--lg .price__amount { font-size: var(--text-2xl); }

/* Meta genérica, para listas y hojas modales. */
.meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: var(--text-xs); color: var(--text-muted); }
.meta__sep { color: var(--ink-200); }
.meta__icon { color: var(--text-muted); }

/* Variante horizontal para carruseles */
.plan-card--compact { width: 280px; flex: none; }
.plan-card--compact .plan-card__media { aspect-ratio: 2 / 1; }

/* D-142/D-143 · hallazgo de internet (buscar_general, calce "web"): sin id de
   catálogo, así que no es un link a detalle.html — pero SÍ abre su propia
   hoja con la ficha completa (D-143), así que se queda con el mismo :hover
   de cualquier otra tarjeta tocable. Sin __media: no hay foto que mostrar. */
.plan-card__foot--web { justify-content: flex-start; }
.plan-card__fuente {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-muted);
}
@media (hover: hover) and (pointer: fine) { .plan-card__fuente:hover { color: var(--accent); } } /* D-146 */

/* D-143 · movidas acá desde screens/investigar/investigar.html (D-88): eran
   locales a esa pantalla, pero la hoja de detalle de un hallazgo de internet
   (buscar_general, inicio.html) necesita exactamente la misma ficha — "único
   lugar" en vez de dos copias que puedan divergir. */
.iv-hero { position: relative; aspect-ratio: 4 / 3; border-radius: var(--radius-xl); overflow: hidden; background: linear-gradient(135deg, var(--ink-100), var(--ink-50)); margin-top: var(--space-4); }
.iv-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.iv-hero__sin { position: absolute; inset: 0; display: grid; place-items: center; color: var(--ink-300); }
.iv-distancia { display: inline-flex; align-items: center; gap: 6px; margin-top: var(--space-3); padding: 6px var(--space-3); border-radius: var(--radius-pill); background: var(--bg-sunken); font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--text-muted); }
.iv-datos { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-4); }
.iv-dato { display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-body); }
.iv-dato i { flex: none; margin-top: 2px; color: var(--text-muted); }
.rail { display: flex; gap: var(--space-3); overflow-x: auto; padding: 2px var(--gutter) var(--space-2); scrollbar-width: none; }
.rail::-webkit-scrollbar { display: none; }


/* ==========================================================================
   Itinerario · línea de tiempo
   Reemplaza las tarjetas apiladas con el número al costado. Aquella versión
   tenía tres problemas: gastaba ~92 px por paso, la secuencia dependía de
   leer los números, y cada paso era una caja suelta sin relación visible con
   la siguiente.
   Acá el riel vertical ES la secuencia, la hora vive sobre el riel y todo
   ocurre en una sola superficie. Cuesta ~68 px por paso.
   ========================================================================== */

.timeline { display: block; }

.tl-step {
  display: grid;
  grid-template-columns: 46px 18px 1fr;
  column-gap: var(--space-3);
}

/* Hora alineada a la derecha, para que quede pegada al riel. */
.tl-time {
  padding-top: 15px;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  color: var(--text-muted);
  text-align: right;
}

/* Riel: la línea nace y muere en el punto, no en el borde del paso. */
.tl-rail { position: relative; display: flex; justify-content: center; }
.tl-rail::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  background: var(--border-strong);
}
.tl-step:first-child .tl-rail::before { top: 20px; }
.tl-step:last-child  .tl-rail::before { bottom: calc(100% - 20px); }

.tl-dot {
  position: relative;
  margin-top: 15px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--bg-surface);
}
/* Lo que está incluido pero no es una parada: estacionamiento, traslado. */
.tl-dot--soft { background: var(--bg-surface); border: 2px solid var(--border-strong); }

.tl-content {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) 0 var(--space-4);
}
.tl-thumb {
  flex: none;
  width: 44px; height: 44px;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--ink-50);
}
/* Los pasos sin foto (estacionamiento, traslado) llevan una caja del mismo
   tamaño con un icono: si no, sus títulos quedan 56 px a la izquierda de los
   demás y la columna se ve rota. */
.tl-thumb--icon {
  display: grid; place-items: center;
  background: var(--bg-sunken);
  color: var(--ink-500);
}
.tl-main  { flex: 1; min-width: 0; }
.tl-title { font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-title); line-height: var(--leading-tight); }
.tl-sub   { margin-top: 3px; font-size: var(--text-xs); color: var(--text-muted); line-height: var(--leading-snug); }
.tl-end   { flex: none; text-align: right; }
.tl-price {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--highlight);
  white-space: nowrap;
}
.tl-free   { font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--money); white-space: nowrap; }
.tl-change {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  color: var(--accent);
  white-space: nowrap;
}

/* Total al pie, alineado con la columna de contenido. */
.tl-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-left: calc(46px + 18px + var(--space-3) * 2);
  padding-top: var(--space-3);
  border-top: var(--border-width) solid var(--border-subtle);
}
.tl-total__label  { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-title); }
.tl-total__amount { font-family: var(--font-display); font-size: var(--text-xl); font-weight: var(--weight-bold); color: var(--highlight); }


/* ==========================================================================
   Resumen de compra
   ========================================================================== */

.card {
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  padding: var(--space-5);
  box-shadow: var(--shadow-card);
}
.card--flat { box-shadow: none; }

.sum-row { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); }
.sum-row__label { font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-title); }
.sum-row__sub { font-size: var(--text-xs); color: var(--text-muted); }
.sum-row__amount { font-family: var(--font-display); font-size: var(--text-lg); font-weight: var(--weight-bold); color: var(--text-title); white-space: nowrap; }
.sum-row__was { display: block; font-family: var(--font-text); font-size: var(--text-xs); font-weight: var(--weight-regular); color: var(--text-muted); text-decoration: line-through; text-align: right; }
.sum-total .sum-row__label { font-size: var(--text-xl); font-family: var(--font-display); }
.sum-total .sum-row__amount { font-size: var(--text-2xl); }

.divider { height: 1px; background: var(--border-subtle); border: 0; margin: var(--space-4) 0; }

/* Código QR decorativo: confirmación de pago y detalle de un plan ya agendado */
.qr-card { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); }
.qr-box { width: 168px; height: 168px; }
.qr-box svg { width: 100%; height: 100%; shape-rendering: crispEdges; }


/* ==========================================================================
   Tab bar (D-01)
   ========================================================================== */

/* D-143 · Cápsula que se ensancha — reemplaza al círculo elevado de D-141/D-142.
   Jerson probó 5 propuestas contra referencias reales (21st.dev "Bottom Nav
   Bar", Material Design, Dribbble) y se quedó con esta: una sola píldora
   flotante, inset de los bordes, donde la pestaña activa gana ancho, fondo y
   etiqueta — nadie se eleva por encima de la barra. */
/* D-147 · Jerson, con captura: "se sigue viendo el contenedor del navbar" —
   el margen solo estaba a los lados y abajo (`0 var(--space-3) ...`), así
   que arriba la cápsula tocaba directo el borde de .screen, sin el mismo
   aire cremita que sí tiene a los otros tres lados. Sin ese cuarto lado no
   se lee como "flotando", se lee como "pegada a lo de arriba". */
.tabbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 4px;
  margin: var(--space-3) var(--space-3) max(var(--space-3), env(safe-area-inset-bottom));
  padding: 6px;
  background: var(--bg-surface);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-raised);
}
.tabbar__item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  flex: none;
  padding: 11px 14px;
  border-radius: var(--radius-pill);
  color: var(--ink-400);
  transition: background-color var(--dur-slow) var(--ease), color var(--dur-slow) var(--ease),
              flex-grow var(--dur-slow) var(--ease), padding var(--dur-slow) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .tabbar__item:hover { color: var(--ink-900); } } /* D-146 */
/* D-142 · Jerson: "los iconos... se ven movidos a la izquierda" — el espacio
   hacia la etiqueta salía de `gap`, que reserva su hueco aunque el <span>
   esté en max-width:0 (invisible). Ahora ese espacio sale de un margin-left
   en el propio <span>, que es 0 cuando no hay nada que mostrar. */
.tabbar__item span {
  font-size: var(--text-xs); font-weight: var(--weight-bold);
  white-space: nowrap; overflow: hidden;
  max-width: 0; margin-left: 0; opacity: 0;
  transition: max-width var(--dur-slow) var(--ease), margin-left var(--dur-slow) var(--ease), opacity var(--dur-fast) var(--ease);
}
/* D-143 · Inicio (`destacado`) se queda coral aunque otra pestaña esté
   activa — "el botón del medio se sienta como el más importante, aunque no
   esté seleccionado" — sin ocupar más espacio ni competir con la que sí lo
   está. */
.tabbar__item--importante { color: var(--coral-600); }
.tabbar__item--on { flex: 1; background: var(--coral-50); color: var(--coral-600); }
.tabbar__item--on span { max-width: 80px; margin-left: 7px; opacity: 1; }

/* D-144 · La cápsula "llega ya animándose" en la página nueva — ver la nota
   larga en js/chrome.js (tabbar()). Solo `from`: al faltar el `to`, el motor
   completa el 100% con el valor que la cascada normal (.tabbar__item--on,
   .tabbar__item--importante, o ninguna) ya le da a ese elemento — así el
   punto de llegada SIEMPRE es el mismo que sin animar, nunca hay que
   duplicarlo a mano. */
.tabbar__item--entra { animation: tabbar-entra 520ms var(--spring-suave) both; }
.tabbar__item--entra span { animation: tabbar-label-entra 520ms var(--spring-suave) both; }
.tabbar__item--sale { animation: tabbar-sale 520ms var(--spring-suave) both; }
.tabbar__item--sale span { animation: tabbar-label-sale 520ms var(--spring-suave) both; }
@keyframes tabbar-entra { from { flex-grow: 0; background-color: transparent; padding: 11px 14px; color: var(--color-inicial, var(--ink-400)); } }
@keyframes tabbar-label-entra { from { max-width: 0; margin-left: 0; opacity: 0; } }
@keyframes tabbar-sale { from { flex-grow: 1; background-color: var(--coral-50); color: var(--coral-600); } }
@keyframes tabbar-label-sale { from { max-width: 80px; margin-left: 7px; opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .tabbar__item--entra, .tabbar__item--entra span, .tabbar__item--sale, .tabbar__item--sale span { animation: none; }
}


/* ==========================================================================
   Hoja modal
   ========================================================================== */

.sheet-host { position: absolute; inset: 0; z-index: 20; display: none; }
.sheet-host--open, .sheet-host--closing { display: block; }
.sheet-host__scrim {
  position: absolute; inset: 0;
  background: rgba(23, 37, 60, .55);
  animation: fade var(--dur-base) var(--ease);
}
.sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 82%;
  display: flex; flex-direction: column;
  background: var(--bg-surface);
  border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
  box-shadow: var(--shadow-sheet);
  animation: rise var(--dur-slow) var(--ease);
}
/* Salida: la misma idea al revés, con la curva de salida (--ease-in,
   acelerada) — pero con SU PROPIO @keyframes (fade-out/rise-out), no
   "fade"/"rise" + direction:reverse. Reusar el mismo animation-name no
   dispara animationend de forma confiable cuando el navegador ya corrió
   esa animación antes en el mismo elemento (el motor no siempre lo trata
   como una animación nueva) — y closeSheet() en proto.js depende de ese
   evento para volver a display:none. Con nombres distintos, siempre es
   una animación nueva. */
.sheet-host--closing .sheet-host__scrim { animation: fade-out var(--dur-fast) var(--ease-in) both; }
.sheet-host--closing .sheet { animation: rise-out var(--dur-base) var(--ease-in) both; }
.sheet__grab { width: 40px; height: 4px; border-radius: var(--radius-pill); background: var(--ink-200); margin: var(--space-3) auto var(--space-2); }
.sheet__head { padding: var(--space-2) var(--gutter) var(--space-4); display: flex; align-items: flex-start; gap: var(--space-3); }
.sheet__head .icon-btn { margin-left: auto; }
.sheet__body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--gutter) var(--space-4); scrollbar-width: none; }
.sheet__body::-webkit-scrollbar { display: none; }
.sheet__foot { flex: none; padding: var(--space-4) var(--gutter) var(--space-8); border-top: var(--border-width) solid var(--border-subtle); }

@keyframes fade     { from { opacity: 0 } to { opacity: 1 } }
@keyframes fade-out { from { opacity: 1 } to { opacity: 0 } }
@keyframes rise     { from { transform: translateY(100%) } to { transform: translateY(0) } }
@keyframes rise-out { from { transform: translateY(0) } to { transform: translateY(100%) } }


/* ==========================================================================
   Secciones de pantalla
   ========================================================================== */

.section { padding-top: var(--space-6); }
.section__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.section__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--text-title);
  letter-spacing: var(--tracking-tight);
}


/* ==========================================================================
   Los componentes se usan tanto con <div> como con <span> (dentro de <button>,
   donde no se puede anidar <div>). Estos elementos siempre ocupan su línea.
   ========================================================================== */
.field__help, .field__hint,
.checkbox__label, .checkbox__note,
.list-item__sub,
.radio-row__title, .radio-row__sub,
.sum-row__label, .sum-row__sub, .sum-row__amount,
.price, .price__amount, .price__label,
.tl-time, .tl-title, .tl-sub, .tl-price, .tl-free { display: block; }

/* Los roles de color ganan a los colores por defecto del componente. */
.sum-row__label.money, .sum-row__amount.money { color: var(--money); }
.sum-row__label.accent, .sum-row__amount.accent { color: var(--accent); }


/* ==========================================================================
   Mapa de zonas · esquema, no cartografía
   Dónde SÍ aplica un mapa en Finde (ver docs/decisiones.md D-12): eligiendo
   las zonas donde la familia suele hacer planes. Ahí el mapa hace algo que
   una lista no puede: mostrar que las zonas elegidas son vecinas entre sí.
   No hace zoom, no busca direcciones, no muestra calles: es un selector que
   además ubica.
   ========================================================================== */

.zonemap {
  position: relative;
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.zonemap svg { display: block; width: 100%; height: auto; }

.zonemap__sea   { fill: var(--blue-100); }
.zonemap__foam  { fill: none; stroke: #fff; stroke-opacity: .55; stroke-width: 2.5; stroke-linecap: round; }
.zonemap__land  { fill: var(--ink-50); }

.zonemap__zone       { cursor: pointer; }
.zonemap__shape {
  fill: var(--bg-surface);
  stroke: var(--border-strong);
  stroke-width: 1.5;
  stroke-linejoin: round;
  transition: fill var(--dur-fast) var(--ease), stroke var(--dur-fast) var(--ease);
}
.zonemap__label {
  font-family: var(--font-text);
  font-size: 9px;
  font-weight: var(--weight-bold);
  fill: var(--text-muted);
  text-anchor: middle;
  pointer-events: none;
  transition: fill var(--dur-fast) var(--ease);
}
@media (hover: hover) and (pointer: fine) { .zonemap__zone:hover .zonemap__shape { fill: var(--ink-50); } } /* D-146 */
.zonemap__zone--on .zonemap__shape {
  fill: var(--coral-50);
  stroke: var(--accent);
  stroke-width: 2.5;
}
.zonemap__zone--on .zonemap__label { fill: var(--coral-700); }

/* El contador vive fuera del mapa: flotando abajo a la izquierda se pisaba
   con Chorrillos. */


/* ==========================================================================
   Traslado
   El otro lugar donde un mapa parecería lógico —el detalle del plan— en
   realidad no lo necesita: con un solo destino, un mapa con un pin no dice
   nada. Lo que sí hay que mostrar es la promesa del producto: se estaciona
   una vez y no hay traslados entre actividades. Eso es un diagrama, no un mapa.
   ========================================================================== */

.route {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-3);
}
.route__end { min-width: 0; }
.route__end--to { text-align: right; }
.route__label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--text-title);
  line-height: 1.2;
}
.route__note { display: block; font-size: var(--text-2xs); color: var(--text-muted); }

/* El tramo: línea punteada con el tiempo montado encima. */
.route__leg { position: relative; display: flex; justify-content: center; padding-top: 2px; }
.route__leg::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  border-top: 2px dashed var(--border-strong);
}
.route__time {
  position: relative;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  border: var(--border-width) solid var(--border-subtle);
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold);
  color: var(--text-muted);
  white-space: nowrap;
}
.route__claim {
  display: flex; gap: var(--space-2); align-items: flex-start;
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: var(--border-width) solid var(--border-subtle);
  font-size: var(--text-xs);
  color: var(--text-body);
}
.route__claim i { color: var(--money); margin-top: 1px; }


/* ==========================================================================
   Toast (FindeProto.toast en proto.js)
   ========================================================================== */

.toast {
  position: absolute;
  left: 50%; bottom: var(--space-8);
  transform: translate(-50%, 8px);
  max-width: calc(100% - var(--gutter) * 2);
  padding: var(--space-3) var(--space-5);
  background: var(--ink-900);
  color: #fff;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  box-shadow: var(--shadow-raised);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease), transform var(--dur-base) var(--ease);
  z-index: 30;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
.toast--on { opacity: 1; transform: translate(-50%, 0); }


/* ==========================================================================
   Modos de exploración (demo) · Encontrar
   Formas distintas de recorrer los mismos planes, para comparar antes de
   elegir una — Grilla (la de siempre), Revelar (uno a la vez, deslizando),
   Mapa (tocar el espacio) y Duelo (pensado para el niño de la familia).
   Ver docs/decisiones.md D-17.
   A propósito NO se ve como un control de producto (nada de píldoras con
   el color de marca): es andamiaje de revisión, no una pieza de la UI
   final, así que se trata como una etiqueta discreta de "modo actual",
   no como navegación real.
   ========================================================================== */
.mode-switch {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  padding: 0 var(--gutter) var(--space-3);
}
.mode-switch__item {
  padding-bottom: 3px;
  border-bottom: 1px dashed transparent;
  font-size: 11px;
  font-weight: var(--weight-medium);
  letter-spacing: .01em;
  color: var(--text-muted);
}
.mode-switch__item--on {
  border-bottom-color: var(--ink-200);
  color: var(--ink-900);
}

/* ==========================================================================
   Estados de red (D-21)
   Desde que la app le pregunta al cerebro en vez de leer un archivo, hay dos
   momentos que antes no existían: el que espera y el que falló. Se decidió
   NO tener respaldo local, así que el error no se puede esconder — se muestra,
   con un botón para reintentar, porque la causa más común es un wifi que
   vuelve a los dos segundos.
   ========================================================================== */
.estado-red {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--gutter);
  text-align: center;
  min-height: 180px;
}
.estado-red i { color: var(--text-muted); }
/* D-50: el cargador entra con un fundido corto y se queda al menos 1,1 s
   (js/brain.js). Antes (D-48) no aparecía en esperas de menos de 450 ms. */
.estado-red--cargando { animation: estado-red-aparece 200ms var(--ease) both; }
@keyframes estado-red-aparece { from { opacity: 0; } }
.estado-red .btn { margin-top: var(--space-2); }

/* D-49 · El cargador: tres puntos en ola y el último es el punto de Finde, que
   salta y se aplasta (el mismo "pensando" del chatbot, su D-124). Antes (D-44)
   era la palabra "finde" con su punto; Jerson lo vio en el detalle de un plan
   armado y no le convenció como cargador — el logo animado quedó para la marca
   (D-48: el punto que cae en el logo y en la barra de pestañas). */
.cargador-puntos { display: flex; align-items: flex-end; gap: 6px; height: 22px; }
.cargador-puntos i { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-200); transform-origin: 50% 100%; animation: cargador-ola 1.1s var(--ease) infinite; }
.cargador-puntos i:nth-child(2) { animation-delay: .12s; }
.cargador-puntos i:nth-child(3) { width: 10px; height: 10px; background: var(--accent); animation-name: cargador-punto; animation-delay: .24s; }
@keyframes cargador-ola { 0%, 70%, 100% { transform: translateY(0); background: var(--ink-200); } 30% { transform: translateY(-7px); background: var(--ink-400); } }
@keyframes cargador-punto {
  0%, 70%, 100% { transform: translateY(0) scale(1, 1); }
  20% { transform: translateY(-11px) scale(.9, 1.12); }
  38% { transform: translateY(0) scale(1.3, .72); }
  50% { transform: translateY(0) scale(1, 1); }
}
@media (prefers-reduced-motion: reduce) { .cargador-puntos i { animation: none; } }

/* ==========================================================================
   Selector de opciones (D-21)
   Las decisiones que llegan del cerebro son de tres tipos y NO se pintan
   igual — meterlas en un solo molde es justo cómo aparecería un "S/ undefined"
   en pantalla:
     · opcion  → {id, nombre, titulo, detalle, precio}  → usa .alt-item, que ya
                 existe para esto y muestra precio a la derecha.
     · horario → {id, nombre} y NADA MÁS. No tiene precio porque elegir la hora
                 no cambia lo que cuesta. Va como píldora, no como fila.
     · butacas → ni siquiera trae `opciones`: trae `sala`. Tiene su propia hoja
                 con el mapa, que ya existía.
   ========================================================================== */
.horarios {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}
.horario {
  flex: none;
  min-width: 84px;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  border: var(--border-width) solid var(--border-subtle);
  background: var(--bg-surface);
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-bold);
  color: var(--text-title);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease);
}
.horario:active { transform: scale(.96); }
.horario--on {
  background: var(--coral-50);
  border-color: var(--coral-600);
  border-width: var(--border-width-selected);
  color: var(--coral-700);
}

/* El chip que el itinerario muestra cuando algo falta por elegir. Se lee como
   una ACCIÓN ("Elegir combo"), con el verbo, no como un estado suelto — misma
   decisión que D-83 tomó en el chatbot, para que las dos apps pidan lo mismo
   de la misma forma. */
.tl-elegir {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: var(--space-2);
  padding: 4px var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px dashed var(--coral-600);
  background: var(--coral-50);
  color: var(--coral-700);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  cursor: pointer;
}
.tl-elegir:active { transform: scale(.96); }

/* Ya elegido: deja de pedir atención, pero sigue siendo tocable para cambiarlo. */
.tl-elegido {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: var(--space-2);
  padding: 4px var(--space-3);
  border-radius: var(--radius-pill);
  border: var(--border-width) solid var(--border-subtle);
  background: var(--bg-sunken);
  color: var(--text-body);
  font-size: var(--text-xs);
  cursor: pointer;
}


/* ==========================================================================
   Corazón de Preferidos (D-47) · js/preferidos.js
   Sobre la foto de la tarjeta y en la cabecera del detalle. Vacío es un
   contorno blanco sobre vidrio; guardado se llena de coral y late una vez.
   ========================================================================== */

.pref-btn {
  width: 36px; height: 36px; flex: none;
  display: grid; place-items: center;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .92);
  backdrop-filter: blur(6px);
  color: var(--ink-900);
  box-shadow: 0 2px 8px rgba(23, 37, 60, .12);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 90ms var(--ease), color var(--dur-base) var(--ease);
}
.pref-btn:active { transform: scale(.88); }
.pref-btn svg { transition: fill var(--dur-base) var(--ease); }
.pref-btn--on { color: var(--coral-600); }
.pref-btn--on svg { fill: currentColor; }
.pref-btn--pop svg { animation: pref-late 520ms var(--spring-vivo, ease); }
@keyframes pref-late { 0% { transform: scale(.6); } 60% { transform: scale(1.18); } 100% { transform: scale(1); } }
.plan-card__media .pref-btn { position: absolute; top: var(--space-3); right: var(--space-3); z-index: 2; }
.revelar__silueta .pref-btn { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .pref-btn--pop svg { animation: none; }
}

/* D-72 · La tarjeta de plan ordenada (Jerson: "en los cards se ve demasiado desordenado el contenido"). Tres bloques con
   un trabajo cada uno: qué es (título y de qué trata, 2 líneas), los datos del plan en una sola fila con íconos (cuánto
   dura, cuándo o cuánto tardan en llegar), y abajo dónde queda —una línea, cortada si no entra— frente al precio. Nada
   se parte a media cifra ("Dura 3 h / 35 m"). */
.plan-card__desc { -webkit-line-clamp: 2; }
.plan-card__meta { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 14px; margin-top: var(--space-3); font-size: var(--text-xs); font-weight: var(--weight-medium); color: var(--text-muted); }
.plan-card__meta span { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.plan-card__meta i { display: flex; color: var(--ink-400); }
.plan-card__foot { align-items: center; }
.plan-card__facts { flex: 1; min-width: 0; }
.plan-card__where { min-width: 0; }
.plan-card__where span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plan-card__where i { flex: none; }
