/* Shell persistente: rail izquierdo de módulos + franja mínima + lienzo del módulo. */
.ops, .ops *, .ops *::before, .ops *::after { box-sizing: border-box; }

.ops {
  margin: 0; background: var(--s0); color: var(--tx);
  font-family: var(--body); font-size: 14px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
.ops button, .ops input, .ops select { font: inherit; color: inherit; }
.ops :focus-visible { outline: 2px solid var(--c-menta); outline-offset: 2px; border-radius: var(--r-s); }
.ops .sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* Dos columnas: el índice del sistema a la izquierda, el módulo a la derecha. La única
   propiedad de layout que se anima es el ancho de la columna del rail. */
.ops .app {
  --rail: 236px;
  --t-rail: .42s;
  min-height: 100%; display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-width: 0;
  background:
    radial-gradient(60% 40% at 12% 0%, rgba(79, 140, 255, .09), transparent 60%),
    radial-gradient(50% 40% at 100% 100%, rgba(46, 230, 197, .05), transparent 60%),
    var(--s0);
  transition: grid-template-columns var(--t-rail) var(--e-salida);
}
.ops .app[data-plegado='si'] { --rail: 80px; }

/* ── RAIL: el índice vertical del sistema ──────────────────────────────────
   Pegajoso y de la altura EXACTA de la caja visible (--ops-caja la declara el overlay, que
   es quien sabe cuánta mesa deja por arriba y por abajo): así el índice no se va nunca
   aunque el módulo mida 3.000 px, y el pie del rail no queda por debajo del borde. */
.ops .rail {
  position: sticky; top: 0; align-self: start; z-index: 30;
  height: var(--ops-caja, 100dvh);
  background: var(--s1); border-right: 1px solid var(--linea);
  padding: 22px 16px; display: flex; flex-direction: column; gap: 18px;
  /* `auto` en vertical, no `hidden`: en una ventana baja el índice no puede COMERSE el pie
     («Restablecer demo»). En horizontal sigue recortando, que es lo que oculta las etiquetas. */
  overflow: hidden auto; min-width: 0; scrollbar-width: thin;
}
.ops .app[data-plegado='si'] .rail { overflow: visible; }   /* deja salir el tooltip */

.ops .rail-cab { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 0 4px; min-height: 44px; }
.ops .marca-glifo {
  flex: 0 0 auto;
  width: 32px; height: 32px; border-radius: var(--r-s); display: grid; place-items: center;
  background: linear-gradient(140deg, var(--c-azul), #2b48a8); color: #fff;
  box-shadow: 0 5px 14px -5px rgba(79, 140, 255, .75);
}
.ops .plegar {
  margin-left: auto; flex: 0 0 auto; width: 44px; height: 44px; border-radius: var(--r-s);
  display: grid; place-items: center; color: var(--tx-mu);
  background: transparent; border: 0; cursor: pointer;
  transition: background-color var(--t-rapido) var(--e-salida), color var(--t-rapido) var(--e-salida);
}
.ops .plegar:hover { background: var(--velo-fuerte); color: var(--tx); }
.ops .plegar svg { transition: transform var(--t-rail) var(--e-salida); }
.ops .app[data-plegado='si'] .plegar { margin-left: 0; }
.ops .app[data-plegado='si'] .plegar svg { transform: rotate(180deg); }
.ops .app[data-plegado='si'] .rail-cab { flex-direction: column; gap: 8px; align-items: center; }

.ops .rail-nav { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* El rail dice NOMBRE, nunca la señal: si la frase se lee dos veces en el mismo fotograma,
   no se lee ninguna. El estado viaja en el puerto, pegado al icono. */
.ops .rail-it {
  position: relative; display: flex; align-items: center; gap: 12px;
  width: 100%; min-width: 0; min-height: 44px; text-align: left;
  padding: 8px 12px; border: 0; border-radius: var(--r-s);
  background: transparent; color: var(--tx-mu); cursor: pointer;
  transition: background-color var(--t-rapido) var(--e-salida), color var(--t-rapido) var(--e-salida);
}
.ops button.rail-it:hover { background: var(--velo-fuerte); color: var(--tx); }
.ops .rail-ico, .ops .rail-it > svg { flex: 0 0 auto; }
.ops .rail-ico { position: relative; width: 19px; height: 19px; display: grid; place-items: center; }
/* nowrap en la etiqueta, no sólo en el nombre: al contraerse el rail la caja de texto se
   encoge a 0 y sin esto «Sistema activo» se partía letra a letra y estiraba la fila. */
.ops .rail-tx { min-width: 0; overflow: hidden; white-space: nowrap; }
.ops .rail-nom {
  display: block; font-family: var(--display); font-weight: 600; font-size: 15.5px;
  letter-spacing: -.025em; line-height: 1.2; color: var(--tx); white-space: nowrap;
}
/* módulo abierto = píldora de foco: el mismo estado que la baldosa del lanzador */
.ops .rail-it[aria-current] { background: var(--c-crema); color: var(--tx-inv); }
.ops .rail-it[aria-current] .rail-nom { color: var(--tx-inv); }
/* el anillo por defecto es del color de la píldora (medido 1,03:1 sobre ella): sobre lo
   oscuro manda el texto invertido. Sin el descendiente —el selector con espacio no llegaba
   nunca al propio botón—, y el mismo caso en el periodo activo, que usa la misma píldora. */
.ops .rail-it[aria-current]:focus-visible,
.ops .periodo button[aria-pressed='true']:focus-visible { outline-color: var(--tx-inv); }

/* el rail rima con el lienzo: lo vivo arriba, lo apagado abajo, tras una línea de pelo */
.ops .rail-sep { flex: 0 0 1px; height: 1px; background: var(--linea-fuerte); margin: auto 8px 8px; }
/* durmientes: se atenúan por COLOR propio, no por opacity (siguen siendo legibles). El tono
   NO basta para decir «esto no se pulsa» (1,3:1 de diferencia con un vivo, misma geometría):
   lo dice una marca de FORMA —la misma píldora «Sin activar» del zócalo— y la ausencia de
   respuesta al puntero. */
/* El contorno DISCONTINUO es la marca de forma: dice «esto todavía no existe» sin gastar
   ancho (la píldora «Sin activar» a la derecha del nombre cortaba «Contabilidad» en un rail
   de 236 px, medido) y —a diferencia de una etiqueta— sobrevive al rail contraído, que es
   justo donde un durmiente se confunde con un módulo vivo. */
.ops .rail-it.durmiente {
  cursor: default; color: var(--tx-dim);
  border: 1px dashed var(--linea-fuerte); background: transparent;
}
.ops .rail-it.durmiente + .rail-it.durmiente { margin-top: 5px; }
.ops .rail-it.durmiente .rail-nom { font-weight: 500; font-size: 14.5px; color: var(--tx-dim); }

/* PUERTOS de estado: viajan con el icono, así la atención sobrevive al plegado. Se distinguen
   por FORMA antes que por tono (triángulo = incidencia, punto = proceso vivo), WCAG 1.4.1. */
.ops .pto { position: absolute; right: -7px; bottom: -6px; pointer-events: none; }
.ops .pto-alerta { color: var(--c-coral); display: grid; place-items: center; }
.ops .pto-vivo {
  width: 10px; height: 10px; border-radius: 999px; background: var(--c-crema);
  display: grid; place-items: center;
}
.ops .pto-vivo::after { content: ''; width: 5px; height: 5px; border-radius: 999px; background: var(--c-menta); }

/* pie del rail: el pulso del sistema y la única acción que lo reinicia */
.ops .rail-pie { flex: 0 0 auto; display: flex; flex-direction: column; gap: 4px; padding-top: 10px; border-top: 1px solid var(--linea); }
.ops .estado-sistema { cursor: default; font-size: 12.5px; }
.ops .punto-vivo { flex: 0 0 auto; width: 9px; height: 9px; margin: 0 5px; border-radius: 50%; background: var(--c-ok); }
.ops .punto-vivo.plan { background: var(--c-ambar); }
.ops .rail-reset .rail-tx { font-size: 12.5px; }
.ops .app[data-plegado='si'] .rail-it { justify-content: center; padding: 12px 0; gap: 0; }
.ops .app[data-plegado='si'] .rail-pie { align-items: stretch; }

/* etiquetas: se van con opacity/transform, nunca con display (o el rail «salta»). El
   `flex-basis:0` es lo que de verdad las saca del reparto: encogiendo por flex-shrink la caja
   se quedaba en 16 px y descentraba los iconos del rail contraído (medido: icono en x=57 de
   una fila de 47 px, cuando le tocaba 71). */
.ops .rail-tx { transition: opacity var(--t-rapido) var(--e-salida), transform var(--t-medio) var(--e-salida); }
.ops .app[data-plegado='si'] .rail-tx { opacity: 0; transform: translateX(-8px); flex-basis: 0; }

/* tooltip del rail contraído: píldora de foco. Sin ella son nueve iconos anónimos. */
.ops .rail-it::after {
  content: ''; position: absolute; left: calc(100% + 12px); top: 50%; z-index: 5;
  padding: 0; border-radius: 999px; background: var(--c-crema); color: var(--tx-inv);
  box-shadow: var(--sombra-2);
  font-family: var(--body); font-size: 13px; line-height: 1.4; white-space: nowrap;
  opacity: 0; visibility: hidden; pointer-events: none; transform: translate3d(-6px, -50%, 0);
  transition: opacity var(--t-rapido) var(--e-salida), transform var(--t-rapido) var(--e-salida), visibility var(--t-rapido);
}
.ops .app[data-plegado='si'] .rail-it::after { content: attr(data-nom); padding: 6px 12px; }
.ops .app[data-plegado='si'] .rail-it:hover::after,
.ops .app[data-plegado='si'] .rail-it:focus-visible::after {
  opacity: 1; visibility: visible; transform: translate3d(0, -50%, 0); transition-delay: .15s;
}

/* ── FRANJA: quién es esto, sobre qué, y por dónde se sale. Nada más ─────── */
.ops .franja {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 12px;
  padding: 13px 22px; background: var(--s0); border-bottom: 1px solid var(--linea);
}
.ops .prod { font-family: var(--display); font-weight: 600; font-size: 16px; letter-spacing: -.025em; }
.ops .chip-demo {
  font-size: 12px; font-weight: 500; letter-spacing: .04em; text-transform: uppercase;
  color: var(--tx-mu); background: var(--velo-fuerte); border-radius: 999px; padding: 3px 9px;
}
.ops .vsep { width: 1px; height: 16px; background: var(--linea-fuerte); }
.ops .ctx { font-size: 13px; color: var(--tx-dim); }
.ops .cerrar {
  margin-left: auto; display: flex; align-items: center; gap: 8px; min-height: 44px;
  padding: 9px 16px; border-radius: 999px; border: 1px solid var(--linea-fuerte);
  background: transparent; font-size: 13px; font-weight: 500; color: var(--tx-mu); cursor: pointer;
  transition: background-color var(--t-rapido) var(--e-salida), color var(--t-rapido) var(--e-salida),
              border-color var(--t-rapido) var(--e-salida);
}
.ops .cerrar:hover { background: var(--c-crema); border-color: var(--c-crema); color: var(--tx-inv); }
.ops .abrir-rail { display: none; }

/* ── periodo: control DE VISTA, vive dentro de la tarjeta que lo consume ─── */
.ops .periodo { display: flex; gap: 2px; padding: 3px; border: 1px solid var(--linea);
  border-radius: var(--r-s); background: var(--velo); }
.ops .periodo button {
  font-size: 12.5px; font-family: var(--display); font-weight: 600; color: var(--tx-mu);
  background: transparent; border: 0; border-radius: var(--r-s); padding: 5px 11px; cursor: pointer;
  min-height: 34px;   /* diana: 29 px medidos era demasiado poco para un control real */
}
.ops .periodo button[aria-pressed='true'] { color: var(--tx-inv); background: var(--c-crema); }
.ops .tarjeta-cab .periodo { margin-left: auto; }

/* ── lienzo del módulo ────────────────────────────────────────────────── */
/* padding: 0 explícito — `body:not(.is-landing) main` del sitio (styles.css:215) mete 64px de
   hueco para su cabecera fija, y el lienzo es un <main>. Medido: la franja caía a y=92. */
.ops .lienzo { padding: 0; display: flex; flex-direction: column; min-width: 0; overflow-x: clip; }
.ops .lienzo > .vista { padding: 20px 22px 34px; }
.ops .vista { max-width: 1400px; margin: 0 auto; width: 100%; }
.ops .vista-cab { display: flex; align-items: flex-end; gap: 14px; margin-bottom: 16px; }
.ops .vista-cab h1 {
  font-family: var(--display); font-weight: 600; font-size: 22px; letter-spacing: -.03em;
  margin: 0; line-height: 1.1;
}
.ops .vista-cab p { margin: 3px 0 0; font-size: 13px; color: var(--tx-dim); }

/* animación de cambio de módulo (corta y con propósito) */
.ops .vista { animation: ops-entrar var(--t-medio) var(--e-salida) both; }
@keyframes ops-entrar { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* Por debajo de 1100 el rail arranca contraído (lo fija app.js con la misma media query):
   su contenido mínimo son ~236 px y ahí ya estrangula al módulo. No hace falta CSS aparte. */

/* ── móvil: el rail sale de flujo y pasa a ser un CAJÓN ───────────────────
   A 390 px un rail permanente se come el 20% del ancho; el módulo debe seguir mandando. */
.ops .rail-velo { display: none; }
@media (max-width: 760px) {
  .ops .app { grid-template-columns: minmax(0, 1fr); transition: none; }
  .ops .rail {
    /* height explícito: `align-self:start` (que en escritorio impide que el rail se estire y
       mate el sticky) también gana sobre `top:0;bottom:0` en un fixed. Medido: 650 de 844. */
    position: fixed; z-index: 60; top: 0; bottom: 0; left: 0; width: 272px; height: 100dvh;
    border-radius: 0 var(--r-xl) var(--r-xl) 0;
    background: var(--s0); border-right: 1px solid var(--linea-fuerte);
    box-shadow: var(--sombra-3); padding: 22px 16px; overflow-y: auto;
    /* con el cajón cerrado el rail sale TAMBIÉN del orden de foco: si no, son nueve
       tabulaciones a ciegas fuera de pantalla antes de llegar al botón visible */
    transform: translateX(-104%); visibility: hidden;
    /* la visibilidad NO se interpola: se apaga al final del cierre y se enciende en el acto al
       abrir. Interpolándola, el foco que se manda al cajón recién abierto cae en el vacío. */
    transition: transform var(--t-medio) var(--e-salida), visibility 0s linear var(--t-medio);
  }
  .ops .app[data-plegado='no'] .rail {
    transform: none; visibility: visible;
    transition: transform var(--t-medio) var(--e-salida), visibility 0s;
  }
  /* dentro del cajón el estado «contraído» no existe: se ve entero o no se ve */
  .ops .app[data-plegado='si'] .rail-tx { opacity: 1; transform: none; flex-basis: auto; }
  .ops .app[data-plegado='si'] .rail-it { justify-content: flex-start; padding: 8px 12px; gap: 12px; }
  .ops .app[data-plegado='si'] .rail-it::after { content: ''; padding: 0; }
  .ops .app[data-plegado='si'] .rail-cab { flex-direction: row; gap: 8px; align-items: center; }
  .ops .app[data-plegado='si'] .plegar { margin-left: auto; }
  .ops .app[data-plegado='si'] .plegar svg { transform: none; }
  .ops .rail-velo {
    display: block; position: fixed; inset: 0; z-index: 50; background: var(--velo-modal);
    opacity: 0; visibility: hidden;
    transition: opacity var(--t-medio) var(--e-salida), visibility 0s linear var(--t-medio);
  }
  .ops .app[data-plegado='no'] .rail-velo {
    opacity: 1; visibility: visible;
    transition: opacity var(--t-medio) var(--e-salida), visibility 0s;
  }

  .ops .franja { gap: 9px; padding: 10px 14px; flex-wrap: wrap; row-gap: 6px; }
  .ops .franja::after { content: ''; flex: 1 0 100%; order: 4; height: 0; }
  .ops .abrir-rail {
    display: grid; place-items: center; width: 44px; height: 44px; margin-left: -10px;
    border: 0; border-radius: var(--r-s); background: transparent; color: var(--tx); cursor: pointer;
  }
  .ops .vsep { display: none; }
  .ops .cerrar { order: 3; margin-left: auto; padding: 9px 14px; }
  .ops .chip-demo { order: 5; }
  .ops .ctx { order: 6; font-size: 12px; }
  .ops .lienzo > .vista { padding: 14px 14px 40px; }
  .ops .vista-cab h1 { font-size: 19px; }
  /* el periodo baja a su propia línea, alineado con el título: estirar los botones a partes
     iguales partía «30 días» en dos renglones y engordaba la píldora (medido a 390) */
  .ops .tarjeta-cab:has(.periodo) { flex-wrap: wrap; row-gap: 8px; }
  .ops .tarjeta-cab .periodo { margin-left: 0; flex: 0 0 100%; }
  /* en táctil la diana manda sobre la compacidad. Sin `flex:1`: repartir el ancho a partes
     iguales partía «30 días» en dos renglones (medido a 390 en la ronda anterior). */
  .ops .periodo button { min-height: 40px; }
}

/* el gate del proyecto ya apaga --t-rapido/--t-medio; el ancho del rail va aparte */
@media (prefers-reduced-motion: reduce) { .ops .app { --t-rail: .001s; } }

/* ── el producto a pantalla completa, encima del capítulo ─────────────────
   Cuelga de <body>: la celda del capítulo tiene perspective y el panel
   preserve-3d, y ahí dentro `fixed` deja de ser fixed.
   La tarjeta-app SANGRA por la derecha y por abajo (referencia A medida): el encuadre dice
   «esto es una aplicación real, y no cabe entera» sin una sola palabra.
   El borde derecho se queda en `right:0` (1425 de 1440 aquí: `html` reserva el hueco de la
   barra de scroll con `scrollbar-gutter:stable`, styles.css:79). Estirar la capa a `100vw`
   metía los últimos 15 px de contenido DEBAJO de ese hueco —medido a 390, el desplegable de
   almacén perdía su flecha—, y ganaba cero: el hueco se pinta por encima de todo. */
/* El encuadre es el del boceto A, MEDIDO: `top:56 left:64 right:-64 bottom:208` sobre 1440×900.
   La tarjeta sangra por la DERECHA («no cabe entera») pero se APOYA en la mesa por abajo: sin
   esos ~200 px de escritorio la sombra de contacto cae fuera del encuadre (medido: 0 px de ella
   visible), la esquina inferior izquierda queda en ángulo recto y el objeto deja de flotar —
   pasa a ser una pantalla completa con una pestaña verde arriba.
   El hueco que `scrollbar-gutter:stable` reserva en <html> (styles.css:79) se pintaba con el
   negro del capítulo: una costura de 15 px a lo alto de todo. Se pinta de mesa mientras la
   aplicación está abierta; no se toca el gutter, que es lo que protege el layout del capítulo. */
html.ops-abierta { background: #a6b9b5; }   /* literal: --mesa sólo existe dentro de .ops.ops-capa */

.ops-capa {
  --ops-pad-t: clamp(0px, 3vh, 34px);
  --ops-pad-b: clamp(0px, 20vh, 180px);
  --ops-caja: calc(100dvh - var(--ops-pad-t) - var(--ops-pad-b));
  position: fixed; inset: 0; z-index: 120; display: none;
  background: var(--velo-capa);
  padding: var(--ops-pad-t) 0 var(--ops-pad-b) clamp(0px, 3vw, 40px);
}
/* la capa y el lienzo son destinos de foco (tabindex="-1"), no controles: no llevan anillo.
   Sin esto, el anillo del contenedor se pintaba como una raya de 2 px a lo alto de la app. */
.ops-capa:focus, .ops-capa:focus-visible, .ops .lienzo:focus, .ops .lienzo:focus-visible { outline: none; }
.ops-capa.is-open { display: block; animation: ops-capaEntra var(--t-medio) var(--e-salida) both; }
@keyframes ops-capaEntra { from { opacity: 0; } to { opacity: 1; } }

/* sin borde: el boceto separa la tarjeta de la mesa con RADIO + sombra larga, no con un filo.
   Además el borde metía 2 px de scroll fantasma en una home que no tiene nada que desplazar. */
.ops-capa .ops-scroll {
  height: 100%; overflow-y: auto; overscroll-behavior: contain;   /* el scroll NO se escapa al capítulo */
  border-radius: var(--r-xxl) 0 0 var(--r-xxl); border: 0;
  box-shadow: var(--sombra-3); background: var(--s0);
}
.ops-capa .app { min-height: 100%; }

@media (max-width: 760px) {
  /* el boceto tampoco va a sangre en móvil: `margin:16px 12px` y radio de baldosa */
  .ops-capa { --ops-pad-t: 14px; --ops-pad-b: 14px; padding: 14px 12px; }
  .ops-capa .ops-scroll { border-radius: var(--r-xl); }
}

/* ══ ajustes del shell que sólo tienen sentido en CLARO ═══════════════════
   Todo lo demás se resuelve solo por token. Aquí quedan los efectos que en
   oscuro se construían con LUZ (auroras, glows, degradado del glifo): sobre
   una superficie clara la luz aditiva no se ve, así que se sustituye por
   contraste sólido. */
.ops-capa .app { background: var(--s0); }
/* El glifo NO puede ser el mismo objeto que «módulo abierto». Con los dos en tinta, el rail
   contraído mostraba dos cuadrados negros gemelos a 24 px y se leían como un control duplicado.
   El contraste máximo es un recurso escaso: se reserva al ESTADO (dónde estás), no a la
   identidad, que está siempre y no se pulsa. El glifo baja al mismo chip claro que el icono de
   una baldosa (`--velo-fuerte` + tinta), que es su rima natural. */
.ops-capa .marca-glifo { background: var(--velo-fuerte); color: var(--tx); box-shadow: none; }
.ops-capa .estado-sistema { background: var(--velo); }
/* la menta conserva su valor de marca porque SIEMPRE va sobre la píldora oscura: es la única
   excepción del contrato, y aquí el fondo del puerto es justo esa píldora (11,3:1). */
.ops-capa .pto-vivo::after { background: #2ee6c5; }
/* el foco por defecto es menta, que sobre claro da 1,6:1: sobre la carta manda la tinta */
.ops-capa :focus-visible { outline-color: var(--tx); }
