/* ── FUENTE LOCAL (v1.26) ──────────────────────────────────────── */
/* Outfit variable (pesos 100-900, subset latino, licencia OFL en
   fonts/OFL-outfit.txt). Es la letra de los temas cozy Mocha/Latte:
   la pila Segoe UI era la última seña genérica que les quedaba.
   Saltmarsh conserva su Georgia (su identidad es serif) y Sonar su
   Courier. Local a propósito (fonts/, sin CDN): el panel sigue
   funcionando offline. Si el navegador no la carga, cae a la pila de
   sistema del fallback — nada se rompe. */
@font-face {
  font-family: "Outfit";
  src: url("../fonts/outfit-variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg-dark:    #050b14;
  --bg-card:    rgba(0, 240, 255, 0.03);
  --text-light: #d4e8f9;
  --neon-cyan:  #00f0ff;
  --neon-glow:  rgba(0, 240, 255, 0.6);
  --border-dim: rgba(0, 240, 255, 0.1);
  --text-white: #ffffff;
  /* v1.66 — texto atenuado. Son COLORES, no opacidad: `opacity` compone
     contra el fondo, y el mismo número da 6,27:1 en Sonar y 2,84:1 en
     Daybreak. Cada tema fija su pareja midiendo el contraste, y los dos
     peldaños quedan por encima de 4,98:1 sobre el peor fondo del tema.
     --text-muted = texto secundario que se lee (hints, metadatos, nav en
     reposo); --text-faint = lo de-enfatizado (hecho, cerrado, notas).
     Si añades un tema, define los dos o heredará los de Sonar. */
  --text-muted: #a6b8c8;
  --text-faint: #8293a1;
  --border-section: #1a1a1a;
  --input-border: #222;
  /* --font-mono varía por tema (nombre de variable heredado de cuando
     los 5 eran monospace — ya no es literal desde v1.13: Saltmarsh es
     serif, Plano/Ártico son sans). Courier New es la de Sonar, la
     original. */
  --font-mono: "Courier New", Courier, monospace;
  /* estructura de caja: esquinas, resplandor y grosor de la barra de
     acento. El color de un recuadro cambia con el tema desde v1.10, pero
     la FORMA del recuadro (esquinas rectas + barra + resplandor neón) es
     lo que realmente lee como "terminal hacker" pase lo que pase con el
     color — así que esto también varía por tema, no solo --neon-cyan. */
  --radius: 0px;
  --accent-width: 3px;
  /* colores semánticos y de interacción (v1.25): antes estaban
     hardcodeados por todo el CSS con los valores de Sonar, y en los
     temas claros el amarillo/rojo de fondo oscuro no daba contraste.
     Cada tema define los suyos. --btn-text es el texto sobre fondos de
     acento o semánticos (negro en Sonar, blanco/oscuro según tema);
     --btn-hover-bg el fondo del botón primario al hover; --hover-tint
     el tinte de hover de filas/tarjetas clicables. */
  --danger: #ff3b5c;
  --warn: #ffcc00;
  --ok: #34d399;
  --btn-text: #000;
  --btn-hover-bg: #fff;
  --hover-tint: rgba(0, 240, 255, 0.08);
  /* elevación (v1.26): en Sonar la jerarquía la pone el resplandor,
     no la sombra — tarjetas planas (none), popover con la sombra dura
     de siempre y modal con glow. Los temas cozy definen sombras suaves
     tintadas con su propio gris: sin ellas, al no tener resplandor,
     buscador y ajustes flotaban sin ninguna profundidad. */
  --shadow-card: none;
  --shadow-pop: 0 4px 20px rgba(0, 0, 0, 0.4);
  --shadow-modal: 0 0 30px var(--neon-glow);
}

/* ── SALTMARSH ─────────────────────────────────────────────────── */
/* v1.56: se llamaba "Nature". Ver el bloque de renombrado en shared.js.
   Tema claro editorial: fondo crudo/marfil, verde bosque para texto y
   acentos, serif Georgia — pensado para leer como una revista/web
   botánica, no como una terminal invertida. Desde v1.22 es un tema COZY
   (applyTheme le añade la clase, ver COZY_THEMES en shared.js): sin
   decoración de terminal, igual que los Catppuccin. */
/* v1.20: los bloques de tema usan .clase a secas (no body.clase) porque
   applyTheme() pone la clase en <html> Y en <body>. Motivo: el scrollbar
   de la página pertenece a <html>, y con la clase solo en body sus
   variables resolvían siempre contra :root — scrollbar cian de Sonar
   en todos los temas (bug de v1.10 a v1.19, visible pero nunca cazado). */
.saltmarsh {
  --bg-dark:    #f6f2e6;
  /* v1.56: el verde sube de #3f7d4f a #337344. NO es cosa de la marca: el
     viejo daba 4,41:1 sobre el marfil y no llegaba a AA — un fallo de
     accesibilidad que llevaba en producción desde v1.22. #337344 da
     5,10:1 y a simple vista es el mismo verde. De paso se arregla que
     --bg-card usara 45,90,62, que no era el acento de nadie. */
  --bg-card:    rgba(51, 115, 68, 0.05);
  --text-light: #3a4a3a;
  --neon-cyan:  #337344;
  /* sin resplandor: el brillo neón es lo más "ciberpunk" del set de
     variables — una web de naturaleza no debería tener bloom de fósforo */
  --neon-glow:  rgba(51, 115, 68, 0);
  --border-dim: rgba(51, 115, 68, 0.16);
  --text-white: #1f2e1f;
  /* 6,03:1 y 5,00:1 sobre --bg-card, que es el peor fondo del tema */
  --text-muted: #4d5b4c;
  --text-faint: #5a6758;
  --border-section: #ddd5bd;
  --input-border: #c9c0a0;
  /* serif elegante, no monospace — pedido explícito: se quería que
     Saltmarsh "se sintiera" distinto, no solo de otro color. Georgia es
     la serif de pantalla más fiable en Mac y Windows sin depender de
     fuentes web externas (la web es 100% local/offline). */
  --font-mono: Georgia, "Palatino Linotype", "Book Antiqua", serif;
  /* esquinas suaves + sin barra de acento lateral: menos "ventana de
     terminal", más tarjeta tranquila */
  --radius: 10px;
  --accent-width: 0px;
  /* semánticos en tierra: rojo teja y ocre oscurecido — los neón de
     Sonar sobre marfil no daban contraste AA como texto */
  --danger: #b13c2e;
  --warn: #8a6100;
  --ok: #2f7a3f;
  --btn-text: #fff;
  --btn-hover-bg: #276c3b;
  --hover-tint: rgba(51, 115, 68, 0.08);
  /* sombras tintadas con el verde-gris del texto, no negro puro */
  --shadow-card: 0 1px 3px rgba(58, 74, 58, 0.09), 0 4px 14px rgba(58, 74, 58, 0.07);
  --shadow-pop: 0 8px 28px rgba(58, 74, 58, 0.2);
  --shadow-modal: 0 12px 40px rgba(58, 74, 58, 0.25);
}

/* ── NIGHTWATCH ────────────────────────────────────────────────── */
/* v1.56: se llamaba "Catppuccin Mocha", y hasta esta versión ERA
   Catppuccin Mocha. Ahora es nuestro. Base "Deepwater" #0c1f30 —puerto de
   noche, agua fría alrededor— y acento "Lamplight" en su variante clara
   #daa8ec: la luz malva que ya dibuja el símbolo, dentro del agua.
   Pedido en 2026-07-12: el neón de Sonar abrumaba — esto es lo
   contrario, pastel suave sobre oscuro calmado. Sin resplandor (el pastel
   no brilla) y esquinas redondeadas.
   El crédito a Catppuccin (MIT) sigue arriba y no se borra: la estructura
   de la paleta y los semánticos vienen de ahí. */
.nightwatch {
  --bg-dark:    #0c1f30;
  --bg-card:    rgba(218, 168, 236, 0.05);
  /* gris de texto SIN lavanda: el #cdd6f4 de Catppuccin es gris lavanda y
     sobre una base marina se lee sucio. 11,87:1. Es el mismo hex que usa
     la imagen Open Graph, a propósito: un solo gris, no dos casi iguales. */
  --text-light: #d1dae6;
  --neon-cyan:  #daa8ec;
  --neon-glow:  rgba(218, 168, 236, 0);
  --border-dim: rgba(218, 168, 236, 0.16);
  /* el rosewater se queda: es la única nota cálida del tema y da 13,22:1 */
  --text-white: #f5e0dc;
  /* 6,74:1 y 4,98:1 sobre --bg-card */
  --text-muted: #a2adbb;
  --text-faint: #8994a3;
  /* v1.56: los dos grises lavanda de Catppuccin (#313244 y #45475a) pasan
     a marino. El criterio es PARIDAD DE PESO, no de valor: se copia la
     relación de contraste que ya tenían, para no cambiar la jerarquía
     visual mientras se cambia el tono. 1,31:1 y 1,57:1, iguales que antes. */
  --border-section: #16354c;
  --input-border: #2a4056;
  /* v1.21: sans cómoda, no monospace — los Catppuccin son los temas
     "cozy" (clase .cozy, ver bloque más abajo) y la letra de terminal
     era lo que más los delataba. v1.26: Outfit local (ver @font-face
     arriba) — la pila de sistema era la última seña genérica que les
     quedaba. Misma familia en ambos sabores: dos caras del mismo
     sistema. */
  --font-mono: "Outfit", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --radius: 8px;
  --accent-width: 0px;
  /* semánticos de la paleta oficial Mocha (red/yellow/green pastel);
     sobre esos pasteles claros el texto va oscuro (base), no negro puro */
  --danger: #f38ba8;
  --warn: #f9e2af;
  --ok: #a6e3a1;
  --btn-text: #0c1f30;
  /* v1.56: el hover del botón primario deja de ser el realce rosewater y
     pasa al acento un peldaño más claro — en oscuro el peldaño va hacia
     arriba. 8,60:1 → 10,87:1, AAA. */
  --btn-hover-bg: #e8c4f4;
  --hover-tint: rgba(218, 168, 236, 0.08);
  /* sombras sobre fondo oscuro, más marcadas que en los temas claros.
     v1.56: iban tintadas con el crust de Catppuccin (#11111b), un gris
     lavanda; ahora con Deepwater oscurecido (#061420), que tiene
     prácticamente la misma luminancia (0,0064 contra 0,0060) — la sombra
     pesa igual, solo deja de tirar a violeta sobre el agua. */
  --shadow-card: 0 1px 3px rgba(6, 20, 32, 0.3), 0 4px 14px rgba(6, 20, 32, 0.22);
  --shadow-pop: 0 8px 28px rgba(6, 20, 32, 0.5);
  --shadow-modal: 0 12px 40px rgba(6, 20, 32, 0.55);
}

/* ── DAYBREAK ──────────────────────────────────────────────────── */
/* v1.56: se llamaba "Catppuccin Latte". El sabor claro de la misma
   familia, ahora con acento propio: base #eff1f5 —un casi-neutro no lleva
   la huella de nadie, así que NO cambia— texto #4c4f69, y el acento
   "Lamplight" #9430b3 en su variante para fondo claro. Es el tema por
   defecto del producto. */
.daybreak {
  --bg-dark:    #eff1f5;
  --bg-card:    rgba(148, 48, 179, 0.04);
  --text-light: #4c4f69;
  --neon-cyan:  #9430b3;
  --neon-glow:  rgba(148, 48, 179, 0);
  --border-dim: rgba(148, 48, 179, 0.14);
  /* sigue a la base oscura de la marca, que ya no es #1e1e2e */
  --text-white: #0c1f30;
  /* 5,98:1 y 4,98:1 sobre --bg-card. Es el tema por defecto y el que menos
     margen tiene: su --text-light base solo da 7,06:1, así que los dos
     peldaños van juntos por fuerza. */
  --text-muted: #53566f;
  --text-faint: #5f627a;
  --border-section: #dce0e8;
  --input-border: #bcc0cc;
  --font-mono: "Outfit", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --radius: 8px;
  --accent-width: 0px;
  /* red de Latte para peligro; el amarillo oficial (#df8e1d) no llega
     a AA como texto sobre fondo claro — se usa un ocre oscurecido.
     Verde también oscurecido para aguantar texto blanco encima.
     Botón primario: texto blanco (negro sobre el malva se quedaba en
     ~3:1; sobre #9430b3 el blanco da 6,30:1) y hover a malva más oscuro,
     no a blanco (un botón blanco sobre fondo claro desaparecía). */
  --danger: #d20f39;
  --warn: #8a6100;
  --ok: #2d7320;
  --btn-text: #fff;
  --btn-hover-bg: #7c1499;
  --hover-tint: rgba(148, 48, 179, 0.08);
  /* sombras tintadas con el gris-lavanda del texto de Daybreak (#4c4f69),
     que no cambia porque el texto tampoco */
  --shadow-card: 0 1px 3px rgba(76, 79, 105, 0.09), 0 4px 14px rgba(76, 79, 105, 0.07);
  --shadow-pop: 0 8px 28px rgba(76, 79, 105, 0.2);
  --shadow-modal: 0 12px 40px rgba(76, 79, 105, 0.25);
}

/* ── BASE ──────────────────────────────────────────────────────── */
* { box-sizing: border-box; }
/* esquinas de todo lo bordeado, gobernadas por --radius (recta en
   Sonar, redondeada en los temas cozy) — universal a propósito:
   así cualquier caja nueva hereda el criterio del tema sin tener que
   acordarse de añadirlo */
*, *::before, *::after { border-radius: var(--radius); }

/* v1.52: la raíz sube de los 16px del navegador a 17. Casi todo el panel
   mide en rem, así que un solo número levanta el cuerpo entero un 6% sin
   tocar sesenta reglas ni descuadrar los paddings, que van en px. Lo que
   se quedaba corto de verdad (pestañas, hints, tablas) sube aparte. */
html { font-size: 17px; }

body {
  margin: 0;
  background-color: var(--bg-dark);
  color: var(--text-light);
  font-family: var(--font-mono);
  line-height: 1.6;
  transition: background-color 0.3s ease, color 0.3s ease, font-family 0s;
}

/* scrollbar temático — mismo truco que ::selection: variables, se re-tematiza solo */
* { scrollbar-color: var(--neon-cyan) var(--bg-dark); scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--bg-dark); }
::-webkit-scrollbar-thumb { background: var(--border-dim); border: 1px solid var(--neon-cyan); }
::-webkit-scrollbar-thumb:hover { background: var(--neon-cyan); }

/* ── AJUSTES (engranaje + panel de tema) ───────────────────────── */
/* v1.57: hasta aquí esto era `position: fixed; top: 20px; right: 20px`, o sea
   dos botones flotando sobre la página sin relación con nada. Coincidían más
   o menos con la cabecera por casualidad, y en cuanto el subtítulo subió a esa
   fila (v1.57) se vio: ni alineados con la marca ni con el subtítulo, y en la
   barra de móvil pegados al techo. Ahora son un hijo más de .panel-header y
   los alinea el flex, que es lo único que garantiza que sigan alineados si
   mañana cambia el tamaño del wordmark o el padding de la cabecera. */
.settings-widget { display: flex; gap: 8px; margin-left: auto; }
#search-toggle,
#settings-toggle {
  background: transparent;
  border: 1px solid var(--neon-cyan);
  color: var(--neon-cyan);
  font-family: inherit;
  font-size: 1.125rem;
  line-height: 1;
  width: 38px;
  height: 38px;
  cursor: pointer;
  transition: all 0.2s ease;
  /* v1.27: dentro va un SVG (antes emoji) — flex lo centra exacto */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
#search-toggle:hover,
#settings-toggle:hover {
  background: var(--neon-cyan);
  color: var(--bg-dark);
  box-shadow: 0 0 12px var(--neon-glow);
}
/* ── AJUSTES (v1.33) — modal centrado con apartados ─────────────
   Sustituye al desplegable pegado al engranaje. Mismo lenguaje visual
   que #app-dialog y el buscador: overlay translúcido + caja opaca. */
.settings-modal {
  position: fixed; inset: 0; z-index: 1150;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
/* el atributo hidden pierde contra un display:flex de igual especificidad
   puesto por nuestro propio CSS de autor (gana siempre al UA stylesheet,
   sin importar el orden) — por eso el panel se veía todo el rato */
.settings-modal[hidden] { display: none; }
.settings-card {
  width: 100%; max-width: 620px;
  max-height: 86vh;
  /* --bg-dark es OPACO (no como --bg-card, un tinte al 4% en los cozy):
     la caja va sobre el overlay, igual que el buscador y el diálogo */
  background: var(--bg-dark);
  border: 1px solid var(--border-dim);
  border-left: var(--accent-width) solid var(--neon-cyan);
  box-shadow: var(--shadow-modal);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.settings-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 20px 24px 14px;
  border-bottom: 1px solid var(--border-dim);
}
.settings-title { margin: 0; color: var(--neon-cyan); font-size: 1.375rem; }
.settings-close {
  background: none; border: 1px solid var(--border-dim); color: var(--text-light);
  font-family: inherit; font-size: 1rem; line-height: 1;
  width: 30px; height: 30px; cursor: pointer; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius, 0);
}
.settings-close:hover { background: var(--neon-cyan); color: var(--bg-dark); }
.settings-body { display: flex; min-height: 0; flex: 1; }
.settings-nav {
  display: flex; flex-direction: column; gap: 2px;
  padding: 14px 10px; flex-shrink: 0;
  border-right: 1px solid var(--border-dim);
  min-width: 132px;
}
.settings-nav-btn {
  background: none; border: none; border-left: 2px solid transparent;
  color: var(--text-muted); font-family: inherit; font-size: 0.875rem;
  text-align: left; padding: 9px 12px; cursor: pointer;
}
.settings-nav-btn:hover { color: var(--text-light); }
.settings-nav-btn[aria-selected="true"] {
  color: var(--neon-cyan);
  border-left-color: var(--neon-cyan);
  background: var(--bg-card);
}
.settings-panes { flex: 1; min-width: 0; overflow-y: auto; padding: 20px 24px 24px; }
.settings-pane { display: flex; flex-direction: column; gap: 18px; }
.settings-pane[hidden] { display: none; }
.settings-hint { margin: 0; font-size: 0.875rem; color: var(--text-muted); line-height: 1.5; }
.settings-group { display: flex; flex-direction: column; gap: 8px; }
.settings-group + .settings-group { border-top: 1px dashed var(--border-dim); padding-top: 16px; }
.settings-group-title { margin: 0; font-size: 0.875rem; color: var(--text-white); font-weight: 600; }
.settings-group.is-danger .settings-group-title { color: var(--danger); }
.settings-row-btns { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* v1.48: su [hidden] (ver el comentario largo en .sync-auth-fields). Esta
   clase la llevan DOS filas que el JS oculta: #sync-choice (las dos puertas,
   que deben irse al abrir el formulario) y #sync-buy-in (los botones de plan,
   que no van si ya hay suscripción). Sin esta regla, ninguna se ocultaba. */
.settings-row-btns[hidden] { display: none; }
.settings-panes input { width: 100%; box-sizing: border-box; }
/* v1.47: .cfg-check nació dentro de .settings-grid (que ya pone display:flex
   en sus label). Suelto en un grupo de ajustes hay que dárselo aquí. */
.settings-group > .cfg-check { display: flex; font-size: 0.875rem; cursor: pointer; }
.settings-group > .cfg-check input { width: auto; }
/* v1.63 — Ajustes › Panel › Sections: una fila por sección, interruptor a la
   izquierda y las flechas a la derecha.
   Ojo con .cfg-check aquí: la regla que le devuelve el ancho automático al
   checkbox (más arriba) es `.settings-group > .cfg-check input`, hijo DIRECTO,
   y estas etiquetas cuelgan de un <li>. Sin la línea de abajo se las come el
   `.settings-panes input { width: 100% }`, que es exactamente el fallo de la
   v1.52: el checkbox reclama la caja entera y parte su propia etiqueta. */
.cfg-sections { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cfg-section-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.cfg-section-row .cfg-check { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; font-size: 0.875rem; cursor: pointer; }
.cfg-section-row .cfg-check input { width: auto; flex: none; }
/* v1.72: el icono de la sección, entre el interruptor y el nombre. Sin color
   propio — hereda el de la fila, así que se apaga solo con la de una sección
   oculta (la regla de .is-off de aquí abajo). */
.cfg-section-row .cfg-section-icon { display: flex; flex: none; color: var(--neon-cyan); }
.cfg-section-row.is-off .cfg-section-icon { color: inherit; }
.cfg-section-row.is-off .cfg-check { color: var(--text-faint); }
.cfg-section-row .cfg-check input:disabled { cursor: not-allowed; }
.cfg-section-move { display: flex; gap: 4px; flex-shrink: 0; }
.cfg-section-move .terminal-btn-sm { padding: 2px 9px; line-height: 1.4; }
.cfg-section-move .terminal-btn-sm:disabled { opacity: 0.3; cursor: not-allowed; }
/* sin :hover en un botón deshabilitado: el de .terminal-btn-sm invierte los
   colores y da a entender que se puede pulsar la flecha de la primera fila */
.cfg-section-move .terminal-btn-sm:disabled:hover { background: transparent; color: var(--neon-cyan); box-shadow: none; }
.settings-inline { display: flex; align-items: center; gap: 8px; font-size: 0.75rem; color: var(--text-muted); flex-wrap: wrap; }
.settings-inline select { width: auto; }
/* chuleta de atajos: pares tecla/qué hace, en dos columnas que se apilan */
.shortcut-list { display: grid; grid-template-columns: auto 1fr; gap: 8px 14px; margin: 0; font-size: 0.875rem; }
.shortcut-list dt { white-space: nowrap; }
.shortcut-list dd { margin: 0; color: var(--text-muted); }
.shortcut-list kbd {
  font-family: var(--font-mono); font-size: 0.75rem;
  border: 1px solid var(--border-dim); border-radius: 4px;
  padding: 1px 6px; background: var(--bg-card);
}

/* tarjetas de tema: cada una pinta una miniatura del panel con los colores
   reales de ese tema (variables --sw-* inline en el HTML), así se elige
   por lo que se ve y no por el nombre */
.theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.theme-card {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch;
  background: none; border: 1px solid var(--border-dim);
  border-radius: var(--radius, 0);
  padding: 10px; cursor: pointer; font-family: inherit;
  color: var(--text-light); text-align: left;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.theme-card:hover { border-color: var(--neon-cyan); }
.theme-card[aria-pressed="true"] {
  border-color: var(--neon-cyan);
  box-shadow: 0 0 0 1px var(--neon-cyan) inset;
}
.theme-mini {
  display: flex; flex-direction: column; gap: 5px; justify-content: center;
  height: 58px; padding: 9px 10px;
  background: var(--sw-bg); border: 1px solid var(--sw-line);
  border-radius: calc(var(--radius, 0px) / 1.5);
}
.theme-mini-bar { height: 7px; width: 46%; background: var(--sw-accent); border-radius: 2px; }
.theme-mini-line { height: 4px; width: 88%; background: var(--sw-text); opacity: 0.5; border-radius: 2px; }
.theme-mini-line.is-short { width: 62%; }
.theme-card-name { font-size: 0.875rem; color: var(--text-white); }
.theme-card-desc { font-size: 0.75rem; color: var(--text-faint); }

/* ── SYNC (v1.19, opcional — solo visible con js/sync-config.js) ── */
.sync-block { display: flex; flex-direction: column; gap: 18px; }
.sync-block[hidden] { display: none; }
.sync-form { display: flex; flex-direction: column; gap: 18px; }
.sync-form[hidden] { display: none; }
/* v1.46: los campos de crear cuenta / entrar. La regla [hidden] es
   OBLIGATORIA en cuanto se fija `display` en un elemento ocultable: el
   display:none de [hidden] lo pone la hoja del navegador y CUALQUIER regla
   de autor con display le gana, tenga la especificidad que tenga. Pasó justo
   aquí — `.settings-group { display: flex }` hacía que el formulario se
   viera siempre, encima del texto de bienvenida. */
.sync-auth-fields { display: flex; flex-direction: column; gap: 8px; }
.sync-auth-fields[hidden] { display: none; }
.sync-block input { width: 100%; box-sizing: border-box; }
.sync-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.sync-user { font-size: 0.875rem; color: var(--neon-cyan); word-break: break-all; margin: 0; }
.sync-status { font-size: 0.75rem; color: var(--text-muted); min-height: 1em; margin: 0; }

/* ── CUENTA: NOMBRE / EMAIL / CONTRASEÑA (v1.52) ─────────────────
   Tres botones, y el que pulsas abre su panel debajo. Ojo con la regla de
   [hidden] de aquí arriba: .acc-panel lleva display, así que necesita su
   propio [hidden] o se vería siempre (es el fallo de v1.46 y v1.47, que ya
   ha vuelto dos veces). */
.acc-actions { display: flex; flex-direction: column; gap: 8px; }
.acc-toggle {
  background: transparent; border: 1px solid var(--border-dim); color: var(--text-light);
  font-family: inherit; font-size: 0.875rem; text-align: left;
  padding: 10px 12px; cursor: pointer; transition: 0.2s;
}
.acc-toggle:hover { border-color: var(--neon-cyan); color: var(--neon-cyan); }
.acc-toggle[aria-expanded="true"] { border-color: var(--neon-cyan); color: var(--neon-cyan); }
.acc-panel {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px; margin-bottom: 4px;
  border: 1px solid var(--border-dim); border-top: none;
}
.acc-panel[hidden] { display: none; }
/* la respuesta va pegada a la acción y con color: un fallo no puede leerse
   igual que un acierto, que era el problema de la línea gris única */
.acc-msg { font-size: 0.875rem; margin: 0; min-height: 1em; line-height: 1.45; }
.acc-msg.is-ok { color: var(--ok); }
.acc-msg.is-err { color: var(--danger); }
/* v1.32: enlace "¿olvidaste tu contraseña?" en el login del panel */
.sync-forgot-link {
  background: none; border: none; color: var(--text-muted);
  font-family: inherit; font-size: 0.75rem;
  cursor: pointer; text-decoration: underline; padding: 2px 0;
  align-self: flex-start;
}
.sync-forgot-link:hover { color: var(--text-light); }

/* v1.33: en móvil el modal ocupa casi todo y los apartados pasan a ser
   una fila horizontal arriba — la columna lateral no cabe */
@media (max-width: 560px) {
  .settings-modal { padding: 0; }
  .settings-card { max-width: none; max-height: 100vh; height: 100%; border-left-width: 0; }
  .settings-body { flex-direction: column; }
  .settings-nav {
    flex-direction: row; min-width: 0; padding: 8px 10px;
    border-right: none; border-bottom: 1px solid var(--border-dim);
    overflow-x: auto;
  }
  .settings-nav-btn {
    border-left: none; border-bottom: 2px solid transparent;
    padding: 8px 12px; white-space: nowrap;
  }
  .settings-nav-btn[aria-selected="true"] { border-bottom-color: var(--neon-cyan); }
  .theme-grid { grid-template-columns: 1fr; }
}

/* ── LAYOUT ────────────────────────────────────────────────────── */
.section-container {
  max-width: 1440px;
  width: 92%;
  margin: 0 auto;
  padding: 80px 20px;
  border-bottom: 1px dashed var(--border-section);
}

/* v1.21: el HTML lleva los textos en tipo frase ("Panel de Control",
   "01 Tracker", "Iniciar") y son los temas terminal los que los GRITAN
   vía text-transform — cozy lo desactiva. Mismo criterio que el "// "
   de los hints desde v1.12: la estética la pone el CSS, no el texto. */
h1 { font-size: 2.5rem; margin: 0 0 12px 0; text-transform: uppercase; }
/* v1.26: sin viudas en títulos ni párrafos — balance reparte las
   líneas del título, pretty evita la palabra suelta al final. Los
   navegadores sin soporte lo ignoran sin más. */
h1, h2, h3 { text-wrap: balance; }
p, .hint { text-wrap: pretty; }
/* v1.57: el lockup horizontal inline en el h1 (antes: símbolo + la palabra
   como texto en Outfit). Dos tintas: el grupo del símbolo lleva el acento y
   la palabra hereda el color del h1, o sea el texto del tema — así funciona
   igual en los cuatro y no hace falta cambiar de archivo sobre fondo oscuro.
   La altura va en em para seguir al h1, con el mínimo de marca de 24px de
   alto como suelo: en la barra móvil 1.15em serían 21px y el horizontal por
   debajo de 24 se cae a símbolo solo. El vertical-align sale de la geometría,
   no del ojo: la baseline del wordmark está a y=85 de 105, o sea que por
   debajo queda el 19% de la altura (0,19 × 1,15em ≈ 0,22em). */
.logo-wordmark { height: max(24px, 1.15em); width: auto; vertical-align: -0.22em; }
.logo-wordmark .lw-symbol { color: var(--neon-cyan); }
/* v1.55: la píldora "Sync" a la derecha del wordmark. Solo aparece cuando
   SABEMOS que la suscripción está activa (lo decide syncRenderBrandBadge),
   así que su sola presencia ya es la información. Va dentro del h1 para
   pegarse al wordmark, pero con tamaño propio en rem: heredar 2.25rem la
   convertiría en otro título. text-transform se hereda del h1 — en los
   temas terminal sale "SYNC" y en los cozy "Sync", que es el mismo criterio
   que el resto de la interfaz desde v1.21. Lleva su [hidden] porque fija
   display (ver el comentario largo en .sync-auth-fields). */
.brand-badge {
  display: inline-block;
  margin-left: 12px;
  padding: 0.15em 0.6em;
  border: 1px solid var(--border-dim);
  border-radius: 999px;
  background: var(--hover-tint);
  color: var(--neon-cyan);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  vertical-align: 0.35em;
  white-space: nowrap;
}
.brand-badge[hidden] { display: none; }
h2 { font-size: 1.75rem; color: var(--neon-cyan); margin-bottom: 28px; }
/* v1.72: el título de sección lleva delante el icono de la sección, donde
   antes iba el número y su " // ". El hueco (.sec-mark) está en el HTML y lo
   rellena paintSectionMarks() desde SECTIONS, para que el dibujo siga
   viviendo en un solo sitio.
   El icono va a 0.95em y no al 1.15em de la barra: ahí el texto es pequeño y
   en mayúsculas, aquí es un título de 1.75rem en caja baja, y al mismo ratio
   el dibujo le comía el sitio al título. 0.95em lo deja a la altura de las
   ascendentes, que es donde se lee como parte de la palabra. */
.panel-section h2 { display: flex; align-items: center; gap: 14px; }
.sec-mark { display: flex; flex: none; }
.sec-mark .sec-icon { width: 0.95em; height: 0.95em; }
h3 { font-size: 1.125rem; margin: 0 0 15px 0; }

p { font-size: 1rem; color: var(--text-muted); max-width: 70ch; }
.subtitle { font-size: 1rem; color: var(--neon-cyan); margin-bottom: 0; }
.subtitle::before { content: "// "; }

/* ── CURSOR ────────────────────────────────────────────────────── */
/* v1.57: el cursor se DIBUJA, ya no se escribe. Mientras la palabra era
   texto, el "_" compartía fuente con ella y caía solo donde debía; con el
   wordmark trazado la referencia desapareció y en Sonar —el único tema que
   enseña el cursor, ver `.cozy .cursor`— quedaba por debajo del pie de la
   palabra, porque en una monoespaciada el guion bajo se dibuja bastante por
   debajo de la línea base. Un rectángulo inline-block apoya su borde inferior
   EN la línea base, que es exactamente donde apoyan las letras, y da igual la
   fuente del tema. El grosor sale del propio lockup: su trazo son 11 unidades
   de 105 de alto, o sea 0.105 × la altura. El glow pasa de text-shadow a
   box-shadow porque ya no hay glifo que iluminar. El "_" del HTML se queda
   (es el contenido real; el color transparente solo lo esconde). */
.cursor {
  display: inline-block;
  width: 0.5em;
  height: 0.105em;
  margin-left: 0.06em;
  vertical-align: baseline;
  /* overflow:hidden es obligatorio, no cosmético: la línea base de un
     inline-block es la de su última línea de texto —o sea la del "_" que
     lleva dentro, que es justo lo que estamos evitando— salvo que su overflow
     no sea visible, y entonces pasa a ser su borde inferior. */
  overflow: hidden;
  color: transparent;
  background: var(--neon-cyan);
  box-shadow: 0 0 10px var(--neon-glow);
  animation: blink 1s step-end infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* v1.27: eliminados los bloques PROJECTS / ARSENAL / EXPERIENCE del
   portfolio original (~95 líneas) — ninguna de sus clases aparecía ya
   en el HTML ni en el JS de este repo. */

/* ── FORM ──────────────────────────────────────────────────────── */
.terminal-form { margin-top: 30px; }
.form-group { margin-bottom: 25px; }
.form-group label {
  display: block;
  color: var(--neon-cyan);
  margin-bottom: 8px;
  font-size: 0.875rem;
}
.terminal-form input,
.terminal-form textarea {
  width: 100%;
  background: transparent;
  border: 1px solid var(--input-border);
  border-left: var(--accent-width) solid var(--neon-cyan);
  color: var(--text-white);
  padding: 12px;
  font-family: inherit;
  outline: none;
  transition: all 0.2s;
}
.terminal-form input:focus,
.terminal-form textarea:focus {
  background: var(--bg-card);
  border-color: var(--neon-cyan);
}
.terminal-form textarea { height: 120px; resize: none; }

/* ── BUTTONS ───────────────────────────────────────────────────── */
.terminal-btn {
  background: var(--neon-cyan);
  color: var(--btn-text);
  border: none;
  padding: 15px 30px;
  font-weight: bold;
  cursor: pointer;
  text-transform: uppercase;
  transition: 0.2s;
  font-family: inherit;
}
.terminal-btn:hover {
  background: var(--btn-hover-bg);
  box-shadow: 0 0 20px var(--neon-glow);
}
.terminal-btn-link {
  color: var(--neon-cyan);
  text-decoration: none;
  font-size: 0.75rem;
  font-weight: bold;
}
.terminal-btn-link:hover { text-shadow: 0 0 8px var(--neon-glow); }

/* ── FOOTER ────────────────────────────────────────────────────── */
.footer-container {
  padding: 60px 20px;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  font-size: 0.75rem;
  color: var(--text-faint);
  flex-wrap: wrap;
  gap: 20px;
  max-width: 1440px;
  margin: 0 auto;
  border-top: 1px dashed var(--border-section);
}
.footer-status::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  background: var(--neon-cyan);
  margin-right: 10px;
}
/* v1.74: el aviso de versión nueva. Hereda el tamaño y el color del pie
   —o sea que no añade ningún valor a la escala— y el texto sube a
   --text-muted porque es un aviso que se lee, no un dato apagado.
   Nace con [hidden], así que lleva su regla: una que fije display sin
   ella se queda visible para siempre. */
.sw-update {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  color: var(--text-muted);
}
.sw-update[hidden] { display: none; }

/* ═══════════════════════════════════════════════════════════════
   PANEL DE CONTROL — COMPONENTES (v1.2)
   El styles.css original solo traía las clases del portfolio.
   Nada de lo que usa el panel (nav, selects nativos, tablas,
   tracker, proyectos, enlaces, todos) tenía estilo: por eso se veía
   con los widgets nativos del navegador (normalmente en claro,
   aunque el body estuviera en modo oscuro). Bloque añadido para
   cubrir eso.
   ═══════════════════════════════════════════════════════════════ */

/* fuerza a los controles nativos (select, date, scrollbar, etc.)
   a seguir la paleta del tema en vez del tema del SO */
html { color-scheme: dark; }
/* saltmarsh y daybreak son los temas claros. Clase a secas (v1.20):
   también en <html>, ver bloques de tema. */
.saltmarsh, .daybreak { color-scheme: light; }

::selection {
  background: var(--neon-cyan);
  color: var(--btn-text);
}

/* todo control de texto/número/fecha lleva el mismo tratamiento, esté o no
   dentro de .terminal-form — antes solo .terminal-form input/textarea lo
   tenía, así que la nota de sesión y otros campos sueltos (enlaces, todos)
   se quedaban con el input nativo del navegador, sin relación visual con
   el resto de la web. */
/* v1.33: email y password faltaban en esta lista desde que existe el login
   (v1.19) — se quedaban con el input nativo del navegador, negro y con
   borde gris, en TODAS las pantallas de cuenta: onboarding, login,
   recuperar contraseña y ahora Ajustes › Cuenta. Es lo primero que ve
   quien se da de alta, así que valía la pena arreglarlo aquí. */
select,
input[type="date"],
input[type="number"],
input[type="text"],
input[type="url"],
input[type="search"],
input[type="email"],
input[type="password"],
textarea {
  font-family: inherit;
  color: var(--text-white);
  background: var(--bg-dark);
  border: 1px solid var(--input-border);
  border-left: var(--accent-width) solid var(--neon-cyan);
  padding: 10px 12px;
  outline: none;
  transition: all 0.2s;
}
select:focus,
input[type="date"]:focus,
input[type="number"]:focus,
input[type="text"]:focus,
input[type="url"]:focus,
input[type="search"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
textarea:focus {
  border-color: var(--neon-cyan);
  background: var(--bg-card);
}
select option {
  background: var(--bg-dark);
  color: var(--text-white);
}
input[type="range"] { padding: 0; border-left: none; border: none; accent-color: var(--neon-cyan); }

/* patrón label + campo a una columna (fuera de .terminal-form, p.ej. la
   nota de sesión del tracker): que ocupe todo el ancho como su hermano select */
.form-group select,
.form-group input,
.form-group textarea {
  width: 100%;
}
/* v1.52 — POR QUÉ "free mode" salía en dos líneas, que no era el móvil ni
   el overflow-wrap: el `width: 100%` de arriba alcanzaba también a los
   radio de #tracker-modes. Un radio con width:100% pide los 328px de la
   caja, `input { min-width: 0 }` le deja encogerse hasta 42px... y esos
   42px se los quita al texto de su propia etiqueta, que ya solo tiene
   sitio para "free" / "mode". "pomodoro" se salvaba por ser una palabra.
   Un radio o una casilla no son un campo de texto: nunca quieren el ancho
   del contenedor. `flex: none` para que tampoco encojan dentro del label. */
.form-group input[type="radio"],
.form-group input[type="checkbox"] {
  width: auto;
  flex: none;
}

/* ── HEADER / DATA ACTIONS ────────────────────────────────────────── */
/* v1.52: la cabecera se comía 275px antes de la primera pestaña — marca,
   subtítulo y un hueco de estado casi siempre vacío, y el contenido
   empezaba a media pantalla. Ahora ronda los 140px. Es aire, no
   información: la marca sigue mandando, pero sin tapar el panel. */
/* v1.57: el subtítulo ocupaba una línea entera de pantalla para
   cuatro palabras, y los dos botones flotaban por su cuenta. Ahora los tres
   comparten fila y hay DOS niveles de alineación, que es lo que pedía el
   problema: marca y subtítulo se emparejan entre ellos por la línea BASE
   —lo que alinea dos textos de tamaños muy distintos es su línea de
   escritura, no su centro óptico—, y ese bloque se centra con los botones,
   que no son texto y con baseline colgarían raro. Un contenedor solo tiene un
   align-items, de ahí .panel-brand.
   El hueco de estado se queda en su propia fila con flex-basis:100% — es
   donde escribe flashStatus desde media app y no debe bailar de sitio al
   aparecer. En móvil manda el bloque de 640: ahí el subtítulo no existe y la
   cabecera se reescribe entera como barra. */
.panel-header {
  padding-top: 28px; padding-bottom: 20px;
  display: flex; flex-wrap: wrap; align-items: center; column-gap: 24px;
}
.panel-brand {
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 20px;
  min-width: 0; /* que el bloque pueda encoger antes de empujar a los botones */
}
.panel-header h1 { margin-bottom: 0; }
.panel-header .subtitle { margin-bottom: 0; }
.data-actions { display: flex; align-items: center; gap: 12px; margin-top: 8px; flex-wrap: wrap; flex-basis: 100%; }
.data-status { font-size: 0.75rem; color: var(--neon-cyan); min-height: 1em; }

/* ── NAV ───────────────────────────────────────────────────────────── */
.panel-nav {
  position: sticky; top: 0; z-index: 900;
  background: var(--bg-dark);
  border-bottom: 1px solid var(--border-dim);
}
/* mismo ancho/centrado que .section-container — antes el nav usaba
   padding en % de viewport y en pantallas anchas los tabs quedaban
   muy a la izquierda del resto del contenido, descuadrados */
.panel-nav-inner {
  max-width: 1440px;
  width: 92%;
  margin: 0 auto;
  display: flex; flex-wrap: wrap; gap: 2px;
  padding: 10px 20px;
}
/* v1.52: los nombres de sección iban a 12px, el texto más pequeño de todo
   el panel siendo lo que más se mira. Suben a 0.9rem, y el reposo dejó de
   ser opacidad en v1.66: se leían grises sobre gris en los temas claros. */
.nav-tab {
  background: transparent; border: 1px solid transparent; color: var(--text-muted);
  font-family: inherit; font-size: 0.875rem; font-weight: bold; letter-spacing: 0.5px;
  text-transform: uppercase;
  padding: 9px 13px; cursor: pointer; transition: 0.2s;
  display: inline-flex; align-items: center; gap: 8px;
}
/* ── ICONOS DE SECCIÓN (v1.72) ─────────────────────────────────────
   Los dibujos viven en SECTIONS (shared.js) como geometría pelada: TODA
   la pintura está aquí, y por eso un icono no puede saltarse "temas =
   solo variables CSS" — hereda currentColor del sitio donde caiga, así
   que sigue al reposo/hover/activo de la pestaña sin una línea más.
   `fill: none` no es cosmético: sin él la carpeta, el embudo y la
   bombilla salen como manchas negras.
   En em, como .inline-icon, para que el icono siga al texto de al lado
   en vez de quedarse fijo mientras la letra crece. */
.sec-icon {
  width: 1.15em; height: 1.15em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.nav-tab:hover { color: var(--neon-cyan); }
.nav-tab.active { color: var(--neon-cyan); border-color: var(--neon-cyan); box-shadow: 0 0 10px var(--neon-glow) inset; }

.panel-section { padding: 30px 20px 60px; }

/* ── 01 TRACKER ────────────────────────────────────────────────────── */
.tracker-layout { display: grid; grid-template-columns: 380px 1fr; gap: 40px; align-items: start; margin-top: 30px; }
.tracker-timer-box, .tracker-history-box { background: var(--bg-card); border: 1px solid var(--border-dim); padding: 25px; }

.tracker-modes { display: flex; gap: 20px; font-size: 0.875rem; }
.tracker-modes label { display: flex; align-items: center; gap: 6px; cursor: pointer; }

.timer-display { text-align: center; padding: 25px 0; border-top: 1px dashed var(--border-section); border-bottom: 1px dashed var(--border-section); margin: 20px 0; }
.timer-phase { color: var(--neon-cyan); font-size: 0.875rem; margin-bottom: 8px; min-height: 1.2em; }
.timer-clock { font-size: 2.5rem; font-weight: bold; color: var(--text-white); letter-spacing: 2px; }
.timer-clock.break { color: var(--neon-cyan); }
.timer-clock.paused { color: var(--text-muted); }
.timer-clock.awaiting { animation: blink 1s step-end infinite; color: var(--neon-cyan); }
.timer-money { font-size: 1.125rem; color: var(--neon-cyan); margin-top: 8px; min-height: 1.4em; }
.timer-sub { font-size: 0.75rem; color: var(--text-faint); margin-top: 6px; min-height: 1.2em; }

.timer-controls { display: flex; gap: 10px; margin-bottom: 20px; }
.timer-controls .terminal-btn { flex: 1; }

.terminal-btn-sm {
  background: transparent; border: 1px solid var(--neon-cyan); color: var(--neon-cyan);
  font-family: inherit; font-size: 0.75rem; font-weight: bold; text-transform: uppercase;
  padding: 8px 12px; cursor: pointer; transition: 0.2s;
}
.terminal-btn-sm:hover { background: var(--neon-cyan); color: var(--btn-text); box-shadow: 0 0 10px var(--neon-glow); }
/* icono SVG dentro de un botón pequeño (toggle lista/mes) */
.terminal-btn-sm svg { width: 13px; height: 13px; vertical-align: -2px; margin-right: 6px; }
.terminal-btn.ghost { background: transparent; color: var(--neon-cyan); border: 1px solid var(--neon-cyan); }
.terminal-btn.ghost:hover { background: var(--neon-cyan); color: var(--btn-text); }
.terminal-btn.danger { background: var(--danger); color: var(--btn-text); }
/* hover del botón de peligro: se aclara el propio rojo del tema en vez
   de saltar a blanco (en temas claros un botón blanco desaparecía).
   color-mix para el resplandor; si el navegador no lo soporta, la
   sombra simplemente no sale. */
.terminal-btn.danger:hover { filter: brightness(1.12); box-shadow: 0 0 15px color-mix(in srgb, var(--danger) 60%, transparent); }
.danger-link { border-color: var(--danger) !important; color: var(--danger) !important; }
.danger-link:hover { background: var(--danger) !important; color: var(--btn-text) !important; }

.sound-box { margin-bottom: 20px; }
.sound-box label { display: block; color: var(--neon-cyan); margin-bottom: 8px; font-size: 0.875rem; }
.sound-controls { display: flex; gap: 10px; align-items: center; }
.sound-controls select { flex: 1; }
#sound-type.sound-on { border-color: var(--neon-cyan); box-shadow: 0 0 8px var(--neon-glow); }

.settings-details { margin-bottom: 20px; border: 1px solid var(--border-dim); padding: 12px 15px; }
.settings-details summary { cursor: pointer; color: var(--neon-cyan); font-size: 0.875rem; }
.settings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: 12px; margin-top: 15px; font-size: 0.75rem; }
/* la regla de siempre: lleva display, así que lleva su [hidden]. Lo estrena
   #fx-rows en v1.60, que se oculta cuando solo se usa una moneda. */
.settings-grid[hidden] { display: none; }
.settings-grid label { display: flex; flex-direction: column; gap: 6px; }
/* v1.64: y la etiqueta también lleva su [hidden] — la regla de arriba le
   pone display, así que sin esto un campo ocultable se ve igual. Lo estrena
   la calidad del conversor de imágenes, que no existe al exportar a PNG. */
.settings-grid label[hidden] { display: none; }
/* v1.60: la unidad de una fila de tipo de cambio, con el aviso de las monedas
   que el BCE no publica. Sin variable de color propia —opacidad, como
   .settings-hint— para no tener que declararla en los cuatro temas. */
.fx-unit { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
/* el aviso ya no encoge respecto a su fila: era 0.85em dentro de un contenedor
   de 0.8rem, o sea 11,56px, por debajo del suelo de la escala. Lo que lo
   distingue es el color atenuado, no el tamaño. El 0.75rem se declara aparte
   a propósito, para que se quede en el suelo aunque la rejilla suba. */
.fx-manual { font-size: 0.75rem; color: var(--text-faint); }
.settings-grid input[type="number"], .settings-grid select { width: 100%; }
.cfg-check { flex-direction: row !important; align-items: center; gap: 8px !important; }
.hint { font-size: 0.875rem; color: var(--text-muted); margin: 10px 0 20px; }
/* iconos dentro de un texto corrido (v1.52): el MISMO SVG que pinta el
   botón, para que lo que dice la frase se corresponda con lo que hay en
   pantalla. En em para que siga al tamaño de letra de alrededor. */
.inline-icon { width: 1.05em; height: 1.05em; vertical-align: -0.18em; }
/* el prefijo "// " es una firma visual de terminal — solo tiene sentido en
   Sonar (tema por defecto, sin clase en body). En el resto de temas el
   hint es una nota al pie normal, sin simular un comentario de código. */
.hint::before { content: "// "; }
/* v1.22: todos los temas no-sonar son cozy, así que la lista de
   excepciones tema a tema se sustituye por la clase cozy */
.cozy .hint::before { content: ""; }

/* ── DECORACIÓN TERMINAL (v1.21) ───────────────────────────────────
   Los adornos de terminal ya no son texto literal en HTML/JS: los pone
   el CSS, para que cada tema decida si los quiere. term-prompt es el
   "> " de labels/summaries; empty-hint es el "// sin X" de listas y
   tablas vacías; sec-num/sec-slash parten el "01 // Título" de las
   cabeceras. El modo cozy (nightwatch/daybreak) los apaga todos más abajo. */
.term-prompt::before { content: "> "; }
.empty-hint { color: var(--text-faint); list-style: none; }
.empty-hint::before { content: "// "; }

/* ── ESTADO VACÍO RICO (v1.30) ─────────────────────────────────────
   Icono + frase + empujón para las listas/tablas de sección vacías.
   Solo variables de tema; en tabla lo aloja una celda .empty-cell que
   anula el nowrap/ellipsis heredado de .panel-table td. */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 8px; padding: 34px 20px;
  white-space: normal; list-style: none;
  grid-column: 1 / -1; /* si el contenedor es grid (p.ej. .links-grid), ocupa todo el ancho */
}
.empty-state-icon { color: var(--neon-cyan); line-height: 0; }
.empty-state-title { margin: 0; font-weight: 600; font-size: 1rem; color: var(--text-light); }
.empty-state-cta { margin: 0; font-size: 0.875rem; color: var(--text-muted); max-width: 360px; line-height: 1.5; }
.empty-cell { white-space: normal; overflow: visible; text-overflow: clip; }
.empty-state-li { list-style: none; padding: 0; }
.schedule-empty::before { content: "// "; }

/* ── COZY (todos los temas menos Sonar) ───────────────────────────
   applyTheme() añade la clase "cozy" a los temas de COZY_THEMES
   (saltmarsh, nightwatch y daybreak desde v1.22). Aquí vive lo que separa un
   tema "cozy" de uno "terminal": sin "// " ni "> ", sin MAYÚSCULAS
   gritadas, sin cursor parpadeante, número de sección como etiqueta
   redondeada y botones en píldora. Sonar, el único tema terminal que
   queda, no se ve afectado: estas reglas solo aplican bajo .cozy. */
.cozy .term-prompt::before,
.cozy .empty-hint::before,
.cozy .schedule-empty::before,
.cozy .subtitle::before { content: ""; }
.cozy h1,
.cozy .nav-tab,
.cozy .terminal-btn,
.cozy .terminal-btn-sm { text-transform: none; }
.cozy .nav-tab { letter-spacing: 0; }
.cozy .cursor { display: none; }
/* v1.72: aquí vivían .cozy .sec-slash (que ocultaba el " // " fuera de Sonar)
   y .cozy .sec-num (que convertía el número en una píldora). Las dos se van
   con la numeración: el <h2> lleva ahora el icono de la sección, que se pinta
   igual en los cuatro temas — o sea DOS reglas de CSS por tema menos. */
.cozy .terminal-btn, .cozy .terminal-btn-sm { border-radius: 999px; }
/* el reloj del timer conserva dígitos monoespaciados aunque el resto
   del tema sea sans — un reloj proporcional "baila" al avanzar */
.cozy .timer-clock { font-family: "Cascadia Mono", Consolas, Menlo, monospace; }

/* ── JERARQUÍA TIPOGRÁFICA COZY (v1.26) ────────────────────────────
   Con Outfit variable hay pesos intermedios de verdad: los títulos y
   botones bajan de bold (700) a semibold (600) con tracking negativo
   en los grandes — menos grito, más editorial. En Saltmarsh (Georgia,
   solo regular/bold) el 600 redondea a bold: se ve como siempre. */
.cozy h1 { font-weight: 600; letter-spacing: -0.02em; }
.cozy h2 { font-weight: 600; letter-spacing: -0.01em; }
.cozy h3 { font-weight: 600; }
.cozy .terminal-btn,
.cozy .terminal-btn-sm,
.cozy .nav-tab { font-weight: 600; }
.cozy .form-group label,
.cozy .sound-box label { font-weight: 500; }

/* ── ELEVACIÓN DE TARJETAS (v1.26) ─────────────────────────────────
   Gobernada por --shadow-card: none en Sonar (allí la jerarquía la
   ponen borde y resplandor), sombra suave tintada en los cozy — sin
   resplandor, era lo único que podía separar la tarjeta del fondo. */
.tracker-timer-box, .tracker-history-box, .checklist-card,
.finance-chart-box, .tier-card, .idea-item, .link-card, .schedule-day {
  box-shadow: var(--shadow-card);
}

/* ── ONBOARDING (v1.24) ────────────────────────────────────────── */
/* fondo opaco a propósito: la primera impresión es esta pantalla, no
   un panel a medio renderizar detrás */
.onboard-modal {
  position: fixed; inset: 0; z-index: 1100;
  background: var(--bg-dark);
  display: flex; align-items: center; justify-content: center;
  padding: 20px; overflow-y: auto;
}
.onboard-modal[hidden] { display: none; }
.onboard-card {
  max-width: 500px; width: 100%;
  background: var(--bg-card);
  border: 1px solid var(--border-dim);
  border-left: var(--accent-width) solid var(--neon-cyan);
  padding: 34px;
  box-shadow: var(--shadow-pop);
}
/* v1.60 — la marca de la pantalla de bienvenida. Hasta ahora la primera
   pantalla del producto abría con "Tell me what you do" y ni un logo: parecía
   un panel cualquiera.
   El ancho va en clamp y no en % suelto para respetar el mínimo de marca del
   vertical, que son 63px DE ALTO: con la proporción 453:277 eso son 103px de
   ancho, y el suelo de 110 lo garantiza en cualquier pantalla. El techo de
   220 es para que no se coma la tarjeta en escritorio. */
.onboard-brand {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding-bottom: 18px; margin-bottom: 20px;
  border-bottom: 1px solid var(--border-dim);
}
.onboard-logo { width: clamp(110px, 55%, 220px); height: auto; }
.onboard-logo .lw-symbol { color: var(--neon-cyan); }
.onboard-tagline { margin: 0; font-size: 0.875rem; color: var(--text-muted); text-align: center; }
.onboard-step { display: flex; flex-direction: column; gap: 12px; }
.onboard-step[hidden] { display: none; }
.onboard-title { margin: 0; color: var(--neon-cyan); font-size: 1.75rem; }
.onboard-sub { margin: 0 0 10px; color: var(--text-muted); font-size: 0.875rem; }
.onboard-q { font-size: 0.75rem; color: var(--text-muted); margin-top: 6px; }
.onboard-kinds { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.onboard-kind {
  background: transparent; border: 1px solid var(--border-dim);
  color: var(--text-light); font-family: inherit; font-size: 0.875rem;
  padding: 12px 10px; cursor: pointer; transition: 0.2s;
}
.onboard-kind:hover { border-color: var(--neon-cyan); }
.onboard-kind.selected {
  border-color: var(--neon-cyan); color: var(--neon-cyan);
  background: var(--bg-card); box-shadow: 0 0 8px var(--neon-glow);
}
.onboard-btns { display: flex; gap: 10px; margin-top: 8px; }
.onboard-btns .terminal-btn { flex: 1; }
.onboard-btns .terminal-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.onboard-status { margin: 0; font-size: 0.75rem; color: var(--text-muted); min-height: 1em; }
.onboard-link {
  background: none; border: none; color: var(--text-muted);
  font-family: inherit; font-size: 0.875rem;
  cursor: pointer; text-decoration: underline; padding: 4px 0;
  align-self: flex-start;
}
.onboard-link:hover { color: var(--text-light); }
/* v1.52 — la puerta de "ya tengo cuenta". Existía, pero como un enlace
   subrayado de 12px al fondo de la tarjeta: quien llegaba con una cuenta ya
   hecha en otro equipo no lo veía y se creaba otra. Ahora es una fila con su
   separador, centrada y del tamaño del resto del texto. No es un botón
   primario a propósito: la primera visita normal ES el cuestionario, y dos
   botones grandes obligarían a elegir a quien no tiene nada que elegir. */
#ob-have-account {
  align-self: stretch; text-align: center;
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--border-dim);
  color: var(--text-muted); text-decoration: none;
}
#ob-have-account:hover { color: var(--neon-cyan); text-decoration: underline; }

/* v1.30: paso final del onboarding — elegir demo o panel en blanco */
.onboard-start-opts { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.onboard-start-opt {
  display: flex; flex-direction: column; gap: 4px; text-align: left;
  background: transparent; border: 1px solid var(--border-dim);
  color: var(--text-light); font-family: inherit;
  padding: 14px 16px; cursor: pointer; transition: 0.2s;
}
.onboard-start-opt:hover {
  border-color: var(--neon-cyan);
  background: var(--bg-card); box-shadow: 0 0 8px var(--neon-glow);
}
.onboard-start-title { font-weight: 600; font-size: 1rem; color: var(--neon-cyan); }
.onboard-start-desc { font-size: 0.75rem; color: var(--text-muted); }

#ob-goal-row { display: flex; flex-direction: column; gap: 12px; }
#ob-goal-row[hidden] { display: none; }

/* v1.69: la tarifa estimada, en el propio cuestionario. Va como tarjeta
   apoyada en el fondo (--bg-card, el tinte con alfa de v1.42) y con el filo
   del acento a la izquierda: es una RESPUESTA a lo que el usuario acaba de
   escribir, no una etiqueta más del formulario.
   La regla [hidden] es obligatoria en cuanto se fija display — es el fallo
   que se coló en v1.46 y otra vez en v1.47. */
.onboard-rate {
  display: flex; flex-direction: column; gap: 4px;
  margin: 0; padding: 10px 12px;
  background: var(--bg-card); border-left: 3px solid var(--neon-cyan);
  font-size: 0.875rem;
}
.onboard-rate[hidden] { display: none; }
.onboard-rate-sub { font-size: 0.75rem; color: var(--text-muted); line-height: 1.45; }

.income-goal-box { margin-bottom: 15px; }
.income-goal-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
.income-goal-text { font-size: 0.875rem; font-weight: bold; }
.income-goal-edit { display: flex; align-items: center; gap: 8px; font-size: 0.75rem; color: var(--text-faint); }
.income-goal-edit input { width: 70px; padding: 4px 6px !important; }
.income-goal-bar { height: 8px; background: var(--bg-card); border: 1px solid var(--border-dim); overflow: hidden; }
.income-goal-fill { height: 100%; background: var(--neon-cyan); transition: width 0.3s; }
.income-goal-fill.goal-met { background: var(--ok); }

.quick-totals { font-size: 0.875rem; color: var(--neon-cyan); margin-bottom: 15px; padding-bottom: 12px; border-bottom: 1px dashed var(--border-section); }

/* ── EL CICLO DEL DINERO (v1.67) ───────────────────────────────────
   Tres peldaños —trabajado, facturado, cobrado— que se distinguen por
   FORMA además de por color: hueco, anillo y disco. Un color solo no es
   información (WCAG 1.4.1), y aquí encima no hay sitio para escribirla:
   el indicador vive dentro de la celda del importe. */
.money-status { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: flex-end; margin-bottom: 15px; }
.money-status:empty { display: none; }
.money-cell { display: flex; flex-direction: column; gap: 2px; }
.money-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
.money-value { font-size: 1.125rem; font-variant-numeric: tabular-nums; }
.money-cell.is-invoiced .money-value { color: var(--warn); }
.money-cell.is-paid .money-value { color: var(--ok); }
/* la espera larga se dice en el tamaño del rótulo, no en el de la cifra:
   es una nota al pie de un número, no otro número */
.money-stale { font-size: 0.75rem; color: var(--danger); }
.money-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-left: auto; }

/* el indicador por sesión, en la celda del importe. Es un botón y cicla al
   siguiente peldaño; 13px de caja para que el blanco de pulsación no sea
   solo el círculo de 11 */
.bill-chip {
  width: 13px; height: 13px; padding: 0; flex: none;
  display: inline-block; vertical-align: -1px; margin-left: 4px;
  background: transparent; border: 1px solid var(--text-faint); border-radius: 50%;
  cursor: pointer; font: inherit;
}
.bill-chip:hover { border-color: var(--neon-cyan); }
.bill-chip.is-invoiced { border: 3px solid var(--warn); }   /* anillo */
.bill-chip.is-paid { border-color: var(--ok); background: var(--ok); } /* disco */

/* el mismo lenguaje en el desglose, ahí sin ser pulsable */
.bill-dot {
  width: 11px; height: 11px; display: inline-block; vertical-align: -1px;
  border: 1px solid var(--text-faint); border-radius: 50%; margin-right: 3px;
}
.bill-dot.is-invoiced { border: 3px solid var(--warn); }
.bill-dot.is-paid { border-color: var(--ok); background: var(--ok); }
.bill-dot-label { font-size: 0.75rem; color: var(--text-muted); }

.history-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; }
.history-filters select, .history-filters input { flex: 1; min-width: min(130px, 100%); }
.history-totals { font-size: 0.875rem; color: var(--neon-cyan); margin-bottom: 15px; }

.manual-grid { display: grid; grid-template-columns: 1.3fr 1fr 60px 60px 1fr 1.4fr auto; gap: 10px; margin-top: 15px; align-items: center; }
.manual-grid input, .manual-grid select { width: 100%; }
/* la última celda "auto" del grid aloja Añadir + Cancelar (edición) */
.manual-actions { display: flex; gap: 6px; align-items: center; }

.table-wrap { overflow-x: auto; }
/* v1.26: cifras tabulares en todo lo que lista dinero o duraciones —
   en monospace (Sonar) no cambia nada; en las sans/serif de los cozy
   evita que los importes "bailen" de ancho entre filas */
.panel-table td, .quick-totals, .history-totals, .timer-money,
.finance-summary-line, .deadline-days, .checklist-progress {
  font-variant-numeric: tabular-nums;
}
.panel-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 0.875rem; margin-top: 10px; }
/* v1.52: el CUERPO sube a 0.9rem (es lo que se lee), la CABECERA se queda en
   0.75 y no por descuido: con la raíz en 17px ya crece de 12 a 12,75px, y
   subirla más rompe #breakdown-table, que en pantallas de 1024 vive en una
   columna de 389px — "DURATION" en mayúsculas dejaba de caber en los 66px de
   su columna y asomaba 7px fuera del .table-wrap. Una etiqueta de columna se
   lee de refilón; el dato no. */
.panel-table th { text-align: left; color: var(--neon-cyan); border-bottom: 1px solid var(--border-dim); padding: 8px 10px; font-size: 0.75rem; text-transform: uppercase; overflow-wrap: break-word; }
/* Y `break-word`, no `anywhere`, que aquí es la excepción a la regla de
   v1.42: en una tabla `table-layout: fixed` la columna ya tiene su ancho, no
   hay min-content que reducir, y `break-word` solo parte la palabra CUANDO
   no cabe ella sola. "DURATION" en una columna de 78px se va a dos líneas y
   "LAST CONTACT" se queda como está. Con `anywhere` se partirían las que sí
   caben, que es justo lo que no se quiere en una etiqueta corta. */
.panel-table td { padding: 8px 10px; border-bottom: 1px dashed var(--border-section); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.panel-table tr:hover td { background: var(--bg-card); }

/* anchos fijos: un nombre de proyecto o cliente largo ya no puede
   descuadrar el resto de columnas (duración/importe/nota).
   Ojo: son específicos de #history-table (7 columnas), no de .panel-table
   en general, porque #breakdown-table reusa la misma clase con 4 columnas. */
/* v1.70: 13 → 16%, y la fecha PUEDE partirse en dos líneas. La fecha pasó de
   "03/08/26 14:30" a "3 Aug 2026, 14:30" (14 → 17 caracteres) y esta celda
   recorta con ellipsis, o sea que lo que no cabe se pierde EN SILENCIO.
   Medido con fontTools contra fonts/outfit-variable.woff2 a wght 400, más los
   20px de padding de la celda: 138px en Outfit y 172px en Courier New (que es
   la --font-mono de los temas terminal, avance exacto 0,6em). Esta tabla vive
   en la columna derecha de .tracker-layout, así que mide ~930px con el panel
   en su ancho máximo (viewport 1565+) y ~814px a 1440: el 16% da 149px y
   130px. O sea una línea en los temas cozy en pantalla grande, y dos en
   cuanto se estrecha o se cambia a un tema terminal. No hay ningún porcentaje
   que quepa a todos los anchos —a 1024 esta tabla son 389px y harían falta el
   35%—, y por eso lo que arregla esto es el `normal`, no la cifra.

   Y de paso arregla algo que venía de antes y no se había visto: en Courier
   New la fecha VIEJA ya pedía 145px y tenía 121, o sea que llevaba versiones
   cortándose en esos dos temas. Con `normal` no se corta a ningún ancho: pasa
   a dos líneas, que es la misma solución que ya usa la columna de la nota. */
#history-table th:nth-child(1), #history-table td:nth-child(1) { width: 16%; white-space: normal; } /* fecha */
#history-table th:nth-child(2), #history-table td:nth-child(2) { width: 15%; } /* cliente */
#history-table th:nth-child(3), #history-table td:nth-child(3) { width: 15%; } /* proyecto */
/* v1.71: 11 → 14%. Esta celda no lleva solo "1h 30m": lleva delante el icono
   del modo (🍅 o ✎), y un emoji de color NO está en Outfit —cae a la fuente
   de respaldo y ocupa cerca de un cuadratín—. Medido sobre el DOM real: pide
   94px en los temas cozy y 103 en los terminal, y el 11% daba 87 a 1440. O
   sea que la duración llevaba recortándose SIEMPRE, a cualquier ancho, y en
   los terminal se quedaba en el icono y un dígito. Los 3 puntos salen de la
   nota, que es la columna `auto` y a partir de 1200 va sobrada. */
#history-table th:nth-child(4), #history-table td:nth-child(4) { width: 14%; } /* duración */
/* v1.67: 11 → 13%. El importe comparte celda con el indicador de cobro (17px
   con su margen), y la celda recorta con ellipsis: sin ese 2% extra el chip
   de una cifra larga se quedaba fuera y no había dónde pulsar. Los 2 salen
   del proyecto, que es texto y ya recortaba. */
/* v1.71: 13 → 14%. Con el chip dentro, un importe de seis cifras pedía 108px
   en los temas terminal y el 13% daba exactamente 108 a 1440: el chip se
   quedaba en el filo y desaparecía al recortar, que es peor que no pintarlo
   —la fila decía que no hay peldaño de cobro cuando sí lo hay—. El punto
   sale de la nota, igual que el de la duración. */
/* v1.73: 14 → 17%, y es la misma celda por cuarta vez. La v1.71 la midió
   sobre el DOM real y con los datos que había delante; medida contra el PEOR
   contenido —que es lo que pide su propia regla— "€12,345.67 ●" son 139px en
   Courier New y el 15% daba 124 a 1440. Un importe de cinco cifras con
   decimales no es un caso raro en este panel. Los 2 puntos salen de la nota,
   que es la columna `auto` y es texto libre: ahí el recorte sí es correcto. */
#history-table th:nth-child(5), #history-table td:nth-child(5) { width: 17%; }
#history-table th:nth-child(6), #history-table td:nth-child(6) { width: auto; white-space: normal; } /* nota: puede ocupar varias líneas */
/* v1.42: la columna de acciones valía 36px y alojaba dos botones que suman
   63: el de borrar se salía y .table-wrap se comía 40px de scroll oculto.
   Ahora mide lo que ocupa su contenido (63 + los 20 de padding de celda). */
#history-table th:nth-child(7), #history-table td:nth-child(7) { width: 96px; overflow: visible; text-overflow: clip; white-space: nowrap; } /* v1.52: 88 → 96, ver la nota de #leads-table */

/* v1.67: cinco columnas. Los 100% se reparten quitando de cliente y proyecto
   —los dos textos, que ya recortaban— y la de cobro se queda en el 10%: son
   38px de los 389 que mide esta tabla a 1024, sitio para tres puntos de 11
   con su margen. El importe NO se toca: es la cifra que se lee. */
/* v1.71: el 10% de la columna de cobro se calculó para TRES PUNTOS, y lo que
   pinta `breakdownBillingHtml` cuando la fila tiene un solo peldaño es el
   punto MÁS su rótulo. "not invoiced" pide 105px (135 en los terminal) y
   tenía 76 a 1440: se leía "○ not i…", que no dice nada, y solo "paid" cabía
   entero. Sube a 16% y los 6 puntos salen de cliente y proyecto, que son
   texto libre y ya recortaban con ellipsis — ahí recortar es correcto,
   porque no hay ancho que garantice un nombre ajeno. */
#breakdown-table th:nth-child(1), #breakdown-table td:nth-child(1) { width: 22%; } /* cliente */
#breakdown-table th:nth-child(2), #breakdown-table td:nth-child(2) { width: 25%; } /* proyecto */
#breakdown-table th:nth-child(3), #breakdown-table td:nth-child(3) { width: 16%; } /* duración */
#breakdown-table th:nth-child(4), #breakdown-table td:nth-child(4) { width: 18%; } /* importe */
/* El `normal` va con el 19% y no en su lugar: el porcentaje hace que quepa
   en una línea en los cuatro temas, y el `normal` es el seguro de que si
   algún día no cupiera —otra fuente de respaldo, un rótulo más largo— pase a
   dos líneas en vez de recortarse en silencio. Es la misma salida que la
   v1.70 le dio a la fecha del historial. */
#breakdown-table th:nth-child(5), #breakdown-table td:nth-child(5) { width: 19%; white-space: normal; } /* cobro */
.del-btn { background: transparent; border: 1px solid var(--danger); color: var(--danger); cursor: pointer; padding: 2px 8px; font-size: 0.75rem; font-family: inherit; }
.del-btn:hover { background: var(--danger); color: var(--btn-text); }

/* ── 02 PROYECTOS ─────────────────────────────────────────────────── */
/* v1.59: lista a la izquierda, detalle a la derecha. La lista no es
   decoración: lleva el estado, el tiempo registrado y lo cobrado, que es lo
   que antes había que abrir proyecto a proyecto para ver. */
.proj-layout { display: grid; grid-template-columns: minmax(200px, 280px) 1fr; gap: 30px; align-items: start; }
/* sin proyectos no hay nada que enseñar a la derecha: el estado vacío vive en
   la columna de la lista y esta se queda con todo el ancho */
#tab-proyectos.is-empty .proj-layout { grid-template-columns: 1fr; }
#tab-proyectos.is-empty .proj-detail { display: none; }
.proj-toolbar { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.proj-toolbar .terminal-btn-sm { flex: 1; }
.proj-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.proj-row {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; text-align: left; cursor: pointer;
  font-family: inherit; font-size: 1rem; color: var(--text-light);
  border: 1px solid var(--border-dim); background: var(--bg-card);
  border-radius: var(--radius); transition: 0.2s;
}
.proj-row:hover { border-color: var(--neon-cyan); }
/* el marcador del abierto va en box-shadow y no en un borde izquierdo: un
   borde cambia el ancho de la caja y la fila daría un salto al elegirla */
.proj-row.is-active { border-color: var(--neon-cyan); background: var(--hover-tint); box-shadow: inset 3px 0 0 var(--neon-cyan); }
/* cerrado: se atenúa con color, no con opacidad, y el nombre baja un solo
   peldaño porque es lo que se lee para encontrar el proyecto */
.proj-row.is-closed { color: var(--text-faint); }
.proj-row.is-closed .proj-row-name { color: var(--text-muted); }
.proj-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.proj-row-name { color: var(--text-white); font-size: 0.875rem; }
.proj-row-client { font-size: 0.75rem; color: var(--text-muted); }
.proj-row-meta { font-size: 0.75rem; color: var(--text-muted); margin-top: 2px; }
.proj-row-side { flex: 0 0 auto; }

/* El avatar: inicial o emoji sobre un tinte derivado del proyecto. El tono lo
   pone JS en el style (es un dato del proyecto, no del tema); aquí solo la
   forma. La TINTA es var(--text-white) a propósito y no el propio tono: con
   alfa sobre el fondo del tema el tinte funciona en los cuatro, pero un texto
   de color a media luminancia no llega a contraste en los claros. El color
   identifica; quien tiene que leerse es la inicial. */
.proj-avatar {
  flex: 0 0 auto; width: 34px; height: 34px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--radius);
  font-size: 0.75rem; font-weight: bold; line-height: 1;
  color: var(--text-white); text-transform: uppercase;
  overflow: hidden;
}
.proj-avatar-lg { width: 52px; height: 52px; font-size: 1.375rem; }

.proj-badge { font-size: 0.75rem; padding: 2px 7px; border: 1px solid; border-radius: var(--radius); }
.proj-activo { color: var(--ok); border-color: var(--ok); }
.proj-pausado { color: var(--warn); border-color: var(--warn); }
.proj-cerrado { color: var(--text-muted); border-color: var(--border-dim); }
/* mismo criterio que .lead-custom (v1.52): un estado que se inventa el
   usuario no se colorea, porque no sabemos si su "waiting on budget" es
   bueno o malo */
.proj-custom { color: var(--text-light); border-color: var(--border-dim); }

.proj-identity { display: flex; align-items: center; gap: 16px; margin-bottom: 25px; flex-wrap: wrap; }
.proj-icon-fields { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* la etiqueta va fuera de .form-group (esta fila no es una rejilla de campos),
   así que no hereda su estilo y se lo pone aquí */
.proj-icon-fields label { display: block; color: var(--neon-cyan); font-size: 0.875rem; }
.proj-icon-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* el campo del emoji cabe uno y se ve entero: a ancho completo parecía un
   campo de texto libre e invitaba a escribir el nombre otra vez */
#proj-icon { width: 4.5em; flex: 0 0 auto; text-align: center; }
.proj-hues { display: flex; gap: 6px; flex-wrap: wrap; }
.proj-hue {
  width: 22px; height: 22px; padding: 0; cursor: pointer;
  border: 2px solid transparent; border-radius: var(--radius);
  font-family: inherit; font-size: 0.75rem; color: var(--text-light);
}
.proj-hue.is-on { border-color: var(--text-white); }
.proj-hue-auto { width: auto; padding: 0 8px; background: transparent; border-color: var(--border-dim); }
.proj-hue-auto.is-on { border-color: var(--text-white); }

.proj-back { display: none; } /* solo móvil, ver el breakpoint de 640 */
.proj-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: 20px 25px; }
.proj-footer { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-top: 10px; }
.proj-time { font-size: 0.75rem; color: var(--text-muted); }
/* el borrar se va al extremo opuesto del guardar: son las dos acciones de la
   fila y la destructiva no se pulsa por vecindad */
#proj-delete { margin-left: auto; }

/* ── 05 ENLACES ───────────────────────────────────────────────────── */
.links-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: 12px; margin-bottom: 25px; }
.link-card { display: flex; justify-content: space-between; align-items: center; border: 1px solid var(--border-dim); background: var(--bg-card); padding: 12px 15px; }
.link-card a { color: var(--text-white); text-decoration: none; font-size: 0.875rem; }
.link-card a:hover { color: var(--neon-cyan); }
.links-add { display: flex; gap: 10px; flex-wrap: wrap; }
.links-add input { flex: 1; min-width: min(160px, 100%); }

/* ── 07 TODOS ─────────────────────────────────────────────────────── */
.todo-add { display: flex; gap: 10px; margin-bottom: 25px; flex-wrap: wrap; }
.todo-add input { flex: 2; min-width: min(200px, 100%); }
.todo-add select { flex: 1; min-width: 100px; }
.todo-list { list-style: none; padding: 0; margin: 0 0 20px; display: flex; flex-direction: column; gap: 8px; }
.todo-item { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-left: 3px solid var(--border-dim); background: var(--bg-card); }
.todo-item.prio-alta { border-left-color: var(--danger); }
.todo-item.prio-media { border-left-color: var(--neon-cyan); }
.todo-item.prio-baja { border-left-color: var(--border-section); }
.todo-item.done { color: var(--text-faint); }
.todo-item.done .todo-text { text-decoration: line-through; }
.todo-text { flex: 1; }
.todo-prio { font-size: 0.75rem; text-transform: uppercase; color: var(--text-faint); }
/* sin white-space: nowrap a propósito — ver "EL MÍNIMO AUTOMÁTICO" (v1.50) */
.todo-project-tag { font-size: 0.75rem; color: var(--text-muted); padding: 2px 8px; border: 1px solid var(--border-dim); }

/* ── 03 PIPELINE ──────────────────────────────────────────────────── */
.lead-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 140px auto auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.lead-grid input, .lead-grid select { width: 100%; }
.lead-notes { width: 100%; margin-bottom: 15px; }

.lead-badge { font-size: 0.75rem; padding: 3px 8px; border: 1px solid; white-space: nowrap; }
.lead-contactar { color: var(--text-light); border-color: var(--border-dim); }
.lead-auditado { color: var(--neon-cyan); border-color: var(--neon-cyan); }
.lead-propuesta { color: var(--warn); border-color: var(--warn); }
/* v1.66: era un gris pizarra fijo (#6a7a8a), que sobre Daybreak se quedaba
   en 3,4:1 y encima no seguía al tema. "Frío" se dice atenuando, que es
   justo lo que --text-faint significa. */
.lead-frio { color: var(--text-faint); border-color: var(--text-faint); }
/* v1.52: chip neutro para los estados que se inventa el usuario. Como los
   estados de proyecto: no adivinamos si su "waiting on budget" es bueno o
   malo, así que va con el color del texto y sin semántica. */
.lead-custom { color: var(--text-light); border-color: var(--border-dim); }

#leads-table th:nth-child(1), #leads-table td:nth-child(1) { width: 18%; }
#leads-table th:nth-child(2), #leads-table td:nth-child(2) { width: 14%; }
#leads-table th:nth-child(3), #leads-table td:nth-child(3) { width: 15%; }
/* v1.73: 13 → 16% y `normal`. "3 Aug 2026 ⚠" pide 133px en los temas
   terminal y el 13% daba 97 a 900 y 112 a 1024: la fecha del último contacto
   llevaba recortándose desde la v1.70, que es la que cambió el formato y
   midió SOLO la del historial. Los 3 puntos salen de las dos columnas de
   texto libre, que ya recortaban. El ⚠ marca el lead frío: si se pierde con
   el recorte, la fila deja de avisar. */
#leads-table th:nth-child(4), #leads-table td:nth-child(4) { width: 16%; white-space: normal; }
#leads-table th:nth-child(5), #leads-table td:nth-child(5) { width: auto; white-space: normal; }
/* v1.42: mismo fallo que en #history-table, y aquí peor — son TRES botones
   (convertir en cliente, editar, borrar) que suman 144 en 90px de columna. */
/* v1.52: 170 → 182px. Es EL MISMO fallo de v1.42 volviendo por otra puerta:
   una columna de acciones medida en píxeles contra unos botones medidos en
   rem. Al subir la raíz de 16 a 17px los tres botones crecieron un 6%, el
   de borrar se salió 2,25px y .table-wrap sacó su barra de scroll. Cada vez
   que se toque el tamaño de letra hay que volver a mirar estas anchuras
   fijas: la de aquí, la de #history-table (col 7) y la de #domains-table
   (col 6). El margen que se deja ahora es holgado a propósito. */
#leads-table th:nth-child(6), #leads-table td:nth-child(6) { width: 182px; overflow: visible; text-overflow: clip; white-space: nowrap; }

.edit-btn { background: transparent; border: 1px solid var(--neon-cyan); color: var(--neon-cyan); cursor: pointer; padding: 2px 8px; font-size: 0.75rem; font-family: inherit; margin-right: 4px; }
.edit-btn:hover { background: var(--neon-cyan); color: var(--btn-text); }
.convert-btn { background: transparent; border: 1px solid var(--ok); color: var(--ok); cursor: pointer; padding: 2px 8px; font-size: 0.75rem; font-family: inherit; font-weight: bold; margin-right: 4px; }
.convert-btn:hover { background: var(--ok); color: var(--btn-text); }

/* ── 04 VENCIMIENTOS ──────────────────────────────────────────────── */
.deadline-grid { display: grid; grid-template-columns: 1.6fr 1fr 160px auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.deadline-grid input, .deadline-grid select { width: 100%; }

.deadlines-list { list-style: none; padding: 0; margin: 20px 0 0; display: flex; flex-direction: column; gap: 6px; }
.deadline-item {
  display: flex; align-items: center; gap: 16px;
  padding: 10px 14px; border-left: 3px solid var(--border-dim);
  background: var(--bg-card); font-size: 0.875rem;
}
.deadline-item[data-jump] { cursor: pointer; }
.deadline-item[data-jump]:hover { background: var(--hover-tint); }
.deadline-date { color: var(--text-muted); font-size: 0.75rem; min-width: 65px; }
.deadline-label { flex: 1; }
.deadline-type { font-size: 0.75rem; text-transform: uppercase; color: var(--text-faint); }
.deadline-days { font-size: 0.75rem; font-weight: bold; min-width: 60px; text-align: right; }
.deadline-goto { color: var(--neon-cyan); }

/* urgencia — compartido entre vencimientos (li) y dominios (td) */
.due-critical { border-left-color: var(--danger); color: var(--danger); }
.due-critical .deadline-days { color: var(--danger); }
.due-soon { border-left-color: var(--warn); }
.due-soon .deadline-days { color: var(--warn); }
.due-past { border-left-color: var(--danger); color: var(--text-muted); }
.due-past .deadline-days { color: var(--danger); }
.due-future { border-left-color: var(--border-dim); }
td.due-critical { color: var(--danger); font-weight: bold; }
td.due-soon { color: var(--warn); }

/* ── 10 DOMINIOS ──────────────────────────────────────────────────── */
.domain-grid { display: grid; grid-template-columns: 1.3fr 1fr 1fr 150px 1.3fr auto auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.domain-grid input { width: 100%; }

/* col 6 pasa de 60px a 130px: ahora son 3 botones (renovado/editar/borrar),
   no 2 — mismo criterio que leads-table (90px para 3 botones, aquí un poco
   más porque "✓ RENOVADO" es más ancho que "→ CLIENTE") */
#domains-table th:nth-child(1), #domains-table td:nth-child(1) { width: 18%; }
#domains-table th:nth-child(2), #domains-table td:nth-child(2) { width: 14%; }
#domains-table th:nth-child(3), #domains-table td:nth-child(3) { width: 15%; }
/* v1.73: 13 → 15% y `normal`, el mismo caso que #leads-table col 4 — la
   fecha de renovación pide 109px desde la v1.70 y tenía 97 a 900. Aquí
   además la celda lleva la clase de urgencia: una fecha de renovación
   recortada es un dominio que se cae sin avisar. */
#domains-table th:nth-child(4), #domains-table td:nth-child(4) { width: 15%; white-space: normal; }
/* "open →" son 80px con la flecha, que no está en Outfit y cae al respaldo
   del sistema ocupando casi un cuadratín. El 11% daba 82 a 900: al filo. */
#domains-table th:nth-child(5), #domains-table td:nth-child(5) { width: 12%; white-space: normal; }
/* v1.52: 130 → 142. v1.62: 142 → 156, y es la tercera vuelta del mismo fallo
   que describe #leads-table. La escala de siete peldaños sube .convert-btn de
   0.7 a 0.75rem, y "✓ Renewed" es el rótulo más ancho de las tres columnas de
   acciones: medido en Outfit, el contenido pasa de 137,31 a 140,50px de 142.
   En los temas terminal la monoespaciada lo pone por encima del ancho actual.
   Aquí no basta con mirarlo: hay que medirlo, y por eso el margen es holgado. */
#domains-table th:nth-child(6), #domains-table td:nth-child(6) { width: 156px; overflow: visible; text-overflow: clip; }

/* ── 06 ESTRATEGIA ────────────────────────────────────────────────── */
.strategy-ref { margin-top: 10px; }
.strategy-ref p { font-size: 0.875rem; }
#strategy-plan-table th:nth-child(1), #strategy-plan-table td:nth-child(1) { width: 25%; }
.strategy-pending-title { margin-top: 40px; color: var(--neon-cyan); font-size: 1rem; }

.tier-card { background: var(--bg-card); border: 1px solid var(--border-dim); border-left: var(--accent-width) solid var(--neon-cyan); padding: 20px 25px; margin-bottom: 25px; }
.tier-title { color: var(--text-white); }
/* título editable (v1.18): input disfrazado de título — el borde solo
   aparece al pasar el ratón o al enfocar, en reposo se lee como un h3 */
.tier-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.tier-title-input {
  flex: 1; min-width: 0; background: transparent; border: 1px solid transparent;
  color: var(--text-white); font-family: inherit; font-size: 1.125rem; font-weight: bold;
  padding: 4px 8px; margin-left: -8px;
}
.tier-title-input:hover, .tier-title-input:focus { border-color: var(--border-dim); }
#tier-new { margin-bottom: 25px; }
.tier-veredicto {
  width: 100%; background: transparent; border: 1px dashed var(--border-section); border-left: var(--accent-width) solid var(--neon-cyan);
  color: var(--neon-cyan); font-size: 0.875rem; font-family: inherit; padding: 10px 12px; resize: vertical; margin-bottom: 15px;
}
.tier-veredicto:focus { border-color: var(--neon-cyan); }
.tier-items { list-style: none; padding: 0; margin: 0 0 15px; display: flex; flex-direction: column; gap: 6px; }
.tier-item { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-left: 3px solid var(--border-dim); background: rgba(0,0,0,0.15); }
/* temas claros (naturaleza, ártico): la superposición oscura por defecto de
   .tier-item quedaría como una mancha gris sobre fondo claro */
body.saltmarsh .tier-item, body.daybreak .tier-item { background: rgba(255,255,255,0.5); }
.tier-item-active { border-left-color: var(--neon-cyan); }
.tier-item-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.tier-item-note { font-size: 0.75rem; color: var(--text-faint); }
.tier-item-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.tier-active-toggle {
  background: transparent; border: 1px solid var(--border-dim); color: var(--text-muted);
  font-family: inherit; font-size: 0.75rem; padding: 4px 10px; cursor: pointer; text-transform: uppercase;
}
.tier-active-toggle.is-active { border-color: var(--neon-cyan); color: var(--neon-cyan); box-shadow: 0 0 8px var(--neon-glow) inset; }
.tier-active-toggle:hover { color: var(--text-light); }
.tier-add { display: flex; gap: 10px; flex-wrap: wrap; }
.tier-add input { flex: 1; min-width: min(160px, 100%); }

/* ── 08 IDEAS ─────────────────────────────────────────────────────── */
.ideas-review-box { display: flex; align-items: center; gap: 15px; margin-bottom: 20px; flex-wrap: wrap; }
.ideas-review-text { font-size: 0.875rem; }
.ideas-review-text.review-stale { color: var(--danger); font-weight: bold; }
/* .stale-text: mismo mecanismo de aviso "hace X días" reusado fuera de
   Ideas — backup atrasado (footer) y revisión de Estrategia (sección 06) */
.stale-text { font-size: 0.875rem; }
.stale-text.review-stale { color: var(--danger); font-weight: bold; }
.idea-add-box { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 25px; align-items: flex-start; }
.idea-add-box textarea { flex: 1; min-width: min(240px, 100%); font-family: inherit; resize: vertical; }
.ideas-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.idea-item { background: var(--bg-card); border: 1px solid var(--border-dim); border-left: var(--accent-width) solid var(--neon-cyan); padding: 14px 16px; }
.idea-text { margin-bottom: 10px; }
.idea-meta { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; font-size: 0.75rem; }
.idea-date { color: var(--text-faint); min-width: min(120px, 100%); }
.idea-tier-select { flex: 1; min-width: min(160px, 100%); }

/* ── 09 CHECKLISTS ────────────────────────────────────────────────── */
.checklist-project-box { display: flex; align-items: center; gap: 12px; margin-bottom: 25px; }
.checklist-project-box label { white-space: nowrap; }
.checklist-project-box select { min-width: min(260px, 100%); }
.checklist-template-group { margin-bottom: 35px; }
.checklist-template-header { display: flex; align-items: center; gap: 15px; margin-bottom: 15px; border-bottom: 1px dashed var(--border-section); padding-bottom: 10px; }
.checklist-template-header h3 { margin: 0; flex: 1; color: var(--neon-cyan); }
.checklist-instances { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 20px; }
.checklist-card { background: var(--bg-card); border: 1px solid var(--border-dim); padding: 20px; }
.checklist-header { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; }
.checklist-project-move { flex: 1; min-width: min(140px, 100%); font-weight: bold; }
.checklist-progress { font-size: 0.75rem; color: var(--neon-cyan); white-space: nowrap; }
.checklist-items { list-style: none; padding: 0; margin: 0 0 15px; display: flex; flex-direction: column; gap: 6px; }
.checklist-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; border-bottom: 1px dashed var(--border-section); }
.checklist-item label { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 0.875rem; }
.checklist-item.checklist-done label span { text-decoration: line-through; color: var(--text-faint); }
.checklist-add { display: flex; gap: 10px; }
.checklist-add input { flex: 1; }

/* ── 11 FINANZAS ──────────────────────────────────────────────────── */
.finance-subheading { margin-top: 40px; }
.finance-chart-box { background: var(--bg-card); border: 1px solid var(--border-dim); padding: 20px 25px; margin-bottom: 30px; }
.finance-chart-row { display: flex; gap: 30px; align-items: center; flex-wrap: wrap; margin-top: 15px; }
.money-chart { width: 180px; height: 180px; flex-shrink: 0; }
.money-chart-side { flex: 1; min-width: min(220px, 100%); }
.money-chart-total { font-size: 1.125rem; color: var(--neon-cyan); margin-bottom: 12px; font-weight: bold; }
.money-chart-legend { display: flex; flex-direction: column; gap: 8px; font-size: 0.875rem; }
.legend-item { display: flex; align-items: center; gap: 10px; }
.legend-swatch, .table-swatch { width: 12px; height: 12px; display: inline-block; flex-shrink: 0; border: 1px solid var(--border-dim); }

.networth-chart { width: 100%; height: 160px; display: block; }
.networth-chart-info { font-size: 0.875rem; color: var(--neon-cyan); margin-top: 10px; }

/* input[type=color] nativo es pequeño y feo por defecto: lo igualamos al resto de campos */
input[type="color"] {
  width: 100%; height: 42px; padding: 3px; background: var(--bg-dark);
  border: 1px solid var(--input-border); border-left: var(--accent-width) solid var(--neon-cyan);
  cursor: pointer;
}

.account-grid { display: grid; grid-template-columns: 1.3fr 1fr 1fr 1fr 90px 70px auto auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.account-grid input, .account-grid select { width: 100%; }
/* las 6 columnas suman 100% exacto: mezclar % con px fijo puede pasarse
   del ancho del contenedor y disparar scroll horizontal */
/* v1.73: la columna de acciones deja de ser un PORCENTAJE. Es la cuarta
   vuelta del fallo de v1.42 y v1.52, y aquí llevaba desde el principio: dos
   botones que suman 65px con su padding de celda no encogen, y el 8% solo
   les da eso a partir de 1214px de viewport. Por debajo se salían de la
   tabla — y no se veía midiendo `scrollWidth > clientWidth` celda a celda,
   porque con `overflow: visible` el contenido se pinta FUERA y el scroll se
   lo come el .table-wrap. Pasa a los 96px que ya usa #history-table para
   ese mismo par de botones, medidos en el navegador. Y el nombre pasa a
   `auto`: es el patrón de las otras tres tablas — unos % para los datos, la
   columna de texto libre en auto y las acciones en píxeles. */
#accounts-table th:nth-child(1), #accounts-table td:nth-child(1) { width: auto; }
#accounts-table th:nth-child(2), #accounts-table td:nth-child(2) { width: 18%; }
#accounts-table th:nth-child(3), #accounts-table td:nth-child(3) { width: 15%; }
/* el saldo pide 124px en los temas terminal ("€123,456.78" en Courier New):
   el 16% se los daba a partir de 930px de tabla y esta cifra no puede salir
   recortada — un saldo a medias no es un dato viejo, es un dato falso. */
#accounts-table th:nth-child(4), #accounts-table td:nth-child(4) { width: 20%; }
#accounts-table th:nth-child(5), #accounts-table td:nth-child(5) { width: 6%; }
#accounts-table th:nth-child(6), #accounts-table td:nth-child(6) { width: 96px; overflow: visible; text-overflow: clip; }

.holding-grid { display: grid; grid-template-columns: 0.9fr 1.2fr 0.8fr 0.9fr 0.9fr 90px 70px auto auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.holding-grid input, .holding-grid select { width: 100%; }
/* v1.73: nueve columnas de cifras es la tabla más apretada del panel, y era
   la que peor estaba: CINCO de sus columnas numéricas recortaban ya a 1024px
   de viewport, o sea en un portátil. Ninguna de esas cifras es texto libre
   del usuario —son precios, participaciones y la ganancia—, así que el
   recorte silencioso las convierte en datos falsos.
   El reparto se calcula contra el peor caso medido en Courier New (los temas
   terminal), no contra el típico: participaciones 109px, precios 106, valor
   124, ganancia 124 partida. El nombre pasa a `auto` y el ticker baja al 7%
   —son 4-6 caracteres— porque los dos son texto libre y ahí el recorte SÍ es
   correcto: no hay ancho que garantice un nombre ajeno (regla de v1.71).
   Por debajo de 1024 no hay reparto que valga y la tabla pasa a tarjetas;
   ver el bloque de tarjetas al final del archivo. */
#holdings-table th:nth-child(1), #holdings-table td:nth-child(1) { width: 7%; }
#holdings-table th:nth-child(2), #holdings-table td:nth-child(2) { width: auto; }
#holdings-table th:nth-child(3), #holdings-table td:nth-child(3) { width: 13%; }
#holdings-table th:nth-child(4), #holdings-table td:nth-child(4) { width: 13%; }
#holdings-table th:nth-child(5), #holdings-table td:nth-child(5) { width: 13%; }
#holdings-table th:nth-child(6), #holdings-table td:nth-child(6) { width: 15%; }
/* la ganancia es "+€12,345.67 (+123.4%)": 213px enteros, más que ninguna otra
   celda del panel. Con `normal` el paréntesis cae a la segunda línea cuando
   no cabe, en vez de perderse — el seguro que la v1.70 dejó para las celdas
   de texto NUESTRO. Es lo que baja el mínimo de esta tabla de 1105 a 1008. */
#holdings-table th:nth-child(7), #holdings-table td:nth-child(7) { width: 15%; white-space: normal; }
#holdings-table th:nth-child(8), #holdings-table td:nth-child(8) { width: 4%; }
#holdings-table th:nth-child(9), #holdings-table td:nth-child(9) { width: 96px; overflow: visible; text-overflow: clip; }
.gain-positive { color: var(--neon-cyan); }
.gain-negative { color: var(--danger); }
/* v1.68: un precio que el usuario no actualiza desde hace más de un mes. No
   se atenúa —la regla de v1.66: nada de bajar el contraste para decir "esto
   importa menos"—, se subraya a trazos, que es marca sin pérdida de lectura.
   El texto del total sí va en --text-muted porque es una nota, no un dato. */
.price-stale { text-decoration: underline dotted var(--warn); text-underline-offset: 3px; cursor: help; }
.price-stale-note { color: var(--text-muted); font-size: 0.75rem; font-weight: normal; }
.finance-summary-line { font-size: 0.875rem; margin: 12px 0 25px; font-weight: bold; }


.expense-grid { display: grid; grid-template-columns: 1.4fr 100px 90px 120px 140px auto auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.expense-grid input, .expense-grid select { width: 100%; }
.expense-notes { width: 100%; margin-bottom: 15px; }
/* v1.73: acciones de 8% a 96px por el mismo motivo que en accounts, y los
   tres datos suben porque los tres recortaban a partir de 800px de viewport:
   el importe pide 106px, la frecuencia 100 ("quarterly") y el próximo cargo
   109 desde que la v1.70 escribe la fecha con el nombre del mes. La nota
   pasa a `auto` y deja de llevar el `normal`: es lo secundario de la fila y
   es texto libre, o sea el sitio donde el recorte sí es correcto. */
#expenses-table th:nth-child(1), #expenses-table td:nth-child(1) { width: 24%; }
#expenses-table th:nth-child(2), #expenses-table td:nth-child(2) { width: 16%; }
#expenses-table th:nth-child(3), #expenses-table td:nth-child(3) { width: 15%; }
/* la fecha con `normal`, como la del historial (v1.70): si no cabe, dos
   líneas en vez de un recorte que nadie ve. */
#expenses-table th:nth-child(4), #expenses-table td:nth-child(4) { width: 16%; white-space: normal; }
#expenses-table th:nth-child(5), #expenses-table td:nth-child(5) { width: auto; }
#expenses-table th:nth-child(6), #expenses-table td:nth-child(6) { width: 96px; overflow: visible; text-overflow: clip; }

/* ── BUSCADOR (Ctrl+K) ───────────────────────────────────────────── */
.search-modal {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 12vh;
}
.search-modal[hidden] {
  display: none;
}
.search-box {
  width: 92%; max-width: 640px;
  background: var(--bg-dark); border: 1px solid var(--neon-cyan);
  box-shadow: var(--shadow-modal);
  padding: 4px;
}
.search-box input {
  width: 100%; font-size: 1rem; padding: 14px 16px;
  border: none; border-bottom: 1px solid var(--border-dim);
  background: transparent;
}
.search-box input:focus { background: transparent; }
.search-results { list-style: none; margin: 0; padding: 0; max-height: 50vh; overflow-y: auto; }
.search-result { display: flex; align-items: center; gap: 12px; padding: 10px 16px; cursor: pointer; border-bottom: 1px dashed var(--border-section); }
.search-result:hover { background: var(--bg-card); }
/* v1.72: aquí iba el número de la sección y ahora va su icono. El font-size
   se queda en el de la nav (y no en el 0.75rem que tenía el número) porque
   .sec-icon mide en em: así el icono del resultado es del mismo tamaño que
   el de la pestaña a la que lleva. */
.search-result-tag { font-size: 0.875rem; color: var(--neon-cyan); flex: none; display: flex; }
/* min-width: 0 porque desde v1.70 aquí caben trozos de nota: sin él, el
   mínimo automático de un item flex es su min-content y una palabra larga
   ensancharía la fila por encima del modal (el bloque "EL MÍNIMO
   AUTOMÁTICO" de más abajo, otra vez). El texto sí puede partirse. */
.search-result-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* v1.70: de qué es el trozo de texto (proyecto · nota, proyecto · día). Solo
   sale en los resultados de texto libre; un nombre se explica solo. */
.search-result-ctx {
  flex: none; font-size: 0.75rem; color: var(--text-muted); white-space: nowrap;
}
/* v1.63: resultado que vive en una sección apagada. Se enseña igual —no
   encontrar algo que sí tienes es peor que un resultado de más— y al pulsarlo
   se vuelve a encender la sección. La chapa dice cuál, porque si no el clic
   parece que se lleva al usuario a una pestaña que no existía. */
.search-result-off {
  flex: none; font-size: 0.75rem; padding: 2px 8px; border-radius: 999px;
  background: var(--hover-tint); color: var(--text-muted); white-space: nowrap;
}
.search-result.is-hidden-sec .search-result-text { color: var(--text-muted); }

/* ── DIÁLOGO PROPIO (v1.28) ──────────────────────────────────────── */
/* Sustituye alert/confirm/prompt nativos. Solo variables de tema: sale
   igual de cozy o de terminal sin CSS por tema. */
.app-dialog {
  position: fixed; inset: 0; z-index: 1200;
  background: rgba(0,0,0,0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.app-dialog[hidden] { display: none; }
.app-dialog-box {
  width: 92%; max-width: 440px;
  /* --bg-dark es OPACO (no como --bg-card, que en los cozy es un tinte al
     4%): la caja va sobre un overlay translúcido, igual que el buscador */
  background: var(--bg-dark);
  border: 1px solid var(--border-dim);
  border-left: var(--accent-width) solid var(--neon-cyan);
  box-shadow: var(--shadow-modal);
  padding: 26px 28px;
  display: flex; flex-direction: column; gap: 14px;
}
.app-dialog-title { margin: 0; color: var(--neon-cyan); font-size: 1.125rem; }
.app-dialog-title[hidden] { display: none; }
.app-dialog-msg { margin: 0; font-size: 0.875rem; line-height: 1.5; white-space: pre-line; }
.app-dialog-input { width: 100%; }
.app-dialog-input[hidden] { display: none; }
.app-dialog-btns { display: flex; gap: 10px; margin-top: 4px; }
.app-dialog-btns .terminal-btn { flex: 1; padding: 12px 20px; }
.app-dialog-btns .terminal-btn[hidden] { display: none; }

/* ── 04 VENCIMIENTOS: VISTA MENSUAL ──────────────────────────────── */
.deadlines-view-toggle { display: flex; gap: 8px; margin: 20px 0 10px; }
.deadlines-view-toggle .view-active { background: var(--neon-cyan); color: var(--bg-dark); }
.deadlines-month { margin-top: 15px; }
.month-nav { display: flex; align-items: center; justify-content: center; gap: 20px; margin-bottom: 15px; }
.month-label { color: var(--neon-cyan); font-weight: bold; text-transform: capitalize; min-width: min(160px, 100%); text-align: center; }
.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.month-dow { text-align: center; font-size: 0.75rem; color: var(--text-faint); padding-bottom: 6px; }
.month-cell {
  aspect-ratio: 1 / 1; border: 1px solid var(--border-dim);
  padding: 6px; display: flex; flex-direction: column; gap: 4px;
}
.month-cell-empty { border-color: transparent; }
.month-cell-today { border-color: var(--neon-cyan); box-shadow: 0 0 8px var(--neon-glow) inset; }
.month-cell-day { font-size: 0.75rem; color: var(--text-muted); }
.month-dots { display: flex; gap: 3px; flex-wrap: wrap; }
.month-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; background: var(--border-dim); }
.month-dot.due-critical { background: var(--danger); }
.month-dot.due-soon { background: var(--warn); }
/* v1.66: los puntos también dejan la opacidad. Eran el ÚNICO indicio de que
   un día tiene algo, y a 0,6/0,7 se quedaban por debajo del 3:1 que pide
   WCAG 1.4.11 para un objeto gráfico. El vencido pasa al gris atenuado en
   vez del rojo a medio gas: ya pasó, y así no se confunde con el crítico,
   que es el rojo entero. */
.month-dot.due-past { background: var(--text-muted); }
.month-dot.due-future { background: var(--neon-cyan); }

/* ── 12 HORARIO ───────────────────────────────────────────────────── */
.schedule-grid-form { display: grid; grid-template-columns: 130px 110px 110px 1fr auto; gap: 10px; align-items: center; margin-bottom: 10px; }
.schedule-grid-form input, .schedule-grid-form select { width: 100%; }
.schedule-week { display: grid; grid-template-columns: repeat(7, 1fr); gap: 12px; margin-top: 20px; }
.schedule-day { background: var(--bg-card); border: 1px solid var(--border-dim); padding: 12px; min-height: 120px; }
.schedule-day-title { font-size: 0.75rem; color: var(--neon-cyan); margin: 0 0 10px; text-align: center; }
.schedule-day-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.schedule-block { border-left: 3px solid var(--neon-cyan); padding: 6px 8px; font-size: 0.75rem; background: rgba(0,0,0,0.15); }
body.saltmarsh .schedule-block, body.daybreak .schedule-block { background: rgba(255,255,255,0.5); }
.schedule-block-time { display: block; font-size: 0.75rem; color: var(--text-muted); }
.schedule-block-label { display: block; margin: 2px 0 6px; }
.schedule-block .del-btn { padding: 1px 6px; font-size: 0.75rem; }
.schedule-empty { list-style: none; color: var(--text-faint); font-size: 0.75rem; text-align: center; padding: 10px 0; }


/* ── 15 HERRAMIENTAS ──────────────────────────────────────────────
   Tres bloques apilados, cada uno una tarjeta apoyada en el fondo. Ni una
   variable de tema nueva: la tira horaria del reloj se tiñe con
   --neon-cyan y --hover-tint, que existen en los cuatro temas. Una
   variable que faltara en uno haría que ese tema heredase el :root, o sea
   los valores de Sonar — el fallo que estuvo suelto de la v1.10 a la v1.19. */
.tool-block { border: 1px solid var(--border-dim); background: var(--bg-card); padding: 18px 20px; margin-bottom: 24px; }
.tool-title { margin: 0 0 4px; font-size: 1.125rem; color: var(--neon-cyan); }
.tool-block > .hint:first-of-type { margin-top: 4px; }

/* la línea de cálculo: el campo manda y el "=" es del tamaño del texto */
.calc-line { display: flex; gap: 8px; align-items: stretch; margin-bottom: 4px; }
.calc-line input { flex: 1 1 auto; font-size: 1.125rem; }
.calc-msg { color: var(--danger); font-size: 0.875rem; margin: 6px 0; }
.calc-msg[hidden] { display: none; }
.calc-history { list-style: none; margin: 10px 0; padding: 0; }
.calc-row { display: flex; justify-content: space-between; gap: 16px; padding: 6px 4px; font-size: 0.875rem; cursor: pointer; border-bottom: 1px solid var(--border-dim); }
.calc-row:hover { background: var(--hover-tint); }
.calc-row-expr { color: var(--text-faint); }
.calc-row-value { font-weight: bold; white-space: nowrap; }

/* salida de los dos formularios de dinero: filas etiqueta/cifra y, en la
   tarifa, el número gordo arriba */
.tool-out { margin-top: 14px; }
.tool-out-row { display: flex; justify-content: space-between; gap: 16px; padding: 5px 0; font-size: 0.875rem; }
.tool-out-row.is-total { border-top: 1px solid var(--border-dim); font-weight: bold; }
.tool-out-big { font-size: 1.75rem; color: var(--neon-cyan); margin-bottom: 8px; }
.tool-out-unit { font-size: 1rem; color: var(--text-faint); }

/* reloj mundial */
.tz-add { display: flex; gap: 8px; margin: 12px 0; }
.tz-add input { flex: 1 1 auto; }
.tz-list { display: flex; flex-direction: column; gap: 10px; }
.tz-row { border: 1px solid var(--border-dim); padding: 10px 12px; }
.tz-row-head { display: flex; align-items: baseline; gap: 10px; }
.tz-city { font-weight: bold; }
.tz-meta { flex: 1 1 auto; font-size: 0.75rem; color: var(--text-faint); }
.tz-clock { font-size: 1.375rem; }
.tz-overlap { font-size: 0.75rem; color: var(--text-muted); }
/* 24 casillas de una hora. La jornada propia se tiñe con el acento; el
   0.35 de la noche de allí es lo que hace que un solape a las 3 de la
   mañana se vea de un vistazo en vez de leerse. */
.tz-strip { display: grid; grid-template-columns: repeat(24, 1fr); gap: 1px; height: 14px; margin: 8px 0 6px; }
.tz-cell { background: var(--hover-tint); border-radius: var(--radius); }
.tz-cell.is-night { opacity: 0.35; }
.tz-cell.is-work { background: var(--neon-cyan); }
.tz-cell.is-now { box-shadow: inset 0 -4px 0 var(--warn); }

/* conversor de imágenes */
.img-pick { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 14px 0; }
#img-input { font-family: inherit; font-size: 0.875rem; color: var(--text-light); max-width: 100%; }
#img-input::file-selector-button { font-family: inherit; font-size: 0.875rem; background: transparent; color: var(--neon-cyan); border: 1px solid var(--neon-cyan); padding: 6px 12px; margin-right: 10px; cursor: pointer; }
#img-input::file-selector-button:hover { background: var(--neon-cyan); color: var(--btn-text); }
.img-quality-val { color: var(--text-faint); }
/* el range no entra en la regla de ancho de .settings-grid (que solo mira
   number y select) y se quedaba en los ~130px del navegador */
#img-quality { width: 100%; }
.img-results { display: flex; flex-direction: column; gap: 10px; }
.img-result { display: flex; align-items: center; gap: 12px; border: 1px solid var(--border-dim); padding: 8px 10px; }
/* el damero se ve DEBAJO de la miniatura: sin él un PNG transparente
   parece un cuadro en blanco y no se sabe si la conversión salió */
.img-thumb {
  width: 48px; height: 48px; flex: 0 0 auto; object-fit: cover;
  border: 1px solid var(--border-dim);
  background-image: repeating-conic-gradient(rgba(128, 128, 128, 0.25) 0% 25%, transparent 0% 50%);
  background-size: 12px 12px;
}
.img-result-info { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.img-result-meta { font-size: 0.75rem; color: var(--text-faint); }

/* ── ACCESIBILIDAD (v1.25) ─────────────────────────────────────── */
/* anillo de foco visible SOLO al navegar con teclado (:focus-visible):
   los inputs señalan el foco con el borde desde siempre, pero botones,
   tabs, selects y summaries no daban ninguna señal con Tab. Va al
   final del archivo a propósito: gana por orden a los outline:none
   de misma especificidad de más arriba. */
button:focus-visible,
a:focus-visible,
summary:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--neon-cyan);
  outline-offset: 2px;
}

/* v1.27: si el SO pide menos movimiento, se respeta — el cursor de
   Sonar y el parpadeo del timer en espera dejan de animar, y las
   transiciones quedan a efectos prácticos instantáneas */
@media (prefers-reduced-motion: reduce) {
  .cursor, .timer-clock.awaiting { animation: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}
/* v1.47: el mismo efecto, a mano, para quien no lo pide al sistema pero lo
   quiere aquí. Se duplica la regla en vez de refactorizar a variables porque
   una media query no se puede "activar" desde JS: la clase es la única
   palanca, y tener las dos escritas al lado deja claro que son lo mismo. */
body.reduce-motion .cursor,
body.reduce-motion .timer-clock.awaiting { animation: none; }
body.reduce-motion *, body.reduce-motion *::before, body.reduce-motion *::after {
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

/* ── LECTURA FÁCIL (v1.47) ─────────────────────────────────────────
   Para leer con dislexia. NO es solo cambiar de tipografía: la evidencia
   sobre las fuentes específicas es floja, y lo que ayuda de forma constante
   es el AIRE — más espacio entre letras, palabras y líneas — más una sans
   corriente sin adornos. Así además no hace falta descargar ninguna fuente,
   que la regla del producto es que todo asset viva en el repo.
   Fiel a "temas = solo variables": esto es una variable y tres propiedades
   heredables. Si algún día entra OpenDyslexic en fonts/, se mete delante en
   la pila de --font-mono de aquí abajo y ya está. */
body.easy-read {
  --font-mono: "Outfit", "Verdana", "Segoe UI", system-ui, sans-serif;
  letter-spacing: 0.045em;
  word-spacing: 0.14em;
  line-height: 1.75;
}
/* los títulos llevan su propio interletraje en algunos temas: se normaliza
   para que no queden apretados justo donde más se lee */
body.easy-read h1, body.easy-read h2, body.easy-read h3 { letter-spacing: 0.03em; }
/* las cifras NO se separan: un importe o un reloj con las cifras sueltas se
   lee peor, no mejor (mismo criterio que el overflow-wrap de v1.42) */
body.easy-read .timer-clock, body.easy-read .quick-totals,
body.easy-read .money, body.easy-read td.num { letter-spacing: normal; word-spacing: normal; }
/* v1.64: y las cifras de la sección 15 — el reloj, los importes del
   desglose de IVA y la tarifa por hora */
body.easy-read .tz-clock, body.easy-read .tool-out .num,
body.easy-read .tool-out-big, body.easy-read .calc-row-value { letter-spacing: normal; word-spacing: normal; }
body.easy-read em, body.easy-read i { font-style: normal; font-weight: 600; }

/* ── EL MÍNIMO AUTOMÁTICO (v1.48) ──────────────────────────────────
   Un <select> aporta como ancho mínimo el de su opción más larga (un nombre
   de proyecto largo, por ejemplo), y el mínimo automático de un item de
   grid/flex es su min-content: basta ese select para ensanchar la columna
   entera y, con ella, la página. min-width:0 desactiva ese mínimo; el campo
   se queda en el 100% de su hueco y el texto largo se corta al mostrarlo,
   no al desplegarlo.

   POR QUÉ ESTÁ AQUÍ Y NO EN EL MÓVIL: nació en v1.41 dentro del
   @media (max-width: 640px), y ahí se quedó — con lo que el fallo seguía
   VIVO en todo lo demás. Medido en v1.48 con un nombre de proyecto largo:
   la página desbordaba a 700, 900, 1200 y hasta 1440px, y solo estaba sana
   por debajo de 640. El mínimo automático no es un problema de móvil: es un
   problema de textos largos, y los textos largos no saben de anchos. Estas
   reglas son globales a propósito y el breakpoint de 640 las hereda.

   Va antes de los @media: las reglas con min(Xpx, 100%) de los breakpoints
   tienen más especificidad y siguen mandando donde interesa. */
input, select, textarea { min-width: 0; }
.tracker-layout > *, .proj-grid > *, .proj-layout > *, .links-grid > *,
.checklist-instances > *, .schedule-week > *, .month-grid > * { min-width: 0; }
/* El texto que ESCRIBE el usuario: además de no imponer mínimo, parte por
   donde haga falta. `anywhere` y no `break-word` — solo `anywhere` reduce
   también el min-content, que es lo que empuja al contenedor (v1.42).

   OJO, y esto costó una prueba: en móvil el ajuste de línea se aplica a
   `.panel-section` ENTERA, y eso NO se puede subir aquí tal cual. Probado en
   v1.48: partía también las etiquetas cortas de la interfaz —"modo libre"
   quedaba en tres líneas, "pom/odor/o"— porque `anywhere` deja el min-content
   en UN carácter y da igual que sobre sitio alrededor. Texto del usuario sí,
   etiquetas nuestras no.

   .todo-project-tag entra aquí en v1.50 y merece su nota: el chip llevaba
   `white-space: nowrap` para leerse de una línea, y `nowrap` GANA al
   overflow-wrap heredado — con un nombre de proyecto largo sin espacios se
   salía de la página. Lo destapó el tema Sonar, que es monoespaciado y
   ensancha lo justo para cruzar el umbral; en los otros tres el mismo
   nombre cabía por poco y el fallo no se veía. Sin `nowrap` el chip sigue
   en una línea siempre que quepa, que es lo que se quería de verdad. */
/* v1.59: .proj-badge entra por lo mismo que .todo-project-tag y merece la
   misma nota. Los estados los INVENTA el usuario ("waiting on client
   approval"), así que es texto suyo, no una etiqueta nuestra — y su columna
   no encoge (flex: 0 0 auto), de modo que sin esto una palabra larga empuja
   la lista entera. Que también pinte "active" no es problema: `anywhere` deja
   el min-content en un carácter pero solo parte cuando de verdad no cabe. */
/* v1.64: de la sección 15 entran la expresión de la calculadora y el
   nombre del archivo convertido — los dos los escribe (o los trae) el
   usuario y ninguno tiene por qué llevar un espacio. */
.todo-text, .deadline-label, .idea-text, .tier-item-text,
.schedule-block-label, .link-card a, .todo-project-tag,
.proj-row-name, .proj-row-client, .proj-badge, .proj-row-side,
.calc-row-expr, .img-result-name {
  min-width: 0; overflow-wrap: anywhere;
}

/* ── RESPONSIVE ────────────────────────────────────────────────── */

/* v1.71: el tracker se apila a 1400, no a 900. Su columna derecha aloja la
   tabla de 7 columnas del historial, y con el reloj al lado esa tabla mide
   `ancho del panel − 460`: a 1024 son 410px, y ahí el 100% se lo comen los
   96px fijos de la columna de acciones más los porcentajes de las otras
   cinco. Lo que quedaba para la nota eran 27px, o sea la nota escrita en
   vertical, una letra por línea, con la duración y el importe recortados al
   lado. Medido, no estimado.

   El umbral es 1400 y no 1200 porque es el ancho a partir del cual la tabla
   cabe de verdad: por debajo, los porcentajes de abajo no cubren el peor
   caso (los temas terminal, con Courier New). Apilado no se pierde nada —el
   reloj arriba y el historial ancho debajo, que es lo que ya hacía en
   móvil— y a 1440 se sigue viendo a dos columnas.

   Va en su propio @media y no dentro del de 900 a propósito: lo que sobra
   ahí (formularios a dos columnas, semana, tiers) sigue cabiendo de sobra a
   1024 y subirlo también sería apagar cosas que funcionan. Aquí el que no
   cabe es el historial. */
@media (max-width: 1400px) {
  .tracker-layout { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .manual-grid { grid-template-columns: 1fr 1fr; }
  .lead-grid, .domain-grid { grid-template-columns: 1fr 1fr; }
  .deadline-grid { grid-template-columns: 1fr 1fr; }
  .tier-add { flex-direction: column; }
  .tier-item { flex-wrap: wrap; }
  .tier-item-actions { width: 100%; justify-content: flex-end; }
  .account-grid, .holding-grid, .expense-grid { grid-template-columns: 1fr 1fr; }
  .finance-chart-row { flex-direction: column; align-items: flex-start; }
  .schedule-week, .month-grid { grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
  .schedule-grid-form { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 600px) {
  h1 { font-size: 1.75rem; }
  /* v1.57: el widget ya no se posiciona (está en el flujo); aquí solo
     encoge, que es lo único que hacía falta de verdad. */
  #search-toggle, #settings-toggle { font-size: 0.875rem; width: 32px; height: 32px; }
  .footer-container { flex-direction: column; align-items: flex-start; gap: 20px; }
  .timer-clock { font-size: 1.75rem; }
}

/* ── EL UMBRAL DE LAS TARJETAS (v1.73) ─────────────────────────────
   Una tabla pasa a tarjetas cuando ya no cabe. Ese es el criterio único
   que fijó la v1.42 y sigue siéndolo; lo que cambia aquí es el NÚMERO,
   que hasta ahora era 640 para las ocho y resulta que no era el de
   ninguna. Medido tabla a tabla —lo que piden sus columnas de datos, más
   las de acciones que no encogen, más un mínimo legible para las de texto
   libre— el viewport por debajo del cual cada una deja de ser viable:

     strategy-plan  283     accounts   621     breakdown  651
     expenses       697     domains    761     history    788
     leads          791     holdings  1105

   O sea que la única que aguantaba de verdad hasta 640 es la de dos
   columnas, y holdings —nueve columnas de cifras— recortaba ya en un
   portátil de 1024. De ahí los dos umbrales: 900 para todas menos
   strategy-plan (que no lo necesita y como tarjeta sería una regresión),
   y 1024 para holdings, que con el reparto nuevo y el `normal` de la
   ganancia baja su mínimo a ~1008.

   Por qué el cuerpo está escrito dos veces: CSS no deja compartir un
   bloque de declaraciones entre dos media queries, y las alternativas
   —devolver a modo tabla entre 901 y 1024, o un `@container style()`—
   son más frágiles que repetirlo. El riesgo real no es la repetición,
   es que DIVERJAN: lo comprueba el arnés descrito en el CHANGELOG, que
   exige que los dos bloques declaren exactamente lo mismo.

   Va aquí, después de los anchos por columna y justo antes del
   breakpoint de móvil, por lo de siempre: gana por orden a las reglas
   que comparten especificidad.                                     */

/* holdings: su propio umbral, porque es la tabla más ancha del panel */
@media (max-width: 1024px) {
  #holdings-table { display: block; table-layout: auto; font-size: 0.875rem; }
  #holdings-table thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  #holdings-table tbody { display: block; }
  #holdings-table tr {
    display: block; margin-bottom: 12px; padding: 10px 12px;
    border: 1px solid var(--border-dim); background: var(--bg-card);
  }
  #holdings-table tr:hover td { background: transparent; }
  #holdings-table td {
    display: flex; gap: 12px; align-items: baseline;
    padding: 5px 0; border-bottom: none;
    overflow: visible; text-overflow: clip;
    white-space: normal; overflow-wrap: anywhere;
    width: auto !important;
  }
  #holdings-table td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 36%; min-width: 0;
    font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--neon-cyan);
  }
  #holdings-table td:has(input, select, textarea) {
    flex-direction: column; align-items: stretch; gap: 6px;
  }
  #holdings-table td:not([data-label]) { justify-content: flex-end; }
  #holdings-table td:first-child:not([data-label]):not([colspan]) {
    justify-content: flex-start;
    font-weight: bold; color: var(--neon-cyan);
    border-bottom: 1px dashed var(--border-section);
    padding-bottom: 8px; margin-bottom: 4px;
  }
  #holdings-table td[colspan] { justify-content: center; text-align: center; }
}

/* las demás. El fallo original que esto arregla (v1.41): table-layout:
   fixed reparte el ancho a partes iguales y con nowrap + ellipsis todo
   sale cortado; el overflow-x del .table-wrap no salva nada, porque con
   fixed la tabla nunca es más ancha que su envoltorio. En bloque, cada
   fila es una tarjeta y cada celda una línea "etiqueta: valor". La
   etiqueta sale de data-label, que pone labelTableCells() en shared.js
   copiando el <th> de su columna — por eso una tabla nueva se etiqueta
   sola y no hay que tocar los ~56 sitios que generan <td>.
   holdings entra también aquí, y no se excluye a propósito: si algún día
   se retira el bloque de 1024 sigue cubierto por este. */
@media (max-width: 900px) {
  .panel-table:not(#strategy-plan-table) { display: block; table-layout: auto; font-size: 0.875rem; }
  .panel-table:not(#strategy-plan-table) thead {
    /* fuera de la vista pero legible por lector de pantalla: las
       etiquetas visibles son los ::before, no hay que repetirlas */
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .panel-table:not(#strategy-plan-table) tbody { display: block; }
  .panel-table:not(#strategy-plan-table) tr {
    display: block; margin-bottom: 12px; padding: 10px 12px;
    border: 1px solid var(--border-dim); background: var(--bg-card);
  }
  /* la tarjeta ya está sobre bg-card: el hover de fila no pinta nada */
  .panel-table:not(#strategy-plan-table) tr:hover td { background: transparent; }
  .panel-table:not(#strategy-plan-table) td {
    display: flex; gap: 12px; align-items: baseline;
    padding: 5px 0; border-bottom: none;
    overflow: visible; text-overflow: clip;
    white-space: normal; overflow-wrap: anywhere;
    /* !important porque los anchos por columna son "#id td:nth-child(n)"
       y ganan por especificidad a cualquier selector de clase de aquí */
    width: auto !important;
  }
  .panel-table:not(#strategy-plan-table) td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 36%; min-width: 0;
    font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--neon-cyan);
  }
  /* celdas con campos: etiqueta encima y campo a ancho completo — al lado
     quedaría un input de ~100px */
  .panel-table:not(#strategy-plan-table) td:has(input, select, textarea) {
    flex-direction: column; align-items: stretch; gap: 6px;
  }
  /* celda de acciones: su <th> va vacío, así que se queda sin data-label */
  .panel-table:not(#strategy-plan-table) td:not([data-label]) { justify-content: flex-end; }
  /* ...salvo la primera, que es el rótulo de la tarjeta */
  .panel-table:not(#strategy-plan-table) td:first-child:not([data-label]):not([colspan]) {
    justify-content: flex-start;
    font-weight: bold; color: var(--neon-cyan);
    border-bottom: 1px dashed var(--border-section);
    padding-bottom: 8px; margin-bottom: 4px;
  }
  /* fila de estado vacío ("ninguna sesión con estos filtros") */
  .panel-table:not(#strategy-plan-table) td[colspan] { justify-content: center; text-align: center; }
}

/* ── MÓVIL DE VERDAD (v1.41) ───────────────────────────────────────
   Primer breakpoint del panel que toca CONTENIDO: hasta ahora el más
   bajo era el de 600px y solo ajustaba el h1, los iconos y el footer.
   Va el último a propósito, para ganar por orden a los de 900 y 600 en
   las reglas que comparten especificidad.
   Tres criterios, y ninguno mete color: los cuatro temas siguen siendo
   solo variables (regla de la arquitectura).
     1. una columna en todo lo que sea rejilla de formulario,
     2. las tablas se leen como tarjetas — eso se fue a su propio bloque
        en la v1.73, porque el umbral no era 640 (ver ahí),
     3. nada con un mínimo en píxeles mayor que la pantalla.        */
@media (max-width: 640px) {

  /* — ANCHOS — el 92% + 20px de padding dejaba ~319px útiles de 390;
     en móvil el margen lo pone el padding, no un porcentaje. */
  .section-container { width: 100%; padding: 48px 16px; }
  .panel-section { padding: 20px 16px 40px; }

  /* — CABECERA COMO BARRA (v1.42) — decisión de producto: arriba del todo la
     marca a la izquierda y buscar/ajustes a la derecha; debajo, la fila de
     secciones. Antes esos dos botones eran un elemento FIJO flotando por
     encima del contenido de cada sección, que se leía como un fallo.
     v1.42 los metió DENTRO de la barra sin tocar el HTML: seguían siendo
     `fixed` y caían ahí por coordenadas (top:6px). Funcionaba de lejos y
     no de cerca — a 38px de alto en una barra de 52 quedaban 8px abajo y
     6 arriba, o sea descentrados y sin aire. v1.57 los mete en el flujo
     de verdad: los centra align-items,
     y el aire de arriba y abajo sale solo y sigue saliendo si mañana
     cambia la altura de la barra o el tamaño de los botones.
     La barra es sticky a propósito: si se fuera con el scroll, en móvil
     te quedas sin buscador y sin ajustes a media página.
     Se va la frase "tu centro de operaciones freelance": en 390px se comía
     una línea entera de campo de visión y la marca ya está al lado. */
  .panel-header {
    position: sticky; top: 0; z-index: 901;
    display: flex; align-items: center;
    height: 52px; padding: 0 16px; margin: 0;
    width: 100%;
    background: var(--bg-dark);
    border-bottom: 1px solid var(--border-dim);
  }
  /* la marca ocupa lo que le sobre a los botones, y encoge antes que ellos */
  .panel-brand { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; }
  .subtitle { display: none; }
  h1 { font-size: 1.125rem; margin: 0; }
  /* v1.57: el lockup ya trae su propio espaciado interno entre símbolo y
     palabra, así que aquí no hay margen que ajustar. A 1.15rem el max()
     de .logo-wordmark lo deja en 24px, el mínimo de marca. */
  /* v1.55: en la barra de 52px la píldora comparte fila con los dos botones
     fijos de la derecha (~90px reservados). Se estrecha y baja a 0.65rem
     para que "Snugport Sync" quepa entero a 320px en los cuatro temas —
     los terminal usan monoespaciada y ensanchan. */
  .brand-badge { margin-left: 8px; padding: 0.1em 0.45em; font-size: 0.75rem; letter-spacing: 0.04em; }
  /* v1.57: sin `top`/`right` — los coloca el flex de la barra. Los botones
     bajan a 34px: en 52 de alto eso deja 9 arriba y 9 abajo, que es el aire
     que faltaba, y los dos juntos ocupan 74px en vez de 84. */
  .settings-widget { gap: 6px; flex: 0 0 auto; }
  #search-toggle, #settings-toggle { width: 34px; height: 34px; }
  /* el hueco de estado (flashStatus, 39 llamadas en 15 archivos) no cabe en
     la barra sin chocar con los botones, y esconderlo dejaría al usuario sin
     confirmación de guardado, importación o sync. Sale de la barra y pasa a
     ser un aviso al pie, visible solo cuando dice algo. */
  .data-actions {
    position: fixed; left: 16px; right: 16px; bottom: 12px; z-index: 950;
    margin: 0; justify-content: center; pointer-events: none;
  }
  /* --bg-dark y NO --bg-card: en los cuatro temas bg-card es un tinte con
     3-5% de alfa, pensado para tarjetas apoyadas en el fondo. Aquí el aviso
     flota sobre el contenido y con ese tinte se leía el texto de debajo. */
  .data-status:not(:empty) {
    background: var(--bg-dark); border: 1px solid var(--neon-cyan);
    padding: 8px 14px; text-align: center;
  }

  /* — TEXTO LIBRE DEL USUARIO — una URL sin espacios no puede empujar la
     página entera (pasaba en Todos, Vencimientos e Ideas: se salía y de
     paso el navegador dejaba alejar el zoom, que era el síntoma).
     `anywhere` —y no `break-word`— porque además de partir la palabra
     reduce el min-content, que es justo lo que hacía crecer al item de
     flex. Se pone en la sección y se hereda; se exceptúan las cifras,
     donde partir por la mitad quedaría peor que ajustar el tamaño. */
  .panel-section { overflow-wrap: anywhere; }
  /* v1.59: .proj-row-meta se suma a las cifras exceptuadas — es "3h 0m ·
     €135.00" y partirlo daría "€135." y "00" en dos líneas. Lleva espacios,
     así que ajusta igual, solo que por donde tiene sentido. */
  .timer-clock, .quick-totals, .history-totals, .finance-summary-line,
  .deadline-days, .checklist-progress, .month-cell-day,
  .proj-row-meta { overflow-wrap: normal; }
  /* (los min-width:0 que había aquí subieron a reglas globales en v1.48 —
     ver "EL MÍNIMO AUTOMÁTICO", justo antes de los @media. El overflow-wrap
     se queda SOLO en móvil a propósito: probado global, parte las etiquetas
     cortas de la interfaz —"modo libre" quedaba en tres líneas— y fuera de
     móvil no hacía falta, el desbordamiento lo causaba el mínimo automático,
     no el ajuste de línea.) */
  /* la etiqueta va con nowrap y el select pedía 260px de mínimo: juntos no
     caben en una línea de 380 (se notaba solo en los temas terminal, que
     usan monoespaciada y ensanchan la etiqueta). El select baja a su línea. */
  .checklist-project-box { flex-wrap: wrap; }
  .checklist-project-box select { min-width: 0; width: 100%; }

  /* — NAV — decisión de producto (2026-07-25): fila única deslizable con
     snap, no desplegable. Con flex-wrap las pestañas eran un muro de
     cuatro filas encima de cada sección; el desplegable ahorraba más
     sitio pero escondía el mapa del panel. Mismo patrón que ya usaba
     .settings-nav a 560px. La pestaña activa la centra showTab(). */
  /* la fila de secciones se pega justo debajo de la barra de marca (52px);
     v1.41 tenía que reservar 108px a la derecha porque los botones fijos se
     tragaban las últimas pestañas — al subir a la barra ya no hace falta */
  .panel-nav { top: 52px; }
  .panel-nav-inner {
    width: 100%; padding: 6px 16px;
    flex-wrap: nowrap; overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  .panel-nav-inner::-webkit-scrollbar { display: none; }
  .nav-tab { flex: 0 0 auto; scroll-snap-align: center; padding: 12px 14px; }
  /* el degradado del borde es lo que dice "hay más a la derecha" */
  .panel-nav::after {
    content: ""; position: absolute; inset: 0 0 0 auto; width: 32px;
    background: linear-gradient(to right, transparent, var(--bg-dark));
    pointer-events: none;
  }

  /* — TABLAS COMO TARJETAS — se fue de aquí en la v1.73. Estaba en este
     breakpoint desde la v1.41 porque se leyó como un problema de móvil, y
     al medirlo resultó que no lo era: siete de las ocho tablas recortan
     datos POR ENCIMA de 640. Vive ahora en su propio bloque, justo antes
     de este @media — ver "EL UMBRAL DE LAS TARJETAS". */

  /* — PROYECTOS (v1.59) — lista y detalle se TURNAN. Apilados, el detalle
     son once campos: había que pasar la lista entera por encima cada vez que
     se vuelve a ella, y volver arriba después de editar. Eligiendo uno la
     lista se va y sale el "← All projects"; en escritorio no pasa nada de
     esto y las dos columnas se ven a la vez. */
  .proj-layout { grid-template-columns: 1fr; gap: 20px; }
  #tab-proyectos.has-pick .proj-list-panel { display: none; }
  #tab-proyectos:not(.has-pick) .proj-detail { display: none; }
  #tab-proyectos.has-pick .proj-back {
    display: inline-flex; align-items: center; min-height: 44px;
    margin-bottom: 4px; padding: 8px 0;
    background: transparent; border: none; color: var(--neon-cyan);
    font-family: inherit; font-size: 0.875rem; cursor: pointer;
  }
  /* con el detalle abierto el formulario ya no necesita despegarse de la
     lista, que no está */
  #tab-proyectos .terminal-form { margin-top: 10px; }
  /* la fila de identidad se queda en línea (el avatar mide 52 y el campo del
     emoji 4.5em: caben en 358px), pero los tonos bajan a su propia línea */
  .proj-icon-row { width: 100%; }
  /* 22px de swatch es un blanco de dedo imposible; con 34 los nueve siguen
     entrando en dos filas a 390px */
  .proj-hue { width: 34px; height: 34px; }
  .proj-hue-auto { width: auto; }

  /* — REJILLAS DE FORMULARIO — a 390px dos columnas dejaban inputs de
     ~140px; el breakpoint de 900 se quedaba corto. */
  .manual-grid, .lead-grid, .domain-grid, .deadline-grid,
  .account-grid, .holding-grid, .expense-grid,
  .schedule-grid-form, .theme-grid { grid-template-columns: 1fr; }
  .manual-grid > button, .lead-grid > button, .domain-grid > button,
  .deadline-grid > button, .account-grid > button, .holding-grid > button,
  .expense-grid > button,
  .schedule-grid-form > button, .manual-actions > button { width: 100%; flex: 1; }

  /* — LISTAS EN FILA — .todo-item y .deadline-item son flex de 4-5 piezas
     sin wrap: a 390px la última (borrar, "ir a") se salía de la pantalla.
     Ahora envuelven, y el texto principal se queda con el hueco de la
     primera línea para que lo accesorio caiga debajo en vez de aplastarlo. */
  /* flex-start y no el center de base: con un texto de tres líneas la
     casilla (o la fecha) se quedaba flotando a media altura */
  .todo-item, .deadline-item { flex-wrap: wrap; gap: 8px 12px; align-items: flex-start; }
  /* la base es "todo el ancho menos lo que va a su izquierda" (la casilla
     en Todos, la fecha en Vencimientos): así el texto siempre se queda con
     el resto de la primera línea y lo accesorio —etiqueta, prioridad,
     borrar— cae debajo. Con un 60% fijo, una nota larga se metía en una
     columna estrecha y la fila crecía a lo alto sin necesidad. */
  .todo-text { flex: 1 1 calc(100% - 44px); }
  .deadline-label { flex: 1 1 calc(100% - 90px); }
  .deadline-days { text-align: left; }
  .todo-item > .del-btn, .deadline-item > .deadline-goto { margin-left: auto; }

  /* — CALENDARIO — el breakpoint de 900 lo pasa a auto-fit, y en móvil
     un mes repartido en 3 columnas ya no es un calendario. Se queda en 7
     y encoge; la semana del horario sí se apila, porque son tarjetas. */
  .month-grid { grid-template-columns: repeat(7, 1fr); gap: 2px; }
  .month-cell { padding: 3px; }
  .month-cell-day { font-size: 0.75rem; }
  .month-dow { font-size: 0.75rem; }
  .month-dot { width: 5px; height: 5px; }
  .schedule-week { grid-template-columns: 1fr; }

  /* — ÁREAS TÁCTILES — 44px es el mínimo recomendado; estaban en 32. */
  #search-toggle, #settings-toggle { width: 44px; height: 44px; font-size: 1rem; }
  .del-btn, .edit-btn, .convert-btn, .terminal-btn-sm { min-height: 44px; padding: 8px 14px; }

  /* iOS hace zoom al enfocar un campo de menos de 16px y deja la página
     descuadrada. (v1.68: caen los dos selectores de las celdas de comidas,
     que ganaban por especificidad — esa sección ya no existe.) */
  input, select, textarea { font-size: 1rem; }

  /* v1.70: el resultado del buscador se apila. A 390px un trozo de nota y el
     "proyecto · día" al que pertenece no caben en la misma línea, y el
     contexto va en `nowrap` a propósito (partir "Acme — Web · next steps" por
     la mitad lo hace ilegible), así que lo que se hace es dejarlo bajar. */
  .search-result { flex-wrap: wrap; align-items: flex-start; gap: 4px 12px; }
  /* alineado bajo el texto: el icono mide 1.15em de 0.875rem (17px) + el gap */
  .search-result-ctx { margin-left: 29px; }

  /* v1.47: la chuleta de atajos deja de ser dos columnas — la tecla arriba
     y lo que hace debajo, que a 390px "Ctrl + K" y su frase no caben. */
  .shortcut-list { grid-template-columns: 1fr; gap: 2px 0; }
  .shortcut-list dd { margin-bottom: 10px; }

  /* v1.60: la marca de bienvenida encoge en móvil. El cuestionario ya es una
     tarjeta larga (cuatro perfiles, oficio, moneda, meta y botones) y con el
     teclado abierto el modal se queda en media pantalla: el lockup no puede
     comerse el sitio de la primera pregunta. Sigue por encima del mínimo de
     marca — 150px de ancho son 92 de alto, y el suelo son 63. */
  .onboard-card { padding: 26px 22px; }
  .onboard-logo { width: clamp(110px, 45%, 150px); }
  .onboard-brand { padding-bottom: 14px; margin-bottom: 16px; }

  /* v1.64 — SECCIÓN 15. La tarjeta pierde aire lateral, y las tres filas
     que son flex de varias piezas envuelven: la cabecera de un huso (ciudad,
     desfase, reloj y borrar) y la fila de un archivo convertido no caben en
     390px sin partirse. La miniatura y el botón de descarga se quedan como
     están: son cajas fijas, no texto. */
  .tool-block { padding: 14px 14px 18px; }
  .tz-add { flex-wrap: wrap; }
  .tz-row-head { flex-wrap: wrap; }
  .tz-clock { margin-left: auto; }
  .img-result { flex-wrap: wrap; }
  .img-result-info { flex-basis: calc(100% - 60px); }
  /* la línea de cálculo NO envuelve a propósito: el "=" al lado del campo
     es lo que dice que se pulsa Enter, y debajo se leería como otra cosa */
  .calc-line input { font-size: 1rem; }
}