/*
  Deductor — sistema de diseño
  Dirección: editorial cálido / institucional de confianza.
  Tema claro, papel cálido, verde pino + terracota, serif de display + sans de cuerpo.
  Fraunces (display serif) y Public Sans (sans de cuerpo) se auto-alojan como fuentes
  variables woff2 en ./fonts/ (SIL OFL, subconjunto latin — cubre el español). Si algún
  día faltase el archivo, --serif/--sans caen limpiamente en su pila de fallback de
  sistema: no hay dependencia de red ni de CDN externo en ningún caso.
*/

/* ---------- 0. Tipografía auto-alojada (SIL OFL) ---------- */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/fraunces-variable-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/public-sans-variable-latin.woff2') format('woff2');
}

/* ---------- 1. Tokens ---------- */
:root {
  --papel: #FAF7F1;
  --tinta: #1E1B16;
  --superficie: #FFFFFF;

  --pino: #1F4B3F;
  --pino-claro: #2E6B58;
  --terracota: #C4703B;
  --terracota-oscuro: #A85A2B;
  --terracota-texto: #9C5227; /* variante de --terracota-oscuro con AA garantizado como texto sobre --posible-suave */

  --exito: #2E6B58;
  --exito-suave: #E4EEE9;
  --posible: #B26A2E;
  --posible-suave: #F6E9DC;
  /* --neutro: texto secundario/caption en todas las pantallas. Debe cumplir AA (4.5:1)
     contra --papel Y --superficie (contraste medido y documentado en task-4-report.md). */
  --neutro: #6B6459;
  --neutro-suave: #EFEAE1;

  --borde: #E7E0D4;
  /* Borde de los CONTROLES de formulario, distinto del decorativo de arriba y por una
     razón que no es estética: WCAG 1.4.11 pide 3:1 a lo que hace falta para identificar un
     control, y el borde de un campo es exactamente eso -- es lo que te dice dónde escribir.
     Medido, `--borde` daba 1,31:1 sobre blanco: los campos y los desplegables no se
     distinguían del fondo. Este da 3,86:1 sobre blanco y 3,61 sobre papel.
     El decorativo NO se toca: el contorno de una tarjeta no identifica ningún control, y
     oscurecerlo endurecería toda la interfaz sin ganar accesibilidad. */
  --borde-control: #8C8071;
  --sombra: 0 4px 24px rgba(30, 27, 22, .08);
  --sombra-alta: 0 12px 40px rgba(30, 27, 22, .14);
  --radio: 14px;
  --radio-chico: 9px;
  --ancho: 640px;
  /* Único sitio con el padding lateral de `.tarjeta` en móvil -- antes duplicado a
     mano entre ese padding y el margen negativo que necesita la barra de acciones
     pegajosa (sección 12) para sangrar hasta el borde de la tarjeta. Sin equivalente
     de breakpoint: las custom properties no funcionan dentro de `@media` (ver el
     inventario de breakpoints en la sección 12), así que esto solo vale para el
     valor de un padding, no para la condición del media query. */
  --tarjeta-lateral-movil: 1.25rem;

  --serif: 'Fraunces', 'Iowan Old Style', Palatino, Georgia, serif;
  --sans: 'Public Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* derivados */
  --espacio-1: .5rem;
  --espacio-2: 1rem;
  --espacio-3: 1.75rem;
  --espacio-4: 2.75rem;
  /* El hueco ENTRE preguntas del paso 5, frente a los .6-.7rem que separan las partes
     DENTRO de una pregunta (legend, ayuda, opciones). Tiene nombre propio porque lo que
     importa no es el valor sino la proporción: con el 1rem de antes la distancia de
     fuera era casi la de dentro y las preguntas se leían como un muro. */
  --espacio-pregunta: 1.75rem;
  --transicion: 180ms cubic-bezier(.4, 0, .2, 1);
  --tam-numero: clamp(2.4rem, 9vw, 3.75rem);
  --grano: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.05 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- 2. Reset ligero ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
/* `hidden` tiene que ganar SIEMPRE. El navegador trae `[hidden] { display: none }` en su
   hoja de usuario-agente, pero cualquier regla de AUTOR que fije `display` la pisa -- y
   aquí las hay: `.campo` y `fieldset` son `display: flex`. Resultado: las preguntas
   dependientes de P5 llevaban el atributo `hidden` puesto y se seguían viendo, porque
   `display: flex` vencía. Con esta regla el atributo vuelve a significar lo que dice.
   El `<noscript>` de p5_afinar.html sigue pudiendo revertirlo: su selector
   `[data-depende-de][hidden]` tiene más especificidad que este. */
[hidden] { display: none !important; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; }
a { color: inherit; }
button, input, select, textarea { font-family: inherit; }

@media (prefers-reduced-motion: reduce) {
  /* animation-delay también a cero: si no, el revelado escalonado del informe
     (hero, CTA, fichas con delay inline por índice) sigue "saltando" en cascada
     aunque cada animación individual dure ~0ms — el efecto de cascada persiste. */
  *, *::before, *::after { animation-duration: .001ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}

/* ---------- 3. Base ---------- */
html { color-scheme: light; }
body {
  font-family: var(--sans);
  color: var(--tinta);
  background-color: var(--papel);
  background-image: var(--grano);
  background-repeat: repeat;
  background-size: 240px 240px;
  line-height: 1.6;
  font-size: 16px;
  /* El documento mide SIEMPRE el viewport PEQUEÑO: el que hay con las barras del
     navegador desplegadas. Es el peor caso visible, así que si el contenido cabe ahí no
     existe ningún estado del navegador en el que aparezca scroll.

     `100vh` era el bug, con nombre propio: en móvil `vh` equivale a `lvh` (el viewport
     GRANDE, el de la barra retraída), o sea que el documento se dimensionaba como si la
     barra no estuviera mientras la barra SÍ está. Medido: en un iPhone SE forzaba 667px
     dentro de una ventana de 553 = 114px de scroll muerto AUNQUE el contenido cupiera, y
     en un Pro Max 137px con 80px de contenido de sobra. Ni una sola pantalla se salvaba.

     Las dos líneas son una cascada de respaldo y el ORDEN es la cascada: cada navegador
     se queda con la última que sabe leer. `svh` y no `dvh` porque `dvh` se recalcula al
     colapsar la barra, y con el héroe centrado eso lo desplazaría a mitad de gesto:
     cambiaríamos scroll muerto por un salto de maquetación. En la carga, que es el
     instante que importa, `dvh` == `svh` de todos modos.

     `min-height` y NUNCA `height`, ni `overflow: hidden` en ninguna parte: este `body` es
     el de las nueve páginas, los pasos tienen que poder crecer porque su barra de
     acciones es `sticky`, y quien suba el tamaño de letra TIENE que poder desplazarse
     (WCAG 1.4.4). Recortar sería peor que hacer scroll.

     Y jamás `calc(100svh + env(safe-area-inset-bottom))`: con `viewport-fit=cover` el
     área segura ya está DENTRO de esos 100svh, así que sumarla fabrica justo el scroll
     que estamos quitando. El área segura se paga con padding, en `.pie`. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* Sin esto el gesto de swipe NO FUNCIONA en un movil de verdad, y no es una sutileza:
     con `touch-action: auto` el navegador es el dueno del desplazamiento, asi que en
     cuanto el dedo se mueve sobre una pagina que hace scroll decide que es un scroll,
     dispara `pointercancel` y deja de mandar eventos a la pagina. `navegacion.js` trata
     ese `pointercancel` como "abortar", asi que el gesto moria antes de llegar a
     `pointerup`. Con `pan-y` el desplazamiento vertical sigue siendo del navegador y el
     horizontal pasa a ser nuestro, que es justo el reparto que hace falta.
     `pinch-zoom` explicito para no perder el zoom con dos dedos: `pan-y` a secas lo
     desactivaria, y eso si seria un problema de accesibilidad.
     Va en `body` y no en `.tarjeta` porque el gesto se escucha en `document`: un dedo
     que empieza fuera de la tarjeta tiene que contar igual. No hay ningun contenedor con
     scroll horizontal en la app, asi que ceder el eje X no rompe nada (comprobado). */
  touch-action: pan-y pinch-zoom;
}

h1, h2, h3 { font-family: var(--serif); color: var(--tinta); line-height: 1.15; font-weight: 600; }

:focus { outline: none; }
:focus-visible {
  outline: 3px solid var(--terracota);
  outline-offset: 3px;
  border-radius: 4px;
}

.saltar-enlace {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--pino);
  color: var(--superficie);
  padding: .5rem 1rem;
  z-index: 100;
}
.saltar-enlace:focus { left: .5rem; top: .5rem; }

/* Nombre accesible desde el contenido (píldoras del progreso, sección 6): visible
   solo para lectores de pantalla. `clip-path` en vez del viejo `clip: rect(...)`
   -- ambos se dejan porque `clip` todavía es el que sostiene Safari más antiguo,
   pero `clip-path: inset(50%)` es la forma vigente. */
.solo-lectores {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- 4. Cabecera / pie (masthead editorial) ---------- */
.cabecera {
  border-bottom: 3px double var(--borde);
  padding: var(--espacio-3) var(--espacio-2) var(--espacio-2);
}
.cabecera-interior {
  max-width: var(--ancho);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
}
.marca {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--pino);
  text-decoration: none;
  letter-spacing: .01em;
}
.marca-punto {
  display: inline-block;
  width: .4em;
  height: .4em;
  margin-left: .18em;
  border-radius: 50%;
  background: var(--terracota);
  vertical-align: middle;
}
.eslogan {
  font-size: .85rem;
  color: var(--neutro);
  font-style: italic;
}

/* Longhands y no el atajo `padding`, en las tres reglas de `.principal` que hay en el
   fichero (esta, la del móvil y la de la portada). Con el atajo, una regla de igual
   especificidad en el mismo bloque barre el eje que no pretendía tocar y lo hace en
   silencio, sin error: es una trampa que ya estaba puesta. */
.principal { flex: 1; padding-block: var(--espacio-4); padding-inline: var(--espacio-2); }

.pie {
  border-top: 1px solid var(--borde);
  padding-top: var(--espacio-3);
  padding-inline: var(--espacio-2);
  /* El pie es lo último del documento y `base.html` lleva `viewport-fit=cover`: los
     100svh se extienden POR DEBAJO del indicador de inicio, así que el último renglón
     puede caer bajo él en cuanto la página deje de hacer scroll. Hasta ahora lo tapaban
     los 114px de scroll muerto; al quitarlos, hay que reservarlo de verdad.
     `max()` y no una suma: con la barra desplegada —que es la definición del viewport
     pequeño— el inset vale 0 y el pie conserva su medida de siempre, coste cero; solo
     paga los ~34px donde son reales (PWA instalada, Android edge-to-edge).
     Va en su PROPIA declaración, sin `svh`, a propósito: si se soldara dentro de la
     rampa elástica de la sección 12 se descartaría junto con ella justo en los
     navegadores viejos, que es donde más falta hace. */
  padding-bottom: max(var(--espacio-3), env(safe-area-inset-bottom));
  margin-top: auto;
}
.pie-interior {
  max-width: var(--ancho);
  margin: 0 auto;
  text-align: center;
  font-size: .85rem;
  color: var(--neutro);
}
.pie-interior strong { color: var(--tinta); }

/* La fila de enlaces institucionales del pie (hoy solo la puerta profesional; manana el
   aviso legal y el de privacidad). Registro deliberadamente menor que las dos promesas de
   encima: son el mensaje del pie y "no se acortan ni un caracter" (ver seccion 12). */
.pie-enlaces {
  margin-top: var(--espacio-2);
  font-size: .8rem;
}
.pie-enlaces a { color: var(--neutro); }
.pie-enlaces a:hover { color: var(--pino); }

/* ---------- 5. Columna central / tarjeta ---------- */
.paso, .contenedor {
  max-width: var(--ancho);
  margin: 0 auto;
}

.tarjeta {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  padding: var(--espacio-4) var(--espacio-3);
  animation: entra-tarjeta 420ms cubic-bezier(.2, .7, .2, 1) both;
}

@keyframes entra-tarjeta {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.paso-titulo {
  font-size: clamp(1.5rem, 4.5vw, 1.9rem);
  margin-bottom: .5rem;
}
.paso-ayuda {
  color: var(--neutro);
  margin-bottom: var(--espacio-3);
  max-width: 46ch;
}
/* La promesa de privacidad del paso "afinar", una sola vez arriba. Más pequeña que
   `.paso-ayuda` porque es una nota al pie, no la explicación del paso: antes se
   repetía en cinco bloques y empujaba hacia abajo la pregunta de cada uno.
   --neutro sobre --superficie: 5,85:1, ya calculado en su token. */
.paso-nota-privacidad {
  color: var(--neutro);
  font-size: .82rem;
  margin-top: calc(-1 * var(--espacio-2));
  margin-bottom: var(--espacio-3);
  max-width: 52ch;
}

/* ---------- 6. Barra de progreso ---------- */
.paso-progreso { margin-bottom: var(--espacio-2); }
.paso-etiqueta {
  font-family: var(--sans);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--pino);
  margin-bottom: .5rem;
}
.progreso-pasos {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}
/* Píldora numerada (antes una barra de 6px, no pulsable en ningún sentido honesto --
   ver el plan). ~28px: deliberadamente por debajo del mínimo de 48px de `.boton-icono`
   -- son cinco en fila y un salto de paso es una acción secundaria, no la principal
   de la pantalla; el objetivo de 48px se reserva para "atrás"/"continuar". */
.progreso-pildora {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1.5px solid var(--neutro);
  background: var(--papel);
  color: var(--neutro);
  font-family: var(--sans);
  font-size: .78rem;
  font-weight: 700;
  cursor: default;
  transition: background var(--transicion), border-color var(--transicion), color var(--transicion);
  /* --neutro sobre --papel: 5,47:1 (calculado). El <header> del progreso vive FUERA
     de la tarjeta, sobre --papel, no sobre --superficie -- por eso el aro se prueba
     contra --papel. Antes, el relleno por defecto era --neutro-suave sobre --papel:
     1,12:1, muy por debajo del 3:1 que exige WCAG 1.4.11 ahora que la píldora es un
     componente (pulsable en la mayoría de los casos). Por eso el estado por defecto
     ya no se distingue por relleno, sino por un aro en --neutro. */
}
button.progreso-pildora {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  border-color: var(--pino-claro);
  color: var(--pino-claro);
  background: var(--superficie);
  /* --pino-claro sobre --papel: 5,84:1. Mismo verde que ya usa el resto de la
     interfaz para "esto se puede pulsar" (`.boton-anadir`, el hover de
     `.opcion-tarjeta`): distingue una píldora alcanzable de una decorativa. */
}
button.progreso-pildora:hover { border-color: var(--pino); color: var(--pino); }
/* Paso ya hecho: aro verde sobre fondo claro. Se lee como "puedes volver aquí", no
   como "estás aquí". --pino-claro sobre --superficie: 6,25:1. */
.progreso-pildora.esta-completo {
  background: var(--superficie);
  border-color: var(--pino-claro);
  color: var(--pino-claro);
}
/* Paso actual: relleno verde pino sólido, el MISMO del botón de continuar.
   La distinción con los pasos hechos la hace el PESO (relleno contra aro), no el tono:
   --pino y --pino-claro son dos verdes demasiado próximos para separarse en 28px, y
   fiarlo al matiz dejaría al usuario sin saber dónde está. El aro exterior en
   --exito-suave lo despega de sus vecinos.
   --superficie sobre --pino: 9,83:1, y el aro --pino sobre --papel: 9,19:1 (calculado).
   Al dígito se le exige el 3:1 de componente y no el 4,5:1 de texto: es `aria-hidden`,
   no aporta nada que no diga ya "Paso N de M" en palabras. */
.progreso-pildora.actual {
  background: var(--pino);
  border-color: var(--pino);
  color: var(--superficie);
  box-shadow: 0 0 0 3px var(--exito-suave);
}

/* ---------- 7. Formularios ---------- */
.formulario-paso { display: flex; flex-direction: column; gap: var(--espacio-3); }
.campo { display: flex; flex-direction: column; gap: .5rem; }
.etiqueta-campo {
  font-weight: 700;
  font-size: .95rem;
}
.campo-ayuda { font-size: .85rem; color: var(--neutro); }

/* Hueco que el desplazamiento automático debe respetar por abajo: es el alto de la barra
   de acciones pegajosa, que en móvil tapa lo que quede debajo de ella. Lo mide
   `navegacion.js` y lo publica en `--alto-barra-acciones` (0px cuando la barra no está
   pegada, o sin JS). Va fuera de la media query a propósito: el valor ya trae el
   breakpoint dentro.

   Cubre las dos cosas que APARECEN solas cerca del borde inferior, que es donde llega el
   pulgar y por tanto donde se pulsa:
     - `[data-depende-de]`  una pregunta de P5 revelada al responder la de arriba;
                            la sube `afinar.js` con `scrollIntoView`.
     - `.fila-hijo`         la fila que añade `flujo.js` en P4; ahí no hay
                            `scrollIntoView` ninguno -- el desplazamiento lo hace el
                            navegador al recibir el `focus()` del `<select>` nuevo, y
                            `scroll-margin` también gobierna ése. Medido: el cuarto hijo
                            caía en 730-806 con la barra empezando en 749. */
[data-depende-de], .fila-hijo { scroll-margin-bottom: var(--alto-barra-acciones, 0px); }

/* Dos campos que son UNA respuesta partida en dos (mes + año de nacimiento). Van en
   grid y no en flex porque `1fr 1fr` reparte el ancho sin mirar el contenido: con flex,
   el <select> de los meses --que lleva "septiembre" dentro-- se llevaría el doble de
   sitio que el de los años. `minmax(0, 1fr)` y no `1fr` a secas porque el mínimo
   automático de un elemento de grid es su contenido, y un nombre de mes largo
   desbordaría la tarjeta a 320 px en vez de encoger el select. */
.pareja-campos {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: .6rem;
}

fieldset { border: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .7rem; }
legend {
  font-weight: 700;
  font-size: .95rem;
  padding: 0;
  margin-bottom: .2rem;
}

.grupo-opciones {
  display: grid;
  grid-template-columns: 1fr;
  gap: .6rem;
}
@media (min-width: 480px) {
  .grupo-opciones.en-linea { grid-template-columns: 1fr 1fr; }
}

/* Radios/checkboxes como tarjetas seleccionables */
.opcion-tarjeta {
  position: relative;
  display: flex;
  align-items: center;
  gap: .75rem;
  border: 1.5px solid var(--borde);
  border-radius: var(--radio-chico);
  padding: .8rem 1rem;
  cursor: pointer;
  background: var(--superficie);
  transition: border-color var(--transicion), background var(--transicion), transform 120ms ease;
  -webkit-tap-highlight-color: transparent;
}
.opcion-tarjeta:hover { border-color: var(--pino-claro); }
.opcion-tarjeta:active { transform: scale(.99); }
.opcion-tarjeta input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.opcion-tarjeta input:focus-visible ~ .opcion-marca {
  outline: 3px solid var(--terracota);
  outline-offset: 2px;
}
.opcion-marca {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  border: 1.5px solid var(--neutro);
  border-radius: 50%;
  display: grid;
  place-items: center;
  transition: border-color var(--transicion), background var(--transicion);
}
.opcion-tarjeta input[type="checkbox"] ~ .opcion-marca { border-radius: 5px; }
.opcion-marca::after {
  content: "";
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  background: var(--pino);
  opacity: 0;
  transform: scale(.5);
  transition: opacity var(--transicion), transform var(--transicion);
}
.opcion-tarjeta input[type="checkbox"] ~ .opcion-marca::after { border-radius: 2px; }
.opcion-tarjeta input:checked ~ .opcion-marca {
  border-color: var(--pino);
}
.opcion-tarjeta input:checked ~ .opcion-marca::after {
  opacity: 1;
  transform: scale(1);
}
.opcion-tarjeta:has(input:checked) {
  border-color: var(--pino);
  background: var(--exito-suave);
}
.opcion-contenido { font-size: .98rem; }
.opcion-contenido .opcion-detalle {
  display: block;
  font-size: .82rem;
  color: var(--neutro);
}

/* Select / número */
.select, .entrada-numero, .entrada-texto {
  width: 100%;
  padding: .75rem 1rem;
  border: 1.5px solid var(--borde-control);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  color: var(--tinta);
  font-size: 1rem;
  transition: border-color var(--transicion);
}
.select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9'%3E%3Cpath d='M1 1l6 6 6-6' stroke='%231F4B3F' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
}
.select:hover, .entrada-numero:hover, .entrada-texto:hover { border-color: var(--pino-claro); }
.select:focus-visible, .entrada-numero:focus-visible, .entrada-texto:focus-visible { border-color: var(--pino); }

/* Repetidor de hijos */
#hijos { display: flex; flex-direction: column; gap: .6rem; }
#hijos .select { max-width: 100%; }
.boton-anadir {
  align-self: flex-start;
  background: none;
  border: 1.5px dashed var(--pino-claro);
  color: var(--pino);
  border-radius: var(--radio-chico);
  padding: .55rem 1rem;
  font-weight: 600;
  font-size: .9rem;
  cursor: pointer;
  transition: background var(--transicion), border-color var(--transicion);
}
.boton-anadir:hover { background: var(--exito-suave); border-color: var(--pino); }
.boton-anadir::before { content: "+ "; }

.subseccion {
  border-top: 1px solid var(--borde);
  padding-top: var(--espacio-3);
  margin-top: var(--espacio-1);
  display: flex;
  flex-direction: column;
  gap: var(--espacio-3);
}
.subseccion:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
.subseccion-titulo {
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  gap: .5rem;
}
.subseccion-titulo::before {
  content: "";
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--terracota);
  display: inline-block;
}

/* Aviso de error */
.aviso-error {
  background: var(--posible-suave);
  border: 1.5px solid var(--posible);
  color: var(--terracota-texto);
  border-radius: var(--radio-chico);
  padding: .75rem 1rem;
  font-size: .92rem;
  font-weight: 600;
}

/* Bloques temáticos plegados de P5 (Fase 1f, paso 6). Mismo patrón visual que
   `.informe-que-es` (summary en --pino, cuerpo con borde superior al abrir): P5 y el
   informe comparten el gesto de "esto es opcional, ábrelo si te interesa", así que
   comparten también su lenguaje visual. Deliberadamente NO usa el acento terracota de
   `.aviso-reforzado`: ese color queda reservado para el dato sensible de verdad. */
.bloque-afinar {
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
}
.bloque-afinar > summary {
  cursor: pointer;
  padding: .85rem 1.05rem;
  font-weight: 700;
  font-size: .98rem;
  color: var(--pino);
  list-style: none;
  display: flex;
  align-items: center;
  gap: .55rem;
}
.bloque-afinar > summary::-webkit-details-marker { display: none; }
.bloque-afinar > summary::before {
  content: "▸";
  display: inline-block;
  transition: transform var(--transicion);
}
.bloque-afinar[open] > summary::before { transform: rotate(90deg); }
.bloque-afinar[open] > summary { border-bottom: 1px solid var(--borde); }
.bloque-afinar-cuerpo {
  padding: 1rem 1.05rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: var(--espacio-pregunta);
}

/* Los grupos que cuelgan de una portera (`<div data-depende-de=...>` con varias preguntas
   dentro) eran `div` mudos: `display: block`, sin gap y con `fieldset { margin: 0 }`, o sea
   preguntas pegadas a CERO. El gap del cuerpo no llegaba porque solo alcanza a los hijos
   directos, y el grupo entero es UN hijo. Aquí se les da el mismo ritmo, de modo que una
   pregunta separe igual esté dentro o fuera de un grupo. */
.grupo-preguntas {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-pregunta);
}

/* El sí/no y el importe que revela son UNA pregunta partida en dos hermanos
   (`_gasto_declarado.html`). Con el hueco de entre-preguntas el importe se despegaría de
   su propio enunciado, así que el par lleva el hueco corto, el de dentro de una pregunta. */
.pregunta-gasto {
  display: flex;
  flex-direction: column;
  gap: .7rem;
}
.bloque-afinar-motivo {
  font-size: .88rem;
  color: var(--neutro);
  margin: 0;
}

/* Aviso reforzado (dato sensible / categoría especial) */
.aviso-reforzado {
  border: 1.5px solid var(--posible);
  background: var(--posible-suave);
  border-radius: var(--radio-chico);
  padding: 1rem 1.15rem;
  margin-top: .25rem;
}
.aviso-reforzado summary {
  cursor: pointer;
  font-weight: 700;
  list-style: none;
  display: flex;
  align-items: center;
  gap: .55rem;
}
.aviso-reforzado summary::-webkit-details-marker { display: none; }
.aviso-reforzado summary::before {
  content: "▸";
  color: var(--terracota-texto);
  transition: transform var(--transicion);
  display: inline-block;
}
.aviso-reforzado[open] summary::before { transform: rotate(90deg); }
.aviso-reforzado .aviso-texto {
  margin-top: .75rem;
  font-size: .88rem;
  color: var(--terracota-texto);
}
.aviso-reforzado .campo { margin-top: 1rem; }

/* ---------- 8. Botones ---------- */
.acciones-paso {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .5rem;
}
/* La pista del gesto de swipe se retiró junto con su marcado: la barra de acciones
   pegajosa la tapaba y solo asomaba el final ("retroceder."), que se leía como texto
   suelto en mitad de la pantalla. El gesto sigue funcionando; lo que queda para
   descubrirlo es el arrastre que sigue al dedo (ver navegacion.js). */
.boton, .boton-anadir, .boton-quitar {
  /* Objetivo táctil mínimo (44px de la guía de Apple/WCAG 2.5.8 se redondea a 48px,
     el mismo objetivo que usa `.boton-icono`). Antes no había un solo `min-height` en
     todo el CSS -- "Añadir otro hijo" medía 43,6px y "Quitar" 40,8px, los dos por
     debajo. Sin gate de `pointer: coarse`: los híbridos (portátil táctil) lo
     reportan mal, y 48px no molesta a quien usa ratón. */
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.boton {
  gap: .4rem;
  border-radius: var(--radio-chico);
  padding: .85rem 1.6rem;
  font-weight: 700;
  font-size: .98rem;
  text-decoration: none;
  border: 1.5px solid transparent;
  cursor: pointer;
  transition: background var(--transicion), color var(--transicion), border-color var(--transicion), transform 120ms ease;
}
.boton:active { transform: scale(.98); }
.boton-primario {
  background: var(--pino);
  color: var(--superficie);
  margin-left: auto;
}
.boton-primario:hover { background: var(--pino-claro); }
.boton-fantasma {
  background: none;
  color: var(--neutro);
  padding: .85rem .5rem;
}
.boton-fantasma:hover { color: var(--pino); }
/* La flecha de texto es para los enlaces "Atrás" que quedan como <a> simple (el pie
   de `error.html`/`comunidad_no_cubierta.html`): se ancla al selector de etiqueta, no
   a la clase. Ya NO es verdad que el botón "Atrás" de un formulario sea siempre un
   <a> -- desde la navegación por iconos es un <button type="submit"> con
   `.boton-icono` y su propia flecha en `.boton-glifo`; por eso este selector sigue
   siendo `a.boton-fantasma`, para no dibujar dos flechas en el botón de icono. */
a.boton-fantasma::before { content: "← "; }
/* Fondo `--terracota-oscuro` y no `--terracota`: el texto blanco sobre el claro daba
   3,67:1 (calculado), por debajo del 4,5 que AA exige a texto normal. Es el botón de
   "Empezar" y el del CTA a la gestoría -- o sea, los dos únicos botones que el negocio
   necesita que se pulsen. El oscuro da 5,05:1 y ya existía como token, así que no hay
   color nuevo que mantener. El hover baja un escalón más para que siga notándose. */
.boton-acento {
  background: var(--terracota-oscuro);
  color: var(--superficie);
}
.boton-acento:hover { background: #8A4820; }
.boton-grande { padding: 1.05rem 2.2rem; font-size: 1.05rem; }

/* Botones solo-icono ("Atrás"/"Continuar" de los cinco pasos): 48×48 circulares.
   Un fantasma solo-icono sin relleno NI aro sería casi invisible sobre blanco --
   por eso lleva un aro explícito abajo. El primario ya tiene el relleno --pino de
   `.boton-primario`, así que no necesita aro propio. */
.boton-icono {
  width: 48px;
  height: 48px;
  padding: 0;
  border-radius: 999px;
  flex: none;
}
/* La flecha es un SVG y no el carácter ← / →, y no es capricho. Medido con
   TextMetrics sobre Public Sans a 19,2px: la tinta de la flecha ocupa 5px y su centro
   cae a 5,5px sobre la línea base, mientras que el de una mayúscula está a 7px. El
   navegador centra la CAJA del glifo, así que la caja quedaba perfecta (desviación
   0,0px medida) y la flecha se veía 1,5px baja dentro del círculo. Corregirlo con un
   translateY a mano ataría el ajuste a esta fuente concreta: con la pila de reserva
   (system-ui, Segoe UI...) el desfase es otro. Un SVG con viewBox se centra por
   geometría y no depende de ninguna métrica tipográfica.
   `currentColor` para que herede el color del botón, que ya está contrastado. */
.boton-glifo {
  width: 1.35rem;
  height: 1.35rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* --borde sobre --superficie: 1,31:1 (calculado) -- muy por debajo de 3:1, así que el
   aro del fantasma-icono NO puede ir en --borde (el borde por defecto de `.boton`,
   transparente, tampoco vale). Va en --neutro: 5,85:1 sobre --superficie, de sobra
   para WCAG 1.4.11. El primario-icono no necesita aro propio: su glifo en
   --superficie sobre el relleno --pino da 9,83:1 (calculado), muy por encima del 3:1
   por el que se juzga un componente. */
.boton-fantasma.boton-icono {
  border-color: var(--neutro);
  color: var(--neutro);
  padding: 0;
}
.boton-fantasma.boton-icono:hover { border-color: var(--pino); color: var(--pino); }

/* ---------- 9. Gancho (P0) ---------- */
.gancho { text-align: center; display: flex; flex-direction: column; gap: var(--espacio-3); align-items: center; }
.gancho-titulo {
  font-size: clamp(2rem, 6vw, 2.9rem);
  /* 26ch y no 20ch: con 20ch (~616 px al tamaño de escritorio) el titular se estrechaba
     por debajo de la columna y partía "escapar" a una línea suya. A 26ch la medida deja
     de morder y manda el ancho del contenedor. */
  max-width: 26ch;
}
/* Segundo tramo del titular: bloque propio para que el salto caiga siempre después de
   "que" y el titular se lea en dos líneas. No se fija ancho ni se toca el tamaño — el
   bloque se parte por dentro en pantalla estrecha, que es el comportamiento correcto. */
.gancho-titulo-cierre { display: block; }
/* En pantalla estrecha el salto forzado hace daño: "Descubre el dinero que" ya no cabe
   de una pieza, así que el bloque deja "que" huérfano en su propia línea. Volviéndolo
   inline, el navegador reparte las cuatro palabras donde caben. Dos líneas es el ideal
   de escritorio, no un requisito a 320 px. */
@media (max-width: 30rem) {
  .gancho-titulo-cierre { display: inline; }
}

.gancho-titulo .subrayado {
  background-image: linear-gradient(transparent 62%, var(--posible-suave) 62%);
  padding: 0 .1em;
  /* El resalte es el énfasis del titular: si "dejando escapar" se parte entre dos
     líneas, la franja se corta por la mitad y deja "escapar" huérfano abajo. Cabe de
     sobra incluso a 320 px (~240 px de ancho contra ~288 px disponibles), así que
     forzarlo a una línea no arriesgaba desbordar... MIENTRAS EL TEXTO FUERA EL DE FÁBRICA.
     Con la letra al 200 % (WCAG 1.4.4) esos ~240 px pasan a ~480 y el titular se salía
     88 px por la derecha, medido.

     Se QUITA el `nowrap`, y el propio razonamiento de arriba es el motivo: si a tamaño
     normal la frase cabe siempre, la regla no estaba haciendo nada salvo el día que hacía
     daño. No hay forma en CSS de decir "no partas mientras quepa" -- `nowrap` no admite
     excepción --, así que entre una preferencia inerte y un desbordamiento real, se cae la
     preferencia. */
}
.gancho-sub { font-size: 1.1rem; color: var(--neutro); max-width: 42ch; }
.gancho-sub strong { color: var(--pino); font-weight: 700; }

/* Botón + promesas de la portada. `fit-content` + hijos estirados = el ancho lo fija el
   hijo más ancho (la lista, por su línea más larga) y el botón se estira hasta ahí, sin
   ninguna medida clavada a mano. `max-width: 100%` para que en una pantalla estrecha el
   envoltorio ceda en vez de desbordar. */
.gancho-accion {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Mismo token que el `gap` de `.gancho`, que es el hueco entre el titular y este
     bloque: así el botón queda a la misma distancia de "escapar" que de "Gratis" por
     construcción, y sigue igualado si algún día se cambia el token. */
  gap: var(--espacio-3);
  width: fit-content;
  max-width: 100%;
}
/* El texto sigue centrado dentro del botón aunque ahora sea más ancho que su etiqueta. */
.gancho-accion .boton { text-align: center; }

/* Las cuatro promesas bajo el botón de la portada. Mismos valores que `.gancho-sub`
   —de la que heredan el tono— pero en columna y alineadas a la izquierda para que los
   arranques en negrita formen una columna que se lee de un barrido. */
.gancho-claves {
  font-size: 1.25rem;
  /* Interlineado apretado, y no es solo estética. El titular va a 1,15 y el cuerpo a
     1,6: con el mismo hueco CSS arriba y abajo del botón, el de abajo se PERCIBE unos
     4 px mayor, porque el semi-interlineado de la línea de "Gratis" empuja el glifo
     hacia dentro. A 1,35 el semi-interlineado baja a 3,5 px contra los 2,4 px del
     titular, y la diferencia percibida queda en ~1 px. De paso son cuatro líneas
     cortas, no prosa: 1,6 las separaba más de lo que pide una lista. */
  line-height: 1.35;
  color: var(--neutro);
  /* Centradas por petición explícita. Estuvieron alineadas a la izquierda para que los
     arranques en negrita formaran una columna que se lee de un barrido, pero dentro de
     una portada centrada se percibían como texto descuadrado, que es lo que se
     reportó. */
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  margin: 0;
}
.gancho-claves strong { color: var(--pino); font-weight: 700; }

/* --neutro sobre --papel: 5,47:1 (calculado), cumple AA. */
.gancho-nota { font-size: .85rem; color: var(--neutro); }

/* ---------- La portada como PRESUPUESTO DE ALTO ----------
   Deja de ser una pila apoyada en el borde superior con todo el sobrante debajo: `main`
   es la caja del presupuesto —mide lo que queda entre cabecera y pie— y el héroe se
   reparte dentro. En un Pro Max eso cambia 44/215 de aire por un reparto equilibrado.

   `:has()` en vez de una clase en <body>: no hay que tocar `base.html` ni las nueve
   plantillas, y el selector solo puede acertar en la portada porque `.gancho` no existe
   en ninguna otra. Si el navegador no conoce `:has()` la regla entera se descarta y queda
   el comportamiento de hoy -- y es aproximadamente el mismo navegador que tampoco conoce
   `svh` (ambas, Safari 15.4), así que la garantía cae de una pieza en vez de a medias.

   Sin `safe center` a propósito, y no por descuido: `.principal` es item flex con
   `flex: 1` y conserva su `min-height: auto`, así que NUNCA queda más baja que su
   contenido. Por eso el centrado no puede empujar el titular fuera del borde superior,
   que es el fallo clásico de `justify-content: center` en un contenedor con scroll.
   Comprobado a 320x390, donde el contenido sí desborda: el titular sigue alcanzable. */
.principal:has(> .gancho) {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Un item flex con márgenes laterales `auto` (los que trae `.contenedor`) deja de
   estirarse: sin esto la sección mediría su contenido en vez de los 640px de `--ancho`. */
.principal:has(> .gancho) > .gancho { width: 100%; }

/* `.paso-etiqueta` trae `margin-bottom: .5rem` para el rótulo de los pasos. Dentro de un
   contenedor flex los márgenes NO se colapsan: se SUMAN al `gap`. Así que en la portada
   el hueco bajo el rótulo valía 24px donde todos los demás valían 16 -- ocho píxeles que
   no aparecían en ninguna medida y una equidistancia rota. Se anula solo aquí; P1-P5 no
   se enteran. */
.gancho .paso-etiqueta { margin-bottom: 0; }

/* `.confianza` (las tres pastillas que había bajo el botón) se retira con las reglas
   que la pintaban: la sustituyen `.gancho-claves` + `.gancho-nota`, y dejar el CSS de
   una clase que ya no usa ninguna plantilla solo invita a resucitarla por error. */

/* ---------- 10. Chips y ficha de resultado (compartido con la Fase 5) ---------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .78rem;
  font-weight: 700;
  padding: .3rem .7rem;
  border-radius: 999px;
}
.estado-cumple .chip, .chip.chip-cumple { background: var(--exito-suave); color: var(--exito); }
/* color: --terracota-texto, NO --posible — --posible sobre --posible-suave da ~3.5:1,
   por debajo de AA (4.5:1) para texto normal. --terracota-texto sobre --posible-suave
   da ~4.83:1. Ver task-5-report.md para el cálculo. */
.estado-posible .chip, .chip.chip-posible { background: var(--posible-suave); color: var(--terracota-texto); }
.estado-no_aplica .chip, .chip.chip-no_aplica { background: var(--neutro-suave); color: var(--neutro); }

.importe-seguro {
  font-family: var(--serif);
  font-size: var(--tam-numero);
  color: var(--pino);
}
.importe-posible {
  font-family: var(--serif);
  font-size: 1.4rem;
  color: var(--terracota-oscuro);
}
/* Cifra de referencia de /deducciones: recuerda el total del que salen los desgloses
   sin competir con el titular del resumen. Deliberadamente pequeña -- el número grande
   es del hub. --neutro sobre --papel: 5,47:1 (calculado); las cifras van en --pino
   (9,19:1) y --terracota-oscuro para el posible. */
.informe-referencia {
  color: var(--neutro);
  font-size: .9rem;
  margin-bottom: var(--espacio-2);
}
.informe-referencia-cifra {
  font-family: var(--serif);
  font-size: 1.15rem;
  color: var(--pino);
}
.informe-referencia-posible { color: var(--terracota-oscuro); }

.ficha {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  padding: var(--espacio-3);
  margin-top: var(--espacio-2);
}

/* ---------- 11. El informe (resultado) ---------- */
.informe {
  max-width: var(--ancho);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--espacio-4);
}

/* Cabecera del importe: pieza protagonista, revelado en dos tiempos. */
.informe-hero {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--espacio-1);
  opacity: 0;
  animation: entra-tarjeta 520ms cubic-bezier(.2, .7, .2, 1) both;
}
.informe-titular {
  font-size: clamp(1.4rem, 4vw, 1.7rem);
  color: var(--neutro);
  font-weight: 500;
  font-family: var(--sans);
  margin-top: .25rem;
}
.informe-importes {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: var(--espacio-3) var(--espacio-4);
  margin-top: var(--espacio-1);
}
.importe-bloque { display: flex; flex-direction: column; align-items: center; gap: .15rem; }
.importe-rotulo {
  font-size: .82rem;
  color: var(--neutro);
  max-width: 22ch;
}
.importe-bloque-posible::before {
  content: "+";
  font-family: var(--serif);
  font-size: 1.4rem;
  color: var(--neutro);
  display: none;
}
@media (min-width: 560px) {
  .importe-bloque-posible { position: relative; }
  .importe-bloque-posible::before { display: block; position: absolute; left: -1.8rem; top: .1rem; }
}
.informe-disclaimer {
  font-size: .82rem;
  color: var(--neutro);
  font-style: italic;
  margin-top: var(--espacio-1);
}

/* `.informe-cta` (la caja terracota "¿Y ahora qué?", justo tras el importe) se
   retiró en la Fase "cierra-el-posible": el CTA de la gestoría ya no es una caja de
   texto en el hub, es el primer botón de `.navegacion-informe` (ver sección 15) --
   mismo destino (/cta), mismo acento terracota, sin texto propio que compita con el
   hero que el usuario ha pedido dejar limpio. */

.informe-aviso-global {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  background: var(--neutro-suave);
  color: var(--tinta);
  border-radius: var(--radio-chico);
  padding: .85rem 1rem;
  font-size: .88rem;
}

.informe-seccion-titulo {
  font-size: clamp(1.2rem, 3.2vw, 1.5rem);
  margin-bottom: var(--espacio-1);
}

/* Tarjetas de deducción, con revelado escalonado por índice (animation-delay inline). */
.ficha-entra { opacity: 0; animation: entra-tarjeta 460ms cubic-bezier(.2, .7, .2, 1) both; }
/* El retardo escalonado vivia en un atributo `style="animation-delay: ..."` calculado en la
   plantilla. La CSP lo prohibe --`style-src 'self'` bloquea tambien los atributos de estilo,
   no solo los <style>-- y sin el, las tarjetas aparecian todas de golpe. Aqui hace lo mismo
   sin estilo en linea: 140 ms de arranque mas 90 por posicion, hasta la decima. De la
   undecima en adelante comparten el ultimo retardo, que a esa altura ya nadie distingue y
   evita una lista interminable de reglas. */
.ficha-entra { animation-delay: 950ms; }
.ficha-entra:nth-of-type(1) { animation-delay: 140ms; }
.ficha-entra:nth-of-type(2) { animation-delay: 230ms; }
.ficha-entra:nth-of-type(3) { animation-delay: 320ms; }
.ficha-entra:nth-of-type(4) { animation-delay: 410ms; }
.ficha-entra:nth-of-type(5) { animation-delay: 500ms; }
.ficha-entra:nth-of-type(6) { animation-delay: 590ms; }
.ficha-entra:nth-of-type(7) { animation-delay: 680ms; }
.ficha-entra:nth-of-type(8) { animation-delay: 770ms; }
.ficha-entra:nth-of-type(9) { animation-delay: 860ms; }

/* Sin JavaScript, las preguntas dependientes del paso 5 que el servidor marco `hidden` no
   las revelaria nadie: quedarian incontestables para siempre, que es peor que enseñarlas
   todas. Esto lo revierte.

   Vivia en un <style> dentro de un <noscript> en la propia plantilla, y la CSP lo bloquea.
   Ahora el interruptor es la clase `hay-js`, que pone `/static/js/hay-js.js` --un fichero
   externo, permitido por `script-src 'self'`-- en el <head> y sin `defer`, para que la clase
   este puesta antes de que se pinte nada y no haya un parpadeo. */
html:not(.hay-js) [data-depende-de][hidden] { display: block !important; }
/* Cabecera plegada de cada deducción: nombre a la izquierda, estado e importe a la
   derecha. Es el `<summary>`, así que se pulsa entera. `list-style: none` + el
   `::-webkit-details-marker` quitan el triangulito nativo, que se sustituye por el
   mismo `▸` que ya usan los otros desplegables del proyecto. */
.ficha-cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .6rem .8rem;
  cursor: pointer;
  list-style: none;
  /* Objetivo táctil: la cabecera es lo único pulsable de la tarjeta cerrada. */
  min-height: 48px;
}
.ficha-cabecera::-webkit-details-marker { display: none; }

/* ---------- Pista de "esto se despliega" ----------
   El triángulo ▸ que ya llevaba `.ficha-nombre::before` no se veía: es pequeño, gris y
   compite con el nombre de la deducción y con el importe, que es donde va la mirada. La
   pista va abajo y CENTRADA, fuera de la línea de texto, que es donde el pulgar espera
   encontrar "hay más".

   Va en el `::after` del `<summary>` y se retira a mano con `details[open]`. Creí que
   ponerla ahí bastaba para que existiera solo en cerrado, y es FALSO: el `<summary>` sigue
   visible cuando el `<details>` se abre, así que la flecha se quedaba señalando hacia abajo
   sobre un desplegable ya desplegado -- peor que ruido, una invitación a hacer lo que ya
   está hecho. Comprobado en el navegador, no deducido.

   `border-box` girado 45° en vez de un SVG o un glifo: hereda el grosor y el color de los
   tokens, escala con el zoom sin pixelarse y no añade una petición ni un data-URI. */
.ficha > .ficha-cabecera,
.oportunidad > .ficha-cabecera,
.ayuda > .ficha-cabecera,
.bloque-afinar > summary {
  position: relative;
  /* Sitio para la flecha sin que pise el texto ni encoja el objetivo táctil de 48px. */
  padding-bottom: 1.15rem;
}
.ficha > .ficha-cabecera::after,
.oportunidad > .ficha-cabecera::after,
.ayuda > .ficha-cabecera::after,
.bloque-afinar > summary::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: .3rem;
  width: .42rem;
  height: .42rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-bottom-right-radius: 2px;
  color: var(--neutro);
  /* El giro va DENTRO del keyframe, no aquí: `transform` es una sola propiedad, así que
     animar el desplazamiento vertical borraría la rotación si se declarara aparte. */
  transform: translateX(-50%) translateY(0) rotate(45deg);
  animation: pista-despliegue 1.7s ease-in-out infinite;
  /* Pulsa el <summary>, no la flecha: es una pista, no un segundo control. */
  pointer-events: none;
}

/* Abierto: la pista ya cumplió su función y estorba. Y el `padding-bottom` que le hacía
   sitio también sobra -- si no, queda un hueco muerto entre la cabecera y el cuerpo. */
.ficha[open] > .ficha-cabecera::after,
.oportunidad[open] > .ficha-cabecera::after,
.ayuda[open] > .ficha-cabecera::after,
.bloque-afinar[open] > summary::after { content: none; }
.ficha[open] > .ficha-cabecera,
.oportunidad[open] > .ficha-cabecera,
.ayuda[open] > .ficha-cabecera,
.bloque-afinar[open] > summary { padding-bottom: .35rem; }

/* El principio no cambia --no parpadea a negro, respira--, pero la primera versión se
   quedó tan discreta que en un móvil apenas se notaba: 2 px de recorrido y de .28 a .75 de
   opacidad, en 2,6 s, es un movimiento que la vista periférica no llega a registrar. Lo que
   se percibe de una animación es la VELOCIDAD del cambio, no su amplitud, así que sube el
   recorrido a 5 px, la opacidad llega hasta 1 y el ciclo baja a 1,7 s. El `scale` acompaña
   al descenso porque una flecha que además crece un poco lee como "ven hacia aquí".
   Sigue sin bajar de .22: un destello desde cero se lee como un error de pintado. */
@keyframes pista-despliegue {
  0%, 100% { opacity: .22; transform: translateX(-50%) translateY(0) rotate(45deg) scale(1); }
  50%      { opacity: 1;   transform: translateX(-50%) translateY(5px) rotate(45deg) scale(1.18); }
}

/* Quien pide menos movimiento sigue necesitando la pista: se queda quieta y visible, no
   desaparece. Quitarla del todo dejaría la tarjeta sin ninguna señal de que se abre. */
@media (prefers-reduced-motion: reduce) {
  .ficha > .ficha-cabecera::after,
  .oportunidad > .ficha-cabecera::after,
  .bloque-afinar > summary::after {
    animation: none;
    opacity: .55;
  }
}

/* ---------- Pista de "esto se desliza" ----------
   Una mano entre dos flechas, meciéndose de lado a lado, DENTRO de la barra de acciones y
   entre los dos botones de navegar. Responde al punto que `navegacion.js` deja abierto por
   escrito: el swipe funciona pero "solo se descubre por accidente", porque el arrastre de
   12 px enseña el gesto EN EL MOMENTO en que ya lo estás haciendo -- o sea, a quien ya lo
   sabe.

   El sitio es el que es por una razón: la barra ya es `display: flex` con
   `justify-content: space-between`, así que un tercer hijo con `flex: 1` se come todo el
   hueco libre y centra su contenido -- la mano cae EXACTAMENTE a mitad de camino entre
   "atrás" y "adelante", sin medir nada ni recalcular al girar el móvil. Antes iba `fixed`
   sobre el centro del viewport, que no es lo mismo: los botones no están centrados
   respecto a la pantalla, sino repartidos dentro de la tarjeta.

   Quien la crea es `navegacion.js`, y no la plantilla, a propósito: el gesto se instala
   tras cinco guardas (pantalla táctil, formulario de paso, los dos botones...). Una pista
   en el HTML se pintaría también donde el gesto NO existe, y una pista que miente es peor
   que ninguna.

   Transparente por encargo: es una señal, no un control. De ahí el `opacity` bajo, el
   `pointer-events: none` (la mano nunca se come un toque destinado a los botones que tiene
   al lado) y el `aria-hidden` que le pone el script: para quien navega con lector de
   pantalla el swipe no es el camino, lo son los botones. */
.pista-swipe {
  /* `flex: 1` + centrado = el punto medio exacto entre los dos botones. `min-width: 0`
     para que en una pantalla estrecha ceda sitio en vez de empujar a los botones. */
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  color: var(--tinta);
  opacity: 0;
  pointer-events: none;
  /* La entrada y la salida son la MISMA transición: el script solo quita la clase y la
     pista se desvanece sin saltos. */
  transition: opacity 600ms ease;
}
.pista-swipe.visible { opacity: .3; }

.pista-swipe-mano {
  /* 36px: más pequeña que los botones de 48px --acompaña, no compite-- pero no menos.
     A 30px, rasterizada y mirada, el pulgar y los nudillos se fundían en un bulto y la
     mano dejaba de leerse como una mano. */
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  /* De contorno y no de silueta: los dedos plegados solo se distinguen si hay línea entre
     ellos. El grosor es 2 sobre un viewBox de 32 -- a 2.4 los valles entre dedos se
     rellenaban y la mano volvía a ser un bulto (comprobado dibujándola). */
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: pista-swipe-vaiven 2.4s ease-in-out infinite;
}
/* Las flechas laten en contrafase con la mano: la de la izquierda se enciende cuando la
   mano va hacia ella. Es lo que convierte el vaivén en "esto va a los dos lados" en vez
   de en un adorno que se mueve. */
.pista-swipe-flecha {
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: pista-swipe-flecha 2.4s ease-in-out infinite;
}
.pista-swipe-flecha.derecha { animation-delay: 1.2s; }

@keyframes pista-swipe-vaiven {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-10px); }
  75%      { transform: translateX(10px); }
}
@keyframes pista-swipe-flecha {
  0%, 100% { opacity: .35; }
  25%      { opacity: 1; }
  60%      { opacity: .35; }
}

/* Sin movimiento: la pista se queda, quieta. La mano y las dos flechas siguen diciendo
   "esto se desliza" sin animarse, igual que la pista de despliegue de arriba. */
@media (prefers-reduced-motion: reduce) {
  .pista-swipe-mano,
  .pista-swipe-flecha { animation: none; opacity: 1; }
}

/* El enlace de "Modificar mis datos" comparte fila con el de volver al resumen cuando los
   dos existen; en el hub va solo y centrado igual. */
.informe-enlaces-pie {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--espacio-1) var(--espacio-3);
  margin-top: var(--espacio-1);
}
.informe-enlaces-pie .enlace-volver-resumen { margin-top: 0; }
.ficha[open] > .ficha-cabecera { margin-bottom: .5rem; }
/* Mismas dos reglas de apertura, pero para `.oportunidad` (_oportunidad.html), que
   reutiliza `.ficha-cabecera`/`.ficha-nombre` como cabecera de su propio `<details>`
   en vez de clonarlas bajo un nombre nuevo (Fase "cierra-el-posible"). */
.oportunidad[open] > .ficha-cabecera { margin-bottom: .5rem; }
.oportunidad[open] > .ficha-cabecera .ficha-nombre::before { transform: rotate(90deg); }
.ficha-nombre {
  font-family: var(--serif);
  font-size: 1.1rem;
  line-height: 1.25;
  /* Deja sitio al bloque de estado sin empujarlo fuera en pantallas estrechas. */
  flex: 1 1 12ch;
}
.ficha-nombre::before {
  content: "▸";
  display: inline-block;
  margin-right: .4rem;
  color: var(--neutro);
  transition: transform var(--transicion);
}
.ficha[open] > .ficha-cabecera .ficha-nombre::before { transform: rotate(90deg); }
.ficha-cabecera-estado {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  flex: none;
}
/* RESUELTO (2026-08-17). Con la letra al 200 % (WCAG 1.4.4) esta cabecera sacaba
   /deducciones 178 px fuera de la pantalla en un móvil de 375: la insignia y el importe
   JUNTOS miden ~390 px a ese tamaño, así que no caben ni ocupando una línea propia.
   Tres intentos anteriores fallaron y conviene saber por qué, porque explican la salida:
   `flex-wrap: wrap` no bastaba (el problema no era envolver, era que la fila entera no
   cabe), `min-width: 0` tampoco (la caja mide su contenido igual), y `flex: 0 1 auto`
   rompía la alineación A TAMAÑO NORMAL, que es lo que no puede cambiar.

   La salida es una media query en **rem**, no en px. Una `@media (max-width: <n>rem)` se
   evalúa contra el tamaño de letra raíz, así que SUBE de umbral cuando el usuario amplía
   el texto: es la única forma de que una regla CSS distinga "pantalla estrecha" de "letra
   grande" sin JavaScript. Las cuentas, en un móvil de 375 px:
     letra normal (raíz 16 px) -> 375 px son 23,4 rem -> NO entra, layout intacto.
     letra al 200 % (raíz 32 px) -> 375 px son 11,7 rem -> entra y apila.
   Cualquier umbral entre 12 y 23 rem sirve; 18 rem deja margen por los dos lados y
   además cubre un móvil muy estrecho (≤288 px) a tamaño normal, donde apilar también
   es lo correcto.

   Dentro, el bloque de estado pasa a ocupar su propia fila Y a envolver por dentro, que
   es lo que faltaba: la insignia arriba y el importe debajo. */
   Y al medirlo con la letra al 200 % apareció que la cabecera NO era la causa principal:
   con ella ya apilada, la página seguía desbordando 178 px. El culpable de verdad son los
   SANGRADOS, que están en `rem` y por tanto se duplican también: `main` (2 rem), `.ficha`
   (1,75 rem a cada lado) y `.checklist-item` (1,3 rem) sumaban ~130 px de los 375
   disponibles, y la caja de texto que quedaba era MÁS ESTRECHA QUE LAS PALABRAS -- 156 px
   para palabras de 186 px, así que ni envolviendo caben. Por eso los tres intentos
   anteriores con `flex` no podían funcionar: el problema nunca estuvo en el flex.

   El aire generoso es correcto a tamaño normal y sobra cuando la letra ocupa el doble: lo
   que se recorta aquí es el hueco, nunca el texto. */
/* Las razones y los nombres de deducción son frases legales largas: sin esto, una palabra
   que no quepa saca la tarjeta entera de la pantalla en vez de partirse. */
.ficha-nombre, .ficha-razon, .ficha-cabecera { overflow-wrap: break-word; min-width: 0; }
.ficha-rango {
  font-family: var(--serif);
  font-size: 1.3rem;
  color: var(--pino);
}
.estado-posible .ficha-rango { color: var(--terracota-oscuro); }
.estado-no_aplica .ficha-rango { color: var(--neutro); }
.ficha-nota { color: var(--neutro); font-size: .85rem; margin-bottom: .5rem; }

.ficha-razones { display: flex; flex-direction: column; gap: .4rem; margin-bottom: var(--espacio-2); }
.razon { display: flex; align-items: baseline; gap: .6rem; font-size: .92rem; }
/* Marca decorativa (color de apoyo); el símbolo ✓/✗/? real ya viene incrustado en
   r.texto desde el motor (MARCAS), así que aquí NO se repite el carácter — solo color. */
.razon-marca {
  flex: none;
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  transform: translateY(-.1rem);
}
.razon-si .razon-marca { background: var(--exito); }
.razon-no .razon-marca { background: var(--neutro); }
.razon-desconocido .razon-marca { background: var(--terracota-texto); }

.ficha-checklist {
  border-top: 1px solid var(--borde);
  padding-top: var(--espacio-2);
  margin-bottom: var(--espacio-2);
}
.checklist-titulo { font-size: .95rem; margin-bottom: .6rem; }
.checklist-columnas {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--espacio-2);
}
@media (min-width: 520px) {
  .checklist-columnas { grid-template-columns: 1fr 1fr; }
}
.checklist-etiqueta {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--pino);
  margin-bottom: .4rem;
}
.checklist { display: flex; flex-direction: column; gap: .4rem; }
.checklist-item {
  position: relative;
  padding-left: 1.3rem;
  font-size: .88rem;
  color: var(--tinta);
}
.checklist-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: .3rem;
  width: .7rem;
  height: .7rem;
  border: 1.5px solid var(--pino-claro);
  border-radius: 3px;
}

.ficha-aviso {
  display: flex;
  gap: .45rem;
  align-items: flex-start;
  background: var(--posible-suave);
  color: var(--terracota-texto);
  border-radius: var(--radio-chico);
  padding: .65rem .85rem;
  font-size: .84rem;
  margin-bottom: var(--espacio-2);
}
.ficha-fuente { font-size: .82rem; }
.ficha-fuente a {
  color: var(--pino);
  text-decoration: underline;
  text-decoration-color: var(--borde);
  text-underline-offset: 3px;
}
.ficha-fuente a:hover { text-decoration-color: currentColor; }

/* "Descartada" (incompatible con una mejor). Dos trampas de contraste ya cazadas aquí:
   1) NUNCA `opacity` en un contenedor con texto — aplana el alfa de todo el subárbol al
      compositar contra --papel y tira los chips por debajo de AA (~4.83:1 → ~3.07:1 en
      "posible"; ~5.26:1 → ~3.20:1 en "cumple").
   2) NUNCA cambiar el fondo de la tarjeta — cada texto de dentro estrena pareja de color.
      Con --neutro-suave, el rango de una ficha "posible" (--terracota-oscuro) caía a
      ~4.21:1.
   Por eso el estado se señala SIN tocar color ni fondo del texto: borde discontinuo, sin
   sombra, y la nota explícita "no se suma al total" (el color nunca es el único portador).
   Si algún día vuelves a atenuar esta tarjeta, recalcula el contraste de TODO su texto. */
.ficha-descartada {
  border-style: dashed;
  border-color: var(--neutro);
  box-shadow: none;
}

.informe-no-aplica { margin-top: calc(var(--espacio-2) * -1); }
.informe-no-aplica summary {
  cursor: pointer;
  color: var(--neutro);
  font-weight: 700;
  font-size: .92rem;
  padding: .5rem 0;
  list-style: none;
}
.informe-no-aplica summary::-webkit-details-marker { display: none; }
.informe-no-aplica summary::before { content: "▸ "; }
.informe-no-aplica[open] summary::before { content: "▾ "; }
.informe-no-aplica .ficha { margin-top: var(--espacio-2); }

.tarjeta-mensaje { text-align: center; }
.tarjeta-mensaje .paso-etiqueta { justify-content: center; display: flex; }

/* ---------- 12. Responsive ---------- */
/* Inventario de breakpoints (no hay custom property de breakpoint posible: las
   custom properties no se resuelven dentro de la condición de un `@media`, solo en
   valores de declaración -- por eso `--tarjeta-lateral-movil` guarda un padding, no
   un ancho de ruptura). Los cuatro que ya existían en este archivo, y siguen siendo
   los únicos:
     - 480px (`max-width`, y su gemelo `30rem` en la sección 9): el único móvil.
       TODO el CSS móvil nuevo entra AQUÍ, no se crea un quinto breakpoint. Tiene DOS
       bloques, y el orden importa: este, y otro al final envuelto en
       `@supports (height: 1svh)` con el cromo elástico, colocado a propósito después
       del bloque de `max-height` para ganarle por orden en un móvil moderno y cederle
       el paso en uno viejo.
     - 520px (`min-width`): `.checklist-columnas` pasa a dos columnas.
     - 560px (`min-width`): `.importe-bloque-posible` gana el "+" posicionado.
     - 720px (`min-width`): `.cabecera-interior` deja de envolver. */
@media (max-width: 480px) {
  .principal { padding-block: var(--espacio-3); padding-inline: 1rem; }
  /* El nombre y su punto ya identifican la marca; el eslogan es lo primero que sobra
     cuando el alto aprieta. Se oculta por ANCHO y no por alto porque una media query de
     `height` mide el viewport GRANDE (ver el bloque de 700px, más abajo): por eso un
     iPhone 13/14/15 declara 844, no entraba en aquel bloque, y era el único móvil de la
     matriz que conservaba el eslogan... siendo el que peor proporción de scroll tenía.
     Aquí sí se pierde algo real: en un móvil de 390px de ancho el eslogan hoy se ve y
     dejará de verse. Son 30px de los 135 que hace falta recuperar ahí. */
  .eslogan { display: none; }
  .tarjeta { padding: var(--espacio-3) var(--tarjeta-lateral-movil); }
  /* NO `flex-wrap: wrap` + `width: 100%`. Esa pareja era la que tiraba el boton grande a
     una segunda linea, dejando la flecha de "atras" sola encima y la barra ocupando el
     doble de alto -- justo donde menos sitio hay. `flex: 1` le da el ancho que sobra
     DESPUES de la flecha, asi que los dos quedan en la misma linea y el boton sigue
     siendo tan ancho como puede. */
  .acciones-paso { flex-wrap: nowrap; }
  /* :not(.boton-icono), no orden de fuente: la especificidad de una sola clase empata
     con `.boton-icono`, así que sin `:not()` el orden de aparición en la cascada
     decidiría por accidente cuál gana. Sin él, estas reglas encogerían también los
     círculos de 48px de "atrás"/"continuar" en P1-P4. */
  .boton-primario:not(.boton-icono) { flex: 1; min-width: 0; margin-left: 0; }
  .boton-fantasma:not(.boton-icono) { padding: .6rem 0; }

  /* Barra de acciones pegajosa, solo en el paso del formulario (nunca en
     `error.html` ni en `comunidad_no_cubierta.html`, que también usan
     `.acciones-paso` pero no deben quedarse pegadas al desplazar -- de ahí el ámbito
     `.formulario-paso .acciones-paso`, no la clase sola).

     `sticky`, nunca `fixed`, y por una razón dura de CSS: `.tarjeta` anima con
     `entra-tarjeta`, cuyo último fotograma es `transform: translateY(0)`. Un
     `transform` distinto de `none` -- aunque sea "sin mover nada" -- convierte a
     `.tarjeta` en el bloque contenedor PERMANENTE de sus descendientes `fixed`; un
     `fixed` aquí dentro se ancoraría a la tarjeta, no al viewport. `sticky` no tiene
     ese problema, además ya ocupa su sitio en el flujo del documento (sin padding
     compensatorio) y degrada sola a `static` si algún ancestro llegara a ganar
     `overflow` (ninguno lo tiene hoy -- si algún día `.tarjeta` gana `overflow`,
     esta barra deja de pegarse en silencio, ojo).

     CORRECCIÓN (medida el 2026-08-15, y aquí decía lo contrario): el `margin-bottom`
     negativo SÍ desplaza la caja pegada. Medido en el paso 4 a 375x812, la barra da
     `bottom: 822` con el viewport en 812, o sea que sus últimos ~10px --su relleno
     inferior, no los botones-- quedan fuera de pantalla. No rompe nada (los botones se
     ven enteros y el relleno es aire), pero quien reserve hueco contra esta barra debe
     saber que el alto de su caja es MAYOR que lo que tapa dentro del viewport: reservar
     `getBoundingClientRect().height` es conservador, que es la dirección segura.
     La preocupación del párrafo anterior sigue siendo cierta solo para `fixed`.

     Los márgenes negativos sangran la barra hasta el borde de la tarjeta -- lateral
     con el token nuevo, inferior con `--espacio-3` porque ese es el padding inferior
     real de `.tarjeta` en este breakpoint (arriba) -- y el `border-radius` redondea
     sus esquinas para que coincidan con las de la tarjeta, ya que no hay
     `overflow: hidden` en `.tarjeta` que las recorte por sí solo. */
  .formulario-paso .acciones-paso {
    position: sticky;
    bottom: 0;
    margin: 0 calc(var(--tarjeta-lateral-movil) * -1) calc(var(--espacio-3) * -1);
    padding: .75rem var(--tarjeta-lateral-movil)
             calc(.75rem + env(safe-area-inset-bottom));
    background: var(--superficie);
    border-top: 1px solid var(--borde);
    border-radius: 0 0 var(--radio) var(--radio);
  }
}

/* Ventana BAJA. Se aprieta el CROMO, nunca el contenido: el titular, el botón y las
   cuatro promesas se quedan exactamente como están.

   Este bloque nació creyendo que `max-height` detectaba móviles bajos. NO los detecta, y
   conviene que quede escrito para que nadie vuelva a creerlo: una media query de `height`
   se juzga contra el bloque contenedor inicial, que en móvil es el viewport GRANDE y no
   se re-evalúa al colapsar la barra. Medido: un iPhone 13/14/15 declara 844 y enseña
   715, así que NO entraba aquí -- y era el móvil más común y el que peor quedaba. Lo que
   sí ve el viewport pequeño es `svh` dentro del valor, y de eso se encarga el bloque de
   cromo elástico del final.

   Así que hoy este bloque cubre dos cosas de verdad:
     - la ventana de escritorio recortada (1280x700 y parecidas), donde la media query de
       alto SÍ es honesta: mide el viewport real y se re-evalúa al redimensionar;
     - el móvil sin `svh`, que no entra en el `@supports` del final y para el que esto
       sigue siendo, exactamente, el comportamiento de siempre.
   Por eso NO lleva guarda de ancho: acotarlo dejaría al móvil viejo peor que hoy. */
@media (max-height: 700px) {
  .cabecera { padding: var(--espacio-2) var(--espacio-2) .75rem; }
  /* El nombre y su punto ya identifican la marca; el eslogan es lo primero que sobra
     cuando el alto aprieta. */
  .eslogan { display: none; }
  .principal { padding-top: var(--espacio-2); padding-bottom: var(--espacio-2); }
  /* Longhands, no el atajo: con `padding: var(--espacio-2)` este bloque barría el
     `padding-bottom` con área segura de la sección 3 y el pie podía quedar bajo el
     indicador de inicio justo en las pantallas más apretadas. */
  .pie {
    padding-top: var(--espacio-2);
    padding-inline: var(--espacio-2);
    padding-bottom: max(var(--espacio-2), env(safe-area-inset-bottom));
  }
  /* Los dos gaps bajan JUNTOS: el de `.gancho` es el hueco sobre el botón y el de
     `.gancho-accion` el de debajo. Tocar uno solo rompería la equidistancia que es
     justo lo que se pidió. */
  .gancho, .gancho-accion { gap: var(--espacio-2); }
}

/* ---------- Cromo elástico: el reparto del presupuesto de alto ----------
   Segundo bloque del MISMO breakpoint de 480px, no un quinto breakpoint. Va aquí abajo,
   DESPUÉS del bloque de `max-height`, y el sitio es la mitad del diseño:

     - Móvil moderno: aplica este y, a igual especificidad, gana por ir el último.
     - Móvil sin `svh`: `@supports` no entra, sobrevive el bloque de alto y el
       comportamiento es EXACTAMENTE el de hoy.
     - Escritorio con ventana baja (1280x700): no entra por ancho, manda el bloque de
       alto, que ahí sí es honesto porque mide el viewport de verdad.

   La alternativa que descarté era acotar el bloque de 700px con un `min-width`. Suena
   equivalente y no lo es: sacaba al móvil viejo del alcance del bloque compacto sin darle
   nada a cambio, y lo dejaba en 168px de scroll contra los 114 de hoy. Peor que no tocar
   nada. Ordenar no tiene ese defecto.

   El `@supports` NO es decorativo, y esta es la trampa que más cara habría salido: una
   declaración que contiene `var()` o `env()` se parsea de forma PERMISIVA, así que si la
   sustitución resulta inválida —`svh` en un navegador que no la conoce— la declaración
   no se descarta al parsear y NO resucita el valor anterior de la cascada: queda
   "invalid at computed-value time" y computa al valor INICIAL. Es decir `padding: 0` y
   `gap: normal`, no "se queda como estaba". Medido sustituyendo `svh` por una unidad
   desconocida: ~268px de aire evaporados y el texto pegado a los bordes. Con el
   `@supports` delante, dentro de este bloque `svh` está garantizada y el problema no
   existe -- por eso aquí sí es seguro usar `var()` en los topes.

   Cada medida es una rampa continua `clamp(suelo, a·svh − b, techo)` anclada en dos
   móviles REALES: el mínimo en el viewport pequeño del iPhone SE (553px) y el máximo en
   el del Pro Max (795px). Entre medias interpola; por encima satura en el token de
   siempre. Por eso un móvil nuevo de alto imprevisto no obliga a tocar ningún número.

   Y la condición es de ANCHO, no de alto, por lo mismo que se explica arriba: una media
   query de `height` se juzga contra el viewport GRANDE, que en móvil es justo el que no
   importa. `svh` dentro del VALOR sí ve el viewport pequeño. Todos los móviles de la
   matriz miden 430px de ancho o menos. */
@supports (height: 1svh) {
  @media (max-width: 480px) {
    .cabecera {
      padding-top: clamp(.5rem, calc(6.6svh - 24.6px), var(--espacio-3));
      padding-bottom: clamp(.375rem, calc(2.9svh - 7px), var(--espacio-2));
    }
    .pie {
      padding-top: clamp(.625rem, calc(7.4svh - 31px), var(--espacio-3));
      padding-bottom: max(clamp(.625rem, calc(7.4svh - 31px), var(--espacio-3)),
                          env(safe-area-inset-bottom));
    }
    /* El pie es el mayor lomo del cromo (98px de 553 en el SE, un 18 %) y su texto está
       protegido por CLAUDE.md §4 y §5 -- "siempre orientativo" y "no vendemos tus datos"
       son promesas del producto, no relleno. Así que no se acorta ni un carácter: se
       reflúa. `em` y no un valor unitario para que el interlineado siga escalando con el
       tamaño de letra del usuario. */
    .pie-interior { line-height: clamp(1.4em, 2.7svh, 1.6em); }
    /* Los dos gaps del héroe bajan JUNTOS y salen de la MISMA expresión: el de `.gancho`
       es el hueco sobre el botón y el de `.gancho-accion` el de debajo, y compartir
       expresión es lo que hace imposible desincronizarlos. No se toca `width: fit-content`
       ni el estirado de los hijos: el botón sigue midiendo lo que la promesa más larga,
       por construcción y sin ninguna medida clavada a mano. */
    .gancho, .gancho-accion { gap: clamp(.625rem, calc(6.6svh - 26.6px), var(--espacio-3)); }
    /* El hueco ENTRE promesas tiene su propia rampa, mucho más corta: es interlineado de
       lista, no separación de bloques. */
    .gancho-claves { gap: clamp(.22rem, calc(.87svh - 1.3px), .35rem); }
    /* En la portada el aire lo pone el centrado, así que este relleno no tiene que
       pagarlo otra vez. Gana a la regla de arriba por especificidad (0,2,0 contra 0,1,0),
       no por orden. */
    .principal:has(> .gancho) { padding-block: clamp(.5rem, 1.2svh, var(--espacio-3)); }
  }
}

@media (min-width: 720px) {
  .cabecera-interior { flex-wrap: nowrap; }
}

/* ---------- 13. Repetidor de hijos (P4) ---------- */
.fila-hijo {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "etiqueta etiqueta" "select quitar";
  gap: .4rem .6rem;
  align-items: center;
  margin-bottom: .7rem;
}
.etiqueta-hijo {
  grid-area: etiqueta;
  font-size: .85rem;
  font-weight: 700;
  color: var(--tinta);
}
.fila-hijo .select { grid-area: select; }
.boton-quitar {
  grid-area: quitar;
  background: none;
  border: 1.5px solid var(--borde);
  border-radius: var(--radio-chico);
  color: var(--neutro);
  padding: .5rem .8rem;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--transicion), color var(--transicion);
}
.boton-quitar:hover { border-color: var(--neutro); color: var(--tinta); }
.boton-anadir:disabled { opacity: 1; color: var(--neutro); border-style: dashed; cursor: default; }

.checklist-nota {
  margin-top: .8rem;
  font-size: .84rem;
  color: var(--neutro);
  font-style: italic;
}

/* ---------- 12. Aclaraciones del informe ---------- */
/* "¿Qué es este dinero?" — la duda más frecuente: deducción no es devolución.
   Plegado para no competir con el importe, pero presente justo debajo de él. */
.informe-que-es {
  margin-top: var(--espacio-2);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
}
.informe-que-es > summary {
  cursor: pointer;
  padding: .7rem .95rem;
  font-weight: 600;
  font-size: .92rem;
  color: var(--pino);
  list-style: none;
}
.informe-que-es > summary::-webkit-details-marker { display: none; }
.informe-que-es > summary::before {
  content: "?";
  display: inline-grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  margin-right: .5rem;
  border-radius: 50%;
  background: var(--exito-suave);
  color: var(--pino);
  font-size: .78rem;
  font-weight: 700;
  vertical-align: -.15em;
}
.informe-que-es[open] > summary { border-bottom: 1px solid var(--borde); }
.informe-que-es-cuerpo { padding: .85rem .95rem; display: grid; gap: .6rem; }
.informe-que-es-cuerpo p { font-size: .9rem; }
.informe-que-es-nota { color: var(--neutro); }

/* Techo de cuota: la deducción resta de la cuota autonómica y con renta baja puede
   no haber cuota suficiente. Se avisa; no se calcula (no pedimos base liquidable). */
/* `display: flex` con el texto suelto dentro REPARTE cada trozo en su propia columna: el
   icono, el "Es probable que", el <strong> y el resto se convertian en cuatro items de flex
   y el aviso se leia en cuatro columnas estrechas. Se veia solo en movil, donde no hay ancho
   para disimularlo. El icono y el texto van ahora en DOS envoltorios (`<span>`), que son los
   dos unicos items que este flex debe tener. */
.informe-aviso-cuota {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  margin-top: var(--espacio-2);
  padding: .85rem 1rem;
  border-radius: var(--radio-chico);
  background: var(--posible-suave);
  color: var(--terracota-texto);
  font-size: .88rem;
}
.informe-aviso-cuota strong { color: var(--terracota-texto); }

/* `.reclamo-aumentar` (el ancla verde "¿quieres ver qué más premiaba la norma?",
   pegada al hero) se retiró en la Fase "cierra-el-posible": /recomendaciones vuelve a
   ser una página propia y su acceso es el segundo botón de `.navegacion-informe`
   (sección 15) -- mismo verde pino, mismo criterio de "no competir con el terracota
   de la gestoría" que ya justificaba este elemento, solo que ahora documentado una
   vez en `.boton-primario` en vez de aquí. */

/* La nota de las casillas [0500]/[0510] se retiró de /deducciones junto con su
   marcado (decisión del usuario): era el párrafo más largo de la página y competía
   con el desglose. El dato sigue en el manual de la AEAT, no en pantalla. */

/* ---------- 14. Oportunidades (Fase 1f; sección fusionada en /resultado en 1g) ---------- */
/* Sección "esto todavía no lo has hecho": deducciones que el usuario TODAVÍA no
   tiene, no deducciones ya ganadas. Título dinámico según si el ejercicio del
   ruleset sigue abierto (`ResultadoVista.ejercicio_abierto`, Fase 1g paso 7):
   "Todavía puedes aumentar esto" o "Lo que te habría sumado en <ejercicio>" -- ver
   `resultado.html`. Deliberadamente sin chip de estado ni color de alarma: el
   titular es una ACCIÓN, no un resultado, así que el acento es --pino (institucional,
   el mismo tono que usan los bloques informativos de P5), nunca --terracota (reservado
   para avisos y el CTA del lead). */
.informe-oportunidades {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-3);
}
.grupo-oportunidades {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-2);
}
/* Titular de cada grupo (comunidad / estatales / aportaciones). Pesa como un titular
   de verdad, igual que "Esto podría aplicarte", porque separa tres cosas que NO son
   intercambiables: consumen bolsas distintas y el margen de cuota solo vale para una.
   Se queda un escalón por debajo de `.informe-seccion-titulo` a propósito: es un h3
   dentro de su h2, y empatarlos borraría la jerarquía en vez de reforzarla. */
.grupo-oportunidades-titulo {
  font-size: clamp(1.05rem, 2.6vw, 1.25rem);
  font-weight: 700;
  margin-top: var(--espacio-4);
  margin-bottom: var(--espacio-1);
}
.grupo-oportunidades:first-of-type .grupo-oportunidades-titulo { margin-top: var(--espacio-2); }
.grupo-oportunidades-nota {
  font-size: .85rem;
  color: var(--neutro);
  max-width: 52ch;
}
/* Descargo del grupo de reducciones (planes de pensiones): "informamos, no
   aconsejamos" es la línea más delicada de toda la tanda (CLAUDE.md §4), así que se
   marca con el mismo tono de aviso que el resto del informe, no como nota neutra.
   --terracota-texto sobre --papel: contraste ~5,4:1, cumple AA para texto normal. */
.grupo-oportunidades-descargo {
  color: var(--terracota-texto);
  font-weight: 600;
}
/* El margen de cuota es COMPARTIDO por el grupo (nunca por ficha): mismo par de color
   que `.informe-aviso-cuota` (terracota-texto sobre posible-suave, ~4,83:1 — ver su
   comentario), reutilizado a propósito para que "cuidado con el techo de tu cuota" se
   lea siempre igual en todo el informe. */
.grupo-oportunidades-margen {
  background: var(--posible-suave);
  color: var(--terracota-texto);
  border-radius: var(--radio-chico);
  padding: .65rem .85rem;
  font-size: .85rem;
}

.oportunidad {
  background: var(--superficie);
  border: 1.5px dashed var(--pino-claro);
  border-radius: var(--radio);
  padding: var(--espacio-3);
}
/* Titular corto en negrita (Fase "cierra-el-posible", `o.titulo`): `.ficha-nombre` se
   reutiliza tal cual de `_ficha.html` (tamaño, marcador `▸`, rotación al abrir), pero
   ahí el nombre de una deducción real NO va en negrita -- aquí sí tiene que serlo
   (decisión del usuario), así que el peso se añade aquí, scoped a `.oportunidad`, en
   vez de tocar `.ficha-nombre` global y negritar también las fichas de /deducciones. */
.oportunidad .ficha-nombre { font-weight: 700; }
/* Estado de realización (Fase 1g): "no nos lo has dicho" / "nos dijiste que no".
   Reutiliza --neutro, ya verificado sobre --papel en `.grupo-oportunidades-nota`
   (5,47:1, AA) -- aquí va sobre --superficie, que es más clara que --papel, así que
   el contraste solo puede ser igual o mayor: ningún color nuevo que recalcular. */
.oportunidad-estado {
  font-size: .8rem;
  color: var(--neutro);
  margin-bottom: .2rem;
}
.oportunidad-accion {
  font-family: var(--serif);
  font-size: 1.05rem;
  margin-bottom: .3rem;
}
/* --terracota-oscuro sobre --superficie: contraste ~5,0:1, cumple AA. Mismo tono que
   `.importe-posible` y `.ficha-rango` en estado "posible": una oportunidad es, por
   definición, siempre condicional — nunca la cifra "segura" en verde pino. */
.oportunidad-importe {
  font-family: var(--serif);
  font-size: 1.25rem;
  color: var(--terracota-oscuro);
  margin-bottom: .6rem;
}
/* Dentro de la cabecera (`.ficha-cabecera`, el `<summary>`) el importe es el segundo
   hijo de una fila flex -- el `margin-bottom` de arriba (pensado para cuando vivía
   suelto, debajo del titular) sobra ahí y desalinearía la fila. */
.ficha-cabecera .oportunidad-importe { margin-bottom: 0; }
/* El importe se queda SIEMPRE arriba a la derecha, aunque el titular ocupe dos líneas.
   Con el `flex-wrap: wrap` heredado de `.ficha-cabecera`, un titular algo más largo
   empujaba el importe a una fila propia: unas tarjetas lo enseñaban al lado y otras
   debajo, y una lista que existe para comparar de un vistazo se leía irregular.
   Solo se acota aquí y no en `.ficha-cabecera`: en /deducciones los nombres son frases
   legales largas y ahí el ajuste SÍ hace falta para que quepan. */
.oportunidad > .ficha-cabecera {
  flex-wrap: nowrap;
  align-items: flex-start;
}
/* `nowrap` mientras quepa: con la letra al 200 % este importe empujaba la cabecera 178 px
   fuera de la pantalla en /deducciones. `min-width: 0` es lo que de verdad lo arregla --
   un item flex no baja de su contenido salvo que se le diga--, y el `nowrap` se queda
   como preferencia para el tamaño normal, que es cuando de verdad importa que la cifra no
   se parta. */
.oportunidad > .ficha-cabecera .oportunidad-importe {
  flex: none;
  white-space: nowrap;
  min-width: 0;
}
.oportunidad > .ficha-cabecera { min-width: 0; flex-wrap: wrap; }
.oportunidad > .ficha-cabecera .ficha-nombre { min-width: 0; }
.oportunidad-descartada { border-style: dotted; }

/* El mecanismo detrás del "hasta ~X €" (porcentaje + base + tope, o el descargo de
   "esto es impuesto ahorrado, no la aportación"). Mismo tamaño y color que
   `.grupo-oportunidades-nota`: es información de apoyo, no una segunda cifra
   protagonista -- la única cifra que compite por la atención en esta tarjeta es
   `.oportunidad-importe`, arriba. Ningún color nuevo: reutiliza --neutro y --tinta. */
.oportunidad-mecanismo {
  margin-top: .3rem;
  margin-bottom: .6rem;
  font-size: .85rem;
  color: var(--neutro);
}
.oportunidad-mecanismo p { margin-bottom: .3rem; }
.oportunidad-mecanismo p:last-child { margin-bottom: 0; }
.oportunidad-mecanismo-lista {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  margin: .2rem 0 .3rem 1rem;
}
.oportunidad-mecanismo-lista li {
  position: relative;
  padding-left: .9rem;
}
.oportunidad-mecanismo-lista li::before {
  content: "–";
  position: absolute;
  left: 0;
}

/* ---------- 15. Navegación entre las páginas del informe (Fase "cierra-el-posible") --
   El hub (/resultado) se reduce a hero + estos tres botones; cada sub-página
   (/cta, /deducciones, /recomendaciones) enseña los OTROS dos más el enlace de vuelta
   -- contrato completo en `_navegacion_informe.html`. Reemplaza tanto a `.informe-cta`
   (la caja terracota del CTA) como a `.reclamo-aumentar` (el ancla verde a
   "oportunidades"): los dos elementos vivían sueltos, cada uno con su propio color y
   forma; ahora son botones normales del sistema (`.boton-acento`/`.boton-primario`),
   así que esta sección no calcula ningún color nuevo, solo el layout.

   En columna y a ancho completo (no en fila, como `.acciones-paso`): los tres deben
   pesar lo mismo entre sí en tamaño -- la jerarquía la marca solo el color (terracota
   = conversión del negocio, CLAUDE.md §3; verde = información), nunca el tamaño. */
.navegacion-informe {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-2);
}
.navegacion-informe .boton { width: 100%; margin-left: 0; }

/* Enlace de vuelta al resumen, en cada sub-página: discreto a propósito -- no es una
   acción nueva, es "ya he visto esto, llévame a donde estaba" -- así que no lleva ni
   relleno ni borde de botón. --neutro sobre --papel: 5,47:1 (calculado), ya
   documentado más arriba en este archivo para el mismo par de colores -- ningún
   valor nuevo que medir. */
.enlace-volver-resumen {
  display: block;
  text-align: center;
  margin-top: var(--espacio-1);
  color: var(--neutro);
  font-size: .88rem;
  text-decoration: none;
}
.enlace-volver-resumen:hover { color: var(--pino); text-decoration: underline; }

/* ------- Letra grande / zoom: va AL FINAL del archivo a propósito -------
   Este bloque tiene que ganar al de `max-width: 480px`, y su especificidad es la misma
   (una clase), así que decide el ORDEN DE FUENTE. Medido: colocado antes, `.ficha`
   seguía con los 28 px del bloque de móvil en vez de los 12 de aquí. Es la misma trampa
   que ya documentaron las rampas de `svh` unas líneas más arriba.

   Y una corrección de premisa que conviene no volver a cometer: en una media query, `rem`
   se resuelve contra el tamaño de letra INICIAL del documento, NO contra el que fije el
   CSS del autor. Así que emular el 200 % con `html { font-size: 200% }` (o desde la
   consola) NO dispara este bloque y no sirve para probarlo -- de hecho la deuda original
   se había medido así, con el instrumento equivocado. Lo que SÍ lo dispara es lo que hace
   un usuario de verdad:
     - subir el tamaño de letra por defecto del navegador (cambia el valor inicial), o
     - el zoom de página, que reduce el viewport en px CSS (375 al 200 % son 188).
   Para comprobarlo desde aquí: redimensionar el viewport a ~188 px, que es el
   equivalente exacto del segundo caso. */
/* «Contesta estas N preguntas para cerrar el rango» (web/cerrar_rango.py).
   Va pegada a los importes, así que tiene que leerse como una CONTINUACIÓN de la cifra
   posible y no como un aviso nuevo: de ahí el fondo --posible-suave y el texto en
   --terracota-texto, los mismos que ya usa el bloque del importe posible. Un color de alarma
   habría convertido "aquí hay dinero que puedes desbloquear" en "algo va mal".

   Contrastes CALCULADOS (no estimados; la primera redacción de este comentario los estimó y
   los dos números estaban mal):
     · titulo   --terracota-texto sobre --posible-suave .... 4,82:1  (AA texto normal)
     · euros    --terracota-texto sobre --superficie ....... 5,75:1
     · petición --tinta sobre --superficie ................ 17,16:1
     · aro foco --pino sobre --superficie ................. 9,83:1  (WCAG 1.4.11 pide 3:1)
   Lo que NO cumple 3:1 es el borde de la tarjeta blanca sobre el fondo teñido (1,19:1): la fila
   se distingue por forma, no por contraste. Es admisible porque el elemento interactivo es el
   enlace y su texto y su aro de foco sí contrastan de sobra; si algún día la tarjeta tuviera
   que ser identificable por sí sola, haría falta un borde en --borde-control. */
.cerrar-rango {
  margin-top: var(--espacio-2);
  padding: var(--espacio-2);
  background: var(--posible-suave);
  border-radius: var(--radio);
}
.cerrar-rango-titulo {
  font-size: .9rem;
  font-weight: 600;
  color: var(--terracota-texto);
  margin-bottom: var(--espacio-1);
}
.cerrar-rango-lista { list-style: none; display: grid; gap: 6px; }
/* El enlace ocupa toda la fila y llega a 48 px de alto, igual que el resto de los controles
   del flujo: en móvil esto es lo primero que el pulgar encuentra bajo la cifra. */
.cerrar-rango-enlace {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 10px;
  min-height: 48px;
  padding: 8px 10px;
  border-radius: var(--radio-chico);
  background: var(--superficie);
  text-decoration: none;
  color: inherit;
}
.cerrar-rango-enlace:hover { background: var(--papel); }
.cerrar-rango-enlace:focus-visible {
  outline: 2px solid var(--pino);
  outline-offset: 2px;
}
.cerrar-rango-peticion { font-size: .9rem; color: var(--tinta); }
/* La cifra en negrita y con la fuente de los importes: es la razón por la que alguien pulsa. */
.cerrar-rango-euros {
  font-family: var(--serif);
  font-size: .9rem;
  color: var(--terracota-texto);
  white-space: nowrap;
}

/* La PORTERA de un grupo de preguntas de P5 (`_portera.html`). Separada del grupo que abre
   con una linea, para que se lea como lo que es --la pregunta que decide si lo de abajo te
   toca-- y no como la primera de una lista. Sin color propio: no es un aviso, es una pregunta
   mas, solo que la que manda. */
.portera { padding-bottom: var(--espacio-2); border-bottom: 1px solid var(--borde); }
.portera > legend { font-weight: 600; }

@media (max-width: 18rem) {
  .ficha-cabecera-estado {
    /* `100%` fuerza la fila propia; `wrap` deja que insignia e importe se separen. */
    flex: 1 1 100%;
    flex-wrap: wrap;
    /* A tamaño normal van a la derecha (`justify-content: space-between` del padre);
       apilados, alinearlos a la izquierda los pone en la columna del nombre. */
    justify-content: flex-start;
  }
  /* Los sangrados pasan a `px`: aquí NO deben crecer con la letra, que es justo lo que
     los rompía. Son hueco, no contenido. */
  .principal { padding-left: 10px; padding-right: 10px; }
  .ficha { padding: 12px; }
  .checklist-item { padding-left: 14px; }
  /* Último recurso para una palabra que siga sin caber (un nombre legal largo, una URL).
     Va en `body` y no en una lista de clases: se probó enumerándolas y siempre quedaba
     alguna fuera --primero `.checklist-item`, luego `.razon`, luego
     `.informe-aviso-cuota`--, porque el informe tiene texto legal en una docena de sitios
     distintos. `overflow-wrap` se hereda, así que una declaración cubre todos.
     `anywhere` y no `break-word`: solo `anywhere` reduce también el ancho MIN-CONTENT, que
     es lo que deja encoger de verdad a las cajas flex y grid que envuelven ese texto.
     A este ancho, partir una palabra es mejor que perder el contenido fuera de pantalla,
     que es lo que WCAG 1.4.10 prohíbe. */
  body { overflow-wrap: anywhere; }
}

/* ---------- Panel de la beta cerrada (`admin.html`) ----------
   Cada fila lleva su propio formulario de "quitar", porque el token anti-CSRF tiene que ir
   en cada uno. Un `<form>` es de bloque y rompería la línea, así que aquí se pone en línea:
   es la única razón de esta regla. */
.formulario-en-linea { display: inline; }

/* ---------- Paso 5: la pregunta como unidad, y el foco sobre papel ----------
   Tres problemas medidos, en este orden:

   1. Habia MAS GLOSA QUE PREGUNTA: 51 % del texto del paso era explicacion. La glosa se
      pliega ahora en `.glosa` (ver `_glosa.html`); aqui solo se le da forma.
   2. Las preguntas no se distinguian unas de otras. Subir el espaciado a 1,75rem no basto,
      y no era problema de espacio sino de JERARQUIA: el enunciado (700, .95rem) y la ayuda
      (.85rem gris) se parecian tanto que, con todo desplegado, se leia como un parrafo
      corrido. Ahora cada pregunta tiene contorno propio.
   3. Faltaba decir CUAL es la que importa ahora.

   El punto 3 se resolvio con "foco sobre papel" y no con cristal esmerilado
   (glassmorphism), que era la idea de partida. La razon esta medida en este mismo fichero,
   unas secciones mas arriba, en `.ficha-descartada`: atenuar un contenedor CON TEXTO tira
   su contraste por debajo de AA (4,83:1 -> 3,07:1 alli). `backdrop-filter: blur()` es esa
   misma trampa y peor, porque emborrona los glifos. Aqui lo que retrocede es el BORDE y la
   SOMBRA de las demas; el texto de todas se queda intacto y legible. */
.pregunta {
  /* Marco de referencia del icono de la glosa, que se ancla arriba a la derecha (ver
     `.glosa > summary`). Y el relleno derecho le reserva sitio: sin el, una pregunta larga
     se metia por debajo del icono. */
  position: relative;
  padding: var(--espacio-2);
  padding-right: 2.9rem;
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  /* La transicion es de sombra y borde, nunca de color de texto: un texto que cambia de
     color al pasar el raton es justo lo que no se quiere aqui. */
  transition: box-shadow var(--transicion), border-color var(--transicion);
}

/* La activa se levanta. `:focus-within` ademas de `:hover` no es un extra: en un movil no
   hay hover, y el movil es donde la pantalla va mas apretada. Con `focus-within` el efecto
   existe tambien para el dedo y para quien navega con teclado. */
.pregunta:hover,
.pregunta:focus-within {
  box-shadow: var(--sombra);
  border-color: var(--borde-control);
}

/* Y las demas retroceden, pero SOLO su borde: dentro de un grupo con una pregunta activa,
   las hermanas pierden el contorno. Su texto no se toca. */
.grupo-preguntas:hover .pregunta:not(:hover):not(:focus-within),
.bloque-afinar-cuerpo:hover .pregunta:not(:hover):not(:focus-within),
.bloque-cuerpo:hover .pregunta:not(:hover):not(:focus-within) {
  border-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .pregunta { transition: none; }
}

/* Los tres sitios donde arranca un enunciado, y por tanto los tres `top` del icono.
   `-.3rem` (el valor base) vale para el <fieldset class="pregunta">, cuyo <legend> monta
   sobre el borde. Los otros dos empiezan mas abajo: */
.pregunta > .glosa > summary { top: .5rem; }              /* <div class="campo">: la <label> */
.pregunta-gasto > fieldset > .glosa > summary { top: .55rem; }  /* el <fieldset> de dentro */
/* Y el <fieldset>, que NO se ajusta con un `top` fijo. Un elemento absoluto se coloca
   respecto a la caja de relleno de su ancestro, y la de un fieldset EMPIEZA POR DEBAJO DEL
   <legend> (el legend se pinta sobre el borde, fuera de ella). O sea que `top: 0` no es la
   linea del enunciado: es la linea de despues. Y como el legend puede ocupar dos lineas, no
   hay numero fijo que valga -- con `top: -.3rem` el icono caia 27 px por debajo del
   enunciado en el selector, que es justo el "flotando en medio" que se veia.

   `bottom: 100%` lo resuelve solo: pega el BORDE INFERIOR del icono al techo de esa caja,
   o sea al final del enunciado, mida lo que mida. Los 2 px lo centran en la linea. */
fieldset.pregunta > .glosa > summary { top: auto; bottom: calc(100% - 2px); }

/* La glosa plegada, tras un icono de informacion. Discreta a proposito: tiene que estar
   disponible sin competir con el enunciado, que es lo unico que la mayoria necesita leer.

   EL COLOR NO ES EL DEL ICONO QUE PIDIO EL USUARIO (circulo negro solido), y la razon es de
   cantidad: hay ~20 glosas en el paso 5, asi que veinte discos negros bajando por la pagina
   pesarian mas que el parrafo que acaban de sustituir -- justo lo contrario de para lo que
   se pusieron. Se queda la FORMA (disco solido con la "i" calada) y se baja el tono a
   `--neutro`, que sube a `--pino` al posar el raton o al abrirse. Asi se ve cuando lo
   buscas y no grita cuando no. */
.glosa { font-size: .82rem; color: var(--neutro); }
.glosa > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  /* AL FINAL DE LA PREGUNTA, no en una linea propia debajo (peticion del usuario). Se ancla
     arriba a la derecha del recuadro de la pregunta y no se cuelga del final del texto,
     porque el texto puede ocupar dos lineas: pegado a la ultima palabra bailaria de sitio
     segun lo larga que fuera la pregunta, y ahi arriba esta siempre donde uno lo busca.

     `position: absolute` lo saca del flujo, asi que con la glosa cerrada el <details> no
     ocupa ni un pixel de alto y no abre un hueco entre la pregunta y sus opciones. */
  position: absolute;
  /* En LA LINEA DEL ENUNCIADO, a su derecha. Se alinea con el, no se pega a su ultima
     palabra: los enunciados miden entre 336 y 523 px (medido), asi que colgado del texto el
     icono bailaria de sitio en cada pregunta. Hay ~20 en el paso 5; que esten SIEMPRE en el
     mismo punto es lo que los vuelve un habito en vez de una busqueda.

     El `top` lo ajusta cada tipo de pregunta ahi abajo, porque el enunciado no arranca a la
     misma altura: en un <fieldset> el <legend> se pinta SOBRE el borde superior (fuera de la
     caja de relleno), y en un <div class="campo"> la <label> empieza ya dentro. Los valores
     salen de medir en el navegador, no de estimar. */
  top: -.3rem;
  right: calc(var(--espacio-2) - .35rem);
  /* Objetivo tactil: el disco mide 18px, que con el pulgar no se acierta. El relleno lo
     lleva a ~34px sin ocupar sitio visible. */
  padding: .5rem;
  color: var(--neutro);
  transition: color var(--transicion);
}
.glosa > summary::-webkit-details-marker { display: none; }
.glosa > summary:hover,
.glosa[open] > summary { color: var(--pino); }
.glosa > summary:focus-visible {
  outline: 2px solid var(--pino);
  outline-offset: 2px;
  border-radius: 999px;
}

.glosa-icono {
  width: 1.15rem;
  height: 1.15rem;
  fill: currentColor;
}
/* La "i" se cala en el disco pintandola del color del papel de la tarjeta. `--superficie`
   y no blanco a pelo: si algun dia la tarjeta cambia de fondo, el calado lo sigue. */
.glosa-icono-punto,
.glosa-icono-asta { fill: var(--superficie); }

.glosa > p {
  /* A plena anchura y debajo de la pregunta. El sangrado que tenia colgaba el texto del
     icono cuando este vivia a la izquierda; con el icono a la derecha, sangrar por la
     izquierda ya no apunta a nada. */
  margin: .35rem 0 .1rem;
}

/* ---------- Relieve de las tarjetas del informe (`relieve.js`) ----------
   El script escribe un `transform` en linea; aqui solo se prepara el terreno y se define
   como VUELVE a su sitio al salir el raton. La vuelta se anima; la ida no, porque la ida ya
   la marca el dedo -- una transicion durante el movimiento haria que la tarjeta persiguiera
   al cursor con retraso.

   `will-change` NO se pone: obligaria al navegador a mantener una capa por cada ficha del
   informe (pueden ser treinta) todo el rato, a cambio de nada en las 29 que no se estan
   tocando. */
.ficha[data-relieve],
.oportunidad[data-relieve] {
  transition: transform 260ms cubic-bezier(.2, .7, .2, 1),
              box-shadow var(--transicion);
}
.ficha[data-relieve]:hover,
.oportunidad[data-relieve]:hover { box-shadow: var(--sombra-alta); }

/* Quien pide menos movimiento no recibe ni la transicion: el script ni siquiera se instala
   (ver sus guardas), pero si la regla de arriba quedara viva, plegar una ficha animaria un
   `transform` que nadie ha pedido. */
@media (prefers-reduced-motion: reduce) {
  .ficha[data-relieve],
  .oportunidad[data-relieve] { transition: box-shadow var(--transicion); }
}

/* El icono de información LATE, muy despacio, para que se vea que está ahí.

   Peticion del usuario: el icono es discreto a proposito --hay ~20 en el paso 5 y no
   pueden gritar-- pero tan discreto que se pasa por alto, y entonces la glosa que hay
   detras no la abre nadie.

   El latido es de OPACIDAD y de escala, no de color: cambiar el color lo haria competir
   con el enunciado de su pregunta, que es lo unico que la mayoria necesita leer.

   4 s por ciclo, y no 1 o 2: a esa velocidad se percibe como "respira" y no como "parpadea".
   Un parpadeo rapido en veinte sitios de la misma pagina es ruido, no una pista.

   Y PARA de latir en cuanto sirve de algo: al pasar el raton por la pregunta, al enfocarla
   o al abrir la propia glosa. Un elemento que sigue moviendose cuando ya lo has encontrado
   solo estorba. */
@keyframes glosa-latido {
  0%, 100% { opacity: .55; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.12); }
}

.glosa > summary .glosa-icono {
  animation: glosa-latido 4s ease-in-out infinite;
  /* El origen en el centro: sin esto la escala tira del icono hacia abajo-derecha y el
     latido se lee como un temblor. */
  transform-origin: center;
}

/* Cuando ya lo has encontrado, se queda quieto. */
.pregunta:hover .glosa > summary .glosa-icono,
.pregunta:focus-within .glosa > summary .glosa-icono,
.glosa[open] > summary .glosa-icono {
  animation: none;
  opacity: 1;
}

/* Quien pide menos movimiento no recibe latido: se queda visible y quieto, que es el mismo
   trato que se le da a la pista de despliegue y a la del swipe. */
@media (prefers-reduced-motion: reduce) {
  .glosa > summary .glosa-icono { animation: none; opacity: 1; }
}

/* ---------- El cuerpo de una pantalla de tema (`p5_bloque.html`) ----------
   FALTABA ENTERO, y se noto enseguida: al partir el paso 5 en pantallas, el contenedor de
   las preguntas paso a llamarse `.bloque-cuerpo` y nadie escribio sus estilos. Sin `gap`,
   las preguntas salian pegadas unas a otras -- justo el "todo aglomerado" del que venia
   este rediseño, reaparecido en las pantallas nuevas.

   Mismo ritmo que tenia el acordeon: `--espacio-pregunta` separa preguntas, y dentro de una
   pregunta mandan sus propios huecos cortos. */
.bloque-cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-pregunta);
}

/* La frase que dice de que va el tema. Antes vivia bajo el titulo del bloque plegable; en
   una pantalla propia va bajo el <h1>, y se separa del primer grupo de preguntas. */
.bloque-cuerpo > .bloque-afinar-motivo { margin-bottom: calc(var(--espacio-2) * -0.5); }

/* ---------- La cortinilla de entrada (`_intro.html` + `intro.js`) ----------

   COREOGRAFIA, 5 s de punta a punta. Los numeros estan repartidos entre este fichero (las
   fases) y `intro.js` (el instante de salida), asi que conviene tenerlos juntos a la vista:

     0,20 - 1,70 s   la casa se dibuja
     1,52 - 2,92 s   las seis monedas caen, una cada 180 ms
     2,98 - 3,68 s   aparece "Deductor"
     3,26 - 3,96 s   aparece "Encuentra tus ahorros"
     4,10 - 6,10 s   la capa se difumina de abajo arriba (la dispara `intro.js`)

   El barrido dura 2 s el solo, casi la mitad del total: es deliberado. La curva se deja
   como estaba --simetrica-- para no cambiarle el caracter al efecto, que ya estaba bien.

   Si se cambia el instante de salida hay que cambiarlo en `intro.js`, no aqui.
   El logotipo se traza, las monedas se apilan y la capa se disuelve dejando ver la portada,
   que ya esta pintada debajo. La animacion cuenta lo que dice el eslogan --los ahorros se
   acumulan dentro de casa-- en vez de decorar por decorar.

   `display: none` DE SALIDA, y no es un detalle de estilo: es la guarda que impide que un
   fallo de JavaScript deje la pantalla tapada sin remedio. Solo `.intro-va`, que pone
   `intro.js`, la descubre. Ver la cabecera de ese fichero y
   `test_sin_js_la_capa_no_se_pinta`. */
.intro { display: none; }

.intro-va .intro {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
  /* Por encima de todo, incluida la barra de acciones pegajosa del paso 5 (z-index 20). */
  z-index: 100;
  background: var(--papel);
  background-image: var(--grano);
  /* La portada no se puede tocar mientras la capa esta puesta, pero SI se puede pulsar la
     capa para saltarla: el oyente del clic vive en ella. */
  cursor: pointer;
}

/* LA SALIDA LA MANDA `intro.js`, que añade esta clase -- al vencer su plazo o cuando el
   usuario pulsa. Antes estaba definida DOS veces: aqui con un retardo, y otra vez sin el
   para el caso de saltarla. Se midio y no parpadeaba (el navegador actualiza la animacion
   en curso en vez de reiniciarla, porque el `animation-name` no cambia), pero dos sitios
   decidiendo cuando se va la cortinilla es una trampa esperando a que alguien cambie uno
   solo. Ahora el CUANDO vive en el JS y el COMO aqui. */
.intro-va .intro.intro-fin {
  /* Curva casi recta y simetrica, no la de acelerar-y-frenar que llevaba antes. Un
     BARRIDO se juzga por su avance, no por su opacidad: con `cubic-bezier(.4, 0, .3, 1)`
     la mascara iba por el 71 % a mitad de animacion --medido-- y la capa ya estaba fuera
     cuando deberia ir por la mitad de la pantalla. Los ultimos 450 ms no barrian nada. */
  animation: intro-salida 2000ms cubic-bezier(.35, 0, .65, 1) both;
  /* EL DIFUMINADO DE ABAJO ARRIBA. No es un fundido uniforme: una mascara de degradado con
     el borde suave sube por la capa, asi que la portada se va descubriendo desde el pie.

     La mascara mide TRES VECES el alto de la capa, y de ahi salen los porcentajes. Con
     `mask-position: 0% 0%` la capa enseña el tercio de ARRIBA de la mascara --que es negro
     entero, o sea opaco-- y con `100%` enseña el de ABAJO, transparente entero. El tercio
     del medio es la rampa, y es lo que barre la capa al deslizarse entre una posicion y
     otra. Con una mascara de solo 100% o 200% no hay forma de que los dos extremos sean
     uniformes: siempre queda medio degradado a la vista al empezar o al acabar.

     EL ANCHO DE LA RAMPA (los stops 47/53) es lo que decide si esto parece un difuminado o
     visión doble. La rampa mide el 6 % de una mascara de 300 %, o sea el 18 % del alto de
     la pantalla. Con 42/58 --el primer intento-- medía casi la mitad de la pantalla, y en
     esa franja se leian a la vez el logotipo y el titular de la portada, uno encima de
     otro. Cuanto mas estrecha, mas limpio el barrido; demasiado, y deja de difuminar.

     `no-repeat` NO es opcional: por defecto la imagen de mascara se repite, y con 300 % de
     alto se apilarian tres copias del degradado -- tres bandas en vez de un barrido.

     Los prefijos `-webkit-` son para Safari, que todavia no acepta `mask-*` a secas. Si un
     navegador no entendiera ninguna de las dos, la capa no se difumina y desaparece de
     golpe: feo, pero nadie se queda encerrado -- el plazo de respaldo de `intro.js` la
     retira igual. */
  -webkit-mask-image: linear-gradient(to top, transparent 0%, transparent 47%, #000 53%, #000 100%);
          mask-image: linear-gradient(to top, transparent 0%, transparent 47%, #000 53%, #000 100%);
  -webkit-mask-size: 100% 300%;
          mask-size: 100% 300%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

.intro-centro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  padding: var(--espacio-2);
}

/* ----- El logotipo ----- */
.intro-marca {
  fill: none;
  stroke: var(--terracota);
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Que no se coma la pantalla en un movil estrecho. */
  max-width: min(52vw, 176px);
  height: auto;
}

/* La casa se dibuja sola. `stroke-dasharray` por encima del trazo real (medido con
   `getTotalLength` en el navegador, no estimado) para que no asome ningun trozo suelto al
   principio; el sobrante no se ve porque el hueco mide lo mismo. */
.intro-arco {
  stroke-dasharray: 560;
  stroke-dashoffset: 560;
  animation: intro-trazo 1500ms cubic-bezier(.65, 0, .35, 1) 200ms both;
}

/* Cada moneda cae y se posa. El retardo escalonado va de abajo arriba porque ese es el
   orden del marcado: primero la que sostiene la pila. */
.intro-moneda {
  opacity: 0;
  animation: intro-moneda 500ms cubic-bezier(.2, .9, .3, 1.2) both;
}
.intro-moneda:nth-of-type(1) { animation-delay: 1520ms; }
.intro-moneda:nth-of-type(2) { animation-delay: 1700ms; }
.intro-moneda:nth-of-type(3) { animation-delay: 1880ms; }
.intro-moneda:nth-of-type(4) { animation-delay: 2060ms; }
.intro-moneda:nth-of-type(5) { animation-delay: 2240ms; }
.intro-moneda:nth-of-type(6) { animation-delay: 2420ms; }

/* ----- El texto ----- */
.intro-nombre {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(2.6rem, 11vw, 4.2rem);
  line-height: 1;
  color: var(--pino);
  letter-spacing: .01em;
  margin-top: .5rem;
  animation: intro-texto 700ms cubic-bezier(.2, .7, .3, 1) 2980ms both;
}
.intro-eslogan {
  font-family: var(--sans);
  font-size: clamp(1rem, 3.6vw, 1.35rem);
  color: var(--tinta);
  animation: intro-texto 700ms cubic-bezier(.2, .7, .3, 1) 3260ms both;
}

@keyframes intro-trazo { to { stroke-dashoffset: 0; } }

/* La moneda entra desde arriba y aterriza. El `cubic-bezier` de `.intro-moneda` pasa de 1
   y le da el rebote corto del metal al posarse. */
@keyframes intro-moneda {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes intro-texto {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* La mascara sube y la capa se descubre de abajo arriba. Se anima la POSICION de la
   mascara y no la opacidad: la opacidad se va de golpe en toda la superficie, y lo que se
   pidio es que el difuminado recorra la pantalla.

   Se quito el `scale(1.06)` que llevaba antes: al combinarlo con la mascara, el borde del
   barrido se estiraba a la vez que subia y el efecto se leia como un temblor. */
@keyframes intro-salida {
  from { -webkit-mask-position: 0% 0%;   mask-position: 0% 0%; }
  to   { -webkit-mask-position: 0% 100%; mask-position: 0% 100%; }
}

/* Quien pide menos movimiento no llega hasta aqui --`intro.js` se va antes de marcar el
   documento-- pero la regla se queda como red: si alguien enseñara la capa por otra via,
   no se movera nada. */
@media (prefers-reduced-motion: reduce) {
  .intro-va .intro,
  .intro-va .intro.intro-fin,
  .intro-arco,
  .intro-moneda,
  .intro-nombre,
  .intro-eslogan { animation: none; }
  .intro-arco { stroke-dashoffset: 0; }
  .intro-moneda { opacity: 1; }
}

/* ---------- El directorio de gestorias (`gestorias.html` + `gestorias.js`) ----------
   Solo existe con `DEDUCTOR_GESTORIAS` encendida. Reutiliza los tokens y las clases que ya
   hay --`.boton`, `.entrada-texto`, `.campo-ayuda`-- porque esta pantalla no es un producto
   aparte: es una mas del informe y tiene que leerse como tal. */
.gestorias { max-width: var(--ancho); margin: 0 auto; }

/* El aviso de que esto es informativo y de que no hay acuerdo con los despachos. Va ARRIBA y
   no en letra pequeña al pie: es la separacion entre informar y recomendar (CLAUDE.md §4), y
   un descargo que se lee despues de llamar a una gestoria no ha servido de nada. */
.gestorias-aviso {
  font-size: .9rem;
  color: var(--neutro);
  background: var(--neutro-suave);
  border-radius: var(--radio-chico);
  padding: var(--espacio-2);
  margin-bottom: var(--espacio-3);
}

.gestorias-buscador {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  padding: var(--espacio-2);
  margin-bottom: var(--espacio-3);
}
.gestorias-buscador-fila {
  display: flex;
  gap: .6rem;
  margin-top: .6rem;
}
.gestorias-buscador-fila .entrada-texto { flex: 1; min-width: 0; }

.gestorias-boton-ubicacion {
  margin-top: .7rem;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: .9rem;
}
/* El glifo del alfiler se dibuja con trazo, como el resto de iconos del proyecto. */
.gestorias-boton-ubicacion .boton-glifo { fill: none; stroke: currentColor; stroke-width: 1.8; }

/* `aria-live`, asi que lo que se escriba aqui lo anuncia un lector de pantalla. Por eso los
   mensajes son frases completas y no "0 resultados". */
.gestorias-estado {
  margin-top: .6rem;
  font-size: .88rem;
  color: var(--neutro);
  min-height: 1.2em;      /* reserva el hueco: sin esto la lista da un salto al aparecer */
}

.gestorias-lista { display: flex; flex-direction: column; gap: var(--espacio-2); }

.gestoria {
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--superficie);
  padding: var(--espacio-2);
}
.gestoria-nombre { font-weight: 700; }
/* Gestoria administrativa / asesoria fiscal. Discreta y en mayusculas pequeñas: identifica
   sin competir con el nombre del despacho, que es lo que el usuario busca primero. */
.gestoria-categoria {
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--neutro);
  margin-top: .15rem;
}
/* "(sin verificar)": la ficha no viene de un registro colegial. Se dice en la propia linea
   de la categoria y no al pie, porque es lo que matiza a la categoria misma -- y quien va a
   llamar a un despacho tiene derecho a saber si hemos comprobado su profesion o solo hemos
   leido su rotulo. */
.gestoria-sin-acreditar { text-transform: none; letter-spacing: 0; font-style: italic; }
.gestoria-direccion { font-size: .9rem; color: var(--neutro); margin-top: .2rem; }
/* La distancia en terracota y en negrita: es el dato por el que el usuario ha llegado aqui. */
.gestoria-distancia {
  font-size: .88rem;
  font-weight: 600;
  color: var(--terracota-texto);
  margin-top: .35rem;
}
.gestoria-contacto { margin-top: .5rem; display: flex; gap: var(--espacio-2); font-size: .9rem; }

/* Los dos vacios honestos: "no hay ninguna cerca" y "no hay ninguna en tu comunidad". Se
   pintan como una nota, no como un error: no ha fallado nada, simplemente todavia no hay. */
.gestorias-vacio {
  border: 1px dashed var(--borde-control);
  border-radius: var(--radio-chico);
  padding: var(--espacio-2);
  font-size: .92rem;
  color: var(--neutro);
}

/* Un boton que se lee como enlace. Es <button> y no <a> porque no navega a ningun sitio:
   actua sobre esta misma pantalla, y un enlace sin destino confunde a un lector de pantalla. */
.enlace-como-boton {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--pino);
  text-decoration: underline;
  cursor: pointer;
}

@media (max-width: 480px) {
  /* El boton debajo del campo: a 320 px, en la misma fila, el campo se queda en nada. */
  .gestorias-buscador-fila { flex-direction: column; }
}

/* ---------- La capa de ayudas (`ayudas.html` + `_ayuda.html` + bloque del hub) ----------
   Tarjeta hermana de `.oportunidad`: mismo contenedor que `.ficha`, mismo `<summary>`
   compartido (las reglas conjuntas de despliegue de la seccion 14 ya incluyen `.ayuda`),
   mismos chips por `estado-*`. Lo propio de aqui es el plazo, el bloque de solicitud y
   el gancho del hub. */
.ayuda {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  padding: var(--espacio-3);
  margin-top: var(--espacio-2);
}
/* Nombres cortos en lenguaje de persona, como las oportunidades y a diferencia de las
   frases legales largas de /deducciones: aguantan la negrita sin comerse la tarjeta. */
.ayuda .ficha-nombre { font-weight: 700; }
/* Mismo tono condicional que `.oportunidad-importe`: lo que se cobre depende de la
   resolucion del organismo — nunca la cifra "segura" en pino. */
.ayuda-importe {
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--terracota-oscuro);
  white-space: nowrap;
}
.ayuda-organismo {
  font-size: .8rem;
  color: var(--neutro);
  margin-bottom: .2rem;
}
/* El estado de la ventanilla, con un punto de color delante. El texto SIEMPRE dice lo
   mismo que el color (lo redacta `evaluador.estado_plazo`): el color es apoyo, nunca el
   unico portador — el mismo contrato que las marcas ✓/✗/? de las razones. */
.ayuda-plazo {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: .92rem;
  margin-bottom: .6rem;
}
.ayuda-plazo-punto {
  width: .55rem;
  height: .55rem;
  border-radius: 50%;
  flex: none;
  background: var(--neutro);
}
.plazo-abierto .ayuda-plazo-punto, .plazo-cierra .ayuda-plazo-punto { background: var(--exito); }
.plazo-abre .ayuda-plazo-punto { background: var(--terracota-oscuro); }
/* Cerrado se queda en --neutro: informativo, no un error. La tarjeta entera sigue
   visible porque dar a conocer tambien es el producto (filosofia prospectiva). */
.ayuda-solicitar { margin-top: var(--espacio-2); }
.ayuda-pasos {
  margin: 0 0 var(--espacio-2) 1.1rem;
  font-size: .92rem;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}
/* La nota de IRPF reutiliza la anatomia de `.informe-aviso-cuota` (icono + texto en un
   span), en pequeño y dentro de la tarjeta. */
.ayuda-irpf {
  display: flex;
  gap: .45rem;
  font-size: .85rem;
  color: var(--neutro);
  margin-top: var(--espacio-2);
}

/* El segundo bloque del hub: «Y en ayudas publicas...». Separado y rotulado distinto
   (decision del usuario): papel propio, cifra en el tono condicional de siempre, y la
   nota que explica por que NO se suma al titular fiscal. */
.hub-ayudas {
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio);
  box-shadow: var(--sombra);
  padding: var(--espacio-3);
  text-align: center;
}
.hub-ayudas-rotulo {
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--neutro);
  margin-bottom: .35rem;
}
.hub-ayudas-cifra {
  font-family: var(--serif);
  font-size: 1.7rem;
  color: var(--terracota-oscuro);
  margin-bottom: .35rem;
}
/* «Y en tu municipio»: el selector respira respecto a las tarjetas y el datalist ya
   hereda el estilo de `.entrada-texto` (regla compartida de la seccion 5). */
.ayudas-municipio { display: flex; flex-direction: column; gap: var(--espacio-2); }
.ayudas-municipio .entrada-texto { max-width: 26rem; }

.hub-ayudas-nota {
  font-size: .9rem;
  color: var(--neutro);
  max-width: 34rem;
  margin: 0 auto var(--espacio-2);
}

/* ---------- La oferta profesional (`_navegacion_informe.html`) ----------
   Registro de nota al pie (`.informe-enlaces-pie`), no de reclamo: comparte la clase con
   el enlace de "Modificar mis datos" a proposito -- el spec (§7.3) pide un enlace de
   TEXTO y no un boton, porque en el hub un boton mas cruzaria la decision cerrada de
   "solo tres botones en pantalla" (cabecera de `templates/resultado.html`). El borde
   discontinuo, como `.gestorias-vacio`, sigue marcando la franja como informacion de
   servicio sin que el enlace de dentro parezca un boton. */
.oferta-profesional {
  border: 1px dashed var(--borde-control);
  border-radius: var(--radio-chico);
  padding: var(--espacio-2);
  flex-direction: column;
  gap: var(--espacio-1);
}
.oferta-profesional-texto {
  font-size: .92rem;
  color: var(--neutro);
  margin: 0;
  text-align: center;
}
.oferta-profesional .enlace-volver-resumen { margin-top: 0; }
