/* =============================================================================
 * F2R Engineering — Component Library
 * Patrones visuales reutilizables. Carga DESPUÉS de variables.css.
 *
 * Cada componente debe usarse por nombre de clase en HTML, sin estilos inline:
 *
 *   <button class="btn btn-primary">Iniciar</button>
 *   <div class="faceplate">…</div>
 *   <span class="badge badge-success">OPERACIONAL</span>
 *
 * Cuando se cree una nueva pantalla, partir de aquí. Si aparece un patrón
 * que no encaje en ningún componente, AÑADIRLO aquí — no improvisar otro
 * sistema CSS local en cada HTML.
 * ============================================================================= */


/* =============================================================================
 * RESET — box-sizing border-box global
 *
 * Sin esto, `width: 100%` + `padding: 8px 12px` produce un ancho real de
 * `100% + 24px` que SE SALE del contenedor. Es la causa #1 de overflows
 * sutiles en los formularios. Con border-box, padding y border se cuentan
 * dentro del ancho declarado.
 * ============================================================================= */
*,
*::before,
*::after {
  box-sizing: border-box;
}


/* =============================================================================
 * SVG SIZING — anula la regla global `svg { max-width:100%; max-height:100% }`
 * de header.css cuando el SVG va dentro de un componente. Cada componente
 * (.btn, .btn-cmd, .btn-close, .badge…) impone su propio tamaño abajo.
 *
 * Los iconos (icons.js) ya vienen con width="1em" height="1em" para auto-
 * dimensionarse al font-size del contenedor. Estos overrides garantizan que
 * NUNCA se estiren al 100% por la regla global.
 * ============================================================================= */
.btn svg,
.btn-icon svg,
.btn-close svg,
.btn-cmd svg,
.badge svg,
.tab svg,
.list-item svg,
[data-icon] svg {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  display: inline-block;
}

/* Los <span data-icon="X"> envuelven el SVG, deben comportarse como icono inline */
[data-icon] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 1;
}


/* =============================================================================
 * BUTTONS — sistema unificado
 * ============================================================================= */
.btn {
  font-family: var(--font-main);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface-overlay);
  color: var(--text-primary);
  cursor: pointer;
  transition: var(--transition-base);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  user-select: none;
  white-space: nowrap;
}
.btn:hover {
  background: var(--surface-elevated);
  border-color: var(--border-emphasis);
}
.btn:active { transform: translateY(1px); }
.btn:disabled,
.btn.is-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Tamaños */
.btn-sm { padding: var(--space-1) var(--space-3); font-size: var(--fs-sm); }
.btn-lg { padding: var(--space-3) var(--space-6); font-size: var(--fs-lg); }

/* Variantes */
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-inverse);
}
.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: #fff;
}

.btn-success {
  background: var(--color-success);
  border-color: var(--color-success);
  color: var(--text-inverse);
}
.btn-success:hover { background: var(--color-success-strong); border-color: var(--color-success-strong); }

.btn-warning {
  background: var(--color-warning);
  border-color: var(--color-warning);
  color: var(--text-inverse);
}
.btn-warning:hover { background: var(--color-warning-strong); border-color: var(--color-warning-strong); }

.btn-danger {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: #fff;
}
.btn-danger:hover { background: var(--color-danger-hover); border-color: var(--color-danger-hover); }

.btn-ghost {
  background: transparent;
  border-color: var(--border-base);
  color: var(--text-secondary);
}
.btn-ghost:hover {
  background: var(--surface-overlay);
  color: var(--text-primary);
}

.btn-icon {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: calc(20px * var(--font-scale));          /* tamaño del icono dentro */
}
.btn-icon:hover {
  background: var(--surface-overlay);
  color: var(--text-primary);
}

/* Botón "Cerrar" (común en faceplates y popups) */
.btn-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  width: 30px;
  height: 30px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: var(--transition-base);
  border-radius: var(--radius-sm);
  font-size: calc(20px * var(--font-scale));          /* tamaño del icono dentro */
  padding: 0;
}
.btn-close:hover {
  color: var(--text-primary);
  background: var(--surface-overlay);
}

/* Botón comando (presionable, hold/release/pulse) */
.btn-cmd {
  font-family: var(--font-main);
  font-size: var(--fs-md);                    /* texto del botón */
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-overlay);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: var(--transition-fast);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* `display: inline-flex` pisa al `[hidden] { display: none }` del UA. Sin
 * esta regla, los .btn-cmd con atributo `hidden` siguen visibles (bug del
 * NEXT en la barra de secuencias). */
.btn-cmd[hidden] {
  display: none;
  min-width: 0;                               /* respeta el contenedor (grid/flex) */
  justify-content: center;
  letter-spacing: 0.5px;
  /* Texto truncado limpio si no cabe */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Hold-to-activate (jog, etc.): bloquear gestos de navegador que
     se comían el pointerup durante un long-press → el bit se quedaba
     pegado a TRUE en el PLC. user-select:none evita arrastres de
     selección; touch-action:none desactiva pan/zoom; -webkit-touch-callout
     evita el menú "guardar imagen" en iOS. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.btn-cmd:hover {
  background: var(--surface-elevated);
  border-color: var(--accent);
  transform: translateY(-1px);
}
.btn-cmd:active { transform: translateY(1px) scale(0.98); }
.btn-cmd > [data-icon],
.btn-cmd > svg {
  font-size: calc(20px * var(--font-scale));          /* tamaño del icono dentro de un comando */
}
.btn-cmd.is-active { background: var(--accent); color: var(--text-inverse); border-color: var(--accent); }

/* Variante compacta — para botones en grids de 3+ columnas (gestión de fallo,
 * acciones secundarias). Texto e icono más pequeños, padding reducido. */
.btn-cmd-compact {
  padding: var(--space-2) var(--space-2);
  font-size: var(--fs-xs);
  letter-spacing: 0;
  gap: var(--space-1);
}
.btn-cmd-compact > [data-icon],
.btn-cmd-compact > svg {
  font-size: calc(16px * var(--font-scale));
}

/* Variantes de color para botones de comando — significado semántico industrial:
 *   primary  = acción principal "ir a posición A" (azul)
 *   secondary= acción secundaria "ir a posición B" (naranja)
 *   neutral  = enable/disable, neutralidad   (gris claro)
 *   danger   = reset de fallos                (rojo)
 * Nota: usan colores propios fuera de la paleta semántica habitual porque en
 * paneles de control industrial el código de color HOME-azul / WORK-naranja
 * es una convención reconocible para los operadores. */
/* Variantes de color para botones de comando — significado semántico industrial.
 * Cada variante saca color de un token --color-* (variables.css) — ÚNICA fuente
 * de verdad para themeing. Si la pantalla Sistema añade un picker de colores,
 * sólo hay que sobrescribir las variables; los botones se repintan solos.
 *
 *   primary   = acción principal a posición/estado A
 *               (HOME en cilindro, REFERENCIAR en servo, START en secuencia)
 *   secondary = acción alternativa a posición/estado B
 *               (WORK en cilindro, STOP en secuencia)
 *   neutral   = acción gris no destructiva (DISABLE)
 *   success   = confirmar / continuar (verde)
 *   warning   = operador manual / atención (JOG fwd y bwd — comparten color)
 *   danger    = reset, abortar, parada de emergencia (rojo)
 */
.btn-cmd-primary {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--text-inverse);
}
.btn-cmd-primary:hover {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

.btn-cmd-secondary {
  background: var(--color-secondary);
  border-color: var(--color-secondary);
  color: var(--text-inverse);
}
.btn-cmd-secondary:hover {
  background: var(--color-secondary-hover);
  border-color: var(--color-secondary-hover);
}

.btn-cmd-neutral {
  background: var(--color-neutral);
  border-color: var(--color-neutral);
  color: var(--text-inverse);
}
.btn-cmd-neutral:hover {
  background: var(--color-neutral-hover);
  border-color: var(--color-neutral-hover);
}

.btn-cmd-success {
  background: var(--color-success);
  border-color: var(--color-success);
  color: var(--text-inverse);
}
.btn-cmd-success:hover {
  background: var(--color-success-strong);
  border-color: var(--color-success-strong);
}

.btn-cmd-warning {
  background: var(--color-warning);
  border-color: var(--color-warning);
  color: var(--text-inverse);
}
.btn-cmd-warning:hover {
  background: var(--color-warning-strong);
  border-color: var(--color-warning-strong);
}

.btn-cmd-danger {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--text-inverse);
}
.btn-cmd-danger:hover {
  background: var(--color-danger-hover);
  border-color: var(--color-danger-hover);
}


/* =============================================================================
 * FACEPLATE — popup modal estilo industrial
 * Reemplaza el HTML repetido en cada device (cylinder, robot, vfd, etc.)
 *
 * Estructura:
 *   <div class="faceplate-overlay is-active">
 *     <div class="faceplate">
 *       <div class="faceplate-header">
 *         <div class="faceplate-title">
 *           <span class="lbl">CILINDRO NEUMÁTICO</span>
 *           <span class="val">Spindle Robot</span>
 *         </div>
 *         <div class="faceplate-actions">
 *           <button class="btn btn-sm btn-ghost">PARÁMETROS</button>
 *           <button class="btn-close">✕</button>
 *         </div>
 *       </div>
 *       <div class="faceplate-body">…contenido específico…</div>
 *       <div class="faceplate-footer">…botones de acción…</div>
 *     </div>
 *   </div>
 * ============================================================================= */
.faceplate-overlay {
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-base), visibility var(--transition-base);
  z-index: var(--z-modal);
}
.faceplate-overlay.is-active {
  opacity: 1;
  visibility: visible;
}

.faceplate {
  background: var(--surface-raised);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  width: min(620px, 92vw);
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: var(--shadow-xl);
  transform: translateY(20px) scale(0.95);
  transition: transform var(--transition-base),
              border-color var(--transition-base),
              box-shadow var(--transition-base);
}
/* Variante de fallo: borde y resplandor rojo cuando el device tiene un fallo activo.
 * El device aplica esta clase via DeviceBase.setFaultState(true). */
.faceplate.is-fault {
  border-color: var(--color-danger);
  animation: faceplate-fault-pulse 2.5s ease-in-out infinite;
}
.faceplate.is-fault .faceplate-header {
  border-bottom-color: var(--color-danger-soft);
}
@keyframes faceplate-fault-pulse {
  0%, 100% { box-shadow: var(--shadow-xl), 0 0 0 1px var(--color-danger), 0 0 16px rgba(231, 76, 60, 0.25); }
  50%      { box-shadow: var(--shadow-xl), 0 0 0 1px var(--color-danger), 0 0 32px rgba(231, 76, 60, 0.55); }
}

.faceplate-overlay.is-active .faceplate {
  transform: translateY(0) scale(1);
}

.faceplate-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.faceplate-title { display: flex; flex-direction: column; }
.faceplate-title .lbl {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: var(--fw-bold);
}
.faceplate-title .val {
  font-size: var(--fs-xl);
  font-weight: var(--fw-black);
  color: var(--text-primary);
  margin-top: 2px;
}

.faceplate-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.faceplate-body  { padding: 0; }
.faceplate-footer {
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

/* =============================================================================
 * SCROLL EN PANELES INTERIORES DEL FACEPLATE
 *
 * Los devices con muchos parámetros (rotating_conveyor tiene 19) generan
 * un panel-params largo que se sale del faceplate, dejando los botones
 * GUARDAR/RECARGAR inaccesibles. Solución: que el panel scrollee internamente
 * mientras el header del faceplate (título + botón cerrar) queda fijo.
 *
 * Aplica también al panel-control para devices con muchos comandos,
 * por consistencia.
 * ============================================================================= */
#panel-control,
#panel-params {
  max-height: calc(88vh - 140px);   /* 88vh = max-height del faceplate, 140px ≈ header + paddings */
  overflow-y: auto;
  /* Pequeño padding interno para que la barra de scroll no se pegue a los inputs */
  padding-right: var(--space-1);
  /* Scrollbar discreta (Firefox) */
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}

/* Scrollbar discreta (Webkit) */
#panel-control::-webkit-scrollbar,
#panel-params::-webkit-scrollbar { width: 6px; }
#panel-control::-webkit-scrollbar-track,
#panel-params::-webkit-scrollbar-track { background: transparent; }
#panel-control::-webkit-scrollbar-thumb,
#panel-params::-webkit-scrollbar-thumb {
  background-color: var(--border-strong);
  border-radius: 3px;
}
#panel-control::-webkit-scrollbar-thumb:hover,
#panel-params::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent);
}


/* =============================================================================
 * PANEL — sección dentro de faceplate o pantalla principal
 * ============================================================================= */
.panel {
  background: var(--surface-overlay);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
}
.panel-title {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-muted);
  font-weight: var(--fw-bold);
  margin-bottom: var(--space-2);
}


/* =============================================================================
 * CARD — superficie con hover y borde acentuado
 * ============================================================================= */
.card {
  background: var(--surface-raised);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  transition: var(--transition-base);
}
.card-hoverable:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-glow-accent);
}
.card-accent { border-left: 4px solid var(--accent); }


/* =============================================================================
 * KPI TILE — números grandes para dashboards
 *
 *   <div class="kpi">
 *     <div class="kpi-label">Producción Hora</div>
 *     <div class="kpi-value">142</div>
 *     <div class="kpi-unit">unidades</div>
 *   </div>
 * ============================================================================= */
.kpi {
  background: var(--surface-raised);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.kpi-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: var(--fw-bold);
}
.kpi-value {
  font-family: var(--font-tech);
  font-size: var(--fs-xl);
  font-weight: var(--fw-black);
  color: var(--text-primary);
  line-height: var(--lh-tight);
}
.kpi-unit {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}
.kpi-success .kpi-value { color: var(--color-success); }
.kpi-warning .kpi-value { color: var(--color-warning); }
.kpi-danger  .kpi-value { color: var(--color-danger); }


/* =============================================================================
 * SERVO POPUP — pestañas JOG/MOVER/MDC/HOME + toggle JOG/INC + panel de parámetros
 *
 * Usado en el popup del eje servo (public/devices/servo.js). Layout compacto:
 * los toggles son botones segmentados (no checkbox) porque el operador en
 * táctil prefiere áreas grandes.
 * ============================================================================= */

/* Pestañas del popup del servo. */
.srv-tabs {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  border-bottom: 1px solid var(--border-base);
  margin-bottom: var(--space-3);
}
.srv-tab {
  font-family: var(--font-main);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 0.5px;
  padding: var(--space-2) var(--space-1);
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--transition-fast), border-color var(--transition-fast);
  touch-action: none;
  user-select: none;
}
.srv-tab:hover { color: var(--text-primary); }
.srv-tab.is-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Sólo el panel activo se ve; los demás llevan `hidden` desde JS. */
.srv-tabpane[hidden] { display: none !important; }

/* Inputs en grid de la MOVER/MDC/HOME — etiqueta arriba, input, sufijo. */
.srv-input-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2) var(--space-3);
}
.srv-input-grid label {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  grid-template-areas:
    "title title"
    "input unit";
}
.srv-input-grid label > span:first-child {
  grid-area: title;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.srv-input-grid label > input { grid-area: input; min-width: 0; }
.srv-input-grid label > .srv-inc-unit { grid-area: unit; }

.srv-row-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: var(--space-2);
}

/* Cabecera del bloque MDC con slot + nombre. */
.srv-mdc-header {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: center;
}
.srv-mdc-slot {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-transform: uppercase;
}
.srv-mdc-slot input { width: 64px; }
.srv-mdc-name {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.srv-mdc-name-label {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.srv-mdc-name-value {
  font-family: var(--font-tech);
  font-size: var(--fs-base);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Botones WorkMode 4-up (ABS/REL/VEL/TOR) — reusa srv-mode-btn pero el grid */
.srv-workmode-row {
  display: grid !important;
  grid-template-columns: repeat(4, 1fr);
}
.srv-workmode-row .srv-mode-btn {
  border-left: 1px solid var(--border-strong);
}
.srv-workmode-row .srv-mode-btn:first-child {
  border-left: none;
}

/* Estado "disabled" sobre srv-mode-btn (ej. WorkMode no permitido por
   PARAMETERS.Enable_Ctrl*). El operador ve el botón visible pero
   apagado, con tooltip "Deshabilitado por configuración". */
.srv-mode-btn.is-disabled,
.srv-mode-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: var(--surface-overlay) !important;
  color: var(--text-muted) !important;
}

/* === resto del bloque servo (definido antes) === */

.srv-mode-toggle {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  flex-shrink: 0;
}
.srv-mode-btn {
  font-family: var(--font-main);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 0.5px;
  padding: var(--space-2) var(--space-4);
  background: var(--surface-overlay);
  color: var(--text-secondary);
  border: none;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
  touch-action: none;
  user-select: none;
}
.srv-mode-btn + .srv-mode-btn { border-left: 1px solid var(--border-strong); }
.srv-mode-btn:hover { background: var(--surface-elevated); color: var(--text-primary); }
.srv-mode-btn.is-active {
  background: var(--accent);
  color: var(--text-inverse);
}

.srv-inc-row {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 0;
}
.srv-inc-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.srv-inc-input {
  flex: 1;
  min-width: 0;
}
.srv-inc-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.srv-inc-unit {
  font-family: var(--font-tech);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* Panel "Parámetros del eje" — colapsable con <details>/<summary>. Tokens
 * de surface para que se adapte a light-mode sin overrides extra. */
.srv-params-details {
  background: var(--surface-overlay);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.srv-params-summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  font-weight: var(--fw-bold);
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-primary);
  user-select: none;
  list-style: none;
}
.srv-params-summary::-webkit-details-marker { display: none; }
.srv-params-summary::before {
  content: "▸";
  display: inline-block;
  transition: transform var(--transition-base);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  width: 12px;
}
.srv-params-details[open] .srv-params-summary::before {
  transform: rotate(90deg);
}
.srv-params-summary:hover { background: var(--surface-elevated); }
.srv-params-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--border-base);
}
.srv-param-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-1) 0;
  border-bottom: 1px dashed var(--border-subtle);
  min-width: 0;
}
.srv-param-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.3px;
  white-space: nowrap;
}
.srv-param-value {
  font-family: var(--font-tech);
  font-size: var(--fs-base);
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}


/* =============================================================================
 * BADGE — etiquetas de estado
 *
 *   <span class="badge badge-success">OPERACIONAL</span>
 *   <span class="badge badge-danger">FALLO</span>
 * ============================================================================= */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px 10px;
  font-family: var(--font-tech);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-radius: var(--radius-pill);
  background: var(--color-neutral-soft);
  color: var(--text-secondary);
  border: 1px solid var(--border-base);
}
.badge-success { background: var(--color-success-soft); color: var(--color-success); border-color: var(--color-success); }
.badge-warning { background: var(--color-warning-soft); color: var(--color-warning); border-color: var(--color-warning); }
.badge-danger  { background: var(--color-danger-soft);  color: var(--color-danger);  border-color: var(--color-danger); }
.badge-info    { background: var(--color-info-soft);    color: var(--color-info);    border-color: var(--color-info); }
.badge-accent  { background: var(--accent-soft);        color: var(--accent);        border-color: var(--accent); }

/* Badge con punto pulsante (alarma/live) */
.badge-pulse::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: currentColor;
  animation: badge-pulse 1.5s infinite;
}
@keyframes badge-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}


/* =============================================================================
 * INPUT — campo de texto/número
 * ============================================================================= */
.input {
  font-family: var(--font-tech);
  font-size: var(--fs-md);
  background: var(--surface-base);
  border: 1px solid var(--border-base);
  color: var(--text-primary);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  transition: var(--transition-base);
  outline: none;
  width: 100%;
}
.input:focus {
  border-color: var(--accent);
  box-shadow: var(--shadow-glow-accent);
}
.input:disabled {
  opacity: 0.5;
  background: var(--surface-overlay);
  cursor: not-allowed;
}
.input.is-error {
  border-color: var(--color-danger);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.field-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  font-weight: var(--fw-bold);
}
.field-help {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.field-error {
  font-size: var(--fs-sm);
  color: var(--color-danger);
}

/* =============================================================================
 * FIELD ROW — fila etiqueta + input para paneles de parámetros
 *
 * Layout vertical (uno debajo de otro), label a la izquierda con ancho fijo
 * y el input ocupa el resto. Usado por DeviceBase.defaultParamsBody para
 * mostrar los parámetros editables del faceplate.
 *
 * Comparado con el grid 2x2: más legible en devices con muchos parámetros,
 * los nombres largos no se cortan y los inputs son lo suficientemente anchos
 * para mostrar números con decimales sin truncar.
 * ============================================================================= */
.field-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.field-row:last-child {
  border-bottom: none;
}
.field-row-label {
  flex: 0 0 200px;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  font-weight: var(--fw-bold);
}
.field-row-input {
  flex: 1;
  min-width: 0;
}
.field-row-input .input,
.field-row-input .checkbox-row {
  width: 100%;
}

/* En pantallas estrechas, label arriba y input abajo (apilado vertical) */
@media (max-width: 540px) {
  .field-row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
  }
  .field-row-label { flex: none; }
}

/* Variante específica para el panel de parámetros: padding más compacto */
.params-panel .field-row { padding: var(--space-2) var(--space-1); }

/* Checkbox row para parámetros booleanos del faceplate. Usado por
 * DeviceBase.defaultParamsBody cuando un parámetro es de tipo "bool". */
.checkbox-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  user-select: none;
}
.input-checkbox {
  width: 18px;
  height: 18px;
  accent-color: var(--accent);
  cursor: pointer;
  margin: 0;
  flex-shrink: 0;
}
.input-checkbox-lbl {
  font-size: var(--fs-base);
  color: var(--text-secondary);
}
.checkbox-row:hover .input-checkbox-lbl { color: var(--text-primary); }
.input-checkbox.is-saved   { outline: 2px solid var(--color-success); outline-offset: 2px; }
.input-checkbox.is-error   { outline: 2px solid var(--color-danger);  outline-offset: 2px; }


/* =============================================================================
 * STATUS DOT — punto coloreado pequeño
 * ============================================================================= */
.dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  display: inline-block;
  background: var(--color-neutral);
}
.dot-success { background: var(--color-success); box-shadow: 0 0 6px var(--color-success); }
.dot-warning { background: var(--color-warning); box-shadow: 0 0 6px var(--color-warning); }
.dot-danger  { background: var(--color-danger);  box-shadow: 0 0 6px var(--color-danger); animation: badge-pulse 1s infinite; }
.dot-info    { background: var(--color-info);    box-shadow: 0 0 6px var(--color-info); }


/* =============================================================================
 * TABS — para pantallas multi-vista (ej. parámetros de un device)
 *
 *   <div class="tabs">
 *     <button class="tab is-active">Estado</button>
 *     <button class="tab">Parámetros</button>
 *   </div>
 * ============================================================================= */
.tabs {
  display: flex;
  border-bottom: 1px solid var(--border-base);
  margin-bottom: var(--space-4);
}
.tab {
  font-family: var(--font-main);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  background: transparent;
  border: none;
  color: var(--text-muted);
  padding: var(--space-3) var(--space-5);
  cursor: pointer;
  transition: var(--transition-base);
  border-bottom: 3px solid transparent;
}
.tab:hover { color: var(--text-primary); }
.tab.is-active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}


/* =============================================================================
 * LIST — lista vertical seleccionable (recetas, alarmas, devices)
 * ============================================================================= */
.list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.list-item {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-overlay);
  border: 1px solid var(--border-base);
  border-left: 4px solid var(--accent);
  color: var(--text-primary);
  cursor: pointer;
  border-radius: var(--radius-md);
  text-align: left;
  transition: var(--transition-base);
  font-family: var(--font-main);
  font-size: var(--fs-base);
}
.list-item:hover {
  background: var(--surface-elevated);
  box-shadow: var(--shadow-glow-accent);
}
.list-item.is-active {
  background: var(--surface-elevated);
  color: var(--accent);
  border-left-width: 6px;
  box-shadow: inset 0 0 15px var(--accent-soft);
}


/* =============================================================================
 * STATE BADGES — pildoritas de estado para faceplates de devices
 * Pequeñas etiquetas redondeadas que indican modos activos (AUTO, MANUAL,
 * EN PRODUCCIÓN, EN HOME, SIMULACIÓN, etc.) en los faceplates de robot, vfd,
 * conveyor_auto, etc.
 *
 *   <div class="state-badge-row">
 *     <div class="state-badge" id="BADGE_AUTO">AUTO</div>
 *     <div class="state-badge is-active" id="BADGE_PROD">EN PRODUCCIÓN</div>
 *   </div>
 * ============================================================================= */
.state-badge-row {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}
/* Grid variant: cada celda ocupa la misma ancho. Útil para que pares
   lógicos (BWD/FWD, REF/INPOS, CARGAR/DESCARGAR) NUNCA se rompan a
   filas distintas — al revés que el flex-wrap clásico de state-badge-row.
   Usar `.is-full` en el badge para que ocupe la fila entera (típico
   para FALLO o HOME, que van solos). */
.state-badge-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}
.state-badge-grid .state-badge { justify-content: center; }
.state-badge.is-full { grid-column: 1 / -1; }

.state-badge {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-1);
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  background: var(--surface-base);
  border: 1px solid var(--border-base);
  color: var(--text-muted);
  transition: var(--transition-slow);
  min-height: 30px;
}
/* Badge "preocupante" — inactivo ya tiene tinte de alerta. Para FALLO. */
.state-badge.is-fault-prone {
  border-color: color-mix(in srgb, var(--color-danger) 30%, var(--border-base));
  color: color-mix(in srgb, var(--color-danger) 70%, var(--text-muted));
}
.state-badge.is-active {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.state-badge.is-active-warn {
  background: var(--color-warning-soft);
  border-color: var(--color-warning);
  color: var(--color-warning);
}
.state-badge.is-active-success {
  background: var(--color-success-soft);
  border-color: var(--color-success);
  color: var(--color-success);
}
.state-badge.is-active-danger {
  background: var(--color-danger-soft);
  border-color: var(--color-danger);
  color: var(--color-danger);
}
.state-badge > [data-icon],
.state-badge > svg { font-size: calc(13px * var(--font-scale)); }


/* =============================================================================
 * INFO ROW — fila etiqueta + valor monospace (programa activo, fault code,
 * pointer, setpoint, etc.)
 *
 *   <div class="info-row">
 *     <div class="info-row-lbl">Programa activo</div>
 *     <div class="info-row-val" id="ROB_PROGRAM">PRG_007</div>
 *   </div>
 * ============================================================================= */
.info-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: var(--surface-base);
  border: 1px solid var(--border-base);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
}
.info-row-lbl {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: var(--fw-bold);
}
.info-row-val {
  font-family: var(--font-tech);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  color: var(--accent);
}
.info-row.info-row-danger .info-row-val { color: var(--color-danger); }


/* =============================================================================
 * INPUT INLINE ROW — para set-points compactos
 *
 *   <div class="inline-input-row">
 *     <span class="inline-input-lbl">Pointer</span>
 *     <input class="input" type="number">
 *     <button class="btn btn-sm btn-primary">SET</button>
 *   </div>
 * ============================================================================= */
.inline-input-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}
.inline-input-lbl {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  font-weight: var(--fw-bold);
  min-width: 60px;
  letter-spacing: 0.5px;
}


/* =============================================================================
 * PARAMS SECTION — encabezado de grupo de parámetros
 *
 *   <div class="params-section">IDENTIFICACIÓN</div>
 *   <div class="field">…</div>
 *   <div class="params-section">CONFIGURACIÓN</div>
 *   …
 * ============================================================================= */
.params-section {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 1px;
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--border-subtle);
  margin-top: var(--space-2);
}


/* =============================================================================
 * DIALOG — modales de confirmación y entrada (reemplazan confirm() y prompt())
 *
 * Construidos con .faceplate-overlay + .faceplate del design system, así que
 * heredan el aspecto del resto del HMI. El módulo `public/js/dialogs.js`
 * define las funciones showConfirm() y showPrompt().
 *
 * Uso JS:
 *   const yes = await window.showConfirm("¿Estás seguro?");
 *   const v   = await window.showPrompt("Introduce número:", { type: "number", min: 1, max: 50 });
 *   if (v !== null) { ... }   // null = canceló
 * ============================================================================= */
.dialog {
  width: min(440px, 90vw);
  max-height: 90vh;
  overflow-y: auto;
}
.dialog-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin: 0 0 var(--space-2);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.dialog-message {
  font-size: var(--fs-md);
  color: var(--text-secondary);
  line-height: var(--lh-base);
  margin: 0 0 var(--space-5);
}


/* =============================================================================
 * TOAST — notificaciones temporales (errores, confirmaciones, info)
 *
 * Reemplaza los alert() del navegador. Aparecen arriba a la derecha,
 * desaparecen solos tras unos segundos (5s por defecto).
 *
 * Uso desde JS:
 *   showToast("Cambios guardados");                // info (default)
 *   showToast("Error al guardar", "danger");
 *   showToast("Acción completada", "success");
 *   showToast("Edición no permitida", "warning");
 *
 * El módulo `public/js/toast.js` define la función. La carga en orden:
 *   <script src="./js/icons.js"></script>
 *   <script src="./js/toast.js"></script>
 * ============================================================================= */
.toast-container {
  position: fixed;
  top: var(--space-4);
  right: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  pointer-events: none;     /* permite clicks "a través" excepto en .toast */
  max-width: min(420px, calc(100vw - 2 * var(--space-4)));
}

.toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  background: var(--surface-raised);
  border: 1px solid var(--border-base);
  border-left: 4px solid var(--color-info);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-md);
  color: var(--text-primary);
  font-family: var(--font-main);
  animation: toast-slide-in 0.25s ease;
  min-width: 280px;
}

.toast.is-leaving {
  animation: toast-slide-out 0.25s ease forwards;
}

@keyframes toast-slide-in {
  from { transform: translateX(120%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
@keyframes toast-slide-out {
  from { transform: translateX(0);    opacity: 1; }
  to   { transform: translateX(120%); opacity: 0; }
}

.toast-icon {
  font-size: calc(24px * var(--font-scale));
  color: var(--color-info);
  flex-shrink: 0;
}
.toast-body {
  flex: 1;
  line-height: var(--lh-base);
}
.toast-close {
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  font-size: calc(18px * var(--font-scale));
  padding: 0;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  transition: var(--transition-fast);
}
.toast-close:hover {
  color: var(--text-primary);
  background: var(--surface-overlay);
}

/* Variantes semánticas */
.toast-success { border-left-color: var(--color-success); }
.toast-success .toast-icon { color: var(--color-success); }

.toast-warning { border-left-color: var(--color-warning); }
.toast-warning .toast-icon { color: var(--color-warning); }

.toast-danger  { border-left-color: var(--color-danger); }
.toast-danger  .toast-icon { color: var(--color-danger); }


/* =============================================================================
 * UTILITIES — helpers atómicos (úsalos con moderación)
 * ============================================================================= */
.flex      { display: flex; }
.flex-col  { display: flex; flex-direction: column; }
.flex-1    { flex: 1; }
.gap-1     { gap: var(--space-1); }
.gap-2     { gap: var(--space-2); }
.gap-3     { gap: var(--space-3); }
.gap-4     { gap: var(--space-4); }
.items-center  { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-end   { justify-content: flex-end; }

.text-muted   { color: var(--text-muted); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-danger  { color: var(--color-danger); }

.font-tech { font-family: var(--font-tech); }
.uppercase { text-transform: uppercase; letter-spacing: 0.5px; }

.hidden { display: none !important; }


/* =============================================================================
 * PREFERENCIAS DEL HMI — overrides activados por clases en <body>
 *
 * Las setea preferences.js a partir de localStorage.plc_prefs. No tocar a mano
 * desde otros sitios; la clave única es Prefs.set().
 * ============================================================================= */

/* ── Densidad — escala los tokens de espaciado y la base tipográfica ────────
 * Comfortable es el default (sin override). Compact comprime para pantallas
 * más densas (más KPIs visibles, sidebar menos hambrienta de scroll).
 * Spacious va al revés, útil en pantallas táctiles grandes con guantes. */
body.density-compact {
  --space-1:   3px;
  --space-2:   6px;
  --space-3:   9px;
  --space-4:  12px;
  --space-5:  15px;
  --space-6:  18px;
  --space-8:  24px;
  --space-10: 30px;
  --space-12: 36px;
  --fs-base:  12px;
  --fs-md:    13px;
  --fs-lg:    16px;
}
body.density-spacious {
  --space-1:   5px;
  --space-2:  10px;
  --space-3:  15px;
  --space-4:  20px;
  --space-5:  25px;
  --space-6:  30px;
  --space-8:  40px;
  --space-10: 50px;
  --space-12: 60px;
  --fs-base:  14px;
  --fs-md:    16px;
  --fs-lg:    20px;
}

/* ── Hotspots del visor 3D — ocultarlos cuando el operador no los quiere.
 * Cubre tanto los puntos del manual (.nodo-dot) como los rombos de la
 * página de producción (.prod-hotspot). */
body.hotspots-hidden .nodo-dot,
body.hotspots-hidden .prod-hotspot { display: none !important; }

/* ── Posición del contenedor de toasts ─────────────────────────────────────
 * Por defecto (sin clase o con .toast-pos-tr) el CSS base ya lo ancla a
 * top-right. Las otras posiciones invierten anclajes. Centrar usa
 * `left: 50%; transform: translateX(-50%);` — `right: 50%` no centra. */
body.toast-pos-tc .toast-container {
  top: var(--space-4);
  bottom: auto;
  right: auto;
  left: 50%;
  transform: translateX(-50%);
}
body.toast-pos-br .toast-container {
  top: auto;
  bottom: var(--space-4);
  right: var(--space-4);
  left: auto;
  transform: none;
}
body.toast-pos-bc .toast-container {
  top: auto;
  bottom: var(--space-4);
  right: auto;
  left: 50%;
  transform: translateX(-50%);
}

/* La animación slide-in original entra por la derecha. Para los toasts
 * anclados al centro, una entrada vertical queda más natural. */
body.toast-pos-tc .toast,
body.toast-pos-bc .toast {
  animation: toast-slide-in-vertical 0.25s ease;
}
@keyframes toast-slide-in-vertical {
  from { transform: translateY(-12px); opacity: 0; }
  to   { transform: translateY(0);     opacity: 1; }
}

/* ── Velocidad del parpadeo del banner de alarma ───────────────────────────
 * El banner principal usa @keyframes flashBanner con duración fija de 2s.
 * Aquí lo redefinimos según la preferencia. "off" elimina la animación. */
body.alarm-blink-off .alarm-banner.has-alarm    { animation: none !important; }
body.alarm-blink-slow .alarm-banner.has-alarm   { animation-duration: 4s !important; }
body.alarm-blink-normal .alarm-banner.has-alarm { animation-duration: 2s !important; }
body.alarm-blink-fast .alarm-banner.has-alarm   { animation-duration: 0.8s !important; }

