@layer xone-reset, xone-base, xone-responsive, xone-layout, xone-components, xone-project;
@layer xone-reset {
  xone-island { display: block; }
  *, *::before, *::after { box-sizing: border-box; }
}
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/Roboto-Regular.ttf') format('truetype');
}
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/Roboto-Medium.ttf') format('truetype');
}
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/Roboto-Bold.ttf') format('truetype');
}
@layer xone-base {
  /* Layout primitives compartidas con el SPA (baselineCss.ts) vía
     @xone-web/css-engine → LAYOUT_PRIMITIVES_CSS (F1 §1.C, fuente única). */
  body { display: flex; flex-direction: column; align-items: stretch; min-height: 100dvh; margin: 0; }

/* El atributo `hidden` debe ganar SIEMPRE: el orchestrator lo togglea para
   `disablevisible` (paridad Android ControlsUtils.isPropertyVisible) y para
   grupos/tabs. Sin `!important`, `.xone-frame { display: flex }` (misma
   especificidad/capa) o `.xone-prop { display: flex }` pisan el `display:none`
   del UA y el elemento "oculto" sigue visible (bug modales flotantes Alivia). */
[hidden] { display: none !important; }

/* ── Canvas — superficie raíz XOne (sistema responsive) ──
   container-type: size (no inline-size) expone cqw (consumido por
   --xone-p-unit en responsive) y cqh. El cqh es la referencia de ALTURA raíz
   (= la pantalla): un frame de nivel superior con height N% emite Ncqh y
   resuelve contra el alto del canvas (paridad ContentFramePage.java:344-345). */
.xone-canvas {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    flex: 1 1 auto;
    /* ALTURA DEFINIDA = la pantalla (paridad Android: el root es el screen).
       Debe ser height (no solo min-height): las cascadas height:N% de los
       frames de nivel superior necesitan un alto de padre DEFINIDO para
       resolver — min-height NO cuenta como definido para % en CSS, así que
       sin esto toda la cadena height:100% colapsaba a la altura del contenido
       (bug paridad login/inicio 2026-05-30). dvh es absoluto (relativo al
       viewport), así que es definido sin depender de <body>. El scroll de
       contenido que desborda va en frames con scroll="true", igual que Android. */
    height: 100dvh;
    min-height: 100dvh;
    width: 100%;
    /* RECORTE A PANTALLA (paridad Android): el View system recorta los hijos a
       los bounds del padre (clipChildren default) y la pantalla NUNCA crece —
       el contenido que excede solo scrollea dentro de frames scroll="true"
       (ContentFramePage.java:344-353 dimensiona el root frame a
       nParentWidth/nParentHeight de la pantalla). Sin esto, un árbol cuyo
       diseño suma >100dvh (p.ej. Inicio mRed: header 8cqh + frmGeneral2 98cqh
       + márgenes) extiende el scrollHeight del body (1027px vs 900 de la
       referencia). */
    overflow: clip;
    container-type: size;
    container-name: xoneCanvas;
}

/* Modo responsive base-FIJA (data-responsive="1" = web-resolution-width > 0).
   Tokens responsive: 1p = (1/base-width)*100cqw; 1 fontsize = font-factor * idem.
   La base la hornea el generador en --xone-base-width.

   Modo responsive base-AUTO (data-responsive="auto" = sin resolución declarada):
   NO se declara --xone-p-unit aquí (la base es desconocida pre-JS). El
   canvasSizer mide el tamaño natural del contenido, fija --xone-base-width y
   sobrescribe --xone-p-unit/--xone-font-unit inline. Sin JS, var(...,1px) cae a
   1px = 1p=1px = tamaño natural del diseño (sin zoom). */
.xone-canvas[data-responsive="1"] {
    min-height: 100dvh;
    --xone-p-unit: calc(100cqw / var(--xone-base-width));
    --xone-font-unit: calc(var(--xone-font-factor) * 100cqw / var(--xone-base-width));
}
/* Modos de canvas y alineación: aplican a ambos modos responsive (la base
   --xone-base-width que necesitan fixed/letterbox la hornea el generador en
   "1" y la fija el canvasSizer tras medir en "auto"). */
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-mode="fluid"] {
    width: min(100vw, var(--xone-canvas-max-width, 540px));
    align-self: center;
}
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-mode="viewport"] {
    width: 100vw;
    max-width: none;
    align-self: stretch;
}
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-mode="fixed"] {
    width: calc(var(--xone-base-width) * 1px);
    height: calc(var(--xone-base-height) * 1px);
    flex: 0 0 auto;
    min-height: 0;
    align-self: center;
}
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-mode="letterbox"] {
    aspect-ratio: var(--xone-base-width) / var(--xone-base-height);
    width: min(100vw, calc(100dvh * var(--xone-base-width) / var(--xone-base-height)));
    /* height:auto deja que aspect-ratio gobierne el alto (la base ahora fija
       height:100dvh, que rompería la relación de aspecto si no se neutraliza). */
    height: auto;
    flex: 0 0 auto;
    min-height: 0;
    align-self: center;
}
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-align="left"]   { align-self: flex-start; }
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-align="right"]  { align-self: flex-end; }
.xone-canvas:is([data-responsive="1"],[data-responsive="auto"])[data-canvas-align="center"] { align-self: center; }

/* ── Coll / Group / Frame: flex column por defecto ── */
.xone-coll, .xone-group, .xone-frame {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}
/* La coll = la pantalla (no puede crecer más allá del canvas): height definido
   en vez de min-height para que la página mida exactamente la pantalla y el
   scroll viva en los frames scroll="true" (overflow:auto emitido por el
   lowering, visualStyle.ts:332-333 / applyVisualAttributes §1b), igual que en
   Android, donde nada se pinta fuera de pantalla. min-height:100% (anterior)
   dejaba crecer la coll y extendía el body (divergencia docHeight 1027 vs 900). */
.xone-coll { flex: 1 1 auto; height: 100%; min-height: 0; }
/* Group raíz = MATCH_PARENT (paridad EditGroupView.java:104). Los groups
   inactivos (tabs) van con [hidden]/display:none y no consumen flex. */
.xone-coll > .xone-group { flex: 1 1 auto; min-height: 0; }
/* position: relative para floating labels / overlays anclados al frame. */
.xone-frame { min-height: 0; position: relative; }

/* ── Row wrapper: agrupa hijos consecutivos con newline="false" ── */
.xone-row {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    align-self: stretch;
    width: 100%;
    gap: 0;
    flex: 0 0 auto;
}
/* La fila única de un group crece a su contenido (canónico: 1 1 auto). */
.xone-group > .xone-row { flex: 1 1 auto; }
/* La row única de un frame con alto explícito ocupa todo ese alto. */
.xone-frame[style*="height"] > .xone-row:only-child { flex: 1 1 auto; }
/* …salvo que el frame declare un align vertical: entonces el bloque de líneas
   NO debe estirarse a todo el alto, sino conservar su tamaño para que el
   posicionamiento vertical (top/center/bottom) lo coloque dentro del alto
   sobrante. Paridad Android EditFramePage.java:936 (vMainView.setGravity(sAlign)
   sobre LinearLayout VERTICAL: posiciona el bloque de líneas, no lo estira).
   P0-B: se pasa del frágil [style*="justify-content"]:only-child (que fallaba
   con varias filas y dependía del substring inline) a [data-xone-align*=...]
   emitido por frameRenderer, válido con N filas. */
.xone-frame[data-xone-align*="top"]    > .xone-row,
.xone-frame[data-xone-align*="bottom"] > .xone-row,
.xone-frame[data-xone-align*="center"] > .xone-row { flex: 0 0 auto; }
/* …EXCEPCIÓN: si el único contenido de la fila es una imagen full-bleed
   (cover/fill = imagen de fondo, p.ej. el fondo del login con width:100%
   height:100%), esa imagen es MATCH_PARENT y DEBE llenar el alto ignorando la
   gravity (en Android un hijo MATCH_PARENT llena; la gravity solo posiciona
   hijos WRAP_CONTENT). La fila vuelve a 1 1 auto para que height:100% de la
   imagen resuelva al alto del frame. Las imágenes contain (keep-aspect, p.ej.
   un logo) NO entran aquí → siguen centradas por gravity. */
.xone-frame[data-xone-align] > .xone-row:has(> img[style*="object-fit: fill"]:only-child),
.xone-frame[data-xone-align] > .xone-row:has(> img[style*="object-fit: cover"]:only-child) { flex: 1 1 auto; }
/* Frame de alto completo dentro de una fila horizontal: el ALTO lo da la
   resolución height:100% (ahora que el canvas tiene alto definido) o el
   align-items:stretch de la fila; el ANCHO lo da su atributo width (40%/60%…).
   ANTES esto era flex:1 1 0, que en una fila reparte el eje principal
   (ANCHO) a partes iguales con basis 0 → aplastaba el width% (p.ej. 40/60 se
   volvía 50/50). flex:0 0 auto respeta el width inline (bug 2026-05-30). */
.xone-row > .xone-frame[style*="height: 100%"] { flex: 0 0 auto; }

/* ── Prop: bloque vertical (stack), ocupa ancho del padre por defecto ── */
.xone-prop { display: flex; flex-direction: column; width: 100%; min-height: 0; }
.xone-prop-tl { display: block; width: 100%; }

/* ── Pestañas de grupos (F2): modelo "un grupo visible" ──
   El chasis (un <xone-island>) es flex column: [barra 0 0 auto][grupos 1 1 auto].
   La barra se omite en SSR si hay <2 grupos normales o notab (R1). El JS solo
   conmuta hidden/aria-selected; el layout y el primer grupo funcionan sin JS. */
.xone-tabs-chassis { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.xone-tabs {
    display: flex; flex-direction: row; flex: 0 0 auto;
    overflow-x: auto; scrollbar-width: thin;
    background: var(--xone-tab-bg, transparent);
}
.xone-tabs[data-tab-orientation="bottom"] { order: 2; }
.xone-tab {
    /* FIX-6: WRAP_CONTENT empaquetado a la izquierda — un prop/botón sin
       width jamás recibe MATCH_PARENT ni reparto implícito (paridad
       EditFramePage.java:1042: lp WRAP_CONTENT; misma regla que imágenes
       XOneImageView.java:271-281). Antes: flex 1 1 0 (reparto equitativo
       no canónico que estiraba cada tab a 1/N del ancho). */
    flex: 0 0 auto;
    min-height: var(--xone-tab-height, auto);
    font-size: var(--xone-tab-fontsize, inherit);
    background: transparent;
    color: var(--xone-tab-fg, inherit);
    border: 0; border-bottom: 2px solid transparent;
    padding: 0.6em 1em; cursor: pointer; white-space: nowrap;
    font-family: inherit;
}
/* Si el proyecto declara tab-width, las pestañas pasan a ancho fijo + scroll. */
.xone-tabs[style*="--xone-tab-width"] .xone-tab { flex: 0 0 var(--xone-tab-width); }
.xone-tab[aria-selected="true"] {
    color: var(--xone-tab-fg-selected, var(--xone-tab-fg, inherit));
    border-bottom-color: var(--xone-tab-indicator, currentColor);
}
.xone-tab:focus-visible { outline: 2px solid var(--xone-tab-indicator, currentColor); outline-offset: -2px; }
.xone-groups { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.xone-groups > .xone-group { flex: 1 1 auto; min-height: 0; }
.xone-group[hidden] { display: none; }

/* ── Grupos fijos (F3): bandas ancladas que restan espacio al centro ──
   El chasis es flex: las bandas (flex:0 0 auto) miden su contenido (WRAP) y el
   centro (flex:1 1 auto) ocupa el resto. Reproduce "pantalla − fijos − tabs" sin
   position:fixed. Banda oculta (disablevisible) → 0 espacio. */
.xone-coll-mid { display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
.xone-coll-center { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; }
.xone-fixed { display: flex; flex-direction: column; flex: 0 0 auto; }
.xone-fixed-left, .xone-fixed-right { min-width: 0; }
.xone-fixed[hidden] { display: none; }
/* Banda con un grupo fixed de altura de relleno (height=100% / -1). El emisor
   (fixedGroupsRenderer) le pone esta clase para que la banda CREZCA en vez de
   medir contenido: así el height:100% del grupo resuelve contra una caja con
   alto real y no colapsa a 0. Paridad Android "fixed group = alto disponible"
   (EditViewHyper.createFixedGroup mide contra getMaxScreenHeight()). Caso raro:
   comparte el espacio del coll con el centro en vez de taparlo por completo. */
.xone-fixed-fill { flex: 1 1 auto; min-height: 0; }

/* ── Grupos drawer (F4): panel off-canvas + scrim ──
   El contenido principal NO se desplaza (el drawer va position:fixed por encima).
   El JS solo togglea .is-open; el slide es por transform. width por grupo via
   --xone-drawer-width (default 100% = pantalla completa, paridad doc 01 §2.4.2). */
.xone-drawer {
    position: fixed; top: 0; bottom: 0; z-index: 1000;
    width: var(--xone-drawer-width, 100%); max-width: 100%;
    display: flex; flex-direction: column;
    background: #fff; will-change: transform;
    transition: transform 0.25s ease; overscroll-behavior: contain;
}
.xone-drawer-left  { left: 0;  transform: translateX(-100%); }
.xone-drawer-right { right: 0; transform: translateX(100%); }
.xone-drawer.is-open { transform: translateX(0); }
.xone-drawer[hidden] { display: none; }
.xone-drawer > .xone-group { flex: 1 1 auto; min-height: 0; }
.xone-drawer-scrim {
    position: fixed; inset: 0; z-index: 999;
    background: rgb(0 0 0 / 0.32);
    opacity: 0; transition: opacity 0.25s ease;
}
.xone-drawer-scrim.is-open { opacity: 1; }
.xone-drawer-scrim[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) {
    .xone-drawer, .xone-drawer-scrim { transition: none; }
}

  /* MPA: fuente por defecto del canvas (en el SPA la pone <body>). */
  .xone-canvas { font-family: 'Roboto', sans-serif; }

  /* Island = el box visible del control (paridad Android EditText/Button).
     :where() da especificidad (0,0,1) para que las clases del proyecto
     en @layer xone-project ganen sin esfuerzo. overflow:hidden evita que
     las esquinas del input rectangular asomen del border-radius. */
  /* text-border-*: el borde del SUBVIEW de texto (EditText en Android) SOLO
     aplica a props de entrada de texto editables (T, N, N#, X, D, DT). El
     css-engine traduce text-border / text-border-lado / text-border-color /
     text-border-width a custom properties (--xone-text-border*) que SOLO esta
     regla consume; en el resto de props (B, IMG, NC, TL, ...) quedan inertes y
     no pintan borde (paridad XoneCSSTextBox: text-border es del EditText, no del
     frame — el frame usa border-* sin prefijo).
     Default text-border=true → solid (XoneCSSTextBox.java:132). Anchura por
     defecto 1px (no medium/3px del navegador), ampliable con text-border-width o
     border-width. Color por defecto currentColor. :where() da especificidad
     (0,0,1) para que el CSS del proyecto en @layer xone-project gane. */
  xone-island:where(.xone-prop-t, .xone-prop-x,
    .xone-prop-n, .xone-prop-n1, .xone-prop-n2, .xone-prop-n3,
    .xone-prop-n4, .xone-prop-n5, .xone-prop-n6,
    .xone-prop-d, .xone-prop-dt) {
    border-style:
      var(--xone-text-border-top, var(--xone-text-border, solid))
      var(--xone-text-border-right, var(--xone-text-border, solid))
      var(--xone-text-border-bottom, var(--xone-text-border, solid))
      var(--xone-text-border-left, var(--xone-text-border, solid));
    border-width: var(--xone-text-border-width, 1px);
    border-color: var(--xone-text-border-color, currentColor);
    /* Color/bg del subview de texto (EditText): SOLO en inputs editables.
       Fallback inherit/revert-layer: sin text-* el input usa el color del
       control (forecolor) y su bg normal. El input interno hereda. */
    color: var(--xone-text-forecolor, inherit);
    background-color: var(--xone-text-bgcolor, revert-layer);
  }

  /* Inner controls = llenan el island, heredan todo. NO contribuyen
     bordes/padding → una sola fuente visual (el island wrapper). */
  xone-island input, xone-island textarea, xone-island select {
    width: 100% !important; height: 100%;
    padding: 0; border: none; border-radius: 0;
    background: transparent; color: inherit; font-family: inherit;
    font-size: inherit; outline: none;
    box-sizing: border-box;
  }
  /* Reset del <button> generado. Incluye TANTO los botones interactivos
     (envueltos en <xone-island class=xone-prop-b>) COMO los estáticos
     (envueltos en <div class=xone-prop-b>, p.ej. los botones de orden del header
     de contents). Sin esto, un <button> sin clase de fondo (.botonOrden solo
     declara width/height/keep-aspect-ratio) muestra el CHROME NATIVO del
     navegador: borde "2px outset", fondo gris "#efefef" y padding "1px 6px" =
     un recuadro alrededor del icono. Paridad XOne: el boton no pinta borde ni
     fondo salvo que su CSS lo declare (y eso vive en el wrapper). */
  .xone-prop-b > button {
    appearance: none; -webkit-appearance: none; width: 100%; height: 100%;
    padding: 0; border: none; border-radius: 0; margin: 0;
    background: transparent; color: inherit; font-family: inherit;
    font-size: inherit; cursor: pointer; outline: none;
    display: flex; align-items: center; justify-content: center;
  }

  /* Checkbox / radio (NC). El reset "xone-island input { width:100% !important }"
     de arriba estira el check a todo el ancho del island (p.ej. 11%) y su label
     cae a la linea siguiente. Paridad Android XOneCheckBox: el control es un
     CheckBox de tamano fijo con el texto AL LADO (a la derecha), centrado en
     vertical, en una sola Line (ControlsUtils/EditFramePage). Reproducimos:
     el <label> es un flex-row (check + texto), y el check recupera su tamano
     intrinseco (18px) con override de mayor especificidad que gana al reset. */
  .xone-prop-nc > label {
    display: flex; align-items: center; gap: 8px;
    width: 100%; cursor: pointer;
  }
  xone-island.xone-prop-nc input[type="checkbox"],
  xone-island.xone-prop-nc input[type="radio"] {
    width: 18px !important; height: 18px;
    min-height: 0; padding: 0; margin: 0; flex: 0 0 auto;
    accent-color: currentColor;
  }
  .xone-prop-nc .xone-checkbox-label { flex: 0 1 auto; min-width: 0; }

  /* Password toggle — position absolute dentro del field-floating.
     Selector explícito para que no lo machaque ningún reset de button. */
  .xone-password-toggle {
    position: absolute; right: 0.3em; top: 50%; transform: translateY(-50%);
    width: auto; height: auto;
    background: transparent; border: none; cursor: pointer; color: #9AA0A6;
    padding: 0.3em; border-radius: 50%; line-height: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .xone-password-toggle svg { width: 1.4em; height: 1.4em; display: block; }
  .xone-password-toggle:hover { color: #2D3436; }
  /* Foco: SIN halo. El canal clasico XOne no pinta estilo de foco propio
     (referencia mRed: toda la cadena input->container->prop->row computa
     box-shadow:none en focus; el indicador es el underline constante).
     Paridad Android: EditText tampoco dibuja halo (G1 iter3). */

  /* Disabled state — custom properties from XOne CSS classes.
     revert-layer fallback: when no disabled color is declared, the property
     reverts to the previous @layer value instead of going IACVT → unset
     (which would reset background-color to transparent, breaking inputs
     that have text-bgcolor set). */
  .xone-prop input:disabled,
  .xone-prop textarea:disabled,
  .xone-prop select:disabled {
    background-color: var(--xone-text-bgcolor-disabled, var(--xone-bgcolor-disabled, revert-layer));
    color: var(--xone-text-forecolor-disabled, var(--xone-forecolor-disabled, revert-layer));
  }
  .xone-prop-b.xone-disabled {
    background-color: var(--xone-bgcolor-disabled, revert-layer);
    color: var(--xone-forecolor-disabled, revert-layer);
  }

  /* Focus background (text input) */
  .xone-prop input:focus, .xone-prop textarea:focus {
    background-color: var(--xone-text-bgcolor-focus, var(--xone-bgcolor-focus, revert-layer));
  }

  /* Pressed/active state */
  .xone-prop-b:active {
    background-color: var(--xone-bgcolor-pressed, revert-layer);
    color: var(--xone-forecolor-pressed, revert-layer);
  }

  /* Lines clamp via custom property */
  .xone-prop-tl .xone-label-text {
    -webkit-line-clamp: var(--xone-lines, unset);
  }

  /* Ripple CSS approximation */
  [style*="--xone-ripple"] { position: relative; overflow: hidden; }
  [style*="--xone-ripple"]::after {
    content: ''; position: absolute; inset: 0;
    border-radius: inherit; background: currentColor;
    opacity: 0; transition: opacity 0.2s ease; pointer-events: none;
  }
  [style*="--xone-ripple"]:active::after { opacity: 0.12; }

  /* Buttons */
  .xone-prop-b {
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; border: none; background: transparent;
    color: inherit; font-family: inherit; font-size: inherit;
    padding: 0; min-width: 0;
  }

  /* Vertical center */
  .xone-prop.xone-prop-centered-y {
    display: flex; flex-direction: column; justify-content: center;
  }

  /* ── Imágenes (prop IMG/PH) ──
     En el MPA el <img> ES el prop (lleva class xone-prop-img y el width/height
     inline de los atributos XOne). object-fit lo emite renderImage inline
     (contain por defecto = ScaleType.FIT_CENTER; cover si keep-aspect-ratio +
     scale-type=center_crop, XOneImageView.java:266-269). Aquí solo se garantiza
     que el <img> no desborde su caja: max-* al 100% y display:block. NO se fuerza
     width/height (los pone el inline) para respetar keep-aspect (eje libre auto). */
  img.xone-prop-img, img.xone-prop-ph {
    max-width: 100%; max-height: 100%;
    object-position: center;
    display: block;
  }

  /* Imagen DENTRO de un botón (<prop type=B img="...">): el <img class=xone-btn-img>
     va directo en el <button> (flex centrado). Sin acotar, el navegador lo pinta
     a su tamaño natural (p.ej. 144×144) y DESBORDA la caja del botón (p.ej. 21×26),
     solapando elementos vecinos. Acotamos al 100% de la caja con object-fit:contain
     = ScaleType.FIT_CENTER por defecto del XOneButton (XOneButton.java:706-749). */
  img.xone-btn-img {
    max-width: 100%; max-height: 100%;
    object-fit: contain; object-position: center;
    display: block;
  }

  /* Placeholder de imagen sin recurso (src vacío o inexistente). Reserva la
     caja (width/height heredados del inline del prop) sin glifo ni fondo.
     Paridad Android XOneImageView.java:479 (path vacío → no se pinta imagen,
     se conserva la LayoutParams). NO debe pintar borde, fondo ni texto. */
  span.xone-img-empty {
    display: inline-block;
    background: none; border: none;
    overflow: hidden;
    flex: 0 0 auto;
  }

  /* Line clamp */
  .xone-prop-tl .xone-label-text.xone-label-clamp {
    display: -webkit-box; -webkit-box-orient: vertical;
    overflow: hidden; text-overflow: ellipsis;
  }

  /* Floating labels.
     SIN padding propio: Android pone el EditText a padding 0 por defecto
     (XOneEditText.java:752 vTextView.setPadding(0,0,0,0)); solo lpadding/
     tpadding... explícitos del prop añaden inset (ControlsUtils.
     setInsidePadding, XOneEditText.java:896-908) y esos ya se loweran aparte.
     El padding 0.4em/0.5em anterior era un invent del template (offset 4px/5px
     vs la referencia). */
  .xone-field-floating {
    position: relative; display: flex; flex-direction: column;
    justify-content: center;
    height: 100%; font-size: inherit; padding: 0;
  }
  .xone-field-floating input,
  .xone-field-floating textarea {
    width: 100%; background: transparent; border: none;
    padding: 0; font-family: inherit; font-size: 1em; color: inherit;
    outline: none;
  }
  /* Label en REPOSO = mismo origen tipográfico que el texto del campo (1em):
     en Android el hint expandido se pinta al text size del EditText. */
  .xone-floating-label {
    position: absolute; left: 0; top: 50%; transform: translateY(-50%);
    pointer-events: none; transition: all 0.15s ease;
    font-size: 1em; opacity: 0.5; line-height: 1;
  }
  /* Label COLAPSADO: hintTextAppearance Material por defecto = 12sp, que XOne
     NUNCA sobreescribe (XoneCSS.java:1034-1036 setHintEnabled+setHint sin
     tocar el appearance). 12sp ≈ 12px legacy (1p=1px); en responsive escala
     con --xone-font-unit. El 0.75em anterior daba 7.5px con fontsize 10. */
  .xone-field-floating input:focus + .xone-floating-label,
  .xone-field-floating input:not(:placeholder-shown) + .xone-floating-label,
  .xone-field-floating textarea:focus + .xone-floating-label,
  .xone-field-floating textarea:not(:placeholder-shown) + .xone-floating-label {
    top: 0.15em; transform: translateY(0);
    font-size: calc(12 * var(--xone-font-unit, 1px)); opacity: 1;
    color: var(--xone-floating-label-color, currentColor);
  }
  /* Autocompletado del navegador: cuando Chrome/Safari rellenan usuario/contraseña
     el campo tiene valor pero (sin interacción del usuario) NO siempre dispara
     :not(:placeholder-shown), así que la etiqueta se quedaba en reposo (centrada)
     y el texto autocompletado se le superponía. Subimos la etiqueta también con
     :-webkit-autofill/:autofill (regla APARTE para que, si un motor no conociera
     :autofill, no invalide la regla principal de focus/valor). */
  .xone-field-floating input:-webkit-autofill + .xone-floating-label,
  .xone-field-floating input:autofill + .xone-floating-label {
    top: 0.15em; transform: translateY(0);
    font-size: calc(12 * var(--xone-font-unit, 1px)); opacity: 1;
    color: var(--xone-floating-label-color, currentColor);
  }
  /* Neutraliza el fondo amarillo/azul y el color de texto que el autofill fuerza,
     para conservar el input transparente del diseño XOne (el color lo aporta el
     frame de detrás). El delay enorme de transición evita el repintado del fondo
     nativo (truco estándar para autofill transparente). */
  .xone-field-floating input:-webkit-autofill,
  .xone-field-floating input:autofill {
    -webkit-text-fill-color: inherit;
    caret-color: inherit;
    transition: background-color 9999s ease-in-out 0s;
  }

  /* Password toggle — el padding-right solo cuando el toggle existe (opt-in
     vía show-password-visibility-toggle, Utils.java:558 + XOneEditText.java:684). */
  .xone-field { position: relative; display: flex; align-items: center; height: 100%; }
  .xone-field input { flex: 1 1 auto; }
  .xone-field:has(.xone-password-toggle) input,
  .xone-field-floating:has(.xone-password-toggle) input { padding-right: 2.5em; }
  .xone-password-toggle {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    background: transparent; border: none; cursor: pointer; color: #9AA0A6;
    padding: 6px; border-radius: 50%; line-height: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .xone-password-toggle svg { width: 20px; height: 20px; display: block; }
  .xone-password-toggle:hover { color: #2D3436; }
  .xone-password-toggle.is-visible { color: #2D3436; }

  /* Contents (prop type="Z") — layout aplicado por islands-runtime */
  .xone-prop-z { position: relative; min-height: 0; }
  /* Paridad Android: un contents NUNCA pinta fuera de su contenedor.
     ViewGroup clipea los hijos a sus bounds (clipChildren=true default) y el
     RecyclerView se mide con AT_MOST(alto disponible del padre) — como máximo
     ocupa el alto del padre y scrollea internamente si el contenido es mayor
     (XOneContentRecyclerView dentro del frame, EditFramePage). En web: el
     island clipea (overflow hidden) y el div interior llena la caja y
     scrollea en vertical. Para height="-2" (wrap content) la cadena
     .xone-row/min-height:0 + max-height:100% reproduce el AT_MOST cuando el
     padre tiene alto definido. La orientación horizontal re-fija
     overflow-y:hidden inline en applyContentsLayout (gana a esta regla). */
  xone-island[data-type="Z"] { max-height: 100%; min-height: 0; overflow: hidden; }
  xone-island[data-type="Z"] > .xone-prop-z {
    height: 100%; max-height: 100%; overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  .xone-row { min-height: 0; }
  .xone-row:has(> xone-island[data-type="Z"]) { max-height: 100%; }
  /* Cabecera de columnas de un contents (header-contents="Coll", extensión
     web-*). El wrapper apila en columna: banda de cabecera (alto natural, sin
     crecer ni scrollear) + island (rellena el resto y scrollea). Las celdas de
     la cabecera heredan anchos/labels de la colección referenciada, alineando
     con las columnas de las filas de datos. */
  .xone-contents-headed { box-sizing: border-box; min-height: 0; }
  .xone-row:has(> .xone-contents-headed) { max-height: 100%; }
  .xone-contents-header { flex: 0 0 auto; overflow: hidden; width: 100%; }
  .xone-contents-header > .xone-frame { width: 100%; }
  /* FIX-5: SIN gap entre celdas — Android no añade separación entre las
     Lines de un contents (empaquetan contiguas, EditFramePage.java:967-975);
     la separación visual la dan los margins/paddings del propio item. */
  .xone-contents { display: flex; flex-direction: column; gap: 0; width: 100%; }
  .xone-contents.is-gridview { display: grid; gap: 0; }
  .xone-contents.is-recyclerview { display: flex; gap: 0; }
  .xone-contents[data-orientation="horizontal"] {
    flex-direction: row; overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  }
  .xone-contents[data-orientation="horizontal"] > .xone-content-item {
    width: auto; flex: 0 0 auto; scroll-snap-align: start;
  }
  .xone-content-item {
    display: flex; flex-direction: column; width: 100%;
    cursor: pointer; transition: filter 0.1s ease;
  }
  .xone-content-item:hover { filter: brightness(0.97); }
  /* Frame raíz de un item de contents = MATCH_PARENT por defecto (paridad
     Android: la fila de una lista llena el ancho del list). Sin esto, un
     frmContent SIN width explícito (o con un typo tipo xwidth="100%") colapsa
     a 0 por su container-type:size + flex 0 0 auto → los props se solapan. Un
     width inline del proyecto (p.ej. 96%) gana por especificidad y preserva el
     inset. En listas horizontales el item es width:auto → el frame no se fuerza. */
  .xone-contents:not([data-orientation="horizontal"]) > .xone-content-item > .xone-frame { width: 100%; }
  .xone-contents-empty {
    color: #9AA0A6; text-align: center; padding: 16px; font-size: 12px;
  }

  /* ── Gráficos (pie/ring/line/bar) renderizados en SVG por el runtime ──
     El runtime pinta el SVG + leyenda dentro del .xone-contents (que ya lleva
     el alto del prop chart, p.ej. 200p). Pie/ring = SVG + leyenda en fila;
     line/bar = SVG arriba + leyenda debajo. */
  /* El chart llena la CAJA DEL PROP (paridad XOnePieChartView.java:239-256
     refreshSize → LayoutParams nWidth/nHeight del prop): height:100% define la
     cadena island(caja del prop)→container→SVG. El antiguo max-width:52% (que
     capaba el pie a media columna) y la altura indefinida del container
     producían el cuadrado 113×113 en vez de llenar 98%×200p. */
  .xone-contents.xone-chart { gap: 8px; overflow: hidden; padding: 4px; align-items: stretch; height: 100%; }
  .xone-contents.xone-chart-row { flex-direction: row; align-items: center; }
  .xone-contents.xone-chart-col { flex-direction: column; }
  .xone-chart-svg { display: block; }
  .xone-chart-row > .xone-chart-svg { height: 100%; width: auto; flex: 0 0 auto; }
  .xone-chart-col > .xone-chart-svg { width: 100%; flex: 1 1 auto; min-height: 0; }
  .xone-chart-axis { font-size: 9px; fill: #777777; }
  .xone-chart-legend {
    list-style: none; margin: 0; padding: 0; font-size: 11px; overflow: auto;
    display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0;
  }
  .xone-chart-col > .xone-chart-legend {
    flex-direction: row; flex-wrap: wrap; justify-content: center;
    gap: 2px 12px; flex: 0 0 auto;
  }
  /* Leyenda del pie SUPERPUESTA al chart, esquina superior-izquierda —
     convención del canal clásico web XOne (referencia mRed, G7 iter3). */
  .xone-contents.xone-chart-row { position: relative; }
  .xone-chart-legend.xone-chart-legend-overlay {
    position: absolute; top: 6px; left: 6px;
    flex: none; max-width: 70%; max-height: calc(100% - 12px);
  }
  .xone-chart-legend-item { display: flex; align-items: center; gap: 5px; line-height: 1.4; }
  .xone-chart-swatch { width: 11px; height: 11px; border-radius: 2px; flex: 0 0 auto; }
  .xone-chart-legend-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .xone-chart-row .xone-chart-legend-label { flex: 1 1 auto; min-width: 0; }
  .xone-chart-legend-value { font-weight: 600; margin-left: auto; padding-left: 6px; }
}

@layer xone-components {
  @keyframes xone-fadeIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes xone-fadeOut { from { opacity: 1; } to { opacity: 0; } }
  @keyframes xone-slideInRight { from { transform: translateX(100%); } to { transform: translateX(0); } }
  @keyframes xone-slideInLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } }
  @keyframes xone-slideOutRight { from { transform: translateX(0); } to { transform: translateX(100%); } }
  @keyframes xone-slideOutLeft { from { transform: translateX(0); } to { transform: translateX(-100%); } }
  @keyframes xone-slideInUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
  @keyframes xone-slideOutUp { from { transform: translateY(0); } to { transform: translateY(-100%); } }
  @keyframes xone-slideInDown { from { transform: translateY(-100%); } to { transform: translateY(0); } }
  @keyframes xone-slideOutDown { from { transform: translateY(0); } to { transform: translateY(100%); } }
  @keyframes xone-zoomIn { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
  @keyframes xone-zoomOut { from { transform: scale(1); opacity: 1; } to { transform: scale(0); opacity: 0; } }
  @keyframes xone-rotate3dIn { from { transform: perspective(400px) rotateY(90deg); opacity: 0; } to { transform: perspective(400px) rotateY(0); opacity: 1; } }
  @keyframes xone-rotate3dOut { from { transform: perspective(400px) rotateY(0); opacity: 1; } to { transform: perspective(400px) rotateY(90deg); opacity: 0; } }
}
@layer xone-project {
.xone-coll {
    --xone-cell-border: false;
    --xone-cell-border-width: 0;
    --xone-cell-tpadding: 2px;
    --xone-cell-bpadding: 2px;
}

.xone-prop {
    --xone-text-border-top: none;
    --xone-text-border-bottom: solid;
    --xone-text-border-right: none;
    --xone-text-border-left: none;
    --xone-grid-text-border: false;
    font-weight: normal;
    font-size: 15px;
    --xone-labelfont-size: 7;
    margin-left: 0px;
    --xone-img-spinner: bt_Arrow_down.png;
    --xone-img-spinner-sel: bt_Arrow_down_Sel.png;
    --xone-img-search: bt_Lupa.png;
    --xone-img-search-sel: bt_Lupa_sel.png;
    --xone-img-delete: bt_Delete.png;
    --xone-img-delete-sel: bt_Delete_sel.png;
    --xone-img-undo: undo.png;
    --xone-img-undo-sel: undo_click.png;
    --xone-img-phone: bt_Phone.png;
    --xone-img-phone-sel: bt_Phone_sel.png;
    --xone-img-date: bt_Date.png;
    --xone-img-date-sel: bt_Date_sel.png;
    --xone-img-time: bt_Time.png;
    --xone-img-time-sel: bt_Time_sel.png;
    --xone-img-checked: bt_check.png;
    --xone-img-checked-disabled: bt_check_disabled.png;
    --xone-img-unchecked: bt_uncheck.png;
    --xone-img-unchecked-disabled: bt_uncheck_disabled.png;
    --xone-img-att: bt_attach.png;
    --xone-img-att-sel: bt_attach_sel.png;
    --xone-img-camera: bt_camera.png;
    --xone-img-camera-sel: bt_camera_sel.png;
    --xone-img-video: bt_camera.png;
    --xone-img-video-sel: bt_camera_sel.png;
    --xone-img-height: 28;
    --xone-img-width: 28;
}

.darkColorColl {
    --xone-cell-border: false;
    --xone-cell-border-width: 0;
    --xone-cell-tpadding: 2px;
    --xone-cell-bpadding: 2px;
}

.xnHeaderBar {
    width: 100%;
    height: 10%;
    --xone-align: left|center;
}

.xnBottomBar {
    width: 100%;
    height: 10%;
}

.xnDrawer {
    width: 75%;
    height: 75%;
}

.xnBodyOuterFrame {
    width: 100%;
    height: 100%;
    --xone-align: center;
}

.xnBodyInnerFrame {
    width: 98%;
    height: 100%;
}

.xnBodyDarkFrame {
    width: 100%;
    height: 100%;
    --xone-align: center;
}

.xnFloatingFrame {
    position: absolute;
    border: none;
}

.xnFloatingBackFrame {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0%;
    top: 0%;
    --xone-grid-framebox: false;
}

.xnDarkTitle {
    font-size: 17px;
}

.xnLightTitle {
    font-size: 17px;
}

.xnSubtitle {
    font-size: 16px;
}

.xnTextReadOnly {
    --xone-text-border-bottom: none;
}

.xnBorderTextEditable {
    --xone-text-border-top: solid;
    --xone-text-border-bottom: solid;
    --xone-text-border-right: solid;
    --xone-text-border-left: solid;
    --xone-grid-text-border: false;
    font-weight: normal;
    font-size: 15px;
    --xone-labelfont-size: 7;
    margin-left: 0px;
    --xone-img-spinner: bt_Arrow_down.png;
    --xone-img-spinner-sel: bt_Arrow_down_Sel.png;
    --xone-img-search: bt_Lupa.png;
    --xone-img-search-sel: bt_Lupa_sel.png;
    --xone-img-delete: bt_Delete.png;
    --xone-img-delete-sel: bt_Delete_sel.png;
    --xone-img-undo: undo.png;
    --xone-img-undo-sel: undo_click.png;
    --xone-img-phone: bt_Phone.png;
    --xone-img-phone-sel: bt_Phone_sel.png;
    --xone-img-date: bt_Date.png;
    --xone-img-date-sel: bt_Date_sel.png;
    --xone-img-time: bt_Time.png;
    --xone-img-time-sel: bt_Time_sel.png;
    --xone-img-checked: bt_check.png;
    --xone-img-checked-disabled: bt_check_disabled.png;
    --xone-img-unchecked: bt_uncheck.png;
    --xone-img-unchecked-disabled: bt_uncheck_disabled.png;
    --xone-img-att: bt_attach.png;
    --xone-img-att-sel: bt_attach_sel.png;
    --xone-img-camera: bt_camera.png;
    --xone-img-camera-sel: bt_camera_sel.png;
    --xone-img-video: bt_camera.png;
    --xone-img-video-sel: bt_camera_sel.png;
    --xone-img-height: 28;
    --xone-img-width: 28;
}

.xnLink {
    --xone-text-border-bottom: none;
    font-style: italic;
}

.xnTextWebview {
    --xone-text-border-top: none;
    --xone-text-border-bottom: solid;
    --xone-text-border-right: none;
    --xone-text-border-left: none;
    --xone-grid-text-border: false;
    font-weight: normal;
    font-size: 15px;
    --xone-labelfont-size: 7;
    margin-left: 0px;
    --xone-img-spinner: bt_Arrow_down.png;
    --xone-img-spinner-sel: bt_Arrow_down_Sel.png;
    --xone-img-search: bt_Lupa.png;
    --xone-img-search-sel: bt_Lupa_sel.png;
    --xone-img-delete: bt_Delete.png;
    --xone-img-delete-sel: bt_Delete_sel.png;
    --xone-img-undo: undo.png;
    --xone-img-undo-sel: undo_click.png;
    --xone-img-phone: bt_Phone.png;
    --xone-img-phone-sel: bt_Phone_sel.png;
    --xone-img-date: bt_Date.png;
    --xone-img-date-sel: bt_Date_sel.png;
    --xone-img-time: bt_Time.png;
    --xone-img-time-sel: bt_Time_sel.png;
    --xone-img-checked: bt_check.png;
    --xone-img-checked-disabled: bt_check_disabled.png;
    --xone-img-unchecked: bt_uncheck.png;
    --xone-img-unchecked-disabled: bt_uncheck_disabled.png;
    --xone-img-att: bt_attach.png;
    --xone-img-att-sel: bt_attach_sel.png;
    --xone-img-camera: bt_camera.png;
    --xone-img-camera-sel: bt_camera_sel.png;
    --xone-img-video: bt_camera.png;
    --xone-img-video-sel: bt_camera_sel.png;
    --xone-img-height: 28;
    --xone-img-width: 28;
    --xone-align: justified;
}

.xnButtonAccent1 {
    border-radius: 8px;
    font-size: 15px;
    overflow: hidden;
}

.xnButtonAccent2 {
    border-radius: 8px;
    font-size: 15px;
    overflow: hidden;
}

.xnButtonAccent3 {
    border-radius: 8px;
    font-size: 15px;
    overflow: hidden;
}

.xnButtonImg {
    width: 10%;
    margin-left: 1%;
}

.xnDarkButtonLine {
    width: 100%;
    height: 4px;
}

.xnLightButtonLine {
    width: 100%;
    height: 4px;
}

.xnCalendar {
    width: 100%;
    height: 100%;
    --xone-cell-border-width: 4;
    --xone-cell-align: center;
    --xone-align: center;
    font-size: 22px;
    --xone-weekdays-fontsize: 6;
    --xone-weekdays-longname: false;
    --xone-weekdays-align: top|left;
    border-width: 2px;
    border: none;
}

.xnCalendarTitle {
    font-size: 19px;
    --xone-align: center;
    --xone-text-border-bottom: none;
}

.xnCheckbox {
    --xone-text-border-top: none;
    --xone-text-border-bottom: solid;
    --xone-text-border-right: none;
    --xone-text-border-left: none;
    --xone-grid-text-border: false;
    font-weight: normal;
    font-size: 15px;
    --xone-labelfont-size: 7;
    margin-left: 0px;
    --xone-img-spinner: bt_Arrow_down.png;
    --xone-img-spinner-sel: bt_Arrow_down_Sel.png;
    --xone-img-search: bt_Lupa.png;
    --xone-img-search-sel: bt_Lupa_sel.png;
    --xone-img-delete: bt_Delete.png;
    --xone-img-delete-sel: bt_Delete_sel.png;
    --xone-img-undo: undo.png;
    --xone-img-undo-sel: undo_click.png;
    --xone-img-phone: bt_Phone.png;
    --xone-img-phone-sel: bt_Phone_sel.png;
    --xone-img-date: bt_Date.png;
    --xone-img-date-sel: bt_Date_sel.png;
    --xone-img-time: bt_Time.png;
    --xone-img-time-sel: bt_Time_sel.png;
    --xone-img-checked: bt_check.png;
    --xone-img-checked-disabled: bt_check_disabled.png;
    --xone-img-unchecked: bt_uncheck.png;
    --xone-img-unchecked-disabled: bt_uncheck_disabled.png;
    --xone-img-att: bt_attach.png;
    --xone-img-att-sel: bt_attach_sel.png;
    --xone-img-camera: bt_camera.png;
    --xone-img-camera-sel: bt_camera_sel.png;
    --xone-img-video: bt_camera.png;
    --xone-img-video-sel: bt_camera_sel.png;
    --xone-img-height: 28;
    --xone-img-width: 50p;
    --xone-text-bgcolor: #00000000;
}
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  @view-transition { navigation: none; }
}
/* Fuentes custom del proyecto (carpeta fonts/) — paridad XoneCSS.loadCustomTypeFace */
@font-face {
  font-family: 'SpaceMono-Regular';
  font-style: normal;
  font-weight: normal;
  font-display: swap;
  src: url('/fonts/SpaceMono-Regular.ttf') format('truetype');
}