/* ==========================================================================
   ÍNDICE — PERSONAL LINK HUB
   Sistema de diseño editorial: papel + tinta, tipografía nativa del sistema,
   sin dependencias externas. Los colores se definen una única vez con
   light-dark() y el modo se resuelve vía la propiedad color-scheme.
   ========================================================================== */

:root {
  color-scheme: light dark;

  /* Tokens del pack "terracota" (por defecto). Cada pack alternativo (ver
     bloques [data-theme-pack] más abajo) redefine estas variables
     --light-* / --dark-* / --accent juntas; el resto de la hoja de estilos
     solo usa light-dark() sobre ellas, así que nunca hay que tocar nada más. */
  --light-bg: #F5F3EC;
  --light-surface: #FFFFFF;
  --light-surface-hover: #FBF9F2;
  --light-line: #E4DFD1;
  --light-line-strong: #17140F;
  --light-text: #17140F;
  --light-text-body: #4B463C;

  --dark-bg: #131110;
  --dark-surface: #1B1815;
  --dark-surface-hover: #201C18;
  --dark-line: #2B2621;
  --dark-line-strong: #F2EFE9;
  --dark-text: #F2EFE9;
  --dark-text-body: #B8B0A2;

  /* Superficies */
  --bg: light-dark(var(--light-bg), var(--dark-bg));
  --surface: light-dark(var(--light-surface), var(--dark-surface));
  --surface-hover: light-dark(var(--light-surface-hover), var(--dark-surface-hover));

  /* Líneas */
  --line: light-dark(var(--light-line), var(--dark-line));
  --line-strong: light-dark(var(--light-line-strong), var(--dark-line-strong));

  /* Tipografía */
  --text: light-dark(var(--light-text), var(--dark-text));
  --text-body: light-dark(var(--light-text-body), var(--dark-text-body));

  /* --text-dim / --text-muted se calculan a partir de --text y --bg (no
     con literales por tema) para que salgan siempre con el matiz correcto
     del tema activo Y con contraste garantizado, en vez de tener que
     ajustar 12 valores a mano (6 temas × claro/oscuro) cada vez. --text-dim
     va por encima del mínimo AA (4.5:1) para texto normal, con margen de
     sobra para que se lea bien también en pantallas más pequeñas o con
     brillo bajo; --text-muted es más claro a propósito y se reserva para
     elementos decorativos/aria-hidden. */
  --text-dim: color-mix(in srgb, var(--text) 68%, var(--bg) 32%);
  --text-muted: color-mix(in srgb, var(--text) 46%, var(--bg) 54%);

  /* Acento (sello / tinta roja, no el naranja genérico de IA) */
  --accent: light-dark(#A6321C, #E2724A);

  /* Indicadores de FASE DE PROYECTO — un único punto de color, sin fondo
     relleno. Solo describen el ciclo de vida; la categoría (repositorio,
     música, servicio...) va en "label" como texto libre, sin color propio. */
  --c-activo: light-dark(#1F9D53, #34D399);
  --c-desarrollo: light-dark(#1D6FD1, #5B9BF2);
  --c-pausa: light-dark(#C27803, #FBBF24);
  --c-proximamente: light-dark(#D9490F, #FB923C);
  --c-inactivo: light-dark(#8A8374, #6B7280);

  /* Indicadores de DISPONIBILIDAD PERSONAL — catálogo separado del de
     proyectos, porque "en pausa" para un proyecto no es lo mismo que
     "estoy de vacaciones" para una persona. */
  --p-disponible: light-dark(#1F9D53, #34D399);
  --p-ocupado: light-dark(#C27803, #FBBF24);
  --p-vacaciones: light-dark(#6D3FBF, #B794F6);
  --p-no-disponible: light-dark(#8A8374, #6B7280);

  --shadow-elevated: light-dark(0 1px 2px rgba(23, 20, 15, 0.04), 0 10px 30px rgba(0, 0, 0, 0.45));

  /* Tipografía: pila nativa del sistema, sin peticiones de red */
  --font-display: ui-serif, Georgia, 'Iowan Old Style', 'Palatino Linotype', 'Times New Roman', serif;
  --font-mono: ui-monospace, 'SF Mono', 'Cascadia Code', 'JetBrains Mono', Menlo, Consolas, monospace;

  --max-width: 980px;
  --radius: 2px;
}

/* --------------------------------------------------------------------------
   Packs de tema alternativos — elegidos desde config.js (SITE_CONFIG.theme),
   aplicados vía [data-theme-pack] en <html>. Cada uno trae ya coordinadas
   su versión clara y su versión oscura; cuál de las dos se ve depende del
   sistema o del botón de tema, exactamente igual que con "terracota".
   "terracota" es el valor por defecto ya definido arriba en :root.
   -------------------------------------------------------------------------- */
:root[data-theme-pack="vino"] {
  --light-bg: #F7F0F1;
  --light-surface: #FFFFFF;
  --light-surface-hover: #F0E6E8;
  --light-line: #E6D5D9;
  --light-line-strong: #1C1013;
  --light-text: #1C1013;
  --light-text-body: #4A3339;

  --dark-bg: #150C0E;
  --dark-surface: #1E1215;
  --dark-surface-hover: #24161A;
  --dark-line: #332025;
  --dark-line-strong: #F3E9EB;
  --dark-text: #F3E9EB;
  --dark-text-body: #BFA3A9;

  --accent: light-dark(#7A2436, #D97B94);
}

:root[data-theme-pack="mostaza"] {
  --light-bg: #F7F3E9;
  --light-surface: #FFFFFF;
  --light-surface-hover: #F0EADA;
  --light-line: #E6DCC2;
  --light-line-strong: #1C1810;
  --light-text: #1C1810;
  --light-text-body: #4A4130;

  --dark-bg: #15120A;
  --dark-surface: #1E1911;
  --dark-surface-hover: #241E14;
  --dark-line: #332B1C;
  --dark-line-strong: #F3EEE0;
  --dark-text: #F3EEE0;
  --dark-text-body: #BFB397;

  --accent: light-dark(#8A6A16, #D9A94D);
}

:root[data-theme-pack="monocromo"] {
  --light-bg: #FFFFFF;
  --light-surface: #FFFFFF;
  --light-surface-hover: #F5F5F5;
  --light-line: #E5E5E5;
  --light-line-strong: #111111;
  --light-text: #111111;
  --light-text-body: #3D3D3D;

  --dark-bg: #000000;
  --dark-surface: #0A0A0A;
  --dark-surface-hover: #141414;
  --dark-line: #242424;
  --dark-line-strong: #F5F5F5;
  --dark-text: #F5F5F5;
  --dark-text-body: #A8A8A8;

  --accent: light-dark(#3A3A3A, #D9D9D9);
}

:root[data-theme-pack="azul"] {
  --light-bg: #E6EBF7;
  --light-surface: #FFFFFF;
  --light-surface-hover: #DAE2F3;
  --light-line: #C4D0E9;
  --light-line-strong: #0E1729;
  --light-text: #0E1729;
  --light-text-body: #33436B;

  --dark-bg: #0D131C;
  --dark-surface: #141C28;
  --dark-surface-hover: #19222F;
  --dark-line: #26303F;
  --dark-line-strong: #EDF1F6;
  --dark-text: #EDF1F6;
  --dark-text-body: #A9B4C4;

  /* Acento: azul puro (hue ~213°), no índigo/violeta — antes se iba hacia
     el morado al bajar demasiado el verde respecto al azul. */
  --accent: light-dark(#1668E6, #5FA8FF);
}

:root[data-theme-pack="petroleo"] {
  --light-bg: #EEF3F1;
  --light-surface: #FFFFFF;
  --light-surface-hover: #E5EDEA;
  --light-line: #D6E3DE;
  --light-line-strong: #0F211D;
  --light-text: #0F211D;
  --light-text-body: #37493F;

  --dark-bg: #0A1613;
  --dark-surface: #101E1A;
  --dark-surface-hover: #152520;
  --dark-line: #223530;
  --dark-line-strong: #E9F2EE;
  --dark-text: #E9F2EE;
  --dark-text-body: #A6BFB4;

  --accent: light-dark(#0F5C56, #5FC2B4);
}

[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }

/* --------------------------------------------------------------------------
   Reset & Base
   -------------------------------------------------------------------------- */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  background-color: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  min-height: 100vh;
  background-color: var(--bg);
  background-image:
    radial-gradient(ellipse at top, light-dark(rgba(23,20,15,0.035), rgba(255,255,255,0.03)), transparent 60%);
  /* La transición índice ↔ servicios saca los paneles fuera del viewport
     (ver .panel-slide-* más abajo) — sin esto aparecería una barra de
     scroll horizontal fantasma mientras dura la animación. */
  overflow-x: hidden;
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -3rem;
  background: var(--text);
  color: var(--bg);
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  z-index: 100;
  transition: top 0.15s ease;
}

.skip-link:focus {
  top: 1rem;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Page Grid — Masthead (sidebar) + Index (main)
   -------------------------------------------------------------------------- */
.page {
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 3.5rem 1.5rem;
  padding-top: max(3.5rem, env(safe-area-inset-top));
  padding-bottom: max(3.5rem, env(safe-area-inset-bottom));
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.75rem;
}

/* display:grid de arriba gana por especificidad al [hidden] del
   user-agent stylesheet — mismo patrón que .whatsapp-cta[hidden] y
   compañía. Solo initQrPage oculta ".page" (ver script.js). */
.page[hidden] {
  display: none;
}

@media (min-width: 860px) {
  /* Página estática: nada de scroll (ni de "rebote" al forzarlo) en todo
     el sitio — lo único que se desplaza es la propia lista de enlaces/
     servicios, dentro de su propio hueco (ver .index-list más abajo). En
     móvil se deja el scroll normal de toda la página (una sola columna,
     sin masthead fijo al lado con el que compaginar el desplazamiento). */
  html, body {
    height: 100%;
    overflow: hidden;
  }

  .page {
    height: 100vh;
    height: 100dvh;
    grid-template-columns: 15.5rem 1fr;
    grid-template-rows: minmax(0, 1fr);
    gap: 4rem;
    align-items: stretch;
    /* Mucho menos hueco muerto abajo que en móvil (3.5rem): con la
       página fija a 100dvh, ese margen le quitaba a la lista casi 1cm de
       alto útil antes de necesitar scroll, dejando la zona de
       desvanecido demasiado arriba, lejos del borde real del navegador. */
    padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
  }

  /* Vista de servicios (ver initViewSwitcher en script.js): el masthead
     pasa a la columna derecha y la lista de contenido a la izquierda —
     solo tiene sentido "el otro lado" cuando hay dos columnas; en una sola
     columna (móvil) el swap queda desactivado y solo se ve la animación.
     "order" por sí solo NO basta: solo decide qué elemento va en qué
     columna, pero las columnas del grid mantienen su ancho (15.5rem/1fr)
     donde estaban — sin invertir también grid-template-columns, la lista
     de servicios quedaría apretada en la columna estrecha. */
  .page--services {
    grid-template-columns: 1fr 15.5rem;
  }
  .page--services .masthead { order: 2; }
  .page--services .index { order: 1; }
}

/* --------------------------------------------------------------------------
   Transición índice ↔ servicios
   El masthead sale de pantalla por la izquierda y vuelve a entrar por la
   derecha (colocándose al otro lado del grid); la lista de contenido hace
   el movimiento espejo. translateX en vw (no en % del propio ancho) para
   que "salir"/"entrar" sea siempre respecto al viewport, sin importar en
   qué columna esté el elemento en ese momento.
   -------------------------------------------------------------------------- */
.masthead,
.index {
  transition: transform 0.42s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.3s ease;
}

.panel-slide-left {
  transform: translateX(-120vw);
  opacity: 0;
}

.panel-slide-right {
  transform: translateX(120vw);
  opacity: 0;
}

.no-transition {
  transition: none !important;
}

/* --------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */
.masthead {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  padding-bottom: 1.75rem;
  border-bottom: 1px solid var(--line);
}

@media (min-width: 860px) {
  .masthead {
    /* La página ya no hace scroll (ver .page más arriba), así que no hace
       falta "sticky" — el masthead simplemente se queda donde está. */
    padding-bottom: 0;
    border-bottom: none;
    min-height: 0;
    overflow: hidden;
  }
}

.masthead-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* Indicador de estado — mismo estilo de texto para todos los estados en
   todo el sitio (masthead y filas del índice); lo único que cambia entre
   uno y otro es el color del punto, vía la clase .status-* en el mismo
   elemento (solo afecta a .flag-dot dentro, no al texto). */
.flag,
.row-flag {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.flag { font-size: 0.7rem; }
.row-flag { font-size: 0.66rem; }

.flag-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background-color: var(--text-muted);
}

.status-activo .flag-dot { background-color: var(--c-activo); }
.status-desarrollo .flag-dot { background-color: var(--c-desarrollo); }
.status-pausa .flag-dot { background-color: var(--c-pausa); }
.status-proximamente .flag-dot { background-color: var(--c-proximamente); }
.status-inactivo .flag-dot { background-color: var(--c-inactivo); }

/* Disponibilidad personal (catálogo separado, ver arriba) */
.status-disponible .flag-dot { background-color: var(--p-disponible); }
.status-ocupado .flag-dot { background-color: var(--p-ocupado); }
.status-vacaciones .flag-dot { background-color: var(--p-vacaciones); }
.status-no-disponible .flag-dot { background-color: var(--p-no-disponible); }

/* Theme toggle: un pequeño "dial" en vez de emoji inconsistentes entre plataformas */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: 0.3rem 0.55rem;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

.theme-toggle:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

.theme-dial {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1.4px solid currentColor;
  background: conic-gradient(currentColor 0 50%, transparent 50% 100%);
}

.theme-toggle[data-state="dark"] .theme-dial { background: currentColor; }
.theme-toggle[data-state="light"] .theme-dial { background: transparent; }

/* Identity block */
.identity {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.identity-kicker {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--text-dim);
  text-transform: uppercase;
}

.identity-name {
  font-family: var(--font-display);
  font-size: 2.15rem;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--text);
}

.identity-role {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  color: var(--text-dim);
  letter-spacing: 0.03em;
  line-height: 1.5;
}

.identity-role .sep {
  color: var(--accent);
  font-weight: 800;
  font-size: 1.15em;
  margin: 0 0.2rem;
}

/* Contact block */
.contact-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.15rem;
}

/* Botones de acción (teléfono, WhatsApp): en fila por defecto (pantalla
   estrecha, donde el masthead ocupa todo el ancho y sobra sitio de sobra
   para varios botones uno junto a otro) y en columna a partir de 860px,
   donde el masthead pasa a ser una columna lateral estrecha (15.5rem) en
   la que una fila de 2-3 botones no cabría. */
.contact-actions {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

@media (min-width: 860px) {
  .contact-actions {
    flex-direction: column;
    align-items: flex-start;
  }
}

.contact-kicker {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--text-dim);
  text-transform: uppercase;
}

/* Es un <button>, no un <a href="mailto:...">, para que el clic solo
   copie el correo sin ofrecer abrir el cliente de correo — de ahí el
   reset de fondo/borde/cursor, que un <a> ya trae gratis. */
.contact-email {
  display: inline-block;
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  text-align: left;
  border-bottom: 1px solid var(--line);
  padding: 0 0 0.1rem;
  margin: 0;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
  word-break: break-all;
}

.contact-email:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* El correo y el teléfono se copian al pulsarlos directamente (ver
   initContactManager/initPhoneCopy) — este es el único feedback visual,
   el mismo verde que ya usaba el antiguo botón de copiar. */
.contact-email.copied,
.contact-phone.copied {
  color: var(--c-activo);
  border-color: var(--c-activo);
}

/* Es un <button>, no un <a href="tel:...">, para que el clic solo copie
   el número sin ofrecer abrir el marcador — mismo reset y misma
   tipografía que .contact-email, se muestra en claro sin paso previo. */
.contact-phone {
  display: inline-block;
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  text-align: left;
  border-bottom: 1px solid var(--line);
  padding: 0 0 0.1rem;
  margin: 0;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.contact-phone:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.contact-phone:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

.contact-phone[hidden] {
  display: none;
}

/* CTA principal del bloque de contacto en la vista de servicios — es el
   único elemento del sitio con relleno de color sólido a propósito, para
   que destaque claramente por encima del correo/teléfono (que siguen el
   tratamiento habitual de Kardex: sin bloques de color). */
.whatsapp-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  align-self: flex-start;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: var(--bg);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 0.55rem 1rem;
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
  transition: all 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

.whatsapp-cta svg {
  width: 17px;
  height: 19px;
  fill: currentColor;
  flex-shrink: 0;
}

.whatsapp-cta:hover {
  filter: brightness(0.92);
  transform: translateY(-1px);
}

.whatsapp-cta:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

.whatsapp-cta[hidden] {
  display: none;
}

.whatsapp-cta--modal {
  font-size: 0.94rem;
  padding: 0.6rem 1.1rem;
}

.whatsapp-cta--modal svg {
  width: 18px;
  height: 20px;
}

.masthead-footer {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-dim);
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   Index (Projects & Links)
   -------------------------------------------------------------------------- */
.index {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.index-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--line-strong);
  margin-bottom: 0.25rem;
}

/* display:flex de arriba gana por especificidad al [hidden] del
   user-agent stylesheet — mismo patrón que .whatsapp-cta[hidden] y
   compañía. Solo initServiceDetail oculta "#index-head" al entrar en el
   detalle de un servicio (ver script.js). */
.index-head[hidden] {
  display: none;
}

/* Único elemento con scroll de toda la página (ver @media 860px de .page
   más arriba) — la entradilla y "N° ..." quedan fuera, así que nunca se
   mueven. El desvanecido (arriba Y abajo) es un mask-image sobre el
   propio contenido (no un fondo de color detrás del título): las filas
   se disuelven según se acercan a cualquiera de los dos bordes en vez de
   cortarse de golpe — como si desaparecieran fuera de la pantalla — y no
   deja ningún "bloque" ni línea de color de por medio. */
@media (min-width: 860px) {
  .index {
    min-height: 0;
  }

  .index-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 1.75rem, black calc(100% - 1.75rem), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, black 1.75rem, black calc(100% - 1.75rem), transparent 100%);
    /* Un poco de margen extra respecto a la zona de desvanecido (ver
       FADE_ZONE_PX en script.js): sin este hueco la primera fila arranca
       justo pegada al borde de esa zona y queda marcada como tapada
       (no-clicable) incluso sin haber hecho scroll todavía. */
    padding-top: 2rem;
    /* Recorrido extra al final: la última fila sube por encima de la zona
       de desvanecido de abajo en vez de quedarse pegada al borde. */
    padding-bottom: 4.5rem;
    /* La franja de acento (.index-row::before) se sale de la fila hacia
       la izquierda (bleed); sin este hueco quedaría recortada por el
       propio scroll (overflow-y:auto obliga a recortar también en X).
       margin-left negativo + padding-left del mismo tamaño + ancho
       ampliado: el contenido queda exactamente donde estaba, solo se
       reserva sitio para que la franja no se corte. */
    margin-left: -1.5rem;
    padding-left: 1.5rem;
    width: calc(100% + 1.5rem);
    /* Barra de scroll nativa: sin ella (transparente, ancho 0 en
       Firefox) — el scroll sigue funcionando igual con rueda/gesto,
       solo no se ve una raya blanca de por medio. */
    scrollbar-width: none;
    -ms-overflow-style: none;
  }

  .index-scroll::-webkit-scrollbar {
    display: none;
  }
}

/* Flecha "hay más abajo" (la crea initListFadeGuard en script.js). Sticky
   al borde inferior del viewport en móvil; en escritorio se solapa con la
   zona de desvanecido de abajo de .index-scroll. */
.scroll-hint {
  position: sticky;
  bottom: 0.75rem;
  align-self: center;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: -2.4rem;
  padding: 0.35rem 0.75rem 0.35rem 0.9rem;
  background: var(--accent);
  color: var(--bg);
  border: 0;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 4px 16px color-mix(in srgb, var(--accent) 45%, transparent);
}

.scroll-hint svg {
  flex: none;
}

.scroll-hint[hidden] {
  display: none;
}

.scroll-hint:focus-visible {
  outline: 2px dashed var(--accent);
  outline-offset: 3px;
}

.index-head-number {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--text-muted);
  letter-spacing: 0.08em;
}

.index-head-title {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--text-dim);
}

/* Mismo motivo que .service-detail-title:focus-visible — se enfoca por
   script al cambiar de vista (ver goTo en initViewSwitcher/
   slideServicePanel), y sin esto el navegador pone su anillo de foco
   por defecto en vez de uno a juego con el resto del sitio. */
.index-head-title:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 4px;
}

.index-list {
  list-style: none;
}

.index-row {
  border-bottom: 1px solid var(--line);
  position: relative;
}

/* Puesta por script.js (initListFadeGuard) en cuanto la fila queda aunque
   sea mínimamente tapada por la zona de desvanecido de arriba — de
   verdad deja de poder pulsarse (no solo queda oculta detrás de otra
   cosa), y sale del orden de tabulación con teclado. */
.index-row.is-obscured {
  pointer-events: none;
}

.index-row::before {
  content: "";
  position: absolute;
  left: -1.1rem;
  top: 0;
  bottom: 0;
  width: 2px;
  background-color: var(--accent);
  opacity: 0;
  transform: scaleY(0.3);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

@media (max-width: 859px) {
  .index-row::before { left: -1rem; }
}

.index-row.animate-init {
  opacity: 0;
  transform: translateY(6px);
}

.index-row.is-visible {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.35s ease, transform 0.35s ease;
}

/* .index-link es el layout en grid (número/cuerpo/CTA) de cada fila. En
   el índice de proyectos sigue siendo un único <a> interactivo; en
   servicios es un <div> con su propio "click" (ver createServiceRow) que
   lleva al detalle de ese servicio — el permalink <a> del título
   conserva su href real (clic derecho, pestaña nueva...) por separado.
   Estos resets de fondo/borde no molestan al <div>, solo hacen que un
   <a> se vea igual cuando SÍ es el propio .index-link el interactivo
   (índice). */
.index-link {
  display: grid;
  grid-template-columns: 2.25rem 1fr auto;
  align-items: baseline;
  gap: 1rem;
  width: 100%;
  padding: 1.15rem 0.75rem 1.15rem 1.25rem;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  color: inherit;
  text-decoration: none;
  outline: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

@media (max-width: 620px) {
  .index-link {
    grid-template-columns: 2rem 1fr;
    padding-left: 0.85rem;
  }
}

/* Cubre tanto que .index-link SEA el elemento con foco (índice de
   proyectos, un único <a>) como que el foco esté en alguno de sus hijos
   interactivos (servicios: el <a> del título o el <button> de
   contacto) — de ahí las dos formas del :has() de abajo. */
.index-row:hover,
.index-link:focus-visible,
.index-row:has(.index-link :focus-visible) {
  background-color: var(--surface-hover);
}

.index-row:hover::before,
.index-row:has(.index-link:focus-visible)::before,
.index-row:has(.index-link :focus-visible)::before {
  opacity: 1;
  transform: scaleY(1);
}

.index-link:focus-visible,
.index-link :focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: -2px;
}

.row-number {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-dim);
  transition: color 0.18s ease;
}

/* Corchetes tipo índice de array ([01], [02]...) — conectan con el resto
   de guiños "de código" del sitio (prompt del kicker, hash de commit). El
   número en sí lo sigue poniendo script.js (o el <noscript> de index.html);
   esto solo añade el envoltorio, más tenue que el número para no competir
   con él. */
.row-number::before {
  content: "[";
  color: var(--text-muted);
}

.row-number::after {
  content: "]";
  color: var(--text-muted);
}

.index-row:hover .row-number {
  color: var(--accent);
}

.row-body {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

/* Flujo de texto normal (no flex): así, si el título es largo y salta de
   línea, la etiqueta de estado (y el precio, si lo hay) quedan pegados a
   la derecha de la última palabra del título en vez de bajar solos a una
   línea aparte — el mismo comportamiento que tendría cualquier texto
   seguido de un elemento en línea. En servicios, .row-head es un <h2>
   (antes un <span> decorativo) — el margen de un h2 rompería el "gap" de
   .row-body, así que se resetea a 0; el tamaño/peso de letra los pone
   .row-name (el <a> de dentro), no el propio <h2>. */
.row-head {
  display: block;
  margin: 0;
  font: inherit;
}

/* En el índice de proyectos sigue siendo un <span> decorativo dentro de
   un <a> que ya envuelve toda la fila (createUnitRow); en servicios es
   el permalink real del título — un <a href="#servicio-slug"> propio
   (createServiceRow), sin subrayado ni al pasar el ratón: toda la caja
   ya avisa de que es clicable (fondo + franja de acento al hover, ver
   .index-row:hover), no hace falta que el título se lea como un enlace
   azul de toda la vida además. */
.row-name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.005em;
  text-decoration: none;
}

.row-flag {
  margin-left: 0.75rem;
  /* "middle" (el valor por defecto de vertical-align) alinea con el punto
     medio del x-height, que queda visiblemente más bajo que el centro
     real del título en mayúsculas — este valor lo sube para que el punto
     de estado quede centrado con el texto del título, no colgando. */
  vertical-align: 0.22em;
}

.row-price {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--accent);
  margin-left: 0.6rem;
  vertical-align: 0.22em;
}

.row-desc {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 0.87rem;
  color: var(--text-body);
  line-height: 1.5;
  max-width: 46rem;
}

/* En el índice de proyectos sigue siendo un <span> decorativo (toda la
   fila ya es un <a>, ver createUnitRow); en servicios es un <button>
   real que abre el modal de contacto (createServiceRow) — de ahí el
   reset de fondo/borde/cursor, que un <span> no necesita pero tampoco le
   afecta. */
.row-url {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  line-height: inherit;
  color: var(--text-dim);
  white-space: nowrap;
  align-self: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.18s ease, transform 0.18s ease;
}

@media (max-width: 620px) {
  .row-url {
    grid-column: 2 / -1;
    white-space: normal;
  }
}

.index-row:hover .row-url {
  color: var(--accent);
  transform: translateX(2px);
}

/* --------------------------------------------------------------------------
   Credit / Watermark
   -------------------------------------------------------------------------- */
.credit {
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.5rem 2.5rem;
  display: flex;
  justify-content: center;
}

.credit[hidden] {
  display: none;
}

.credit a {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  text-decoration: none;
  transition: color 0.15s ease;
}

.credit a:hover {
  color: var(--text);
}

.credit a:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

/* Mismo planteamiento que la marca "Powered by" de otro proyecto propio
   del autor: franja fija de ancho completo, centrada con flexbox, casi
   invisible (30% de opacidad) hasta que se pasa el ratón.
   Con la página fija a 100dvh (ver @media 860px de .page, arriba del
   todo), dejarla en el flujo normal le restaba altura real a la lista;
   así, fuera del flujo, no le quita ni un píxel. pointer-events en dos
   pasos: el hueco vacío de alrededor no bloquea clics sobre lo que haya
   debajo, solo el propio enlace los recibe.
   Esta regla vive AQUÍ (junto a la base) y no en el @media de arriba a
   propósito: con la misma especificidad, en igualdad de condiciones gana
   la que esté más abajo en el archivo — si esta regla quedara antes que
   la base en el código, la base la pisaría entera. */
@media (min-width: 860px) {
  .credit {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-width: none;
    margin: 0;
    padding: 0;
    pointer-events: none;
  }

  .credit a {
    pointer-events: auto;
    padding: 4px 8px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 9px;
    letter-spacing: normal;
    color: var(--text);
    opacity: 0.3;
    transition: opacity 0.15s ease;
  }

  .credit a:hover {
    color: var(--text);
    opacity: 0.7;
  }
}

/* --------------------------------------------------------------------------
   Vista de servicios
   No es una página aparte: es index.html cambiando de contenido (ver
   initViewSwitcher en script.js). Reutiliza casi todo lo de arriba
   (.index-row, .row-*, .contact-block...); esto es solo lo que no existía:
   el enlace de volver, el "portón" del teléfono y el párrafo
   introductorio orientativo.
   -------------------------------------------------------------------------- */
/* Entradilla editorial (como el "deck" bajo el titular de un artículo):
   serif en cursiva, más grande que el cuerpo normal, sin caja ni borde —
   funciona como subtítulo de la sección, por encima de "N° SERVICIOS",
   no como un aviso aparte. */
.services-intro {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--text-body);
  line-height: 1.45;
  max-width: 34rem;
  margin-bottom: 1.25rem;
}

/* La parte que de verdad importa leer (la pregunta/CTA) resalta en el
   color de acento del tema activo y sin cursiva, para que el ojo vaya
   directo a lo accionable en vez de perderse en el resto de la frase. */
.services-intro strong {
  font-style: normal;
  font-weight: 600;
  color: var(--accent);
}

/* Tres puntos a favor, no tres tarjetas: sin cajas, sombras ni fondos de
   color de por medio — solo icono + texto, con una línea fina debajo para
   separarlos de la lista, igual que el resto de divisores del sitio. */
/* Sin línea propia debajo: justo después va .index-head, que ya trae la
   suya — dos rayas casi seguidas se veían redundantes. El espacio lo da
   solo el margen. */
.services-highlights {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  margin-bottom: 1.25rem;
}

.services-highlights[hidden] {
  display: none;
}

.service-highlight {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  flex: 1 1 200px;
  min-width: 0;
}

.service-highlight svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 0.2rem;
  fill: var(--accent);
}

.service-highlight-text {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}

.service-highlight-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}

.service-highlight-desc {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 0.8rem;
  color: var(--text-body);
  line-height: 1.45;
}

.masthead-service-extra {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
}

/* Ver el porqué de esta regla en .whatsapp-cta[hidden] más arriba:
   mismo problema de especificidad contra el [hidden] del user-agent. */
.masthead-service-extra[hidden] {
  display: none;
}

.masthead-back {
  margin: 0;
}

/* --------------------------------------------------------------------------
   Detalle de servicio (ver initServiceDetail en script.js)
   -------------------------------------------------------------------------- */
.service-detail {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.service-detail[hidden] {
  display: none;
}

/* Título grande de verdad (mismo tratamiento serif que .identity-name,
   un punto por debajo) — antes reutilizaba .index-head-title, pensado
   para una etiqueta de sección pequeña ("N° SERVICIOS"), no para ser el
   titular del propio detalle. Subrayado minimalista (línea fina, no un
   divisor grueso) bajo el propio texto — "inline-block" para que el
   ancho del borde sea el del texto, no el de toda la columna. */
.service-detail-title {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 1.9rem;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0;
  padding-bottom: 0.4rem;
  border-bottom: 1.5px solid var(--accent);
}

/* Se enfoca por script al entrar al detalle (ver slideServicePanel en
   script.js, para que el lector de pantalla anuncie el título nuevo) —
   sin esto, el navegador le pone su propio anillo de foco por defecto
   (un recuadro negro grueso, nada que ver con el resto del sitio) cada
   vez que el foco "se ve" como de teclado. Mismo tratamiento que el
   resto de :focus-visible del sitio, con más "offset" para no pegarse
   al subrayado de arriba. */
.service-detail-title:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 6px;
}

/* Hueco propio para el precio (antes era .row-price suelto, pensado
   para ir en línea junto a un título pequeño) — aquí es una insignia
   con su propio borde/relleno, al mismo nivel visual que el título
   grande de arriba. */
.service-detail-price {
  display: inline-flex;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.35rem 0.75rem;
}

/* display:inline-flex de arriba gana por especificidad al [hidden] del
   user-agent stylesheet — mismo patrón que .whatsapp-cta[hidden] y
   compañía. Sin priceRange, paintServiceDetailView oculta este badge. */
.service-detail-price[hidden] {
  display: none;
}

/* "description"/"details" pasan por renderMarkdown (ver script.js) en
   vez de texto plano — estos estilos valen tanto para los <p>/<ul> que
   genera como para lo que venga dentro (<strong>/<em>/<code>/<a>). */
.service-detail-body {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  max-width: 46rem;
}

.service-detail-body p,
.service-detail-body ul {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 0.95rem;
  color: var(--text-body);
  line-height: 1.6;
}

.service-detail-body ul {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-left: 1.25rem;
}

.service-detail-body strong {
  color: var(--text);
  font-weight: 700;
}

.service-detail-body code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: var(--surface-hover);
  padding: 0.15em 0.4em;
  border-radius: var(--radius);
}

.service-detail-body a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--accent) 50%, transparent);
}

.service-detail-body a:hover,
.service-detail-body a:focus-visible {
  text-decoration-color: var(--accent);
}

/* Mismo tratamiento que .phone-reveal-btn/.whatsapp-btn del sitio de
   servicios aparte (marceloananga.neocities.org): borde discontinuo en
   acento, sin relleno — se lee como "otra acción", no como un botón
   sólido más de la misma familia que la insignia del precio. Al pasar
   el ratón/foco el borde se cierra a línea continua y se rellena de un
   tinte de acento, sin invertir a acento sólido/texto claro. */
.service-detail-hablamos {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 0.4rem;
  background: transparent;
  border: 1px dashed var(--accent);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius);
  margin-top: 0.5rem;
  cursor: pointer;
  transition: all 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

.service-detail-hablamos:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-style: solid;
}

.service-detail-hablamos:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

/* Antes era un enlace de texto discreto — ahora es el único camino de
   vuelta desde la vista de servicios, así que se trata como un botón de
   verdad: borde y relleno tenue del acento, y se invierte a "lleno" al
   pasar el ratón/foco para que destaque claramente. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1.5px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.5rem 0.9rem;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

.back-link:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
  transform: translateX(-2px);
}

.back-link:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

/* "← Volver a servicios" vive dentro del panel de detalle, junto a
   "¿Hablamos?" (ver .service-detail-hablamos) — mismo tratamiento de
   marceloananga.neocities.org: borde discontinuo en acento, sin
   relleno, en vez de la línea continua/relleno tenue del
   "← Volver al índice" del masthead (mismo .back-link, sin tocar esa
   versión). Un pelín más pequeño, y el hover propio se queda en tinte +
   borde continuo (no invierte a acento sólido como el resto de
   .back-link). */
.service-detail .back-link {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 0.4rem 0.7rem;
  color: var(--accent);
  background: transparent;
  border: 1px dashed var(--accent);
}

.service-detail .back-link:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-style: solid;
  border-color: var(--accent);
  color: var(--accent);
  transform: none;
}

/* Ubicación — solo en la vista de servicios (ver
   SITE_CONFIG.location y initViewSwitcher en script.js). Mismo tratamiento
   tipográfico que .contact-phone (para que no desentone), con un pin en
   el color de acento del tema — un icono da más peso visual a este dato
   que otro simple renglón de texto suelto. */
.service-location {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text);
}

.service-location[hidden] {
  display: none;
}

.location-pin {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  fill: var(--accent);
}

/* --------------------------------------------------------------------------
   Modal de contacto
   Se abre al pulsar cualquier servicio (ver initContactModal en script.js):
   una versión más grande y centrada del bloque de contacto, sobre un fondo
   oscurecido que tapa el resto de la página.
   -------------------------------------------------------------------------- */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: light-dark(rgba(23, 20, 15, 0.45), rgba(0, 0, 0, 0.65));
  animation: modal-fade-in 0.18s ease;
}

/* display:flex de arriba gana por especificidad al [hidden] del user-agent
   stylesheet — mismo patrón que .whatsapp-cta/.masthead-service-extra más arriba. */
.modal-backdrop[hidden] {
  display: none;
}

@keyframes modal-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.contact-modal {
  position: relative;
  width: 100%;
  max-width: 26rem;
  max-height: calc(100vh - 3rem);
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-elevated);
  padding: 2.5rem 2rem 2.25rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  animation: modal-pop-in 0.22s cubic-bezier(0.2, 0.8, 0.3, 1.1);
}

@keyframes modal-pop-in {
  from { opacity: 0; transform: translateY(8px) scale(0.97); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-close {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text-dim);
  font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}

.modal-close:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.modal-close:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

/* Línea de apoyo bajo "CONTACTO" que quita presión antes de que la
   persona elija canal — solo aparece aquí (el modal es donde alguien
   duda si escribir), no en el bloque de contacto del masthead. */
.contact-modal-note {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 0.85rem;
  color: var(--text-body);
  line-height: 1.45;
  margin: -0.3rem 0 0;
}

/* Reutiliza .contact-kicker, .contact-email, .contact-actions y
   .contact-phone tal cual (mismos componentes que el masthead) — estos
   modificadores solo agrandan la tipografía para que se lea bien como
   pieza central de la pantalla, no de refilón en una barra lateral. El
   factor de escalado (~1.2x sobre el tamaño del masthead) es el mismo
   para .whatsapp-cta--modal y para correo/teléfono, para que los tres
   crezcan en la misma proporción en vez de que uno destaque muchísimo
   más que los otros dos. */
.contact-email--modal {
  font-size: 1.05rem;
  font-weight: 700;
  padding-bottom: 0.12rem;
}

/* Mismo peso visual que .contact-email--modal — antes se quedaba bastante
   más pequeño y sin negrita, como si importara menos que el correo. */
.contact-phone--modal {
  font-size: 1.05rem;
  font-weight: 700;
  padding-bottom: 0.12rem;
}

body.modal-open {
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Tarjeta "/qr" y "/servicios/qr" (ver initQrPage en script.js)
   -------------------------------------------------------------------------- */
.qr-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1.25rem;
}

.qr-card {
  width: 100%;
  max-width: 22rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-elevated);
  padding: 2.5rem 2rem;
}

.qr-card .identity-role {
  margin-bottom: 0.4rem;
}

.qr-code {
  width: 100%;
  max-width: 15rem;
  margin-top: 0.6rem;
}

.qr-code svg {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.qr-hint {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-dim);
  letter-spacing: 0.03em;
  margin-top: 0.3rem;
}

.qr-back-link {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.1rem;
  margin-top: 0.6rem;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.qr-back-link:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.qr-back-link:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 2px;
}

.qr-page[hidden] {
  display: none;
}

/* --------------------------------------------------------------------------
   Accessibility & Reduced Motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .index-row.animate-init {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Cinturón y tirantes: script.js ya evita animar la transición de vista
     cuando hay preferencia de movimiento reducido, esto es solo por si
     alguna de estas clases quedara puesta. */
  .panel-slide-left,
  .panel-slide-right {
    transform: none !important;
    opacity: 1 !important;
  }

  .modal-backdrop,
  .contact-modal {
    animation: none !important;
  }
}

/* --------------------------------------------------------------------------
   Small Screens
   -------------------------------------------------------------------------- */
@media (max-width: 480px) {
  .page {
    padding: 2.25rem 1.1rem;
    gap: 2rem;
  }

  .identity-name {
    font-size: 1.7rem;
  }

  .index-link {
    padding: 1rem 0.6rem 1rem 0.85rem;
    gap: 0.75rem;
  }

  .row-name {
    font-size: 0.98rem;
  }

  .row-desc {
    font-size: 0.84rem;
  }
}

/* --------------------------------------------------------------------------
   Página 404
   -------------------------------------------------------------------------- */
.error-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.4rem;
  padding: 2rem 1.5rem;
}

.error-code {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--accent);
}

.error-title {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 500;
  color: var(--text);
  margin: 0.25rem 0 0.5rem;
}

.error-desc {
  font-size: 0.92rem;
  color: var(--text-body);
  max-width: 26rem;
  line-height: 1.5;
  margin-bottom: 1.25rem;
}

.error-link {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.15rem;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.error-link:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.error-link:focus-visible {
  outline: 2px solid var(--line-strong);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   Print
   -------------------------------------------------------------------------- */
@media print {
  .skip-link,
  .theme-toggle {
    display: none;
  }

  body {
    background: #fff;
  }
}
