/* ============================================================
   GP · Gastos Personales — Rediseño completo (Orden Maestra v5)
   Fase 9 — Fundamentos: tokens de color, tipografía, navegación,
   modo claro/oscuro alternable.

   Sistema de color extraído de las 33 imágenes aprobadas:
   - Logo/ícono de la app: degradado naranja (#FF8A00→#FF5A00) sobre
     fondo negro (variante 4.1, ver bitácora).
   - Interfaz: la mayoría de las pantallas usa un degradado azul→morado
     para encabezados/hero y morado como color de acento principal en
     botones/estados activos, con un sistema de colores semánticos por
     función (verde=ingresos/éxito, rojo=gastos/peligro, azul=info/
     seguridad, naranja=alertas, morado=personalización, teal=respaldo,
     rosado=metas) usado consistentemente en los círculos de ícono de
     cada fila de lista en todas las imágenes.
   ============================================================ */

:root{
  color-scheme: dark;
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* Marca / acento principal — azul vívido, extraído con pipeta del botón
     "INICIAR SESIÓN" de Imagen_01.png (pipeta #254EEE, ajustado a un valor
     de sistema #2A4DEE que coincide con el resplandor del hero). Reemplaza
     el morado inventado en la primera pasada: ningún mockup de Login/Crear
     cuenta usa morado como acento primario de marca (ver bitácora — el
     selector de "color de fondo" de Imagen_06/07 con 9 acentos, incluido
     el morado, fue eliminado por orden explícita del usuario: toda
     personalización vive únicamente en "Diseño de la aplicación", y esa
     pantalla — Imagen_33 — no incluye selector de color de acento). */
  --accent: #2A4DEE;
  --accent-2: #1E3FAE;
  --accent-soft: #2A4DEE26;

  /* Degradado "hero" usado en Login/Crear cuenta — pipeta sobre Imagen_01/02
     (marino oscuro arriba-izquierda → azul vívido abajo-derecha, ver
     tools/color-notes.md). Corrige el degradado azul→morado inventado en
     la primera pasada. */
  --hero-grad: linear-gradient(160deg, #081A4D 0%, #0B2C7A 45%, #1E3FAE 75%, #2A4DEE 100%);
  --hero-grad-purple: linear-gradient(160deg, #0b0a2e 0%, #241259 40%, #5b21b6 75%, #9333ea 100%);

  /* Degradado propio de Inicio (Figura 3/image4.png, pipeta aproximada —
     ver nota extensa junto a .hero-home en la sección de tarjetas más
     abajo): a diferencia de Login/Crear cuenta, esta referencia SÍ se
     inclina a violeta en la esquina inferior derecha. No se sampleó con
     la misma pipeta 3x3 de tools/sample.py por una caída temporal de la
     herramienta durante esta sesión — se estimó visualmente sobre la
     imagen a resolución nativa y se ajustó a tonos ya usados en el
     sistema (--accent #2A4DEE, morados ya definidos arriba en
     --hero-grad-purple) para no inventar colores nuevos sin relación con
     la paleta existente. Pendiente: re-verificar con pipeta exacta en
     cuanto la herramienta esté disponible (ver tools/color-notes.md). */
  --hero-grad-home: linear-gradient(160deg, #060B22 0%, #0F1E52 22%, #17308A 42%, #2A4DEE 64%, #5B21B6 86%, #7C3AED 100%);

  /* Colores semánticos (círculos de íconos, estados) */
  --blue: #3b82f6;
  --green: #22c55e;
  --red: #ef4444;
  --orange: #f97316;
  --amber: #f59e0b;
  --purple: #8b5cf6;
  --pink: #ec4899;
  --teal: #14b8a6;
  --gray: #94a3b8;

  /* Logo (siempre igual, no cambia con el tema) — variante 4.1 confirmada,
     valores impresos en Imagen_29.png "VERSIÓN 4.1" + verificados con
     pipeta (ver tools/color-notes.md). */
  --logo-orange: #FF8A00;
  --logo-orange-2: #FF5A00;
  --logo-bg: #0D0D0D;
  --logo-bg-2: #1A1A1A;

  /* Superficies — modo oscuro (predeterminado) */
  --bg: #0a0e1f;
  --bg-elevated: #0f1530;
  --surface: #151c34;
  --surface-2: #1b2340;
  --surface-hover: #212a4a;
  --border: #2a3355;
  --ink: #f4f6fb;
  --ink-soft: #c3cae0;
  --muted: #8b92b3;
  --muted-2: #6b7299;
  --card-on-hero: #ffffff;
  --card-on-hero-ink: #16192b;
  --card-on-hero-muted: #6b7280;
  --shadow: 0 18px 42px #00000055;

  /* CORRECCIÓN PUNTUAL — CONTRASTE DE TEXTOS SOBRE ESTILOS DE FONDO:
     variables nuevas, independientes de claro/oscuro a propósito. No
     dependen del modo porque el problema real no es el modo -- es que
     estos textos ahora viven directamente sobre var(--hero-grad) (el
     degradado vivo de "Estilo de fondo"), NO sobre var(--bg) (el fondo
     plano neutro para el que --muted/--muted-2 fueron diseñados
     originalmente). Mismo criterio que .hero{color:#fff}, ya usado y
     validado en Inicio/Más/Login en ambos modos: sobre el degradado
     vivo, lo que determina la legibilidad es el propio degradado, no
     el modo claro/oscuro.
     Se agrega además una sombra de texto sutil (no usada antes en la
     app) -- SOLO para este caso puntual -- porque un color de texto fijo
     por sí solo no puede garantizar contraste en TODA la superficie de
     un degradado arcoíris (se midió: incluso blanco puro cae por debajo
     de un contraste aceptable justo sobre sus tramos más claros/
     amarillos). La sombra oscura crea un borde de separación alrededor
     de cada letra que sigue funcionando ahí donde el color plano solo
     ya no alcanza, sin necesidad de convertir el texto en un bloque/
     tarjeta nuevo. */
  --muted-on-fondo: rgba(255,255,255,.84);
  --muted-2-on-fondo: rgba(255,255,255,.94);
  --text-shadow-on-fondo: 0 1px 2px rgba(0,0,0,.55), 0 1px 9px rgba(0,0,0,.38);
  /* CORRECCIÓN DE APARIENCIA -- color de texto de .hero (Inicio/Más)
     cuando el Estilo de fondo está activo: blanco fijo, igual que
     siempre (ver .hero{color:...} más abajo). Se redefine junto con las
     3 variables de arriba en [data-fondo-activo="false"]. */
  --hero-ink: #fff;
  /* CORRECCIÓN PUNTUAL (contraste del encabezado de Inicio en modo
     claro): variante "suave" de --hero-ink, para textos secundarios que
     ya tenían un tono propio ligeramente distinto al título (el
     subtítulo "Ten el control de tus finanzas" usaba #dbe0ff, no #fff)
     -- se conserva ESE mismo tono cuando el Estilo de fondo está activo
     (nada cambia ahí) y se redefine a var(--muted), el texto secundario
     normal del modo, en [data-fondo-activo="false"]. */
  --hero-ink-soft: #dbe0ff;
}

/* CORRECCIÓN DE APARIENCIA -- prioridad Modo claro/oscuro vs Estilo de
   fondo: cuando el usuario elige Claro/Oscuro (fondoActivo=false, ver
   render.js), Inicio/Más y las 41 pantallas unificadas dejan de pintar
   el degradado personalizado (ver --screen-bg más abajo) y vuelven al
   fondo plano del modo (--bg) -- pero el texto que esas mismas
   pantallas ya tenían pensado para verse SIEMPRE sobre un degradado
   (blanco fijo + sombra, ver la nota de arriba) quedaría ilegible sobre
   ese fondo ahora plano y claro. Redefinir aquí las mismas 4 variables
   (nunca se tocan sus USOS, uno por uno, en ninguna pantalla -- eso
   sería duplicar lógica) hace que ese mismo texto vuelva a su color
   normal del modo activo (--ink/--muted/--muted-2, ya definidos arriba/
   en [data-mode="light"]) automáticamente, en TODA la app a la vez.
   Cuando fondoActivo vuelve a true, este bloque deja de aplicar y las
   4 variables regresan solas a sus valores fijos de arriba (blanco). */
[data-fondo-activo="false"],body.is-auth{ /* body.is-auth: ver FASE 2.9.2 junto a .screen-auth */
  --hero-ink: var(--ink);
  --hero-ink-soft: var(--muted);
  --muted-on-fondo: var(--muted);
  --muted-2-on-fondo: var(--muted-2);
  --text-shadow-on-fondo: none;
}

/* ---- Modo claro ---- */
[data-mode="light"],body.is-auth{ /* body.is-auth: ver FASE 2.9.2 junto a .screen-auth */
  color-scheme: light;
  /* AJUSTE MÍNIMO (auditoría histórica: --bg nunca cambió desde el primer
     commit, pero desde 5711f83 "Priorizar Modo claro/oscuro sobre Estilo
     de fondo personalizado" es realmente el fondo visible de 32+
     pantallas con fondoActivo=false -- antes ese rol lo cumplía siempre
     el degradado --hero-grad, mucho más oscuro). #f3f4f9 vs --surface
     #ffffff daba una separación casi imperceptible (contraste 1.098:1).
     Se sube ligeramente hacia un gris/lavanda más perceptible -- SOLO
     este valor, ninguna otra variable -- para separar mejor fondo/
     tarjetas sin salir de "modo claro" (contraste resultante contra
     --surface: 1.19:1; contra --ink: 14.6:1, sin riesgo de legibilidad). */
  --bg: #e9ebf3;
  --bg-elevated: #ffffff;
  --surface: #ffffff;
  --surface-2: #f6f7fb;
  --surface-hover: #eef0f9;
  --border: #e2e5f1;
  --ink: #16192b;
  --ink-soft: #3c4257;
  --muted: #6b7280;
  --muted-2: #8891ab;
  --card-on-hero: #ffffff;
  --card-on-hero-ink: #16192b;
  --card-on-hero-muted: #6b7280;
  --shadow: 0 12px 32px #16192b1a;
}

/* ORDEN CORRECTIVA — Manual Oficial, decisión 013 (§4.1 Registro de
   decisiones oficiales): "La paleta de colores vuelve a formar parte de
   Apariencia." Esto REVIERTE la orden de la ronda anterior, que había
   eliminado por completo el selector de 9 acentos (Imagen_06/07) — ver
   la nota que existía aquí antes (conservada en el historial de git/
   bitácora) y el aviso explícito sobre esta reversión en la respuesta al
   usuario y en bitacora-v5.md. Valores de los 9 acentos verificados con
   pipeta sobre image10.png del Manual (Figura 6, "APARIENCIA — COLOR DE
   FONDO"), ver tools/color-notes.md. El acento de marca por defecto
   (--accent azul arriba) se mantiene como estado inicial "morado" ya
   definido en storage.js — al elegir un acento, estas reglas
   sobreescriben --accent/--accent-2/--accent-soft en toda la app. */
[data-accent="morado"]{--accent:#803FDB;--accent-2:#5B21B6;--accent-soft:#803FDB26}
/* "azul" se unifica con el --accent de marca ya verificado con pipeta
   sobre Imagen_01 (#2A4DEE, ver arriba) en vez del valor crudo de la
   pipeta sobre image10.png del Manual (~#0257EE) — evita tener dos
   "azules" distintos conviviendo en el sistema y mantiene el aspecto
   por defecto de la app idéntico al ya verificado/aprobado. */
[data-accent="azul"],body.is-auth{--accent:#2A4DEE;--accent-2:#1E3FAE;--accent-soft:#2A4DEE26} /* body.is-auth: ver FASE 2.9.2 junto a .screen-auth */
[data-accent="verde"]{--accent:#04B543;--accent-2:#0E7A34;--accent-soft:#04B54326}
[data-accent="rojo"]{--accent:#F13D3C;--accent-2:#B91C1C;--accent-soft:#F13D3C26}
[data-accent="rosado"]{--accent:#EF3F82;--accent-2:#BE185D;--accent-soft:#EF3F8226}
[data-accent="amarillo"]{--accent:#FBB723;--accent-2:#D97706;--accent-soft:#FBB72326}
[data-accent="naranja"]{--accent:#FC7F06;--accent-2:#C2570A;--accent-soft:#FC7F0626}
[data-accent="turquesa"]{--accent:#1FB7BC;--accent-2:#0E7C80;--accent-soft:#1FB7BC26}
[data-accent="gris"]{--accent:#868FA0;--accent-2:#5B6478;--accent-soft:#868FA026}
/* FASE 10 (Punto 4, "Estilo de fondo"): 2 tonos nuevos que agrega la
   imagen de referencia de esta fase (índigo/pizarra) + arcoíris (usa un
   violeta representativo para --accent/--accent-2 de botones y estados,
   ya que un control sólido no puede ser literalmente multicolor -- el
   degradado --hero-grad de arcoíris SÍ es multicolor real, ver
   FONDO_COLOR_STOPS.arcoiris en render.js). */
[data-accent="indigo"]{--accent:#4F46E5;--accent-2:#3730A3;--accent-soft:#4F46E526}
[data-accent="pizarra"]{--accent:#5B6B85;--accent-2:#3F4A5E;--accent-soft:#5B6B8526}
[data-accent="arcoiris"]{--accent:#8B5CF6;--accent-2:#6D28D9;--accent-soft:#8B5CF626}

/* FASE 10 (Punto 4, "Unificar completamente: Fondo + Paleta de colores
   bajo un único apartado"): los FONDOS A-E (Manual §3.8, Figura 30) que
   antes gobernaban --hero-grad/--hero-grad-home por su cuenta quedan
   RETIRADOS -- ver bitácora. El degradado hero ahora nace del mismo
   color elegido arriba en "Estilo de fondo" (ver buildFondoGradient() y
   el setProperty('--hero-grad',...) en render(), js/render.js), no de
   un atributo [data-fondo] independiente. Se conserva el campo
   settings.fondo en storage.js solo por compatibilidad de datos ya
   guardados (ver comentario ahí), pero ninguna regla CSS lo usa ya. */

/* Ver también: Fase 11 más abajo — 5 estilos reales de ícono SVG. */

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
/* Tamaño base de los íconos SVG reales (sprite js/icons.js). Un <svg>
   sin width/height explícito se renderiza a 300x150px por defecto según
   el spec HTML — sin esta regla, cada ícono aparecía gigante. Los
   contextos que necesitan otro tamaño lo sobreescriben puntualmente
   (.row-icon .icon, .auth-social .icon, etc). */
.icon{width:20px;height:20px;flex:none;display:inline-block;vertical-align:middle}
html,body{margin:0;padding:0}
body{
  font-family:var(--font);
  background:var(--bg);
  color:var(--ink);
  min-height:100vh;
}
button,input,select,textarea{font:inherit;color:inherit}
button{border:0;background:none;cursor:pointer}
a{color:var(--accent);text-decoration:none}
::selection{background:var(--accent-soft)}

/* FASE 13 (Punto 5, "Evitar selección accidental"): por defecto NADA de
   texto/ícono/botón/tarjeta/título es seleccionable -- solo se
   reactiva explícitamente en los campos de texto editables (input/
   textarea/[contenteditable]) y en la información puntual que la app
   marca a propósito como copiable (.copyable-value, ver
   #screen-profileInfo/#screen-about-info y el botón .row-copy-btn en
   events.js/render.js). -webkit-touch-callout:none evita además el menú
   de "Copiar/Compartir" que iOS abre con una pulsación prolongada sobre
   texto no editable. No afecta el arrastre del selector de color
   (.color-wheel-thumb) ni ningún otro gesto táctil -- user-select solo
   bloquea SELECCIÓN de texto, nunca eventos de puntero/touch. */
body{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
input,textarea,[contenteditable="true"]{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}
.copyable-value{-webkit-user-select:text;user-select:text;-webkit-touch-callout:default}

.app{
  width:min(100%,520px);
  min-height:100vh;
  margin:auto;
  background:var(--bg);
  position:relative;
  overflow-x:hidden;
}

/* ============================================================
   Pantallas / navegación por pila (screen stack)
   ============================================================ */
.screen{display:none}
.screen.active{display:block;animation:screenIn .18s ease}
@keyframes screenIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.screen.active{animation:none}}

.topbar{
  display:flex;align-items:center;gap:10px;
  /* ORDEN CORRECTIVA — Fidelidad visual y Safe Area: la flecha de
     regreso vivía a una distancia fija en píxeles del borde superior,
     así que en iPhone con notch/Dynamic Island terminaba debajo del
     área del sistema (o demasiado cerca de ella). env(safe-area-inset-
     top) devuelve 0 en dispositivos sin recorte (Android normal,
     navegador de escritorio), así que sumarlo siempre es seguro — nunca
     reduce el espacio ya existente, solo lo aumenta donde el sistema
     realmente lo necesita. Mismo patrón que ya se usaba abajo con
     safe-area-inset-bottom en la barra inferior y la hoja modal. */
  /* AUDITORÍA COMPLETA DEL MODO HORIZONTAL (revisión post-iPhone real):
     el padding lateral de la flecha de regreso se protege con
     safe-area-inset-left/right en TODA orientación, no solo en
     horizontal -- en un iPhone actual con Dynamic Island, el recorte
     lateral solo existe girado, así que en vertical estos dos valores
     son 0 y no cambian nada (verificado); se agregan aquí igual, de
     forma incondicional, para que la protección no dependa de qué
     media query esté activa en cada momento, pedido explícito de esta
     auditoría ("verifica que la flecha respete los márgenes seguros...
     en vertical y en horizontal"). */
  padding:calc(18px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 10px calc(16px + env(safe-area-inset-left));
  position:sticky;top:0;z-index:5;
  background:var(--bg);
}
.topbar h1,.topbar h2{margin:0;font-size:1.15rem;flex:1;text-align:center;padding-right:44px}
.topbar.no-back h1,.topbar.no-back h2{padding-right:0}
/* FASE 2.3.2 (corrección responsive -- topbar con nombres de documento
   largos, hallazgo real en iPhone): #docViewerTitle es el ÚNICO título
   de topbar de TODA la app cuyo contenido es texto LIBRE elegido por el
   usuario (el nombre real del documento, ver renameDocument() en
   js/document-db.js) -- el resto de títulos de topbar (Perfil, Mis
   documentos, etc.) son textos traducidos cortos y fijos, por eso la
   regla genérica ".topbar h1,.topbar h2" de arriba nunca había
   necesitado min-width:0/truncamiento y NO se toca aquí (se agrega una
   regla más específica, solo para esta pantalla, en vez de modificar la
   compartida por las otras 40+ pantallas).
   CAUSA RAÍZ (confirmada): esa regla genérica da flex:1 al título pero
   nunca fija min-width:0 -- por defecto un elemento flex tiene
   min-width:auto, es decir, nunca se encoge por debajo del ancho mínimo
   de su propio contenido. Un nombre de archivo sin espacios (ej.
   "Plan_de_Pago_..._2027.pdf") no tiene ningún punto donde el navegador
   pueda cortar línea, así que su "ancho mínimo de contenido" es
   prácticamente el ancho COMPLETO del texto sin envolver -- el título se
   niega a encogerse, la fila flex de la topbar excede el ancho
   disponible y, como .app tiene overflow-x:hidden (ver arriba), el
   contenido sobrante (los botones Renombrar/Eliminar, que van después
   del título en el DOM) se recorta fuera de la pantalla SIN ninguna
   barra de scroll visible -- de ahí que "desaparecieran" en vez de
   simplemente verse cortados.
   SOLUCIÓN: min-width:0 permite que el título SÍ pueda encogerse por
   debajo de su contenido; overflow:hidden + text-overflow:ellipsis +
   white-space:nowrap lo truncan visualmente con "…" en una sola línea --
   mismo patrón ya usado en otro lugar de la app para el mismo problema
   (ver .donut-legend-row .cat-name más abajo). text-align:left (en vez
   del center heredado) es necesario porque text-overflow:ellipsis con
   texto centrado no produce un truncamiento predecible entre
   navegadores -- alineado a la izquierda, junto a la flecha Atrás, el
   resultado es el mismo patrón "←  Nombre truncado…  ✏️ 🗑️" ya usado como
   referencia. #docViewerActions (envoltorio de Renombrar/Eliminar) recibe
   flex-shrink:0 explícito como refuerzo adicional -- con el título ya
   absorbiendo el desbordamiento esto no debería activarse en la
   práctica, pero es la misma defensa por partida doble que ya usan
   .back-btn/.topbar-action (flex:none) para no depender de un solo
   mecanismo. IMPORTANTE: esto es puramente visual/CSS -- el nombre REAL
   del documento (document.name, IndexedDB) no se toca en absoluto, ver
   js/document-db.js sin cambios en esta corrección. */
#screen-docViewer .topbar h2{
  min-width:0;text-align:left;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
#docViewerActions{flex-shrink:0}
/* Área táctil mínima de 44x44 (mínimo recomendado por Apple HIG /
   Material Design) aunque el ícono visual siga siendo pequeño — antes
   era 34x34, por debajo del mínimo accesible. */
.back-btn{
  width:44px;height:44px;border-radius:12px;
  display:grid;place-items:center;
  background:var(--surface-2);color:var(--ink);
  font-size:1.1rem;flex:none;
}
.topbar-action{
  width:44px;height:44px;border-radius:12px;
  display:grid;place-items:center;
  background:var(--surface-2);color:var(--ink);flex:none;
}
/* FASE 13 (Punto 5): botón pequeño "copiar" junto a un valor de solo
   lectura que la app marca a propósito como copiable (correo, versión
   de la app, etc.) -- el ÍCONO/BOTÓN en sí sigue sin ser seleccionable
   como texto (hereda user-select:none de body), pero es tocable/
   clicable con normalidad y copia .copyable-value al portapapeles
   (ver events.js). */
.row-copy-btn{
  width:32px;height:32px;border-radius:9px;flex:none;
  display:grid;place-items:center;background:var(--surface-2);color:var(--muted);
}
.row-copy-btn[data-copied="true"]{background:var(--green);color:#fff}
.row-copy-btn svg{width:16px;height:16px}
/* FASE 2.3.3 (corrección táctil -- Cambiar asociación/Desvincular):
   .row-copy-btn es un componente COMPARTIDO (también usado en Perfil
   para "copiar", ver events.js) -- no se toca la regla de arriba para no
   afectar ese otro uso ya aprobado. Regla ESPECÍFICA solo para estos 2
   botones concretos: sube el área táctil real de 32x32 a 44x44 (mínimo
   Apple HIG/Material Design, mismo estándar ya usado en .back-btn/
   .topbar-action) sin agrandar el ícono visual -- .row-copy-btn svg
   (16px, arriba) no se toca, así que el ícono sigue viéndose igual de
   compacto, centrado por el place-items:center ya heredado de
   .row-copy-btn; solo crece la CAJA clicable/tocable alrededor de él. */
#docChangeAssocBtn,#docUnlinkBtn{width:44px;height:44px}

/* FASE 2.3.4 (Abrir/Ver movimiento asociado desde el documento): zona
   informativa de la franja "Asociado a" (ícono+descripción+monto),
   envuelta en su propio <button> para hacerla tocable SIN afectar a
   Cambiar/Desvincular (arriba) ni a .list-row (compartida por 40+
   pantallas, sin tocar su regla). Layout equivalente al que ya tenía
   como <span> suelto dentro de .list-row (mismo flex/gap/alineación,
   heredado del padre) -- min-height:44px asegura el área táctil mínima
   (Sección 13 de la orden) sin agrandar visualmente el ícono/texto
   (siguen midiendo lo mismo, solo crece el área tocable si hiciera
   falta). El color de retroalimentación táctil reutiliza el mismo token
   --surface-hover que ya usa ".list-row:active,.list-row:hover" (línea
   ~1830) -- no se toca esa regla compartida, se define un selector
   nuevo y aislado con el mismo valor (Sección 12: "REUTILIZAR estilos
   existentes... NO introducir un nuevo sistema visual"). */
.doc-assoc-info-btn{
  display:flex;align-items:center;gap:12px;flex:1;min-width:0;min-height:44px;
  background:none;border:none;padding:0;margin:0;text-align:left;cursor:pointer;
  border-radius:8px;
}
.doc-assoc-info-btn:active,.doc-assoc-info-btn:hover{background:var(--surface-hover)}

/* FASE 13 (Punto 2): tarjetas de "Frecuencia de copia" (imagen aprobada,
   Panel 13.1) -- mismo componente conceptual que .mode-grid/.mode-card
   (Modo Claro/Oscuro) pero con varias columnas en vez de 2 y una
   insignia de check en la esquina en vez de un punto central, para
   igualar el mockup con la mayor fidelidad posible.
   CORRECCIÓN PUNTUAL (Fase 13, "solo frecuencia de copia"): con la nueva
   opción "Quincenal" ya son 5 tarjetas -- 4 columnas fijas dejaría la
   5ta sola en una segunda fila estirada a todo el ancho (tamaño
   inconsistente con las demás). Se baja a 3 columnas (en vez de 4) para
   que las 5 quepan en dos filas (3+2) SIN reducir el ancho de cada
   tarjeta -- de hecho quedan un poco más anchas que antes, nunca más
   angostas, así el texto no se corta ni se desborda. El tamaño/estilo de
   cada tarjeta (.freq-card: borde, radio, padding, tipografía, ícono,
   estado seleccionado) no se tocó, solo el número de columnas de la
   rejilla que las contiene. */
.freq-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.freq-card{
  border:2px solid var(--border);border-radius:14px;padding:10px 3px 8px;
  display:grid;justify-items:center;gap:4px;background:var(--surface-2);
  position:relative;text-align:center;
}
.freq-card svg{width:22px;height:22px;color:var(--amber)}
.freq-card strong{font-size:.72rem;line-height:1.15}
.freq-card span{font-size:.6rem;color:var(--muted);line-height:1.2}
.freq-card[aria-pressed="true"]{border-color:var(--accent)}
.freq-card[aria-pressed="true"]::after{
  content:'✓';position:absolute;right:-6px;bottom:-6px;width:18px;height:18px;border-radius:50%;
  background:var(--accent);color:#fff;font-size:.62rem;font-weight:900;display:grid;place-items:center;
  box-shadow:0 1px 3px #00000040;
}

/* Oculta visualmente una etiqueta pero la deja disponible para lectores
   de pantalla -- usado en Enviar sugerencia/Reportar problemas (Punto
   3/4): la imagen aprobada no muestra un <label> de texto visible sobre
   el área de escritura (el hero ya explica qué escribir), pero el campo
   sigue necesitando una etiqueta accesible real. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* FASE 2.6.1.2: indicadores dinámicos de los 3 requisitos de contraseña
   nueva en #screen-security (secRuleMin8/secRuleMayus/secRuleNumero,
   ver events.js). data-rule-state="pending" (por defecto, campo vacío o
   requisito aún no cumplido) usa el mismo gris neutro que el resto de
   texto secundario de la app (--muted) -- nunca rojo, para no dar la
   impresión de un error mientras el usuario simplemente está escribiendo.
   data-rule-state="met" pasa a --green -- el color nunca es la única
   señal: el icono ya cambia por separado (i-minus/i-check-circle, ver
   el <use> en index.html) y .sr-only añade el estado en texto para
   lectores de pantalla. */
.pw-rule{color:var(--muted)}
.pw-rule[data-rule-state="met"]{color:var(--green)}
/* FASE 2.6.1.2.1: #screen-createAccount usa var(--hero-grad), un fondo
   oscuro fijo (no el claro/oscuro normal de la app) -- var(--muted) no
   tiene el contraste probado ahí, así que se reutilizan los mismos 2
   colores que YA usaba esta pantalla para su lista de requisitos
   (#8891ab, igual que .auth-demo-hint; #9ae6b4, el verde claro que ya
   tenía la lista estática) en vez de inventar un color nuevo sin probar. */
#screen-createAccount .pw-rule{color:#8891ab}
#screen-createAccount .pw-rule[data-rule-state="met"]{color:#9ae6b4}

/* FASE 13 (Punto 3/4): fila "Adjuntar archivo (opcional)" -- mismo
   componente visual que .list-row (ícono + texto + chevron) pero como
   <button> de ancho completo fuera de un .list-card, igual que en la
   imagen aprobada (recuadro propio, no dentro de una lista). */
.attach-row{
  display:flex;align-items:center;gap:12px;width:100%;
  border:1px solid var(--border);border-radius:16px;background:var(--surface);
  padding:12px 14px;text-align:left;margin-top:4px;
}
.attach-row:active{background:var(--surface-hover)}
/* Lista de archivos ya adjuntados (vista previa local, ver renderAttachList
   en render.js) -- cada fila reutiliza .list-row dentro de un .list-card
   compacto, con un botón × para quitar el archivo antes de enviar. */
.attach-list:not(:empty){margin-top:8px;border:1px solid var(--border);border-radius:16px;overflow:hidden;background:var(--surface)}
.attach-list .list-row{padding:10px 14px}
.attach-limits{font-size:.74rem;margin:4px 0 0}
.attach-limits + .attach-limits{margin-top:2px}

.hero{
  background:var(--hero-grad);
  padding:26px 20px 30px;
  position:relative;overflow:hidden;
  /* CORRECCIÓN DE APARIENCIA: blanco fijo cuando el Estilo de fondo está
     activo (comportamiento igual que siempre); pasa a var(--ink), el
     color de texto normal del modo, cuando fondoActivo=false -- ver
     --hero-ink en :root/[data-fondo-activo="false"] arriba. .hero SOLO
     se usa realmente en Inicio y Más (Login/Crear cuenta usan
     .auth-logo, una clase distinta, sin cambios aquí). */
  color:var(--hero-ink,#fff);
}
/* CORRECCIÓN PUNTUAL (prueba local en VS Code, "Unificar el Estilo de
   fondo -- eliminar la división visual en Inicio y Más"):

   CAUSA REAL investigada (no asumida) de la división que reportó el
   usuario -- existían DOS fuentes de fondo independientes para la
   misma pantalla, cada una calculando su propio degradado sobre una
   caja de distinto tamaño:
     1) El header (.hero, con su propio `background:var(--hero-grad)`,
        ver regla base más arriba) -- un degradado calculado SOLO
        sobre la altura corta del propio header.
     2) #screen-more -- antes con una segunda capa de fondo, limitada
        a [data-mode="light"] y recortada a 700px con un fundido a
        transparente/--bg (ver historial arriba), o sin ningún
        tratamiento en modo oscuro (Ajustes quedaba sin degradado en
        modo oscuro).
   Un `linear-gradient(...)` se recalcula de cero según el tamaño de
   la caja de CADA elemento que lo declara -- por eso, aunque ambas
   capas usaran técnicamente "el mismo color", el tono exacto en el
   punto donde el header terminaba y #screen-more empezaba no
   coincidía (dos degradados independientes, no uno solo), lo que se
   percibía como una costura/división. Mismo diagnóstico que ya se
   había corregido para Inicio (.hero-home vs #screen-home, ver más
   abajo) pero que aquí seguía sin resolver, y en Ajustes NUNCA se
   había limitado la separación al header como pedía esta corrección.

   FIX: una única fuente de fondo por pantalla. #screen-more pasa a
   pintar el degradado UNA sola vez, sin importar claro/oscuro (se
   quita el envoltorio [data-mode="light"] -- ahora también funciona en
   modo oscuro, antes no tenía ningún tratamiento ahí) ni el 700px/
   fundido artificial (ya no hace falta "esconder" el corte con un
   fundido si no hay ningún corte real). El .hero de dentro de Ajustes
   deja de pintar su propio fondo (ver "#screen-more .hero" más abajo)
   para no volver a crear una segunda fuente independiente -- se limita
   a #screen-more, así que Login, Inicio y cualquier otra pantalla que
   también use la clase compartida .hero (ver regla base) NO se tocan.
   <main> sigue sin fondo propio (transparente) y .list-card sigue
   opaca (var(--surface)), así que el degradado sigue sin entrar NUNCA
   dentro de las tarjetas.

   Nota sobre la altura (investigada, no se duplica sin necesidad): NO
   hace falta agregar min-height:100dvh aquí -- ya existe una regla más
   abajo, ".screen.active:not(.screen-auth){display:flex;flex-
   direction:column;height:100dvh}" (ver "Adaptación a orientación
   horizontal"/scroll interno), que ya fija la altura de #screen-more (y
   de #screen-home) a 100dvh SIEMPRE, con su <main> como flex:1 y
   overflow-y:auto propio -- por eso el fondo pintado aquí ya cubre la
   pantalla completa sin necesidad de repetir esa altura en esta regla.
   <main> se desplaza DENTRO de su propia caja (scroll interno, no de la
   página) -- el fondo vive en el contenedor exterior de altura fija que
   nunca se mueve/recorta, así que nunca "se acaba" ni cambia de tono al
   hacer scroll: solo las tarjetas se deslizan por encima de él. */
/* CORRECCIÓN DE APARIENCIA (prioridad Modo vs Estilo de fondo): usa
   --screen-bg, NO --hero-grad -- ver la nota completa junto a
   "--screen-bg" en render.js. --hero-grad sigue existiendo tal cual
   (Login/Crear cuenta lo siguen usando directamente), pero ya no es lo
   que pinta el fondo de esta pantalla. */
#screen-more{background:var(--screen-bg)}
#screen-more .hero{background:none}

/* ============================================================
   PRUEBA PILOTO — ENCABEZADO DINÁMICO CON SCROLL ESTILO iOS
   (Más/Ajustes). Sistema GLOBAL y REUTILIZABLE (ver js/dynHeader.js
   para la única implementación central en JS): se activa por pantalla
   agregando [data-dyn-header] en el <section class="screen"> y
   [data-dyn-title] en el <h1> del título grande, que debe vivir como
   primer hijo de <main> (ver #screen-more en index.html). No se toma
   como referencia el diseño visual de Apple -- solo el COMPORTAMIENTO
   de colapso; colores, fondos, iconos, tarjetas y tipografía siguen
   siendo 100% los de GP.

   [data-dyn-header] > main{padding:0} + .dyn-content{padding:16px;
   grid...}: <main> normalmente trae su propio padding:16px + grid +
   gap:14px (ver regla base "main{...}" más abajo en este archivo) --
   ese padding/gap se pensó para cuando .hero vivía FUERA de <main>. Al
   mover .hero para que sea el primer hijo de <main> (requisito para que
   pueda desplazarse y "salir de vista", ver comentario en
   js/dynHeader.js), aplicar el padding/gap genérico directamente
   encima de .hero sumaría espacio extra que antes no existía (.hero ya
   trae su propio padding con safe-area). Se anula el padding/grid de
   <main> SOLO en pantallas con [data-dyn-header] y se reproduce
   exactamente el mismo padding:16px/grid/gap:14px en un contenedor
   .dyn-content que envuelve el resto del contenido (todo excepto
   .hero) -- el resultado en scrollTop=0 es visualmente IDÉNTICO al
   diseño anterior, verificado con captura real. */
/* EXTENSIÓN — dos variantes (ver js/dynHeader.js): esta reestructuración
   de padding/grid (mover el padding de <main> a un .dyn-content interno)
   solo tiene sentido en la variante "hero" (título grande que vive DENTRO
   de <main>, ver arriba) -- se restringe explícitamente a
   [data-dyn-header="hero"] (antes era [data-dyn-header] a secas, cuando
   solo existía esta variante) para que la variante "topbar" (pantallas
   con encabezado compacto, añadida después) NO SUFRA NINGÚN CAMBIO
   estructural: sus <main> conservan exactamente el padding:16px/grid/gap
   genérico de la regla base "main{...}" de este archivo, sin ningún
   .dyn-content ni reordenamiento -- justo lo pedido ("no mover
   estructuralmente el diseño"). */
.screen[data-dyn-header="hero"]>main{padding:0;display:block}
.screen[data-dyn-header="hero"]>main>.dyn-content{padding:16px;display:grid;grid-template-columns:minmax(0,1fr);grid-auto-rows:max-content;gap:14px;align-content:start}

/* Barra compacta (Estado 3 de la orden): creada dinámicamente por
   dynHeader.js, insertada antes de <main> -- position:absolute (no
   sticky) para que NUNCA ocupe espacio propio en el flujo ni empuje el
   contenido, ni siquiera 1px, mientras está oculta (Estado 1: debe
   verse igual que hoy). El progreso de scroll (variable --dyn-progress,
   0 a 1, escrita por dynHeader.js en cada fotograma de scroll) controla
   opacidad/blur/traducción de forma CONTINUA -- nunca aparece "de
   golpe" (Estado 2) y se retira igual de suave al volver arriba
   (Estado 4).

   Adaptación al fondo (requisito explícito, "no colocar un color fijo
   blanco"): no se pinta ningún color propio de fondo -- solo un velo
   NEGRO semitransparente (cuya opacidad también depende de
   --dyn-progress) + backdrop-filter:blur(), que oscurece/difumina
   levemente lo que ya se ve detrás (el propio --hero-grad de la
   pantalla, cualquiera sea el Estilo de fondo elegido) en vez de
   sustituirlo por un color fijo. Todos los tonos de --hero-grad
   arrancan con un stop oscuro en su extremo superior (ver
   FONDO_COLOR_STOPS en render.js) -- el mismo criterio de "texto blanco
   siempre legible sobre el degradado" ya usado en toda la app (.hero,
   --muted-on-fondo, etc.) se reutiliza aquí sin inventar un criterio
   nuevo. */
/* AJUSTE VISUAL FINO (reporte real de iPhone, "se percibe una línea
   horizontal donde termina la barra"): AUDITORÍA -- capturas ampliadas
   (3x, zoom real de píxel) confirmaron la causa exacta, sin asumirla:
   el `border-bottom:1px solid rgba(255,255,255,.18)` que llevaba esta
   regla ANTES pintaba un borde real de 1px justo en el borde inferior
   de la caja de la barra (y=44px+safe-area) -- un borde SÓLIDO es,
   por definición, una línea dura, sin ningún degradado que lo suavice,
   sin importar cuánto blur tuviera el fondo de al lado. Se elimina por
   completo (no se reemplaza por una sombra/línea más tenue -- cualquier
   borde real, por sutil que sea, sigue siendo un límite recto).

   Segunda causa, menor pero real: el fondo+blur (`background`/
   `backdrop-filter`) terminaban en el borde RECTANGULAR de la propia
   caja de la barra -- sin ningún borde visible, ese límite recto seguía
   ahí (el blur dejaba de aplicarse de golpe un píxel más abajo). Se
   separa el "vidrio" (fondo + blur) a un ::before independiente con su
   PROPIA máscara -- se desvanece dentro de su propia caja en vez de
   cortarse en su borde recto, fundiéndose con la máscara de <main> (ver
   más abajo) en lugar de crear una costura donde una termina y otra
   sigue. El texto ("Ajustes") queda en un elemento aparte, SIN máscara,
   para que su legibilidad no se vea afectada por este desvanecimiento. */
.dyn-header-bar{
  position:absolute;top:0;left:0;right:0;z-index:6;
  display:flex;align-items:center;justify-content:center;
  padding:calc(10px + env(safe-area-inset-top)) 56px 10px;
  min-height:calc(44px + env(safe-area-inset-top));
  opacity:var(--dyn-progress,0);
  pointer-events:none;
}
.dyn-header-bar.is-visible{pointer-events:auto}
.dyn-header-bar::before{
  content:'';position:absolute;inset:0;
  /* AJUSTE FINO -- MATERIAL ADAPTATIVO (reporte real: "en modo claro se
     percibe blanco → negro → desaparece; debería sentirse como una sola
     superficie continua, blanco → blanco/translúcido → fade").

     AUDITORÍA: el velo anterior era rgba(0,0,0,...) -- negro fijo, sin
     relación con el tema. Las filas que suben (.list-row dentro de
     .list-card) toman su fondo de var(--surface) -- la MISMA variable
     que ya cambia sola entre modo claro (#ffffff) y oscuro (#151c34,
     ver :root/[data-mode="light"] arriba). Usarla aquí (en vez de negro
     fijo o blanco fijo, ninguno de los dos pedido) hace que el material
     de la barra sea literalmente la MISMA superficie que la fila que
     está entrando, con transparencia -- de ahí que ahora se sienta como
     una continuación de la propia tarjeta en vez de una capa distinta.

     color-mix(in srgb, var(--surface) X%, transparent): mezcla
     --surface con transparencia en la proporción X (atada a
     --dyn-progress, igual que antes) -- en progress=0 es 100%
     transparente (sin cambio respecto al diseño en reposo), en
     progress=1 llega a un 62% de --surface, dejando el resto
     transparente a propósito (no se sube más allá) para que el Estilo
     de fondo del usuario (sólido/degradado/arcoíris) siga
     transparentándose por debajo, junto con el backdrop-filter --
     "debe mezclarse con el fondo, no reemplazarlo por completo". La
     declaración rgba(0,0,0,...) de arriba queda como respaldo -- si un
     navegador no soporta color-mix(), esa línea (ignorada por los que
     sí lo soportan, ya que la última declaración válida gana) sigue
     dando un resultado funcional en vez de ningún fondo.

     AJUSTE FINO -- GRADIENTE VERTICAL (reporte real: "todavía se
     percibe una franja de tonalidad uniforme" -- en modo claro como
     banda clara, en otros fondos/modos como banda más oscura).

     CAUSA exacta: el color-mix(...) de arriba era un color PLANO -- un
     solo porcentaje de --surface, igual en toda la altura de la caja.
     La variación vertical que sí existía venía SOLO de mask-image (más
     abajo), que recorta la opacidad TOTAL del elemento a partir del
     62% de su alto -- pero desde 0% hasta ese 62%, el material se
     mostraba a densidad constante, sin ninguna gradación propia: de
     ahí la sensación de "franja/banda" en ese primer tramo, antes de
     que la máscara empezara a actuar.

     SOLUCIÓN: el mismo color-mix(...) de --surface, pero como varios
     stops de un linear-gradient vertical en vez de un color único --
     más presente arriba (68%, para que "Ajustes" tenga suficiente
     superficie/blur detrás y siga legible), disminuyendo progresivamente
     hacia la zona media (46%) y quedando ya muy tenue antes de llegar
     al final de la caja (16%), transparente en el borde inferior. No
     se duplica con la máscara para no oscurecer de más: esta gradación
     controla la DENSIDAD del material punto a punto, mientras que
     mask-image (sin cambios) sigue controlando únicamente el recorte
     final de opacidad total -- ambas trabajan sobre franjas distintas
     de la misma caja (la máscara actúa sobre todo el 100% de la altura,
     con su propio tramo de salida a partir del 62%; el degradado del
     material ya viene decayendo desde el inicio), así que se
     complementan en vez de sumarse una encima de la otra. */
  background:rgba(0,0,0,calc(var(--dyn-progress,0) * .4));
  background:linear-gradient(to bottom,
    color-mix(in srgb, var(--surface) calc(var(--dyn-progress,0) * 68%), transparent) 0%,
    color-mix(in srgb, var(--surface) calc(var(--dyn-progress,0) * 46%), transparent) 48%,
    color-mix(in srgb, var(--surface) calc(var(--dyn-progress,0) * 16%), transparent) 82%,
    transparent 100%
  );
  -webkit-backdrop-filter:blur(calc(var(--dyn-progress,0) * 22px));
  backdrop-filter:blur(calc(var(--dyn-progress,0) * 22px));
  -webkit-mask-image:linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
  mask-image:linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
  -webkit-mask-repeat:no-repeat;
  mask-repeat:no-repeat;
}
/* CORRECCIÓN GLOBAL — CONTRASTE ADAPTATIVO (auditoría: "encabezado
   dinámico/translúcido... puede perder contraste dependiendo del
   fondo"): color:#fff quedaba fijo sin importar modo/fondo -- el propio
   material de esta barra (::before, arriba) ya se mezcla con
   var(--surface) (claro en modo claro), así que blanco fijo podía quedar
   casi invisible sobre un material claro. Se reutiliza var(--hero-ink) --
   MISMA variable que ya usa .hero para su título grande -- que ahora
   resuelve correctamente a claro u oscuro según fondoActivo/modo/
   luminancia del degradado elegido (ver fondoNeedsDarkForeground en
   render.js), sin introducir ninguna variable ni lógica nueva aquí. No
   se toca geometría, máscara ni tamaño de la franja -- solo el color de
   texto. */
.dyn-header-bar span{
  position:relative;
  color:var(--hero-ink,#fff);font-size:1.02rem;font-weight:700;
  text-shadow:var(--text-shadow-on-fondo,0 1px 2px rgba(0,0,0,.5));
  transform:translateY(calc((1 - var(--dyn-progress,0)) * 8px));
  transition:transform .08s linear;
  max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
@media(prefers-reduced-motion:reduce){
  .dyn-header-bar span{transition:none}
}

/* AJUSTE FINO — "las opciones que van subiendo se deben ocultar
   progresivamente por detrás del encabezado, no verse recortadas de
   golpe / demasiado expuestas justo debajo de él" (referencia:
   Configuración de iPhone).

   CAUSA del problema señalado: <main> tiene overflow-y:auto (ver regla
   base ".screen.active:not(.screen-auth) > main" más abajo) -- ese
   recorte de caja es normal y correcto (cualquier lista con scroll lo
   tiene, también en iOS nativo), pero sin ningún tratamiento adicional
   el contenido se ve COMPLETAMENTE nítido hasta el último píxel antes
   de desaparecer de golpe contra el borde -- por eso la primera fila se
   sentía "pegada" justo debajo de la barra compacta en vez de
   desvanecerse dentro de ella.

   SOLUCIÓN: máscara de opacidad (mask-image) sobre <main> que funde a
   transparente los primeros ~72px+safe-area superiores -- el contenido
   que sube (incluida .hero mientras todavía asoma) se va perdiendo
   suavemente dentro de esa franja en vez de cortarse en seco, dando la
   sensación de pasar "por detrás" del vidrio esmerilado de la barra.

   Por qué está condicionada a ".dyn-scrolling" (clase que dynHeader.js
   agrega/quita según main.scrollTop, ver ese archivo) en vez de estar
   siempre activa: en scrollTop=0 (Estado 1) esta misma franja superior
   es donde vive el título GRANDE "Ajustes" -- una máscara siempre
   activa habría desvanecido parte de ese título incluso en reposo,
   rompiendo el requisito ya aprobado "en posición inicial conserva
   visualmente su diseño actual". Al activarse solo con scroll real
   (scrollTop>0), el estado de reposo queda intacto y la máscara entra
   en juego exactamente cuando empieza a haber contenido real
   desplazándose por esa franja.

   CORRECCIÓN PUNTUAL (reporte real de iPhone, "el fade solo se sentía
   continuo durante la transición inicial del título grande, luego las
   filas siguientes -- Moneda, Idioma, Categorías...-- se cortaban de
   golpe"): auditoría confirmó que el fade SÍ se aplicaba correctamente
   la primera vez (título grande/Apariencia), pero fallaba de forma
   visible a partir de la segunda fila. Causa real, verificada
   midiendo getComputedStyle() en vivo, no asumida: faltaba
   "mask-repeat:no-repeat" -- sin esa propiedad, su valor por defecto es
   "repeat", así que el navegador MOSAICABA el degradado de ~72px una y
   otra vez a lo largo de TODO el alto desplazable de <main> (que puede
   superar los 1000px de contenido real) en vez de aplicarlo una única
   vez arriba y mantener el resto opaco. El resultado eran bandas
   transparente→opaco repetidas cada ~72px invisibles a simple vista
   pero que hacían que el desvanecimiento pareciera "funcionar solo a
   veces" según en qué banda cayera cada fila. Con mask-repeat:no-repeat
   el degradado se pinta una sola vez (su color final se sostiene de
   forma opaca el resto del alto de <main>, comportamiento normal de
   cualquier gradiente), así que TODA fila que pase por esa franja
   superior -- la primera y cualquier otra después -- se desvanece
   exactamente igual, durante todo el recorrido del scroll. */
/* AJUSTE VISUAL FINO -- tercera causa, menor, de la línea reportada:
   la máscara anterior era una rampa de SOLO 2 puntos reales
   (transparente → opaco), es decir, un degradado de pendiente
   CONSTANTE que pasa a pendiente CERO (plano/opaco) de forma abrupta
   justo en su último stop -- ese quiebre en la pendiente (aunque el
   color en sí sea continuo) el ojo lo percibe como un límite. Se
   reemplaza por una curva de varios puntos (aproximando un "ease-out",
   como usa iOS) que empieza y termina su transición de forma progresiva
   en vez de con pendiente constante, y se estira un poco más (72px →
   96px) para una transición más amplia y dispersa, sin ningún stop
   brusco.

   CORRECCIÓN PUNTUAL (reporte real de iPhone, "en Perfil/Apariencia/
   Categorías/Manual/Idioma/Moneda la franja translúcida es demasiado
   ALTA y el fade demasiado FUERTE -- el contenido llega a desaparecer
   casi por completo; comparar con Ajustes, donde se siente más
   contenida"):

   AUDITORÍA (antes de tocar ningún número): esta regla -- selector
   "[data-dyn-header]" a secas, SIN filtrar por variante -- era la ÚNICA
   fuente de la altura/intensidad de la franja, y se aplicaba IDÉNTICA a
   las dos variantes por igual: los mismos 96px+safe-area de recorrido y
   la misma curva de opacidad (.14/.45/.8) tanto en Ajustes (variante
   "hero") como en Perfil/Categorías/etc. (variante "topbar"). El
   material/blur de la propia .topbar (regla siguiente, más abajo) NO es
   la causa -- ese es un tinte sobre la barra en sí, de altura fija (no
   "crece"); la franja que el ojo percibe agrandarse es enteramente esta
   máscara sobre <main>.

   Por qué en Ajustes se siente más contenida con la MISMA regla: ahí el
   título grande (~200px con subtítulo/ícono) es lo primero en retirarse
   -- la franja de 96px es una fracción pequeña de ese recorrido, y para
   cuando las filas reales (Moneda, Idioma...) llegan a esa zona, la
   barra compacta ya está a densidad completa, dando contexto visual al
   desvanecimiento. En las 41 pantallas "topbar", en cambio, las filas
   reales están pegadas al borde superior desde el primer píxel de
   scroll -- la misma franja de 96px con esa curva de opacidad domina de
   inmediato buena parte de la pantalla visible.

   SOLUCIÓN (alcance exclusivo a la variante "topbar", ver más abajo):
   se separa esta regla en dos -- [data-dyn-header="hero"] conserva ESTOS
   96px/misma curva sin ningún cambio (Ajustes/Inicio quedan intactos,
   pixel por pixel) y se agrega [data-dyn-header="topbar"] con su propio
   recorrido, más corto y con una curva que nunca hunde tanto la
   opacidad (ver esa regla, más abajo). */
.screen[data-dyn-header="hero"]>main.dyn-scrolling{
  -webkit-mask-image:linear-gradient(to bottom,
    transparent 0,
    transparent calc(env(safe-area-inset-top) * .1),
    rgba(0,0,0,.14) calc(30px + env(safe-area-inset-top)),
    rgba(0,0,0,.45) calc(58px + env(safe-area-inset-top)),
    rgba(0,0,0,.8) calc(80px + env(safe-area-inset-top)),
    #000 calc(96px + env(safe-area-inset-top))
  );
  mask-image:linear-gradient(to bottom,
    transparent 0,
    transparent calc(env(safe-area-inset-top) * .1),
    rgba(0,0,0,.14) calc(30px + env(safe-area-inset-top)),
    rgba(0,0,0,.45) calc(58px + env(safe-area-inset-top)),
    rgba(0,0,0,.8) calc(80px + env(safe-area-inset-top)),
    #000 calc(96px + env(safe-area-inset-top))
  );
  -webkit-mask-repeat:no-repeat;
  mask-repeat:no-repeat;
}
/* Variante "topbar" -- mismo mecanismo (mask-image + no-repeat), rango
   e intensidad propios.

   REFINAMIENTO (reporte real de iPhone, "todavía se percibe una zona de
   transición demasiado profunda comparada con Ajustes" -- tras la
   primera reducción de 96px a 52px): AUDITORÍA en ejecución real
   (getBoundingClientRect(), no estimado) antes de tocar ningún número:

     - Altura real de .topbar en las 41 pantallas: 72-76px (padding-top
       calc(18px + safe-area) + contenido ~44px + padding-bottom 10px;
       Perfil mide 76px por su botón de editar en 2 líneas de alcance
       táctil, el resto 72px) -- ver auditoría con measure.js.
     - <main> arranca EXACTAMENTE en el borde inferior de .topbar (cero
       superposición, confirmado: mainTop === topbarBottom en las 5
       pantallas medidas) -- así que el 100% del recorrido de 52px
       anterior caía sobre contenido real (main ya trae padding-top:16px
       propio antes de la primera tarjeta, así que 52px alcanzaba a
       cubrir ese padding MÁS unos 36px reales de la primera fila).
     - Referencia de comparación (Ajustes/hero): su barra compacta
       (.dyn-header-bar) mide solo 44px de min-height -- bastante MENOS
       que los 72-76px de una .topbar completa. Los 52px anteriores ya
       superaban ese propio punto de referencia.

   CAUSA de que 52px siguiera sintiéndose grande: no era un problema de
   curva (la de la corrección anterior ya subía rápido) sino de RECORRIDO
   -- 52px es más de un tercio del alto real de la topbar (72-76px), una
   fracción demasiado grande para sentirse "localizada justo debajo del
   borde" en vez de "una franja que domina la parte superior de la
   pantalla".

   AJUSTE: recorrido reducido de 52px a 34px (por debajo incluso de los
   44px de la barra compacta de Ajustes, para que la sensación sea
   claramente más contenida que cualquier referencia existente en la
   app) y curva aún más adelantada (.55/.82/.95 en vez de .38/.7/.92) --
   el contenido se mantiene mayormente visible desde los primeros
   píxeles y solo se difumina de verdad muy cerca del borde real de la
   topbar, nunca a lo largo de una fila completa. */
.screen[data-dyn-header="topbar"]>main.dyn-scrolling{
  -webkit-mask-image:linear-gradient(to bottom,
    transparent 0,
    transparent calc(env(safe-area-inset-top) * .1),
    rgba(0,0,0,.55) calc(10px + env(safe-area-inset-top)),
    rgba(0,0,0,.82) calc(18px + env(safe-area-inset-top)),
    rgba(0,0,0,.95) calc(26px + env(safe-area-inset-top)),
    #000 calc(34px + env(safe-area-inset-top))
  );
  mask-image:linear-gradient(to bottom,
    transparent 0,
    transparent calc(env(safe-area-inset-top) * .1),
    rgba(0,0,0,.55) calc(10px + env(safe-area-inset-top)),
    rgba(0,0,0,.82) calc(18px + env(safe-area-inset-top)),
    rgba(0,0,0,.95) calc(26px + env(safe-area-inset-top)),
    #000 calc(34px + env(safe-area-inset-top))
  );
  -webkit-mask-repeat:no-repeat;
  mask-repeat:no-repeat;
}

/* ============================================================
   EXTENSIÓN — VARIANTE "topbar" DEL ENCABEZADO DINÁMICO
   (pantallas con .topbar compacto existente: Perfil, Categorías, Límites
   de gasto, Calendario financiero, etc. -- ver js/dynHeader.js). NO se
   crea .hero ni título grande, NO se mueve ningún elemento: .topbar
   sigue siendo exactamente el mismo hijo directo de .screen, en el mismo
   lugar, con el mismo botón Atrás/título/acciones. Lo único nuevo es que
   su PROPIA superficie gana un material translúcido adaptativo (mismo
   lenguaje visual que la barra compacta de la variante "hero": tono de
   --surface mezclado con color-mix() + backdrop-filter blur) a medida
   que --dyn-progress avanza -- variable que aquí escribe dynHeader.js en
   la propia .topbar (no crea una barra nueva: no existe título grande
   que colapsar, la topbar YA ES el encabezado compacto).

   En reposo (--dyn-progress no definida -> calc() la trata como 0) esta
   regla es un NO-OP total: color-mix(var(--surface) 0%, var(--bg)) es
   exactamente var(--bg) -- Estado 1 IDÉNTICO al diseño actual, sin
   excepción, en cualquiera de las 41 pantallas. El desvanecimiento del
   contenido que sube ("sin corte brusco") NO se duplica aquí -- lo
   resuelve la máscara "[data-dyn-header="topbar"]>main.dyn-scrolling" de
   arriba (mismo mecanismo que la variante "hero", con su propio rango e
   intensidad -- ver el comentario junto a esa regla). */
.screen[data-dyn-header="topbar"]>.topbar{
  background:var(--bg);
  background:color-mix(in srgb, var(--surface) calc(var(--dyn-progress,0) * 55%), var(--bg));
  -webkit-backdrop-filter:blur(calc(var(--dyn-progress,0) * 18px));
  backdrop-filter:blur(calc(var(--dyn-progress,0) * 18px));
}
.hero::before{
  content:'';position:absolute;inset:0;
  background:radial-gradient(circle at 85% 10%, #ffffff22, transparent 45%);
  pointer-events:none;
}
.hero-logo{display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;position:relative}
.hero-logo img{width:84px;height:84px;border-radius:22px;box-shadow:0 12px 30px #00000055}

/* FASE 10 (Punto 1, cabecera de Ajustes: "mantener la tuerca más
   llamativa con apariencia 3D"): mismo tratamiento visual que el estilo
   de ícono "3D" ya aprobado para el resto de la app (superficie clara en
   degradado + sombra en relieve + sombra interior, ver
   [data-iconstyle="3d"] .row-icon más abajo) para que la tuerca decorativa
   del hero de Ajustes deje de ser un simple trazo de color plano flotando
   sobre el degradado y pase a leerse como una placa/insignia con volumen
   real, EN VEZ de cambiar su tamaño o posición (eso sí se mantiene igual
   que antes -- ver el <span> en gastos_personales_app.html). */
.settings-gear-badge{
  width:56px;height:56px;border-radius:16px;flex:none;
  display:grid;place-items:center;
  background:linear-gradient(160deg,#ffffff,#eef1f8);
  color:var(--accent);
  box-shadow:0 4px 0 #00000018, 0 10px 22px #00000040, inset 0 -3px 6px #00000014, inset 0 2px 2px #ffffff;
}
.settings-gear-badge .icon{width:30px;height:30px}

/* ORDEN DE CORRECCIÓN (post-Tanda 5, "logo sin cuadro blanco"): antes
   había aquí un anillo de rescate de contraste (.is-low-contrast) que se
   agregaba/quitaba desde applyLogoContrastGuard() en render.js. Se
   retiró por completo -- el usuario pidió explícitamente eliminar
   cualquier "cuadro/bloc blanco, borde, tarjeta, contenedor blanco, halo
   o fondo... alrededor del logo", y un anillo de rescate (claro u
   oscuro) es exactamente ese tipo de contenedor. El logo (icons/
   logo-mark.png) ahora es un PNG con transparencia real -- solo la
   billetera, sin ningún fondo ni caja -- y en su lugar de un anillo se
   usa un filter:drop-shadow(), que sigue el contorno real (alfa) de la
   ilustración en vez de dibujar un rectángulo/aro alrededor: da
   profundidad y ayuda a que "resalte" sobre cualquier fondo (como pide
   la corrección) sin crear ningún contenedor visible. Ver la nota
   extensa en render.js junto a applyLogoContrastGuard() (conservada como
   no-operación) y el reporte de esta corrección para la validación
   visual real que reemplaza al cálculo WCAG de un solo color. */
.logo-guard{filter:drop-shadow(0 10px 20px rgba(0,0,0,.45))}
.hero-logo h1{margin:4px 0 0;font-size:1.7rem;font-weight:800}
.hero-logo p{margin:0;color:#dbe0ff;font-size:.92rem}

/* grid-template-columns:minmax(0,1fr) en vez de la columna implícita
   "auto" por defecto: sin esto, un hijo con contenido flex que no puede
   encogerse (ej. montos largos "45.000,00 DOP" en 3 columnas, ver
   .stat-col) fuerza el ancho de la ÚNICA columna del grid más allá del
   viewport, y como .app tiene overflow-x:hidden (necesario para el fix
   de position:sticky, ver más abajo), el excedente se recorta en seco —
   afecta a TODAS las tarjetas de la pantalla, no solo a la que originó
   el desborde. Detectado en Inicio (resumen financiero de 3 columnas)
   durante la reconstrucción de la Orden Correctiva; el fix es genérico
   así que se aplica a "main" globalmente, no solo a Inicio. */
/* align-content:start evita que las filas de la grilla se "estiren" para
   rellenar el alto sobrante cuando el contenido de la pantalla es corto
   (p. ej. Idioma con pocas opciones): sin esto, "normal" se computa como
   "stretch" en un contenedor grid con alto definido (main es flex:1 dentro
   de .screen.active), inflando .section-label y .list-card muy por encima
   de su tamaño natural y dejando huecos vacíos entre ellos. Detectado en
   Tarea #84 (Idioma) mediante inspección de layout con Playwright. */
/* grid-auto-rows:max-content (auditoría horizontal v2): sin esto, cuando
   una fila implícita de "main" contiene un hijo que es a su vez un grid
   (p. ej. .list-card en horizontal, que se vuelve display:grid de 2
   columnas) Y ese hijo tiene overflow:hidden, el navegador puede calcular
   mal el alto de la fila del grid EXTERNO (se detectó, p. ej., una fila de
   155px calculada para una .list-card cuyo alto real correcto era 341px),
   dejando que el siguiente elemento (p. ej. un .section-label como
   "Otros") se dibuje encimado sobre el contenido de la fila anterior en
   vez de debajo. Es una segunda manifestación, a nivel del contenedor
   externo, de la misma familia de bug ya documentada más abajo en
   ".list-card" (tamaño automático mal calculado con overflow≠visible
   dentro de un grid) — aquí el defecto está en el cálculo del alto de la
   FILA del grid externo, no en el elemento mismo. Forzar
   grid-auto-rows:max-content hace que cada fila implícita se mida según
   el contenido real de su hijo más alto, evitando el encimado. Se aplica
   en la regla base de "main" (afecta TODAS las pantallas, ambas
   orientaciones) porque el problema no es exclusivo de una pantalla ni de
   una orientación. */
main{padding:16px;display:grid;grid-template-columns:minmax(0,1fr);grid-auto-rows:max-content;gap:14px;align-content:start}

.card{
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:16px;
  padding:16px;
}
.card-hero{
  background:var(--card-on-hero);
  color:var(--card-on-hero-ink);
  border-radius:18px;
  padding:20px;
  box-shadow:var(--shadow);
}
.card-hero .muted,.card-hero small{color:var(--card-on-hero-muted)}
/* .card-hero es SIEMPRE blanca (--card-on-hero, ver :root arriba) sin
   importar modo claro/oscuro — pero .section-label/.row-right heredan
   por defecto --muted-2/--muted de PÁGINA (pensadas para .card, que sí
   cambia con el tema). Necesario ahora que Límites/Categorías de Inicio
   usan .card-hero (blancas en la Figura 3) en vez de .card. */
.card-hero .section-label,.card-hero .row-right{color:var(--card-on-hero-muted)}

/* ============================================================
   ORDEN CORRECTIVA — Fidelidad visual de Inicio (Manual §3.3,
   Figura 3 / image4.png, "referencia visual prioritaria del
   proyecto"). Reconstrucción completa de encabezado, resumen
   financiero, accesos rápidos, límites (semanal+mensual) y
   gastos por categoría contra la imagen oficial.

   Nota de fidelidad (logotipo): la Figura 3 muestra un isotipo
   circular multicolor "GP" distinto del logo 4.1 (naranja sobre
   fondo negro) reafirmado en el Manual §3.18/Figura 29 y usado en
   TODA la app por decisión explícita y ya verificada del usuario
   (ver bitácora, Fase 9). Es un conflicto real entre dos fuentes
   oficiales del propio Manual. Se resuelve manteniendo el logo 4.1
   (fuente más específica y ya confirmada por el usuario para la
   identidad de marca) en la posición/tamaño que indica esta imagen,
   en vez de introducir un segundo isotipo distinto no aprobado
   como variante de marca. Documentado también en la respuesta al
   usuario y en la bitácora — no es una resolución silenciosa.

   Nota de fidelidad (degradado): esta imagen (Figura 3) muestra un
   degradado que se inclina a violeta en la esquina inferior derecha,
   distinto del degradado azul puro ya verificado con pipeta para
   Login/Crear cuenta (Imagen_01/02, ver tools/color-notes.md — esa
   corrección fue explícitamente "sin violeta" pero basada SOLO en
   las imágenes de Login, no en esta). Como Inicio nunca se había
   muestreado con la misma pipeta (quedaba "pendiente" en
   color-notes.md) y tiene su propia imagen oficial distinta a la de
   Login, se le da su propio degradado --hero-grad-home en vez de
   alterar el ya verificado de Login/Crear cuenta. */
/* Tanda 1 - Punto 5 (área segura superior): padding-top propio con
   env(safe-area-inset-top), mismo patrón ya usado en .topbar (ver más
   arriba). env() devuelve 0 en dispositivos sin notch/isla dinámica, así
   que esto nunca reduce el espacio ya existente en pantallas normales --
   solo lo aumenta donde el sistema realmente lo necesita, para que el
   logo/perfil/campana queden completamente visibles y pulsables sin
   manipular el teléfono. Se define aquí (después de .hero en el archivo)
   para que gane por orden de cascada sobre el padding-top corto de
   .hero, sin tocar esa regla general (usada también por Login/Crear
   cuenta, fuera del alcance de esta tanda). */
.hero-home{
  /* .hero-home hereda también la clase base .hero (ver HTML:
     class="hero hero-home"), que declara su propio
     `background:var(--hero-grad)` -- sin este `background:none`,
     .hero-home seguiría pintando esa segunda instancia independiente
     del degradado (recalculada sobre su propia caja corta) por encima
     del único fondo real que ahora vive en #screen-home, reproduciendo
     exactamente la misma división visual que esta corrección busca
     eliminar. Misma técnica que "#screen-more .hero{background:none}"
     más abajo. */
  background:none;
  padding-top:calc(26px + env(safe-area-inset-top));
  /* Tanda 4 (Punto 4): antes vivía como style="padding-bottom:22px" en
     el HTML -- movido aquí (mismo valor) para que la media query de
     orientación horizontal más abajo pueda reducirlo sin !important. */
  padding-bottom:22px;
}
/* FASE 10 (corrección puntual, "Extender el estilo de fondo en Inicio")
   + CORRECCIÓN PUNTUAL (prueba local en VS Code, "eliminar la división
   visual restante"):

   Primera vuelta (ya documentada, correcta en su diagnóstico original):
   antes el Estilo de fondo solo vivía en .hero-home (el bloque
   superior) -- en cuanto <main> empezaba a dibujar "Límites de gasto"/
   "Gastos por categoría", el fondo volvía de golpe al --bg plano de
   toda la app, dejando una franja/corte detrás de esas tarjetas y hacia
   abajo. Se agregó ESTA regla (#screen-home) para pintar el mismo
   degradado sobre toda la pantalla.

   CAUSA REAL de la división que TODAVÍA se percibía (investigada en
   esta corrección, no asumida): .hero-home seguía declarando su PROPIO
   `background`, la misma variable que #screen-home, pero un
   `linear-gradient(...)` se recalcula según el tamaño de la caja de
   CADA elemento que lo declara -- el degradado de .hero-home (una caja
   corta, solo el header) y el de #screen-home (la pantalla completa,
   mucho más alta) NO son el mismo degradado renderizado una sola vez,
   son DOS instancias independientes escaladas a alturas distintas, así
   que el tono exacto no coincidía justo donde terminaba el header. Esa
   nota anterior ("cero costo de la redundancia") era un supuesto sin
   verificar -- esta corrección lo comprueba y lo corrige: se quita el
   `background` propio de .hero-home (arriba) para que exista una ÚNICA
   fuente de degradado por pantalla, calculada una sola vez, visible a
   través de .hero-home (que ya no pinta nada propio encima). Las
   tarjetas (.card-hero/.card) siguen pintando su propio fondo blanco/
   superficie por encima sin cambios, así que el degradado nunca se ve
   DENTRO de ellas -- solo detrás y alrededor. La barra de navegación
   (.tabs) tampoco se toca. Responde de forma dinámica a cualquier
   Estilo de fondo (sólido, degradado, arcoíris) y a modo claro/oscuro
   porque reutiliza la misma fuente de verdad (--hero-grad-home/
   --hero-grad) que ya gobierna toda la app.

   Nota sobre la altura (investigada, no se duplica sin necesidad): se
   retira el min-height:100dvh que llevaba esta regla -- no hacía falta
   (redundante, no incorrecto): ".screen.active:not(.screen-auth)" ya
   fija height:100dvh en TODA pantalla activa salvo Login (ver
   "Adaptación a orientación horizontal"/scroll interno, más abajo), así
   que #screen-home ya tiene su altura completa garantizada desde ahí.
   Esa misma regla hace que <main> se desplace DENTRO de su propia caja
   (flex:1;overflow-y:auto) en vez de la página -- el fondo vive en el
   contenedor exterior de altura fija que nunca se mueve/recorta, así
   que nunca "se acaba" ni cambia de tono al hacer scroll: solo las
   tarjetas se deslizan por encima de él. */
/* CORRECCIÓN DE APARIENCIA (prioridad Modo vs Estilo de fondo): idem
   #screen-more, ver esa nota -- --screen-bg-home en vez de
   --hero-grad-home. */
#screen-home{background:var(--screen-bg-home,var(--screen-bg))}

/* CORRECCIÓN PUNTUAL (Punto 2 de esta orden, "Unificar Estilo de fondo
   en todas las pantallas") — AUDITORÍA (Paso 1) realizada antes de
   escribir una sola línea de código, tal como pedía la orden:

   Se inspeccionó el estado real (no se asumió) de las 41 pantallas
   listadas en el alcance (Historial, Bancos, Metas, Apariencia, Perfil,
   Calendario financiero, Moneda, Idioma, Categorías, Seguridad, Manual/
   Soporte/Acerca de y el resto de pantallas secundarias). Resultado de
   la auditoría, confirmado con getComputedStyle() en vivo:

     - Ninguna de estas pantallas tenía background-image alguno --
       backgroundImage:"none" en las 41, sin importar qué "Estilo de
       fondo" estuviera seleccionado (se probó explícitamente con
       arcoíris al 60%, el caso más visible, y no se veía ningún color).
     - Su fondo real era el de #screen-home (--bg), plano y neutro,
       heredado de .app/body -- una variable independiente del "Estilo
       de fondo" (solo depende de claro/oscuro, ver :root/:root[data-
       mode="dark"] más arriba), así que la selección de color/degradado/
       arcoíris del usuario JAMÁS llegaba a mostrarse en ninguna de
       estas pantallas. No era una costura (dos fuentes en conflicto,
       el bug que sí tenían Inicio/Más) sino ausencia total de fondo.

   FIX (Paso 2, "implementación mínima"): una única fuente de fondo por
   pantalla, mismo patrón ya usado en #screen-home/#screen-more --
   `background:var(--hero-grad)` en el contenedor de cada pantalla, para
   que el degradado cubra toda la pantalla de forma continua "detrás del
   contenido" (Punto 4.1 de la orden). <main> sigue sin fondo propio
   (transparente) y .list-card/.card/.field siguen opacas (var(--surface)/
   var(--surface-2)), así que el fondo nunca entra a las tarjetas,
   formularios ni bloques internos -- solo el espacio de atrás y
   alrededor, exactamente como pide el Punto 4.2 ("el fondo debe afectar
   el espacio de atrás y alrededor, no el interior de los cuadros").

   DECISIÓN DE ALCANCE (documentada, no improvisada): la barra superior
   (.topbar, back-btn + título) de estas 41 pantallas se deja SIN TOCAR
   a propósito -- no se le quita su fondo propio (var(--bg), plano,
   neutro) ni se le cambia color de texto/ícono. Motivo: a diferencia de
   .hero-home/.hero (usados solo en Inicio/Más), .topbar nunca pintó el
   degradado -- no hay "reinicio de degradado al terminar el
   encabezado" porque el encabezado nunca tuvo degradado que reiniciar;
   es simplemente la barra de navegación neutra que ya usa toda la app
   (incluida en pantallas fuera de este alcance). Tocar su color de
   texto/ícono para exponerla al degradado habría sido un cambio
   cosmético no solicitado (Punto 1, "no cambies nada fuera del alcance
   exacto") con riesgo real de legibilidad (texto oscuro/claro fijo por
   modo, no preparado para sentarse sobre cualquier tono de degradado o
   arcoíris) -- se documenta aquí en vez de improvisarlo, disponible
   para una orden futura si se pide explícitamente.

   Pantallas corregidas con esta única regla (Paso 2, exactamente las
   que la auditoría marcó como pendientes -- ninguna de las que ya
   estaban bien, que son solo #screen-home y #screen-more, se vuelve a
   tocar aquí): */
#screen-movements,
#screen-addMovement,
#screen-goals,
#screen-goalForm,
#screen-profile,
#screen-editProfile,
#screen-profileInfo,
#screen-secureLogin,
#screen-accountActivity,
#screen-financeCalendar,
#screen-security,
#screen-appearance,
#screen-colorCustomize,
#screen-currency,
#screen-language,
#screen-notifications,
#screen-limits,
#screen-categories,
#screen-catEditor,
#screen-catColor,
#screen-reports,
#screen-backup,
#screen-backupCloud,
#screen-backupCloudDestino,
#screen-bancos,
#screen-bankForm,
#screen-bankSearch,
#screen-actualizaciones,
#screen-documents,
/* FASE 2.2.1: #screen-documents ya recibía el fondo global de esta lista;
   sus 3 pantallas nuevas (categoría/clasificar/visor) son extensión directa
   de "Mis documentos" -- se agregan aquí para que el fondo personalizado
   no "desaparezca" al entrar a una categoría y solo se vea en el hub
   (hallazgo de la propia validación visual de esta fase, ver reporte). */
#screen-docCategory,
#screen-docClassify,
#screen-docViewer,
#screen-support,
#screen-support-report,
#screen-support-suggestion,
#screen-support-sent,
#screen-manual,
#screen-manual-topic,
#screen-novedades,
#screen-about,
#screen-about-info,
#screen-licenses,
#screen-legal,
#screen-legal-doc{
  /* CORRECCIÓN DE APARIENCIA (prioridad Modo vs Estilo de fondo): idem
     #screen-more/#screen-home, ver la nota junto a "--screen-bg" en
     render.js -- estas 41 pantallas pasan a leer --screen-bg en vez de
     --hero-grad. */
  background:var(--screen-bg);
}

/* CORRECCIÓN PUNTUAL — CONTRASTE DE TEXTOS SOBRE ESTILOS DE FONDO.

   AUDITORÍA (antes de tocar código): se recorrieron programáticamente
   las 41 pantallas del alcance anterior, comparando el color calculado
   de cada elemento contra var(--muted)/var(--muted-2) y subiendo por
   sus ancestros hasta encontrar el primer fondo no transparente --
   si ese fondo resultaba ser el degradado de la propia pantalla (no una
   tarjeta/.list-card/.card/.info-box/.field opaco de por medio), el
   texto quedó marcado como "sobre fondo dinámico". Balance encontrado:
   ~101 usos de estas clases en el alcance, de los cuales exactamente
   los que se listan abajo (y ningún otro) están directamente sobre el
   degradado; el resto ya vive dentro de una tarjeta/lista opaca y NO se
   toca (sigue usando var(--muted)/var(--muted-2) normal, que ya
   responde bien a claro/oscuro sobre una superficie blanca/oscura
   plana). Cada selector usa el atributo [data-i="..."] o el id ya
   existente del elemento (no la clase sola) precisamente para no
   arrastrar sin querer alguna otra instancia de la misma clase que sí
   esté ya bien -- verificado 1 a 1 contra la auditoría, no por
   aproximación.

   No se tocó ningún tamaño, peso de fuente, mayúscula/espaciado ni la
   jerarquía visual "secundaria" de estos textos -- únicamente su color
   y una sombra de texto (ver --muted-on-fondo/--muted-2-on-fondo/
   --text-shadow-on-fondo, definidas arriba junto al resto de
   variables). No se tocó la implementación del "Estilo de fondo" en sí
   (#screen-X{background:var(--hero-grad)}, bloque anterior) ni ninguna
   tarjeta/formulario/botón/ícono/topbar/nav. Login, Crear cuenta y
   Olvidé mi contraseña no aparecen en esta lista -- no fueron parte de
   la auditoría ni se tocan aquí. */

/* --- Etiquetas tipo "MIS CATEGORÍAS DE GASTOS" (.section-label, peso
   fuerte/mayúsculas) --- */
#screen-more [data-i="settings.otros"],
#screen-goalForm [data-i="goals.tipoAhorro"],
#screen-goalForm [data-i="goals.frecuenciaAhorro"],
#screen-profile [data-i="profile.accesosRapidos"],
#screen-accountActivity [data-i="accountActivity.historialInicio"],
#screen-accountActivity [data-i="accountActivity.cambiosSeguridad"],
#screen-accountActivity [data-i="accountActivity.dispositivos"],
#screen-accountActivity [data-i="accountActivity.sesiones"],
#screen-security [data-i="security.biometriaTitulo"],
#screen-appearance [data-i="appearance.modo"],
#screen-appearance [data-i="appearance.estiloIconos"],
#screen-appearance [data-i="appearance.estiloFondo"],
#screen-colorCustomize [data-i="cats.codigoColor"],
#screen-colorCustomize [data-i="appearance.vistaPreviaTiempoReal"],
#screen-colorCustomize [data-i="appearance.coloresRapidos"],
#screen-currency [data-i="currency.monedaActual"],
#screen-currency [data-i="currency.otrasMonedas"],
#screen-language [data-i="language.elegirIdioma"],
#screen-notifications [data-i="notifications.preferencias"],
#screen-categories #catListSectionLabel,
#screen-catEditor [data-i="cats.icono"],
#screen-catEditor [data-i="cats.color"],
#screen-backup [data-i="backup.historial"],
#screen-backupCloudDestino [data-i="backup.frecuenciaCopia"],
#screen-bankForm #bankTypeLabel,
#screen-bankForm #bankColorLabel,
#screen-bankSearch [data-i="bankSearch.resultados"],
/* FASE 2.2.1: las 4 líneas "#screen-documents [data-i="documents.*"]" que
   vivían aquí (Fase 8) corregían el contraste de las antiguas etiquetas
   .section-label "Facturas y comprobantes"/"Fotografías"/"Cierres
   mensuales (PDF)"/"Respaldos (JSON)". Esas etiquetas ya no existen --
   ahora esos mismos 4 textos son <strong> DENTRO de un .list-row
   clickeable (título de categoría, no encabezado de sección), y ese
   selector por atributo [data-i] los seguía alcanzando igual (no filtra
   por tag), aplicándoles el color apagado equivocado (--muted-2-on-fondo)
   sobre fondo personalizado -- hallazgo de la validación visual de esta
   fase (captura con "Estilo de fondo" activo: 3 de los 4 títulos de
   categoría se veían desvaídos/casi ilegibles sobre la tarjeta blanca).
   Se retiran para que esos <strong> usen el mismo color ya correcto que
   cualquier otro título de .list-row (ver "Documentos personales", el
   único que no estaba en esta lista y por eso ya se veía bien). -->
#screen-manual [data-i="manual.indiceContenido"],
#screen-about-info [data-i="about.acercaDeLabel"],
#screen-about-info [data-i="about.detallesLabel"],
#screen-about-info [data-i="about.desarrolladorLabel"]{
  color:var(--muted-2-on-fondo);
  text-shadow:var(--text-shadow-on-fondo);
}

/* --- Subtítulos/párrafos sueltos, etiquetas de campo de formulario y
   otros textos secundarios de peso normal (.muted/.section-sublabel/
   <label> de .field/.row-chevron usado como estado) ---
   Excepción documentada: #goalsIntroMasTardeBtn ("Más tarde" en Metas)
   no usa var(--muted) sino var(--accent) en línea -- se incluye igual
   porque es uno de los ejemplos señalados explícitamente en esta orden
   y tiene el mismo síntoma (peso visual secundario, directamente sobre
   el fondo dinámico); al ser el mismo --accent de la propia paleta que
   alimenta el degradado, su contraste es aún más frágil en fondo
   sólido (texto y fondo comparten familia de color). */
#screen-more [data-i="settings.subtitulo"],
#screen-goals [data-i="goals.intro.subtitulo"],
#screen-secureLogin [data-i="secureLogin.intro"],
#screen-secureLogin [data-i="secureLogin.nota"],
#screen-security [data-i="security.biometriaNota"],
#screen-appearance [data-i="appearance.personalizaEstilo"],
#screen-appearance [data-i="appearance.estiloFondoSub"],
#screen-currency [data-i="currency.convertirNota"],
#screen-language [data-i="language.subtitulo"],
#screen-backupCloudDestino [data-i="backup.seleccionaDestinoSub"],
#screen-backupCloudDestino [data-i="backup.seAbriraSelector"],
#screen-bankForm [data-i="bancos.avisoManualCorto"],
#screen-bankForm [data-i="bancos.cuentaActivaSub"],
#screen-bankSearch [data-i="bankSearch.habilitaraPronto"],
#screen-actualizaciones [data-i="actualizaciones.buscarNota"],
#screen-novedades [data-i="novedades.sub"],
#screen-reports #reportSummaryPeriodLabel,
#screen-reports #reportPeriodLabel,
#screen-profile #profileEmail,
#screen-profile #profileMemberSince,
#screen-support-report #reportDescCount,
#screen-support-suggestion #sugDescCount,
#screen-manual #manualSearchEmpty,
#screen-goals #goalsIntroMasTardeBtn,
#screen-catEditor .row-chevron,
#screen-catColor .row-chevron,
#screen-catColor #catColorCurrentHex,
#screen-bankForm #bankActiveState,
#screen-addMovement .field label,
#screen-goalForm .field label,
#screen-editProfile .field label,
#screen-security .field label,
#screen-catEditor .field label,
#screen-bankForm .field label,
#screen-support-report .attach-limits,
#screen-support-suggestion .attach-limits,
#screen-goals .empty-state-standalone,
#screen-bancos .empty-state-standalone{
  color:var(--muted-on-fondo);
  text-shadow:var(--text-shadow-on-fondo);
}
/* .empty-state-standalone también pinta el ícono en el mismo tono
   (antes var(--muted-2) fijo) -- mismo criterio, un ícono decorativo
   con bajo contraste sobre el degradado es el mismo problema que el
   texto de al lado. Selectores con id+clase (en vez de la clase sola)
   a propósito -- necesitan más especificidad que la regla base
   ".muted{color:var(--muted)}"/".empty-state{color:var(--muted)}", que
   aparecen más abajo en este mismo archivo y ganarían el empate de
   especificidad si solo se usara la clase sola. */
#screen-goals .empty-state-standalone svg.icon,
#screen-bancos .empty-state-standalone svg.icon{color:var(--muted-on-fondo)}

/* CORRECCIÓN GLOBAL — CONTRASTE ADAPTATIVO (hallazgo nuevo de esta
   auditoría, no cubierto por la corrección anterior): #profileName (el
   nombre en negrita de Perfil) no tenía ningún color propio -- heredaba
   "body{color:var(--ink)}", plano, sin relación con el fondo. Es texto
   PRINCIPAL (negrita, mismo peso visual que un título), no secundario --
   usa var(--hero-ink) (la MISMA variable que ya usa .hero para su título
   grande), no las variables "muted" de arriba. */
#screen-profile #profileName{
  color:var(--hero-ink);
  text-shadow:var(--text-shadow-on-fondo);
}

/* CORRECCIÓN GLOBAL — CONTRASTE EN SUPERFICIES PROPIAS CON TINTE DE
   ACENTO, activo con Estilo de fondo (auditoría en iPhone real: Bancos
   "cuadro informativo", Metas "¡Es hora de saber...!", Perfil "Cuenta
   activa", Moneda "Moneda actual" perdían contraste con ciertos fondos).

   AUDITORÍA (antes de tocar código): se recorrió TODA la hoja de estilos
   y el HTML buscando cualquier superficie con fondo translúcido/coloreado
   (no var(--surface) ni var(--card-on-hero), ambos opacos) que pudiera
   quedar directamente sobre el degradado personalizado. Resultado: el
   patrón se repite exactamente igual en 4 sitios --
   .info-box/.current-choice-card/.badge-accent (Perfil) y dos tarjetas ad
   hoc con "background:var(--accent-soft)" en Bancos/Buscar bancos --
   TODAS usan var(--accent-soft) como fondo (el MISMO accent que ya
   alimenta el degradado, ~15% de opacidad) con texto en var(--ink)/
   var(--ink-soft)/var(--muted)/var(--accent) fijos, ajenos al fondo
   personalizado. .badge-accent aparece TAMBIÉN en Manual/Acerca de, pero
   ahí vive dentro de una .card blanca opaca -- no se toca, de ahí el
   selector por data-i en vez de la clase sola (mismo criterio que el
   resto de este archivo). Aparte, dos textos de Metas nunca se habían
   conectado al sistema adaptativo: el título de la introducción (sin
   ninguna variable, heredaba --ink plano) y "Más tarde" (color:var(--accent)
   en línea en el HTML).

   ESTRATEGIA: no se crea un segundo sistema ni una nueva fórmula de
   luminancia -- se reutiliza la MISMA decisión clara/oscura que ya
   calcula fondoNeedsDarkForeground() una sola vez por render() (ver
   render.js), a través de las 5 variables ya existentes
   (--hero-ink/--hero-ink-soft/--muted-on-fondo/--muted-2-on-fondo/
   --text-shadow-on-fondo). Justificación de por qué esa MISMA decisión
   sigue siendo válida aquí: --accent-soft mezcla solo ~15% de un color
   que pertenece a la MISMA familia tonal que ya alimenta el degradado
   (mismo dato de origen, state.settings.accent) -- una mezcla de un 15%
   del mismo tono no cruza el umbral de decisión salvo casos límite ya
   cubiertos por el margen del umbral (0.42, ver justificación completa en
   render.js). No se persigue el color EXACTO percibido píxel por píxel
   (explícitamente no pedido) -- se evita la combinación realmente
   ilegible ya confirmada en iPhone.

   Todos los selectores usan [data-fondo-activo="true"] -- con el fondo
   personalizado apagado, ninguna de estas reglas aplica y el
   comportamiento/color queda IDÉNTICO al ya aprobado (mismo criterio que
   el resto del archivo, ver comentario de arriba junto a
   "[data-fondo-activo=\"false\"]"). En los dos casos donde el color actual
   está fijado con `style="color:..."` en línea en el HTML (Bancos/Buscar
   bancos, "Más tarde" de Metas) un selector externo normal NO puede
   ganarle a un estilo en línea por especificidad -- se usa !important
   ÚNICAMENTE ahí, acotado a estos 3 selectores exactos y solo dentro de
   [data-fondo-activo="true"], nunca de forma general. */
[data-fondo-activo="true"] .info-box{
  color:var(--hero-ink-soft);
  text-shadow:var(--text-shadow-on-fondo);
}
[data-fondo-activo="true"] .current-choice-card .row-text strong{
  color:var(--hero-ink);
  text-shadow:var(--text-shadow-on-fondo);
}
[data-fondo-activo="true"] .current-choice-card .row-text span{
  color:var(--hero-ink-soft);
}
[data-fondo-activo="true"] #screen-profile [data-i="profile.cuentaActiva"]{
  color:var(--hero-ink);
}
[data-fondo-activo="true"] #screen-bancos [data-i="bancos.avisoManual"],
[data-fondo-activo="true"] #screen-bankSearch [data-i="bankSearch.aviso"]{
  color:var(--hero-ink) !important;
  text-shadow:var(--text-shadow-on-fondo);
}
[data-fondo-activo="true"] #screen-goals [data-i="goals.intro.titulo"]{
  color:var(--hero-ink);
  text-shadow:var(--text-shadow-on-fondo);
}
[data-fondo-activo="true"] #screen-goals #goalsIntroMasTardeBtn{
  color:var(--hero-ink-soft) !important;
}

/* Tanda 4 (Punto 4): antes style="display:flex;flex-direction:column;
   gap:14px;margin-top:20px" en línea en el HTML -- mismos valores,
   movidos a clase para poder comprimir gap/margin-top solo en
   horizontal (ver media query) sin tocar el valor por defecto (vertical). */
.home-hero-stack{display:flex;flex-direction:column;gap:14px;margin-top:20px}
/* Tanda 4 (Punto 8): título compartido entre "Límites de gasto" y
   "Gastos por categoría" en Inicio -- antes solo el primero llevaba
   este refuerzo (font-size/weight) en línea, heredado de la Tanda 1
   (Punto 8: "título con más peso"), y el segundo se quedaba en el
   tamaño/peso base de .section-label (.72rem/800), viéndose más débil.
   Se sube el más débil al nivel del que ya estaba aprobado -- no se
   reduce ninguno -- y se centraliza en una sola clase para que ambos
   títulos no puedan volver a desalinearse por separado. */
/* Selector combinado (.section-label.home-block-title, no solo
   .home-block-title) a propósito: ambas clases conviven en el mismo
   <span> en el HTML, y .section-label se define MÁS ABAJO en este mismo
   archivo con la misma especificidad (una sola clase) -- sin combinar
   ambas clases aquí, la regla de .section-label que aparece después en
   la cascada terminaría ganando y el título volvería a quedar en .72rem/
   800 en vez de .82rem/900. Verificado con Playwright que esto se
   corrigió (antes: font-size computado 11.52px/peso 800 pese a esta
   regla; después: 13.12px/900 en ambos títulos). */
.section-label.home-block-title{font-size:.82rem;font-weight:900;padding:0}
/* Tanda 4 (Punto 4): paddings de las tarjetas de Inicio (Límites,
   Categorías, Mis bancos) -- antes en línea (style="padding:14px 18px
   4px" / style="padding:22px" x2), mismos valores movidos a clase para
   poder comprimirlos solo en horizontal. */
.home-limits-card{padding:14px 18px 4px}
.home-section-card{padding:22px}

.home-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;position:relative}
.home-header-side{display:flex;flex:none;width:48px}
.home-header-side.is-right{justify-content:flex-end}
.home-header-center{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;gap:2px;min-width:0}
/* ORDEN DE CORRECCIÓN (Punto 1, "logo en Inicio más grande y con más
   presencia"): +38% adicional sobre el tamaño ya aumentado en la Tanda 5
   (45px). border-radius/box-shadow de "tarjeta" se quitan (ya no hay
   fondo cuadrado que redondear -- ver logo-mark.png); el realce visual
   ahora lo da el filter:drop-shadow de .logo-guard, que sigue el
   contorno real de la billetera. */
.home-header-center img{width:62px;height:62px;margin-bottom:2px}
.home-header-center h1{margin:0;font-size:1.15rem;font-weight:800}
/* CORRECCIÓN PUNTUAL (contraste en modo claro + fondo base): color fijo
   #dbe0ff reemplazado por var(--hero-ink-soft,#dbe0ff) -- mismo tono
   exacto mientras el Estilo de fondo está activo, pasa a var(--muted)
   (texto secundario normal del modo) cuando no lo está. Ver la nota
   completa junto a --hero-ink-soft en :root. */
.home-header-center p{margin:0;color:var(--hero-ink-soft,#dbe0ff);font-size:.82rem}
/* Tanda 1 - Punto 6: la opción de usuario (acceso a Perfil) se ve un
   poco más grande que el ".avatar" genérico (48px vs 44px) y con un
   halo/sombra propios para "sobresalir mejor" sobre el degradado del
   hero, sin tocar ".avatar" en sí (compartida con Perfil/Editar perfil,
   fuera de alcance de esta tanda). Sigue siendo el mismo <button> con
   data-nav="profile", pulsable igual que antes. */
.avatar-home{
  width:48px;height:48px;
  box-shadow:0 4px 14px #00000055,0 0 0 2px #ffffff40;
}
.avatar-home .icon{width:24px;height:24px}
/* Tanda 1 - Punto 7: se quita el fondo circular de ".icon-btn" -- la
   campana ya no se ve "encerrada en un círculo". Conserva un área táctil
   de 44x44 (mínimo accesible) y un ícono más grande que antes (26px vs
   20px) para seguir siendo claramente pulsable y no confundirse con un
   adorno decorativo; "position:relative" se mantiene para anclar el
   indicador de notificaciones pendientes. No cambia la funcionalidad de
   notificaciones ya aprobada (Tarea #97), solo la presentación. */
/* CORRECCIÓN PUNTUAL (contraste en modo claro + fondo base): color fijo
   #fff reemplazado por var(--hero-ink,#fff) -- MISMA variable que ya usa
   .hero para su propio texto (blanco cuando el Estilo de fondo está
   activo, var(--ink) cuando no) -- el ícono deja de "casi desaparecer"
   sobre un fondo base claro. */
.home-bell-wrap{
  position:relative;width:44px;height:44px;flex:none;
  display:grid;place-items:center;
  background:transparent;border:none;color:var(--hero-ink,#fff);
  filter:drop-shadow(0 2px 6px #00000055);
}
.home-bell-wrap .icon{width:26px;height:26px}
.home-bell-badge{
  position:absolute;top:2px;right:2px;min-width:17px;height:17px;padding:0 4px;
  border-radius:99px;background:var(--red);color:#fff;font-size:.65rem;font-weight:800;
  display:grid;place-items:center;border:2px solid var(--bg,#0a0e1f);line-height:1;
}

/* Tarjeta oscura translúcida "flotando" sobre el degradado del hero
   (Resumen de este mes, Agregar ingreso/gasto) — distinta de
   .card-hero (siempre blanca, ver arriba) que se reserva para las
   tarjetas Límites/Categorías que SÍ son blancas en la referencia. */
.card-on-hero-dark{
  background:#0a1230ee;border:1px solid #ffffff1f;border-radius:18px;
  padding:16px;color:#fff;backdrop-filter:blur(2px);
}
.card-on-hero-dark .muted{color:#c6cdec}

.stat-col{flex:1;min-width:0;text-align:center;display:flex;flex-direction:column;align-items:center;gap:7px}
.stat-col + .stat-col{border-left:1px solid #ffffff26}
.stat-label{font-size:.62rem;font-weight:800;letter-spacing:.04em;text-transform:uppercase}
.stat-amount{font-size:.8rem;font-weight:800;max-width:100%;overflow-wrap:break-word}
.stat-icon{
  width:34px;height:34px;border-radius:50%;flex:none;
  display:grid;place-items:center;color:#fff;
}
.stat-icon .icon{width:17px;height:17px}
.text-blue{color:var(--blue)}

.quick-action-row{display:flex;gap:8px}
.quick-action-card{
  flex:1;min-width:0;display:flex;align-items:center;gap:6px;
  background:#0a1230ee;border:1px solid #ffffff1f;border-radius:16px;
  padding:10px;color:#fff;text-align:left;
}
.quick-action-card.is-income{border-color:#2dd4bf73}
.quick-action-card strong{font-size:.76rem;font-weight:700;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.quick-action-card .stat-icon{width:30px;height:30px;flex:none}
.quick-action-card .stat-icon .icon{width:18px;height:18px}

/* Límites de gasto — semanal + mensual (Figura 3). Cada fila reutiliza
   .progress-track/.progress-fill ya existentes (ver más abajo) para no
   duplicar el sistema de barras de progreso. */
.limit-row{display:flex;flex-direction:column;gap:6px}
.limit-row + .limit-row{margin-top:16px}
.limit-row-head{display:flex;align-items:center;gap:10px}
.limit-row-head strong{flex:1;font-size:.9rem}
.limit-row-pct{font-weight:800;font-size:.95rem}
.limit-row-amounts{font-size:.78rem;color:var(--card-on-hero-muted);padding-left:44px}

/* Pantalla dedicada de Límites (Tarea #86, Figura 22/23/image21.png):
   filas de alternar de alerta + resumen combinado, dentro de las mismas
   tarjetas .card-hero ya usadas para el bloque de límites en Inicio, para
   mantener consistencia visual entre ambas pantallas. */
.alert-toggle-row{
  display:flex;align-items:center;gap:12px;
  padding:12px 0;border-bottom:1px solid var(--border);
}
.alert-toggle-row .row-text{flex:1;min-width:0}
.alert-toggle-row .row-text strong{display:block;font-size:.86rem;color:var(--card-on-hero-ink)}
.alert-toggle-row .row-text span{display:block;font-size:.74rem;color:var(--card-on-hero-muted);margin-top:1px}
.alert-toggle-row input[type="checkbox"]{width:20px;height:20px;accent-color:var(--accent);flex:none}
.limits-summary-row{display:flex;gap:12px}
.limits-summary-row > div{flex:1;background:#f3f4f8;border-radius:12px;padding:10px 12px}
.limits-summary-row span{display:block;font-size:.72rem;color:var(--card-on-hero-muted);margin-bottom:2px}
.limits-summary-row strong{font-size:1rem;color:var(--card-on-hero-ink)}

.donut-row{display:flex;gap:16px;align-items:center;margin-top:6px}
.donut-canvas-wrap{position:relative;flex:none;width:118px;height:118px}
.donut-canvas-wrap canvas{width:100%;height:100%}
.donut-total{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;pointer-events:none;
}
.donut-total span:first-child{font-size:.66rem;color:var(--card-on-hero-muted);font-weight:700;text-transform:uppercase;letter-spacing:.04em}
/* Tarea #87: se detectó que un total largo (p. ej. "31.500,00 DOP") se
   desbordaba visualmente SOBRE el anillo de color de la dona — M()
   (Intl.NumberFormat) separa el número del código de moneda con un
   ESPACIO DE NO RUPTURA (U+00A0), así que el navegador nunca partía esa
   cadena por su cuenta. overflow-wrap:anywhere fuerza el corte; pero el
   max-width debe ajustarse al AGUJERO TRANSPARENTE de la dona, no al
   cuadrado completo del canvas: drawDonutChart (charts.js) dibuja el
   anillo con rInner = rOuter*0.6, es decir el hueco central mide ~55-58%
   del ancho de .donut-canvas-wrap, no el 88% usado en el primer intento
   de este fix (que dejaba el texto ancho de sobra pero solapado sobre el
   propio anillo de color). Afecta por igual a Inicio (donde ya existía
   este componente) y a Reportes (Tarea #87, que lo reutiliza) —
   corregido una sola vez aquí. */
.donut-total span:last-child{display:block;font-size:.72rem;font-weight:800;color:var(--card-on-hero-ink);max-width:56%;overflow-wrap:anywhere;line-height:1.15}
.donut-legend{flex:1;display:flex;flex-direction:column;gap:7px;min-width:0}
.donut-legend-row{display:flex;align-items:center;gap:7px;font-size:.78rem}
.donut-legend-dot{width:9px;height:9px;border-radius:50%;flex:none}
.donut-legend-row .cat-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:650}
.donut-legend-row .cat-pct{font-weight:800;flex:none}
.donut-legend-row .cat-amt{display:block;font-size:.72rem;color:var(--muted);text-align:right}
/* Tanda 1 - Punto 9: dona y texto de leyenda más grandes SOLO en Inicio
   -- selector con prefijo "#homeCats" para no afectar el mismo
   componente compartido (.donut-*) usado por Reportes (reports.js,
   donutBlockInto), que no forma parte del alcance de esta tanda. */
#homeCats .donut-canvas-wrap{width:140px;height:140px}
#homeCats .donut-legend-row{font-size:.85rem}

.alert-banner{
  display:flex;align-items:center;gap:12px;
  background:#ef444417;border:1px solid #ef444440;border-radius:14px;
  padding:13px 14px;width:100%;text-align:left;
}
.alert-banner .stat-icon{background:var(--red)}
.alert-banner strong{display:block;font-size:.88rem;color:var(--red)}
.alert-banner span{display:block;font-size:.78rem;color:var(--muted);margin-top:1px}
.alert-banner .row-chevron{margin-left:auto;flex:none}

.section-label{
  font-size:.72rem;font-weight:800;letter-spacing:.06em;
  color:var(--muted-2);text-transform:uppercase;
  padding:4px 4px 0;
}
/* Subtítulo opcional bajo .section-label (Idioma, Figura 8/image18.png:
   "El cambio se aplicará en toda la aplicación"). */
.section-sublabel{font-size:.8rem;color:var(--muted);padding:4px 4px 0;margin:0}

/* Caja informativa reutilizable (Idioma, Figura 8/image18.png: "Puedes
   cambiar el idioma cuando quieras desde Ajustes"). Reutiliza --accent-soft
   ya definido para acentos de marca. */
.info-box{
  display:flex;align-items:flex-start;gap:10px;
  background:var(--accent-soft);border-radius:14px;
  padding:12px 14px;font-size:.8rem;color:var(--ink-soft);
}
.info-box-icon{flex:none;width:20px;height:20px;color:var(--accent)}
.info-box-icon svg{width:100%;height:100%}

/* Etiqueta "Idioma actual" (Figura 8/image18.png) sobre el idioma
   seleccionado en #languageList, sustituye visualmente al radio-dot simple
   por un indicador con texto, más fiel a la referencia. */
.lang-current-tag{
  font-size:.68rem;font-weight:800;color:var(--accent);
  white-space:nowrap;
}

/* Cabecera de grupo por día en Movimientos (Figura 19/image7.png):
   etiqueta del día + total neto del día, dentro del propio list-card. */
.mov-day-header{
  display:flex;justify-content:space-between;align-items:center;
  padding:12px 14px 6px;font-weight:800;font-size:.85rem;color:var(--ink);
}
.mov-day-header span:last-child{font-weight:800}

.muted{color:var(--muted)}
.ink-soft{color:var(--ink-soft)}

/* ---- Formularios ---- */
.field{display:grid;gap:6px;margin-bottom:12px}
/* AUDITORÍA HORIZONTAL (complemento Tanda 6): envoltorio opcional para
   agrupar 2 campos en una fila SOLO en horizontal (ver la regla real
   dentro de "@media (orientation:landscape)..." más abajo). En
   cualquier otra circunstancia es "display:contents" -- desaparece por
   completo de la caja de layout, así que los ".field" de adentro se
   comportan exactamente como si este envoltorio no existiera: cero
   cambio visual en el diseño vertical ya aprobado. */
.field-pair{display:contents}
/* Misma mecánica que ".field-pair" pero genérica para 2 controles
   cualesquiera que no sean ".field" (p. ej. 2 botones/label-botón
   consecutivos que hoy ocupan una fila completa cada uno) -- reutilizada
   en vez de crear una tercera variante ad-hoc, mismo criterio de
   "reglas consistentes y reutilizables" pedido en esta auditoría. */
.btn-pair{display:contents}
.field label{font-size:.82rem;font-weight:650;color:var(--muted)}
.input-wrap{
  display:flex;align-items:center;gap:10px;
  background:var(--surface-2);border:1px solid var(--border);
  border-radius:12px;padding:0 14px;min-height:50px;
}
.input-wrap input,.input-wrap select{
  flex:1;background:none;border:0;min-height:48px;color:var(--ink);
  min-width:0;
}
.input-wrap input::placeholder{color:var(--muted-2)}
.input-wrap:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.input-wrap .icon{color:var(--muted);flex:none;display:grid;place-items:center}
.input-wrap button.toggle-eye{color:var(--muted);flex:none}
/* CORRECCIÓN PUNTUAL — FASE 9 (Punto 1, "Nombre del banco" pasa a ser de
   solo lectura tras eliminar la duplicidad con "Buscar bancos"): sin
   cambiar bordes/colores/espaciados generales del campo (todo eso viene de
   .input-wrap de arriba, sin tocar), solo se evita el cursor de texto para
   no sugerir que se puede escribir ahí. */
#bankName[readonly]{cursor:default}
/* CORRECCIÓN PUNTUAL — FASE 9 (Punto 2, insignia de marca del banco
   seleccionado dentro de "Nombre del banco"): contenedor pequeño, MISMO
   espacio que ocupaba el ícono genérico de siempre (ver .input-wrap .icon
   arriba, sin tocarla) -- por defecto (sin banco reconocido) sigue
   mostrando ese mismo ícono sin ningún cambio visual. Solo cuando hay un
   banco reconocido (--brand) se convierte en un círculo de color con el
   monograma de esa marca. */
.bank-name-icon-slot{display:grid;place-items:center;flex:none}
/* Nota: las clases .bg-* (ver más abajo, sección de acentos) SOLO fijan la
   variable --rc; el color de fondo real de .row-icon lo pintan las reglas
   de [data-iconstyle] (Fase 10). Esta insignia vive FUERA de .row-icon (es
   un ícono de campo, no un ícono de fila), así que pinta su propio fondo
   directamente con var(--rc) para no depender de qué "Estilo de íconos"
   esté activo -- mismo bg-* de siempre, un uso más directo de --rc. */
.bank-name-icon-slot--brand{width:22px;height:22px;border-radius:50%;color:#fff;background:var(--rc,var(--accent))}
.bank-name-icon-slot--brand .bank-mono{font-size:.6rem}

input[type="date"],input[type="time"],select{color-scheme:dark}
[data-mode="light"] input[type="date"],[data-mode="light"] input[type="time"],[data-mode="light"] select{color-scheme:light}

/* ---- Botones ---- */
.btn{
  min-height:50px;display:flex;align-items:center;justify-content:center;gap:8px;
  border-radius:12px;font-weight:800;padding:10px 16px;width:100%;
}
.btn-primary{background:linear-gradient(120deg,var(--accent),var(--accent-2));color:#fff}
.btn-secondary{background:var(--surface-2);color:var(--ink);border:1px solid var(--border)}
.btn-outline{background:transparent;border:1.5px solid var(--accent);color:var(--accent)}
.btn-danger{background:var(--red);color:#fff}
.btn-danger-outline{background:transparent;border:1.5px solid var(--red);color:var(--red)}
.btn-success{background:linear-gradient(120deg,#22c55e,#16a34a);color:#fff}
.btn-sm{min-height:38px;padding:6px 12px;width:auto;font-size:.85rem}
.btn:disabled{opacity:.5;pointer-events:none}
.btn:focus-visible,.icon-btn:focus-visible,.back-btn:focus-visible,button:focus-visible{outline:3px solid var(--accent-soft);outline-offset:2px}

/* CORRECCIÓN PUNTUAL — "Cerrar sesión" queda demasiado pegado a la barra
   inferior en Perfil (reporte real de iPhone).

   AUDITORÍA (antes de tocar nada):
     1. El contenedor que posiciona "Cerrar sesión" es el propio <main>
        de #screen-profile: es el último hijo dentro del grid genérico
        de <main> (gap:14px entre filas, ver regla base "main{...}"), sin
        ningún margin/padding propio en el botón (.btn-danger-outline no
        define margin-bottom) -- su posición depende enteramente del
        padding-bottom reservado en <main>.
     2. Ese padding-bottom SÍ existe y es GLOBAL, no ausente: la regla
        base ".screen.active:not(.screen-auth) > main{padding-bottom:
        96px}" (ver más abajo en este archivo) ya reserva espacio para
        que el último elemento de CUALQUIER pantalla no quede tapado por
        .tabs (barra inferior fija, position:fixed).
     3. Causa real encontrada (no asumida, comparando ambas reglas):
        ".tabs" calcula su propio alto con
        "padding:8px ... calc(10px + env(safe-area-inset-bottom))" +
        54px de alto de pestaña -- su alto CRECE con el safe-area real
        del dispositivo (34px en iPhone con Face ID/Dynamic Island). El
        padding-bottom:96px de <main>, en cambio, es un número FIJO que
        NO incluye env(safe-area-inset-bottom) -- se calibró pensando en
        el alto de .tabs SIN ese inset (~72-88px + colchón), así que en
        un dispositivo sin muescas el colchón resultante es de ~20-24px
        (correcto), pero en un iPhone con home indicator real, .tabs
        crece por su propio safe-area mientras la reserva de <main> se
        queda igual -- el colchón se reduce o desaparece, y el botón
        puede terminar parcialmente detrás de la barra.
     4. Esto podría, en teoría, afectar a cualquiera de las 41 pantallas
        con contenido que llegue justo hasta el final -- pero el reporte
        es específico de Perfil (donde "Cerrar sesión" es, en la mayoría
        de los casos, la última fila visible pegada al borde), así que la
        corrección se aplica ÚNICAMENTE aquí, sin tocar la regla global
        compartida por las otras 40 pantallas ni la barra inferior en sí.

   SOLUCIÓN (mínima, específica de #screen-profile): se suma
   env(safe-area-inset-bottom) al padding-bottom ya existente de 96px/
   64px (portrait/horizontal, ver el valor correspondiente más abajo en
   la media query de horizontal) -- en un dispositivo sin muescas
   (env=0) el resultado es IDÉNTICO a hoy (96px, cero cambio visual); en
   un iPhone con inset real, el colchón crece exactamente lo mismo que
   crece .tabs, preservando siempre los mismos ~20-24px de separación
   visible entre "Cerrar sesión" y la barra inferior, sin importar el
   modelo de iPhone. Selector con id (mayor especificidad que la regla
   global de clases) para no necesitar !important. */
#screen-profile > main{padding-bottom:calc(96px + env(safe-area-inset-bottom))}
/* FASE 2.2.2 (Punto 14, hallazgo real en iPhone): mismo problema exacto ya
   diagnosticado arriba para #screen-profile -- "Guardar documento" es el
   último elemento de #screen-docClassify > main, pegado al padding-bottom
   fijo de 96px, que no crece con env(safe-area-inset-bottom) real del
   dispositivo. Misma solución mínima y localizada (sin tocar la regla
   global de las demás pantallas ni la barra inferior). */
#screen-docClassify > main{padding-bottom:calc(96px + env(safe-area-inset-bottom))}

.btn-row{display:flex;gap:10px}
.btn-row .btn{flex:1}

/* ---- Filas de lista (settings-style) ---- */
/* CORRECCIÓN CRÍTICA (auditoría horizontal v2): el tamaño mínimo automático
   de un elemento con overflow≠visible dentro de un contenedor grid se
   resuelve a 0 según la especificación CSS, en vez de basarse en su
   contenido. Como <main> es SIEMPRE un grid (display:grid en la regla base
   de main, ver más abajo) y tiene una altura definida (flex:1;min-height:0
   dentro de .screen.active>main), cualquier .list-card —que tiene
   overflow:hidden— podía colapsar a una fracción de su alto real (se
   detectaron colapsos de 12px, 68px, 129px, 211px, 569px según el caso),
   ocultando filas sin posibilidad de scroll. Esto afectaba TANTO vertical
   como horizontal (ej. Historial con más de ~10 movimientos ya perdía
   filas en modo vertical, un bug preexistente a esta sesión). La corrección
   universal es forzar min-height:max-content para que el navegador calcule
   la altura real según el contenido en vez de aplicar el mínimo automático
   de 0. Se aplica en la regla base (no solo en la media query horizontal)
   porque el problema es independiente de la orientación. */
.list-card{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden;min-height:max-content}
.list-row{
  display:flex;align-items:center;gap:12px;
  padding:14px 16px;
  border-bottom:1px solid var(--border);
  width:100%;text-align:left;
}
.list-row:last-child{border-bottom:0}
.list-row:active,.list-row:hover{background:var(--surface-hover)}
.row-icon{
  width:38px;height:38px;border-radius:50%;flex:none;
  display:grid;place-items:center;color:#fff;font-size:1.05rem;
}
/* CORRECCIÓN PUNTUAL — FASE 9 (Punto 2, insignia/monograma de marca de
   banco): mismo patrón de .row-icon svg use{color:inherit} de abajo, pero
   para texto -- el monograma hereda el color correcto automáticamente en
   cualquiera de los 5 "Estilo de íconos" (Fase 10) sin código extra, ya
   que solo usa la propiedad `color` normal de CSS, igual que el ícono SVG
   que reemplaza. Único selector nuevo: .bank-mono, usado SOLO dentro de
   .row-icon (resultados de "Buscar bancos") y dentro de
   .bank-name-icon-slot--brand (ver input-wrap más abajo) -- no toca
   ningún otro texto de la app. */
.bank-mono{font-weight:800;letter-spacing:.2px;line-height:1}
.row-icon .bank-mono{font-size:.72rem}
.row-text{flex:1;min-width:0}
.row-text strong{display:block;font-size:.96rem}
.row-text span{display:block;font-size:.8rem;color:var(--muted);margin-top:1px}
.row-chevron{color:var(--muted);flex:none}
.row-danger .row-text strong{color:var(--red)}
.row-right{flex:none;color:var(--muted);font-size:.85rem}
/* FASE 2.3.6 (Indicador de documentos asociados en Historial, movRowHtml()
   en render.js): insignia pequeña y discreta, puramente informativa -- NO
   es un elemento tocable aparte (vive dentro del mismo <button
   data-edit-tx> de la fila), por eso no necesita cursor/hover propio.
   HALLAZGO REAL corregido en esta misma fase (ver comentario extenso en
   movRowHtml(), render.js): insertar el indicador como hijo flex directo
   de .list-row (junto a .row-icon/.row-text/monto) agregaba 1px de alto
   real y medible a la fila -- se corrigió sacándolo por completo del
   flujo flex normal. .row-icon-badge-wrap envuelve el YA EXISTENTE
   .row-icon SIN tocar su regla compartida (usada en decenas de otras
   pantallas) -- mismo flex:none y mismo tamaño de caja que .row-icon
   tenía solo (38x38, display:inline-flex se ajusta exactamente a su
   único hijo normal), con position:relative como único aporte propio
   para servir de referencia a la insignia. .row-doc-indicator pasa a
   position:absolute, anclada a la esquina inferior derecha de ese
   contenedor -- al quedar fuera del flujo normal de cajas (definición
   del modelo de cajas de CSS), NUNCA participa en el cálculo de alto de
   .list-row, sin importar su tamaño: 0px de diferencia, verificado.
   Tamaño reducido (14px, con un aro del color de fondo de la tarjena
   para separarla visualmente del ícono de categoría) y color atenuado
   (--muted, mismo token que .row-chevron/.row-right de arriba) la
   mantienen discreta. */
.row-icon-badge-wrap{position:relative;flex:none;display:inline-flex}
.row-doc-indicator{
  position:absolute;right:-2px;bottom:-2px;width:14px;height:14px;border-radius:50%;
  flex:none;color:var(--muted);background:var(--surface);
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 0 0 2px var(--surface);
}
.row-doc-indicator .icon{width:9px;height:9px}

/* FASE 8 — Reorganización del Perfil (Punto 7: "más limpio, más moderno,
   mejor distribuido"). Sección "Accesos rápidos" de Perfil: grilla de
   tarjetas cuadradas (ícono arriba + título + subtítulo), fiel al patrón
   de la imagen de referencia adjunta a la orden. Nombres de clase con
   prefijo "profile-" a propósito -- son de uso exclusivo de #screen-profile,
   no se reutilizan en ningún otro lugar de la app, para no arriesgar
   ninguna otra pantalla ya aprobada. Reusa las mismas variables de diseño
   que .list-card/.card (--surface/--border) para mantener la identidad
   visual intacta. */
.profile-quick-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.profile-quick-tile{
  background:var(--surface);border:1px solid var(--border);border-radius:16px;
  padding:16px 14px;text-align:left;display:flex;flex-direction:column;gap:8px;
}
.profile-quick-tile:active,.profile-quick-tile:hover{background:var(--surface-hover)}
.profile-quick-tile .row-icon{width:38px;height:38px}
.profile-quick-tile strong{font-size:.92rem;display:block}

/* FASE 8 (corrección puntual, Punto 1.1 "íconos del Perfil"): contenedor
   EXCLUSIVO de las 5 filas de Perfil que reciben el nuevo set de íconos a
   todo color (i-prof-*, ver icons.js). No se toca .row-icon (regla
   global, compartida por el resto de la app) -- esta clase se agrega
   ADEMÁS de .row-icon (mismo <span class="row-icon profile-icon">) y
   solo sobreescribe lo necesario para que "el fondo tenga menor
   protagonismo" que la figura: pasa de círculo con color sólido a
   cuadrado redondeado con un fondo muy claro y neutro (var(--surface-2)),
   más grande (44px, antes 38px) para que el ícono detallado se lea bien.
   quick-tile ya fija .row-icon a 38px con mayor especificidad (arriba) --
   se repite el tamaño aquí con el mismo peso de selector para que
   .profile-icon gane también dentro de "Accesos rápidos". */
.row-icon.profile-icon,.profile-quick-tile .row-icon.profile-icon{
  width:44px;height:44px;border-radius:14px;
  background:var(--surface-2);color:var(--ink);
  border:0;box-shadow:none;
}
/* ORDEN DE CORRECCIÓN (Fase 8, ajuste puntual, Punto 1 "resaltar más el
   icono, no el fondo"): el contenedor (.row-icon, 44px) NO cambia de
   tamaño a propósito -- solo crece la figura interna. Se detectó que la
   regla anterior (".profile-icon .icon", 2 clases) tenía la MISMA
   especificidad que la regla global ".row-icon .icon{width:22px}" de más
   abajo y, al venir antes en el archivo, perdía el empate -- el ícono
   real se estaba pintando a 22px en vez de los 28px que el código ya
   intentaba. Se sube el selector a 3 clases (".row-icon.profile-icon
   .icon") para ganarle siempre a la regla global sin importar el orden,
   y se aumenta el tamaño real (28px -> 32px, ~73% del contenedor) para
   que la figura se note claramente más grande y reconocible. */
.row-icon.profile-icon .icon,.row-icon.profile-icon svg{width:32px;height:32px}

/* FASE 8 (corrección puntual, Punto 1.2 "botón del lápiz"): botón
   EXCLUSIVO de la cabecera de Perfil -- se agrega a .topbar-action (no la
   reemplaza), así que conserva su tamaño/posición base y solo gana lo
   necesario para "verse más grande, llamativo y profesional": blanco
   puro (en vez del gris neutro de .topbar-action), borde sutil, sombra
   elevada tipo botón flotante, e ícono un poco más grande y en el color
   de acento (en vez de var(--ink) plano) para que se identifique de
   inmediato al entrar a la pantalla. */
.profile-edit-btn{
  width:48px;height:48px;border-radius:13px;
  background:#fff;border:1px solid var(--border);
  color:var(--accent);
  box-shadow:0 4px 14px rgba(20,24,40,.14);
}
[data-mode="dark"] .profile-edit-btn{background:var(--surface-2)}
/* ORDEN DE CORRECCIÓN (Fase 8, ajuste puntual, Punto 1 "también en el
   botón del lápiz"): mismo criterio que .profile-icon arriba -- el botón
   (48px) no cambia de tamaño, solo el trazo del lápiz dentro (22px ->
   28px, ~58% del botón) para que se lea más grande y llamativo sin
   agrandar la caja. */
.profile-edit-btn .icon{width:28px;height:28px}
/* Nota a propósito: el selector del subtítulo NO es un ".profile-quick-tile
   span" genérico -- eso también capturaría el <span class="row-icon"> (que
   ya trae su propio color:#fff con menor especificidad) y se lo pisaría,
   dejando el ícono gris en vez de blanco. Se excluye explícitamente con
   :not(.row-icon) para que solo afecte al subtítulo real. */
.profile-quick-tile > strong + span:not(.row-icon),.profile-quick-tile .row-text span{font-size:.76rem;color:var(--muted);line-height:1.3}
.profile-quick-tile-wide{grid-column:1/-1;flex-direction:row;align-items:center}

/* FASE 8 (corrección puntual, Punto 2 "Calendario financiero") --
   implementación real, reemplaza el grid estático de solo-interfaz de la
   Fase 8 original. Grilla de 7 columnas DOMINGO-primero (ver
   weekdayLabels() en render.js), generada en JS por
   renderFinanceCalendar()/buildFinCalMonthGrid() -- ver
   #screen-financeCalendar. Cada día real es ahora un <button> (antes un
   <div> decorativo) para poder seleccionarlo (Punto 2.1 "selección de un
   día específico"). */
.fin-cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-top:4px}
.fin-cal-dow{font-size:.7rem;font-weight:700;color:var(--muted-2);text-align:center;padding:2px 0 6px}
.fin-cal-day{
  aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  border-radius:10px;font-size:.82rem;color:var(--ink);position:relative;
}
/* Días de relleno del mes anterior/siguiente (Punto 2.1, fidelidad
   visual de la imagen aprobada, que sí los muestra en gris): NO llevan
   data-fincal-day, así que no son seleccionables -- solo referencia
   visual de dónde empieza/termina la semana. */
.fin-cal-day-outside{color:var(--muted-2);opacity:.55}
/* Columna domingo (primera columna del grid): mismo tratamiento en rojo
   suave que la imagen aprobada, independiente de si ese día tiene o no
   movimientos. */
.fin-cal-day-sunday{color:var(--red)}
.fin-cal-day-today{box-shadow:inset 0 0 0 2px var(--accent);font-weight:800}
.fin-cal-day-selected{background:var(--accent);color:#fff;font-weight:800}
.fin-cal-dots{display:flex;gap:2px;height:5px}
.fin-cal-dot{width:5px;height:5px;border-radius:50%;background:var(--gray);flex:none}
.fin-cal-dot-green{background:var(--green)}
.fin-cal-dot-red{background:var(--red)}
.fin-cal-dot-blue{background:var(--blue)}
.fin-cal-dot-gray{background:var(--gray)}
/* El día seleccionado usa fondo de acento sólido -- sus puntos de color
   perderían contraste, así que se muestran en blanco (Punto 2.4 "buena
   legibilidad"), igual que el pequeño punto blanco de la imagen
   aprobada dentro del círculo del día 14. */
.fin-cal-day-selected .fin-cal-dot{background:#fff!important}

/* Punto 2.4 "Colores e indicadores" -- leyenda de los 4 puntos, debajo
   del grid de la vista Mes. */
.fin-cal-legend{display:flex;flex-wrap:wrap;gap:10px 14px;margin-top:14px;padding-top:12px;border-top:1px solid var(--border)}
.fin-cal-legend span{display:inline-flex;align-items:center;gap:6px;font-size:.74rem;color:var(--muted)}
.fin-cal-legend .fin-cal-dot{width:8px;height:8px}

/* Punto 2.1 "navegación entre meses y años" + "búsqueda o acceso rápido
   a una fecha": fila con el selector de periodo (pill "Mayo 2025 ⌄", que
   abre #finCalJumpSheet) y las flechas ‹ › para avanzar/retroceder un
   paso (mes en vista Mes, año en vista Año). */
.fin-cal-nav{display:flex;align-items:center;gap:8px;margin:10px 0 14px}
.fin-cal-navpill{
  flex:1;display:flex;align-items:center;gap:8px;justify-content:center;
  padding:11px 12px;border-radius:12px;background:var(--surface);border:1px solid var(--border);
  font-weight:700;font-size:.9rem;color:var(--ink);min-width:0;
}
.fin-cal-navpill span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fin-cal-navpill svg{flex:none;width:18px;height:18px;color:var(--muted)}
.fin-cal-arrows{display:flex;gap:6px;flex:none}
.fin-cal-arrows button{width:44px;height:44px;border-radius:12px;background:var(--surface-2);color:var(--ink);display:grid;place-items:center;flex:none}

/* Vista Año (Punto 2.1 "vista por año"): grilla de 12 meses, mismo
   patrón visual que .mode-grid/.freq-grid ya usados en otras pantallas
   (tarjeta con borde, ícono/indicador arriba, texto abajo). */
.fin-cal-yeargrid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.fin-cal-monthcell{
  background:var(--surface);border:1px solid var(--border);border-radius:14px;
  padding:14px 8px;display:flex;flex-direction:column;align-items:center;gap:6px;
  font-size:.85rem;color:var(--ink);
}
.fin-cal-monthcell-today{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.fin-cal-monthcell .fin-cal-dots{height:6px}
.fin-cal-monthcell .fin-cal-dots-empty{color:var(--muted-2);font-size:.7rem;height:auto}

/* ORDEN CORRECTIVA — Moneda/Idioma (Manual §3.9/§3.10, Figura 7/image16.png):
   cada fila necesita bandera real (no solo código de 2-3 letras) + datos
   centrales + indicador de selección tipo radio, en vez del texto "✓"
   plano que había antes. */
.flag-icon{
  width:38px;height:38px;border-radius:50%;flex:none;
  display:grid;place-items:center;font-size:1.3rem;
  background:var(--surface-2);border:1px solid var(--border);overflow:hidden;
}
.radio-dot{
  width:22px;height:22px;border-radius:50%;flex:none;
  border:2px solid var(--border);display:grid;place-items:center;
}
.radio-dot.is-checked{border-color:var(--accent);background:var(--accent)}
.radio-dot.is-checked::after{content:'';width:8px;height:8px;border-radius:50%;background:#fff}
.current-choice-card{
  background:var(--accent-soft);border:1.5px solid var(--accent);border-radius:16px;
  display:flex;align-items:center;gap:12px;padding:14px 16px;
}

/* Colores funcionales para row-icon: definen la variable --rc ("row
   color"), reutilizados en toda la app. El tratamiento visual real
   (relleno sólido / contorno / lineal / degradado / 3D) lo decide
   [data-iconstyle] más abajo (Fase 11 — Diseño de la aplicación),
   para que el estilo elegido en Imagen_33 se aplique consistentemente
   en cada pantalla, no solo en el selector. */
.bg-blue{--rc:var(--blue)}
.bg-green{--rc:var(--green)}
.bg-red{--rc:var(--red)}
.bg-orange{--rc:var(--orange)}
.bg-amber{--rc:var(--amber)}
.bg-purple{--rc:var(--purple)}
.bg-pink{--rc:var(--pink)}
.bg-teal{--rc:var(--teal)}
.bg-gray{--rc:var(--gray)}
.bg-accent{--rc:var(--accent)}

/* ---- Avatar ---- */
.avatar{
  width:44px;height:44px;border-radius:50%;
  display:grid;place-items:center;
  background:var(--surface-2);color:var(--ink);font-weight:800;flex:none;
  overflow:hidden;border:1px solid var(--border);
}
.avatar img{width:100%;height:100%;object-fit:cover}
.avatar-lg{width:104px;height:104px;font-size:2rem}

/* FASE 9 (Punto 2, mensaje motivacional de Metas): versión ampliada de
   .row-icon para el ícono hero de #goalsIntroView -- mismo componente
   (círculo de color + ícono blanco centrado), solo más grande, igual que
   .avatar-lg hace con .avatar. */
.goals-intro-icon{width:96px;height:96px}
/* Especificidad reforzada (#goalsIntroView + .icon) a propósito: el
   selector genérico ".row-icon .icon" (styles.css, sección de Bancos)
   fija width/height:20px con la MISMA especificidad de 2 clases y
   aparece más abajo en este archivo, así que un simple
   ".goals-intro-icon .icon" perdería la cascada y el ícono se quedaría
   en 20px pese a este bloque. */
#goalsIntroView .goals-intro-icon.icon,#goalsIntroView .goals-intro-icon .icon{width:46px;height:46px}

/* ORDEN — Imagen definitiva de bienvenida de Metas (aprobada tal cual,
   NO recreada): reemplaza el bloque anterior (ícono+título+subtítulo+2
   botones de texto, reglas de arriba, que quedan sin uso pero NO se
   eliminan -- mismo criterio de trazabilidad ya usado en el resto de
   este archivo, ej. .tab-fab) por img/metas-bienvenida.png como asset
   real, mostrado igual en TODOS los idiomas (decisión explícita del
   usuario).

   .goals-intro-imgwrap fija aspect-ratio:1024/1536 -- las medidas REALES
   del PNG (verificadas con Pillow, no estimadas) -- así los 3 hotspots de
   abajo, posicionados en PORCENTAJE, quedan siempre alineados con los
   controles reales de la imagen (X / "Crear mi primera meta" / "Ahora
   no") sin importar el tamaño de pantalla, sin recortar ni deformar el
   asset (object-fit:contain + max-width fijo, nunca width/height sueltos
   que puedan estirarlo). max-width:380px es un límite de tamaño de
   TARJETA (no del viewport) -- en un iPhone normal el wrapper mide
   min(100%,380px) de ancho; su alto se recalcula solo vía aspect-ratio,
   nunca se fija en vh, así la imagen nunca se recorta -- si no cabe
   completa, es <main> (overflow-y:auto ya existente, ver regla base más
   abajo) quien scrollea, no la imagen quien se encoge de más.

   Los 3 <button> .goals-intro-hotspot son capas invisibles superpuestas
   -- la imagen NO se convirtió en HTML, solo se le agregaron 3 zonas de
   toque encima. Coordenadas medidas a nivel de píxel sobre el PNG real
   (bounding box de cada color de botón) y ligeramente ampliadas hacia el
   espacio en blanco ADYACENTE (nunca invadiendo el botón vecino ni el
   contenido de arriba/abajo, verificado píxel a píxel) para acercarse al
   mínimo táctil recomendado de 44px en las pantallas de iPhone más
   pequeñas, sin alterar ni recortar el asset en sí -- el área de toque no
   es visible, así que ampliarla no cambia en nada la imagen aprobada. */
.goals-intro-view{
  display:flex;justify-content:center;align-items:flex-start;
  padding:16px 12px 6px;
}
.goals-intro-imgwrap{
  position:relative;width:100%;max-width:380px;
  aspect-ratio:1024/1536;
}
.goals-intro-img{
  width:100%;height:100%;display:block;object-fit:contain;
}
.goals-intro-hotspot{
  position:absolute;background:transparent;border:none;padding:0;margin:0;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
/* Botón "X" (cerrar) -- círculo real de la imagen: x[851,940] y[84,172]
   sobre 1024x1536, ampliado a x[828,963] y[59,197] (~136x138 en el
   espacio de la imagen) para acercarse a 44px reales en pantallas
   pequeñas; misma acción que "Ahora no" (ver events.js). */
.goals-intro-hotspot-close{left:80.86%;top:3.84%;width:13.18%;height:8.98%;border-radius:50%}
/* Botón "Crear mi primera meta" -- real x[88,936] y[1245,1340],
   ampliado verticalmente hacia el espacio en blanco de arriba/abajo a
   y[1215,1355] sin tocar el botón "Ahora no" (gap real hasta su borde
   superior ~1383). */
.goals-intro-hotspot-crear{left:8.59%;top:79.10%;width:82.81%;height:9.11%;border-radius:999px}
/* Botón "Ahora no" -- real x[88,936] y[~1383,1433], ampliado a
   y[1359,1487] (deja ~4px de separación con el hotspot de arriba y queda
   dentro de la tarjeta opaca, antes de que empiece a desvanecerse la
   sombra ~y1490). */
.goals-intro-hotspot-mastarde{left:8.59%;top:88.48%;width:82.81%;height:8.33%;border-radius:999px}

/* Botón de campana en Inicio (Manual Oficial §3.3: "Mostrar logo y saludo
   ... campana a la derecha" / "Abrir Notificaciones al tocar la campana").
   Vive sobre el .hero (fondo siempre oscuro vía --hero-grad), por eso usa
   colores fijos blancos translúcidos en vez de las variables de --surface
   que dependen del modo claro/oscuro de la página. */
.icon-btn{
  width:40px;height:40px;border-radius:50%;flex:none;
  display:grid;place-items:center;
  background:#ffffff26;color:#fff;border:1px solid #ffffff33;
}
.icon-btn .icon{width:20px;height:20px}

/* ---- Chips / badges ---- */
.badge{padding:4px 9px;border-radius:99px;font-size:.72rem;font-weight:800}
.badge-green{background:#22c55e26;color:#4ade80}
.badge-red{background:#ef444426;color:#f87171}
.badge-accent{background:var(--accent-soft);color:var(--accent)}
.badge-new{background:var(--accent);color:#fff}
/* Tanda 3 (Bancos, 5.6/5.12): estado "Cuenta inactiva" en la lista de
   #screen-bancos -- neutro (ni verde de éxito ni rojo de peligro), y
   siempre acompañado del texto de la etiqueta (nunca solo el color,
   ver bankRowHtml en render.js), así que basta un tono gris apagado. */
.badge-gray{background:#6b728033;color:#9ca3af}
[data-mode="light"] .badge-green{color:#15803d}
[data-mode="light"] .badge-red{color:#b91c1c}
[data-mode="light"] .badge-gray{color:#4b5563}

/* ---- Segmented control (tabs internas, p.ej. Reportes) ---- */
.segtabs{display:flex;gap:6px;background:var(--surface-2);border:1px solid var(--border);border-radius:12px;padding:4px;overflow-x:auto}
.segtabs button{flex:1;white-space:nowrap;padding:9px 10px;border-radius:9px;font-weight:700;font-size:.86rem;color:var(--muted)}
.segtabs button.active{background:var(--accent);color:#fff}
/* Variante para segtabs de 4 (Reportes, Tarea #87): con 4 botones el
   presupuesto de ancho por botón es menor que en los de 2-3 (mismo bug
   de desborde ya corregido en Respaldo/Tarea #85) — fuente y padding
   reducidos para que las 4 etiquetas quepan sin recortarse. */
.segtabs-4 button{font-size:.74rem;padding:9px 4px}

/* Tarjetas de estadística de Reportes (Tarea #87, Figura 24/25). */
.report-stat-row{display:flex;gap:10px}
.report-stat-card{flex:1;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:12px 14px}
.report-stat-card>span:first-child{display:block;font-size:.76rem;color:var(--muted)}
.report-stat-card strong{display:block;font-size:1.05rem;margin-top:2px}
.report-stat-delta{display:block;font-size:.72rem;margin-top:3px;font-weight:700;color:var(--muted)}
.report-stat-delta.up{color:var(--green)}
.report-stat-delta.down{color:var(--red)}
.report-quick-row{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid var(--border);font-size:.84rem;color:var(--muted)}
.report-quick-row strong{font-weight:800;color:var(--ink);text-align:right}
.report-ring-card{flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:14px}
.report-ring-card>div{text-align:center}
.report-ring-card>div>span{display:block;font-size:.76rem;color:var(--muted)}
.report-ring-card strong{font-size:.92rem}
.report-ring-pct{font-size:1.05rem;font-weight:800;color:var(--ink)}

/* ---- Barra de navegación inferior ---- */
.tabs{
  position:fixed;left:50%;bottom:0;transform:translateX(-50%);
  width:min(100%,520px);z-index:20;
  /* Tanda 2 (fusionada con la Tanda 3): antes "1fr 1fr auto 1fr 1fr"
     reservaba la columna central "auto" para el FAB circular de 56px.
     Con el (+) retirado y sustituido por una pestaña normal ("Bancos"),
     las 5 pestañas vuelven a repartirse el ancho por igual, igual que ya
     ocurría antes de que existiera el FAB. */
  display:grid;grid-template-columns:repeat(5,1fr);
  align-items:center;gap:2px;
  padding:8px 10px calc(10px + env(safe-area-inset-bottom));
  background:var(--bg-elevated);
  border-top:1px solid var(--border);
}
.tab{
  height:54px;display:grid;place-items:center;gap:2px;
  border-radius:10px;color:var(--muted);font-size:.68rem;font-weight:650;
  transition:background-color .15s ease;
}
.tab svg{width:27px;height:27px} /* ORDEN DE CORRECCIÓN (Punto 4, "mayor fidelidad y presencia"): +3px adicional sobre el tamaño ya aumentado en la Tanda 5 (24px) */
/* Tanda 5 (íconos "Gradiente Suave" de referencia): antes el color
   activo/inactivo lo daba currentColor sobre un trazo (stroke) simple
   -- accent cuando .tab.active, --muted en otro caso -- y el ícono en sí
   cambiaba de color según el estado. Los íconos nuevos son ilustraciones
   con relleno en degradado PROPIO de cada pestaña (azul/verde/morado/
   rosa-naranja/naranja, ver <linearGradient> de cada <svg> más abajo en
   el HTML) que se mantienen IDÉNTICOS en activo/inactivo y en modo claro/
   oscuro -- así lo muestra la imagen de referencia en sus 4 combinaciones
   (claro/oscuro × activo/inactivo). La diferenciación de estado se movió
   entonces del propio ícono a: (1) el color/peso de la etiqueta de texto
   (ya lo hacía .tab.active{color:var(--accent)}, sin cambios) y (2) un
   fondo "píldora" suave detrás de toda la pestaña activa, reutilizando
   la variable --accent-soft ya existente (así la píldora respeta
   cualquier color de acento que el usuario elija en Apariencia, igual
   que ya hacía el color de la etiqueta). */
.tab.active{color:var(--accent);background:var(--accent-soft)}
/* .tab-fab queda sin uso tras la Tanda 2 (el botón central (+) se quitó
   de la barra inferior) -- se conserva la regla por trazabilidad y por si
   una futura orden explícita pide reintroducir un FAB en otra pantalla;
   no se elimina el CSS para no perder el historial de la decisión. */
.tab-fab{
  width:56px;height:56px;border-radius:50%;
  display:grid;place-items:center;color:#fff;
  background:linear-gradient(135deg,var(--accent),var(--accent-2));
  box-shadow:0 10px 24px var(--accent-soft);
  margin:0 4px;
}

/* FASE 2.2.3 (visor de documentos -- "Compartir / Exportar" siempre
   accesible, hallazgo real de iPhone: en una fotografía alta había que
   desplazarse hasta el final para encontrarlo): barra fija propia,
   creada dinámicamente como hermana de <main> dentro de
   #screen-docViewer (ver ensureDocViewerShareBar(), render.js) -- SOLO
   existe/se ve mientras esa pantalla está activa (es hija del <section>
   de esa pantalla, así que hereda su display:none/¬.active igual que
   cualquier otro contenido normal, pese a ser position:fixed).
   Mismo patrón exacto que .tabs (fixed + centrado + ancho máximo +
   safe-area), colocada exactamente PEGADA encima de .tabs, sin hueco:
   "bottom" usa el alto REAL ya medido de .tabs en cada media query
   (73px = 8px pad-top + 54px alto de pestaña + 10px pad-bottom + 1px de
   su propio border-top en vertical; 53px = 4+42+6+1 en horizontal, ver
   la media query de abajo), más su propio env(safe-area-inset-bottom)
   -- el de .tabs ya va incluido en el "73px"/"53px" de abajo porque son
   el alto YA renderizado de esa barra con su padding y borde, así que
   no se duplica. CORRECCIÓN (esta fase, medición real con
   getBoundingClientRect): el valor anterior (72px/52px) no contaba el
   border-top:1px de .tabs, dejando un solapamiento real de 1px entre
   el borde inferior de esta barra y el borde superior de .tabs --
   detectado con la medición objetiva exigida (actionBar.bottom <=
   tabs.top), no a simple vista. */
.doc-viewer-sharebar{
  position:fixed;left:50%;bottom:calc(73px + env(safe-area-inset-bottom));
  transform:translateX(-50%);width:min(100%,520px);z-index:20;
  padding:10px 16px;background:var(--bg-elevated);border-top:1px solid var(--border);
  /* CORRECCIÓN DEFINITIVA (Chrome/iPhone, ver comentario extenso en
     renderDocViewer(), render.js): esta barra ahora puede contener UNO
     ("Compartir/Exportar" solo, imágenes) o DOS botones apilados
     ("Abrir documento" encima de "Compartir/Exportar", PDF/otro) --
     display:flex en columna los apila en el orden del DOM (visual:
     Abrir documento arriba, Compartir/Exportar abajo, tal como pide la
     orden), con un espacio fijo entre ambos. Con un solo botón se ve
     idéntico a antes (flex de un solo hijo = mismo resultado visual que
     bloque). El alto total de la barra ahora depende ÚNICAMENTE de
     cuántos botones .btn (min-height:50px, ver más abajo) contiene --
     nunca de la altura del PDF/iframe. */
  display:flex;flex-direction:column;gap:8px;
}
.doc-viewer-sharebar:empty{display:none} /* nada que mostrar mientras el visor no cargó su documento (o al salir de la pantalla) -- evita una franja vacía parpadeando */
/* El contenido desplazable de #screen-docViewer necesita colchón inferior
   EXTRA sobre el ya reservado para .tabs (96px/64px, regla global) --
   ahora también hay que despejar el alto de esta barra nueva (~70px:
   10+10 de padding + 50px de alto mínimo de .btn, UN botón: caso
   imagen) para que la última parte de una imagen alta no quede tapada
   ni por .tabs ni por "Compartir / Exportar". Mismo patrón de selector
   con id (mayor especificidad, sin !important) ya usado en
   #screen-profile/#screen-docClassify. */
#screen-docViewer > main{padding-bottom:calc(152px + env(safe-area-inset-bottom))}
/* CORRECCIÓN DEFINITIVA (Chrome/iPhone -- reemplaza el parche anterior
   de "padding extra a ciegas" que intentaba adivinar la altura real del
   iframe del PDF, insuficiente en algunas posiciones reales; ver
   comentario extenso en renderDocViewer(), render.js). Esta regla YA NO
   compensa nada del PDF -- "Abrir documento" ya no vive dentro del
   contenido desplazable, así que su posición NUNCA depende de 60vh, del
   iframe, ni de ningún padding. Lo único que este valor reserva es el
   espacio real, exacto y conocido de la barra fija cuando tiene DOS
   botones en vez de uno: +58px sobre el colchón base de arriba
   (50px del segundo .btn + 8px del gap entre ambos) -- 152+58=210.
   Especificidad ID+clase, mayor que "#screen-docViewer > main"
   (ID+tag), sin necesitar !important. */
#docViewerMain.doc-viewer-pdf{padding-bottom:calc(210px + env(safe-area-inset-bottom))}

/* ============================================================
   Fase 10 (segunda pasada, fidelidad estricta) — Login / Crear cuenta.
   Imagen_01.png y Imagen_02.png: el degradado del hero cubre TODA la
   pantalla (no solo una franja superior), con la tarjeta blanca (Login)
   u oscura sin tarjeta (Crear cuenta) flotando encima, y los botones
   secundarios (Crear una cuenta / Olvidé mi contraseña / continuar con
   Google-Apple / Tus datos están protegidos) viven directamente sobre el
   fondo del hero, no sobre el fondo neutro de la app.
   ============================================================ */
/* FASE 2.9.2: el fondo de Login/Crear cuenta ya no lee var(--hero-grad)
   -- render() la reescribe en <html> con el Estilo de fondo de la cuenta
   (y tras cerrar sesión, con la apariencia guardada del dispositivo), así
   que la personalización de una sesión anterior teñía estas pantallas.
   --auth-static-bg es el degradado azul original aprobado, declarado en
   la propia .screen-auth: ningún escritor de preferencias la toca, y solo
   la heredan estas dos pantallas (fondo, ::before y la flecha). */
.screen-auth{--auth-static-bg:linear-gradient(160deg, #081A4D 0%, #0B2C7A 45%, #1E3FAE 75%, #2A4DEE 100%);background:var(--auth-static-bg);min-height:100vh;min-height:100dvh;padding-bottom:36px} /* ORDEN DE CORRECCIÓN (Punto 5): 100dvh además de 100vh, mismo patrón ya usado en el resto de pantallas, para que la barra de herramientas dinámica del navegador móvil no deje un hueco de fondo al girar */
.screen-auth.active{isolation:isolate}
.screen-auth.active::before{content:'';position:fixed;top:0;left:0;right:0;margin:0 auto;width:min(100%,520px);height:100vh;height:100lvh;background:var(--auth-static-bg);z-index:-1;pointer-events:none}
/* FASE 2.9.2 (controles públicos): el fondo ya era fijo, pero botones,
   enlaces, foco, campos y tarjetas de Login, Crear cuenta, Recuperar
   contraseña, Correo enviado y los documentos legales abiertos desde ahí
   seguían leyendo --accent*, --bg/--surface/--ink/--muted... y
   color-scheme, que [data-accent]/[data-mode] fijan en <html> con la
   apariencia guardada de la última cuenta (createAnonymousState()).
   body.is-auth (syncNav(), render.js) solo existe mientras la raíz es
   una de esas pantallas; las reglas [data-mode="light"],
   [data-accent="azul"] y [data-fondo-activo="false"] de arriba la
   incluyen, así toda la interfaz pública hereda siempre la apariencia
   predeterminada de primera apertura (claro + azul de marca), sin tocar
   la preferencia guardada ni el tema dentro de la sesión. --screen-bg
   llega en línea en <html> ya resuelta allí; se reescribe aquí con el
   mismo valor que render() usa sin fondo personalizado. */
body.is-auth{--screen-bg:var(--bg);--screen-bg-home:var(--bg)}
:root:has(> body.is-auth){color-scheme:light}
/* ORDEN DE CORRECCIÓN (Punto 3, "logo tapado por la cámara/notch/isla
   dinámica en Iniciar sesión"): el padding superior era un valor fijo
   (30px) sin ningún término env(safe-area-inset-top) -- a diferencia de
   .topbar/.hero-home, que sí lo tenían desde tandas anteriores. En un
   iPhone con notch o isla dinámica, esos 30px fijos no alcanzan a
   despejar el área que el sistema reserva arriba, así que el logo quedaba
   parcialmente debajo de esa zona. Se agrega el mismo patrón ya probado
   en .topbar/.hero-home (ver Tanda 1, Punto 5). En dispositivos sin
   recorte (la mayoría de Android, iPhones sin notch) env() devuelve 0 y
   el padding queda igual que antes -- sin cambio visual ahí. */
.auth-logo{display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;padding:calc(30px + env(safe-area-inset-top)) 20px 22px;color:#fff}
.auth-logo img{width:98px;height:98px} /* Tanda 5: logo nuevo, +11% (antes 88px). border-radius/box-shadow de "tarjeta" retirados -- ver .logo-guard */
.auth-logo h1{margin:2px 0 0;font-size:1.55rem;font-weight:800}
.auth-logo p{margin:0;color:#dbe4ff;font-size:.9rem;text-align:center;max-width:280px}

.auth-card{background:#fff;color:#16192b;border-radius:20px;padding:22px 18px;margin:0 16px;box-shadow:0 20px 44px #00123f4d}
.auth-card .section-label{color:#6b7280;padding:0}
.auth-card h2{margin:0 0 2px;font-size:1.25rem}
.auth-card .field label{color:#4b5568}
.auth-card .input-wrap{background:#f4f5f9;border-color:#e2e5f1}
.auth-card .input-wrap input{color:#16192b}
.auth-card .input-wrap .icon{color:#8a90a6}
.auth-card .btn-primary{background:var(--accent);text-transform:uppercase;letter-spacing:.02em}
.screen-auth .btn-primary{text-transform:uppercase;letter-spacing:.02em}

.auth-divider{display:flex;align-items:center;gap:10px;margin:16px 0;color:#9aa0b4;font-size:.78rem}
.auth-divider::before,.auth-divider::after{content:'';flex:1;height:1px;background:#e2e5f1}

.auth-social{display:flex;gap:10px}
.auth-social button{
  flex:1;display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:48px;border-radius:12px;border:1px solid #e2e5f1;background:#fff;
  color:#16192b;font-weight:650;font-size:.92rem;
}
.auth-social .icon{width:19px;height:19px}

.auth-actions{padding:20px 16px 0;display:grid;gap:14px;text-align:center}
.auth-btn-outline-green{
  display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:52px;border-radius:14px;border:1.5px solid #22c55e;color:#22c55e;
  font-weight:800;letter-spacing:.02em;text-transform:uppercase;font-size:.92rem;
}
.auth-link-amber{
  display:flex;align-items:center;justify-content:center;gap:7px;
  color:#f59e0b;font-weight:800;letter-spacing:.02em;text-transform:uppercase;font-size:.86rem;
}
.auth-protected{display:flex;align-items:center;justify-content:center;gap:6px;color:#c7cee8;font-size:.78rem}
.auth-demo-hint{color:#8891ab;font-size:.74rem;text-align:center;margin:-2px 0 0}

/* FASE 2.9.1 (Crear cuenta en móvil): la pantalla no cabía en vertical
   (1027px de contenido en un viewport de 844px) y la flecha se iba con
   el scroll -- su .topbar sticky nunca "veía" desplazamiento porque el
   scroll era del documento y .app (overflow-x:hidden) es su contenedor
   de scroll nominal. Mismo patrón ya usado por las pantallas internas
   (ver ".screen.active:not(.screen-auth)" más abajo): la sección pasa a
   ser su propio contenedor de scroll de alto fijo, así el sticky de la
   flecha funciona cuando hace falta desplazarse (teclado, horizontal,
   viewport muy corto). El fondo fijo de a474eb1 (::before) no cambia.
   En vertical, la flecha deja de reservar una fila propia (flota a la
   izquierda del logo, nunca a la altura del título) y los espacios/
   alturas se adaptan al alto real con clamp(): en pantallas altas
   vuelven a los valores de siempre, en un teléfono normal se compactan sin
   ocultar nada ni bajar de 44px los controles táctiles. */
#screen-createAccount.active{height:100vh;height:100dvh;overflow-y:auto;-webkit-overflow-scrolling:touch}
@media (orientation:portrait){
  #screen-createAccount.active{padding-bottom:calc(clamp(8px,1.2vh,36px) + env(safe-area-inset-bottom))}
  #screen-createAccount .topbar{height:0;padding:0;pointer-events:none}
  #screen-createAccount .back-btn{position:absolute;top:calc(8px + env(safe-area-inset-top));left:calc(16px + env(safe-area-inset-left));pointer-events:auto}
  /* Bajo su capa blanca translúcida (la misma en línea de siempre), el
     botón pinta el trozo del degradado que el ::before fijo tiene justo
     en esa posición: en reposo se ve igual que antes, y si al desplazarse
     queda sobre un campo blanco la flecha blanca no desaparece. Lee
     var(--auth-static-bg), el mismo degradado fijo del ::before. */
  #screen-createAccount .back-btn{
    background:linear-gradient(#ffffff26,#ffffff26),var(--auth-static-bg)!important;
    background-size:auto,min(100vw,520px) 100vh!important;
    background-size:auto,min(100vw,520px) 100lvh!important;
    background-position:0 0,calc(-16px - env(safe-area-inset-left)) calc(-8px - env(safe-area-inset-top))!important;
  }
  #screen-createAccount .auth-logo{gap:clamp(2px,.6vh,8px);padding:calc(clamp(8px,1.6vh,30px) + env(safe-area-inset-top)) 20px clamp(6px,1.4vh,22px)}
  #screen-createAccount .auth-logo img{width:clamp(48px,8vh,98px);height:clamp(48px,8vh,98px)}
  #screen-createAccount .auth-logo h1{font-size:clamp(1.25rem,3vh,1.55rem)}
  #screen-createAccount main{gap:clamp(6px,1.2vh,14px);padding-bottom:0}
  #screen-createAccount .field{margin-bottom:clamp(6px,1vh,12px)}
  #screen-createAccount .input-wrap{min-height:clamp(44px,5.8vh,50px)}
  #screen-createAccount .input-wrap input{min-height:calc(clamp(44px,5.8vh,50px) - 2px)}
  #createForm > .grid{gap:clamp(2px,.5vh,6px)!important;margin-bottom:clamp(4px,1vh,14px)!important}
  #createError{margin:0 0 clamp(4px,1vh,14px)}
  #screen-createAccount .btn{min-height:clamp(44px,5.8vh,50px)}
  #screen-createAccount .auth-divider{margin:clamp(0px,.6vh,16px) 0}
  #screen-createAccount .auth-social button{min-height:clamp(44px,5.6vh,48px)}
  #screen-createAccount main > p{margin:0}
}

/* ---- Toast ---- */
.toast{
  position:fixed;left:50%;bottom:88px;transform:translateX(-50%) translateY(16px);
  width:min(calc(100% - 28px),480px);
  padding:13px 16px;border-radius:12px;
  background:#111528;color:#fff;
  opacity:0;pointer-events:none;transition:.22s;z-index:60;
  font-size:.9rem;font-weight:600;
}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
@media(prefers-reduced-motion:reduce){.toast{transition:none}}

/* ---- Modal / hoja de confirmación ---- */
.modal-overlay{
  position:fixed;inset:0;background:#00000090;
  display:none;align-items:flex-end;justify-content:center;z-index:70;
}
.modal-overlay.show{display:flex}
.modal-sheet{
  width:min(100%,520px);background:var(--bg-elevated);
  border-radius:20px 20px 0 0;padding:22px 18px calc(22px + env(safe-area-inset-bottom));
  display:grid;gap:14px;text-align:center;
}
/* Tarea #88 (Soporte): se detectó que .modal-icon.bg-* nunca tenía
   background real -- igual que el bug de .swatch ya corregido en la
   Tarea #83 -- las clases .bg-* solo definen la variable --rc, y nada
   la consumía aquí. Afecta también a la pantalla "Correo enviado"
   (auth/emailSent), preexistente, corregida de paso por consistencia. */
.modal-icon{width:64px;height:64px;border-radius:50%;display:grid;place-items:center;margin:0 auto;font-size:1.6rem;color:#fff;background:var(--rc,var(--accent))}
.modal-icon svg{width:30px;height:30px}
/* FASE 13 (corrección puntual, Puntos 1 y 2): fondo suave dedicado
   SOLO para las dos ilustraciones a color de "Comparte tu idea"
   (bombillo) y "Cuéntanos qué pasó" (boya), inspirado en los tonos de
   la imagen aprobada (tarjeta lavanda / tarjeta rosa). Reglas nuevas y
   exclusivas: no tocan .modal-icon (compartida con el resto de la
   app) ni las clases genéricas .bg-amber/.bg-red. */
.modal-icon--idea-illustration{background:linear-gradient(180deg,#F1E7FF,#E6D6FF)}
.modal-icon--report-illustration{background:linear-gradient(180deg,#FFE3E3,#FFD1D1)}

/* ---- Progreso ---- */
.progress-track{height:10px;border-radius:99px;background:var(--surface-2);overflow:hidden}
.progress-fill{height:100%;border-radius:inherit;background:var(--accent)}
.progress-fill.warn{background:var(--amber)}
.progress-fill.bad{background:var(--red)}
.progress-fill.good{background:var(--green)}

/* ---- Swatches de color (Apariencia) ---- */
.swatch-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.swatch{
  aspect-ratio:1;border-radius:14px;position:relative;
  border:2px solid transparent;
  display:grid;place-items:center;color:var(--ink);
  /* CORRECCIÓN — bug preexistente encontrado en la auditoría (Tarea
     #83): los swatches de COLOR (.swatch.bg-*, sin ícono) nunca tenían
     `background`, solo definían la variable --rc vía .bg-* — el
     selector de color de "Nueva categoría" era invisible salvo el
     borde del seleccionado. Los swatches de ÍCONO ya traen su propio
     `background` inline (var(--surface-2)), que sigue ganando por
     especificidad de estilo en línea. */
  background:var(--rc,var(--surface-2));
}
.swatch[aria-pressed="true"]{border-color:var(--ink)}
.swatch[aria-pressed="true"]::after{content:'✓';position:absolute;inset:0;display:grid;place-items:center;color:#fff;font-weight:900;font-size:1.2rem;text-shadow:0 1px 3px #00000070}
.swatch-label{text-align:center;font-size:.78rem;margin-top:6px;color:var(--muted)}
/* CORRECCIÓN PUNTUAL (Fase 12 -- solo tamaño INTERNO de los íconos):
   los íconos de #catIconSwatches (rejilla de "alimentación, transporte,
   vivienda, servicios, café, salud, educación, entretenimiento, ropa,
   mascotas, ahorro, otros" + botón "más íconos") heredaban el tamaño
   genérico de la clase compartida .icon (20px, ver regla global más
   arriba) dentro de una casilla de ~62px -- se veían diminutos. Esta
   regla SOLO agranda la figura interna (la propia etiqueta <svg>) a
   ~40px, el doble aproximado pedido, dejando margen de sobra dentro de
   la casilla para que no se recorte ni toque el borde. El tamaño de la
   casilla (.swatch), su borde/estado seleccionado, el espaciado
   (.swatch-grid gap) y la cuadrícula de columnas NO se tocan -- esta
   regla solo afecta al <svg> de adentro. Alcance limitado a
   #catIconSwatches (por id) para no afectar cualquier otro lugar que
   reutilice la clase .swatch (p. ej. los swatches de color de Bancos,
   que de todos modos no tienen ícono interno). */
#catIconSwatches .icon{width:40px;height:40px}

/* ---- FASE 12: editor de categorías (Punto 2/4/5) ---- */
/* Botón de texto en el topbar (imagen aprobada, "Guardar" arriba a la
   derecha de #screen-catEditor) -- distinto de .topbar-action (que es
   siempre un cuadro de 44x44 con un ícono), porque aquí el contenido es
   una palabra, no un ícono; mismo color de acento que cualquier acción
   primaria de la app. */
.topbar-action-text{flex:none;font-weight:800;font-size:.95rem;color:var(--accent);background:none;padding:8px 2px}
/* Insignia cuadrada-redondeada con el ícono de "etiqueta" en el
   encabezado del editor -- el color (verde/naranja) refuerza de un
   vistazo el tipo de movimiento ya fijado (Ingresos/Gastos), igual idea
   que .cat-type-dot de la lista pero en tamaño protagonista. */
.cat-editor-icon-badge{width:56px;height:56px;border-radius:18px;display:grid;place-items:center;color:#fff}
/* CORRECCIÓN PUNTUAL (Fase 12): ícono principal de la categoría (el
   grande dentro del cuadro de color verde/naranja) -- misma idea que
   #catIconSwatches arriba, se agranda SOLO la figura interna (28px ->
   46px). No es exactamente el doble (56px) a propósito: la propia
   casilla de color mide 56x56 y no se agranda (orden explícita, "no
   agrandar el cuadro naranja"), así que llevar el ícono a 56px lo haría
   tocar los 4 bordes -- se deja el margen mínimo indispensable para que
   la figura crezca lo más posible sin tocar el borde del cuadro. */
.cat-editor-icon-badge svg{width:46px;height:46px}
.cat-editor-icon-badge--income{background:var(--green)}
.cat-editor-icon-badge--expense{background:var(--orange)}
/* Punto reforzado visual de la lista de Categorías (renderCategoriesScreen,
   render.js): un punto fijo verde/naranja que indica el TIPO de
   movimiento de la fila, independiente del color propio de la
   categoría (Punto 5) -- ninguno de los dos reemplaza al otro. */
.cat-type-dot{width:10px;height:10px;border-radius:50%;flex:none;margin:0 2px}
/* Overlay "¿A qué tipo de movimiento pertenece esta categoría?"
   (#catTypeOverlay, imagen aprobada Panel 2 superior) -- mismo patrón de
   dos tarjetas que .mode-grid/.mode-card (Modo Claro/Oscuro) pero
   teñidas verde/naranja en vez de neutras, para reforzar Ingresos/Gastos. */
.cattype-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:4px}
.cattype-btn{
  border:2px solid transparent;border-radius:14px;padding:16px 10px;
  display:grid;justify-items:center;gap:8px;font-weight:700;
}
.cattype-btn svg{width:26px;height:26px}
.cattype-btn--income{background:#E9FBF0;color:var(--green)}
.cattype-btn--expense{background:#FFF1E6;color:var(--orange)}
[data-mode="dark"] .cattype-btn--income{background:#123423}
[data-mode="dark"] .cattype-btn--expense{background:#3a2412}
/* Campo de solo lectura "#FF8A00" de "Personalizar color" cuando el
   contexto es una categoría (#colorCustomizeHexSection) -- Punto 5,
   la imagen aprobada de esa variante muestra el código en vez de la
   vista previa/colores rápidos (que solo existen para el contexto
   'appearance', ver renderColorCustomize en render.js). */
.hex-readout{
  background:var(--surface-2);border:1px solid var(--border);border-radius:12px;
  padding:12px 14px;font-weight:800;font-size:1rem;letter-spacing:.03em;
  text-align:center;margin:6px 0 4px;
}

.mode-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.mode-card{
  border:2px solid var(--border);border-radius:14px;padding:18px 10px;
  display:grid;justify-items:center;gap:8px;background:var(--surface-2);
  position:relative;
}
.mode-card[aria-pressed="true"]{border-color:var(--accent)}
.mode-card .dot{width:18px;height:18px;border-radius:50%;border:2px solid var(--muted)}
.mode-card[aria-pressed="true"] .dot{border-color:var(--accent);background:var(--accent)}
.mode-card-icon{width:30px;height:30px;color:var(--muted)}
.mode-card[aria-pressed="true"] .mode-card-icon{color:var(--accent)}

/* Auditoría "Tipo de ahorro" (Metas → Nueva meta): .mode-card/.mode-grid
   de arriba también los usan Apariencia (#modeGrid) y Bancos
   (#bankTypeChoices) -- por eso estas reglas van SCOPEADAS
   exclusivamente a #goalSavingTypeChoices, sin tocar .mode-card ni
   .mode-card-icon de forma global (no debe afectar a esas otras dos
   pantallas). .saving-type-icon-fallback empieza oculto: solo se
   muestra si el <img> falla al cargar (ver savingTypeIconHTML(),
   render.js), tomando el lugar del <svg> original.
   AJUSTE VISUAL (revisión en iPhone real, iteración final): el
   objeto sube de 48px a 56px -- pero sin arrastrar a la tarjeta a un
   alto mayor (.mode-card usa alto automático, un objeto más grande la
   agranda 1:1 si no se compensa). Se recupera el alto aproximado
   original (~131px) reduciendo SOLO el padding VERTICAL de la
   tarjeta (18px→9px arriba/abajo); el padding horizontal (10px) y el
   gap entre objeto/texto/dot (8px) quedan intactos, para no comprimir
   el ancho ni amontonar el contenido. Fórmula verificada empíricamente
   con 3 puntos de datos ya medidos (131px con 38px/pad18, 137px con
   44px/pad18, 131px con 48px/pad13 -- alto = 2×padding + objeto + 57
   de "resto fijo" -- constante en los 3 casos): con padding vertical
   9px + objeto 56px, el alto vuelve a dar ~131px (2×9+56+57=131). */
#goalSavingTypeChoices .mode-card{padding:9px 10px}
#goalSavingTypeChoices .saving-type-icon{width:56px;height:56px;object-fit:contain;flex:none}
#goalSavingTypeChoices .saving-type-icon-fallback{display:none}

/* FASE 10 (Punto 2, "el cuadro de Modo Oscuro debe verse REALMENTE
   oscuro para que la diferencia sea evidente"): hallazgo -- antes las
   dos tarjetas usaban "background:var(--surface-2)" (arriba), una
   variable que cambia según el MODO ACTIVO global. Con el modo activo
   en claro, ambas tarjetas se veían claras (incluida "Oscuro"); con el
   modo activo en oscuro, ambas se veían oscuras (incluida "Claro") --
   nunca se podía distinguir una de otra por color, contradiciendo el
   propósito mismo de un selector de vista previa. Se fijan colores
   PROPIOS por tarjeta (independientes del modo activo, igual que en la
   imagen de referencia: la tarjeta "Claro" siempre blanca, la "Oscuro"
   siempre casi negra) sin tocar la distribución/estructura ya aprobada
   (mismo grid, mismo ícono, mismo punto indicador). */
.mode-card[data-mode-choice="light"]{background:#ffffff;border-color:#e2e5f1;color:#16192b}
.mode-card[data-mode-choice="light"] .mode-card-icon{color:#94a3b8}
.mode-card[data-mode-choice="light"][aria-pressed="true"]{border-color:var(--accent)}
.mode-card[data-mode-choice="light"][aria-pressed="true"] .mode-card-icon{color:var(--accent)}
.mode-card[data-mode-choice="dark"]{background:#0b0d17;border-color:#0b0d17;color:#f4f6fb}
.mode-card[data-mode-choice="dark"] .mode-card-icon{color:#7d85ab}
.mode-card[data-mode-choice="dark"][aria-pressed="true"]{border-color:var(--accent)}
.mode-card[data-mode-choice="dark"][aria-pressed="true"] .mode-card-icon{color:var(--accent)}

/* ---- Vacíos ---- */
.empty-state{text-align:center;padding:30px 14px;color:var(--muted)}
.empty-state .icon{font-size:2.2rem;margin-bottom:8px}
.empty-state svg.icon{width:40px;height:40px;display:block;margin:0 auto 8px}
/* Estado vacío "de pantalla completa" (p. ej. Metas sin metas): a diferencia
   de los estados vacíos anidados dentro de un .card/.list-card (que ya
   quedan bien encuadrados por su tarjeta contenedora), este vive suelto
   directamente en <main>. Desde que "main{align-content:start}" dejó de
   estirar las filas de la grilla para rellenar el alto sobrante (fix de la
   Tarea #84, ver comentario junto a esa regla), este bloque quedaba pegado
   arriba con un vacío enorme debajo. Se centra explícitamente en el alto
   restante para que la ausencia de datos se vea intencional, no rota. */
.empty-state-standalone{
  min-height:min(46vh,360px);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
}

.hidden{display:none !important}

/* ---- Utilidades de texto ---- */
.text-green{color:var(--green)}
.text-red{color:var(--red)}
.text-accent{color:var(--accent)}
.amount-lg{font-size:2.1rem;font-weight:900;line-height:1.05}

@media(max-width:340px){
  .tabs{grid-template-columns:repeat(5,1fr)}
  .tab{font-size:.62rem;height:50px}
}

/* Accesibilidad heredada de Fase 5 */
.tab:focus-visible{outline:3px solid var(--accent-soft);outline-offset:2px}

/* ============================================================
   Fase 11 (segunda pasada, fidelidad estricta) — "Diseño de la
   aplicación" → Estilo de íconos. Referencia ÚNICA y definitiva:
   Imagen_33.png ("CONFIGURA EL DISEÑO DE TU APLICACIÓN"), confirmada
   explícitamente por el usuario. Los 5 estilos, en el orden y con los
   nombres exactos de la imagen:
     1. Lineal moderno    — solo trazo, color de acento uniforme, sin
                             círculo de fondo.
     2. Relleno colorido  — círculo sólido de color por función,
                             glifo blanco.
     3. Gradiente suave    — círculo con degradado azul uniforme (mismo
                             tono para todos los íconos), glifo blanco.
     4. Contorno colorido  — círculo con anillo de color por función,
                             glifo del mismo color, fondo transparente.
                             *** PREDETERMINADO *** (marcado ✓ en la
                             imagen).
     5. 3D suave           — tarjeta clara redondeada con sombra/relieve
                             (efecto glossy), glifo de color por función.

   Los íconos ahora son SVG reales (sprite en js/icons.js, ver
   injectIconSprite()/svgIcon()), NO emoji — cumple la regla de
   fidelidad #3. Un solo trazo vectorial por ícono se "re-pinta" con
   estas 5 reglas CSS, así los 5 estilos son tratamientos reales y no
   una simulación con glifos distintos.

   Estas reglas se aplican tanto a) globalmente vía [data-iconstyle] en
   <html> (para que el estilo elegido se refleje en TODA la app, tal
   como indica la propia Imagen_33: "El cambio se aplicará en toda la
   aplicación"), como b) localmente vía [data-iconstyle-preview] en cada
   fila del selector, para poder mostrar los 5 ejemplos simultáneamente
   con su propio estilo (igual que en la imagen), sin depender del
   estilo activo global.
   ============================================================ */
/* FASE 10 (Punto 3): 20px → 22px dentro del mismo círculo de 38px (ver
   comentario extenso en icons.js sobre "menos protagonismo del círculo,
   más protagonismo del ícono") -- no se toca el tamaño del propio
   círculo (.row-icon, más arriba) para no alterar ninguna distribución
   ya aprobada, solo la proporción que ocupa el glifo dentro de él. */
.row-icon .icon{width:22px;height:22px}
.row-icon svg use{color:inherit}
/* PILOTO "3D realista" (6 íconos aprobados, ver ICON_3D_MAP en
   render.js): tamaño del <img> alterno dentro del mismo .row-icon (que
   NO cambia de tamaño, sigue siendo 38x38px -- ver más abajo, sección
   AJUSTE VISUAL). object-fit:contain preserva la proporción real de
   cada asset (192x192 con distinto contenido interior por ícono, ver
   informe de generación) sin recortarla ni deformarla. display:none
   por defecto -- JS (applyIcon3dStyle(), render.js) lo revela solo
   cuando iconStyle==='3d' y el asset cargó correctamente; nunca se
   muestran ambos (SVG + IMG) a la vez.
   AJUSTE VISUAL (auditoría en iPhone real tras el piloto integrado,
   commit cfaaf21: "el objeto se ve demasiado pequeño"): 22px sobre un
   contenedor de 38px es ~58% del área -- se sube a 29px (~76%, dentro
   del rango 70-80% pedido) probado visualmente sin invadir el texto de
   la fila ni alterar su altura (el contenedor .row-icon sigue fijo en
   38x38, la fila no cambia).
   AJUSTE FINAL (piloto ya aprobado visualmente en iPhone, único pedido
   restante: "aumentar exclusivamente el tamaño visual de los 6
   objetos"): 29px -> 32px (~84% del contenedor de 38px). Único cambio
   de esta pasada -- el contenedor .row-icon sigue exactamente en
   38x38px, sin tocar filas/texto/chevrons, y la tarjeta de vista previa
   de Apariencia sigue gobernada por su propia regla, sin cambios (ver
   ".iconstyle-mini-grid .row-icon-3d" más abajo, 34px, intacta). */
.row-icon-3d{width:32px;height:32px;object-fit:contain;display:none;flex:none}

/* Las reglas "globales" (activadas por [data-iconstyle] en <html>) se
   escriben dentro de :where(...) para que tengan especificidad CERO en
   esa parte del selector — así, cuando una fila de previsualización
   local ([data-iconstyle-preview], especificidad normal) pide un estilo
   distinto al global activo, la local SIEMPRE gana sin importar el
   orden de declaración. Sin esto, las 5 tarjetas del selector terminaban
   todas mostrando el estilo global activo en vez de su propio estilo
   (bug detectado en la verificación visual de esta fase). */
:where([data-iconstyle="lineal"]) .row-icon,[data-iconstyle-preview="lineal"] .row-icon{
  background:transparent;border:0;color:var(--accent);box-shadow:none;
}
:where([data-iconstyle="relleno"]) .row-icon,[data-iconstyle-preview="relleno"] .row-icon{
  background:var(--rc,var(--accent));border:0;color:#fff;box-shadow:none;
}
:where([data-iconstyle="gradiente"]) .row-icon,[data-iconstyle-preview="gradiente"] .row-icon{
  background-image:linear-gradient(135deg,#60A5FA,#2563EB);border:0;color:#fff;box-shadow:none;
}
:where([data-iconstyle="contorno"]) .row-icon,[data-iconstyle-preview="contorno"] .row-icon{
  background:transparent;border:2px solid var(--rc,var(--accent));color:var(--rc,var(--accent));box-shadow:none;
}
:where([data-iconstyle="3d"]) .row-icon,[data-iconstyle-preview="3d"] .row-icon{
  background:linear-gradient(160deg,#ffffff,#eef1f8);border:0;color:var(--rc,var(--accent));border-radius:11px;
  box-shadow:0 3px 0 #00000014, 0 6px 14px #00000030, inset 0 -3px 5px #00000014, inset 0 2px 2px #ffffff;
}
/* AJUSTE VISUAL (auditoría en iPhone real, PILOTO 6 íconos, commit
   cfaaf21: "el estilo 3D realista está mostrando una base/cuadro
   blanco con sombra alrededor del objeto" -- el objetivo aprobado es
   que el objeto 3D sea el protagonista, sin baldosa visible detrás).
   ORIGEN CONFIRMADO por auditoría previa a este cambio: la baldosa la
   generaba ÚNICAMENTE la regla de arriba (fondo blanco/gris + sombra +
   radio de 11px sobre CUALQUIER .row-icon bajo iconStyle==='3d'), sin
   distinguir si ese contenedor en particular mostraba de verdad el
   objeto 3D o seguía en SVG de respaldo -- los 6 WebP no contienen
   ningún fondo propio (verificado: alfa=0 en las 4 esquinas de cada
   uno). "row-icon-3d-active" es la marca que pone JS (applyIcon3dStyle()
   y renderAppearance(), render.js) SOLO en los .row-icon que están
   mostrando de verdad el <img> 3D en ese momento -- mayor especificidad
   (2 clases) que la regla de arriba (:where(...) aporta 0), así que
   siempre gana sin importar el orden. Los símbolos que queden en SVG de
   respaldo (fuera de ICON_3D_MAP, o el asset falló) NUNCA reciben esta
   clase y conservan intacto el tratamiento "3D suave" de arriba -- ver
   Sección 2 de la orden ("Los SVG que permanezcan como fallback NO deben
   quedar accidentalmente sin el tratamiento que necesiten"). El propio
   .row-icon (38x38px, geometría/alineación de la fila) NO cambia: solo
   se le quita el fondo/borde/sombra/radio quedando como una caja
   estructural invisible. */
:where([data-iconstyle="3d"]) .row-icon.row-icon-3d-active,[data-iconstyle-preview="3d"] .row-icon.row-icon-3d-active{
  background:transparent;box-shadow:none;border-radius:0;
}
/* LOTE 3 (Perfil, íconos prof-*): auditoría previa detectó que la regla
   de arriba NO alcanza a quitar la baldosa dentro de "Accesos rápidos"
   (Mis documentos/Actividad de la cuenta/Calendario financiero) --
   ".profile-quick-tile .row-icon.profile-icon" (3 clases, línea ~1802)
   tiene MÁS especificidad que ".row-icon.row-icon-3d-active" (2 clases)
   y gana sin importar el orden, verificado empíricamente antes de
   escribir esta regla. Mismo criterio que el propio código ya usa para
   el tamaño (comentario en la línea ~1799: "se repite el tamaño aquí
   con el mismo peso de selector para que .profile-icon gane también
   dentro de Accesos rápidos") -- se repite aquí esa misma idea para la
   excepción de fondo/sombra, ahora con 3 clases para empatar y ganar
   por orden (esta regla va después en el archivo). Los 2 prof-* que NO
   están en Accesos rápidos (Mi información/Inicio de sesión seguro) ya
   quedaban cubiertos por la regla de arriba, sin necesitar esta. */
:where([data-iconstyle="3d"]) .profile-quick-tile .row-icon.row-icon-3d-active,[data-iconstyle-preview="3d"] .profile-quick-tile .row-icon.row-icon-3d-active{
  background:transparent;box-shadow:none;border-radius:0;
}

/* Fila del selector "Diseño de la aplicación": cada una de las 5 debe
   mostrarse en SU PROPIO estilo (no en el estilo global activo) para que
   las 5 opciones se puedan comparar a la vez, tal como en Imagen_33. */
.iconstyle-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.iconstyle-card{
  background:var(--surface);border:2px solid var(--border);border-radius:16px;
  padding:12px;text-align:left;
}
.iconstyle-card[aria-pressed="true"]{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.iconstyle-card-head{display:flex;align-items:center;gap:8px;font-size:.86rem;font-weight:800}
.iconstyle-num{
  width:20px;height:20px;border-radius:50%;background:var(--accent);color:#fff;
  display:grid;place-items:center;font-size:.7rem;flex:none;
}
.iconstyle-check{margin-left:auto;color:var(--accent);flex:none}
.iconstyle-check .icon{width:18px;height:18px}
.iconstyle-mini-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}
.iconstyle-mini-grid .row-icon{margin:0 auto}
/* CORRECCIÓN PUNTUAL — FASE 10 (forma y tamaño interno de los íconos de
   "Apariencia → Estilo de íconos"): se agranda ÚNICAMENTE la figura
   interior mostrada dentro de cada una de las 5 tarjetas de estilo,
   igual que en #catIconSwatches (Fase 12). Ámbito estrictamente
   local vía ".iconstyle-mini-grid .row-icon .icon" -- NO se toca la
   regla global ".row-icon .icon{width:22px;height:22px}" de más arriba,
   que sigue rigiendo el resto de la app (filas de listas, accesos
   rápidos de Perfil, etc.), para no alterar ninguna otra pantalla.
   El círculo/tarjeta contenedora (.row-icon, 38x38) NO cambia de tamaño.
   32px es el máximo verificado que no toca el aro en "Contorno colorido"
   (el más ajustado: el borde de 2px reduce el área útil interior a 34px)
   ni recorta la figura del ícono "Ajustes" (el diseño más ajustado al
   viewBox de los 6 íconos de muestra) -- un verdadero 2x (44px) excedería
   ese aro, así que se usa el tamaño seguro más grande posible en vez de
   un doble literal. */
.iconstyle-mini-grid .row-icon .icon{width:32px;height:32px}
/* Misma ampliación de 22px→32px, ahora para el <img> del piloto "3D
   realista" dentro de esta misma cuadrícula de muestra (ver
   ".row-icon-3d" más arriba) -- mismo criterio, mismo tamaño máximo ya
   verificado. */
/* AJUSTE VISUAL (Sección 5 de la orden): la tarjeta "3D realista" ya no
   tiene el borde/aro que antes limitaba el glifo a 32px máximo (ese
   límite era específico de "Contorno colorido", ver comentario de
   arriba) -- al quitarle la baldosa a esta tarjeta (regla
   ".row-icon.row-icon-3d-active" más arriba), sobra espacio dentro del
   mismo contenedor de 38px, así que el objeto puede crecer un poco más
   que en el resto de tarjetas para que se sienta protagonista, sin
   tocar ni superponerse con sus vecinos (gap de 8px entre columnas,
   verificado visualmente a 390px de ancho). */
.iconstyle-mini-grid .row-icon-3d{width:34px;height:34px}

/* ============================================================
   FASE 10 (Punto 5) — #screen-colorCustomize: selector circular de
   degradación + barra deslizante + vista previa en tiempo real. El
   degradado real de fondo de .color-wheel/.card-hero de esta pantalla se
   fija por JS (renderColorCustomize, render.js) según el color y el
   "tono" elegidos -- aquí solo se define la geometría/el "marco" fijo.
   ============================================================ */
.color-wheel-wrap{display:flex;justify-content:center;padding:10px 0 18px}
.color-wheel{
  width:230px;height:230px;border-radius:50%;position:relative;flex:none;
  box-shadow:0 14px 34px #00000030;
}
/* Botón real (no solo decorativo) para que el thumb sea alcanzable por
   teclado/lector de pantalla -- se mueve arrastrando (pointerdown/move,
   ver events.js) o, sin puntero, no tiene un equivalente por flechas
   todavía (mismo alcance que el resto de esta fase: interacción táctil/
   mouse, consistente con cómo ya funcionan swatches y sliders nativos
   del resto de la app). */
.color-wheel-thumb{
  position:absolute;top:50%;left:50%;width:30px;height:30px;
  border-radius:50%;border:3px solid #fff;background:transparent;
  box-shadow:0 2px 8px #00000055;transform:translate(-50%,-50%);
  cursor:grab;touch-action:none;
}
.color-wheel-thumb:active{cursor:grabbing}

/* Slider nativo re-estilizado (mismo patrón cross-browser de siempre:
   resetear -webkit/-moz-appearance y redefinir track+thumb) -- el
   degradado del track también lo fija JS por color/tono elegidos. */
.tone-slider{
  -webkit-appearance:none;appearance:none;width:100%;height:14px;border-radius:999px;
  margin:4px 0 18px;background:linear-gradient(90deg,#000,#fff);outline:none;
}
.tone-slider::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:26px;height:26px;border-radius:50%;
  background:#fff;border:3px solid var(--accent);box-shadow:0 2px 6px #00000050;cursor:pointer;
}
.tone-slider::-moz-range-thumb{
  width:26px;height:26px;border-radius:50%;background:#fff;border:3px solid var(--accent);
  box-shadow:0 2px 6px #00000050;cursor:pointer;
}

/* ============================================================
   ORDEN CORRECTIVA — Apariencia unificada (Manual §3.8, Corrección 2):
   Fondos A-E y Paleta de 9 colores, dentro de la misma pantalla que
   Modo e Íconos. Tarjetas de Fondo (una por fila, con degradado real de
   muestra) y swatches de acento (grid 3x3, igual patrón visual que
   Imagen_06/07 y que .swatch-grid ya usado en Categorías).
   ============================================================ */
/* FASE 10 (Punto 4, "Unificar completamente: Fondo + Paleta bajo un
   único apartado ESTILO DE FONDO"): reemplaza los bloques .fondo-grid/
   .fondo-card (5 presets A-E) y .palette-grid/.palette-swatch (9
   acentos) de arriba por una sola cuadrícula de cuadros de color
   redondeados, fiel a la imagen de referencia de esta fase (2 filas de
   6). Pulsar un color YA NO lo selecciona in-place (por eso ya no hay
   variante ":pressed" con check aquí -- salvo el color ACTIVO
   persistido, que sí lleva check para reflejar la elección vigente):
   toda personalización real vive en #screen-colorCustomize. */
.fondo-color-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:10px}
.fondo-color-swatch{
  position:relative;aspect-ratio:1;border-radius:14px;border:2px solid transparent;
  display:grid;place-items:center;box-shadow:inset 0 0 0 1px #ffffff26;
}
.fondo-color-swatch[aria-pressed="true"]{border-color:var(--ink)}
.fondo-color-swatch-check{color:#fff;filter:drop-shadow(0 1px 2px #00000080)}
.fondo-color-swatch-check .icon{width:20px;height:20px}
.fondo-color-swatch--arcoiris{background:conic-gradient(from 200deg,#F13D3C,#FC7F06,#FBB723,#04B543,#2A4DEE,#803FDB,#F13D3C)}
/* Variante compacta reutilizada dentro de "Colores rápidos" en
   #screen-colorCustomize -- mismos cuadros, más pequeños, sin el hueco
   visual de gap tan grande que sí tiene sentido en la pantalla
   principal. */
.fondo-color-grid-compact{grid-template-columns:repeat(6,1fr);gap:8px;margin-bottom:8px}
.fondo-color-grid-compact .fondo-color-swatch{border-radius:10px}
/* ORDEN CORRECTIVA — Fidelidad visual y Safe Area: generalizado a TODAS
   las pantallas internas (antes era un fix quirúrgico solo de
   Apariencia). Motivo: position:sticky solo "pega" respecto a un
   ancestro con scroll REAL. .app tiene overflow-x:hidden, y por la
   regla de interoperabilidad de CSS Overflow (cuando un eje no es
   'visible', el otro eje 'visible' se computa como 'auto') termina
   siendo un contenedor de scroll aunque no sea el que en la práctica se
   desplaza (la página/ventana crece y es quien hace scroll, porque .app
   no tiene alto fijo) — con eso, ninguna .topbar sticky "veía"
   desplazamiento real y la flecha de regreso se iba con el resto del
   contenido al hacer scroll en pantallas largas (Moneda, Categorías,
   etc.), quedando inaccesible sin desplazarse de vuelta hacia arriba —
   justo lo contrario de "accesible sin girar el teléfono" que pide esta
   orden. Cada pantalla interna pasa a ser su propio contenedor de
   scroll de alto fijo (100dvh), con su <main> desplazándose dentro de
   sí misma: la barra superior con la flecha queda SIEMPRE visible, sin
   depender de sticky. No se aplica a .screen-auth (Login/Crear cuenta),
   que usa un layout de una sola tarjeta sin overflow y no lo necesita —
   se deja su comportamiento de scroll de documento tal como está,
   verificado que cabe completo en pantalla sin recorte. */
.screen.active:not(.screen-auth){display:flex;flex-direction:column;height:100vh;height:100dvh}
/* La franja libre para la barra inferior fija (96px) ahora la reserva
   cada <main> internamente (arriba). El padding-bottom:96px que antes
   vivía en .app (ver regla .app más arriba) quedó eliminado: sumado al
   nuevo alto fijo 100dvh de cada pantalla, generaba un sobrante de
   scroll a nivel de documento que arrastraba la pantalla entera
   (incluida la barra superior) hacia arriba al desplazarse — la flecha
   de regreso se salía de la pantalla por completo. Detectado con
   Playwright (tools/verify-safearea.mjs) comparando la posición de
   .back-btn antes/después de un scroll grande. */
.screen.active:not(.screen-auth) > main{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding-bottom:96px}

/* Textarea del formulario de Soporte (Fase 16) */
textarea{font-family:var(--font)}
textarea:focus{outline:none}

/* Checkbox de preferencias (Notificaciones, Respaldo automático) */
.list-row input[type="checkbox"]{width:20px;height:20px;accent-color:var(--accent);flex:none;margin-left:auto}

details > summary{list-style:none}
details > summary::-webkit-details-marker{display:none}
details > summary::before{content:'▸ ';color:var(--accent)}
details[open] > summary::before{content:'▾ '}

/* ============================================================
   TANDA 4 (Punto 4) — Adaptación real a orientación horizontal.
   ============================================================
   Diagnóstico (documentado como limitación en la Tanda 3, ahora resuelto
   aquí): en un viewport horizontal típico de teléfono (alto ~360-430px),
   el hero fijo de Inicio (~297px con su encabezado + resumen + accesos
   rápidos) más la barra inferior fija (~70px) dejaban a <main> con solo
   ~30-40px reales de alto visible -- técnicamente desplazable
   (verificado por entonces con getBoundingClientRect/scrollTop), pero
   inútil en la práctica para ver "Límites de gasto", "Gastos por
   categoría" o "Mis bancos".

   La solución NO es reducir uniformemente todo el contenido de la app
   (la orden de esta Tanda 4 lo prohíbe expresamente) sino comprimir
   ÚNICAMENTE los encabezados/franjas fijas que restan alto a <main>
   (que ya es flex:1 + overflow-y:auto en TODAS las pantallas, ver regla
   ".screen.active:not(.screen-auth) > main" más arriba) -- así <main>
   recupera espacio real y su scroll vertical (que ya funcionaba) por fin
   tiene algo útil que desplazar. El contenido en sí (textos, botones,
   tarjetas) no se oculta ni se elimina, solo se compacta su margen/
   relleno y se reducen ligeramente tamaños puramente decorativos (logo,
   dona de categorías). El umbral max-height:500px cubre teléfonos en
   horizontal (iPhone/Android típicos: 360-430px de alto) sin afectar
   tablets/escritorio en horizontal, que ya tienen alto de sobra y no lo
   necesitan.

   Se aplica a TODAS las pantallas (topbar/tabs son compartidas por
   Historial, Bancos, Metas, Ajustes, formularios y subpantallas), y
   además específicamente a Inicio (hero-home) por ser el caso más
   crítico ya documentado. */
@media (orientation:landscape) and (max-height:500px){
  /* Barra superior compartida por TODAS las pantallas internas
     (Historial, Bancos, Metas, Ajustes, formularios, subpantallas):
     menos padding vertical libera alto real a <main> sin ocultar el
     título ni mover la flecha de regreso fuera del área segura (el
     env(safe-area-inset-top) se conserva intacto). */
  .topbar{padding:calc(10px + env(safe-area-inset-top)) calc(16px + env(safe-area-inset-right)) 8px calc(16px + env(safe-area-inset-left))}
  .topbar h1,.topbar h2{font-size:1.05rem}

  /* Barra inferior: se reduce de 54px a 42px de alto por pestaña (sigue
     por encima del mínimo táctil de 44px de ANCHO -- en un grid de 5
     columnas repartiendo min(100%,520px), cada pestaña mide ~100px de
     ancho, muy por encima del mínimo; solo se reduce el ALTO, que no
     tiene un mínimo de accesibilidad equivalente al ser una barra
     continua, no un botón aislado). Los íconos y el texto se achican
     ligeramente en proporción, sin perder la etiqueta (no se oculta
     información). */
  .tabs{padding:4px calc(10px + env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom)) calc(10px + env(safe-area-inset-left))}
  .tab{height:42px;font-size:.6rem;gap:1px}
  .tab svg{width:22px;height:22px} /* ORDEN DE CORRECCIÓN: proporcional al nuevo tamaño 27px en vertical (antes 20px) */
  /* <main> reserva exactamente el nuevo alto de .tabs (antes 96px,
     pensado para .tabs a 88px de alto aprox.) para que el último
     elemento de cada pantalla no quede tapado por la barra inferior. */
  .screen.active:not(.screen-auth) > main{padding-bottom:64px}
  /* Mismo ajuste puntual de #screen-profile (ver el comentario completo
     junto a la regla equivalente en vertical, más arriba en este
     archivo) aplicado también aquí para que el colchón extra por
     safe-area no desaparezca al girar el dispositivo. */
  #screen-profile > main{padding-bottom:calc(64px + env(safe-area-inset-bottom))}
  /* FASE 2.2.2: mismo ajuste puntual aplicado a #screen-docClassify (ver
     comentario completo junto a la regla equivalente en vertical, más
     arriba en este archivo). */
  #screen-docClassify > main{padding-bottom:calc(64px + env(safe-area-inset-bottom))}
  /* FASE 2.2.3: en horizontal .tabs mide 53px real (4px pad-top + 42px
     alto de pestaña + 6px pad-bottom + 1px de su propio border-top, ver
     la media query de .tabs/.tab más arriba) en vez de los 73px de
     vertical -- la barra fija de Compartir/Exportar y el colchón de
     #screen-docViewer bajan en la misma proporción para seguir pegados
     a .tabs sin hueco ni solapamiento (corrección del border-top, ver
     comentario completo junto a la regla equivalente en vertical). */
  .doc-viewer-sharebar{bottom:calc(53px + env(safe-area-inset-bottom))}
  #screen-docViewer > main{padding-bottom:calc(132px + env(safe-area-inset-bottom))}
  /* CORRECCIÓN DEFINITIVA: mismo cálculo estructural (+58px sobre el
     colchón base de horizontal: 50px del segundo .btn + 8px de gap,
     132+58=190) aplicado también en horizontal -- ver comentario
     completo junto a la regla equivalente en vertical, más arriba en
     este archivo. */
  #docViewerMain.doc-viewer-pdf{padding-bottom:calc(190px + env(safe-area-inset-bottom))}

  /* ---- Inicio: hero compacto (Punto 4, caso crítico documentado) ---- */
  .hero-home{padding-top:calc(12px + env(safe-area-inset-top));padding-bottom:12px;padding-left:calc(16px + env(safe-area-inset-left));padding-right:calc(16px + env(safe-area-inset-right))}
  .home-header-center img{width:36px;height:36px;margin-bottom:2px} /* ORDEN DE CORRECCIÓN: proporcional al +38% del logo en vertical (antes 29px) */
  .home-header-center h1{font-size:.95rem}
  /* El subtítulo ("Ten el control de tus finanzas") es puramente
     decorativo -- se oculta SOLO aquí para ganar una línea de alto; el
     saludo principal (h1) y el resto de la información permanecen. No
     es el mismo caso que "ocultar información importante" (Punto 4):
     ningún dato financiero, botón o control desaparece. */
  .home-header-center p{display:none}
  .home-hero-stack{gap:8px;margin-top:10px}
  .card-on-hero-dark{padding:10px}
  .card-on-hero-dark > div[style*="margin-top:14px"]{margin-top:8px}
  .stat-icon{width:26px;height:26px}
  .stat-icon .icon{width:13px;height:13px}
  .quick-action-card{padding:7px}
  .quick-action-card .stat-icon{width:24px;height:24px}
  .home-limits-card{padding:10px 14px 4px}
  .home-section-card{padding:14px}
  /* Dona de "Gastos por categoría": 140px de diámetro (Tanda 1, Punto 9)
     es más de un tercio del alto disponible en horizontal -- se reduce
     a 92px (sigue siendo mayor que el tamaño base de 118px usado en
     Reportes/otras pantallas) para que la tarjeta completa quepa sin
     recortar la leyenda de categorías que la acompaña. */
  #homeCats .donut-canvas-wrap{width:92px;height:92px}

  /* Modales (confirmaciones, oferta biométrica, etc.): sin alto máximo
     podían salirse del área visible por arriba (el overlay los ancla al
     borde inferior con align-items:flex-end) en un viewport de ~360-
     430px de alto. Con max-height + overflow-y:auto, el contenido del
     modal se desplaza internamente en vez de recortarse; los botones
     "Cancelar"/"Confirmar"/"En otro momento" permanecen dentro del
     área visible siempre porque son el último elemento del propio
     sheet desplazable. */
  .modal-sheet{max-height:calc(100dvh - 16px);overflow-y:auto}

  /* ORDEN DE CORRECCIÓN (Punto 5, "al girar no abarca bien el tamaño del
     teléfono como otras aplicaciones"): la causa real no era la
     compactación de esta Tanda 4 (ya verificada 52/52 en el barrido de
     horizontal) sino un límite de ancho que ni siquiera es específico de
     horizontal -- ".app"/".tabs" usan "width:min(100%,520px)" en TODA la
     app, pensado originalmente como ancho de lectura cómodo para cuando
     el sitio se abre en un navegador de escritorio ancho. En un teléfono
     normal en vertical (390-430px de ancho típico) ese límite nunca se
     activa porque 100% ya es menor que 520px -- por eso nunca se notó
     hasta ahora. Pero un teléfono en HORIZONTAL puede medir 700-930px de
     ancho real (iPhone/Android típicos), y ahí el límite de 520px SÍ se
     activa: la app entera queda encogida en una columna centrada con
     franjas vacías a los lados, en vez de usar el ancho real del
     teléfono como hacen otras apps. Se libera el límite únicamente
     dentro de este mismo umbral ya establecido en la Tanda 4
     (orientation:landscape + max-height:500px, que en la práctica
     identifica "teléfono girado", no tablet/escritorio en horizontal,
     que sí tienen alto de sobra y no entran aquí) -- así un teléfono
     girado usa su ancho real, y una tablet o el navegador de escritorio
     en horizontal siguen viendo la columna de 520px, que ahí sí tiene
     sentido como ancho de lectura. */
  .app{width:100%}
  .screen-auth.active::before{width:100%}
  .tabs{width:100%}

  /* ================================================================
     CORRECCIÓN FINAL — ADAPTACIÓN HORIZONTAL (COMPLEMENTO DE LA TANDA 6)
     Hasta aquí este mismo media query ya liberaba el ANCHO real del
     teléfono girado (".app"/".tabs" arriba) y compactaba encabezados/
     barra inferior -- pero el CONTENIDO seguía en una sola columna,
     igual que en vertical, solo que estirada al ancho nuevo: por eso
     "seguía pareciendo una versión vertical estirada" tras esa
     corrección. Esta corrección redistribuye el contenido en columnas
     reales para aprovechar el ancho, sin tocar logo/íconos/barra
     inferior/nombres de pestañas ya aprobados (Punto 6 de la orden). */

  /* ---- Punto 2: encabezado de Inicio más compacto ----
     Se reduce aún más el aire vertical alrededor de logo/usuario/
     campana (ya compactados en la corrección anterior), SIN mover el
     logo de su posición aprobada: sigue centrado en la misma fila
     junto al avatar y la campana, mismo tamaño. */
  .hero-home{padding-bottom:8px}

  /* ---- Puntos 1 y 3: "Resumen de este mes" + accesos rápidos, lado a
     lado en vez de apilados ----
     Antes: tarjeta de Resumen arriba, botones "Agregar ingreso/gasto"
     debajo -- el doble de alto del necesario cuando sobra ancho. En
     fila se recupera casi la mitad de ese alto Y se usa el ancho real
     de un teléfono girado (redistribución, no una simple reducción de
     tamaños: mismo texto, mismos íconos, mismo tamaño de letra). Los
     accesos rápidos pasan de una fila de 2 botones anchos a una
     columna angosta de 2 botones apilados junto al Resumen. */
  .home-hero-stack{flex-direction:row;align-items:stretch;gap:10px;margin-top:8px}
  .card-on-hero-dark{flex:1.3}
  .quick-action-row{flex:1;flex-direction:column;justify-content:center}

  /* ---- Puntos 3 y 4: "Límites de gasto" / "Gastos por categoría" /
     "Mis bancos", en columnas dentro de <main> ----
     <main> ya es un grid de una sola columna (ver regla base "main{...}"
     más arriba, compartida por toda la app); aquí se amplía a 2
     columnas SOLO para Inicio, solo en este mismo umbral de "teléfono
     real girado" ya establecido. "Límites de gasto" y "Gastos por
     categoría" quedan lado a lado (misma fila). La alerta de límite
     (si aparece) y "Mis bancos" (fila de tarjetas con su propio scroll
     horizontal) ocupan el ancho completo debajo -- partirlas en 2
     columnas no aportaría nada, ya son horizontales por sí mismas.
     Nada se oculta ni se elimina: solo cambia dónde cae cada tarjeta. */
  #screen-home.active>main{grid-template-columns:repeat(2,minmax(0,1fr))}
  #screen-home.active>main>.home-limits-card{grid-column:1}
  #screen-home.active>main>.home-section-card:not(#homeBanksCard){grid-column:2}
  #screen-home.active>main>#homeLimitAlert{grid-column:1/-1}
  #screen-home.active>main>#homeBanksCard{grid-column:1/-1}
  .home-limits-card{padding:10px 14px 4px}
  .home-section-card{padding:12px}
  .limit-row+.limit-row{margin-top:10px}

  /* ---- Punto 5: la dona de "Gastos por categoría" CRECE, no se
     achica ----
     La corrección anterior la reducía a 92px porque tenía que competir
     por ALTO en una única columna angosta. Ahora que "Gastos por
     categoría" tiene su propia columna de ~400px de ancho en un
     teléfono girado típico, se le da más diámetro -- mismo canvas de
     180px de resolución real (ver drawDonutChart()/render.js), así que
     no se ve borrosa al escalarla -- en vez de quedarse con apariencia
     de pantalla vertical achicada. */
  #homeCats .donut-canvas-wrap{width:128px;height:128px}

  /* ---- Punto 4: Historial en 2 columnas ----
     El buscador y los 3 filtros (Todos/Ingresos/Gastos) comparten fila
     -- ya no hace falta apilarlos con el ancho real disponible. La
     lista de movimientos (agrupada por día, ver groupedMovRows() en
     render.js) se reparte en 2 columnas: cada cabecera de día ("Hoy
     +$120.00"...) sigue ocupando el ancho COMPLETO -- para no
     separarla visualmente de sus propios movimientos -- y sus filas se
     reparten debajo, de izquierda a derecha, dentro de ese mismo
     grupo. Se ve más información sin desplazarse, sin romper el
     agrupamiento por fecha. */
  #screen-movements.active>main{grid-template-columns:1fr 1fr;column-gap:12px}
  #screen-movements.active>main>.input-wrap{grid-column:1}
  #screen-movements.active>main>#movTabs{grid-column:2;align-self:center}
  #screen-movements.active>main>#movList{grid-column:1/-1;display:grid;grid-template-columns:repeat(2,1fr);column-gap:14px}
  #screen-movements .mov-day-header{grid-column:1/-1}

  /* ---- Punto 4: Bancos y Metas en 2 columnas ----
     "#bancosList"/"#goalsList" ya eran "display:grid;gap:12px" (una
     sola columna implícita, sin "grid-template-columns" propio -- ver
     HTML) -- se agrega la segunda columna únicamente aquí. Las
     tarjetas de cada cuenta/tarjeta bancaria y de cada meta ya son
     compactas y legibles a la mitad del ancho: se diseñaron pensando
     en el ancho de un teléfono en vertical, muy similar al de media
     columna de uno girado. */
  #bancosList{grid-template-columns:repeat(2,1fr)}
  #goalsList{grid-template-columns:repeat(2,1fr)}

  /* ================================================================
     CORRECCIÓN FINAL — AUDITORÍA COMPLETA DEL MODO HORIZONTAL
     (COMPLEMENTO DE LA TANDA 6)
     Las dos correcciones anteriores ya arreglaron el ANCHO real del
     teléfono girado y la redistribución de Inicio/Historial/Bancos/
     Metas. Esta auditoría cubre TODA la app: pantallas de
     autenticación, formularios, Notificaciones, Bancos (mensaje +
     botón), encabezado de Ajustes, y un huso sistémico para las ~20
     pantallas tipo "lista de opciones" (Moneda, Idioma, Categorías,
     Manual, Acerca de, Soporte, Respaldo, Documentos, Perfil, Ajustes,
     Legal...) que comparten el mismo componente `.list-card`. No se
     toca logo/íconos/barra inferior/nombres de pestañas/colores
     aprobados (Punto 8 de la orden). */

  /* ---- Punto 7 (hallazgo de arquitectura): notch/isla dinámica en
     horizontal ----
     env(safe-area-inset-left)/env(safe-area-inset-right) NO se usaban
     en ningún lugar de este CSS (confirmado por búsqueda antes de esta
     corrección) -- únicamente top/bottom. Mientras la app estaba
     limitada a una columna de 520px centrada, esto no importaba: nunca
     tocaba el borde físico del teléfono. Pero la corrección anterior ya
     liberó ese límite (".app{width:100%}"), así que ahora el contenido
     SÍ llega al borde real -- y en un iPhone con notch/isla dinámica
     girado, la isla se recorta hacia uno de los bordes laterales, no
     arriba. Por eso la flecha de "Atrás" de Crear cuenta (y cualquier
     otro contenido pegado al borde) podía quedar parcialmente debajo
     del área del sistema en un iPhone real -- algo que ningún
     navegador de escritorio ni el modo responsive genérico reproduce
     (env() siempre da 0 sin hardware real), por eso no se había
     detectado antes. env(safe-area-inset-left/right) es 0 en
     dispositivos sin recorte lateral (Android, la mayoría de iPhones
     sin isla, escritorio), así que sumarlo aquí nunca reduce espacio
     ya existente, solo lo protege donde hace falta -- mismo patrón ya
     usado con safe-area-inset-top/bottom en toda la app. Se añade a
     ".topbar"/"main"/".tabs"/".hero-home" (arriba) y aquí también a
     ".auth-logo" y los contenedores de Login/Crear cuenta (abajo),
     que son las únicas piezas que faltaban por cubrir. Nota de
     verificación honesta: un navegador (incluida esta suite de
     pruebas) SIEMPRE reporta env(safe-area-inset-*)=0 sin hardware
     real, así que este arreglo no se puede fotografiar "funcionando"
     en horizontal -- se verifica por revisión de código (grep
     confirmando que ya no hay ninguna pieza de borde sin el inset) y
     queda documentado como tal, no como una captura visual. */
  main{padding-left:calc(16px + env(safe-area-inset-left));padding-right:calc(16px + env(safe-area-inset-right))}

  /* ---- Puntos 1/2/9: Login, Crear cuenta, Recuperar contraseña,
     Correo enviado -- ya no se ven como una pantalla vertical
     apilada y cortada por el alto de 393px ----
     Login y Crear cuenta usan ".screen-auth" (fondo degradado a toda
     página, sin la barra "topbar+main" del resto de pantallas) con
     logo/título arriba, tarjeta de formulario en medio y accesos
     (crear cuenta/olvidé contraseña) abajo -- las 3 secciones
     apiladas verticalmente se salían muy por debajo de 393px de alto.
     Se redistribuyen en 2 columnas (logo a la izquierda, formulario +
     accesos a la derecha) reutilizando la misma técnica de asignar
     "grid-column" a los hijos directos ya usada en Inicio/Historial --
     sin envolver nada en HTML nuevo, sin cambiar textos ni campos. Si
     aun así el contenido de la derecha no cabe entero en 393px de
     alto, la página sigue permitiendo scroll normal (igual que ya
     ocurría en vertical, donde estas pantallas también son más altas
     que un único viewport) -- no se oculta ni se recorta nada. */
  #screen-login.active{
    display:grid;grid-template-columns:minmax(0,34%) minmax(0,1fr);
    align-items:start;gap:2px 18px;
    padding:8px calc(18px + env(safe-area-inset-right)) calc(10px + env(safe-area-inset-bottom)) calc(18px + env(safe-area-inset-left));
  }
  #screen-login.active>.auth-logo{grid-column:1;grid-row:1/3;padding:4px 0;gap:4px;align-self:center}
  #screen-login.active>.auth-logo img{width:52px;height:52px}
  #screen-login.active>.auth-logo h1{font-size:1rem;margin-top:0}
  #screen-login.active>.auth-logo p{font-size:.72rem;max-width:190px}
  #screen-login.active>.auth-card{grid-column:2;grid-row:1;margin:6px 0 0;padding:12px 16px}
  #screen-login.active>.auth-card h2{font-size:1.05rem}
  #screen-login.active>.auth-card .field{margin-bottom:8px}
  #screen-login.active>.auth-actions{grid-column:2;grid-row:2;padding:4px 0 0;gap:6px}

  #screen-createAccount.active{
    display:grid;grid-template-columns:minmax(0,34%) minmax(0,1fr);
    align-items:start;padding-bottom:calc(10px + env(safe-area-inset-bottom));
  }
  #screen-createAccount.active>.topbar{grid-column:1/-1}
  #screen-createAccount.active>.auth-logo{
    grid-column:1;gap:4px;
    padding:4px 12px 4px calc(12px + env(safe-area-inset-left));
  }
  #screen-createAccount.active>.auth-logo img{width:48px;height:48px}
  #screen-createAccount.active>.auth-logo h1{font-size:.95rem;margin-top:0}
  #screen-createAccount.active>.auth-logo p{font-size:.7rem;max-width:200px}
  #screen-createAccount.active>main{
    grid-column:2;padding:4px calc(16px + env(safe-area-inset-right)) 10px 0;
  }
  #screen-createAccount.active>main .field{margin-bottom:8px}
  #screen-createAccount.active>main .auth-divider{margin:10px 0}

  /* Recuperar contraseña / Correo enviado: ya usan el mismo patrón de
     "topbar + main con scroll propio" que el resto de la app (no son
     ".screen-auth"), así que su alto ya se maneja bien -- lo único
     que se veía raro en horizontal era la tarjeta central estirándose
     al ancho completo del teléfono real (700-930px) con un solo campo
     angosto adentro. Se limita el ancho de la tarjeta a una columna de
     lectura cómoda y se centra, en vez de dejarla estirarse.
     FASE 7 (Punto 1): "Correo enviado" no está en el alcance de esta
     fase (la orden solo lista Iniciar sesión/Crear cuenta/Olvidé mi
     contraseña) y queda exactamente igual que antes (420px) -- se separa
     de "Olvidé mi contraseña" en dos selectores para poder ajustar solo
     esta última sin tocar la otra. "Olvidé mi contraseña" pasaba
     prácticamente inadvertida en un teléfono horizontal real (700-930px
     de ancho): con el límite de 420px, más de la mitad del ancho
     disponible quedaba vacío a ambos lados de una tarjeta angosta,
     exactamente el "no aprovecha el ancho disponible" que reporta esta
     orden. Se amplía a un máximo más generoso (620px) -- suficiente para
     usar el espacio de forma notoria sin estirar el campo de correo ni el
     botón a un ancho poco natural (que sí se vería como "una pantalla
     vertical simplemente estirada", justo lo que la orden pide evitar). */
  #screen-emailSent.active>main>.card{max-width:420px;margin:0 auto;width:100%}
  #screen-forgotPassword.active>main>.card{max-width:620px;margin:0 auto;width:100%}

  /* ---- Punto 3: pares de campos en formularios ----
     ".btn-row" ya existe pero es INCONDICIONAL (se usa en TODAS las
     orientaciones para Fecha/Hora y Moneda/Saldo) -- reutilizarla tal
     cual para estos pares nuevos alteraría también el diseño vertical
     ya aprobado, fuera del alcance de esta corrección. ".field-pair"
     es una clase nueva que en vertical es "display:contents" (invisible
     para el layout -- los dos ".field" de adentro se comportan
     exactamente igual que si el envoltorio no existiera, cero cambio
     visual en vertical) y solo se activa como fila de 2 columnas aquí,
     en horizontal. Los pares elegidos son campos cortos que hoy ocupan
     una fila completa cada uno sin necesitarla: Monto/Categoría (Nuevo
     movimiento), Monto objetivo/Monto ahorrado (Nueva meta), Alias/
     Últimos 4 dígitos (Agregar cuenta), Nombre/Correo (Editar perfil),
     Contraseña actual/Nueva contraseña (Seguridad), ¿En qué pantalla?/
     ¿Con qué frecuencia? (Reportar problema), ¿En qué área?/Prioridad
     (Enviar sugerencia). */
  .field-pair{display:flex;gap:12px}
  .field-pair>.field{flex:1;margin-bottom:0;min-width:0}

  /* ---- Auditoría completa (post-iPhone real): pares de botones ----
     Mismo mecanismo que ".field-pair" arriba, para 2 botones/controles
     consecutivos que hoy ocupan una fila completa cada uno: "Crear
     copia ahora"/"Restaurar copia" (Copia de seguridad). */
  .btn-pair{display:flex;gap:12px}
  .btn-pair>.btn,.btn-pair>label{flex:1;min-width:0}

  /* ---- Auditoría completa (post-iPhone real): Editar perfil ----
     Formulario muy corto (2 campos + 1 botón) -- con el par Nombre/
     Correo ya agrupado en una fila, el resto de la pantalla quedaba
     como un bloque angosto arriba y un área vacía injustificada abajo,
     exactamente el defecto que reportó el usuario. En vez de forzar
     más columnas donde no hay más contenido que repartir, se centra
     verticalmente el contenido dentro del alto disponible -- mismo
     "main" grid de siempre, solo se cambia "align-content" de "start"
     a "center" únicamente en esta pantalla corta. */
  #screen-editProfile.active>main{align-content:center}

  /* ---- Auditoría completa (post-iPhone real): Soporte (hub) ----
     "Reportar problemas" y "Enviar sugerencias" son 2 tarjetas de
     opción del mismo tamaño, una debajo de la otra -- se reparten lado
     a lado como las 2 opciones grandes que son. La tarjeta de
     introducción y los 2 avisos informativos siguen a ancho completo. */
  #screen-support.active>main{grid-template-columns:repeat(2,minmax(0,1fr))}
  #screen-support.active>main>*{grid-column:1/-1}
  #screen-support.active>main>.list-card:nth-child(2){grid-column:1}
  #screen-support.active>main>.list-card:nth-child(3){grid-column:2}

  /* ---- Auditoría completa (post-iPhone real): Mis documentos ----
     Regla de landscape de 2 columnas ELIMINADA en FASE 2.2.1: apuntaba a
     la estructura plana de la Fase 8 (.input-wrap/.section-label/
     #docPdfList/#docBackupList como hijos directos de <main>), que ya no
     existe -- "Mis documentos" pasó a ser un hub de categorías (1
     .list-card + 1 botón "Agregar documento"). Se detectó al validar
     landscape en esta fase: la regla seguía existiendo y aplicaba
     grid-template-columns:repeat(2,...) sin ningún hijo que la
     necesitara, comprimiendo el hub en una columna angosta y estirando
     el botón en la otra. El hub nuevo no necesita ningún tratamiento
     especial en landscape -- usa el grid de 1 columna por defecto, igual
     que la mayoría de pantallas no listadas aquí. */

  /* ---- Auditoría completa (post-iPhone real): Copia de seguridad ----
     "Crear copia ahora"/"Restaurar copia" pasan de estar apiladas a
     compartir fila (".btn-pair", ver arriba) -- requiere envolverlas en
     el HTML (ver gastos_personales_app.html), documentado en la
     bitácora igual que los envoltorios ".field-pair" ya existentes. */

  /* ---- Auditoría completa (post-iPhone real): Reportes -- "Resumen
     rápido" y "Comparación con el periodo anterior" ----
     Estas 2 tarjetas tienen varias filas "etiqueta ... valor" apiladas
     (".report-quick-row") que hoy ocupan una fila completa cada una sin
     necesitarlo -- se reparten en una grilla de 2 columnas. Ambos
     grupos usados hoy en la app tienen un número PAR de filas (4 y 2),
     así que ":nth-last-child(-n+2)" (las últimas 2 filas del HTML, es
     decir, la última fila VISUAL de 2 columnas) es exactamente la fila
     final sin borde inferior -- si en el futuro se agrega una fila y el
     total pasa a ser impar, esto se debe revisar (documentado aquí para
     quien continúe este trabajo). Ver ".report-quick-grid" añadida al
     HTML sobre los 2 contenedores existentes (mismo criterio de
     etiquetar el contenedor exacto en vez de adivinarlo por CSS). */
  .report-quick-grid{display:grid;grid-template-columns:repeat(2,1fr);column-gap:16px}
  .report-quick-grid>.section-label{grid-column:1/-1}
  .report-quick-grid>.report-quick-row{border-bottom:1px solid var(--border) !important}
  .report-quick-grid>.report-quick-row:nth-last-child(-n+2){border-bottom:0 !important}

  /* ---- Punto 5: Bancos -- mensaje informativo + botón "Agregar
     cuenta o tarjeta", lado a lado en vez de apilados ----
     La lista de cuentas (#bancosList) ya se reparte en 2 columnas
     desde la corrección anterior; lo que seguía apilado verticalmente
     era el aviso informativo y el botón de arriba, cada uno ocupando
     una fila completa por sí solo. */
  #screen-bancos.active>main{grid-template-columns:1.7fr 1fr}
  #screen-bancos.active>main>.card{grid-column:1}
  #screen-bancos.active>main>#bancosAddBtn{grid-column:2;align-self:center}
  #screen-bancos.active>main>#bancosList{grid-column:1/-1}

  /* ---- Punto 6: encabezado de Ajustes ("Más") más compacto ----
     ".hero" de esta pantalla trae su padding por ATRIBUTO "style" en
     el HTML (más específico que cualquier regla externa sin
     "!important"), así que es la única excepción de esta auditoría
     donde hace falta "!important" para poder compactarlo sin tocar el
     HTML -- mismo criterio ya usado para no reescribir marcado
     aprobado. El subtítulo decorativo se oculta igual que ya se hizo
     en el encabezado de Inicio (mismo criterio: información
     decorativa, no un dato o control). El ícono de tuerca decorativo
     se achica en proporción. */
  #screen-more .hero{
    padding:10px calc(16px + env(safe-area-inset-right)) 10px calc(16px + env(safe-area-inset-left)) !important;
  }
  #screen-more .hero h1{font-size:1.25rem !important;margin:0 !important}
  #screen-more .hero p{display:none !important}
  #screen-more .hero svg{width:40px !important;height:40px !important;top:8px !important;right:10px !important}

  /* ---- Punto 1/2/4: huso sistémico para pantallas tipo "lista de
     opciones" (".list-card") ----
     Este componente se reutiliza en ~20 pantallas: Moneda, Idioma,
     Categorías, Notificaciones (lista + preferencias), Respaldo,
     Documentos, Manual (índice), Acerca de (hub + Información de la
     app), Soporte, Legal, Perfil, Ajustes... -- todas comparten el
     mismo problema: una lista de filas angostas apiladas que no
     aprovecha el ancho real del teléfono girado. En vez de repetir una
     regla por pantalla, un único selector ligado al componente arregla
     todas a la vez (mismo criterio de "punto único de alto impacto" ya
     usado con la regla base de "main{...}").
     REVISIÓN (auditoría completa post-iPhone real): la versión anterior
     de esta regla usaba ":has(.list-row:nth-child(2))" para activar las
     2 columnas solo con 2+ filas. Tras el reporte de que varias
     pantallas que dependían de este mecanismo (Idioma, Categorías,
     Copia de seguridad, Actualizaciones, Perfil) seguían viéndose como
     en vertical en un iPhone real, y sin poder confirmar con certeza si
     la causa era soporte de ":has()" en esa versión de Safari u otra
     razón, se optó por el criterio más defensivo posible: dejar de
     depender de ":has()" por completo (soportado en Safari desde 15.4,
     pero no universal) y usar en su lugar únicamente pseudo-clases de
     soporte universal (":last-child"/":nth-child()", disponibles desde
     hace más de una década en cualquier navegador). El caso de "una
     sola fila" se resuelve con ".list-row:last-child:nth-child(odd)"
     (ver abajo): en vez de "no aplicar 2 columnas", la única fila (o la
     última de un total impar) pasa a ocupar el ancho completo -- mismo
     resultado visual de "no dejar media tarjeta vacía", logrado con una
     técnica más simple y sin ninguna dependencia de soporte moderno.
     El espacio entre columnas usa el color de borde de fondo de la
     tarjeta para dibujar una línea divisoria fina entre celdas (mismo
     truco ya usado en otras tarjetas de esta app). Se excluye
     explícitamente "#movList" (la lista de Historial): ya tiene su
     propia regla de 2 columnas más específica desde la corrección
     anterior, con cabeceras de día que NO son ".list-row" -- incluirla
     aquí también arriesgaría un color de fondo/gap inconsistente en una
     pantalla que el usuario confirmó que ya funciona bien y pidió no
     tocar salvo necesidad real. */
  .list-card:not(#movList){
    display:grid;grid-template-columns:repeat(2,1fr);
    background:var(--border);column-gap:1px;row-gap:1px;
  }
  .list-card:not(#movList) > .list-row{background:var(--surface)}
  /* Una lista con total IMPAR de filas (incluida una lista de 1 sola
     fila) deja su última fila sola en la última línea de la grilla --
     en vez de dejarla a media tarjeta con la celda derecha vacía, se
     estira a las 2 columnas. ":last-child:nth-child(odd)" es verdadero
     únicamente cuando la última fila del HTML está en una posición
     impar (1ª, 3ª, 5ª...), que es exactamente cuándo queda sola en su
     fila de la grilla de 2 columnas. */
  .list-card:not(#movList) > .list-row:last-child:nth-child(odd){grid-column:1/-1}

  /* ---- Punto 1: Límites de gasto (pantalla dedicada, no la mini-
     tarjeta de Inicio ya arreglada en la corrección anterior) ----
     "Límite semanal" y "Límite mensual" son 2 tarjetas idénticas en
     estructura, cada una a ancho completo, apiladas -- se reparten
     lado a lado. El resumen combinado y el botón de guardar siguen a
     ancho completo debajo (no ganan nada partiéndose en 2). */
  #screen-limits.active>main{grid-template-columns:repeat(2,minmax(0,1fr))}
  #screen-limits.active>main>.card-hero:nth-of-type(1){grid-column:1}
  #screen-limits.active>main>.card-hero:nth-of-type(2){grid-column:2}
  #screen-limits.active>main>.card-hero:nth-of-type(3){grid-column:1/-1}
  #screen-limits.active>main>#limitsSaveBtn{grid-column:1/-1}

  /* ---- Calendario financiero (vista Mes) ----
     En vertical cada día es un cuadrado (aspect-ratio:1 en .fin-cal-day).
     Al girar, el ancho de cada columna crece mientras el alto disponible
     baja, así que los cuadrados volvían el calendario mucho más alto que
     la pantalla. Aquí se libera la proporción cuadrada y cada fila toma
     solo el alto táctil mínimo (44px): las 7 columnas siguen usando todo
     el ancho con repeat(7,1fr), sin escalar ni tamaños por modelo. */
  #screen-financeCalendar .fin-cal-day{aspect-ratio:auto;min-height:44px}
}

/* ============================================================
   FASE 11 — ventana emergente compartida de Moneda/Idioma
   (#localeInfoOverlay): se muestra únicamente al pulsar una moneda o un
   idioma de la lista (nunca antes), con contenido que cambia según la
   selección (ver openLocaleInfo/renderLocaleInfoModal en render.js/
   events.js). Es UN solo componente reutilizado para ambos casos --
   difieren solo en el color fijo (verde moneda / morado idioma, ver
   variantes --currency/--language) y en qué campos llenan la tarjeta de
   detalle, nunca en la estructura. No reutiliza .modal-overlay/
   .modal-sheet (bottom sheet) porque la imagen aprobada de esta fase
   muestra una tarjeta centrada con esquinas redondeadas en las 4
   esquinas, no una hoja inferior.
   ============================================================ */
.locale-info-overlay{
  position:fixed;inset:0;background:#00000090;z-index:75;
  display:none;align-items:center;justify-content:center;padding:18px;
}
.locale-info-overlay.show{display:flex}
.locale-info-card{
  width:min(100%,440px);max-height:calc(100% - 8px);overflow-y:auto;
  background:var(--bg-elevated);border-radius:22px;padding:20px 18px calc(18px + env(safe-area-inset-bottom));
  position:relative;box-shadow:0 20px 60px #00000040;
}
.locale-info-close{
  position:absolute;top:14px;right:14px;width:32px;height:32px;border-radius:50%;
  border:0;background:var(--surface-2);color:var(--ink);font-size:1rem;line-height:1;cursor:pointer;
  display:grid;place-items:center;
}
.locale-info-icon{
  width:56px;height:56px;border-radius:50%;margin:0 auto;display:grid;place-items:center;color:#fff;
}
.locale-info-icon svg{width:28px;height:28px}
.locale-info-icon--currency{background:var(--green)}
.locale-info-icon--language{background:var(--purple)}
.locale-info-detail-card{
  border:1px solid var(--border);border-radius:14px;padding:12px 14px;
  display:flex;flex-direction:column;gap:8px;margin:4px 0 16px;
}
.locale-info-detail-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.locale-info-detail-main{display:flex;align-items:center;gap:10px;min-width:0}
.locale-info-detail-main strong{font-size:.95rem}
/* OJO: .flag-icon TAMBIÉN es un <span> dentro de este mismo contenedor
   -- si se necesita estilar el texto del nombre por separado, usar un
   selector de clase dedicado (ej. ">strong"), nunca ">span" a secas, o
   se le pisa su propio font-size/display a la bandera (recorte visual,
   bug real encontrado y corregido en esta misma fase). */
.locale-info-detail-code{font-weight:700;font-size:.85rem}
.locale-info-detail-code--currency{color:var(--green)}
.locale-info-detail-code--language{color:var(--purple)}
.locale-info-detail-value{font-weight:800;font-size:1.05rem;text-align:right}
.locale-info-detail-value--currency{color:var(--green)}
.locale-info-badge{
  flex:none;font-size:.68rem;font-weight:700;padding:4px 10px;border-radius:99px;white-space:nowrap;
}
.locale-info-badge--currency{background:var(--green);color:#fff;opacity:.9}
.locale-info-badge--language{background:var(--purple);color:#fff;opacity:.9}

.locale-globe-wrap{
  position:relative;width:100%;aspect-ratio:1/1;max-width:230px;margin:0 auto 20px;
}
/* ORDEN DE CORRECCIÓN (Fase 11, ajuste puntual, "sensación general de
   realismo"): sombra de contacto propia debajo del globo (elipse
   difuminada, igual que las imágenes aprobadas muestran el planeta
   "flotando" sobre una sombra suave) -- puramente decorativa, con
   pointer-events:none para no interferir con el cierre del modal ni
   ningún otro control. */
.locale-globe-wrap::after{
  content:'';position:absolute;left:50%;bottom:-6px;width:74%;height:14%;
  transform:translateX(-50%);
  background:radial-gradient(ellipse at center, #00142a55, #00142a00 72%);
  pointer-events:none;
}
/* ORDEN CORRECTIVA (Fase 11, 5ta corrección puntual -- "sistema de
   globos regionales aprobados", compartido Moneda/Idioma): imagen real
   (foto de la región aprobada correspondiente, ya recortada al círculo
   exacto del planeta) en vez del <svg> dibujado a mano que usaba esta
   misma clase con otro nombre en rondas anteriores -- object-fit:cover +
   border-radius:50% aseguran que llene el contenedor circular
   .locale-globe-wrap sin deformarse. */
.locale-globe-photo{
  width:100%;height:100%;display:block;object-fit:cover;border-radius:50%;
  filter:drop-shadow(0 10px 22px #00000035);position:relative;z-index:1;
}
.locale-globe-marker{
  position:absolute;width:32px;height:32px;transform:translate(-50%,-100%);
  filter:drop-shadow(0 3px 5px #00000060);z-index:2;
}
.locale-globe-marker svg{width:100%;height:100%}
.locale-globe-marker--currency{color:var(--green)}
.locale-globe-marker--language{color:var(--purple)}
.locale-globe-arrow{
  /* ORDEN CORRECTIVA (Fase 11, 5ta corrección puntual): tamaño reducido
     (antes 46x23) para que, junto al nuevo origen relativo al marcador
     (ver GLOBE_ARROW_MARKER_OFFSET en render.js), la flecha quede corta
     y pegada al marcador en vez de estirarse por buena parte del globo. */
  position:absolute;width:34px;height:17px;transform-origin:0% 100%;
  filter:drop-shadow(0 3px 5px #00000040);z-index:2;
}
.locale-globe-arrow svg{width:100%;height:100%}

.locale-info-note{
  display:flex;align-items:flex-start;gap:8px;font-size:.85rem;color:var(--ink-soft);
  margin-bottom:12px;
}
.locale-info-note-icon{flex:none;width:19px;height:19px;margin-top:1px}
.locale-info-note-icon svg{width:100%;height:100%}
.locale-info-note--currency .locale-info-note-icon{color:var(--green)}
.locale-info-note--language .locale-info-note-icon{color:var(--purple)}
.locale-info-note b{font-weight:700}
.locale-info-note--currency b{color:var(--green)}
.locale-info-note--language b{color:var(--purple)}

.locale-info-disclaimer{
  display:flex;align-items:flex-start;gap:10px;border-radius:14px;padding:12px 14px;
  font-size:.8rem;margin-bottom:16px;
}
.locale-info-disclaimer--currency{background:#22c55e1f;color:var(--ink-soft)}
.locale-info-disclaimer--language{background:#8b5cf61f;color:var(--ink-soft)}
.locale-info-disclaimer-icon{flex:none;width:20px;height:20px}
.locale-info-disclaimer-icon svg{width:100%;height:100%}
.locale-info-disclaimer--currency .locale-info-disclaimer-icon{color:var(--green)}
.locale-info-disclaimer--language .locale-info-disclaimer-icon{color:var(--purple)}

/* FASE 2.9.1: Crear cuenta -- la flecha deja de compartir el scroll con
   el formulario. La sección ya no se desplaza: cabecera (flecha + logo)
   queda fija y solo <main> tiene scroll propio, así la flecha nunca queda
   encima de un campo/botón ni intercepta sus toques. La rejilla horizontal
   corta existente recibe la misma separación. Fondo (::before) intacto. */
#screen-createAccount .topbar{pointer-events:none}
#screen-createAccount .back-btn{pointer-events:auto}
@media not all and (orientation:landscape) and (max-height:500px){
#screen-createAccount.active{display:flex;flex-direction:column;overflow:hidden}
#screen-createAccount .topbar,#screen-createAccount .auth-logo{flex:none}
#screen-createAccount main{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch}
}
@media (orientation:landscape) and (max-height:500px){
#screen-createAccount.active{grid-template-rows:auto minmax(0,1fr);overflow:hidden}
#screen-createAccount.active>main{align-self:stretch;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch}
}

/* FASE 2.9.5: en pantallas muy estrechas (<=350px, p. ej. iPhone con
   Zoom de pantalla) la fila de contraseña con el ojo no podía encogerse
   por debajo del ancho intrínseco del <input> (.field es grid y su hijo
   tenía min-width:auto) y sobresalía de la tarjeta. Solo Login, Crear
   cuenta y Recuperar contraseña; en anchos normales no cambia nada. */
#screen-login .input-wrap,#screen-createAccount .input-wrap,#screen-forgotPassword .input-wrap{min-width:0}

/* FASE 2.10: desbordamiento horizontal en anchos estrechos.
   Campos en fila (.btn-row: Fecha/Hora y Moneda/Saldo; .field-pair en
   horizontal: Seguridad y demás pares): misma causa que 2.9.5 -- el
   .field y su .input-wrap tienen min-width:auto y no bajaban del ancho
   intrínseco del <input> (~185px), así que la fila salía de <main> y en
   Seguridad el ojo quedaba encima del campo vecino. Con width:0 el
   <input> deja de imponer ese mínimo (su ancho real sigue dándolo
   flex:1); los <select> conservan su ancho natural. */
.btn-row>.field input,.field-pair>.field input{width:0}
/* Al caber en su columna, una etiqueta larga puede pasar a 2 líneas;
   los campos del par se alinean abajo para que las cajas sigan a la
   misma altura. Sin etiquetas de distinta altura no cambia nada. */
.field-pair>.field{align-content:end}
/* Íconos de Nueva categoría: el padding nativo del <button> (6px por
   lado) más el ícono de 40px impedía que las 5 columnas cupieran en
   320px. Con box-sizing:border-box la casilla mide lo mismo. */
#catIconSwatches .swatch{padding:0}
/* Calendario: el padding-right:98px en línea del título hacía que la
   barra midiera 365px y Buscar/Hoy salieran de pantalla por debajo de
   ese ancho (vertical estrecho; horizontal no se toca). */
@media (max-width:364px){
  #screen-financeCalendar .topbar h2{padding-right:0 !important}
}
@media (max-width:359px){
  /* Apariencia: 2 tarjetas de 3 íconos de 38px no cabían con los
     espacios normales; se reducen solo los espacios horizontales. */
  .iconstyle-grid{column-gap:8px}
  .iconstyle-card{padding-left:6px;padding-right:6px}
  .iconstyle-mini-grid{column-gap:4px}
}
/* Seguridad: el .sr-only (position:absolute) de cada regla de contraseña
   tomaba .app como bloque contenedor, escapaba del recorte de <main> y
   hacía desplazable a .app en pantallas bajas: al llegar al final del
   scroll se movía la pantalla entera y la barra con la flecha salía de
   vista (y seguía así al girar). Con la regla como bloque contenedor el
   texto oculto queda dentro de <main>; sin cambio visual. */
#screen-security .pw-rule{position:relative}
/* Historial en horizontal: con repeat(2,1fr) cada columna no bajaba del
   ancho mínimo de sus filas (ícono + palabra más larga + importe), así
   que en 568-740px las 2 columnas salían de la tarjeta y los importes
   quedaban fuera. Se mantienen 2 columnas donde caben (>=340px cada
   una); si no, la lista usa 1 columna como en vertical. Una palabra que
   no cabe en su caja se parte en vez de montarse sobre el importe. */
@media (orientation:landscape) and (max-height:500px){
  #screen-movements.active>main>#movList{grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr))}
  #screen-movements .row-text strong{overflow-wrap:anywhere}
}
/* Calendario en horizontal, detalle del día: misma causa que Historial
   (repeat(2,1fr) de .list-card). Las columnas crecían hasta el ancho
   mínimo de sus filas (ícono + palabra más larga + importe + editar), la
   lista salía de la tarjeta y su overflow:hidden recortaba los importes
   de la derecha. Una fila habitual necesita hasta ~358px sin partir
   palabras: se mantienen 2 columnas cuando cada una tiene >=360px; si no,
   1 columna como en vertical. Las palabras largas: ver la regla siguiente. */
@media (orientation:landscape) and (max-height:500px){
  #screen-financeCalendar #finCalDayDetail>.list-card{grid-template-columns:repeat(auto-fit,minmax(min(100%,360px),1fr))}
}
/* Calendario, filas del detalle del día y de Todos (vertical y
   horizontal): el importe (y la tarjeta resumen) no se encoge y
   .row-text sí, hasta 0px, así que en anchos estrechos o con importes
   grandes la descripción quedaba más estrecha que su palabra más larga y
   se montaba sobre el importe o salía de la tarjeta. Ahora .row-text no
   baja de su palabra más larga (sin pasar del ancho de la fila menos el
   ícono): si no cabe junto al importe, el importe pasa a una segunda línea
   alineado a la derecha; si cabe, la fila queda igual que antes. Una
   palabra más ancha que la fila se parte. El margen +48/-48px solo hace
   que editar y el chevron pasen de línea junto con el importe (en la
   misma línea se compensan y no mueven nada). */
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row,#screen-financeCalendar #finCalAllList>.list-row{flex-wrap:wrap;row-gap:4px}
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row>.row-text,#screen-financeCalendar #finCalAllList>.list-row>.row-text{flex:1 1 auto;width:min-content;max-width:calc(100% - 50px);overflow-wrap:break-word}
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row>.row-text+span,#screen-financeCalendar #finCalAllList>.list-row>.row-text+span{margin-left:auto}
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row>.row-text+span:not(:last-child){margin-right:48px}
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row>.row-text+span+svg.icon{margin-left:-46px !important}
#screen-financeCalendar #finCalDayDetail>.list-card>.list-row>.row-text+span+.row-chevron{margin-left:-48px}
/* Todos en horizontal: recibía repeat(2,1fr) de .list-card (misma causa
   que el detalle del día) y recortaba filas y cabeceras de día. Usa sus
   mismas filas que Historial, así que el mismo criterio: 2 columnas donde
   caben (>=340px cada una); si no, 1 columna. */
@media (orientation:landscape) and (max-height:500px){
  #screen-financeCalendar #finCalAllList{grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr))}
}
/* Reserva inferior de <main>: la barra inferior crece con
   env(safe-area-inset-bottom) y la reserva global (96px; 64px en
   horizontal) no, así que con indicador de inicio el final del contenido
   (Guardar en Seguridad/Banco, última fila de Historial, etc.) quedaba
   7-11px debajo de la barra al final del scroll. Mismo ajuste que ya
   tenían Perfil y Clasificar documento, ahora en la regla común; sin
   safe area inferior no cambia nada. */
.screen.active:not(.screen-auth) > main{padding-bottom:calc(96px + env(safe-area-inset-bottom))}
@media (orientation:landscape) and (max-height:500px){
  .screen.active:not(.screen-auth) > main{padding-bottom:calc(64px + env(safe-area-inset-bottom))}
}
/* FASE 2.11: comprobantes pendientes en Nuevo/Editar movimiento. Botones
   con texto largo en otros idiomas: pueden partir línea sin desbordar. */
#addAttachField .btn{white-space:normal;text-align:center;line-height:1.2}
.add-attach-thumbs{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.add-attach-thumbs:empty{display:none}
.add-attach-thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;background:var(--surface-2);border:1px solid var(--border)}
.add-attach-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.add-attach-remove{position:absolute;top:3px;right:3px;width:26px;height:26px;border-radius:50%;background:rgba(0,0,0,.62);color:#fff;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;line-height:1;padding:0}

/* FASE 2.10 (franja oscura al hacer overscroll en iPhone): <html> y
   <body> llevan style="background:#0a0e1f" en index.html (color de
   arranque) y ese estilo en línea gana a body{background:var(--bg)}.
   WebKit pinta la superficie que deja al descubierto el rebote con el
   background-color de html/body (nunca con imágenes ni degradados), así
   que con sesión iniciada aparecía un lienzo #0a0e1f fijo, ajeno a la
   pantalla. Con sesión iniciada html y body toman var(--bg), el color
   plano del modo activo (la .topbar o el fondo de la pantalla). Solo en
   Inicio y Más con Estilo de fondo activo, cuyo borde superior es el
   degradado personalizado, toman --screen-bg-top: la primera parada ya
   ajustada al tono de ese mismo degradado (la fija render() en
   js/render.js; sin ella, var(--bg)). Es un color sólido de continuidad,
   no una copia del degradado: el borde superior real varía a lo ancho
   (más en horizontal y con arcoíris) y el rebote nativo no puede pintar
   imágenes ni degradados. Su integración en el rebote de iOS depende de
   la prueba en el iPhone real.
   Alcance: solo los layouts de teléfono donde .app ocupa todo el ancho
   y no hay márgenes laterales visibles -- vertical hasta 520px (el mismo
   límite de .app{width:min(100%,520px)}) y el umbral horizontal ya
   existente (orientation:landscape and max-height:500px, donde .app pasa
   a width:100%). Tablet y escritorio conservan sus márgenes #0a0e1f.
   Fuera de la regla (siguen con #0a0e1f): arranque (#appBootScreen) y
   pantallas públicas (body.is-auth). Sin soporte de :has la regla se
   descarta entera y todo queda como antes. */
@media (max-width:520px),(orientation:landscape) and (max-height:500px){
  :root:not(:has(> body.is-auth,#appBootScreen)),
  :root:not(:has(> body.is-auth,#appBootScreen))>body{background-color:var(--bg)!important}
  :root:not(:has(> body.is-auth,#appBootScreen))[data-fondo-activo="true"]:has(#screen-home.active,#screen-more.active),
  :root:not(:has(> body.is-auth,#appBootScreen))[data-fondo-activo="true"]:has(#screen-home.active,#screen-more.active)>body{background-color:var(--screen-bg-top,var(--bg))!important}
}

/* FASE 2.10: identidad de esta hoja de estilos. Más > Actualizaciones
   > "Versión aplicada" la lee con getComputedStyle (APP_BUILD_ID en
   js/render.js) y solo muestra "Verificada" si coincide con ella y con
   <meta name="gp-build-id"> de index.html, así que prueba qué styles.css
   está aplicado de verdad en la pantalla. No tiene
   efecto visual. Mantenerla como ÚLTIMA regla del archivo facilita
   comprobar la build, pero no acredita por sí sola la integridad de todo
   el archivo. Solo se escribe aquí: nunca desde JS, HTML ni estilos
   inline.
   En cada publicación: nuevo ID con formato GP-AAAA.MM.DD.N (fecha de la
   build + n.º del día, nunca reutilizado), el MISMO aquí, en
   APP_BUILD_ID y en el meta gp-build-id de index.html. Comprobación
   (deben salir 3 líneas con el mismo ID):
   grep -oE 'GP-[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]+' css/styles.css js/render.js index.html */
:root{--gp-build-id:"GP-2026.10.25.1"}

/* ============================================================
   GATE E+F (Comunidad GP, candidato local U2) -- SOLO clases nuevas
   .cm-* y las dos pantallas nuevas. Ninguna regla existente cambia.
   El texto que queda directamente sobre el fondo de pantalla usa
   --hero-ink/--muted-on-fondo (se adaptan a Claro/Oscuro/Estilo de
   fondo); todo lo demás vive en tarjetas con su propio fondo.
   ============================================================ */
#screen-community,
#screen-communityCompose{background:var(--screen-bg)}
.cm-card{display:grid;gap:10px;justify-items:stretch}
.cm-card .modal-icon{justify-self:center}
.cm-card h2{margin:0;text-align:center;font-size:1.1rem}
.cm-card > p{margin:0;text-align:center;font-size:.85rem}
.cm-consent-box{margin-top:2px}
.cm-check{display:flex;gap:10px;align-items:flex-start;font-size:.82rem;color:var(--ink);cursor:pointer;min-height:44px}
.cm-check input{flex:none;width:22px;height:22px;margin-top:1px;accent-color:var(--accent)}
.cm-hint{font-size:.74rem}
.cm-error{margin:0;min-height:0;font-size:.8rem;font-weight:650;color:var(--red)}
.cm-error:empty{display:none}
.cm-status{margin:6px 0;text-align:center;font-size:.85rem;color:var(--muted-on-fondo,var(--muted));text-shadow:var(--text-shadow-on-fondo,none)}
.cm-status:empty{display:none}
.cm-status .cm-link{display:inline-block;margin-left:6px}
.cm-link{color:var(--accent);font-weight:700;font-size:.85rem;min-height:44px;padding:0 8px;background:none}
.cm-link-row{display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px}
.cm-leave{display:block;margin:10px auto 0;color:var(--muted-on-fondo,var(--muted));text-shadow:var(--text-shadow-on-fondo,none);font-weight:600}
.cm-head{text-align:center;margin:2px 0 12px}
.cm-head h2{margin:0 0 6px;font-size:1.15rem;color:var(--hero-ink,var(--ink));text-shadow:var(--text-shadow-on-fondo,none)}
.cm-head p{margin:0;font-size:.85rem;color:var(--muted-on-fondo,var(--muted));text-shadow:var(--text-shadow-on-fondo,none)}
.cm-new-btn{margin-bottom:12px}
.cm-new-btn .icon{width:18px;height:18px}
.cm-tabs{display:flex;gap:2px;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:4px;margin-bottom:12px;overflow-x:auto}
.cm-tab{flex:1 0 auto;min-height:40px;padding:0 12px;border-radius:10px;font-size:.82rem;font-weight:700;color:var(--muted);background:none;white-space:nowrap}
.cm-tab.active{background:var(--accent-soft);color:var(--accent)}
#cmFeed{display:grid;gap:12px}
.cm-post{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:14px;display:grid;gap:8px;min-width:0}
.cm-post-head{display:flex;align-items:center;gap:10px;min-width:0}
.cm-avatar{flex:none;width:40px;height:40px;border-radius:50%;display:grid;place-items:center;font-weight:800;font-size:.9rem;color:#fff;background:linear-gradient(135deg,var(--accent),var(--accent-2))}
.cm-author{flex:1;min-width:0;display:grid}
.cm-author strong{font-size:.92rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cm-author span{font-size:.75rem;color:var(--muted)}
.cm-chip{flex:none;font-size:.72rem;font-weight:700;padding:4px 10px;border-radius:999px}
.cm-chip-consejo{background:#3b82f626;color:var(--blue)}
.cm-chip-experiencia{background:#22c55e26;color:var(--green)}
.cm-chip-duda{background:#8b5cf626;color:var(--purple)}
.cm-post-title{margin:0;font-size:1rem;font-weight:800;color:var(--ink);overflow-wrap:anywhere}
.cm-post-body{margin:0;font-size:.88rem;color:var(--ink-soft,var(--ink));white-space:pre-wrap;overflow-wrap:anywhere}
.cm-actions{display:flex;align-items:center;gap:4px;margin-top:2px}
.cm-act{display:inline-flex;align-items:center;gap:6px;min-height:44px;min-width:44px;padding:0 10px;border-radius:12px;color:var(--muted);font-weight:700;font-size:.85rem;background:none}
.cm-act .icon{width:20px;height:20px}
.cm-act[aria-pressed="true"]{color:var(--accent)}
.cm-act.cm-like[aria-pressed="true"]{color:var(--red)}
.cm-act.cm-like[aria-pressed="true"] .icon use{fill:currentColor}
.cm-act.cm-save[aria-pressed="true"] .icon use{fill:currentColor}
.cm-act-spacer{flex:1}
.cm-act:disabled{opacity:.5}
#cmMoreBtn{margin-top:12px}
#screen-communityComments{background:var(--screen-bg)}
#cmCPost{margin-bottom:12px}
#cmComments{display:grid;gap:10px}
.cm-comment{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:12px;display:grid;gap:6px;min-width:0}
.cm-comment-head{display:flex;align-items:center;gap:10px;min-width:0}
.cm-avatar-sm{width:32px;height:32px;font-size:.78rem}
.cm-comment-body{margin:0;font-size:.88rem;color:var(--ink-soft,var(--ink));white-space:pre-wrap;overflow-wrap:anywhere}
#cmCMoreBtn{margin-top:12px}
