/* davidmuntane.com — hoja única del sitio.
   Todo valor sale de `docs/02-design-system.md`. Si hace falta uno nuevo, se mide y se
   escribe allí primero; aquí no se inventa ninguno. La referencia va entre paréntesis. */

/* Las dos únicas familias que se pueden servir, según §3 de `docs/02b-tipografia.md`.
   Van tal cual salen de `type/fuentes/usables/`, sin resubsetear (§2.4 y regla 4 de la
   constitución). Las familias bloqueadas están listadas en ese documento y no entran
   aquí en ninguna forma; el nombre del sitio va como SVG, nunca como texto compuesto. */
@font-face {
  font-family: "Veracruz Azulejo";
  src: url("/tipos/Veracruz-Azulejo.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Veracruz";
  src: url("/tipos/Veracruz-Regular.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  /* §1.1 — color, muestreado del material */
  --cal: #F1F3F9;
  --cal-hueso: #FBFBF8;
  /* El hueco deja ver la pared por debajo. El 0.88 no es estético: es el valor más bajo
     medido que mantiene el peor par de contraste muy por encima de AA con cualquiera de
     las cinco tintas de la pared detrás — 12,1:1 el texto sobre hueco claro y 11,1:1 el
     texto claro sobre la puerta de cobalto. */
  --velo: 0.88;
  /* El velo de la PARED (no el del hueco). La pared llena 8 de las 14 columnas a
     ancho completo —los 2+2 de margen y los 4 del costado—, y a plena tinta compite
     con el texto en vez de acompañarlo. Este velo la lava sin tocar el patrón ni la
     retícula. 0 = pared a plena tinta; 1 = pared blanca. */
  --velo-pared: 0.55;
  --hueco-fondo: color-mix(in srgb, var(--cal-hueso) calc(var(--velo) * 100%), transparent);
  --cobalto: #0A1580;
  --cobalto-medio: #223883;
  --azulejo: #006297;
  --azulejo-hondo: #004E7A;
  --geranio: #C22F5C;
  --geranio-hondo: #A8244B;
  --albero: #C0923A;
  --albero-hondo: #7A5A18;
  --teja: #6E3C3F;
  --tinta-negra: #000000;
  --lavanda: #D3D6FA;

  /* §1.1 — la pared. Siete tokens remedidos píxel a píxel sobre
     `assets-src/visuales/azulejos-nombre-ines.png` (3178×1562). Los usa la retícula-patio
     del inicio; las cuatro tintas del patrón están además repetidas en `rgb()` dentro del
     data-URI de la pared, porque un data-URI no puede leer una custom property. */
  --azulejo-blanco: #F9F9F7;   /* (1841,  967) — el bizcocho vidriado */
  --patron-hondo:   #002649;   /* (2484,  804) — el perfilado */
  --patron-medio:   #247CAA;   /* (1436, 1412) — la aguada media */
  --patron-claro:   #A7C9D6;   /* (1421, 1292) — la aguada clara */
  --patron-palido:  #C8DCE5;   /* (1525, 1145) — el velo */
  --cielo:          #83BCFF;   /* `single-me-quedao-pegao.jpg` (500, 300) */
  /* La luz del patio (§7.3): --patron-hondo al 7 %. Es un velo, no un color de nada */
  --luz-patio: rgba(0, 38, 73, 0.07);

  /* §2.4 — familias. La pila de sistema existe solo para cifras (§2.3) */
  --fuente-titular: "Veracruz Azulejo", "Veracruz", Georgia, serif;
  --fuente-texto: Georgia, "Times New Roman", serif;
  --fuente-dato: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* §2.2 y §2.4: un verso pasa de dos líneas, y ahí Azulejo deja de leerse como marco y
     empieza a leerse como tachado. Los versos van en Veracruz Regular, que es la misma
     familia sin cenefa. El respaldo real de las dos es Georgia: los dos OTF tienen los
     mismos 91 codepoints, así que Regular nunca es respaldo de Azulejo. */
  --fuente-verso: "Veracruz", Georgia, serif;

  /* §2.5 — escalera. Estos son los tamaños de 375 px; suben en la consulta de 900 px */
  --t-portada: clamp(40px, 7vw, 92px);
  --t-titulo: 34px;
  --t-verso: 24px;
  --t-seccion: 24px;
  --t-tarjeta: 20px;
  --t-texto: 17px;
  --t-destacado: 20px;
  --t-dato: 20px;
  --t-anotacion: 14px;

  --int-portada: 1.02;
  --int-titulo: 1.08;
  --int-verso: 1.25;
  --int-seccion: 1.2;
  --int-tarjeta: 1.3;
  --int-texto: 1.65;
  --int-destacado: 1.6;
  --int-dato: 1.25;
  --int-anotacion: 1.5;

  --let-portada: -0.01em;
  --let-titulo: -0.005em;
  --let-verso: 0;
  --let-seccion: 0;
  --let-tarjeta: 0.005em;
  --let-texto: 0.015em;
  --let-destacado: 0.01em;
  --let-dato: 0.01em;
  --let-anotacion: 0.02em;

  /* §2.5 — ritmo vertical de 28 px. Toda caja y separación es múltiplo de 28 o de 14 */
  --medio-ritmo: 14px;
  --ritmo: 28px;
  --ritmo-2: 56px;
  --ritmo-3: 84px;
  --ritmo-4: 112px;

  /* §3.1 y §3.2 — retícula de azulejo */
  --azulejos: 4;
  --junta: 3px;
  --margen: 20px;
  --ancho-maximo: 1280px;
  --medida-texto: 30rem;       /* §3.2: 6 azulejos, los 52 caracteres de §2.6 */
  --filete: 1px;               /* §6: filete de 1 px en --lavanda */
  --foco: 3px;                 /* §1.4: contorno de 3 px */
  --foco-aire: 2px;            /* §1.4: 2 px de separación */
  /* §5.2 — `--sello` (la garrapata, entre 64 y 160 px) ya no vive aquí: depende de
     `--pieza`, que solo existe dentro de `.patio-pared`. Declarada aquí quedaba
     inválida en tiempo de cómputo y esa invalidez se heredaba hacia abajo — ver el
     bloque `.patio-pared` donde se declara ahora, con la medición. */
  --mosaico-maximo: 400px;     /* §4.1: la galería publica a 4 azulejos (≈400 px) */
  --vertical-maxima: 360px;    /* §4.2: una vertical de vídeo, nunca más ancha */
  --producto-imagen: 480px;    /* §4.2: formaciones, tienda y conciertos sirven a 480 */
  /* Encargo 2026-09-22, «la foto del widget de Spotify se sale en móvil»: medido, a
     375 px el widget normal (352 px de alto, el que declara su propio oEmbed) ocupa el
     86 % de una ventana de iPhone de 667 px. 152 px es el alto del embed COMPACTO que
     la propia documentación de Spotify define (80/152/352 según la altura que se le
     reserve) — no es un número inventado, es el que Spotify ya usa para su layout
     reducido; basta con reservarle esa caja para que pinte compacto solo. */
  --alto-fachada-compacta: 152px;
  /* El lado de un azulejo, en porcentaje del ancho del bloque que lo contiene (§3.1) */
  --lado-azulejo: calc((100% - (var(--azulejos) - 1) * var(--junta)) / var(--azulejos));
}

@media (min-width: 600px) { :root { --azulejos: 6;  --margen: 28px; } }
@media (min-width: 900px) {
  :root {
    --azulejos: 8; --margen: 40px; --junta: 4px;
    --t-titulo: 56px; --t-verso: 34px; --t-seccion: 30px;
    --t-tarjeta: 21px; --t-dato: 24px;
  }
}
@media (min-width: 1200px) { :root { --azulejos: 12; --margen: 56px; } }

/* ---------------------------------------------------------------- base */

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--cal);
  /* Contenedor de consulta para `--pieza` (más abajo, en `.patio-pared`): `100vw`
     incluye la barra de desplazamiento cuando la hay y desborda la página en
     escritorio, y en algunos WebKit de iOS con overscroll también. `100cqw` mide el
     propio ancho ya maquetado de `html`, que no la incluye. Ver la nota junto a
     `--pieza`. */
  container-type: inline-size;
}

body {
  margin: 0;
  background: var(--cal);
  color: var(--cobalto);                    /* 13.22:1 sobre --cal (§1.3) */
  font-family: var(--fuente-texto);
  font-size: var(--t-texto);
  line-height: var(--int-texto);
  letter-spacing: var(--let-texto);
  font-weight: 400;                          /* §2.6.3: ni negrita ni cursiva */
  overflow-wrap: break-word;
}

/* §2.6.3 — no hay negrita que sintetizar, así que nada la pide */
h1, h2, h3, strong, b { font-weight: 400; }
h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul { list-style: none; padding: 0; }
img, svg { max-width: 100%; }

/* Las cifras, siempre en pila de sistema (§2.3 y regla 4 de la constitución) */
.dato {
  font-family: var(--fuente-dato);
  letter-spacing: var(--let-dato);
  font-variant-numeric: lining-nums tabular-nums;
}

/* §1.4 — el foco nunca se quita */
:focus-visible {
  outline: var(--foco) solid var(--geranio);
  outline-offset: var(--foco-aire);
}
.sobre-cobalto :focus-visible { outline-color: var(--albero); }

a { color: var(--azulejo); }                 /* 5.93:1 sobre --cal (§1.3) */
a:hover { color: var(--azulejo-hondo); }     /* 7.98:1 */

/* Escondido con la misma técnica de recorte que `.visualmente-oculto` (arriba), no con
   `left: -9999px`: ese método viejo deja el enlace fuera del lienzo sin `top`, con su
   bloque contenedor en el inicial, y en Safari de iOS es causa conocida de scroll
   horizontal. Recortado a 1 px se queda dentro del flujo y no lo produce. */
.salto-al-contenido {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  background: var(--cobalto);
  color: var(--cal);
}
.salto-al-contenido:focus {
  width: auto; height: auto;
  margin: 0;
  overflow: visible;
  clip-path: none;
  white-space: normal;
  padding: var(--medio-ritmo) var(--ritmo);
  left: var(--margen);
  top: var(--medio-ritmo);
  z-index: 2;
}

.envoltorio {
  max-width: var(--ancho-maximo);
  margin-inline: auto;
  padding-inline: var(--margen);
}

.columna { max-width: var(--medida-texto); }  /* §2.6.1 */

.anotacion {
  font-size: var(--t-anotacion);
  line-height: var(--int-anotacion);
  letter-spacing: var(--let-anotacion);
  color: var(--cobalto-medio);                /* 9.65:1 sobre --cal (§1.3) */
}

/* ---------------------------------------------------- cabecera de azulejo
   §3.3: una sola zona de azulejo por pantalla, y es esta. El logotipo entra en un
   hueco de azulejo blanco macizo, como en `azulejos-nombre.png`. */

.cabecera { background: var(--cal); }

.banda { display: grid; gap: var(--junta); }

.banda__fila { display: flex; gap: var(--junta); }
.banda__fila > span { flex: 1 1 0; aspect-ratio: 1; }
.banda__fila > span:nth-child(odd)  { background: var(--azulejo); }
.banda__fila > span:nth-child(even) { background: var(--cal-hueso); }
.banda__fila--contraria > span:nth-child(odd)  { background: var(--cal-hueso); }
.banda__fila--contraria > span:nth-child(even) { background: var(--azulejo); }
/* Tantos azulejos como columnas tenga la retícula en cada ancho (§3.2) */
.banda__fila > span:nth-child(n+5) { display: none; }
@media (min-width: 600px)  { .banda__fila > span:nth-child(n+5)  { display: block; }
                             .banda__fila > span:nth-child(n+7)  { display: none; } }
@media (min-width: 900px)  { .banda__fila > span:nth-child(n+7)  { display: block; }
                             .banda__fila > span:nth-child(n+9)  { display: none; } }
@media (min-width: 1200px) { .banda__fila > span:nth-child(n+9)  { display: block; } }

.banda__hueco {
  background: var(--cal-hueso);
  display: flex;
  justify-content: center;
  align-items: center;
  /* §5.1 pide un azulejo entero de aire. Por debajo de 600 px un azulejo son ~82 px y
     el logotipo se quedaría en 167 px, por debajo de su mínimo de 180 px: ahí manda el
     mínimo y el aire lateral baja a la junta. Anotado para `ui-designer`. */
  padding-block: var(--ritmo);
  padding-inline: var(--junta);
}
@media (min-width: 600px) { .banda__hueco { padding: var(--lado-azulejo); } }

.banda__logotipo { display: block; line-height: 0; }
.banda__logotipo img {
  display: block;
  width: 100%;
  height: auto;
  min-width: 180px;                 /* §5.1: por debajo, los remates se empastan */
}

/* ------------------------------------------------------------ navegación */

.navegacion { padding-block: var(--ritmo); }
.navegacion ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--medio-ritmo) var(--ritmo);
}
.navegacion a {
  text-decoration: none;
  border-bottom: var(--filete) solid var(--lavanda);
  padding-bottom: calc(var(--medio-ritmo) / 2);
}
.navegacion a[aria-current="page"] {
  color: var(--cobalto);
  border-bottom-color: var(--cobalto);
}

/* -------------------------------------------------------------- secciones */

.seccion { padding-block: var(--ritmo-2); }
.seccion + .seccion { border-top: var(--filete) solid var(--lavanda); }

.seccion__titulo {
  /* Azulejo trae la cenefa dibujada dentro de cada letra: funciona en rótulos
     cortos como estos, donde el filete lee como marco y no como tachado. */
  font-family: var(--fuente-titular);
  font-size: var(--t-seccion);
  line-height: var(--int-seccion);
  letter-spacing: var(--let-seccion);
  margin-bottom: var(--ritmo);
}

.verso {
  font-family: var(--fuente-titular);
  font-size: var(--t-verso);
  line-height: var(--int-verso);
  letter-spacing: var(--let-verso);
  max-width: var(--medida-texto);
  margin-bottom: var(--medio-ritmo);
}

.prosa p + p { margin-top: var(--ritmo); }


/* El único uso de geranio de la página (§1.2). Sobre cobalto el geranio claro no pasa
   AA (2.69:1), así que va invertido: cal sobre --geranio-hondo, 6.27:1 (§1.3) */
.boton {
  display: inline-block;
  /* Un botón nunca puede ser más ancho que lo que lo contiene. Medido a 375 px: el de
     correo llegaba a 316 px dentro de un párrafo de 269 y se salía del margen 14 px.
     `box-sizing` para que el relleno cuente dentro del tope, y `overflow-wrap` para que
     una dirección larga parta en dos líneas en vez de empujar la caja. */
  max-width: 100%;
  box-sizing: border-box;
  overflow-wrap: anywhere;
  background: var(--geranio-hondo);
  color: var(--cal);
  border: var(--filete) solid var(--lavanda);
  padding: var(--medio-ritmo) var(--ritmo);
  text-decoration: none;
  font-size: var(--t-destacado);
  letter-spacing: var(--let-destacado);
}
.boton:hover { color: var(--cal); background: var(--geranio-hondo); }

/* Encargo 2026-09-22: el correo de la tienda, como botón azul de esquinas redondeadas.
   Modificador de `.boton`, no un componente nuevo: hereda tipografía, relleno y
   filete, y solo cambia el fondo y el radio. `--cobalto` porque es el par ya
   verificado con `--cal` (13,22:1, muy por encima de AA — §1.3), no un azul nuevo que
   medir. `--radio-boton` está declarado junto a `--cerco` (§ arriba). */
.boton--correo,
.boton--correo:hover {
  background: var(--cobalto);
  border-radius: var(--radio-boton);
}

/* ---------------------------------------------------------------- escucha */

.enlaces { display: flex; flex-direction: column; gap: var(--medio-ritmo); }

/* ------------------------------------------------------------------- pie */

.pie {
  background: var(--cobalto);
  color: var(--cal);
  padding-block: var(--ritmo-2);
}
.pie a { color: var(--cal); }
.pie .anotacion { color: var(--lavanda); }   /* 10.31:1 sobre --cobalto (§1.3) */
/* Encargo 2026-09-22, «los iconos a la derecha del membrete»: la navegación y los
   cuatro iconos de redes son dos filas de la misma fila flex, y se reparten los
   extremos. A 375 px (§ encargo, «que quepan sin apelotonarse») cinco enlaces de texto
   más cuatro iconos no caben en una línea: `flex-wrap: wrap` dejaba caer los iconos a
   su propia línea, debajo de la navegación — medido, sin solaparse ni recortarse. */
.pie__fila {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--medio-ritmo) var(--ritmo);
}
.pie__navegacion ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--medio-ritmo) var(--ritmo);
}
/* `.pie__atribucion` se quitó el 2026-09-21 con la línea de tipografía del pie. */

/* Los cuatro iconos de redes, en el pie: solo icono, así que el `<a>` entero es el
   objetivo de pulsación. `padding: var(--medio-ritmo)` alrededor de un icono de 1em
   (≈17 px, §2.5) da una caja de ≈45×45 px — de sobra sobre el mínimo de 24×24 px, y
   sale de los mismos tokens de espaciado que el resto del sitio, no de un píxel
   suelto. */
/* Medido a 375 y 414 px: cuando los iconos caían a su propia línea, `justify-content:
   space-between` de `.pie__fila` los dejaba pegados al margen izquierdo —un único
   elemento en su línea no tiene nada de qué separarse—, no a la derecha que pide el
   encargo. `margin-left: auto` empuja ese `<nav>` al final de su línea tanto si
   comparte fila con la navegación como si cae a la suya propia. */
.pie__redes { margin-left: auto; }
.pie__redes ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--cerco);
}
.pie__redes a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--medio-ritmo);
}
/* El icono hereda `currentColor` del enlace que lo envuelve: --cal en el pie
   (13,22:1 sobre --cobalto, §1.3), muy por encima del 3:1 de WCAG 1.4.11 para un
   elemento no textual. */
.icono-red { width: 1em; height: 1em; flex: none; }
.enlaces--redes a {
  display: inline-flex;
  align-items: center;
  gap: var(--medio-ritmo);
}

/* ------------------------------------------------- título de página interior
   El h1 de las cuatro páginas que no son el inicio. En Veracruz Azulejo, como los
   títulos de sección, y con su verso debajo (§7: nunca más de uno por pantalla). */

.pagina { padding-block: var(--ritmo-2) var(--ritmo); }

.pagina__titulo {
  font-family: var(--fuente-titular);
  font-size: var(--t-titulo);
  line-height: var(--int-titulo);
  letter-spacing: var(--let-titulo);
  margin-bottom: var(--ritmo);
}

/* Para lectores de pantalla y nada más: el aviso de «copiado» (§6.3 pide texto, no
   iconos; esto es el texto que oye quien no ve el botón cambiar). */
.visualmente-oculto {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------- fichas de contacto (§6.3)
   Dos fichas de rango distinto, no una lista. La de contratación va primera y en
   cobalto macizo: es la respuesta a la pregunta que trae al visitante. */

.seccion--fichas { display: flex; flex-direction: column; gap: var(--ritmo); }

.ficha { padding: var(--ritmo); }

.ficha__rotulo {
  font-family: var(--fuente-titular);
  font-size: var(--t-seccion);
  line-height: var(--int-seccion);
  letter-spacing: var(--let-seccion);
  margin-bottom: var(--medio-ritmo);
}

.ficha__persona {
  font-size: var(--t-destacado);
  line-height: var(--int-destacado);
  letter-spacing: var(--let-destacado);
}

.ficha__datos {
  display: flex;
  flex-direction: column;
  gap: var(--medio-ritmo);
  margin-block: var(--ritmo);
}

/* El dato y su botón, en la misma línea, y el botón cae debajo si no cabe a 375 px */
.ficha__linea {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--medio-ritmo);
}

.ficha__dato {
  font-size: var(--t-dato);
  line-height: var(--int-dato);
}
/* El correo no es cifra: en la escala de dato no cabe en una línea de 375 px */
.ficha__correo {
  font-size: var(--t-texto);
  line-height: var(--int-texto);
  letter-spacing: var(--let-texto);
}

/* La ficha de contratación: fondo cobalto, texto cal, 13.22:1 (§1.3) */
.ficha--contratacion {
  background: var(--cobalto);
  color: var(--cal);
}
.ficha--contratacion a { color: var(--cal); }
.ficha--contratacion .ficha__papel { color: var(--lavanda); }  /* 10.31:1 (§1.3) */

/* La de David: cal con filete, y su teléfono un escalón por debajo. Un solo teléfono
   destacado en la página, y es el de contratación. */
.ficha--secundaria { border: var(--filete) solid var(--lavanda); }
.ficha--secundaria .ficha__papel { color: var(--cobalto-medio); }  /* 9.65:1 (§1.3) */
.ficha__dato--discreto {
  font-size: var(--t-texto);
  line-height: var(--int-texto);
  color: var(--azulejo-hondo);                                     /* 7.98:1 (§1.3) */
}

/* ----------------------------------------------------- botones de copiar
   No se escriben en el HTML: los crea `copiar.js` si existe `navigator.clipboard`
   (criterio 5 de la slice 2). Sin él no hay botón y el dato sigue seleccionable. */

.copiar {
  font-family: var(--fuente-texto);
  font-size: var(--t-anotacion);
  line-height: var(--int-anotacion);
  letter-spacing: var(--let-anotacion);
  background: transparent;
  color: var(--azulejo);                    /* 5.93:1 sobre --cal (§1.3) */
  border: var(--filete) solid var(--lavanda);
  border-radius: 0;                         /* §6: el azulejo no tiene esquinas */
  padding: calc(var(--medio-ritmo) / 2) var(--medio-ritmo);
  cursor: pointer;
}
.copiar:hover { color: var(--azulejo-hondo); }                     /* 7.98:1 */
.sobre-cobalto .copiar { color: var(--cal); }                      /* 13.22:1 */
.sobre-cobalto .copiar:hover { color: var(--lavanda); }            /* 10.31:1 */

.copiar-entera:empty { display: none; }     /* sin JavaScript no queda ni el hueco */
.copiar--entera {
  font-size: var(--t-texto);
  letter-spacing: var(--let-texto);
  padding: var(--medio-ritmo) var(--ritmo);
}
/* ------------------------------------------------------------------ redes */

.enlaces--redes li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--medio-ritmo);
}

/* ------------------------------------------------------- sello (§5.2)
   La garrapata entera, con su cuadro negro, a dos azulejos (`--sello: calc(var(--pieza)
   * 2)`, arriba). Un azulejo se probó primero (83,75–117 px, ver arriba) y quedó por
   debajo del sello de 120 px fijos que el cliente ya daba por bueno — «muy pequeña»,
   corrección directa. Dos azulejos sigue siendo un número entero de la retícula, y su
   rango (167,5–192 px) se documenta como excepción sobre el tope de 160 px de §5.2 en
   HALLAZGOS, no en silencio. */

.sello { padding-block: var(--ritmo); }
.sello img {
  display: block;
  margin-inline: auto;   /* el sello centrado en su azulejo, no pegado al margen */
  width: var(--sello);
  height: auto;
}
.seccion--sello { border-top: var(--filete) solid var(--lavanda); }

/* ------------------------------------------- tarjeta de concierto (§6.1)
   Jerarquía: fecha, población, sala, formato. La población antes que la sala, porque
   quien programa piensa en mapa. Sin azulejo de fondo: §3.3 lo prohíbe expresamente
   detrás de la tarjeta, que es el dato que decide una contratación. */

.conciertos { display: flex; flex-direction: column; gap: var(--ritmo); }

.concierto {
  background: var(--cal);
  border: var(--filete) solid var(--lavanda);
  padding: var(--ritmo);
  display: flex;
  flex-direction: column;
  gap: calc(var(--medio-ritmo) / 2);
}

.concierto__fecha {
  font-size: var(--t-dato);
  line-height: var(--int-dato);
}
/* §1.2: un solo uso de geranio por página, y aquí es «la fecha más próxima». En el
   estado vacío no hay lista y el geranio se lo lleva el botón de contratar: nunca
   coinciden los dos en la misma pantalla. */
.conciertos--proximas > li:first-child .concierto__fecha {
  color: var(--geranio-hondo);              /* 6.27:1 sobre --cal (§1.3) */
}

.concierto__poblacion {
  font-size: var(--t-destacado);
  line-height: var(--int-destacado);
  letter-spacing: var(--let-destacado);
}
.concierto__pie {
  font-size: var(--t-anotacion);
  line-height: var(--int-anotacion);
  letter-spacing: var(--let-anotacion);
  color: var(--cobalto-medio);              /* 9.65:1 sobre --cal (§1.3) */
}
.concierto__nota { margin-top: calc(var(--medio-ritmo) / 2); }
.concierto .boton { margin-top: var(--medio-ritmo); }

/* Un historial de bolos vende, pero no compite con lo que viene (§6.1) */
.concierto--pasado { color: var(--cobalto-medio); }     /* 9.65:1 sobre --cal (§1.3) */

/* ------------------------------------------------ estado vacío (§6.1)
   El que más se va a ver: el verso hace el trabajo emocional y la línea siguiente el
   práctico. El botón lleva a contacto, no a un formulario vacío. */

.vacio {
  border: var(--filete) solid var(--lavanda);
  padding: var(--ritmo);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--medio-ritmo);
}
.vacio .sello { padding-block: 0; }
.vacio__texto { max-width: var(--medida-texto); }
.vacio .boton { margin-top: var(--medio-ritmo); }

/* ------------------------------------------------ mosaico de galería (§6.5)
   Mosaico libre de 3:4, 1:1 y 4:3, máximo 4 azulejos de ancho (§4.1). El pie va
   debajo de la foto, nunca encima, y el crédito en anotación de 14 px (§4.4). */

.mosaico {
  display: grid;
  gap: var(--ritmo);
  grid-template-columns: 1fr;
  margin-bottom: var(--ritmo);
}
@media (min-width: 600px) { .mosaico { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1200px) { .mosaico { grid-template-columns: repeat(3, 1fr); } }

/* §4.2: la vertical de vídeo nunca va a ancho completo ni de fondo. A 375 px la
   columna mide 335 px y ya cumple; de 1200 px en adelante hay que frenarla. */
.pieza--captura { max-width: var(--vertical-maxima); }

/* TRÍO — encargo 4 (2026-09-22): eventos de una sola foto, en la misma fila, triple
   columna equiespaciada, en vez de una sección entera para una sola imagen. Vive
   dentro de `.patio--galeria` (mismo ensanchado que el mosaico, ver más abajo). Por
   debajo de 600 px: una columna, porque tres a la vez dejarían la miniatura
   inservible (§4.1). De 600 a 899: dos, y la tercera baja a su propia fila — no hay
   ancho para sostener tres columnas legibles antes de los 900 px, el mismo umbral que
   ya usa `sizes` en `marca_pieza()`. Desde 900: las tres, iguales. Declarada después
   de `.mosaico` y con su misma especificidad a propósito: gana la cascada en las tres
   franjas sin depender de qué clase venga antes en el HTML. */
.trio {
  display: grid;
  gap: var(--ritmo);
  grid-template-columns: 1fr;
  margin-bottom: var(--ritmo);
}
@media (min-width: 600px) { .trio { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .trio { grid-template-columns: repeat(3, 1fr); } }
.trio__item {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* El dintel de cada columna es el componente de verdad (`marca_dintel()`, igual que
   el resto de la página) — mismo tratamiento, salvo la talla en el tramo estrecho de
   arriba, medida junto a `--t-dintel-trio`. */
@media (min-width: 900px) and (max-width: 1039px) {
  .patio-pared .trio .dintel .rotulo { font-size: var(--t-dintel-trio); }
}

.pieza__figura { display: flex; flex-direction: column; gap: var(--medio-ritmo); }

.pieza__enlace {
  display: block;
  aspect-ratio: var(--aspecto);
  /* Es un hijo flex en `.pieza__figura` y en `.trio__item` (ambos en columna): sin
     esto, el tamaño mínimo automático de un ítem flex puede colarse por la relación
     de aspecto real de la foto de dentro y ganarle a `--aspecto` — medido en el trío
     (encargo 2026-09-22): una foto 3:4 dentro de una columna forzada a 1:1 salía más
     alta que sus dos vecinas 4:3 y 1:1, con los tres bordes escalonados. */
  min-height: 0;
  background: var(--lavanda);          /* mientras carga, no un hueco blanco */
  line-height: 0;
}
.pieza__enlace picture { display: block; width: 100%; height: 100%; }
.pieza__enlace img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--punto, 50% 50%);   /* §4.2: el centro por defecto no vale */
}

/* `.pieza__pie` y `.pieza__credito` se quitaron el 2026-09-21: el pie de cada foto del
   mosaico desapareció (slice 4, punto 1 — la información sube al subtítulo del grupo,
   ver `.dintel__subtitulo` más abajo) y con él, su crédito. */

/* LA GUITARRA — encargo 2026-09-22: la casilla libre de un grupo de mosaico (hoy,
   «Sesión de cartel») se rellena en vez de dejarse en pared. Solo existe la casilla a
   partir de 1200 px, donde `.mosaico` llega a 3 columnas (línea 609): por debajo, 2
   fotos ya llenan su fila entera (1 o 2 columnas) y no hay nada que tapar, así que la
   guitarra se queda oculta — `tools/construir.py` no decide el ancho, el CSS sí, con el
   mismo corte que ya gobierna las columnas del mosaico. Clase 0 de §7.1: no responde
   nunca, `aria-hidden` en el marcado y fuera del tabulador por construcción (no es un
   enlace ni tiene `tabindex`). */
.pieza--decorativa { display: none; pointer-events: none; }
@media (min-width: 1200px) { .pieza--decorativa { display: block; } }
.pieza__enlace--decorativo {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--ritmo);
  /* `.pieza__enlace` trae `background: var(--lavanda)` para la foto que aún carga
     (línea 655): la guitarra no es una foto, así que no lleva ese sustrato — fondo
     transparente, igual que las macetas (§3.7), que se apoyan directamente en el
     fondo del hueco. */
  background: transparent;
}
/* Más pequeña que el hueco a propósito —no es una foto, es un guiño— y girada 45°
   a la derecha, apoyada en su propio centro del azulejo. */
.pieza__enlace--decorativo svg {
  width: 68%;
  height: 68%;
  transform: rotate(45deg);
}

/* ------------------------------------------------------- vista grande (§6.5)
   El único momento en que el sitio se pone oscuro, y solo mientras se mira una foto.
   Es una página con su URL, no un modal: funciona sin JavaScript (ADR 0002). */

.vista-grande #contenido {
  background: var(--cobalto);
  color: var(--cal);                    /* 13.22:1 (§1.3) */
}
.vista-grande #contenido a { color: var(--cal); }
.vista-grande #contenido a:hover { color: var(--lavanda); }   /* 10.31:1 */
.vista-grande #contenido :focus-visible { outline-color: var(--albero); }
.vista-grande .anotacion { color: var(--lavanda); }

.vista { padding-block: var(--ritmo-2); }
/* La foto manda en esta página: la figura se ciñe a su ancho y se centra, y el pie
   se queda alineado con el borde izquierdo de la imagen, no con el de la página. */
.vista__figura {
  display: flex;
  flex-direction: column;
  gap: var(--medio-ritmo);
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}

/* Nunca por encima del tamaño nativo (§4.2): `--nativo` lo escribe el constructor
   leyendo la derivada mayor de `media/manifiesto.json`. */
.vista__marco { max-width: min(100%, var(--nativo)); }
.vista__marco--captura { max-width: var(--vertical-maxima); }
.vista__marco img { display: block; width: 100%; height: auto; }

.vista__pie { max-width: var(--medida-texto); display: flex; flex-direction: column; }

.vista__saltos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--medio-ritmo) var(--ritmo);
  margin-top: var(--ritmo-2);
}


/* ═══════════════════════════════════════════════════════════════════════════════════
   LA RETÍCULA-PARED, PROPAGADA A LAS CUATRO PÁGINAS (§3 y §7)

   `.patio-pared` ya no es un ensayo de una sola página: `tools/construir.py` le pone
   esta clase de cuerpo al inicio, a contacto, a conciertos y a galería. Las cuatro
   comparten pared, cabecera, huecos y tipografía de azulejo; lo que cambia entre ellas
   son dos modificadores de sección, añadidos más abajo, y nada del bloque compartido:

   - `.patio--fijo`   — la sección no alterna de lado (§ más abajo). La usan contacto y
     conciertos, que tienen dos o tres secciones y para las que la alternancia se leía
     como un baile sin ritmo en vez de un patrón (encargo: «atendiendo a las necesidades
     de cada pestaña»).
   - `.patio--galeria` — la sección es más ancha que las 6 columnas de una sección
     normal, para que el mosaico de fotos no caiga a miniaturas de 180 px. Ver el bloque
     GALERÍA, más abajo, con la medida justificada.

   El inicio no lleva ningún modificador: es la retícula tal cual se demostró en
   `docs/especimen-patio.html`, y este cambio no le toca una sola regla — se ha
   verificado que su HTML generado no cambia salvo la clase de cuerpo compartida.
   ═══════════════════════════════════════════════════════════════════════════════════ */

/* ── La pared ───────────────────────────────────────────────────────────────────────
   §3.2: un solo `background-image` con un SVG de 4×4 azulejos que se repite. Cero nodos
   del DOM, cero peticiones, 3 505 bytes en bruto y 782 comprimidos. Va en el cuerpo, no
   en cada sección: así el dibujo es continuo de la cabecera al pie y no hay una costura
   donde acaba una sección y empieza la siguiente.

   CLASE 0 de §7.1: la pared no responde a nada, nunca. No es que se le haya quitado el
   `hover`: es que no existe como nodo al que dárselo.

   Las cuatro tintas del patrón están repetidas en `rgb()` dentro del data-URI, porque un
   data-URI no puede leer una custom property. Si se cambia una en §1.1, hay que
   cambiarla también ahí; está anotado en el propio SVG.                                */
.patio-pared {
  /* La retícula (§3.1): n = floor(ancho / 88), y el azulejo cae siempre entre 88 y
     117 px. Los cortes son múltiplos de 88: no hay ni un breakpoint inventado. */
  --cols: 4;
  /* Rediseño «pared sencilla»: ya no hay un patio con tope de 12 que se come todo lo
     que sobra. Hay una sola rejilla de --cols columnas a todo el ancho, un margen de
     pared desnuda IGUAL a los dos lados (--off, en azulejos) y una sección de
     --ancho-seccion columnas que alterna de lado dentro de lo que queda. Por debajo,
     --ancho-seccion vale lo mismo que --cols: no hay hueco para alternar y toda
     sección ocupa el ancho entero (fila «≤ 7» de la escalera, más abajo). */
  --off: 0;                     /* azulejos de pared desnuda a cada lado */
  --ancho-seccion: var(--cols); /* columnas que ocupa una sección de contenido */
  --filas-portada: 3;      /* alto de la reja grande, en azulejos enteros (§3.3) */
  /* Encargo del cliente, «quiero que se respete siempre el margen»: por debajo de
     --cols 8 no hay azulejo de --off que dar (2+2 no dejaría contenido, ver más abajo),
     así que el margen ahí es `var(--margen)` — el mismo token que ya usa `.envoltorio`
     para esto mismo — en vez de azulejos. En cuanto --off deja de ser 0 (--cols ≥ 8, ver
     la escalera más abajo), `--borde` vuelve a 0: el margen en azulejo ya lo da `--off`
     por sí solo y sumar los dos lo duplicaría. */
  --borde: var(--margen);
  /* `100cqw`, no `100vw`: la unidad de contenedor de `html` (arriba) mide el ancho
     real ya maquetado, sin la barra de desplazamiento. Con `vw` el azulejo salía más
     ancho que la rejilla en cuanto había barra, y `--pieza` es la unidad de la que
     cuelga toda la retícula, así que el desbordamiento se contagiaba a la página
     entera. Reparto medido a 1280 px sin cambios: 2,00 azulejos de margen y 6,00 de
     sección. `--borde` se resta aquí, no se añade como `padding` aparte, para que el
     azulejo siga siendo cuadrado también en el tramo sin --off: si se restara solo en
     el `padding-inline` de `main` y no aquí, las columnas medirían menos que las filas. */
  --pieza: calc((100cqw - 2 * var(--borde)) / var(--cols));
  /* §5.2, encargo 2026-09-22 «que la garrapata ocupe el azulejo»: tiene que declararse
     aquí, no en el bloque de tokens de `:root`. `--pieza` solo existe dentro de
     `.patio-pared`; declarar `--sello` en función de `--pieza` fuera de este bloque la
     deja inválida en tiempo de cómputo en el elemento que la declara (`:root`, donde
     `--pieza` no existe) y ese valor inválido es el que hereda todo lo de abajo,
     `.patio-pared` incluido — no se «arregla sola» al usarla más adentro. Medido: fuera
     de aquí, `getComputedStyle(...).getPropertyValue('--sello')` daba cadena vacía en
     toda la página.
     Un azulejo (83,75–117 px según el tramo) se probó primero y el cliente lo vio
     «muy pequeña» frente al sello anterior de 120 px fijos — un solo azulejo siempre
     queda por debajo de esos 120 px, así que dos azulejos es la lectura que de verdad
     conserva el tamaño con el que el cliente ya estaba conforme. **Esto saca el token
     fuera de la horquilla de 64–160 px que §5.2 y §6.1 documentan hoy** (2 azulejos mide
     167,5–192 px según el tramo, siempre por encima de 160): queda anotado en
     HALLAZGOS para que `ui-designer` suba el tope documentado, no se silencia aquí. */
  --sello: calc(var(--pieza) * 2);

  /* §1.5 — el cerco doble del hueco. 3 px de cal por dentro y 2 px de perfilado por
     fuera: así el hueco se recorta contra cualquiera de las cinco tintas de la pared
     sin saber cuál le ha tocado detrás. Peor caso medido, 4,46:1. */
  --cerco-interior: 3px;
  --cerco: 5px;
  --reja-aire: 6px;        /* §3.4: el padding de la reja es sustrato de contraste */

  /* Encargo 2026-09-22, «el correo de tienda resaltado en fondo azul como botón,
     bordes redondeados». El sistema no tenía ningún token de radio (§6: «el azulejo
     no tiene esquinas»); este botón es la primera excepción deliberada, no un olvido
     de la regla. Se mide igual que el resto de la escala, la mitad de `--cerco`, para
     que la esquina se note sin volver el botón una píldora. Declarado aquí, junto a
     `--cerco`, por la misma razón que `--sello` unas líneas arriba: fuera de este
     bloque `var(--cerco)` no existe y el token quedaría inválido. */
  --radio-boton: calc(var(--cerco) / 2);

  /* §2.5 — la escalera del patio. Los rótulos de Veracruz Azulejo llevan aquí el
     tracking y el interlineado que exige §2.4 (≥ 0,06em y ≥ 1,40): sin ellos la cenefa
     de cada letra se funde con la de al lado y los títulos se pisan. La maqueta en
     columna de las otras páginas todavía no lo cumple; queda anotado. */
  --t-dintel: clamp(20px, 5.2vw, 40px);
  --int-dintel: 1.15;                     /* rótulo de una línea garantizada (§2.4) */
  --let-dintel: 0.10em;                   /* 0,06em a partir de 528 px */
  /* Encargo 2026-09-22 (trío de galería): entre 900 y 1039 px el trío ya tiene tres
     columnas y `--t-dintel` ya toca su tope de 40 px —`5.2vw` llega a 40 en 769 px—,
     pero la columna todavía mide 221–265 px. Medido en Chrome headless: «San
     Cayetano» pasa a dos líneas por debajo de esos 265 px. 30px es la talla mayor que
     cabe en una sola línea en la columna más estrecha del tramo (221 px, a 900 px). */
  --t-dintel-trio: 30px;
  --t-rotulo: clamp(17px, 3.4vw, 21px);
  --int-rotulo: 1.40;                     /* puede partir en dos líneas: mínimo de §2.4 */
  --let-rotulo: 0.10em;
  --t-verso: clamp(19px, 4.4vw, 26px);
  --int-verso: 1.45;
  --let-verso: 0.02em;
  --t-dato: clamp(20px, 5vw, 26px);
  --int-dato: 1.15;
  --t-menor: 15px;
  --int-menor: 1.5;

  background-color: var(--azulejo-blanco);
  background-image:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 400'><defs><g id='f'><circle cx='0' cy='-7' r='4'/><circle cx='0' cy='7' r='4'/><circle cx='-7' cy='0' r='4'/><circle cx='7' cy='0' r='4'/><circle cx='0' cy='0' r='3.2' fill='rgb(0,38,73)'/></g><g id='r'><circle cx='50' cy='50' r='34' fill='none' stroke='rgb(0,38,73)' stroke-width='3'/><g fill='rgb(200,220,229)'><circle cx='24' cy='24' r='11'/><circle cx='76' cy='24' r='11'/><circle cx='24' cy='76' r='11'/><circle cx='76' cy='76' r='11'/></g><g fill='rgb(36,124,170)'><ellipse cx='50' cy='27' rx='7' ry='14'/><ellipse cx='50' cy='73' rx='7' ry='14'/><ellipse cx='27' cy='50' rx='14' ry='7'/><ellipse cx='73' cy='50' rx='14' ry='7'/></g><circle cx='50' cy='50' r='10' fill='rgb(0,38,73)'/></g><g id='o' fill='none' stroke='rgb(0,38,73)' stroke-width='5'><circle cx='0' cy='50' r='31'/><circle cx='100' cy='50' r='31'/><circle cx='50' cy='0' r='31'/><circle cx='50' cy='100' r='31'/><circle cx='50' cy='50' r='9' stroke='rgb(36,124,170)'/></g><g id='d'><path d='M-24 24L24 -24M-4 64L64 -4M16 104L104 16M56 124L124 56' stroke='rgb(167,201,214)' stroke-width='11' fill='none'/><path d='M-12 46L46 -12M28 110L110 28' stroke='rgb(0,38,73)' stroke-width='5' fill='none'/></g><g id='h'><path d='M10 90C10 40 40 10 90 10C90 60 60 90 10 90Z' fill='rgb(36,124,170)'/><path d='M16 84C40 60 60 40 84 16' stroke='rgb(249,249,247)' stroke-width='4' fill='none'/><path d='M30 80C36 62 48 50 66 44M54 76C58 62 66 52 80 46' stroke='rgb(249,249,247)' stroke-width='3' fill='none'/><circle cx='82' cy='82' r='9' fill='rgb(200,220,229)'/></g><g id='p' fill='rgb(36,124,170)'><use href='%23f' x='25' y='25'/><use href='%23f' x='75' y='25'/><use href='%23f' x='50' y='50'/><use href='%23f' x='25' y='75'/><use href='%23f' x='75' y='75'/></g><g id='l' fill='none' stroke='rgb(0,38,73)' stroke-width='4'><rect x='22' y='22' width='56' height='56'/><rect x='22' y='22' width='56' height='56' transform='rotate(45 50 50)'/><circle cx='50' cy='50' r='9' fill='rgb(200,220,229)' stroke='none'/></g></defs><rect width='400' height='400' fill='rgb(249,249,247)'/><svg x='0' y='0' width='100' height='100' viewBox='0 0 100 100'><use href='%23r'/></svg><svg x='100' y='0' width='100' height='100' viewBox='0 0 100 100'><use href='%23d'/></svg><svg x='200' y='0' width='100' height='100' viewBox='0 0 100 100'><use href='%23o'/></svg><svg x='300' y='0' width='100' height='100' viewBox='0 0 100 100'><use href='%23p'/></svg><svg x='0' y='100' width='100' height='100' viewBox='0 0 100 100'><use href='%23l'/></svg><svg x='200' y='100' width='100' height='100' viewBox='0 0 100 100'><use href='%23h'/></svg><svg x='300' y='100' width='100' height='100' viewBox='0 0 100 100'><use href='%23d'/></svg><svg x='0' y='200' width='100' height='100' viewBox='0 0 100 100'><use href='%23o'/></svg><svg x='100' y='200' width='100' height='100' viewBox='0 0 100 100'><use href='%23r'/></svg><svg x='200' y='200' width='100' height='100' viewBox='0 0 100 100'><use href='%23p'/></svg><svg x='0' y='300' width='100' height='100' viewBox='0 0 100 100'><use href='%23h'/></svg><svg x='100' y='300' width='100' height='100' viewBox='0 0 100 100'><use href='%23l'/></svg><svg x='200' y='300' width='100' height='100' viewBox='0 0 100 100'><use href='%23d'/></svg><svg x='300' y='300' width='100' height='100' viewBox='0 0 100 100'><use href='%23r'/></svg><path d='M100 0V400M200 0V400M300 0V400M0 100H400M0 200H400M0 300H400' stroke='rgb(241,243,237)' stroke-width='2'/></svg>");
  /* El azulejo se mide sobre el ancho del propio cuerpo, no sobre 100vw: con barra de
     desplazamiento visible, 100vw es mayor que la retícula y la llaga se despegaría del
     borde del hueco a lo largo de la fila. */
  /* Solo se fija el ANCHO. Un porcentaje en la altura de `background-size` se mide
     contra la ALTURA del área, no contra su anchura: con `calc(100% / 4 * 4)` en los dos
     ejes el SVG salía estirado a lo largo de la página entera en vez de repetirse. Con
     `auto` manda la relación intrínseca del SVG —viewBox cuadrado—, y el azulejo es un
     cuadrado de verdad. (El espécimen tiene todavía la versión estirada.) */
  background-size: calc(100% / var(--cols) * 4) auto;
  /* Encargo del cliente, «que la retícula del azulejo cuadre también en móvil»:
     `main` abre a `var(--borde)` de aire (línea 877), y por debajo de --cols 8 ese
     borde es `--margen` (20 px a 375 px) — no un múltiplo de `--pieza` (93,75 px a
     375 px). Sin desplazar el patrón, todo el contenido arrancaba en 0,21t de la
     pared, medido. Desplazando el origen del `background-image` por el mismo
     `--borde` que ya abre `main`, la primera llaga cae exactamente en el borde del
     contenido: a partir de --cols 8, `--borde` vuelve a 0 (el margen ya lo da --off
     en azulejos enteros) y este desplazamiento no cambia nada, verificado. */
  background-position: var(--borde) 0;
  background-attachment: scroll;          /* rígida: §7.3 prohíbe el parallax */
}

/* Los cortes salen del propio azulejo: 88·n (§3.1). En cada uno cambian las columnas
   y el alto de la reja grande, que se elige para que su relación se quede entre 0,80
   (el 4:5 de móvil de §4.1) y 1,83 (cerca del 16:9 de escritorio) sin dejar de ser un
   número entero de azulejos.

   La escalera del encargo («2 + 6 + 4 + 2, y por debajo de 8 columnas se acaba la
   alternancia») cuelga de los mismos cortes, sin inventar uno nuevo:
     · --cols ≤ 7   → --off se queda a 0 y --ancho-seccion = --cols (fila de arriba):
                      no cabe un margen Y una sección de 6 a la vez sin romper la
                      lectura, así que cada sección ocupa el ancho entero.
     · --cols 8–13  → --off pasa a 1 y --ancho-seccion se fija en 6. La pared que
                      sobra del lado contrario no es un número fijo: es lo que quede
                      de --cols tras el margen y la sección, y crece sola con --cols
                      (0 a 1056 px, hasta 5 a 1144 px). Es la fila «10–11» y «12–13»
                      del encargo fundidas en una: con --ancho-seccion fijo en 6 la
                      aritmética no puede dar un resto negativo, así que no hacía
                      falta encogerla más a medida que crecía --cols.
     · --cols ≥ 14  → --off sube a 2 y se queda ahí. Es la retícula ya cerrada del
                      encargo, 2 + 6 + 4 + 2 a 14 columnas; --ancho-seccion sigue en 6
                      y lo único que crece con la pantalla es la pared del lado
                      contrario a la sección, que es la idea entera de §0. No hace
                      falta seguir subiendo --off en las pantallas aún más anchas: la
                      pared de sobra ya demuestra la metáfora sin que el margen tenga
                      que perseguirla. */
/* --filas-portada, recalculada: ya no mide contra --cols (la portada dejó de ser a
   sangre, arreglo del cliente «la foto de David, más pequeña, dentro de los
   márgenes»), sino contra `--cols − 2·--off` — el mismo ancho disponible que usa
   `.patio--galeria` (justificado más abajo, en su bloque). El objetivo sigue siendo el
   de §4.1: una relación entre 0,80 (4:5 de móvil) y 1,83 (cerca de 16:9), y aquí se
   deja más cerca de 1,2–1,4 a propósito — «más pequeña» también quiere decir que no se
   coma la primera pantalla entera, no solo que quede dentro del margen. */
@media (min-width:  440px) { .patio-pared { --cols:  5; --filas-portada: 4; } }
@media (min-width:  528px) { .patio-pared { --cols:  6; --filas-portada: 5;
                                             --let-dintel: 0.06em; --let-rotulo: 0.06em; } }
@media (min-width:  616px) { .patio-pared { --cols:  7; --filas-portada: 5; } }
@media (min-width:  704px) { .patio-pared { --cols:  8; --filas-portada: 5;
                                             --off: 1; --ancho-seccion: 6; --borde: 0px; } }
@media (min-width:  792px) { .patio-pared { --cols:  9; --filas-portada: 5; } }
@media (min-width:  880px) { .patio-pared { --cols: 10; --filas-portada: 6; } }
@media (min-width:  968px) { .patio-pared { --cols: 11; --filas-portada: 6; } }
@media (min-width: 1056px) { .patio-pared { --cols: 12; --filas-portada: 7; } }
@media (min-width: 1144px) { .patio-pared { --cols: 13; --filas-portada: 7; } }
@media (min-width: 1232px) { .patio-pared { --cols: 14; --off: 2; --filas-portada: 7; } }
@media (min-width: 1320px) { .patio-pared { --cols: 15; } }
@media (min-width: 1408px) { .patio-pared { --cols: 16; } }
@media (min-width: 1496px) { .patio-pared { --cols: 17; } }
@media (min-width: 1584px) { .patio-pared { --cols: 18; } }
@media (min-width: 1672px) { .patio-pared { --cols: 19; } }
@media (min-width: 1760px) { .patio-pared { --cols: 20; } }

/* La luz del patio (§7.3): una viñeta FIJA al viewport. No se anima, no tiene
   transición y no cuesta un frame de compositing. Lo que se mueve al desplazarse es la
   pared pasando por debajo de una luz que está quieta. */
/* EL VELO DE LA PARED. Una sola capa fija al viewport, por debajo de todo el
   contenido: la pared se lava, y el texto, la portada y las fichas se quedan a su
   tinta porque van por encima. Fija y sin transición, como la luz: no cuesta un frame
   de compositing ni añade un nodo al DOM (§7.3). */
.patio-pared::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background: color-mix(in srgb, var(--cal-hueso) calc(var(--velo-pared) * 100%), transparent);
}
/* Cabecera, contenido y pie por encima del velo: lo que se lava es la pared desnuda,
   no lo que se ha puesto encima de ella. */
.patio-pared > header,
.patio-pared > main,
.patio-pared > footer { position: relative; z-index: 1; }

.patio-pared::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background: linear-gradient(180deg, var(--luz-patio), transparent 26%,
                              transparent 74%, var(--luz-patio));
}
/* La puerta de servicio por encima de la luz, para que no le caiga el velo encima */
/* El anillo de foco del enlace de salto cae sobre la franja clara de la cabecera, donde
   --geranio mide 1,74:1 y WCAG 1.4.11 pide 3:1 para un elemento no textual. --cobalto
   sobre ese mismo fondo da 4,69:1, medido. El resto de anillos del sitio no cambian:
   sobre --cal el geranio ya cumple (5,2:1). */
.patio-pared .salto-al-contenido:focus { z-index: 4; outline-color: var(--cobalto); }

/* ── La página: una sola rejilla de --cols columnas, de la que cuelga cada sección ──
   El encargo: dos columnas de margen a los lados —pared desnuda, sin contenido—, las
   secciones alternando de lado, y lo que sobra del lado contrario relleno con la
   pared, sin nada encima. `<main>` es la única rejilla de la página: cada
   `<section class="patio">` es una pieza suya, colocada con `grid-column` y no con
   `padding-inline`. Así el orden del DOM sigue siendo el orden de tabulación —ninguna
   sección decorativa que saltar— y no hace falta `grid-auto-flow: dense` (§3.3, y
   regla 3.3.2 más abajo). */
.patio-pared main {
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  /* «Que se respete siempre el margen» (encargo del cliente): por debajo de --cols 8
     --off vale 0 y no había nada que impidiera que una sección tocara el borde. Este
     padding es --borde (0 en cuanto --off manda), así que no se suma al margen de
     azulejo cuando ya existe uno: es el mismo margen contado de otra manera, nunca
     los dos a la vez. */
  padding-inline: var(--borde);
}

/* Por debajo de --cols 8, --ancho-seccion vale --cols: toda sección ocupa el ancho
   disponible de `main`, que ya descuenta `--borde` (fila «≤ 7» del encargo). Esta regla
   basta para ese caso y queda como valor de reserva para portada y cierre por debajo de
   704 px, donde `.patio--ancho` no redefine nada. */
.patio-pared .patio { grid-column: 1 / -1; }

/* La alternancia entra en cuanto --ancho-seccion se separa de --cols (--cols ≥ 8, ver
   arriba). Izquierda, empezando justo después del margen; derecha, terminando justo
   antes del margen.

   REGRESIÓN (commit 8fa4ee7) Y POR QUÉ YA NO ES `nth-of-type()`: las citas pasaron a
   ser su propia `<section class="patio patio--ancho">` a ancho completo, y
   `nth-of-type()` cuenta **toda sección hermana de la misma etiqueta**, sin filtrar por
   clase — el `:not()` de abajo decidía a quién se le aplicaba la regla, no a quién
   contaba `nth-of-type()` para numerar. Cada cita intercalada desplazaba el número de
   las seis secciones que sí alternan, y en el inicio de hoy eso deja tres «izquierda»
   seguidas (medido a 1280 px: contratación, quién es quién y el disco). En CSS puro no
   hay forma de contar solo un subconjunto de hermanos, así que el lado se decide fuera
   de la hoja: `tools/construir.py` (`asignar_lado_alternancia()`) numera solo las
   secciones `class="patio"` a secas, en el orden real en que `marca_pared()` las
   escribe, y escribe `data-lado="izquierda"` o `data-lado="derecha"` en el HTML. **No
   es una clase de lado a mano** — nadie la elige, la deriva el orden del documento —,
   es mover el conteo a un motor que sí puede filtrar. */
@media (min-width: 704px) {
  .patio-pared .patio[data-lado="izquierda"] {
    grid-column: calc(var(--off) + 1) / span var(--ancho-seccion);
  }
  .patio-pared .patio[data-lado="derecha"] {
    grid-column: calc(var(--cols) - var(--off) - var(--ancho-seccion) + 1)
                 / span var(--ancho-seccion);
  }
}

/* Ancho completo entre márgenes — una sola regla para las tres modificadoras que
   llegan al margen, no tres copias. La lleva `.patio--ancho` (portada, cierre),
   `.patio--fijo` (contacto, tienda, la cabecera de galería y conciertos) y
   `.patio--galeria` (el mosaico). Las tres comparten también la rejilla interior
   ensanchada de más abajo, así que la sección y su contenido miden lo mismo: nunca hay
   pared desnuda entre el margen y la ficha. ADR 0004 registró `.patio--ancho` a sangre; el
   cliente lo revocó el 2026-09-21 («quiero que se respeten los márgenes siempre») —
   pendiente de enmendar el ADR, no en silencio. Todas ocupan **todo lo que no es
   margen** (`--cols − 2·--off`), nunca los azulejos de pared desnuda de los lados. Por
   debajo de --cols 8 no hay `--off` que dar, y el margen lo pone `--borde` en el
   padding de `main`: la sección solo necesita llegar al borde de esa caja. */
.patio-pared .patio--ancho,
.patio-pared .patio--fijo,
.patio-pared .patio--galeria { grid-column: 1 / -1; }
@media (min-width: 704px) {
  .patio-pared .patio--ancho,
  .patio-pared .patio--fijo,
  .patio-pared .patio--galeria {
    grid-column: calc(var(--off) + 1) / calc(var(--cols) - var(--off) + 1);
  }
}

/* Por qué el mosaico de galería necesita más que las 6 columnas de una sección normal,
   medido: una sección de `--ancho-seccion` (6) a 1280 px cae en --cols 14, --off 2, así
   que mide 6 × (1280/14) ≈ 549 px; con el mosaico a 3 columnas y sus dos huecos de
   `--ritmo` (28 px), cada miniatura queda en (549 − 56) / 3 ≈ 164 px — por debajo de lo
   que hace falta para leer una foto de directo como galería. Con la anchura de arriba
   (`--cols − 2·--off`, 10 columnas a 1280 px) la misma cuenta da (914 − 56) / 3 ≈ 286 px
   — 74 % más ancha, y ya funciona como galería. Medido en Chrome headless, ver el
   informe. */

/* La rejilla interior de cada sección: sus huecos se miden en azulejos de SU propio
   ancho, no del ancho de la pantalla. Una sección de --ancho-seccion columnas del
   `main` mide exactamente --ancho-seccion × --pieza, así que una rejilla interior con
   el mismo número de pistas reparte el mismo tamaño de pieza — el azulejo no cambia de
   tamaño al cruzar de la pared a una sección y viceversa. */
.patio-pared .patio {
  display: grid;
  grid-template-columns: repeat(var(--ancho-seccion), 1fr);
  grid-auto-rows: minmax(var(--pieza), auto);   /* columnas rígidas, cursos elásticos */
  /* NUNCA `dense`. No es estética: `dense` reordena lo que ve el ojo sin reordenar el
     DOM, y entonces el foco de teclado salta a un hueco que no es el siguiente que se
     está mirando. El hueco que queda sin rellenar es la decoración; el orden de
     tabulación es la razón de que no se rellene (§3.3). */
  grid-auto-flow: row;
}
/* Portada, cierre y galería miden su rejilla interior en `--cols − 2·--off` columnas
   (una sola regla, no dos copias), no contra `--cols` completo ni contra
   `--ancho-seccion`: ya no son a sangre (ver arriba), pero siguen siendo más anchas
   que una sección de 6, y su reja/zócalo/mosaico tienen que llenar exactamente ese
   ancho para que el azulejo no cambie de tamaño al entrar en la sección. */
.patio-pared .patio--ancho,
.patio-pared .patio--fijo,
.patio-pared .patio--galeria {
  grid-template-columns: repeat(calc(var(--cols) - var(--off) * 2), 1fr);
}
/* El mosaico —y el trío del encargo 4— ocupan la sección entera, no los `--n: 4`
   azulejos por defecto de un hijo de `.patio` (regla general, más abajo): son una
   rejilla libre, no una fila de huecos. */
.patio-pared .patio--galeria .mosaico,
.patio-pared .patio--galeria .trio { grid-column: 1 / -1; }

/* Enmendado el 2026-09-22, segunda vez que lo pide el cliente: «contacto, conciertos y
   tienda siguen en formato doble columna; quiero columna simple al ancho de los
   márgenes.» La rejilla de `.patio--fijo` ya no se queda en sus `--ancho-seccion`
   pistas centradas (eso era la «doble columna»: una banda de ficha estrecha con pared
   desnuda a los dos lados) — ahora comparte la regla de arriba y gana tantas pistas de
   `--pieza` como caben en `--cols − 2·--off`, el mismo azulejo que la pared, nunca uno
   estirado (§3.5). Y cada puerta/ventana/tarjeta pasa a ocupar TODAS esas pistas —
   `grid-column: 1 / -1` — así que cada una es de verdad una columna simple, margen a
   margen, una por curso. El verso (`.encalado`) y el cielo del estado vacío se quedan
   fuera a propósito: su `--n` mide la línea de lectura, no la ficha, y ensancharlos
   volvería el verso o el aviso vacío demasiado largos para leer de un vistazo. */
.patio-pared .patio--fijo > .hueco:not(.encalado):not(.cielo):not(.producto),
.patio-pared .patio--fijo .lista-patio > .hueco:not(.encalado):not(.cielo):not(.producto) {
  grid-column: 1 / -1;
}

/* Enmendado el 2026-09-22, tercera vez: «los azulejos [de la tienda] son muy grandes...
   ponlo en formato doble columna y de un tamaño más modesto.» Solo el catálogo
   (`.producto`) se parte en dos columnas — el dintel, su subtítulo y la cita siguen a
   ancho completo por la regla de arriba, que ya no los toca a ellos por seguir
   excluidos como `.encalado`/`.cielo`. Bajo 704 px (`--off` sigue en 0, la sección solo
   tiene 4 pistas) dos columnas de producto no caben con holgura, así que se queda en
   columna simple — la misma que ya defendía el cliente hace dos encargos — hasta que la
   pared gana su primer margen propio, el mismo corte que ya usan `--cols`/`--off`/
   `--ancho-seccion` en la línea 806. De 704 px en adelante, la mitad exacta: no un
   `--n` de azulejo (ver la regla siguiente, que reemplaza `subgrid` por dos columnas
   iguales en vez de repartir pistas), pero sigue siendo la misma medida «más modesta»
   que pidió el cliente frente a la columna simple de antes, sin inventar un token de
   tamaño nuevo — sale de dividir el ancho ya existente entre dos, no de un píxel suelto. */
.patio-pared .patio--fijo .producto { grid-column: 1 / -1; }
@media (min-width: 704px) {
  /* Dos columnas de verdad, no «tantas como quepan»: en vez de repartir `.producto` en
     pistas de azulejo (que no siempre dan un número par de columnas — medido, 9 y 11
     pistas disponibles en varios tramos entre 704 y 1232 px — y dejarían caer tres
     fichas por fila en cuanto sobrara sitio), la propia lista pasa a una rejilla de 2
     columnas iguales. Dos columnas es la unidad que se mide aquí, no el azulejo: por
     eso dejan de ser `subgrid` en este único caso. El `.hueco` de cada ficha sigue
     poniendo su propio `--cerco` (5 px) a los cuatro lados (§1.5), que es lo que separa
     una columna de la otra sin gap nuevo que sumar. */
  .patio-pared .patio--fijo .lista-patio { grid-template-columns: repeat(2, 1fr); }
  .patio-pared .patio--fijo .producto { grid-column: auto; }
}

/* Las citas a ancho completo (encargo del cliente: «que ocupen todo el ancho
   respetando el margen»): dentro de una sección ya ensanchada a `.patio--ancho`, el
   encalado deja de medir los --n azulejos de una sección normal (4/5/6, pensados para
   6 columnas) y llena la sección entera, igual que ya hacían la reja y el zócalo. */
.patio-pared .patio--ancho > .hueco.encalado { grid-column: 1 / -1; }

.patio-pared .patio > *,
.patio-pared .lista-patio > * { grid-column-end: span var(--n, 4); }

/* La lista sigue siendo una lista: `subgrid` la mete en las columnas de la pared sin
   recurrir a `display: contents`, que se lleva por delante la semántica de lista. */
.patio-pared .lista-patio {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  grid-auto-rows: minmax(var(--pieza), auto);
}

/* ── Los huecos (§3.4) ───────────────────────────────────────────────────────────────
   CLASE 1 de §7.1: hueco inerte. Sin transición, sin hover, sin cursor de mano. Lo
   único que se le puede hacer es leerlo y seleccionar su texto.                        */
.patio-pared .hueco {
  position: relative;
  z-index: 1;
  background: var(--hueco-fondo);
  /* Sin aro oscuro: el hueco ya no se recorta contra la pared, la deja ver. Queda solo
     el reborde de cal, que separa lo justo sin dibujar una línea negra. */
  box-shadow: 0 0 0 var(--cerco-interior) var(--hueco-fondo);
  margin: var(--cerco);
  padding: var(--ritmo);
  display: flex;
  flex-direction: column;
  gap: var(--medio-ritmo);
  justify-content: center;
  min-width: 0;
}
/* Ningún hueco enfocado queda pegado al borde de la pantalla: por encima se ve siempre
   un curso entero de pared, que es lo que dice dónde estás (§7.4). */
.patio-pared .hueco,
.patio-pared a { scroll-margin-block: var(--pieza); }

/* DINTEL — todo el ancho del patio, un curso exacto. El rótulo de la sección.
   `.dintel__subtitulo` (slice 4, punto 1: el subtítulo del grupo de galería) no lleva
   reglas propias a propósito — hereda de `.anotacion` (tipo y color) y del `gap` de
   `.hueco` (aire con el rótulo), así que no añade ni un token nuevo. El curso crece
   solo: `grid-auto-rows: minmax(var(--pieza), auto)` en la rejilla (línea 858). */
.patio-pared .dintel {
  grid-column: 1 / -1;
  align-items: center;
  text-align: center;
}
.patio-pared .dintel .rotulo {
  font-size: var(--t-dintel);
  line-height: var(--int-dintel);
  letter-spacing: var(--let-dintel);
}

/* ENCALADO — el único hueco de altura libre (§3.3), porque la prosa no se recorta */
.patio-pared .encalado { --n: 4; }
@media (min-width: 528px) { .patio-pared .encalado { --n: 5; } }
@media (min-width: 880px) { .patio-pared .encalado { --n: 6; } }

/* VENTANA — tarjeta con cerco de teja: la formación, la persona (§6.4) */
.patio-pared .ventana { --n: 4; }
@media (min-width: 528px) { .patio-pared .ventana { --n: 3; } }
.patio-pared .ventana .lista-hueco {
  border-top: var(--filete) solid var(--lavanda);
  border-bottom: var(--filete) solid var(--lavanda);
  padding-block: var(--medio-ritmo);
}

/* VENTANA DE PRODUCTO (§6.2, slice 5) — foto 1:1 dentro de la ventana. */
.patio-pared .producto__imagen { aspect-ratio: 1 / 1; }
.patio-pared .producto__imagen picture { display: block; width: 100%; height: 100%; }
.patio-pared .producto__imagen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--punto, 50% 50%);
}
/* Motivo, no ficha de producto (respuesta de David, §3): las láminas de pegatinas se
   enmarcan hacia dentro con el mismo filete de §6 en vez de llenar el hueco a sangre,
   para que se lea como muestra de un patrón y no como foto de la prenda. El tratamiento
   es la señal; la anotación de `marca_producto_imagen()` solo lo confirma en texto. */
.patio-pared .producto__imagen--motivo {
  width: calc(100% - var(--ritmo-2));
  margin-inline: auto;
  border: var(--filete) solid var(--lavanda);
}
.patio-pared .producto__nombre {
  font-family: var(--fuente-verso);          /* §6.2: el nombre va en Rvfina/Veracruz */
  font-size: var(--t-destacado);
  line-height: var(--int-destacado);
  letter-spacing: var(--let-destacado);
}
.patio-pared .producto__precio { font-family: var(--fuente-dato); }
.patio-pared .producto__tallas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--medio-ritmo);
}

/* ESCUCHA — la fachada de Spotify/YouTube (encargo 2, opción C). Misma ventana que un
   producto: portada 1:1 (`.producto__imagen`, reutilizada), rótulo y botón. El botón
   lleva el mismo margen que ya usan `.concierto` y `.vacio`, por coherencia.
   Encargo 2026-09-22: los dos widgets se apilan, cada uno a todo el ancho de la
   sección. `--n: var(--ancho-seccion)` es ese ancho completo en cualquier tramo —el
   mismo dato que ya reparte la rejilla interior de `.patio` (línea 925)—, así que no
   hace falta ni un breakpoint nuevo ni un número a mano: gana a `--n: 3` de `.ventana`
   por venir después en la cascada, con la misma especificidad. */
.patio-pared .escucha { --n: var(--ancho-seccion); }
.patio-pared .escucha .boton { margin-top: var(--medio-ritmo); }
/* `.escucha__medio` es la caja que cambia de contenido al pulsar — la miniatura antes,
   el iframe oficial después (`escucha.js`) — y solo ella: el título, la anotación y el
   botón se quedan quietos, así que su tamaño no depende de qué haya dentro.
   `--alto-fachada` (Spotify, px fijo — su oEmbed no es de proporción fija) o
   `--proporcion` (YouTube, 16/9 siempre) las fija el generador con el dato real de cada
   enlace (encargo 1: cero desplazamiento de maquetación al pulsar). */
.patio-pared .escucha__medio {
  height: var(--alto-fachada, auto);
  aspect-ratio: var(--proporcion, 1 / 1);
}
/* Solo Spotify, y solo por debajo de 528 px (el mismo corte que ya usan `.ventana` y
   `.encalado` unas líneas más abajo): la caja se reserva en `--alto-fachada-compacta`
   (152 px) en vez de en el alto real del oEmbed (352 px), así que Spotify pinta su
   propio layout compacto dentro del iframe sin parámetro nuevo en la URL. La reserva
   sigue siendo exacta a los dos lados del corte — no cambia el alto real, cambia cuál
   de los dos altos se reserva —, así que el encargo 1 (cero salto al pulsar) sigue
   cumplido. YouTube no lleva esta clase: sigue en `--proporcion` (16:9), sin tocar. */
.patio-pared .escucha--spotify .escucha__medio { height: var(--alto-fachada-compacta); }
@media (min-width: 528px) {
  .patio-pared .escucha--spotify .escucha__medio {
    height: var(--alto-fachada, var(--alto-fachada-compacta));
  }
}
.patio-pared .escucha__medio iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* PUERTA — el hueco grande: la ficha de contratación (§3.4 y §3.5). Fondo invertido */
.patio-pared .puerta { --n: 4; }
@media (min-width: 528px) { .patio-pared .puerta { --n: 6; } }
.patio-pared .puerta--cobalto {
  background: color-mix(in srgb, var(--cobalto) calc(var(--velo) * 100%), transparent);
  color: var(--cal);                              /* 11,07:1 en el peor azulejo */
  box-shadow: 0 0 0 var(--cerco-interior) var(--hueco-fondo);
}
.patio-pared .puerta--cobalto a { color: var(--lavanda); }   /* 10,31:1 (§1.3) */
.patio-pared .puerta--cobalto .boton { color: var(--cal); }  /*  6,27:1 (§1.3) */
.patio-pared .puerta--cobalto .anotacion,
.patio-pared .puerta__papel { color: var(--lavanda); }
.patio-pared .puerta__dato { font-size: var(--t-dato); line-height: var(--int-dato); }
.patio-pared .puerta--cobalto :focus-visible { outline-color: var(--albero); }
/* El botón de copiar se quedó fuera de la reescritura de color de esta ficha y heredaba
   --azulejo sobre el cobalto: 1,71:1 medido sobre píxel, muy por debajo del 4,5:1 de AA.
   Mismo patrón que `.sobre-cobalto .copiar`, que sí lo resolvía. Medido: --cal da
   10,12:1 y --lavanda 7,90:1 sobre rgb(37,47,141). */
.patio-pared .puerta--cobalto .copiar { color: var(--cal); }
.patio-pared .puerta--cobalto .copiar:hover { color: var(--lavanda); }

/* Historial de conciertos (§6.1): un bolo pasado no compite con el que viene, así que
   va en `--cobalto-medio` — 9,65:1 sobre `--cal-hueso` (§1.3) — en vez del cobalto
   pleno del resto de la pared. Se aplica al hueco entero: sus hijos (`.dato`, `.rotulo`,
   `.menor`) no fijan color propio y heredan este. */
.patio-pared .puerta--pasada { color: var(--cobalto-medio); }

/* REJA — foto. Anchura y altura exactas en azulejos: la foto se recorta, el hueco no
   cede (§3.3). Los 6 px de padding no son aire: son el sustrato de contraste conocido
   sobre el que se dibuja el foco, porque la luminancia de una foto no se conoce. */
.patio-pared .reja {
  --n: 4;
  display: block;
  padding: var(--reja-aire);
  grid-row-end: span 3;
}
.patio-pared .reja--portada {
  grid-column: 1 / -1;
  grid-row-end: span var(--filas-portada);
}
.patio-pared .reja picture { display: block; width: 100%; height: 100%; }
.patio-pared .reja img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--punto, 50% 50%);      /* §4.2: el centro por defecto no vale */
}
/* `.reja__credito` se quitó el 2026-09-21: el cliente retiró toda atribución del
   sitio, incluido el crédito de la portada (§4.3 ya no aplica). */

/* CIELO — el hueco sin nada detrás. ES el estado vacío, y se ve el cielo (§3.4) */
.patio-pared .cielo { --n: 4; background: var(--cielo); }
@media (min-width: 528px) { .patio-pared .cielo { --n: 6; } }
.patio-pared .cielo--ancho { grid-column: 1 / -1; }
.patio-pared .cielo p { color: var(--cobalto); }       /* 7,40:1 sobre --cielo (§1.3) */

/* MACETA — la única decoración que es un nodo del DOM. Nunca lleva información, nunca
   es enfocable y nunca responde: clase 0 de §7.1.

   Enmendado el 2026-09-22, cuarto encargo: «los quiero rellenando los huecos que
   quedan a izquierda y derecha del texto, dimensionados para el tamaño del hueco en
   cuestión, llenando el espacio» — el encargo anterior (2×2 fijo) llenaba la mitad de
   un hueco de 4 azulejos, medido a 1280 px, porque el tamaño no seguía al hueco.

   De 880 px en adelante (el corte de siempre: es donde el hueco del lado contrario
   llega a medir 2 pistas, ver más abajo) la maceta se saca del flujo y se apoya en el
   hueco de pared del lado contrario, con dos medidas derivadas de la propia retícula,
   nunca un píxel o un margen sueltos:

   - **Ancho** — `--hueco-lado`, azulejos enteros: `--cols − 2·--off − --ancho-seccion`,
     la misma resta que ya calcula el CSS de la alternancia (línea 908) para saber
     cuánta pared sobra. Crece con la pantalla — 2 a 880 px, 4 a 1232 px — y por eso
     «llena el hueco» en vez de quedarse fijo en 2. Con un tope en `min()`: pasado
     --cols 14 el hueco contrario sigue creciendo sin límite (§3.9, no hay corte
     escrito por encima de 1760 px) y una maceta más ancha que la propia sección de
     contenido dejaría de leerse como decoración y pasaría a ser el protagonista; el
     tope es `--ancho-seccion` (6), la anchura de la sección a la que escolta — nunca
     mayor que lo que acompaña.
   - **Alto** — `top: 0; bottom: 0` en vez de una altura en píxeles: la caja ocupa el
     alto entero de su sección, que es elástico (`grid-auto-rows: minmax(...)`, §3.5) y
     por tanto ya sigue la altura real del bloque de texto al que escolta, sin medirla
     a mano.

   El dibujo del jarrón no se deforma para rellenar ese rectángulo alto y no cuadrado:
   sigue siendo el `viewBox="0 0 100 100"` cuadrado de siempre, y `preserveAspectRatio`
   (por defecto en SVG, no se toca) lo escala entero y lo centra dentro de la caja —
   **encaje centrado, nunca estirado**. Es la decisión de las tres que ofrecía el
   encargo (encaje / repetición / anclaje): repetir el mismo jarrón apilado exigiría
   convertirlo en imagen de fondo con los colores en hexadecimal literal, perdiendo el
   `var()` que hoy lo liga a los tokens de color (§1.1 hace ese sacrificio solo para la
   pared, y solo porque la pared no tiene alternativa); anclarlo a un tamaño fijo es
   exactamente el defecto que se corrige aquí.

   Enmendado el 2026-09-22, quinto encargo — «la primera»: la caja llenaba el 100 % del
   hueco pero el dibujo, centrado dentro por el `preserveAspectRatio` por defecto,
   quedaba pequeño y flotando a media altura. El cliente elige anclar el jarrón al
   suelo: ancho = el del hueco, sin deformar el cuadrado del `viewBox`, y si el hueco es
   bajo (El disco, 3,79t) manda la altura en vez del ancho, sin desbordar nunca. La caja
   (`.maceta`) sigue siendo la que ya calculaba el ancho/alto del hueco — no se toca esa
   aritmética —; lo que cambia es cómo se coloca el SVG dentro: en vez de `width:100%;
   height:100%` (que estira la caja entera y dispara el `meet` centrado), el SVG mide su
   propio lado con `aspect-ratio: 1/1` y `max-width/max-height: 100%`, que es la misma
   álgebra que ya usa `object-fit: contain` para una imagen — coge el mayor cuadrado que
   quepa en la caja sin desbordar ninguno de los dos lados. `.maceta` pasa a flex para
   anclarlo (`align-items: flex-end`) al borde inferior de su hueco y centrarlo en
   horizontal (`justify-content: center`) cuando es la altura la que manda. Sin zócalo
   que discutir: `.patio--ancho .maceta` ya está a `display:none` (línea de abajo) y el
   `.zocalo` solo vive dentro de `.patio--ancho` (la sección de cierre) — maceta y
   zócalo nunca comparten sección, comprobado en las cinco páginas. */
.patio-pared .maceta {
  /* Por debajo de 880 px (ver la media query de abajo): 2×2, ancladas al borde final
     de su propia sección — sin hueco de pared que rellenar todavía (§3.2: --off no
     deja 2 pistas libres hasta cols 10), así que se quedan dentro del flujo. */
  grid-column: -3 / -1;
  grid-row-end: span 2;
  position: relative;
  z-index: 1;
  pointer-events: none;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.patio-pared .maceta svg {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 1 / 1;
}

/* Encargo del cliente: «en el formato móvil los jarrones no aparecen centrados».
   Medido: por debajo de --cols 8 (704 px, §3.9) no hay hueco de pared que rellenar
   todavía, así que la maceta se coloca al final de su fila (-3/-1, arriba) y queda
   pegada al borde derecho del ancho de contenido — no descentrada dentro de su propia
   caja, sino la caja entera desplazada. Misma anchura que antes (2 azulejos, sin
   deformar el dibujo), pero centrada en el ancho de la sección en vez de anclada al
   final: `grid-column: 1 / -1` le da toda la fila y `justify-self: center` la centra
   dentro. Desde --cols 8 (704 px) no se toca nada — ahí ya se anuncia un hueco de
   pared (--off pasa a 1) y a partir de 880 px la maceta sale al hueco lateral de
   verdad (regla de abajo), que es justo el layout que el cliente aprobó y no pidió
   cambiar. */
@media (max-width: 703px) {
  .patio-pared .maceta,
  .patio-pared .pared-viva-hueco {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc(var(--pieza) * 2);
  }
}

/* El vídeo, además, se centra en la franja de tableta (704–879 px): ahí ya hay un
   azulejo de margen pero todavía NO hay hueco lateral donde apoyarse —la regla de
   abajo entra a 880 px—, así que sin esto se quedaba pegado al borde izquierdo de su
   sección: medido 96..288 dentro de una sección de 96..672. */
@media (min-width: 704px) and (max-width: 879px) {
  .patio-pared .pared-viva-hueco {
    grid-column: 1 / -1;
    justify-self: center;
  }
}

/* Una sección `.patio--ancho` (la portada, las citas espaciadoras — §3.4) ocupa todo
   el ancho de `main`: no le queda un lado contrario que rellenar, así que una maceta
   ahí no tiene hueco donde apoyarse y cae en su propia fila nueva, alargando la
   sección — medido en la portada, 2 pistas de alto de más sin texto detrás. Decisión
   de este encargo: **una sección sin hueco lateral no lleva maceta.**
   `tools/construir.py` ya no la escribe (ver `construir_inicio()`); esta regla es el
   cinturón junto al tirante, para que una maceta añadida sin mirar aquí no vuelva a
   abrir esa fila. */
.patio-pared .patio--ancho .maceta,
.patio-pared .patio--ancho .pared-viva-hueco { display: none; }

/* LA REJA EN LA TIENDA — encargo del cliente: «a la derecha del azulejo de camiseta,
   del mismo tamaño y centrado, recorta de abajo igual que antes». Con tres productos en
   dos columnas, la casilla a la derecha de la camiseta queda libre: la reja la ocupa
   como una ficha más, así que mide exactamente lo que ella sin que haya que escribir
   ningún tamaño. Es decoración, no producto: el `aria-hidden` lo pone el generador.
   `cover` con el borde superior anclado, igual que el vídeo del inicio: lo que se
   recorta es la parte de abajo, nunca la de arriba. */
.patio-pared .lista-patio > .producto--decorado { display: block; }
.patio-pared .producto--decorado .pared-viva-hueco {
  display: block;
  max-width: none;
  width: 100%;
  height: 100%;
}
.patio-pared .producto--decorado .pared-viva {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}

@media (min-width: 880px) {
  .patio-pared .patio[data-lado] {
    position: relative;
    --hueco-lado: min(
      calc(var(--cols) - 2 * var(--off) - var(--ancho-seccion)),
      var(--ancho-seccion)
    );
  }
  /* El vídeo llena el hueco entero: mismo alto que la sección —rótulo incluido— y
     todo el ancho del hueco. Por eso pierde aquí el tope de `--vertical-maxima`, que
     lo dejaba en 3,94 azulejos de los 4 disponibles, y su `aspect-ratio`, que le
     impedía estirarse a la altura de la fila. El recorte lo resuelve `cover` sobre la
     pieza: es una figura centrada andando, y perder unos píxeles de los lados no le
     quita nada. Con `contain` quedaba una franja muerta arriba de casi 100 px. */
  .patio-pared .patio[data-lado] .pared-viva-hueco {
    max-width: none;
  }
  .patio-pared .patio[data-lado] .pared-viva-hueco .pared-viva {
    aspect-ratio: auto;
    height: 100%;
    object-fit: cover;
    /* Corta por ABAJO, nunca por arriba: encuadrado al centro, el recorte le llegaba a
       la cabeza a David. Anclando el borde superior, lo que se pierde son los pies. */
    object-position: center top;
  }
  .patio-pared .patio[data-lado] .maceta,
  .patio-pared .patio[data-lado] .pared-viva-hueco {
    grid-column: auto;
    grid-row: auto;
    position: absolute;
    top: 0;
    bottom: 0;
    width: calc(var(--pieza) * var(--hueco-lado));
  }
  /* Contenido a la izquierda → hueco de pared a la derecha, y viceversa: el mismo dato
     `data-lado` que ya decide el lado del contenido (línea 897) decide el de la
     maceta, nunca una clase de lado escrita a mano. `left: 100%` / `right: 100%` (no
     un desplazamiento en `--pieza`) porque el ancho ya no es fijo: apoyar la maceta
     justo en el borde de su sección basta, sea cual sea `--hueco-lado`. */
  .patio-pared .patio[data-lado="izquierda"] .maceta,
  .patio-pared .patio[data-lado="izquierda"] .pared-viva-hueco { left: 100%; }
  .patio-pared .patio[data-lado="derecha"] .maceta,
  .patio-pared .patio[data-lado="derecha"] .pared-viva-hueco { right: 100%; }
}

/* PARED VIVA — los canvas de Spotify en un hueco vacío (encargo 3): decoración, nunca
   contenido, igual que la maceta. 2 azulejos de ancho, vertical 9:16 (el formato nativo
   de las piezas, §4.2 ya documenta esa proporción para «vertical de vídeo»), con el
   mismo tope de `--vertical-maxima` que usa `.pieza--captura`. Sin JS el `<video>`
   enseña su `poster` y no reproduce nada: no lleva `autoplay` (ver
   `plantillas/pared-viva.js`). */
.patio-pared .pared-viva-hueco {
  --n: 2;
  grid-row-end: span 3;
  max-width: var(--vertical-maxima);
  position: relative;
  z-index: 1;
  pointer-events: none;
}
.patio-pared .pared-viva {
  display: block;
  width: 100%;
  height: 100%;
  /* Encargo del cliente, «opacidad de los vídeos a 88 %, que se mezclen un poquito».
     No es un número nuevo: es `--velo` (§1.1.1), el mismo 0,88 con el que ya se lava
     el fondo de cada hueco. El vídeo no lleva texto encima en ninguna página — es
     decoración `aria-hidden`, nunca contenido —, así que mezclarlo con la pared no
     abre ningún par de contraste nuevo que medir. */
  opacity: var(--velo);
  aspect-ratio: 9 / 16;
  /* `contain`, no `cover`: al salir al hueco lateral la caja toma toda la altura de la
     sección, y `cover` recortaría la pieza por arriba y por abajo. Apoyada al fondo,
     igual que el jarrón, para que las dos decoraciones se lean como una familia. */
  object-fit: contain;
  object-position: center bottom;
  height: 100%;
}

/* ZÓCALO — el separador de §3.4: medio azulejo de teja, sin animación. Su sección es
   `.patio--ancho`, que ya no es a sangre (el cliente retiró esa excepción, arriba): el
   zócalo llena la rejilla interior de su sección con `grid-column: 1 / -1`, que hoy
   mide `--cols − 2·--off`, así que respeta el margen solo. */
.patio-pared .zocalo {
  grid-column: 1 / -1;
  height: calc(var(--pieza) / 2);
  background: var(--teja);
  position: relative;
  z-index: 2;
}

/* ── Tipografía dentro de los huecos (§2.4 y §2.5) ───────────────────────────────── */
.patio-pared .rotulo {
  font-family: var(--fuente-titular);
  font-size: var(--t-rotulo);
  line-height: var(--int-rotulo);
  letter-spacing: var(--let-rotulo);
}
/* Encargo del cliente, «las citas las quiero centradas»: `.verso` heredaba
   `text-align: start` y se leía pegada al margen izquierdo de su sección, alternara o
   no. Se centra en todas, no solo en la primera. */
.patio-pared .verso { font-family: var(--fuente-verso); text-align: center; }
/* Un rótulo que ES un dato se compone entero en pila de sistema: «SET 1» lleva una
   cifra, y Veracruz no tiene ni una (§2.6.1 y regla 4 de la constitución). Sin esta
   regla `.rotulo` le ganaba a `.dato` por especificidad y el «1» caía en el respaldo. */
.patio-pared .rotulo.dato {
  font-family: var(--fuente-dato);
  letter-spacing: var(--let-dato);
}
.patio-pared .hueco .verso { max-width: none; margin-bottom: 0; }
.patio-pared .recuento {
  font-size: var(--t-dato);
  line-height: var(--int-dato);
  color: var(--azulejo-hondo);                   /* 7,98:1 sobre --cal-hueso (§1.3) */
}
.patio-pared .destacado {
  font-size: var(--t-destacado);
  line-height: var(--int-destacado);
  letter-spacing: var(--let-destacado);
}
.patio-pared .menor { font-size: var(--t-menor); line-height: var(--int-menor); }
.patio-pared .lista-hueco {
  display: flex;
  flex-direction: column;
  gap: calc(var(--medio-ritmo) / 2);
}
.patio-pared .prosa { max-width: var(--medida-texto); }

/* ── La cabecera, dentro del patio ───────────────────────────────────────────────────
   §3.3 pide una sola zona de azulejo por pantalla, y en esta página la zona de azulejo
   es la pared entera. Las dos filas decorativas de la banda sobran: son `aria-hidden`,
   así que apagarlas no quita ni un dato. Lo que queda es lo que §5.1 pedía desde el
   principio —el logotipo en un hueco blanco macizo—, solo que ahora el hueco está de
   verdad abierto en una pared de verdad.                                               */
.patio-pared .cabecera { background: transparent; }
.patio-pared .banda__fila { display: none; }
.patio-pared .banda__hueco,
.patio-pared .navegacion {
  background: var(--cal-hueso);
  box-shadow: 0 0 0 var(--cerco-interior) var(--hueco-fondo);
  margin: var(--cerco);
}
/* El aire de arriba deja ver un dedo de pared entre el logotipo y la navegación: si
   los dos huecos se quedan a 5 px, sus cercos se tocan y se leen como un filete doble */
.patio-pared .navegacion { padding: var(--ritmo); margin-top: var(--medio-ritmo); }
/* La cabecera se alinea con la retícula, no con el envoltorio de 1280 px: si no, sus
   huecos empiezan 23 px a la derecha del borde de la reja y la llaga se ve partida.
   El margen es el mismo --off a los dos lados, simétrico como el resto de la página. */
.patio-pared .cabecera .envoltorio,
.patio-pared .pie .envoltorio {
  max-width: none;
  margin-inline: 0;
  /* `--borde` cubre el tramo sin `--off` (< 704 px); en cuanto `--off` manda, `--borde`
     es 0 y esto vuelve a ser exactamente lo de antes. El pie llevaba su propio
     `var(--margen)` —más estrecho que el de la pared— y por eso su navegación medía
     menos que los 2 azulejos del resto de la página; con el mismo cálculo que la
     cabecera, el margen es uno solo en toda la página. */
  padding-inline: calc(var(--off) * var(--pieza) + var(--borde));
}

/* ── §7.6 · prefers-reduced-motion ───────────────────────────────────────────────────
   La regla, en una línea: se quita el movimiento, nunca el cambio de estado. En esta
   página no hay ni una transición, ni un `animation`, ni un `@keyframes` que quitar —la
   pared es rígida, la viñeta es fija y el color de un enlace cambia en 0 ms (§7.5)—,
   así que lo único que queda por apagar es el desplazamiento suave de los anclajes. Los
   canvas de `.pared-viva` (encargo 3) no se paran aquí porque no hay nada que pararles
   en CSS —nunca llevan `autoplay`—: `plantillas/pared-viva.js` lee la misma media query
   y, si pide menos movimiento, no llama a `.play()` en ningún momento. Enseñan su
   póster siempre, no solo mientras carga. */
@media (prefers-reduced-motion: reduce) {
  .patio-pared, .patio-pared *, .patio-pared *::before, .patio-pared *::after {
    transition-duration: 0ms !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* ── La garrapata de carga — ÚNICA excepción a «ningún @keyframes en la hoja» ───────
   Enmienda de §7.3 y §7.7 del sistema de diseño, autorizada por el cliente el
   2026-09-22: «la garrapata puede dar vueltas sobre su centro» mientras la página
   carga. Nada más — sin fundido, sin escalado, sin rebote.

   La capa la crea `plantillas/carga.js`, nunca el HTML servido (así que sin
   JavaScript no hay capa que tape nada), y ese mismo script no la crea si
   `prefers-reduced-motion: reduce` — por eso esta regla no necesita repetir la guarda
   aquí: si el navegador pide menos movimiento, `.carga` sencillamente no llega a
   existir en el DOM. */
.carga {
  position: fixed;
  inset: 0;
  /* Por encima de todo lo demás del sitio — el máximo hoy es 4, en el anillo de foco
     del salto de servicio (línea 986) — porque esta capa tiene que tapar la página
     entera mientras carga, cabecera y pie incluidos. */
  z-index: 10;
  display: grid;
  place-items: center;
  /* Fondo alpha, por encargo del cliente: la capa no tapa la página, solo sostiene a la
     garrapata girando encima mientras carga. Era `--cal-hueso` opaco y bloqueaba la
     vista entera. Al no tapar nada, tampoco debe interceptar el ratón ni el foco. */
  background: transparent;
  pointer-events: none;
}
.carga__garrapata {
  width: var(--carga-garrapata);
  height: auto;
  animation: girar var(--carga-duracion) linear infinite;
}
/* 1,2 s: no sale de ninguna medición — es el primer gesto continuo del sistema, y
   §7.5 solo mide gestos puntuales (90–240 ms) de causa y efecto, no un giro en bucle
   mientras algo carga. Se documenta como token nuevo en vez de un número suelto para
   que, si se mide y cambia, sea un solo sitio el que tocar. */
:root {
  /* Más lento por encargo del cliente: 1,2 s se leía nervioso para un giro en bucle. */
  --carga-duracion: 2.2s;
  /* Y más grande. El tamaño vive aquí y no en `carga.js` para que se pueda ajustar sin
     tocar el script; los atributos width/height del <img> solo reservan la caja. */
  --carga-garrapata: 140px;
}
@keyframes girar {
  to { transform: rotate(360deg); }
}

/* ── Contacto y conciertos: todo el texto centrado ──────────────────────────────────
   Encargo del cliente. Son las dos páginas de una sola columna y pocos datos, donde
   el texto a la izquierda dejaba la mitad derecha del hueco vacía y descuadrada.
   Va sobre una clase de cuerpo propia, `pagina--centrada`, y no sobre `.patio--fijo`,
   que la comparten también tienda y la cabecera de galería: ahí NO se centra.
   Los teléfonos y el correo se quedan en pila de sistema (§2.4): centrar no los
   convierte en Veracruz, que no dibuja dígitos.                                      */
.pagina--centrada main { text-align: center; }
/* `text-align` no centra nada aquí: el dato y su botón de copiar viven en
   `.ficha__linea` (`display: flex`), y `justify-content` gobierna esa fila, no
   `text-align`. El `margin-inline: auto` que hubo antes en `.copiar` centraba el
   botón como si fuera el único elemento con espacio libre que absorber, y dejaba el
   enlace pegado a la izquierda — medido: el par no compartía centro. Centrando la
   fila entera, enlace y botón (inyectado por copiar.js, §6.3) quedan agrupados y con
   el mismo aire a los dos lados. */
.pagina--centrada .ficha__linea { justify-content: center; }
