/* ═══════════════════════════════════════════════════════════════════════════
   OneBox — sitio de producto · cromo propio
   ───────────────────────────────────────────────────────────────────────────
   REGLA: este archivo NO maqueta. Solo pone lo que el catálogo NO tiene:
   la barra de navegacion del sitio, el pie, y tres bloques de marketing
   (comparativa, linea de tiempo, precio). TODO lo demas — botones, tarjetas,
   campos, insignias, cifras, listas, alertas — sale de omnis-module.css.

   Si necesitas maquetar algo que ya existe en el catálogo, esto es un bug:
   lo que falta es una clase, no un override aqui.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Contraste: correccion de la rampa en tema claro ─────────────────────
   MEDIDO, no supuesto. Sobre el fondo real #f5f5f4:
     #a8a29e (--text-mute claro)  2.31:1  FALLA  (necesita 4.5)
     #78716c                     4.40:1  FALLA por poco
     #57534e                     6.99:1  pasa
     #44403c                     9.42:1  pasa
     #1c1917 (--text)            16.03:1  pasa
   El --text-mute de claro medido es un gris de la rampa oscura (#a8a29e es un
   tono pensado sobre fondo negro): sobre papel no puede ser texto. Se
   reescalona la rampa completa para que los TRES niveles sigan siendo
   distintos, monotonos y todos AA:
     text #1c1917 16.03  >  soft #44403c 9.42  >  mute #57534e 6.99
   Solo en claro: el tema oscuro ya usa #d6d3d1/#78716c sobre fondo oscuro y
   alli si cumple, asi que se deja como el catalogo lo define.

   BUG REAL CORREGIDO (2026-10-01): esto vivia en un `:root {}` SIN guardia,
   en un archivo SIN @layer. Lo no-encapado-en-capa siempre gana sobre lo
   encapado (omnis-tokens.css SI usa @layer), asi que esta correccion "solo
   claro" pisaba el oscuro de verdad en TODO el sitio para cualquier
   visitante con el sistema en oscuro (sin tocar el interruptor): badges,
   notas, pies, captions — cualquier texto mute/soft salia casi invisible
   sobre fondo oscuro. Es la causa raiz de "todo se ve mal en oscuro".
   Guardia correcta para las 4 combinaciones sistema×eleccion: la correccion
   solo se emite cuando el resultado final es claro. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --text-soft: #44403c;
    --text-mute: #57534e;
  }
}
:root[data-theme="light"] {
  --text-soft: #44403c;
  --text-mute: #57534e;
}

/* El color de marca sirve para RELLENOS, no para texto: #e8400f sobre #f5f5f4
   mide 3.72:1 y solo alcanza para texto grande. El tono presionado #b81f00 da
   5.95:1. Los usos concretos van al FINAL de este archivo, porque si estan
   aqui las reglas posteriores del propio ob.css los pisan —misma
   especificidad, gana la ultima— y el override no servia de nada. */

/* ── Rejilla del sitio ─────────────────────────────────────────────────── */
.ob { max-width: 74rem; margin-inline: auto; padding-inline: var(--space-lg); }
.ob__seccion { padding-block: calc(var(--space-2xl) * 1.4); }
.ob__h2 { margin-block: 0 var(--space-md); }
.ob__nota { color: var(--text-mute); max-width: 48rem; margin-block: 0 var(--space-lg); }
/* Parrafo de entrada de una seccion, FUERA del hero. No es lo mismo que
   .ob__nota (texto auxiliar pequeno) ni que .ob-hero__lead (que vive sobre
   la banda de marca y por eso es casi blanco).
   BUG REAL CORREGIDO (2026-10-01): index.php:169 usaba .ob-hero__lead
   dentro de una seccion normal. Medido: color-mix(--brand-contrast 88%,
   transparent) sobre --bg #f5f5f4 daba 1.08:1 — texto invisible. La causa raiz
   no era el color: era una clase de hero usada como clase de texto generico.
   Un componente con color de fondo no puede vivir suelto. */
.ob__lead { font-size: clamp(1.05rem, .95rem + .5vw, 1.3rem); color: var(--text); max-width: 46rem; margin-block: 0 var(--space-lg); }
.ob__lead strong { font-weight: 600; }
.ob__centrado { text-align: center; }
.ob__centrado .ob__nota { margin-inline: auto; }

/* ── Barra del sitio ───────────────────────────────────────────────────── */
.ob-nav {
  position: sticky; top: 0; z-index: 40;
  background: var(--bg); border-bottom: 1px solid var(--border);
  backdrop-filter: saturate(1.4) blur(10px);
}
.ob-nav__in {
  display: flex; align-items: center; gap: var(--space-lg);
  min-height: 60px; max-width: 74rem; margin-inline: auto; padding-inline: var(--space-lg);
  /* Ancla del mega-menu. Ver .ob-mega: el panel se centra aqui, sobre la
     barra completa, y no sobre el enlace "Modulos". */
  position: relative;
}
.ob-nav__marca { display: flex; align-items: center; gap: var(--space-sm); font-weight: 700; text-decoration: none; color: inherit; }
.ob-nav__punto { width: 10px; height: 10px; border-radius: 3px; background: var(--brand); }
.ob-nav__links { display: flex; gap: var(--space-xs); margin-left: auto; flex-wrap: wrap; }
.ob-nav__link {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; padding-inline: var(--space-sm);
  border-radius: var(--radius-sm); text-decoration: none; color: var(--text-mute); font-size: var(--fluid-sm);
}
.ob-nav__link:hover, .ob-nav__link:focus-visible { color: var(--text); background: var(--bg-alt, rgba(127,127,127,.12)); }
.ob-nav__link[aria-current="page"] { color: var(--text); font-weight: 600; background: rgba(127,127,127,.14); }
/* El boton de la barra no se estira con la banda desplazable.
   SIN margin-left: la banda ya pone gap: var(--space-xs) entre cada hijo, y
   este margen le sumaba un segundo espaciado igual. MEDIDO 2026-10-01: con ese
   margen de sobra la banda necesitaba 7px mas de los que caben a 1280 y 6 a
   1024, y aparecia scroll horizontal dentro de la barra. No hacia falta
   apretar tipografia ni acortar el CTA: sobraba espacio entre el CTA y el
   boton de tema. */
.ob-nav__cta { white-space: nowrap; }

/* ── Mega-menú de "Módulos" ────────────────────────────────────────────────
   Homólogo del desplegable de pulpos.com. Abre con :hover/:focus-within y
   con la clase .is-abierto que pone el JS al hacer clic. Ese JS es UNA
   mejora encima de la base, no la base: sin JS el panel sigue abriéndose con
   hover, y "Módulos" sigue siendo un <a href> comun que lleva a producto.php
   (ver inc/foot.php). Solo en escritorio: en movil no hay hover, el panel es
   display:none y el menu ya resuelto (banda horizontal, ver abajo) no se toca. */
/* posicion: static a proposito. Antes este bloque era el contenedor del
   mega-menu, y el panel se centraba sobre el enlace "Modulos". Ese enlace
   cae a la derecha del nav, asi que a 768px un panel de 640px se salia
   118px del viewport y hacia aparecer scroll horizontal en TODA la pagina
   (medido: 768px -> 118px de desborde, 800px -> 96px). Sin posicion
   relativa, el panel se ancla a .ob-nav__in, que si ocupa el ancho
   disponible, y entra completo desde 736px. */
.ob-nav__item--mega { position: static; display: flex; align-items: center; }
.ob-mega {
  position: absolute; top: 100%; left: 50%; transform: translate(-50%, 8px);
  width: min(46rem, 94vw);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 30px 60px -24px rgba(0,0,0,.35);
  padding: var(--space-lg);
  opacity: 0; visibility: hidden; pointer-events: none;
  /* GRACIA AL SALIR (2026-10-01). El retardo va en el estado CERRADO y no en
     el abierto, y por eso el cierre espera 400ms pero la apertura es
     inmediata: el estado que se aplica al abrir es el de la regla de abajo,
     sin retardo. Sin esto, mover el mouse en diagonal desde el enlace hacia
     el panel (o solo apartarlo para leer) lo hacia desaparecer antes de
     llegar. visibility necesita su propio retardo para no cortar el hover
     mientras se desvanece. */
  transition: opacity .15s ease .4s, transform .15s ease .4s, visibility 0s linear .4s;
  z-index: 50;
}
/* Tres aperturas: hover, foco y is-abierto (clic). La tercera es la que pide
   el requisito de que el panel sobreviva a la perdida del mouse. */
.ob-nav__item--mega:hover .ob-mega,
.ob-nav__item--mega:focus-within .ob-mega,
.ob-nav__item--mega.is-abierto .ob-mega {
  opacity: 1; visibility: visible; pointer-events: auto; transform: translate(-50%, 4px);
  transition: opacity .15s ease, transform .15s ease, visibility 0s linear;
}
/* CIERRE EXPLICITO — y aqui esta el bug que motivó todo esto.
   Al hacer clic, el navegador deja el FOCO en el enlace. :focus-within
   entonces sigue matcheando y el panel se queda abierto SOLO por el foco:
   un segundo clic para cerrarlo no cerraba nada, y Escape "cerraba" y lo
   reabria en el acto al devolverle el foco al enlace.

   .no-foco declara el estado CERRADO con una clase mas (:focus-within son
   tres, esta son cuatro), asi que gana por especificidad. No espera la
   gracia de los 400ms: si el visitante ya decidio cerrarlo, 400ms de panel
   pegado son un retraso y no una ayuda. Y durante la gracia el panel sigue
   con pointer-events: auto, asi que un rapido clic se pegaria a un panel que
   ya se estaba cerrando.

   No es estado muerto: el JS la quita al salir el foco del enlace, al entrar
   el mouse al item, y al entrar el foco al propio panel — que es justo cuando
   el teclado vuelve a necesitar que :focus-within lo mantenga abierto. */
.ob-nav__item--mega.no-foco .ob-mega,
.ob-nav__item--mega.no-foco:focus-within .ob-mega,
.ob-nav__item--mega.no-foco.is-abierto .ob-mega,
.ob-nav__item--mega.no-foco:hover .ob-mega {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .1s ease, transform .1s ease, visibility 0s linear .1s;
}
.ob-mega__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr)); gap: var(--space-md); }
.ob-mega__grupo { display: flex; flex-direction: column; gap: var(--space-2xs, .25rem); }
.ob-mega__grupo-titulo {
  margin: 0 0 var(--space-2xs, .25rem); font-size: var(--fluid-2xs, .7rem);
  text-transform: uppercase; letter-spacing: .08em; color: var(--brand-press); font-weight: 700;
}
.ob-mega__item {
  display: flex; flex-direction: column; gap: 2px; padding: var(--space-sm);
  border-radius: var(--radius-sm); text-decoration: none; color: inherit;
}
.ob-mega__item:hover, .ob-mega__item:focus-visible { background: var(--surface-2); }
.ob-mega__titulo { font-weight: 600; color: var(--text); font-size: var(--fluid-sm); }
.ob-mega__desc { color: var(--text-mute); font-size: var(--fluid-xs); line-height: 1.35; }
.ob-mega__todo {
  display: block; margin-top: var(--space-sm); padding-top: var(--space-sm);
  border-top: 1px solid var(--border); text-align: center;
  color: var(--brand-press); font-weight: 600; font-size: var(--fluid-sm); text-decoration: none;
}
.ob-mega__todo:hover { text-decoration: underline; }
@media (prefers-reduced-motion: reduce) { .ob-mega { transition: none; } }

/* En movil los enlaces se vuelven una banda desplazable: nunca se esconden,
   porque esconderlos sin JS es exactamente el fallo que el kit ya prohibio. */
@media (max-width: 46rem) {
  .ob-nav__in { flex-wrap: wrap; min-height: 0; padding-block: var(--space-xs); }
  .ob-nav__links { width: 100%; margin-left: 0; overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .ob-nav__links::-webkit-scrollbar { display: none; }
  /* BUG REAL (2026-10-01): .ob-nav__link trae min-width:44px para el area
     tactil, pero sin flex-shrink:0 eso NO protege el ancho — es solo un
     piso que flexbox SI puede usar para encoger. Dentro de un contenedor
     nowrap, flexbox encogia los 7 enlaces al piso de 44px antes de
     desplazar, y con white-space:nowrap el texto no cabia en esa caja:
     "Diferenciadores" y "Punto de venta" se desbordaban sobre el vecino,
     ilegibles. flex:none deja que cada enlace mida lo que su texto pide,
     y entonces si el total no cabe, desplaza — que es lo que este bloque
     ya queria lograr. */
  .ob-nav__link { white-space: nowrap; flex: none; }
  .ob-nav__item--mega { flex: none; }
  /* Sin hover en tactil: el panel se queda invisible siempre y "Módulos" se
     comporta como cualquier otro enlace de la banda. */
  .ob-mega { display: none; }
}

/* ── Hero ──────────────────────────────────────────────────────────────── */
/* ── HERO · la portada tiene que tener color y jerarquia ────────────────────
   El problema medido: el h1 salia a 28px (el h1 del catalogo, no de portada)
   sobre un fondo casi blanco, y toda la pagina era gris sobre gris. Aqui el
   hero sube de escala, toma el naranja de la marca y se le da fondo para que
   la entrada tenga peso. El resto de la pagina sigue igual: el color se
   concentra donde el visitante mira primero, no se reparte por todo. */
.ob-hero {
  padding-block: calc(var(--space-2xl) * 1.6) calc(var(--space-2xl) * 1.2);
  border-radius: var(--radius-lg);
  background: var(--brand-press);
  border: 0;
  padding-inline: clamp(var(--space-lg), 4vw, var(--space-2xl));
  box-shadow: 0 20px 50px -32px var(--brand-press);
  color: var(--brand-contrast);
}
/* El logo de Obi es un PNG OSCURO, y el heroe es una BANDA de marca
   (--brand-press #b81f00) que no cambia con el tema. Medido sobre el archivo
   real: 0% de su tinta por encima de L=200 (mediana 51), y su rojo propio
   (~#c02000) sobre esa banda da 1.07:1 — invisible; el negro da 3.23:1, poco
   mas que el minimo de 3:1 para un componente grafico. Por eso va BLANCO, que
   es lo que ya hace el heroe con su texto (6.49:1) y con el mismo filtro que
   el logo de la barra en oscuro. Sin regla de tema oscuro: la banda es la
   misma en los dos. El original mide 6826x2672; aqui va la version de 900. */
.ob-hero__logo {
  display: block;
  height: 96px; width: auto;
  max-width: 100%;
  object-fit: contain;
  filter: brightness(0) invert(1);
  margin-bottom: var(--space-lg);
}
.ob-hero h1 {
  /* Portada, no seccion: por encima del --fluid-2xl del catalogo. */
  font-size: clamp(2rem, 1.1rem + 3.6vw, 3.5rem);
  line-height: 1.08;
  max-width: 19ch;
  margin: 0 0 var(--space-md);
  text-wrap: balance;
  color: var(--brand-contrast);
}
/* El enfado al 88% (color-mix) bajaba esto a 3.06:1 medido sobre
   --brand-press: falla AA para texto normal. Al 100% son 6.49:1. */
.ob-hero__lead { font-size: clamp(1.05rem, 0.95rem + 0.5vw, 1.3rem); color: var(--brand-contrast); max-width: 46rem; }
/* Micro-promesas de refuerzo bajo los botones del hero de portada: tres datos
   verificados (30 dias gratis / sin comision / datos propios) en una linea
   discreta. A 100% de color para no repetir el fallo de contraste del lead. */
.ob-hero__micro {
  display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; align-items: center;
  margin: var(--space-md) 0 0; font-size: var(--fluid-sm);
  color: var(--brand-contrast);
}
.ob-hero__micro span { display: inline-flex; align-items: center; }
.ob-hero__micro span::before { content: "\2713"; margin-right: .35rem; font-weight: 700; }
.ob-hero__marcas { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.ob-hero__eslogan {
  margin: var(--space-md) 0 0; font-size: var(--fluid-lg); font-weight: 700;
  color: var(--brand-contrast); letter-spacing: -.01em;
}
.ob-hero__eslogan::before { content: "\201C"; }
.ob-hero__eslogan::after  { content: "\201D"; }
/* Las acciones se envuelven: en movil tres botones en linea se salen de la
   pantalla, y el sitio no debe depender de que quepan. */
.ob-hero__acciones { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-top: var(--space-lg); }

/* ── Hero con captura real a un lado ──────────────────────────────────────
   El texto solo no demuestra nada: la portada necesita el producto a la
   vista desde el primer fotograma, no diez pantallas despues. En movil la
   imagen cae DESPUES del texto (el gancho se lee primero); en escritorio
   van lado a lado. El marco blanco con sombra la separa del rojo de marca
   para que la captura (interfaz clara) no se funda con el fondo. */
.ob-hero__grid {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: clamp(var(--space-lg), 4vw, var(--space-2xl));
  align-items: center;
}
.ob-hero__visual {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #fff;
  box-shadow: 0 30px 60px -24px rgba(0,0,0,.5), 0 0 0 1px color-mix(in srgb, var(--brand-contrast) 14%, transparent);
}
.ob-hero__visual img { display: block; width: 100%; height: auto; aspect-ratio: 1200/750; object-fit: cover; object-position: top; }
@media (max-width: 860px) {
  .ob-hero__grid { grid-template-columns: 1fr; }
  .ob-hero__visual { order: 2; }
  .ob-hero__texto { order: 1; }
}

/* ── Hero de portada, y SOLO de portada ───────────────────────────────────
   Gerry (2026-10-01): "el HERO de inicio debe ser diferente" — y tenia
   razon: despues de homologar producto/mercado/soluciones/pos/obi al mismo
   .ob-hero (franja --brand-press plana + la misma pildora de kicker), la
   portada dejo de distinguirse de una pagina interior. Las paginas
   interiores SIGUEN usando .ob-hero tal cual (es correcto que compartan
   chrome entre ellas); esta clase se añade, no reemplaza, y vive SOLO en
   index.php. Nada de esto se toca si se usa en otra pagina sin querer: sin
   la clase, no pasa nada. */
.ob-hero--portada {
  position: relative;
  background:
    radial-gradient(120% 160% at 88% -15%, color-mix(in srgb, var(--brand-hover) 60%, transparent) 0%, transparent 48%),
    linear-gradient(145deg, var(--ink) 0%, var(--brand-press) 58%, var(--brand) 100%);
  box-shadow: 0 46px 100px -40px rgba(0,0,0,.6);
  overflow: hidden;
}
/* Textura sutil: puntos, no una imagen. Se ve a 4% de opacidad — aporta
   profundidad sin competir con el texto ni pesar un KB de descarga. */
.ob-hero--portada::before {
  content: "";
  position: absolute; inset: 0;
  background-image: radial-gradient(color-mix(in srgb, var(--brand-contrast) 35%, transparent) 1px, transparent 1px);
  background-size: 28px 28px;
  opacity: .08;
  pointer-events: none;
}
.ob-hero--portada .ob-hero__grid { position: relative; }
.ob-hero--portada h1 {
  font-size: clamp(2.25rem, 1rem + 4.8vw, 4.25rem);
}
.ob-hero--portada .ob-hero__visual {
  box-shadow: 0 55px 110px -36px rgba(0,0,0,.65), 0 0 0 1px color-mix(in srgb, var(--brand-contrast) 18%, transparent);
  transition: transform .35s ease, box-shadow .35s ease;
}
/* Antes la captura entraba girada (-1.1deg) y se enderezaba al pasar el raton:
   Gerry lo leyo como "imagenes chuecas" (2026-10-08) y tenia razon — una
   pantalla de producto inclinada se lee como error de maquetacion, no como
   diseno. Ahora entra recta y al pasar el raton se eleva, sin rotar. */
.ob-hero--portada .ob-hero__visual:hover {
  transform: translateY(-4px);
  box-shadow: 0 70px 120px -34px rgba(0,0,0,.7), 0 0 0 1px color-mix(in srgb, var(--brand-contrast) 26%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .ob-hero--portada .ob-hero__visual,
  .ob-hero--portada .ob-hero__visual:hover { transition: none; transform: none; }
}

/* ── AGENTE DISEÑO WEB (index/soluciones/producto) 2026-09-30 ────────────
   Palabra clave con degradado en el H1 del hero — patron de Papiro
   (ps/index.html: <span class="gradient-text-brand">) via background-clip:
   text. NO se copia el morado de Papiro: el degradado usa los tokens de
   marca que OneBox ya tiene (--brand-contrast/--brand-hover).

   Por que no hace falta guardia de tema: el H1 vive SIEMPRE dentro de
   .ob-hero, que es fondo solido --brand-press — ese token y los de marca
   NO se redefinen en modo oscuro (omnis-tokens.css), asi que el rojo de
   fondo es igual en los dos temas y el degradado no necesita el patron
   @media(prefers-color-scheme)+[data-theme] del resto del archivo. Se evito
   a proposito poner esta palabra-con-degradado como utilidad suelta para
   texto sobre el fondo CLARO/OSCURO de la pagina: ahi --brand-hover mide
   2.5:1 sobre el fondo claro (#f5f5f4) y rompe el mismo bug de contraste
   que el resto de este archivo ya corrigio. */
.ob-hero h1 .ob-destacado {
  background: linear-gradient(135deg,
    var(--brand-contrast) 10%,
    color-mix(in srgb, var(--brand-contrast) 45%, var(--brand-hover) 55%) 55%,
    var(--brand-contrast) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
@supports not (background-clip: text) {
  .ob-hero h1 .ob-destacado { color: var(--brand-contrast); }
}

/* ── Comparativa (tabla de mercado) ────────────────────────────────────── */
/* La comparativa tiene siete columnas de contenido real y NO puede reducirse
   a la pagina entera: en escritorio las cabeceras (nombre + precio) la
   empujan mas alla de su tarjeta. El .table-wrap es el encargado de que eso
   no se vea roto: la tabla se desplaza dentro de su tarjeta con su propio
   tirador, en vez de recortarse contra el borde. Esta regla es la que faltaba:
   sin ella, table-wrap era un div sin propiedades y la tabla sangraba por el
   lado derecho de la tarjeta (medido: 1,624px dentro de 1,144px). */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }
.ob-cmp { width: 100%; border-collapse: collapse; font-size: var(--fluid-sm); }
.ob-cmp caption { text-align: left; color: var(--text-mute); padding-bottom: var(--space-sm); }
.ob-cmp th, .ob-cmp td { padding: var(--space-sm); border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.ob-cmp thead th { font-weight: 600; }
.ob-cmp tbody th { font-weight: 500; }
.ob-cmp td[data-n] { text-align: center; white-space: nowrap; }
.ob-cmp tbody tr:hover { background: rgba(127,127,127,.07); }
/* El tag bajo el glifo describe la marca con palabra, no solo con simbolo:
   a simple vista se lee el significado ("Publica esto") y solo quien quiere
   el detalle pasa el cursor. Vive encima de titulo en el mismo <td>. */
.ob-cmp__tag { display: block; margin-top: 2px; font-size: .78rem; color: var(--text-mute); max-width: 12rem; }
.ob-cmp__nos .ob-cmp__tag { font-weight: 600; color: var(--text); }
/* La columna de OneBox se separa del resto: la comparativa tiene que dejar
   ver de un vistazo quien es quien, sin leer. */
.ob-cmp .ob-cmp__nos { background: rgba(127,127,127,.05); box-shadow: inset 2px 0 0 var(--brand); font-weight: 600; }
.ob-cmp__si { color: var(--ok); font-weight: 700; }
.ob-cmp__no { color: var(--danger); }
.ob-cmp__parcial { color: var(--text-mute); }
/* Fila divisoria de categoria (planes.php): no es un competidor mas, es un
   separador — se saca por completo de la grid-ificacion movil de abajo para
   que no intente convertirse en una fila de "nombre : valor". */
.ob-cmp tr.ob-cmp__area th { background: var(--surface-2); font-weight: 700; padding-top: var(--space-md); border-bottom: 1px solid var(--border); }
@media (max-width: 52rem) {
  .ob-cmp tbody tr.ob-cmp__area { display: block; }
}
@media (min-width: 52.001rem) {
  /* En escritorio el QUIEN se ve en la columna ancha del tag; el ::before
     no pinta, porque el data-nombre seria ruido repetido en siete celdas. */
  .ob-cmp td[data-nombre]::before { display: none; }
}
@media (max-width: 52rem) {
  .ob-cmp thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  /* Movil: cada eje es un bloque y cada competidor UNA linea —nombre a la
     izquierda, glifo a la derecha— en vez de las siete celdas apiladas que
     rompian el orden de lectura. El nombre sale del atributo que la celda
     lleva desde el PHP (data-nombre); el tag queda fuera porque el nombre
     ya lo dice. */
  .ob-cmp tbody tr { display: grid; gap: 0; }
  .ob-cmp tbody th {
    display: block; border-bottom: 0; white-space: normal;
    font-weight: 600; color: var(--text);
    padding: var(--space-md) var(--space-sm) var(--space-xs);
  }
  .ob-cmp tbody td {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm);
    padding-block: 4px; border-bottom: 0; text-align: right;
  }
  .ob-cmp tbody td::before { content: attr(data-nombre); color: var(--text); font-weight: 500; text-align: left; }
  .ob-cmp tbody td:last-child { border-bottom: 1px solid var(--border); padding-bottom: var(--space-sm); }
  .ob-cmp__tag { display: none; }
  .ob-cmp .ob-cmp__nos { box-shadow: inset 3px 0 0 var(--brand); }
  .ob-cmp td[data-n][data-nombre*="OneBox"] { box-shadow: inset 3px 0 0 var(--brand); background: rgba(127,127,127,.04); }
}

/* ── Linea de tiempo ───────────────────────────────────────────────────── */
.ob-tiempo { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-md); }
.ob-tiempo li { display: grid; grid-template-columns: 8.5rem 1fr; gap: var(--space-md); align-items: baseline; }
/* grid-row explicita en los TRES hijos. MEDIDO (Chrome headless, 1280px):
   el texto salia en 136px — el ancho de la primera columna. Sin esto el
   autoplacement pone ::before en (1,1), el <time> en (1,2) y el <span> en
   (2,1), o sea el texto hereda la columna estrecha. Afecta a obi.php y
   novedades.php, las dos unicas paginas con linea de tiempo. */
.ob-tiempo li::before { content: ""; grid-column: 1; grid-row: 1; height: 1px; background: var(--border); align-self: center; }
.ob-tiempo time { grid-column: 1; grid-row: 1; color: var(--text-mute); font-variant-numeric: tabular-nums; font-size: var(--fluid-sm); }
.ob-tiempo li > span { grid-column: 2; grid-row: 1; }
@media (max-width: 40rem) { .ob-tiempo li { grid-template-columns: 1fr; gap: var(--space-xs); } }

/* ── Módulos: ficha compacta ───────────────────────────────────────────── */
.ob-mod { break-inside: avoid; }
.ob-mod__lista { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-xs); }
.ob-mod__lista li { display: flex; gap: var(--space-xs); align-items: baseline; font-size: var(--fluid-sm); }
.ob-mod__cod { color: var(--text-mute); }

/* ── Identidad Phoenix ─────────────────────────────────────────────────────
   OneBox 7.2 Phoenix. El naranja de marca YA EXISTIA en omnis-tokens
   (--brand: #e8400f, con sus hover/press/wash medidos para contraste) y el
   sitio no lo usaba: se veia gris. No se invento un color, se conecto el que
   el sistema ya tenia. Por eso aqui no hay ningun hex propio. */
/* Los botones de las paginas publicas no heredan el tintado de la app
   (aquel vive bajo .omnis): en el sitio se veian NEGROS, que es el color por
   defecto de .btn, no la marca. Se les da el tono fijo aqui, con la misma
   logica de contraste que el resto: blanco sobre --brand es 4.06:1 y no pasa
   AA para texto de boton (14px), asi que el fondo es --brand-press (6.49:1)
   y --brand queda para el hover grande. El hover oscurece, no aclara. */
.ob .btn { font-weight: 600; }
/* El tintado de la app vive bajo .omnis (.omnis .btn--primary da 0,3,0) y el
   body del sitio es .omnis: sin igualar esa especificidad, el negro por
   defecto se impone aunque ob.css cargue despues. Doble clase = 0,3,0.
   El CTA de la nav va al mismo selector: vive fuera del envoltorio .ob y no
   le llega la regla de dos clases, pero SI compite contra .omnis. */
.ob .btn.btn--primary, .ob-nav__cta.btn--primary { background: var(--brand-press); border-color: var(--brand-press); color: var(--brand-contrast); }
.ob .btn.btn--primary:hover, .ob-nav__cta.btn--primary:hover { background: color-mix(in srgb, var(--brand-press) 84%, #000); border-color: color-mix(in srgb, var(--brand-press) 84%, #000); color: var(--brand-contrast); }
.ob .btn.btn--ghost { background: transparent; border-color: var(--border); border-width: 1px; color: var(--text); }
.ob .btn.btn--ghost:hover { background: var(--bg-alt, rgba(127,127,127,.10)); color: var(--text); }
.ob .btn:focus-visible, .ob-nav__cta:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* La marca y los enlaces son objetivos tactiles, no solo texto: los dos miden
   44 px de alto como minimo. "Inicio" con padding de barra se quedaba en 42 de
   ancho, y la marca en 20 de alto: se ven bien y se tocan mal. */
.ob-nav__marca { position: relative; display: flex; align-items: center; gap: var(--space-sm); min-height: 44px; font-weight: 700; text-decoration: none; color: inherit; }
/* Logotipo real de la app. El PNG es oscuro (903x275, razon 3.28:1): va tal
   cual sobre el sitio claro. El texto "OneBox" queda como respaldo en sr-only
   — absoluto, para que no ocupe sitio y el ::after caiga junto al logo. */
.ob-nav__logo {
  display: block; height: 36px; width: auto;
  object-fit: contain;
}
.ob-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* La version va anclada al logo, no al puntito que lo sustituyo. */
.ob-nav__marca::after {
  content: "7.2";
  font-size: .6rem; font-weight: 700; color: var(--brand); letter-spacing: .02em;
  align-self: center; margin-left: -.4rem;
}

/* El eslogan va pegado a la marca, en el heroe y en el pie. Es el nombre de la
   release, no un titulo: por eso el peso lo lleva el color, no el tamano. */
.ob-hero__kicker {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  margin: 0 0 var(--space-sm);
  padding: 4px 12px; border-radius: 999px;
  background: color-mix(in srgb, var(--brand-contrast) 14%, transparent);
  color: var(--brand-contrast);
  font-size: var(--fluid-sm); font-weight: 700; letter-spacing: .01em;
}
.ob-hero h1, .ob__h2 { letter-spacing: -.02em; }
.ob-hero h1 { color: var(--brand-contrast); }
.ob-hero__lead strong, .ob-lista strong { color: var(--text); font-weight: 600; }
.ob-hero .ob-lista strong { color: var(--brand-contrast); }
/* Los botones dentro del hero son blanco sobre la banda de marca, al reves
   que en el resto de la pagina: sobre --brand-press un boton naranja no se
   veria y uno oscuro pesarfa. Que el primario sea el blanco deja el naranja
   para donde la pagina ya lo pone. */
.ob-hero a.btn { text-decoration: none; }
.ob-hero .btn.btn--primary { background: var(--brand-contrast); border-color: var(--brand-contrast); color: var(--brand-press); }
.ob-hero .btn.btn--primary:hover { background: color-mix(in srgb, var(--brand-contrast) 88%, transparent); color: var(--brand-press); }
.ob-hero .btn.btn--ghost { background: transparent; border-color: color-mix(in srgb, var(--brand-contrast) 55%, transparent); color: var(--brand-contrast); }
.ob-hero .btn.btn--ghost:hover { background: color-mix(in srgb, var(--brand-contrast) 12%, transparent); color: var(--brand-contrast); }

/* ── Planes ───────────────────────────────────────────────────────────── */
/* ".ob-plan__mas" es lo unico que sigue vivo de la version anterior (2
   tarjetas + toggle Mensual/Anual). El resto de la familia .ob-plan__* /
   .ob-planes / .ob-plan--* / .ob-plan__tab quedo muerto cuando esa version
   se reemplazo por las 3 tarjetas de .ob-ptier (2026-10-01) — verificado con
   grep contra todo el sitio antes de borrarlo, cero referencias en HTML. */
.ob-plan__mas { margin: 0 0 var(--space-md); font-size: var(--fluid-sm); }
.ob-plan__mas a { color: var(--text-mute); text-decoration: underline; text-underline-offset: 2px; }
.ob-plan__mas a:hover { color: var(--text); }

/* ── Encabezado compacto de planes.php (sin .ob-hero) ─────────────────────
   Paginas utilitarias (precio) no necesitan el bloque rojo de marca: entre
   mas rapido se vea la tarjeta, mejor. Los tres checks son el equivalente a
   los badges de confianza que Pulpos pone bajo su H1 de precios. */
.ob-precios__head { padding-block: var(--space-xl) var(--space-lg); max-width: 42rem; }
.ob-precios__head h1 { font-size: clamp(1.75rem, 1.3rem + 2vw, 2.75rem); margin: 0 0 var(--space-sm); letter-spacing: -.02em; }
.ob-precios__lead { color: var(--text-mute); font-size: var(--fluid-md); margin: 0 0 var(--space-md); }
.ob-confianza { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-md); font-size: var(--fluid-sm); font-weight: 600; }
.ob-confianza li { display: flex; align-items: center; gap: var(--space-xs); }

/* ── Complementos ─────────────────────────────────────────────────────────
   Misma familia visual que las tarjetas de precio de arriba (.ob-ptier):
   radio grande, insignia en pildora, precio en el color de marca. Antes
   usaban .badge + .card planos, la pieza que el catalogo trae para un
   panel interno — al lado de las tarjetas de precio recien rediseñadas se
   leia como una pantalla distinta pegada a la de ventas. El paquete (mejor
   valor por unidad) se resalta con el mismo borde de marca que usa el tier
   Pro, no un elemento nuevo. */
.ob-addon {
  display: flex; flex-direction: column; gap: var(--space-xs); align-items: flex-start;
  border-radius: var(--radius-lg); padding: var(--space-lg);
}
.ob-addon--paquete { border-color: var(--brand); box-shadow: 0 16px 32px -20px color-mix(in srgb, var(--brand) 40%, transparent); }
.ob-addon__tipo {
  align-self: flex-start; font-size: var(--fluid-sm); font-weight: 700;
  padding: 4px 14px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-mute);
}
.ob-addon__tipo--paquete { background: var(--brand); color: var(--brand-contrast); }
.ob-addon__nombre { margin: var(--space-sm) 0 0; font-size: var(--fluid-md); }
.ob-addon__incluye { margin: 0; color: var(--text-mute); font-size: var(--fluid-sm); }
.ob-addon__precio { margin: var(--space-sm) 0 0; font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--brand-press); }
.ob-addon__precio small { font-size: .55em; font-weight: 500; color: var(--text-mute); }

/* ── Calculadora de arranque (planes.php) ─────────────────────────────────
   Tres columnas: lo fijo (arranque), lo que el visitante ajusta (inputs),
   lo que resulta (total). En movil se apilan en ese mismo orden: primero
   que sabe cuanto es el piso, luego ajusta, luego ve el total — nunca el
   total antes de saber que lo movio. */
.ob-calc { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--space-lg); align-items: start; }
@media (max-width: 760px) { .ob-calc { grid-template-columns: 1fr; } }
.ob-calc__rotulo { margin: 0; font-size: var(--fluid-sm); color: var(--text-mute); font-weight: 600; text-transform: uppercase; letter-spacing: .02em; }
.ob-calc__precio { margin: var(--space-xs) 0 var(--space-sm); font-size: 1.6rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.ob-calc__precio small { font-size: .6em; font-weight: 500; color: var(--text-mute); }
.ob-calc__controles { display: grid; gap: var(--space-md); }
.ob-calc__resultado { border-left: 1px solid var(--border); padding-left: var(--space-lg); }
@media (max-width: 760px) { .ob-calc__resultado { border-left: 0; padding-left: 0; border-top: 1px solid var(--border); padding-top: var(--space-lg); } }
.ob-calc__total { margin: var(--space-xs) 0 0; font-size: 2rem; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--brand-press); }
.ob-calc__total--sub { font-size: 1.4rem; color: var(--text); }

/* ── Listas y notas de pagina ─────────────────────────────────────────── */
.ob-lista { max-width: 48rem; margin: 0 0 var(--space-lg); padding-left: 1.1rem; display: grid; gap: var(--space-xs); color: var(--text-mute); }
.ob-lista li::marker { color: var(--brand); }
.ob-nota { color: var(--text-mute); max-width: 48rem; font-size: var(--fluid-sm); margin: var(--space-sm) 0 0; }

/* Etiqueta "se conecta con" (pos.php): hace visible el pilar de integracion
   paso a paso, sin otra tabla ni diagrama — son las mismas badges que ya
   usa novedades.php, solo que aqui nombran un modulo en vez de un area de
   cambio. */
.ob-conecta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-xs); margin: var(--space-md) 0 0; }
.ob-conecta__rotulo { color: var(--text-mute); font-size: var(--fluid-sm); margin-right: var(--space-xs); }

/* ── Capas de cascada: el orden, hecho lista ──────────────────────────── */
.ob-capas { list-style: none; margin: 0 0 var(--space-lg); padding: 0; display: grid; gap: var(--space-xs); max-width: 44rem; counter-reset: capa; }
.ob-capas__item {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm); border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg);
  font-size: var(--fluid-sm);
}
.ob-capas__n { flex: none; width: 1.6rem; text-align: center; color: var(--text-mute); font-variant-numeric: tabular-nums; font-weight: 700; }
.ob-capas__nombre { font-weight: 600; }
/* La capa legacy se ve como lo que es: la parte que todavia no es del sistema
   nuevo. No se esconde, porque esconderla seria mentir sobre el estado. */
.ob-capas__item--legado { border-style: dashed; color: var(--text-mute); }
.ob-capas__nota { color: var(--text-mute); font-size: .85em; margin-left: auto; text-align: right; }
@media (max-width: 34rem) { .ob-capas__nota { display: none; } }

.ob-contraste__chip { width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--border); flex: none; }
.ob-contraste { width: 100%; border-collapse: collapse; font-size: var(--fluid-sm); margin-bottom: var(--space-sm); }
.ob-contraste caption { text-align: left; color: var(--text-mute); padding-bottom: var(--space-xs); }
.ob-contraste th, .ob-contraste td { padding: var(--space-xs) var(--space-sm); border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; }
.ob-contraste th { font-weight: 500; white-space: nowrap; }
.ob-contraste td { display: flex; align-items: center; gap: var(--space-xs); }
.ob-contraste__ratio { font-variant-numeric: tabular-nums; font-weight: 600; }
.ob-contraste__papel { color: var(--text-mute); font-size: .82em; }
@media (max-width: 40rem) {
  .ob-contraste__papel { display: none; }
}

/* ── Pie ────────────────────────────────────────────────────────────────
   Identidad estilo papiro (2026-10-07, en comparativa con la ps/ de la
   agencia: la pieza que da identidad ahi es el cierre SIEMPRE oscuro con
   la linea de gradiente de marca encima). El pie no sigue al tema: en
   claro es el cierre oscuro clasico y en oscuro se mantiene en el MISMO
   tono, para que ninguna pagina "flote" en distinto fondo al final.

   HTML: .ob-pie__grid de 4 columnas + .ob-pie__abajo (copyright/legales).
   El CTA de la columna de accion hereda del .btn del sitio. */
.ob-pie {
  --pie-bg: #16161a;
  --pie-ttl: #f2f2f5;
  --pie-txt: #b0b0c4;
  position: relative;
  margin-top: var(--space-2xl);
  background: var(--pie-bg);
  color: var(--pie-txt);
  isolation: isolate;
}
/* La linea de marca: 2px de gradiente del rojo de OneBox, la firma visual
   que la ps/ dibuja encima de su pie. Se corta a transparentes en los
   bordes para que respire dentro del contenedor. */
.ob-pie::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: linear-gradient(90deg, transparent,
    var(--brand) 22%, var(--brand-press) 48%, var(--brand) 74%, transparent);
  opacity: .55; pointer-events: none;
}
.ob-pie h3 {
  margin: 0 0 var(--space-md); color: var(--pie-ttl);
  font-size: 1.01rem; font-weight: 700; letter-spacing: .01em;
}
.ob-pie__in {
  max-width: 74rem; margin-inline: auto;
  padding: clamp(2.5rem, 4vw, 3.5rem) var(--space-lg) var(--space-lg);
}
.ob-pie__grid { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }
@media (min-width: 48rem) { .ob-pie__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 64rem) {
  .ob-pie__grid { grid-template-columns: minmax(0,1.9fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1.35fr); }
}
.ob-pie__col:first-child { max-width: 36ch; }
.ob-pie__nota { margin: 0 0 var(--space-md); }
.ob-pie__lema { margin: 0 0 var(--space-md); font-size: var(--fluid-sm); line-height: 1.55; }
.ob-pie__eslogan { margin: 0 0 var(--space-md); font-size: var(--fluid-sm); display: flex; align-items: center; gap: var(--space-xs); }
.ob-pie__eslogan em { font-style: normal; font-weight: 600; color: var(--pie-ttl); }
/* Logo del pie: el PNG es oscuro (medido 13/255) y el pie ahora SIEMPRE
   oscuro, asi que la inversion es constante — el mismo tratamiento que le
   da la app de OneBox en su propio pie oscuro. */
.ob-pie__logo { display: block; height: 30px; width: auto; filter: brightness(0) invert(1); opacity: .95; }
.ob-pie__col--accion p { margin: 0 0 var(--space-md); font-size: var(--fluid-sm); line-height: 1.55; }
.ob-pie__contacto {
  display: inline-block; color: #ff8a5c; text-decoration: none;
  font-weight: 600; font-size: var(--fluid-sm); margin-top: var(--space-md);
}
.ob-pie__contacto:hover, .ob-pie__contacto:focus-visible { text-decoration: underline; }
/* Fondo presionado: el blanco sobre --brand son 4.06:1 y esta insignia pesa
   .7rem, asi que con el tono claro no llega. Con --brand-press son 6.49:1. */
.ob-pie__version { padding: 2px 8px; border-radius: 999px; background: var(--brand-press); color: var(--brand-contrast); font-size: .7rem; font-weight: 800; }
.ob-pie__abajo {
  display: flex; flex-wrap: wrap; gap: var(--space-xs) var(--space-lg);
  justify-content: space-between; align-items: center;
  margin-top: clamp(1.75rem, 3vw, 2.5rem); padding-top: var(--space-md);
  border-top: 1px solid rgba(255,255,255,.07); font-size: .82rem;
}
.ob-pie__legal { display: flex; gap: var(--space-md); flex-wrap: wrap; }
.ob-pie__legal a, .ob-pie__abajo a { color: var(--pie-txt); text-decoration: none; }
.ob-pie__legal a:hover, .ob-pie__abajo a:hover { color: #ff8a5c; }

/* ── Acceso asections ──────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .ob-revelar { opacity: 0; transform: translateY(10px); transition: opacity .5s ease, transform .5s ease; }
  .ob-revelar.is-visible { opacity: 1; transform: none; }
}
/* Si el JS nunca arranca, el contenido se queda invisible. Eso es peor que
   no animarse: por eso el estado final se aplica desde el primer fotograma
   y la animacion solo se restaura si el navegador la permite. */
@media (prefers-reduced-motion: reduce) { .ob-revelar { opacity: 1; transform: none; } }

/* ── Obi: lo que se le puede preguntar ────────────────────────────────────
   La columna izquierda es la capacidad, en palabras de negocio. La derecha es
   la frase literal que la dispara: el visitante no tiene que creernos, puede
   probarla tal cual. Por eso la frase va en <code> y no en parrafo. */
.ob-preguntas { display: grid; gap: var(--space-lg); grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); margin: 0 0 var(--space-lg); }
.ob-preguntas__grupo { margin: 0; }
.ob-preguntas__titulo {
  margin: 0 0 var(--space-sm); font-size: var(--fluid-sm); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--brand);
}
.ob-preguntas__lista { margin: 0; display: grid; gap: var(--space-xs); }
.ob-preguntas__par {
  display: grid; gap: 2px; padding: var(--space-xs) 0;
  border-bottom: 1px solid var(--border);
}
.ob-preguntas__par:last-child { border-bottom: 0; }
.ob-preguntas__par dt { font-weight: 600; color: var(--text); }
.ob-preguntas__par dd { margin: 0; }
/* La frase se muestra tal cual se escribe, con sus comillas: es la prueba de
   que la capacidad existe, no una promesa de copy.
   El color es --text-soft y no --text-mute porque el mute claro mide 2.31:1
   sobre el fondo real #f5f5f4 (medido, no supuesto) y queda por debajo del
   minimo AA. --text-soft da 6.99:1 y ademas tiene version oscura, asi que
   el bloque no pierde contraste al cambiar de tema. */
.ob-preguntas__par code {
  font-family: var(--font-mono, ui-monospace, monospace); font-size: .9em;
  color: var(--text-soft); word-break: break-word;
}
@media (max-width: 34rem) { .ob-preguntas { grid-template-columns: 1fr; } }

/* ═══ Correcciones de contraste · al final a proposito ═══════════════════
   Estas reglas van aqui, no arriba: las anteriores de este archivo compiten
   con la MISMA especificidad, y en CSS gana la ultima. Puestas al principio
   se dejaban pisar sin avisar. Este bloque es el que cierra la pagina, asi
   que es el que decide. Cada valor viene de medir, no de suponer.

   El eslogan del heroe NO va aqui: el heroe es fondo de marca y su texto es
   blanco fijo (--brand-contrast, definido arriba). Metido en este grupo se
   pisaba y quedaba invisible sobre el mismo fondo.

   BUG REAL CORREGIDO (2026-10-01): las 4 correcciones de abajo se midieron
   TODAS contra el fondo claro (#f5f5f4 o su lavado de color) — el propio
   comentario de insignias lo dice: "los 4.70:1 de omnis-tokens.css miden
   contra el fondo oscuro, no contra el lavado". Pero vivian en selectores
   SIN guardia de tema, en un archivo SIN @layer: lo no-encapado siempre le
   gana a lo encapado (omnis-tokens.css SI usa @layer), asi que esta
   correccion "solo claro" pisaba el oscuro de verdad en TODO el sitio para
   cualquier visitante con el sistema en oscuro — insignias, alertas, el
   letrero PRO y el titulo de las preguntas de Obi salian con un rojo oscuro
   invisible sobre fondo casi negro. Misma causa raiz que --text-mute arriba.
   Se envuelven las 4 en la misma guardia: solo se emiten cuando el
   resultado final es claro (sistema en claro sin eleccion en contra, o
   eleccion explicita de claro). */
@media (prefers-color-scheme: light) {
  html:not([data-theme="dark"]) .ob-preguntas__titulo { color: var(--brand-press); }

  html:not([data-theme="dark"]) .omnis .badge--ok,     html:not([data-theme="dark"]) .badge--ok     { color: var(--ok-solid); }
  html:not([data-theme="dark"]) .omnis .badge--warn,   html:not([data-theme="dark"]) .badge--warn   { color: var(--warn-solid); }
  html:not([data-theme="dark"]) .omnis .badge--danger, html:not([data-theme="dark"]) .badge--danger { color: var(--danger-solid); }
  html:not([data-theme="dark"]) .omnis .badge--info,   html:not([data-theme="dark"]) .badge--info   { color: var(--info-solid); }

  html:not([data-theme="dark"]) .ob a[href*="producto"]:not(.btn),
  html:not([data-theme="dark"]) .ob a[href*="planes"]:not(.btn),
  html:not([data-theme="dark"]) .ob a[href*="obi"]:not(.btn) { color: var(--brand-press); }

  html:not([data-theme="dark"]) .omnis .alert--ok,     html:not([data-theme="dark"]) .alert--ok     { color: var(--ok-solid); }
  html:not([data-theme="dark"]) .omnis .alert--warn,   html:not([data-theme="dark"]) .alert--warn   { color: var(--warn-solid); }
  html:not([data-theme="dark"]) .omnis .alert--danger, html:not([data-theme="dark"]) .alert--danger { color: var(--danger-solid); }
  html:not([data-theme="dark"]) .omnis .alert--info,   html:not([data-theme="dark"]) .alert--info   { color: var(--info-solid); }
}
html[data-theme="light"] .ob-preguntas__titulo { color: var(--brand-press); }

html[data-theme="light"] .omnis .badge--ok,     html[data-theme="light"] .badge--ok     { color: var(--ok-solid); }
html[data-theme="light"] .omnis .badge--warn,   html[data-theme="light"] .badge--warn   { color: var(--warn-solid); }
html[data-theme="light"] .omnis .badge--danger, html[data-theme="light"] .badge--danger { color: var(--danger-solid); }
html[data-theme="light"] .omnis .badge--info,   html[data-theme="light"] .badge--info   { color: var(--info-solid); }

html[data-theme="light"] .ob a[href*="producto"]:not(.btn),
html[data-theme="light"] .ob a[href*="planes"]:not(.btn),
html[data-theme="light"] .ob a[href*="obi"]:not(.btn) { color: var(--brand-press); }

html[data-theme="light"] .omnis .alert--ok,     html[data-theme="light"] .alert--ok     { color: var(--ok-solid); }
html[data-theme="light"] .omnis .alert--warn,   html[data-theme="light"] .alert--warn   { color: var(--warn-solid); }
html[data-theme="light"] .omnis .alert--danger, html[data-theme="light"] .alert--danger { color: var(--danger-solid); }
html[data-theme="light"] .omnis .alert--info,   html[data-theme="light"] .alert--info   { color: var(--info-solid); }

/* ── Capturas reales ──────────────────────────────────────────────────────
   No son mockups: son la aplicacion real, capturada de la instancia local.
   El marco y la sombra no son decoracion, sirven para separar la foto del
   papel — sin eso, una captura de interfaz sobre fondo claro se ve como un
   bloque de texto desalineado. */
.ob-captura { margin: 0 0 var(--space-lg); }
.ob-captura img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px rgba(28,25,23,.05), 0 12px 28px -18px rgba(28,25,23,.35);
  background: var(--bg);
}
/* El pie de la captura dice de donde sale. Una foto sin procedencia se lee
   como ilustracion; esta dice que se ejecuto. */
.ob-captura figcaption {
  margin-top: var(--space-xs); font-size: var(--fluid-sm); color: var(--text-mute);
}
.ob-captura figcaption b { color: var(--text); font-weight: 600; }

/* ── VITRINA DE MÓDULOS ─────────────────────────────────────────────────────
   El bloque que más vende y el que no existía: cada área de la operación
   con SU captura al lado, en grande, y lo que resuelve al lado en palabras
   de negocio.

   Por qué no se reutiliza .ob-captura a secas: ahí la foto manda y el texto
   cuelga debajo, lo que sirve para una captura con su nota. En la vitrina la
   foto y el texto se leen a la vez, y el texto necesita su propia medida —
    dos renglones de 90 caracteres son dos lineas de nota al pie, no un beneficio.

   La foto va con proporciónFIJA (16/10, la de las capturas) y no con
   height:auto sobre el ancho: las diecinueve miden 1200x750, pero si mañana
   entra una de otro tamaño, un alto variable desalinea la retícula y las
   tarjetas de al lado dejan de alinearse entre sí. */
.ob-vitrina { display: grid; gap: var(--space-xl); margin: 0 0 var(--space-lg); }
.ob-vitrina__fila {
  display: grid; gap: clamp(var(--space-lg), 3vw, var(--space-2xl));
  align-items: center;
  grid-template-columns: 1.15fr .85fr;
}
/* La alternancia: la foto cambia de lado en cada bloque. Con la foto siempre a
   izquierda el ojo aprende a saltarla, y media vitrina se lee sin ver nada. */
.ob-vitrina__fila:nth-child(even) .ob-vitrina__foto { order: 2; }
.ob-vitrina__foto { margin: 0; }
.ob-vitrina__foto img {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  object-position: top left;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px rgba(28,25,23,.05), 0 18px 40px -24px rgba(28,25,23,.45);
  background: var(--bg);
}
.ob-vitrina__texto { min-width: 0; }
.ob-vitrina__etiqueta {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  margin: 0 0 var(--space-xs); padding: 3px 10px; border-radius: 999px;
  background: var(--brand-wash); color: var(--brand-press);
  font-size: var(--fluid-sm); font-weight: 700; letter-spacing: .02em;
}
.ob-vitrina__titulo {
  margin: 0 0 var(--space-sm);
  font-size: clamp(1.25rem, 1.05rem + .9vw, 1.75rem);
  line-height: 1.15; letter-spacing: -.02em; color: var(--text);
}
.ob-vitrina__que { margin: 0 0 var(--space-md); color: var(--text-soft); font-size: var(--fluid-md); }
.ob-vitrina__lista { list-style: none; margin: 0 0 var(--space-md); padding: 0; display: grid; gap: var(--space-xs); }
.ob-vitrina__lista li { display: flex; gap: var(--space-xs); align-items: baseline; font-size: var(--fluid-sm); }
.ob-vitrina__lista li::before {
  content: "\2713"; flex: none; font-weight: 700; color: var(--brand-press);
}
/* Lista de dos columnas para tarjetas con catalogo largo (ej. "Es para mi
   negocio"): solo en filas amplias; de 56rem hacia abajo vuelve a una
   columna porque la foto ya esta arriba y el texto es todo el ancho. */
@media (min-width: 56.001rem) {
  .ob-vitrina__lista--ancha { grid-template-columns: 1fr 1fr; }
}
.ob-vitrina__nota { margin: 0; color: var(--text-mute); font-size: var(--fluid-sm); }
@media (max-width: 56rem) {
  .ob-vitrina__fila { grid-template-columns: 1fr; }
  /* En una columna la foto SIEMPRE va arriba: el orden alternado en una
     columna pone la foto de la segunda fila debajo de un bloque de texto que
     ya se leyó, y el texto de la segunda antes de su foto. */
  .ob-vitrina__fila:nth-child(even) .ob-vitrina__foto { order: 0; }
}

/* ── GALERÍA: todo el sistema en una pantalla ───────────────────────────────
   El argumento final de venta: "estas son todas las pantallas, ninguna está
   dibujada". Rejilla de miniaturas con el nombre del area. Es el equivalente
   visual de la tabla comparativa — la misma función (demostrar en vez de
   prometer) con otro formato.

   La miniatura NO lleva pie de foto: diecinueve pies repetidos son ruido. El
   nombre va en el <figcaption> y el alt lleva el contenido, que es lo que
   necesita un lector de pantalla. */
.ob-galeria {
  display: grid; gap: var(--space-md);
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  margin: 0 0 var(--space-lg);
}
.ob-galeria__item { margin: 0; }
.ob-galeria__item img {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  object-position: top left;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg);
  box-shadow: 0 1px 2px rgba(28,25,23,.05), 0 10px 24px -20px rgba(28,25,23,.4);
  transition: box-shadow .18s ease, transform .18s ease;
}
.ob-galeria__item:hover img,
.ob-galeria__item:focus-within img {
  transform: translateY(-2px);
  box-shadow: 0 1px 2px rgba(28,25,23,.06), 0 16px 34px -20px rgba(28,25,23,.5);
}
.ob-galeria__item figcaption {
  margin-top: var(--space-xs); display: flex; align-items: baseline; gap: var(--space-xs);
  font-size: var(--fluid-sm);
}
.ob-galeria__area { color: var(--text-mute); font-size: .85em; }
.ob-galeria__nombre { color: var(--text); font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
  .ob-galeria__item img { transition: none; }
  .ob-galeria__item:hover img { transform: none; }
}

/* ── FICHA DE MÓDULO en lenguaje de negocio ─────────────────────────────────
   Reemplaza a la tarjeta que llevaba el nombre del archivo de codigo en
   <code>. El nombre del archivo no le dice nada a quien compra; lo que le
   dice es qué resuelve. El nombre real se conserva en un <details> al pie de
   la pagina, para quien quiera verificar — verificar sigue siendo posible,
   solo dejo de ser el mensaje principal. */
.ob-ficha { margin: 0; display: flex; flex-direction: column; gap: var(--space-xs); }
.ob-ficha__que { margin: 0; color: var(--text-soft); font-size: var(--fluid-sm); }
.ob-ficha__hace { margin: 0; list-style: none; padding: 0; display: grid; gap: 2px; }
.ob-ficha__hace li {
  display: flex; gap: var(--space-xs); align-items: baseline;
  font-size: var(--fluid-sm); color: var(--text-mute);
}
.ob-ficha__hace li::before { content: "\2013"; color: var(--brand-press); font-weight: 700; flex: none; }

/* El bloque plegable de "lo que hay adentro, con nombre real". Para quien
   quiera comprobar, no para quien compra. */
.ob-verificar { margin: 0 0 var(--space-lg); }
.ob-verificar > summary {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: 44px; cursor: pointer; color: var(--text-soft);
  font-size: var(--fluid-sm); font-weight: 600;
  list-style: none;
}
.ob-verificar > summary::-webkit-details-marker { display: none; }
.ob-verificar > summary::before {
  content: "\25B8"; color: var(--brand-press); transition: transform .15s ease;
}
.ob-verificar[open] > summary::before { transform: rotate(90deg); }
.ob-verificar__cuerpo {
  margin-top: var(--space-sm); padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}
.ob-verificar__lista {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--space-xs) var(--space-md);
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  font-size: var(--fluid-sm); color: var(--text-mute);
}
.ob-verificar__lista li { display: flex; gap: var(--space-xs); align-items: baseline; }
.ob-verificar__lista code { color: var(--text-soft); font-size: .9em; }
@media (prefers-reduced-motion: reduce) { .ob-verificar > summary::before { transition: none; } }


/* ── Dos ajustes que eran style="" en el HTML ─────────────────────────────
   El catalogo ya trae .ob__h2 con su margen; dentro de una tarjeta ese margen
   sobra porque la tarjeta ya separa el bloque. Y el gap de una fila de
   botones no cambia nunca, asi que tampoco necesita vivir en el HTML. */
.ob__h2--apilado { margin-top: 0; }
.ob-filas { gap: var(--space-sm); }

/* ── Utilidades que reemplazan los style="" del HTML ──────────────────────
   Estas cuatro vivian dentro del HTML en 9 lugares. No cambian el diseno:
   cambian DONDE vive la decision, que es lo que permite cambiarla despues. */
.ob-rejilla-md { gap: var(--space-md); }
.ob-mt-md      { margin-top: var(--space-md); }
.ob-ancho-46   { max-width: 46rem; }
/* El <code> de un nombre de controlador no debe crecer con el contexto: se
   neutraliza para que todos los nombres del listado se vean igual. */
.ob-cod { font-size: 1em; }

/* Reparto en columnas de la rejilla, para que el HTML no lleve la cuenta. */
.ob-span-3 { grid-column: span 3; }
.ob-span-5 { grid-column: span 5; }
.ob-span-6 { grid-column: span 6; }
.ob-span-7 { grid-column: span 7; }
/* Titular dentro de una tarjeta: sin margen propio, porque la tarjeta ya separa. */
.ob-card-h2 { margin: 0; font-size: 1.1rem; }
.ob-filas-lg { gap: var(--space-lg); align-items: flex-start; }
.ob-ancho-26 { max-width: 26rem; }
/* Una etiqueta que va en su propia linea dentro de un texto corrido. */
.ob-bloque { display: block; }
.ob-bloque-ligero { display: block; font-weight: 400; }
.ob-mb-sm { margin-bottom: var(--space-sm); }
/* Una cita ajena: sangria y filete, para que se lea como voz de otro. */
.ob-cita {
  margin: 0 0 var(--space-sm); padding-left: var(--space-sm);
  border-left: 3px solid var(--border);
}

/* En una rejilla que ya colapsa a una sola columna, decir "ocupa 6 columnas"
   no es una medida: es una instruccion imposible que empuja el ancho por
   encima de la pantalla. Medido: a 390px una figura con span-6 llegaba a
   442px de ancho. En una columna, todo ocupa la columna entera. */
@media (max-width: 46rem) {
  .ob-span-3, .ob-span-5, .ob-span-6, .ob-span-7 { grid-column: 1 / -1; }
}

/* ── El hueco: dos salidas malas y una buena, y que se note ────────────────
   Con tres tarjetas iguales, el visitante lee los tres renglones con el mismo
   peso y se queda sin decision. Aqui las dos opciones que no recomendamos se
   desaturan (opacidad, sin borde de color) y OneBox se levanta: sombra de
   marca y borde de color. Es jerarquia con los mismos datos, sin inventar
   ninguno. */
.ob-hueco { align-items: stretch; }
.ob-hueco__malo { opacity: .72; }
.ob-hueco__malo:hover { opacity: 1; }
.ob-hueco__uno {
  border-color: color-mix(in srgb, var(--brand) 40%, var(--border));
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--brand-wash) 80%, var(--bg)) 0%, var(--bg) 62%);
  box-shadow: 0 18px 44px -30px color-mix(in srgb, var(--brand) 85%, transparent);
  opacity: 1;
}
.ob-hueco__uno .card__head h3 { color: var(--brand-press); }

/* ═══ BANDA DE CONVERSION ════════════════════════════════════════════════
   Lo que el sitio no tenia: un "y ahora que" con respuesta. Antes el pie era
   un aviso tecnico (tokens, capas, "sin bootstrap") — voz de desarrollador en
   una pagina que se supone que vende. La banda se pone antes del pie, con la
   oferta concreta, y la jerga tecnica salio del pie por completo.
   El color de marca entra aqui, a pantalla completa, para que el cierre de la
   pagina tenga el mismo peso que la entrada. */
.ob-cta { padding-block: calc(var(--space-2xl) * 1.3); }
.ob-cta__in {
  display: grid; gap: var(--space-lg); align-items: center;
  grid-template-columns: 1.35fr auto;
  padding: clamp(var(--space-lg), 3.5vw, var(--space-2xl));
  border-radius: var(--radius-lg);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--border));
  background:
    radial-gradient(90% 160% at 100% 0%, color-mix(in srgb, var(--brand) 16%, transparent) 0%, transparent 58%),
    linear-gradient(160deg, color-mix(in srgb, var(--brand-wash) 92%, #fff) 0%, #fff 70%);
  box-shadow: 0 22px 50px -34px color-mix(in srgb, var(--brand) 80%, transparent);
}
.ob-cta__titulo {
  margin: 0 0 var(--space-xs);
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.15rem);
  line-height: 1.12; letter-spacing: -.02em; color: var(--text);
}
.ob-cta__lead { margin: 0; max-width: 42rem; color: var(--text-soft); font-size: var(--fluid-md); }
.ob-cta__lead strong { color: var(--text); font-weight: 600; }
/* La nota dice que pasa ahora que no hay buzon. Sin ella, el boton de abajo
   seria una promesa que el sitio no cumple. */
.ob-cta__nota { margin: var(--space-sm) 0 0; font-size: var(--fluid-sm); color: var(--text-mute); max-width: 40rem; }
.ob-cta__acciones { display: flex; flex-direction: column; gap: var(--space-sm); min-width: 15rem; }
.ob-cta__acciones .btn { width: 100%; }
@media (max-width: 52rem) {
  .ob-cta__in { grid-template-columns: 1fr; }
  .ob-cta__acciones { min-width: 0; width: 100%; }
  .ob-cta__acciones .btn { width: 100%; }
}

/* ── Pie: enlaces en vez de jerga ───────────────────────────────────────────
   Se quito "omnis-tokens → omnis-core → omnis-module. Sin Bootstrap"
   del pie: eso le interesa al desarrollador, no a quien compra. En su lugar
   el pie ofrece las salidas que un visitante de verdad busca cuando ya
   decidio seguir mirando. */
.ob-pie__links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .6rem; }
.ob-pie__links a {
  color: var(--pie-txt); text-decoration: none;
  font-size: var(--fluid-sm); padding: 2px 0;
  transition: color .15s ease, padding-left .15s ease;
}
.ob-pie__links a:hover, .ob-pie__links a:focus-visible {
  color: color-mix(in srgb, var(--brand) 74%, #fff); padding-left: 4px;
}

/* ── Pie del hero: lo secundario, sin competir ─────────────────────────────
   El hero tenia tres botones del mismo tamano. Con tres acciones identicas la
   gente elige la de la derecha por costumbre y se va sin hacer nada. Ahora hay
   UN boton que pide algo, y los otros tres destinos bajan a una linea de
   texto: siguen estando a un clic, pero ya no gritan lo mismo que el CTA. */
.ob-hero__pie {
  display: flex; flex-wrap: wrap; gap: var(--space-xs) var(--space-lg);
  margin: var(--space-lg) 0 0; padding-top: var(--space-md);
  border-top: 1px solid color-mix(in srgb, var(--brand) 18%, var(--border));
  font-size: var(--fluid-sm);
}
.ob-hero__pie a { color: var(--text-soft); text-decoration: none; font-weight: 500; }
.ob-hero__pie a::after { content: " \2192"; color: var(--brand-press); }
.ob-hero__pie a:hover, .ob-hero__pie a:focus-visible { color: var(--brand-press); text-decoration: underline; }

/* ══ LOGOTIPO EN EL PIE ══════════════════════════════════════════════════════
   Misma razon 3.28:1 que en la barra. El lema pasa a estar al lado en lugar de
   precedido por un "—", porque la imagen ya ocupa el lugar de la marca. */
/* Logo/nota/lema del pie: viven en la seccion ── Pie ── (el pie es oscuro
   fijo y el logo oscuro se invierte SIEMPRE — ver alli). */

/* ═══ AREA: la lista de fichas y su foto, lado a lado ═══════════════════
   El bloque .ob-area es lo que hace que la foto sea PRUEBA y no adorno:
   va pegada a las fichas que dice demostrar. Si se separan —foto arriba,
   fichas abajo— el visitante la mira, asiente y sigue bajando sin
   relacionar una cosa con la otra.

   DOS COLUMNAS, Y CUANDO SE APILAN LA FOTO SE VA PRIMERO. En una sola
   columna, la foto de 1200x750 con su figcaption empuja las fichas fuera
   de la pantalla, y el que está leyendo las fichas no las ve. Por eso el
   orden se invierte en el breakpoint: primero lo que se ve, después el
   respaldo. */
.ob-area { display: grid; gap: var(--space-lg); align-items: start; }
@media (min-width: 62rem) {
  .ob-area { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}

/* La foto se pega a la lista en vez de flotar: en grid, sticky no hace
   falta y un position sticky dentro de un grid se comporta distinto en
   cada navegador. La alineacion start evita que la foto estire la fila. */
.ob-area__foto {
  margin: 0;
  position: sticky;
  top: calc(var(--nav-h, 4rem) + var(--space-md));
}
@media (max-width: 61.99rem) {
  .ob-area__foto { position: static; order: -1; }
  .ob-area__foto img { aspect-ratio: 16 / 9; object-fit: cover; }
}

/* La ficha: el titulo manda, la explicacion acompana, la prueba se
   retira a una linea de gris pequeno. Ese orden es el decision — si el
   nombre del archivo subiera, la pagina volveria a ser un indice. */
.ob-ficha__titulo {
  margin: 0 0 var(--space-2xs, .25rem);
  font-size: var(--fluid-md);
  line-height: 1.3;
  letter-spacing: -0.01em;
}
.ob-ficha__prueba {
  margin: auto 0 0;              /* se pega abajo: el bloque de pruebas
                                    queda en linea base en todas las
                                    tarjetas de la fila, no a distinta
                                    altura segun cuanto texto tenga cada
                                    ficha. Sin esto, una fila de seis
                                    tarjetas sale con los pies en
                                    escalera. */
  padding-top: var(--space-2xs, .25rem);
  border-top: 1px solid var(--line);
  font-size: var(--text-xs, 0.75rem);
}
.ob-ficha__prueba code {
  color: var(--text-mute);
  background: none;
  padding: 0;
  font-size: inherit;
  letter-spacing: 0.02em;
}

/* ═══ CATÁLOGO COMPRIMIDO: UNA LÍNEA POR ÁREA ════════════════════════════
   La página de producto estaba contada en 50 tarjetas y nadie las leyó.
   Aquí vive esa misma información, pero como lista: nombre del área a la
   izquierda, sus capacidades en una línea al centro, y el número de
   capacidades a la derecha.

   TRES COLUMNAS Y UNA REGLA DE COMPOSICION: la columna de las capacidades
   toma todo el espacio que tiene. Entre el nombre y el texto no hay más
   que espacio — el ojo lee la línea completa como una unidad, y el número
   al final cierra la fila. La rejilla es de anchos fijos: sin ellos, el
   área más corta ("Sucursales") colapsaría su columna y el ojo se perdería
   de fila en fila. */
.ob-areas {
  display: grid;
  gap: var(--space-sm);
  max-width: 46rem;
}
.ob-areas__fila {
  display: grid;
  grid-template-columns: minmax(9rem, 12rem) 1fr auto;
  gap: var(--space-md);
  align-items: baseline;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--line);
}
.ob-areas__nombre {
  font-weight: 600;
  color: var(--text);
}
.ob-areas__hace {
  color: var(--text-soft);
  font-size: var(--fluid-sm);
  line-height: 1.45;
}
/* AGENTE DISEÑO WEB 2026-09-30: el conteo era un numero suelto en gris; se
   pasa a .badge (ya existe en omnis-module.css, pastilla con fondo suave —
   el mismo patron de la pagina de licenciamiento de Papiro para sus cuotas,
   "Ubicaciones máximas: 2") sobre el dato REAL que ya calculaba este archivo
   (count($items)), sin inventar cifra nueva. */
.ob-areas__n {
  justify-self: end;
  font-variant-numeric: tabular-nums;
  color: var(--text-mute);
  font-size: var(--fluid-sm);
  white-space: nowrap;
}
@media (max-width: 40rem) {
  .ob-areas__fila { grid-template-columns: 1fr auto; gap: var(--space-2xs, .25rem); }
  .ob-areas__hace { grid-column: 1 / -1; order: 3; }
  .ob-areas__n { grid-column: 2; grid-row: 1; }
}

/* ═══ ACORDEON DE FUNCIONES DE LA PORTADA (2026-10-07, "Todo esto, en un
   solo sistema") — un <details> por grupo de la taxonomia; el clic abre y
   cierra (nativo), el hover abre con el mouse. La animacion de despliegue se
   hace con grid-template-rows 0fr->1fr: funciona para altura desconocida y
   no depende de JS. El cuerpo del sitio es .omnis, asi que los badges
   heredan su pastilla (omnis-module.css); aqui vive solo el acordeon. Para
   que el bloque respire igual que el resto, ancho acotado a max-w-74. */
.ob-badges {
  margin-inline: auto;
  max-width: 74rem;
  padding-inline: var(--space-gutter);
}
.ob-badges__grupo {
  border-block-end: 1px solid var(--line);
}
.ob-badges__titulo {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  cursor: pointer;
  user-select: none;
  list-style: none;
}
.ob-badges__titulo::-webkit-details-marker { display: none; }
.ob-badges__nombre {
  font-size: var(--fluid-md);
  font-weight: 600;
  color: var(--text);
}
.ob-badges__cuenta {
  font-variant-numeric: tabular-nums;
  font-size: var(--fluid-xs);
  font-weight: 700;
  line-height: 1;
  padding: 4px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 16%, var(--surface));
  color: color-mix(in srgb, var(--brand) 55%, var(--text));
  border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
}
.ob-badges__carr {
  margin-inline-start: auto;
  width: .5rem;
  height: .5rem;
  border-right: 2px solid var(--text-soft);
  border-bottom: 2px solid var(--text-soft);
  transform: rotate(-45deg) translateY(-2px);
  transition: transform .25s ease;
}
.ob-badges__grupo[open] .ob-badges__carr {
  transform: rotate(45deg) translateY(-1px);
}
/* Despliegue animado sin medir alturas: la fila pasa de 0 a 1fr y el hijo
   con overflow hidden recorta su contenido mientras colapsa. */
.ob-badges__cuerpo {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .45s cubic-bezier(.22, .61, .36, 1);
}
.ob-badges__nube {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding-block-end: var(--space-md);
}
.ob-badges__grupo[open] .ob-badges__cuerpo { grid-template-rows: 1fr; }

/* La insignia de cada funcion: pastilla con el color de marca. No usa el tono
   crudo --brand como texto (sobre el lavado mide 3.7:1, no llega a AA): mezcla
   marca y color de texto, que se adapta solo a claro y oscuro y mantiene el
   contraste. El punto de marca a la izquierda y el relleno solido al pasar el
   mouse son lo que la hace resaltar. */
.ob-badges__nube .badge {
  font-size: var(--fluid-sm);
  padding: 5px var(--space-md);
  gap: var(--space-xs);
  font-weight: 600;
  background: color-mix(in srgb, var(--brand) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  color: color-mix(in srgb, var(--brand) 62%, var(--text));
  box-shadow: 0 1px 0 color-mix(in srgb, var(--brand) 18%, transparent);
  transition: background var(--t-fast), color var(--t-fast),
              border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
.ob-badges__nube .badge::before {
  content: "";
  inline-size: .45rem;
  block-size: .45rem;
  border-radius: 999px;
  background: var(--brand);
  flex: none;
}
.ob-badges__nube .badge:hover {
  background: var(--brand-press);
  border-color: var(--brand-press);
  color: var(--brand-contrast);
  transform: translateY(-2px);
  box-shadow: 0 10px 20px -12px color-mix(in srgb, var(--brand-press) 75%, transparent);
}
.ob-badges__nube .badge:hover::before { background: var(--brand-contrast); }

/* Revelado en cascada al abrir: cada insignia entra con un retardo que crece
   con su indice (el --i que trae del render). Se animan translate y scale
   —propiedades sueltas— para no pisar el transform del hover. */
@keyframes ob-badge-in {
  from { opacity: 0; translate: 0 8px; scale: .94; }
  to   { opacity: 1; translate: 0 0;  scale: 1; }
}
.ob-badges__grupo[open] .ob-badges__nube .badge {
  animation: ob-badge-in .4s cubic-bezier(.2, .7, .3, 1) both;
  animation-delay: calc(var(--i, 0) * 28ms);
}
@media (prefers-reduced-motion: reduce) {
  .ob-badges__grupo[open] .ob-badges__nube .badge { animation: none; }
  .ob-badges__cuerpo { transition: none; }
}
.ob-badges__titulo:hover .ob-badges__nombre { color: var(--brand-press); }
.ob-badges__titulo:hover .ob-badges__carr { border-color: var(--brand-press); }
@media (hover: none) {
  .ob-badges__titulo:hover .ob-badges__nombre,
  .ob-badges__titulo:hover .ob-badges__carr { color: inherit; border-color: var(--text-soft); }
}
/* ═══ MODO OSCURO ═══════════════════════════════════════════════════════════
   El tema lo da el catalogo: la clase html.dark-mode (elegida a mano) o la
   preferencia del sistema giran los tokens --bg/--text/--border/--surface.
   Aqui vive solo el cromo propio del sitio que el catalogo no conoce:
   el logo, el boton del interruptor, los bordes fantasma y la tarjeta de
   conversion. Culquier regla oscura de este sitio va aqui, al final, para no
   competir contra su propio modo claro. */

/* El logo es un PNG OSCURO (medido: brillo medio 80 sobre 903x275). Sobre
   fondo carbon desapareceria: se invierte igual que hace la app en su pie
   (filter: brightness(0) invert(1)). Se cubren los DOS caminos al oscuro —
   el interruptor del sitio y la preferencia del sistema — y forzar claro
   (data-theme="light") lo deja tal cual, sin invertir. */
html.dark-mode .ob-nav__logo,
html.dark-mode .ob-pie__logo,
:root[data-theme="dark"] .ob-nav__logo,
:root[data-theme="dark"] .ob-pie__logo {
  filter: brightness(0) invert(1);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ob-nav__logo,
  :root:not([data-theme="light"]) .ob-pie__logo {
    filter: brightness(0) invert(1);
  }
}

/* Boton de tema: pastilla en la barra, con los mismos tokens que el resto del
   cromo. El icono es un trazo actual (svg), no un emoji: sin color, sin
   tono de piel, nada que se vea distinto entre plataformas. */
/* Boton de tema: al final de la banda, junto al CTA. Pastilla con los mismos
   tokens que el resto del cromo. Los iconos son trazo actual (svg), no
   emoji: sin color, sin tono de piel, nada que se vea distinto entre
   plataformas.

   ICONO, CSS PURO (2026-10-01). El <html> lleva la clase .dark-mode, asi que
   el icono correcto se elige sin una linea de JS: en claro se ve la luna
   (porque el proximo estado es oscuro) y en oscuro se ve el sol. Antes el
   boton dibujaba siempre luna y solo cambiaba el texto, que ademas era
   ambiguo: decia "Claro" estando en oscuro, y "Oscuro" estando en claro —
   el nombre del estado destino, no del actual. */
.ob-tema {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding-inline: var(--space-sm);
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text-soft);
  font: inherit; cursor: pointer;
}
.ob-tema:hover, .ob-tema:focus-visible { color: var(--text); border-color: var(--brand); }
.ob-tema:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.ob-tema__icono { flex: none; }
.ob-tema__icono--sol { display: none; }
.dark-mode .ob-tema__icono--luna { display: none; }
.dark-mode .ob-tema__icono--sol { display: block; }

/* ETIQUETA AL HOVER. Antes el rotulo vivia en el flujo, siempre visible, y
   empujaba la banda: 6 secciones + CTA + "Oscuro" no cabian comodos. Ahora
   flota y no ocupa sitio hasta que se pasa el puntero o se enfoca con el
   teclado — :focus-visible tambien, porque en teclado no hay hover.

   ABAJO, no arriba (2026-10-01). La barra es position: sticky; top: 0, asi
   que un boton pegado al borde superior no tiene nada por encima: la etiqueta
   salia del viewport. MEDIDO: en 1280 y 1024 salia cortada por arriba
   (dentroH = false); en 768 si se veia, porque ahi la nav ocupa dos renglones
   y el boton baja lo suficiente. Abajo siempre hay sitio.

   aria-hidden en el <span>: el nombre real lo da aria-label, que el JS
   reescribe; leerlo dos veces solo molesta al lector de pantalla. */
.ob-tema__label {
  position: absolute; top: calc(100% + 8px); left: 50%;
  transform: translate(-50%, -4px);
  padding: 5px var(--space-xs); border-radius: var(--radius-sm);
  background: var(--text); color: var(--bg);
  font-size: var(--fluid-xs); font-weight: 600; line-height: 1.2;
  white-space: nowrap; pointer-events: none;
  opacity: 0; visibility: hidden;
  transition: opacity .12s ease, transform .12s ease;
}
.ob-tema:hover .ob-tema__label,
.ob-tema:focus-visible .ob-tema__label { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

/* En movil la banda de enlaces es una tira con overflow-x: auto, y una
   etiqueta absoluta dentro se recortaria al borde de la tira. Encoje en un
   telefono no hay hover que valga: se queda con el icono, y aria-label sigue
   diciendo que hace. Por eso la etiqueta se apaga aqui y no en un media
   query de "solo escritorio", para que no reaparezca en tablet. */
@media (max-width: 46rem) {
  .ob-tema__label { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ob-tema__label { transition: none; }
}

/* Fantasmas: en claro el borde --border se ve; en oscuro carbon sobre carbon
   se borra y el boton queda como texto suelto encima de nada. El borde fuerte
   aguanta los dos fondos. */
html.dark-mode .ob .btn.btn--ghost,
:root[data-theme="dark"] .ob .btn.btn--ghost { border-color: var(--border-strong); }

/* La tarjeta de conversion mezcla brand-wash con #fff; en oscuro no puede
   salir casi blanca como en claro. Se rehace sobre la superficie oscura con
   la marca como tinte de baja opacidad. */
html.dark-mode .ob-cta__in,
:root[data-theme="dark"] .ob-cta__in {
  background:
    radial-gradient(90% 160% at 100% 0%, color-mix(in srgb, var(--brand) 26%, transparent) 0%, transparent 58%),
    linear-gradient(160deg, color-mix(in srgb, var(--brand) 13%, var(--surface-2)) 0%, var(--surface-2) 72%);
}

/* ═══════════════════════════════════════════════════════════════════════════
   JARVIS — ronda ventas/conversion (contacto.php, novedades.php, inc/cta.php)
   Bloque nuevo al final, aparte, para no chocar con los 2 agentes que tocan
   index/soluciones/producto/pos/obi/mercado/planes en paralelo.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── BUG REAL CORREGIDO: fondo de pagina transparente en modo oscuro ───────
   MEDIDO al verificar novedades.php en oscuro (y confirmado tambien en
   index.php SIN TOCARLO: no es un bug de esta ronda, es de TODO el sitio).
   El body quedaba con background-color:transparent con data-theme="dark" /
   html.dark-mode activos — el titulo y el texto claro de tema oscuro caian
   sobre el fondo claro real del navegador, casi ilegibles.

   Causa raiz, de dos pasos:
   1) omnis-core.css define `body { background: var(--bg); }` DENTRO de
      `@layer omnis.base`. Una capa SIEMPRE pierde contra una regla sin capa,
      sin importar especificidad ni orden — el mismo patron que ya rompio el
      contraste de claro (ver el otro BUG REAL CORREGIDO, arriba).
   2) dark.css (obox/assets/css_onebox/dark.css, legado, SIN @layer) trae
      `html.dark-mode body { background-color: var(--gray-900); }` y
      --gray-900 no esta definido en este documento (vive en variables.css,
      que esta pagina no carga). Un var() invalido invalida TODA la
      declaracion, que cae a su valor inicial: transparent.
   No se toca dark.css (compartido con la app, fuera de este alcance): se
   repara aqui, al final de la cascada visible de esta pagina, con el token
   real y reactivo. */
html.dark-mode body,
:root[data-theme="dark"] body { background-color: var(--bg); }

/* ── Pasos numerados (contacto.php) — patron Eleventa: "1. 2. 3." literal ──
   Antes el numero iba a mano en un <span class="ob-cmp__si"> pensado para el
   check verde de la comparativa, no para esto. Un circulo con el numero se
   lee como proceso; la lista suelta se leia como un parrafo cortado. */
.ob-pasos { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-md); }
.ob-pasos li { display: grid; grid-template-columns: 2rem 1fr; gap: var(--space-sm); align-items: baseline; }
.ob-pasos__n {
  display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border-radius: 50%;
  background: var(--brand-wash); color: var(--brand-press);
  font-weight: 700; font-size: var(--fluid-sm); line-height: 1;
}

/* ── Encabezado ligero de novedades.php (sin .ob-hero) ─────────────────────
   Mismo criterio que .ob-precios__head en planes.php: un changelog no es la
   pagina donde se necesita el bloque rojo de marca completo. */
.ob-novedades__head { padding-block: var(--space-xl) var(--space-lg); max-width: 46rem; }
.ob-novedades__head h1 { font-size: clamp(1.75rem, 1.3rem + 2vw, 2.75rem); margin: 0 0 var(--space-sm); letter-spacing: -.02em; }
.ob-novedades__lead { color: var(--text-mute); font-size: var(--fluid-md); margin: 0; }

/* ── Eyebrow de la banda de conversion (inc/cta.php) ────────────────────────
   Mismo patron que .ob-plan__ahorro (brand-wash + brand-press, ya probado en
   los dos temas: la marca no cambia con el tema, por eso no necesita guardia
   aparte). Resume la objecion que mas frena antes de que el visitante la
   piense — el mismo gesto que el "kuki-meta" de Papiro sobre su H1. */
.ob-cta__eyebrow {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  margin: 0 0 var(--space-sm);
  padding: 4px 12px; border-radius: 999px;
  background: var(--brand-wash); color: var(--brand-press);
  font-size: var(--fluid-sm); font-weight: 700; letter-spacing: .01em;
}

/* ═══ BLOQUE NUEVO (2026-09-30, marketing de producto: pos/obi/mercado) ═════
   Solo una regla: el "Nuestra propuesta" sobre la columna OneBox de la tabla
   comparativa (mercado.php). Usa .badge/.badge--ok del catalogo (ya pasa
   contraste en los dos temas, igual que el resto del archivo) — esto solo
   separa el badge del nombre con margen, no inventa color nuevo. */
.ob-cmp__ribbon { display: inline-block; margin-bottom: 4px; }
/* ═══ FIN BLOQUE NUEVO ═══════════════════════════════════════════════════ */

/* ═══ TARJETAS DE PRECIO — formato clasico (referencia dada por Gerry,
   2026-10-01): ribbon de precio arriba, nombre del plan en grande, lista
   con check/X, boton redondeado prominente. Reemplaza el formato anterior
   (.ob-plan / .card__head) que se cortaba al forzar todo en una fila. Esto
   NO usa .card ni .card__head: es su propio componente, autonomo, para no
   heredar el display:flex;row que rompio la version anterior. ═══════════ */
.ob-pricing {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl);
  /* stretch (no start): las tarjetas de una fila igualan su altura y el CTA
     queda al fondo — asi una tarjeta con menos bullets no se ve "a medias". */
  align-items: stretch; margin: 0 0 var(--space-lg);
}
.ob-pricing--3 { grid-template-columns: 1fr 1fr 1fr; }
@media (max-width: 960px) { .ob-pricing--3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 760px) { .ob-pricing { grid-template-columns: 1fr; } }
.ob-ptier {
  position: relative; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--space-xl) var(--space-lg) var(--space-lg);
  text-align: center; display: flex; flex-direction: column;
}
.ob-ptier--featured {
  border: 2px solid var(--brand); box-shadow: 0 28px 56px -28px color-mix(in srgb, var(--brand) 45%, transparent);
}
@media (min-width: 760.001px) { .ob-ptier--featured { margin-top: calc(-1 * var(--space-lg)); padding-top: calc(var(--space-xl) + var(--space-lg)); } }
.ob-ptier__badge {
  position: absolute; top: calc(-1 * var(--space-sm)); left: 50%; transform: translateX(-50%);
  /* --brand-press, no --brand: medido 4.06:1 con --brand sobre el texto de la
     insignia (blanco) — falla AA. El bloque de contraste de este archivo
     (arriba) ya dice que para texto normal sobre fondo de marca el token
     correcto es --brand-press: 6.49:1. El ribbon grande queda con --brand
     porque a 22px/800 es texto grande y 3:1 le basta. */
  background: var(--brand-press); color: var(--brand-contrast); font-size: var(--fluid-sm); font-weight: 700;
  padding: 4px 16px; border-radius: 999px; white-space: nowrap;
}
.ob-ptier__badge--ahorro { background: var(--ok-solid); }
.ob-ptier__ribbon {
  display: inline-block; margin: 0 auto var(--space-md); background: var(--ink); color: #fff;
  font-size: 1.4rem; font-weight: 800; padding: 8px 22px; border-radius: var(--radius);
  font-variant-numeric: tabular-nums;
}
.ob-ptier__ribbon small { font-size: .55em; font-weight: 500; opacity: 1; }
.ob-ptier__ribbon--pro { background: var(--brand-press); }
.ob-ptier__nombre {
  margin: 0 0 4px; font-size: 1.7rem; font-weight: 800; letter-spacing: -.01em;
  text-transform: uppercase; color: var(--text-mute);
}
.ob-ptier__nombre--pro { color: var(--brand); }
.ob-ptier__periodo { margin: 0 0 var(--space-lg); color: var(--text-mute); font-size: var(--fluid-sm); }
.ob-ptier__check {
  list-style: none; margin: 0 0 var(--space-md); padding: 0; display: grid; gap: 12px;
  text-align: left; flex: 1 0 auto;
}
.ob-ptier__check li { display: flex; gap: var(--space-xs); align-items: baseline; font-size: var(--fluid-sm); }
.ob-ptier__check li.si::before { content: "✓"; color: var(--ok); font-weight: 900; flex: none; }
.ob-ptier__check li.no { color: var(--text-mute); }
.ob-ptier__check li.no::before { content: "✗"; color: var(--danger); font-weight: 900; flex: none; }
.ob-ptier__cta {
  display: block; width: 100%; margin-top: auto; padding: 14px; border-radius: 999px;
  background: var(--ink); color: #fff; font-weight: 700; text-decoration: none; text-align: center;
  transition: transform var(--t-fast, 120ms) ease;
}
.ob-ptier__cta:hover { transform: translateY(-2px); }
.ob-ptier__cta--pro { background: var(--brand-press); }

/* ── Banda de prueba gratutita (banda fina, no compite con las tarjetas) ──
   Estandar de las paginas de precio reales (Shopify, Google): la prueba
   gratis vive arriba como banda fina; la estrella del bloque son las
   tarjetas de plan, no un "$0" compitiendo contra un "$999". */
.ob-trial {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-md) var(--space-lg); margin: var(--space-lg) 0 var(--space-2xl);
  background: color-mix(in srgb, var(--brand) 7%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--brand) 26%, var(--border));
  border-left: 4px solid var(--brand);
  border-radius: var(--radius-lg); padding: var(--space-md) var(--space-lg);
}
.ob-trial__texto { margin: 0; font-size: var(--fluid-sm); color: var(--text); max-width: 52rem; }
.ob-trial__texto strong { color: var(--brand-press); }
.ob-trial__accion { display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap; }

/* ── Toggle Mensual / Anual (patron Google Workspace / Canva) ────────────
   Control segmentado, centrado, sobre las tarjetas. El estado activo se
   pinta con --brand-press (el token correcto para texto sobre marca: 6.49:1;
   --brand a texto normal falla AA — ver bloque de contraste de este archivo). */
.ob-planfor { position: relative; }
.ob-toggle-wrap {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-sm);
  margin: 0 0 var(--space-2xl);
}
.ob-toggle-wrap::before {
  content: "Elige tu forma de pago";
  font-size: .8125rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-mute);
}
.ob-toggle {
  display: inline-flex; gap: 4px; padding: 5px;
  background: color-mix(in srgb, var(--ink) 6%, var(--surface));
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--ink) 8%, transparent);
}
.ob-toggle__btn {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-mute); border-radius: 12px;
  padding: 10px 22px; min-width: 184px; font: inherit;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  transition: background .18s ease, color .18s ease, box-shadow .18s ease;
}
.ob-toggle__btn:hover { color: var(--text); }
.ob-toggle__t { font-size: var(--fluid-md); font-weight: 700; display: inline-flex; align-items: center; gap: 6px; }
.ob-toggle__d { font-size: .8125rem; font-weight: 500; opacity: .82; }
.ob-toggle__btn.is-activo {
  background: var(--brand-press); color: #fff;
  box-shadow: 0 10px 22px -12px color-mix(in srgb, var(--brand) 75%, transparent);
}
.ob-toggle__btn.is-activo:hover { color: #fff; }
.ob-toggle__btn.is-activo .ob-toggle__d { opacity: .92; }
.ob-toggle__ahorro {
  font-size: .6875rem; font-weight: 700; letter-spacing: .02em; padding: 2px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok);
}
.ob-toggle__btn.is-activo .ob-toggle__ahorro { background: rgba(255,255,255,.24); color: #fff; }

/* El toggle decide que precio/leyenda se ve: todo .ob-pv--* dentro del bloque
   cambia a la vez (precio del liston Y la leyenda de cobro). */
.ob-pv--anual { display: none; }
.ob-planfor.is-anual .ob-pv--mensual { display: none; }
.ob-planfor.is-anual .ob-pv--anual { display: inline; }

/* Leyenda de cobro bajo el nombre: mensual = recurrente; anual = un solo pago. */
.ob-ptier__cobro { margin: 0 0 var(--space-lg); font-size: var(--fluid-sm); color: var(--text-mute); }
.ob-ptier__cobro .ob-pv--mensual { color: var(--text-mute); }
.ob-ptier__cobro .ob-pv--anual { color: var(--ok); font-weight: 600; }

/* Fila-encabezado dentro del checklist ("Todo lo de X, más:"): texto, sin ✓/✗. */
.ob-ptier__check li.ob-ptier__check-h { font-weight: 700; color: var(--text); }
.ob-ptier__check li.ob-ptier__check-h::before { content: none; }

/* ── Ritmo vertical de la tarjeta ────────────────────────────────────────
   Gerry: "los bloques se ven apilados". Se abre aire entre liston, nombre,
   periodo, checklist y CTA para que cada bloque respire. */
.ob-ptier__ribbon { margin-bottom: var(--space-lg); }
.ob-ptier__nombre { margin-bottom: var(--space-xs); }
.ob-ptier__periodo { margin-bottom: var(--space-sm); }
.ob-ptier__check { gap: 14px; margin-bottom: var(--space-lg); }
.ob-plan__mas { margin-bottom: var(--space-lg); }

/* ── Personalizada con color ─────────────────────────────────────────────
   Gerry: "en la Personalizada debes usar mas color". Fondo con lavado de
   marca, ribete de marca y liston de marca; el nombre tambien en marca. El
   Pro sigue siendo el destacado (criado + insignia); la Personalizada se
   distingue por el color, no por altura. */
.ob-ptier--perso {
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--brand) 13%, var(--surface)) 0%,
    color-mix(in srgb, var(--brand) 4%, var(--surface)) 60%);
  border: 1px solid color-mix(in srgb, var(--brand) 38%, var(--border));
}
.ob-ptier--perso .ob-ptier__nombre { color: var(--brand-press); }
.ob-ptier--perso .ob-ptier__periodo { color: color-mix(in srgb, var(--brand-press) 70%, var(--text)); }
.ob-ptier--perso .ob-ptier__ribbon {
  background: linear-gradient(135deg, var(--brand), var(--brand-press));
  color: #fff; border: 0; font-size: var(--fluid-md);
  box-shadow: 0 12px 26px -14px color-mix(in srgb, var(--brand) 70%, transparent);
}
.ob-ptier--perso .ob-ptier__ribbon small { opacity: .9; font-weight: 600; }
.ob-ptier--perso .ob-ptier__check li.si::before { color: var(--brand-press); }
.ob-ptier--perso .ob-ptier__cta { background: var(--brand-press); }
/* ═══ FIN TARJETAS DE PRECIO ═════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════
   ALTA "EMPEZA GRATIS" (empezar-gratis.php) — 2026-10-01
   Bloque al final, como los demas, para no cruzarme con los cambios anteriores.
   ═══════════════════════════════════════════════════════════════════════════ */
.ob-alta { padding: clamp(3rem, 8vw, 5.5rem) var(--space-gutter); }
.ob-alta__caja {
  max-width: 34rem; margin-inline: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: clamp(1.75rem, 5vw, 3rem);
}
/* El "ojo" repite el dato que mas se olvida: que no hay tarjeta. Va antes del
   h1 porque es la condicion, no el titular. */
.ob-alta__ojo {
  display: inline-block; margin: 0 0 1rem;
  background: var(--brand-wash); color: var(--brand-press);
  border-radius: 999px; padding: .35rem .9rem;
  font-size: .82rem; font-weight: 700;
}
.ob-alta__lead { color: var(--text-muted); margin: 0 0 2rem; font-size: 1.05rem; }
.ob-alta__campo { margin-bottom: 1.15rem; }
.ob-alta__campo label {
  display: block; margin-bottom: .4rem;
  font-weight: 700; font-size: .92rem;
}
.ob-alta__campo input {
  width: 100%; padding: .8rem .95rem;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text);
  font: inherit; font-size: 1rem;
}
.ob-alta__campo input:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px; border-color: var(--brand);
}
.ob-alta__enviar { width: 100%; margin-top: .5rem; }
/* El aviso de error se pinta con borde, no solo con color: si alguien no
   distingue el rojo, igual lo lee como bloque. */
.ob-alta__errores {
  border: 1px solid var(--danger, #b91c1c); border-left-width: 4px;
  border-radius: var(--radius-sm); padding: 1rem 1.15rem; margin-bottom: 1.5rem;
  background: color-mix(in srgb, var(--danger, #b91c1c) 8%, transparent);
}
.ob-alta__errores p { margin: 0; }
.ob-alta__errores p + p { margin-top: .4rem; }
.ob-alta__nota {
  margin: 1.75rem 0 0; padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  color: var(--text-muted); font-size: .9rem;
}
@media (prefers-reduced-motion: no-preference) {
  .ob-alta__enviar { transition: background-color .2s ease, transform .2s ease; }
  .ob-alta__enviar:hover { transform: translateY(-2px); }
}

/* 2026-10-07: en movil el documento sangraba 13px a la derecha en index.php
   (la banda .ob-nav__links clipea su contenido, pero ese contenido inflaba el
   scroll del documento igual). clip corta el sangrado; la banda sigue
   scrollandose por dentro. No afecta variantes: ninguna otra pagina usaba
   scroll horizontal del documento. */
html, body { overflow-x: clip; }

/* ── Carrusel de pantallas del hero de portada (2026-10-08) ──────────────
   Las capturas se apilan en la misma celda (misma proporcion 1200x750) y el
   crossfade alterna la clase .is-activa: la saliente se desvanece mientras la
   entrante aparece y se asienta (zoom sutil 1.06 -> 1). La primera se ve igual
   sin JS: el sitio no esconde contenido detras del scripting. */
.ob-hero__carrusel { display: grid; position: relative; }
.ob-hero__carrusel-img {
  grid-area: 1 / 1; opacity: 0; visibility: hidden;
  transform: scale(1.06);
  transition: opacity .9s ease, transform 1.3s ease, visibility 0s linear .9s;
}
.ob-hero__carrusel-img.is-activa {
  opacity: 1; visibility: visible; transform: scale(1);
  transition: opacity .9s ease, transform 1.3s ease;
}
@media (prefers-reduced-motion: reduce) {
  .ob-hero__carrusel-img,
  .ob-hero__carrusel-img.is-activa { transition: none; transform: none; }
}

/* ── CTA: acciones en fila (pills a ancho natural, no barras estiradas) ─
   Los botones de accion NO se estiran en grids de columnas: quedan a su
   ancho de contenido y envuelven sin romper. (2026-10-07 — Gerry: "los
   botones no se parecen a un CTA": el trío de producto llegaba a 1104 px
   porque md:col-span-4 no existe en las utilidades.) */
.ob-cta-fila { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: center; margin-top: var(--space-lg); }
.ob-cta-fila--centrada { justify-content: center; }

/* ── Controles del carrusel del hero: flechas, pie y puntos ───────────── */
.ob-car__btn {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 38px; height: 38px; border: 0; border-radius: 999px; cursor: pointer;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ink) 72%, transparent); color: #fff;
  font-size: 1.15rem; line-height: 1; opacity: .8;
  transition: opacity .15s ease, background .15s ease;
}
.ob-car__btn:hover { opacity: 1; background: var(--ink); }
.ob-car__btn--ant { left: .75rem; }
.ob-car__btn--sig { right: .75rem; }
/* Mensaje cambiante de cada pantalla: se apila en la misma celda del carrusel
   y sube/desvanece junto con la imagen. Reemplaza al viejo pie de una linea.
   Va sobre la captura (que es clara), asi que el degradado oscuro + la sombra
   de texto sostienen la legibilidad sin depender de donde caiga el pixel. */
.ob-car__mensajes { grid-area: 1 / 1; z-index: 2; pointer-events: none; }
.ob-car__mensaje {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0;
  padding: var(--space-2xl) var(--space-lg) var(--space-lg);
  background: linear-gradient(transparent, rgba(0,0,0,.5) 42%, rgba(0,0,0,.8));
  opacity: 0; transform: translateY(10px);
  transition: opacity .55s ease, transform .55s ease;
  text-shadow: 0 1px 4px rgba(0,0,0,.65);
}
.ob-car__mensaje.is-activa { opacity: 1; transform: none; }
.ob-car__mensaje-eyebrow {
  margin: 0 0 .15rem; font-size: var(--fluid-xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--brand-contrast);
}
.ob-car__mensaje-titulo {
  margin: 0; font-size: clamp(1.05rem, .95rem + .55vw, 1.4rem);
  font-weight: 700; line-height: 1.2; color: #fff; max-width: 32ch;
}
.ob-car__puntos { display: flex; gap: .45rem; justify-content: center; margin-top: var(--space-sm); }
.ob-car__punto {
  width: 9px; height: 9px; border-radius: 999px; border: 0; padding: 0; cursor: pointer;
  background: color-mix(in srgb, var(--brand-contrast) 35%, transparent);
  transition: background .15s ease, width .15s ease;
}
.ob-car__punto.is-activa { background: var(--brand); width: 22px; }
@media (prefers-reduced-motion: reduce) {
  .ob-car__btn, .ob-car__punto, .ob-car__mensaje { transition: none; }
}
