/* ============ Encabezado de vista ============ */

.view-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 20px;
}

.view-header h1 {
  font-family: var(--font-display);
  font-size: clamp(26px, 7vw, 34px);
  line-height: 1.05;
  letter-spacing: -0.015em;
  text-transform: uppercase;
}

.view-sub {
  font-size: 14px;
  color: var(--color-text-muted);
  margin-top: 4px;
}

/* Indicador "en vivo" + hora de última actualización. A propósito solo
   dice la hora relativa ("hace 2 min"), no "Actualizado hace 2 min" ni el
   día (eso ya lo dice el selector de día de arriba): el punto que pulsa ya
   comunica "esto se actualiza solo", así el texto puede ser corto. */
.live-status {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
  padding: 5px 9px 5px 7px;
  border-radius: var(--radius-pill);
  background: var(--color-surface);
}

/* Mientras se está actualizando en vivo, el badge lleva el mismo tinte
   teal que su punto pulsante (reusa el token de "éxito", ya calibrado para
   contraste en ambos temas) en vez de gris neutro: es una sola señal de
   color con intención (algo está "vivo"), no decoración suelta. */
.live-status:not(.is-stale) {
  background: var(--color-success-bg);
  color: var(--color-success-fg);
}

.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ucal-teal);
  flex-shrink: 0;
  animation: pulse-dot 1.6s ease-in-out infinite;
}

.live-status.is-stale .live-dot {
  background: var(--gray-400);
  animation: none;
}

/* ============ Skeletons de carga ============
   Reemplazan el "Cargando..." con puntos por placeholders con la forma
   final del contenido (tarjetas Ahora/Siguiente, filas del cronograma,
   tarjetas de stands, comentarios): así la página no "salta" cuando
   llegan los datos reales — se siente más rápida aunque tarde lo mismo.
   Reusan las clases reales de cada tarjeta (.now-card, .schedule-row,
   .promo-card, .comment-card) para el tamaño y el borde; esto solo agrega
   las barras grises con brillo animado de adentro. */

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

.skeleton {
  display: block;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--color-border) 25%, var(--color-surface) 50%, var(--color-border) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.5s ease-in-out infinite;
}

.skeleton-circle {
  border-radius: 50%;
  flex-shrink: 0;
}

.skeleton-text {
  height: 13px;
  margin: 4px 0;
}

.skeleton-text.is-title {
  height: 17px;
}

/* Las tarjetas/filas de esqueleto reusan la forma real (borde, padding,
   radio) pero no son clickeables (no llevan data-idx/role/tabindex, así
   que los manejadores de clic las ignoran solas) — esto solo les apaga el
   cursor y el hover para que no parezcan interactivas. */
.is-skeleton {
  cursor: default;
  pointer-events: none;
}

.is-skeleton:hover {
  border-color: var(--color-border);
}

/* ============ Estados de carga / vacío / error ============ */

.state-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 40px 16px;
  color: var(--color-text-muted);
}

.state-block .dots {
  display: flex;
  gap: 8px;
}

.state-block .dots span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gray-300);
  animation: bounce-dot 1.1s ease-in-out infinite;
}

.state-block .dots span:nth-child(2) { animation-delay: 0.15s; }
.state-block .dots span:nth-child(3) { animation-delay: 0.3s; }

.state-block p {
  font-size: 14px;
  max-width: 32ch;
}

.state-block.is-error p {
  color: var(--color-error-fg);
}

/* ============ Selector de día (evento de 4 días) ============
   Vive "al mismo nivel" que la marca Creatifest: dentro del header
   pegajoso en móvil y arriba del todo en el sidebar de escritorio (ver
   base.css), no dentro del contenido de la vista Cronograma. Por eso es
   compacto: una fila de 4 botones chicos con el nombre del día abreviado,
   pensada para caber junto al logo en vez de ocupar una sección propia. */

.day-tabs-mini {
  display: flex;
  gap: 6px;
}

.day-tab-mini {
  position: relative;
  flex: 1;
  padding: 7px 4px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-border);
  background: var(--color-bg);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: center;
  color: var(--color-text-muted);
}

.day-tab-mini.is-active {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-bg);
}

.day-tab-mini.is-today:not(.is-active) {
  border-color: var(--ucal-teal);
  color: var(--color-text);
}

.day-tab-mini-dot {
  position: absolute;
  top: -3px;
  right: -3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ucal-teal);
  border: 2px solid var(--color-bg);
}

.day-tab-mini.is-active .day-tab-mini-dot {
  border-color: var(--color-text);
}

/* ============ Sección "Ahora" del cronograma ============
   Puede haber más de una actividad al mismo tiempo (dos charlas en paralelo,
   un taller y un torneo a la misma hora...), así que "Ahora" y "Siguiente"
   son GRUPOS que pueden traer 1 o varias tarjetas apiladas, no una tarjeta
   fija cada uno. El label del grupo va una sola vez arriba del grupo, no
   repetido en cada tarjeta. */

.now-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-bottom: 28px;
}

@media (min-width: 560px) {
  .now-grid {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
}

.now-group-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 8px;
}

.now-group-count {
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--gray-400);
}

.now-card-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.now-card {
  position: relative;
  border-radius: var(--radius-md);
  padding: 18px 20px;
  background: var(--color-surface);
  border: 2px solid var(--color-border);
}

.now-card.is-current {
  background: var(--fac-bg, var(--ucal-black));
  color: var(--fac-fg, var(--ucal-white));
  border-color: transparent;
}

/* Las tarjetas "Ahora" / "Siguiente" son un atajo al mismo detalle que se
   abre tocando la fila en "Cronograma completo" (ver cronograma.js). Solo
   son clickeables cuando de verdad tienen una actividad adentro: la versión
   "no hay nada en curso" no lleva a ningún lado. */
.now-card.is-clickable {
  cursor: pointer;
  padding-right: 40px;
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

.now-card.is-clickable:hover {
  border-color: var(--gray-300);
}

.now-card.is-clickable.is-current:hover {
  border-color: transparent;
  opacity: 0.94;
}

.now-card.is-clickable:active {
  transform: scale(0.99);
}

.now-card .schedule-row-chevron {
  position: absolute;
  top: 18px;
  right: 16px;
  color: currentColor;
  opacity: 0.55;
}

.now-card-activity {
  font-family: var(--font-display);
  font-size: clamp(18px, 4.5vw, 22px);
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.now-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 13px;
  font-weight: 600;
}

.now-card-meta span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.now-card-meta svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.now-card.is-empty {
  color: var(--color-text-muted);
  font-size: 14px;
  display: flex;
  align-items: center;
  min-height: 64px;
}

/* ============ Lista completa del cronograma ============ */

.section-label {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: 12px;
}

.section-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.section-label-row .section-label {
  margin-bottom: 0;
}

.schedule-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.schedule-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  cursor: pointer;
  transition: opacity var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.schedule-row:hover {
  border-color: var(--gray-300);
}

.schedule-row:active {
  transform: scale(0.99);
}

.schedule-row.is-past {
  opacity: 0.55;
}

.schedule-row-bar {
  width: 6px;
  flex-shrink: 0;
  background: var(--fac-bg, var(--gray-300));
}

.schedule-row-body {
  flex: 1;
  min-width: 0;
  padding: 14px 4px 14px 2px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.schedule-row-chevron {
  align-self: center;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-right: 14px;
  color: var(--gray-300);
}

.schedule-row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.schedule-time {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-muted);
}

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  flex-shrink: 0;
}

.status-badge::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.status-badge.status-anterior {
  background: var(--gray-100);
  color: var(--color-text-muted);
}

.status-badge.status-actual {
  background: var(--color-success-bg);
  color: var(--color-success-fg);
}

.status-badge.status-siguiente {
  background: var(--color-info-bg);
  color: var(--color-info-fg);
}

/* Badge neutro para el "Tipo" de un stand (Food Truck / Stand / Bus) sobre
   el header de color del modal: no representa un estado, así que no usa
   los colores de anterior/actual/siguiente. */
.status-badge.status-tipo {
  background: rgba(255, 255, 255, 0.28);
  color: inherit;
}

.status-badge.status-tipo::before {
  display: none;
}

/* Mismo badge de "Tipo", pero para la tarjeta en la grilla (fondo blanco),
   donde el translúcido de arriba no tendría contraste. Usa el mismo par
   --fac-bg/--fac-fg que ya trae la tarjeta (heredado de .promo-card), así
   el badge combina con el borde izquierdo en vez de quedar gris suelto. */
.type-badge {
  display: inline-flex;
  align-items: center;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  flex-shrink: 0;
  background: var(--fac-bg, var(--gray-100));
  color: var(--fac-fg, var(--color-text-muted));
}

.schedule-activity {
  font-size: 15.5px;
  font-weight: 700;
  line-height: 1.3;
}

.schedule-desc {
  font-size: 13px;
  line-height: 1.4;
  color: var(--color-text-muted);
}

.schedule-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font-size: 13px;
  color: var(--color-text-muted);
}

.schedule-meta span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.schedule-meta svg {
  width: 13px;
  height: 13px;
}

/* ============ Stands (food trucks, stands, bus) ============ */

.promo-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
}

@media (min-width: 560px) {
  .promo-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* Borde izquierdo coloreado por Tipo (mismo --fac-bg que ya trae el
   avatar/logo, ver stands.js), igual patrón que .schedule-row-bar en
   Cronograma: así Stands se ve tan colorido como Cronograma sin inventar
   un elemento nuevo. */
.promo-card {
  position: relative;
  border: 2px solid var(--color-border);
  border-left: 4px solid var(--fac-bg, var(--color-border));
  border-radius: var(--radius-lg);
  padding: 18px 40px 18px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: pointer;
  text-align: left;
  transition: border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.promo-card:hover {
  border-color: var(--gray-300);
  border-left-color: var(--fac-bg, var(--gray-300));
}

.promo-card:active {
  transform: scale(0.99);
}

.promo-card .schedule-row-chevron {
  position: absolute;
  top: 50%;
  right: 14px;
  transform: translateY(-50%);
}

.promo-card-top {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Placeholder de logo: mientras Al no tenga el logo real del stand (columna
   "Logo" en el Sheet, opcional), se muestra un avatar con sus iniciales,
   coloreado según su Tipo (mismo `tipoTokens` que usa el cronograma) — así
   ya se ve bien de una vez y se actualiza solo apenas haya un logo real. */
.promo-logo {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--fac-bg, var(--gray-900));
  color: var(--fac-fg, var(--ucal-white));
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.01em;
}

.promo-logo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.promo-card-heading {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.promo-card-heading .type-badge {
  align-self: flex-start;
}

.promo-name {
  font-family: var(--font-display);
  font-size: 18px;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  line-height: 1.15;
}

.promo-desc {
  font-size: 14px;
  line-height: 1.5;
  color: var(--color-text-muted);
}

.promo-meta {
  margin-top: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--color-text-muted);
}

.promo-meta span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.promo-meta svg {
  width: 14px;
  height: 14px;
}

/* ============ Formularios (reseñas, encuesta) ============ */

.form-card {
  border: 2px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 20px;
  margin-bottom: 28px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 18px;
}

/* Sin esto, el último campo (normalmente el comentario) queda pegado al
   botón de "Publicar reseña" / "Enviar encuesta" que le sigue: la regla
   asumía que el último campo siempre es lo último dentro de su contenedor,
   pero el botón de envío viene justo después. */
.field:last-of-type {
  margin-bottom: 26px;
}

.field label {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

.field input[type="text"],
.field select,
.field textarea {
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-border);
  background: var(--color-bg);
  font-size: 15px;
  width: 100%;
}

.field textarea {
  min-height: 96px;
  resize: vertical;
  font-family: inherit;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--color-text);
  outline: none;
}

.field-hint {
  font-size: 12px;
  color: var(--gray-400);
}

.field.has-error input,
.field.has-error textarea,
.field.has-error select {
  border-color: var(--ucal-pink);
}

.field-error {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--color-error-text);
  min-height: 15px;
}

/* Selector de estrellas */
.star-rating {
  display: flex;
  gap: 6px;
}

.star-rating button {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: transform var(--dur-fast) var(--ease-out);
}

.star-rating button:active {
  transform: scale(0.9);
}

.star-rating svg {
  width: 26px;
  height: 26px;
  color: var(--gray-300);
  transition: color var(--dur-fast) var(--ease-out);
}

.star-rating button.is-filled svg {
  color: var(--ucal-orange);
  fill: var(--ucal-orange);
}

/* Sí/No (grupo de botones) */
.choice-group {
  display: flex;
  gap: 10px;
}

.choice-btn {
  flex: 1;
  padding: 12px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-border);
  font-size: 14.5px;
  font-weight: 700;
  text-align: center;
}

.choice-btn.is-selected {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-bg);
}

.form-alert {
  margin: 0 0 14px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--color-error-bg);
  color: var(--color-error-fg);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
}

.form-alert.is-success {
  background: var(--color-success-bg);
  color: var(--color-success-fg);
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 52px;
  padding: 14px 22px;
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: 15.5px;
  letter-spacing: 0.01em;
  transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}

.btn:active {
  transform: scale(0.98);
  opacity: 0.88;
  transition-duration: 60ms;
}

.btn-primary {
  background: var(--color-text);
  color: var(--color-bg);
}

.btn-primary:disabled {
  opacity: 0.4;
  transform: none;
}

.btn-outline {
  background: transparent;
  color: var(--color-text);
  border: 2px solid var(--color-text);
}

/* Botón de texto simple (sin fondo ni borde): "Actualizar"/"Salir" del
   panel de administrador, "Editar"/"Borrar"/"Cancelar" del CRUD, "Ahora
   no" del popup de WhatsApp. Nunca tuvo estilo propio antes (solo el
   reset base de <button>, que lo deja como texto plano) — se nota más
   ahora que el CRUD lo usa mucho más seguido. */
.btn-text {
  font-size: 13px;
  font-weight: 700;
  color: var(--color-text-muted);
  transition: color var(--dur-fast) var(--ease-out);
}

.btn-text:hover {
  color: var(--color-text);
}

/* "Borrar" es la única acción destructiva de texto plano en el sitio —
   se tiñe con el mismo token de error que ya usan los mensajes de
   formulario, para que se note que no es una acción cualquiera. */
.admin-crud-actions [data-action="delete"] {
  color: var(--color-error-text);
}

/* ============ Menú virtual (stands) ============ */

.modal-menu-label {
  margin-top: 4px;
}

.menu-link {
  margin-bottom: 24px;
}

.menu-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 24px;
}

.menu-list li {
  font-size: 14px;
  line-height: 1.4;
  padding: 10px 14px;
  background: var(--color-surface);
  border-radius: var(--radius-sm);
}

/* ============ Comentarios recientes (reseñas) ============ */

.comment-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.comment-card {
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}

.comment-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.comment-name {
  font-size: 13.5px;
  font-weight: 700;
}

.comment-stars {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.comment-stars svg {
  width: 14px;
  height: 14px;
  color: var(--ucal-orange);
  fill: var(--ucal-orange);
}

.comment-stars svg.is-empty {
  color: var(--gray-200);
  fill: var(--gray-200);
}

.comment-text {
  font-size: 14px;
  line-height: 1.5;
  color: var(--color-text);
}

.comment-time {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--gray-400);
}

/* ============ Modal: detalle de actividad + reseñas ============ */

/* Abrir/cerrar es una `transition` sobre una clase (`.is-open`), no un
   `@keyframes` de una sola pasada: así, si alguien cierra el modal a medio
   abrir o lo vuelve a abrir enseguida, la animación arranca desde el valor
   que tiene EN PANTALLA en ese instante en vez de saltar al principio del
   keyframe. `pointer-events` (no `display`/`hidden`) es lo que lo saca de
   el paso cuando está cerrado, porque `display` no se puede transicionar. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(18, 18, 16, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.modal-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

@media (min-width: 640px) {
  .modal-overlay {
    align-items: center;
    padding: 24px;
  }
}

/* El "sheet" en sí NO hace scroll (overflow: hidden): solo recorta las
   esquinas redondeadas y sirve de referencia fija para la "X". Todo el
   contenido que sí puede crecer (header + reseñas + formulario) vive
   adentro de .modal-scroll, que es la única pieza que hace scroll. Así la
   "X" queda siempre en el mismo lugar visual sin importar cuánto se haya
   bajado para llegar al botón de publicar reseña. */
.modal-sheet {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  background: var(--color-bg);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  overflow: hidden;
  position: relative;
  display: flex;
  flex-direction: column;
  transform: translateY(24px);
  opacity: 0.6;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}

.modal-overlay.is-open .modal-sheet {
  transform: translateY(0);
  opacity: 1;
}

@media (min-width: 640px) {
  .modal-sheet {
    border-radius: var(--radius-lg);
    max-height: 82vh;
  }
}

.modal-scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
  flex: 1;
  min-height: 0;
}

/* Círculo blanco fijo (no hereda el color de --fac-fg): el header cambia de
   color según la facultad, así que un botón de contraste fijo es más
   confiable que intentar adivinar el color de texto correcto en cada caso.
   También necesita su propia sombra: al ya no vivir dentro del área que
   hace scroll, puede quedar sobre el header de color O sobre el fondo
   blanco de las reseñas, y el círculo translúcido se perdía contra blanco. */
.modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 5;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ucal-black);
  box-shadow: 0 2px 10px rgba(18, 18, 16, 0.18);
  border: 1px solid rgba(18, 18, 16, 0.06);
}

.modal-header {
  padding: 44px 24px 22px;
  background: var(--fac-bg, var(--gray-900));
  color: var(--fac-fg, var(--ucal-white));
}

.modal-header h2 {
  font-family: var(--font-display);
  font-size: clamp(22px, 6vw, 26px);
  line-height: 1.15;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  margin: 8px 0 12px;
}

.modal-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 13.5px;
  font-weight: 600;
}

.modal-meta span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.modal-meta svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.modal-body {
  padding: 22px 24px 28px;
}

.modal-description {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--color-text);
  margin: 0 0 24px;
}

.modal-form-label {
  margin-top: 26px;
}

/* ============ Popup: promo de WhatsApp (Stands) ============
   Mismo mecanismo de transición interrumpible que el modal de detalle
   (opacity + pointer-events sobre .is-open, nunca `display`/`hidden`),
   pero siempre centrado (nunca bottom-sheet): es un mensaje corto, no una
   ficha con scroll. */
.promo-popup-overlay {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: rgba(18, 18, 16, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
}

.promo-popup-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.promo-popup-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  background: var(--color-bg);
  border-radius: var(--radius-lg);
  padding: 36px 24px 24px;
  text-align: center;
  overflow: hidden;
  transform: scale(0.94);
  transition: transform var(--dur-base) var(--ease-out);
}

.promo-popup-overlay.is-open .promo-popup-card {
  transform: scale(1);
}

/* Logo + nombre arriba de todo, bien visible: para que el popup se lea
   como algo oficial de Creatifest/UCAL y no como spam genérico. Sobre la
   misma superficie que el resto de la tarjeta (sin franja de color propia
   ni degradado): más "de marca real" que "banner llamativo". */
.promo-popup-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-bottom: 16px;
}

.promo-popup-brand img {
  height: 22px;
  width: auto;
}

.promo-popup-brand-name {
  font-family: var(--font-display);
  font-size: 17px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text);
}

.promo-popup-icon {
  font-size: 40px;
  line-height: 1;
  margin-bottom: 12px;
}

.promo-popup-title {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.promo-popup-body {
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--color-text-muted);
  margin-bottom: 20px;
}

.promo-popup-card .btn {
  margin-bottom: 8px;
}

.promo-popup-dismiss {
  display: block;
  width: 100%;
}

/* ============ Panel de administrador (?panel=admin) ============ */

.admin-gate-wrap {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.admin-gate-card {
  width: 100%;
  max-width: 360px;
}

.admin-gate-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.admin-gate-header .admin-title {
  margin-bottom: 0;
}

.admin-title {
  font-family: var(--font-display);
  font-size: 22px;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

.admin-wrap {
  max-width: 1000px;
  margin: 0 auto;
  padding: 32px 20px 60px;
}

.admin-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 24px;
}

.admin-header-actions {
  display: flex;
  gap: 16px;
  flex-shrink: 0;
  padding-top: 6px;
}

.admin-summary {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin-bottom: 28px;
}

@media (min-width: 640px) {
  .admin-summary {
    grid-template-columns: repeat(4, 1fr);
  }
}

.admin-stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
}

.admin-stat-num {
  font-family: var(--font-display);
  font-size: 26px;
  letter-spacing: -0.01em;
}

.admin-stat-label {
  font-size: 12px;
  color: var(--color-text-muted);
}

.admin-table-wrap {
  overflow-x: auto;
  margin-bottom: 28px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  white-space: nowrap;
}

.admin-table th,
.admin-table td {
  padding: 10px 14px;
  text-align: left;
  border-bottom: 1px solid var(--color-border);
}

.admin-table td {
  white-space: normal;
  vertical-align: top;
}

.admin-table thead th {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background: var(--color-surface);
  white-space: nowrap;
}

.admin-table tbody tr:last-child td {
  border-bottom: none;
}

.admin-badge-hidden {
  color: var(--color-error-text);
  font-weight: 700;
}

/* Filas del CRUD de administrador (cronograma agrupado por día + stands):
   nombre/meta a la izquierda, Editar/Borrar a la derecha. Una fila oculta
   (Mostrar=false) se atenúa con opacity en vez de un color de "error" —
   ocultar es una decisión del admin, no algo roto. */
.admin-toggle-group {
  margin-bottom: 20px;
}

.admin-toggle-group-label {
  font-family: var(--font-display);
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: 8px;
}

.admin-crud-item {
  margin-bottom: 8px;
}

.admin-crud-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border: 2px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: opacity var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}

.admin-crud-row.is-hidden {
  opacity: 0.55;
}

.admin-toggle-info {
  min-width: 0;
}

.admin-toggle-label {
  font-size: 14px;
  font-weight: 700;
}

.admin-toggle-meta {
  font-size: 12.5px;
  color: var(--color-text-muted);
  margin-top: 2px;
}

.admin-crud-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  flex-shrink: 0;
}

.admin-visible-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--color-text-muted);
  cursor: pointer;
  margin-right: auto;
}

/* 4 tickboxes (Mar/Mié/Jue/Vie) para elegir en qué días del evento se
   muestra un stand — independiente de Mostrar (el apagado total, ver
   admin-visible-toggle arriba). Ver Dias en api/stands.php y el filtro
   por día en js/stands.js. */
.admin-day-toggles {
  display: flex;
  gap: 8px;
}

.admin-day-toggle {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  cursor: pointer;
}

/* Pestañas del panel (4 días + Stands): mismo lenguaje visual que
   .day-tab-mini del sitio público (activa = colores invertidos), pero
   más grandes y con el nombre completo del día en vez de la abreviatura
   — acá hay lugar de sobra, a diferencia del header compacto público. */
.admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
}

.admin-tab {
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  background: var(--color-bg);
  font-size: 13px;
  font-weight: 700;
  color: var(--color-text-muted);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}

.admin-tab.is-active {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-bg);
}

/* Tarjetas de actividad (Cronograma): a diferencia de la fila compacta de
   Stands, acá hace falta lugar para descripción/lugar/tipo visibles de un
   vistazo, la manija de arrastre para reordenar dentro del día, y un
   acento de color por Tipo — mismo --fac-bg/--fac-fg que usa el sitio
   público (ver tipoTokens() en shared/js/utils.js), pasado inline desde
   js/admin-dashboard.js. */
.admin-crono-card {
  display: flex;
  align-items: stretch;
  gap: 10px;
  padding: 12px 14px;
  border: 2px solid var(--color-border);
  border-left: 6px solid var(--fac-bg, var(--color-border));
  border-radius: var(--radius-md);
  transition: opacity var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}

.admin-crono-card.is-hidden {
  opacity: 0.55;
}

.admin-crud-item.is-dragging .admin-crono-card,
.admin-crud-item.is-dragging .admin-crud-row {
  box-shadow: var(--shadow-md);
}

.admin-drag-handle {
  flex-shrink: 0;
  width: 22px;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
  cursor: grab;
  touch-action: none;
}

.admin-drag-handle:active {
  cursor: grabbing;
}

.admin-drag-handle svg {
  width: 16px;
  height: 16px;
}

.admin-crono-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.admin-crono-top {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.admin-crono-actions {
  margin-top: 2px;
  padding-top: 8px;
  border-top: 1px solid var(--color-border);
}

.admin-tipo-custom {
  margin-top: 6px;
}

/* Form inline de crear/editar: vive en el "slot" justo debajo de la fila
   que se está editando (o arriba de todo para "+ Nueva actividad"/"+
   Nuevo stand"), no en un modal — mantiene el patrón sin-JS-framework del
   resto del sitio simple. */
.admin-crud-form {
  margin: 8px 0 16px;
}

.admin-crud-form-actions {
  display: flex;
  gap: 14px;
  align-items: center;
}

.admin-crud-form-actions .btn {
  width: auto;
  min-height: 44px;
  padding: 10px 20px;
}

/* ============ Reduced motion ============ */

@media (prefers-reduced-motion: reduce) {
  .live-dot {
    animation: pulse-dot 2.4s ease-in-out infinite;
  }
  .state-block .dots span {
    animation-name: pulse-dot;
  }
  .skeleton {
    animation: none;
    background: var(--color-border);
  }
  .btn:active,
  .star-rating button:active,
  .schedule-row:active,
  .promo-card:active,
  .now-card.is-clickable:active {
    transform: none;
  }
  /* Se mantiene el fundido (opacity): es la forma "segura" que ya pide
     esta media query en el resto del archivo. Lo que se quita es el
     desplazamiento del sheet, que es la parte que lee como movimiento. */
  .modal-sheet,
  .promo-popup-card {
    transform: none !important;
  }
}
