/* ==========================================================================
   NARA Contemporary Tattoo Studio — stylesheet
   Static site. No framework, no build step. Client canon only:
   Blanco #FFFFFF · Crema #D6C4B0 · Expreso #301E12
   Velista (display, titles only, upright — no italic file exists)
   Poppins (body/UI, weights 400/500/600 only)
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   --------------------------------------------------------------------------
   Los .woff2 llevan ?v= igual que style.css y main.js: el .htaccess sirve las
   fuentes con `immutable, max-age=1 año`, así que un cambio de fichero con el
   mismo nombre sería invisible para quien ya visitó. El mismo ?v= tiene que
   estar en el <link rel="preload"> de cada página o el navegador descarga la
   fuente dos veces.
   Los originales sin subconjunto están fuera del build, en
   `06 — Font originals (no desplegar)/`.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Velista";
  src: url("../fonts/VELISTA.woff2?v=20260828a") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/Poppins-Regular.woff2?v=20260828a") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/Poppins-Medium.woff2?v=20260828a") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("../fonts/Poppins-SemiBold.woff2?v=20260828a") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Palette — exactly three tones, per BMB canon. Never a fourth. */
  --blanco: #ffffff;
  --crema: #d6c4b0;
  --expreso: #301e12;

  /* Derived tints/shades of the three canon tones only */
  --crema-15: rgba(214, 196, 176, 0.15);
  --crema-30: rgba(214, 196, 176, 0.3);
  --crema-50: rgba(214, 196, 176, 0.5);
  --expreso-06: rgba(48, 30, 18, 0.06);
  --expreso-08: rgba(48, 30, 18, 0.08);
  --expreso-12: rgba(48, 30, 18, 0.12);
  --expreso-30: rgba(48, 30, 18, 0.3);
  --expreso-55: rgba(48, 30, 18, 0.55);
  --expreso-70: rgba(48, 30, 18, 0.7);
  --blanco-70: rgba(255, 255, 255, 0.7);

  /* Type families */
  --ff-display: "Velista", Georgia, "Times New Roman", serif;
  --ff-body: "Poppins", -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Type scale */
  --fs-h1: clamp(2.25rem, 1.4rem + 4.2vw, 5rem);
  --fs-h2: clamp(1.75rem, 1.3rem + 2.2vw, 3rem);
  --fs-h3: clamp(1.25rem, 1.05rem + 1vw, 1.75rem);
  --fs-eyebrow: clamp(0.6875rem, 0.65rem + 0.2vw, 0.75rem);
  --fs-body: clamp(0.9375rem, 0.9rem + 0.2vw, 1.0625rem);
  --fs-meta: clamp(0.8125rem, 0.8rem + 0.1vw, 0.875rem);

  /* Spacing (8px base) */
  --space-1: 8px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 32px;
  --space-5: 48px;
  --space-6: 64px;
  --space-7: 96px;
  --space-8: 128px;

  /* Grid */
  --container-max: 1200px;
  --pad-base: 20px;
  --pad-tablet: 32px;
  --pad-desktop: 64px;

  /* Motion */
  --ease: cubic-bezier(0.2, 0, 0, 1);      /* KEEP. Buttons, nav, links, form focus, card hover. */
  --dur: 600ms;                             /* KEEP. Default body-copy reveal duration. */

  /* Motion — appear-from-nothing family, reserved for headings only */
  --ease-veil: cubic-bezier(0.16, 1, 0.3, 1);  /* out-expo. Heavy deceleration, zero overshoot —
    reads as something arriving under its own weight and settling once, not bouncing into place. */
  --dur-veil: 720ms;        /* section headings (heading-display + .reveal, everywhere except hero) */
  --dur-veil-hero: 900ms;   /* the homepage hero title only — the single most-weighted entrance */

  /* Motion — differentiated reveal roles */
  --dur-kicker: 380ms;      /* eyebrow labels */

  /* Misc */
  --radius: 2px;
  --border-thin: 1px solid var(--expreso-12);
}

/* --------------------------------------------------------------------------
   3. Reset / base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

html,
body {
  /* Red de seguridad contra desbordes horizontales — verificada midiendo
     scrollWidth en todas las páginas a 390/430/768/1024/1080/1440, no a ojo. */
  overflow-x: hidden;
  max-width: 100%;
}
/* `overflow-x: hidden` en <body> lo convierte en contenedor de scroll
   (overflow-y pasa a `auto`), y eso ROMPE `position: sticky` en cualquier
   descendiente: la declaración de .anticipation__statement dejaba de fijarse
   y la columna izquierda de labio y cejas quedaba vacía media pantalla.
   `clip` recorta igual pero NO crea contenedor de scroll, así que sticky
   vuelve a funcionar. El `hidden` de arriba queda como respaldo para
   navegadores sin `clip` (Safari < 16), donde el único coste es que sticky
   se comporta como estático — degradación aceptable, no rotura. */
@supports (overflow-x: clip) {
  html,
  body {
    overflow-x: clip;
  }
}

body {
  margin: 0;
  background: var(--blanco);
  color: var(--expreso);
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

h1,
h2,
h3,
h4,
p,
figure,
dl,
dd {
  margin: 0;
}

button,
input,
select,
textarea {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

button {
  cursor: pointer;
}

/* Visible focus states everywhere — keyboard-navigable */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--expreso);
  outline-offset: 3px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: -48px;
  left: var(--space-2);
  background: var(--expreso);
  color: var(--crema);
  padding: 10px 18px;
  z-index: 200;
  transition: top 200ms var(--ease);
  font-family: var(--ff-body);
  font-weight: 500;
}
.skip-link:focus {
  top: var(--space-2);
}

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--pad-base);
}
@media (min-width: 768px) {
  .container {
    padding: 0 var(--pad-tablet);
  }
}
@media (min-width: 1024px) {
  .container {
    padding: 0 var(--pad-desktop);
  }
}

.container--narrow {
  max-width: 820px;
}

.section {
  padding: 72px 0;
}
.section--tight {
  padding: 48px 0;
}
@media (min-width: 1024px) {
  .section {
    padding: 140px 0;
  }
  .section--tight {
    padding: 88px 0;
  }
}

.section--crema {
  background: var(--crema);
}
.section--blanco {
  background: var(--blanco);
}
.section--expreso {
  background: var(--expreso);
  color: var(--crema);
}

/* --------------------------------------------------------------------------
   5. Typography components
   -------------------------------------------------------------------------- */
.heading-display {
  font-family: var(--ff-display);
  font-weight: 400;
  font-style: normal; /* Velista has no italic file — never fake it */
  line-height: 1.1;
  letter-spacing: 0.005em;
}
.heading-display--h1 {
  font-size: var(--fs-h1);
  line-height: 1.05;
}
.heading-display--h2 {
  font-size: var(--fs-h2);
}
.heading-display--h3 {
  font-size: var(--fs-h3);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso-70);
}
.section--expreso .eyebrow {
  color: var(--crema);
}
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: currentColor;
}

.lede {
  font-family: var(--ff-body);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.65;
  color: var(--expreso);
  max-width: 60ch;
}
.section--expreso .lede {
  color: var(--crema);
}

.meta {
  font-family: var(--ff-body);
  font-size: var(--fs-meta);
  color: var(--expreso-70);
}

/* --------------------------------------------------------------------------
   6. Buttons / CTAs
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 16px 30px;
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
  border-radius: var(--radius);
  transition: background 260ms var(--ease), color 260ms var(--ease),
    border-color 260ms var(--ease), transform 260ms var(--ease);
}
.btn:hover {
  transform: translateY(-2px);
}

.btn--primary {
  background: var(--expreso);
  color: var(--crema);
  border-color: var(--expreso);
}
.btn--primary:hover {
  background: #241609;
}

.btn--ghost {
  background: transparent;
  color: var(--expreso);
  border-color: var(--expreso);
}
.btn--ghost:hover {
  background: var(--expreso);
  color: var(--crema);
}

.btn--on-dark {
  background: var(--crema);
  color: var(--expreso);
  border-color: var(--crema);
}
.btn--on-dark:hover {
  background: var(--blanco);
  border-color: var(--blanco);
}

.btn--outline-on-dark {
  background: transparent;
  color: var(--crema);
  border-color: var(--crema-50);
}
.btn--outline-on-dark:hover {
  background: var(--crema);
  color: var(--expreso);
  border-color: var(--crema);
}

.btn svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   7. Header / navigation
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
  background: transparent;
  transition: background 320ms var(--ease), border-color 320ms var(--ease);
  border-bottom: 1px solid transparent;
}
.site-header.is-scrolled {
  background: var(--blanco);
  border-bottom: 1px solid var(--expreso-12);
}
/* Pages without a dark hero start solid immediately */
.site-header.is-solid-default {
  background: var(--blanco);
  border-bottom: 1px solid var(--expreso-12);
}

.nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 18px;
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 10px;
}
.nav-logo img {
  height: 34px;
  width: auto;
}
/* Swap mark for light-on-dark automatically while over a dark/transparent hero */
.nav-logo .mark-on-light {
  display: block;
}
.nav-logo .mark-on-dark {
  display: none;
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-logo .mark-on-light {
  display: none;
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-logo .mark-on-dark {
  display: block;
}

.nav-list {
  display: none;
  align-items: center;
  gap: var(--space-5);
}
.nav-link {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--expreso);
  padding: 4px 0;
  border-bottom: 1px solid transparent;
  transition: border-color 220ms var(--ease), color 220ms var(--ease);
}
.nav-link:hover,
.nav-link.is-active {
  border-color: currentColor;
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-link {
  color: var(--blanco);
}

.nav-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.nav-whatsapp {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--expreso);
  color: var(--expreso);
  transition: background 220ms var(--ease), color 220ms var(--ease),
    border-color 220ms var(--ease);
}
.nav-whatsapp:hover {
  background: var(--expreso);
  color: var(--crema);
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-whatsapp {
  border-color: var(--blanco);
  color: var(--blanco);
}
.nav-whatsapp svg {
  width: 18px;
  height: 18px;
}

.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 42px;
  height: 42px;
  background: transparent;
  border: 1px solid var(--expreso);
  border-radius: 50%;
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-toggle {
  border-color: var(--blanco);
}
.nav-toggle span {
  display: block;
  width: 16px;
  height: 1px;
  background: var(--expreso);
  transition: transform 260ms var(--ease), opacity 260ms var(--ease);
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-toggle span {
  background: var(--blanco);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

.nav-panel {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  width: min(320px, 84vw);
  background: var(--expreso);
  color: var(--crema);
  padding: 100px var(--space-4) var(--space-4);
  transform: translateX(100%);
  transition: transform 380ms var(--ease);
  z-index: 99;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.nav-panel.is-open {
  transform: translateX(0);
}
.nav-panel__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.nav-panel__link {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 20px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crema);
}
.nav-panel__cta {
  margin-top: auto;
}
.nav-scrim {
  position: fixed;
  inset: 0;
  background: var(--expreso-55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 380ms var(--ease);
  z-index: 98;
}
.nav-scrim.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* Conmutación a barra horizontal: ver bloque 26. El breakpoint vive allí
   (1080px), no aquí — con MICROPIGMENTACIÓN en la lista, 900px ya no
   alcanza y dos umbrales distintos en el mismo fichero es una trampa. */

/* --------------------------------------------------------------------------
   7b. Language toggle — ES · EN. Discreet, header + mobile nav panel.
   Header instance lives inside .nav-right (hidden until the desktop nav-list
   also shows, ≥1080px) so it reads as "beside the navigation", not a 4th
   item spaced apart by .nav-bar's justify-content:space-between. The
   mobile-panel instance is a always-visible duplicate so EN is reachable
   below 1080px too, per canon: every page must expose the toggle.
   -------------------------------------------------------------------------- */
.lang-toggle {
  display: none;
  align-items: center;
  gap: 6px;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--expreso);
}
.lang-toggle__current {
  color: inherit;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
}
.lang-toggle__link {
  color: inherit;
  /* 0.5 dejaba el enlace en 3.19:1 sobre la barra blanca — falla AA a 12 px.
     0.75 da 5.1:1 y conserva la jerarquía: idioma actual (1) > idioma
     alternativo (0.75) > separador (0.65). */
  opacity: 0.75;
  transition: opacity 220ms var(--ease), color 220ms var(--ease);
}
.lang-toggle__link:hover {
  opacity: 1;
}
.lang-toggle__sep {
  /* Decorativo y aria-hidden, pero se pinta y se audita igual: 0.35 daba
     2.14:1. 0.65 = 5.1:1. */
  opacity: 0.65;
}
.site-header:not(.is-scrolled):not(.is-solid-default) .lang-toggle {
  color: var(--blanco);
}
/* Visibilidad del selector en desktop: bloque 26, mismo umbral que .nav-list. */

/* Mobile nav panel variant — dark surface (.nav-panel is --expreso), own row
   above the link list, separated by a hairline in the crema family. */
.nav-panel__lang {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--crema);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid var(--crema-30);
}
.nav-panel__lang .lang-toggle__current {
  color: var(--blanco);
}
.nav-panel__lang .lang-toggle__link {
  color: var(--crema);
  opacity: 0.7;
}
.nav-panel__lang .lang-toggle__link:hover {
  opacity: 1;
}

/* --------------------------------------------------------------------------
   8. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  color: var(--blanco);
  background-color: var(--expreso); /* fallback while .hero__bg paints */
  overflow: hidden;
}
/* Background moved to its own layer so parallax animates only `transform`
   (translateY) — never background-position/background-size, which would
   paint on every frame. inset:-6% gives 12% total vertical headroom for
   the scroll-linked shift. See assets/js/main.js initParallax(). */
.hero__bg {
  position: absolute;
  inset: -6% 0;
  z-index: 0;
  background-color: var(--expreso);
  background-image: url("../img/hero/hero-mobile.jpg");
  background-image: image-set(
    url("../img/hero/hero-mobile.webp") type("image/webp") 1x,
    url("../img/hero/hero-mobile.jpg") type("image/jpeg") 1x
  );
  background-size: cover;
  background-position: right 35%;
  background-repeat: no-repeat;
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    180deg,
    rgba(48, 30, 18, 0.78) 0%,
    rgba(48, 30, 18, 0.78) 60%,
    rgba(48, 30, 18, 0.88) 100%
  );
}
.hero__content {
  position: relative;
  z-index: 2;
  padding-block: var(--space-6);
  width: 100%;
}
.hero__eyebrow {
  color: var(--crema);
  margin-bottom: var(--space-3);
}
.hero__title {
  color: var(--blanco);
  /* El corte del H1 lo manda <br> explícito (§1.1/§1.2 del memo), no la
     medida: 14ch partía "The First Tattoo Atelier" a la fuerza en EN. La
     medida queda como red de seguridad, no como mecanismo de corte. */
  max-width: 24ch;
  margin-bottom: var(--space-3);
}
.hero__sub {
  color: var(--crema);
  max-width: 42ch;
  margin-bottom: var(--space-4);
  font-size: var(--fs-body);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Hero entrance — the single bespoke brand moment on the site, hand-set
   fixed delays instead of the generic data-reveal-group stagger. 120ms
   overlap on the title while the eyebrow is still finishing; 620/780ms
   let the 900ms title mask-reveal settle most of the way before
   sub/actions arrive — overlapping action, not a queue. */
.hero .hero__eyebrow.reveal { transition-delay: 0ms; }
.hero .hero__title-wrap.reveal { transition-delay: 120ms; }
.hero .hero__sub.reveal { transition-delay: 620ms; }
.hero .hero__actions.reveal { transition-delay: 780ms; }
/* Single heaviest entrance on the site — one notch slower than every
   other mask-reveal heading (which use --dur-veil, 720ms). */
.hero__title-wrap.reveal.reveal-veil > * {
  transition-duration: var(--dur-veil-hero);
}

@media (min-width: 768px) {
  .hero__bg {
    background-image: url("../img/hero/hero-desktop.jpg");
    background-image: image-set(
      url("../img/hero/hero-desktop.webp") type("image/webp") 1x,
      url("../img/hero/hero-desktop.jpg") type("image/jpeg") 1x
    );
    background-position: center 30%;
  }
  .hero {
    align-items: center;
  }
  .hero__content {
    padding-block: var(--space-8);
  }
}

/* Sub-hero — division pages, atelier, reservar */
.sub-hero {
  position: relative;
  padding: 168px 0 var(--space-6);
  background: var(--expreso);
  color: var(--crema);
  overflow: hidden;
}
.sub-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(
      circle at 15% 20%,
      rgba(214, 196, 176, 0.12),
      transparent 55%
    ),
    radial-gradient(circle at 85% 80%, rgba(214, 196, 176, 0.08), transparent 55%);
}
.sub-hero__inner {
  position: relative;
  z-index: 1; /* sube a 2 cuando hay .sub-hero__bg — ver sección 44 */
}
.sub-hero__eyebrow {
  color: var(--crema);
  margin-bottom: var(--space-3);
}
.sub-hero__title {
  color: var(--blanco);
  margin-bottom: var(--space-2);
  /* Requisito editorial: los títulos tienen que leerse en una línea, sin
     guiones ni cortes raros. Un H1 que compone «Micropigmenta-ción» es
     exactamente lo que ese requisito excluye, así que este titular NO se parte: se
     compone equilibrado y sin guion, igual que el resto de h1/h2/h3.

     El problema real que aquí se resolvía con guiones —«Micropigmentación»,
     17 caracteres, la palabra más larga de cualquier H1 del sitio— se
     resuelve con tipografía. Ver el bloque de ancho estrecho más abajo.

     `overflow-wrap: break-word` se queda como red de seguridad: si algún
     día entra una palabra aún más larga, romperá SIN guion antes que
     desbordar la caja. */
  text-wrap: balance;
  hyphens: none;
  -webkit-hyphens: none;
  overflow-wrap: break-word;
}
/* EY-01: en/micropigmentation + en/scalp-micropigmentation. El clamp baja el
   cuerpo en esas dos portadas para que "Micropigmentation" quepa entera.
   `hyphens: none` (antes `manual`) alinea el modificador con la regla
   general: ningún H1 del sitio se parte con guion. Este clamp gobierna TODO
   el rango, también por debajo de 480 px: allí lo reimpone el selector
   combinado del bloque de ancho estrecho (ver más abajo). */
.sub-hero__title--no-orphan {
  hyphens: none;
  -webkit-hyphens: none;
  font-size: clamp(26px, 8vw, 38.78px);
}

/* --------------------------------------------------------------------------
   MEDIDA DEL H1 EN PANTALLA ESTRECHA — sustituye al guionado
   --------------------------------------------------------------------------
   Palabra más larga de cualquier H1 del sitio: "Micropigmentación"
   (ES) y "Micropigmentation" (EN), 17 caracteres las dos. Medida real en
   Velista con canvas: el ancho de la palabra es 9.578 × font-size (ES) y
   9.499 × font-size (EN) — la ES es la que manda.

   Con el `--fs-h1` general (1.4rem + 4.2vw) la palabra sólo cabe a partir de
   ~426 px de ventana; por debajo desbordaba, y de ahí venía el `hyphens:
   auto`. Esta rampa la sustituye: pasa por (320 px → 27.00) y (480 px →
   42.56), y 42.56 px es exactamente lo que vale `--fs-h1` a 480 px, así que
   el empalme al salir del media query es continuo — tablet y escritorio no
   cambian ni un píxel.

   Deja la palabra al 92.5 % de la caja en TODO el rango de teléfono
   (320/360/375/390/430/479), que es holgura suficiente y constante.
   El mínimo de 26 px sólo entra por debajo de ~310 px. */
@media (max-width: 479.98px) {
  .sub-hero__title {
    font-size: clamp(26px, 9.725vw - 4.12px, 42.56px);
  }

  /* Selector combinado (0,2,0) — NO simplificar a una sola clase.
     La regla de arriba y `.sub-hero__title--no-orphan` tienen la misma
     especificidad (0,1,0), y ésta va después en el archivo, así que por
     debajo de 480 px la rampa estrecha le ganaba al modificador: el H1 de
     las dos portadas con --no-orphan medía 42.46 px a 479 px y 38.40 px a
     480 px, o sea que ENCOGÍA al ensanchar la ventana. Con el selector
     doble el modificador gobierna todo el rango y el empalme queda
     continuo y creciente: 38.32 px a 479 px → 38.40 px a 480 px.
     La palabra larga ("Micropigmentation", 9.499 × font-size) sigue
     cabiendo con holgura en todo el rango de teléfono. */
  .sub-hero__title.sub-hero__title--no-orphan {
    font-size: clamp(26px, 8vw, 38.78px);
  }
}
.sub-hero__desc {
  max-width: 56ch;
  color: var(--crema);
  font-size: var(--fs-body);
}
@media (min-width: 1024px) {
  .sub-hero {
    padding: 208px 0 var(--space-7);
  }
}

/* Sub-hero entrance — same bespoke-delay treatment as the homepage hero,
   one notch lighter (uses --dur-veil 720ms, not --dur-veil-hero). */
.sub-hero .sub-hero__eyebrow.reveal { transition-delay: 0ms; }
.sub-hero .sub-hero__title-wrap.reveal { transition-delay: 100ms; }
.sub-hero .sub-hero__desc.reveal { transition-delay: 560ms; }

/* --------------------------------------------------------------------------
   9. Positioning strip
   -------------------------------------------------------------------------- */
.positioning {
  display: grid;
  gap: var(--space-4);
}
.positioning__statement {
  color: var(--expreso);
}
.positioning__body {
  max-width: 62ch;
}
@media (min-width: 1024px) {
  .positioning {
    grid-template-columns: 1.1fr 0.9fr;
    align-items: start;
    gap: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   10. Zero-portfolio texture slot
   -------------------------------------------------------------------------- */
.texture-slot {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--expreso-12);
  background: radial-gradient(
      circle at 20% 20%,
      var(--crema-30),
      transparent 60%
    ),
    radial-gradient(circle at 80% 70%, var(--expreso-08), transparent 55%),
    var(--blanco);
}
.texture-slot--4x5 {
  aspect-ratio: 4 / 5;
}
.texture-slot--1x1 {
  aspect-ratio: 1 / 1;
}
.texture-slot__mark {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 42%;
  transform: translate(-50%, -50%);
  opacity: 0.16;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   11. Division cards
   -------------------------------------------------------------------------- */
.division-grid {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 768px) {
  .division-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
  }
  /* El inicio presenta dos disciplinas, no tres divisiones: con repeat(3)
     la segunda tarjeta quedaba descentrada y sobraba una columna vacía. */
  .division-grid--2 {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }
}

.division-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid transparent;
}
.division-card__figure {
  margin: 0;
  isolation: isolate; /* contains the wash overlay's stacking context */
}
/* Wash hover — canon corner-grow overlay (wash-hover-card-pattern),
   adapted to the figure only. .division-card itself carries no
   background/border at rest, so nothing about the resting card changes;
   the wash triggers on hovering the whole card, not just the figure.
   Desktop/pointer-fine only — touch has no hover state, and without this
   gate a tap can leave a "sticky hover" wash on mobile.

   El velo es TRANSLÚCIDO, no un tapón: con expreso sólido el barrido
   ocultaba la foto justo cuando el visitante muestra interés en ella. A
   `--expreso-30` la obra se sigue leyendo y el estado se nota igual, porque
   lo que comunica el hover es el barrido creciendo desde la esquina, no la
   opacidad. Se usa el token que ya existe en el sistema en vez de inventar
   un valor suelto: la paleta son tres tonos y sus derivados declarados. */
@media (hover: hover) and (pointer: fine) {
  .division-card__figure::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 0;
    height: 0;
    background: var(--expreso-30);
    z-index: 1;
    transition: width 520ms var(--ease), height 520ms var(--ease);
  }
  .division-card:hover .division-card__figure::after,
  .division-card:focus-within .division-card__figure::after {
    width: 100%;
    height: 100%;
  }
}
.division-card__number {
  font-family: var(--ff-body);
  font-size: var(--fs-meta);
  letter-spacing: 0.14em;
  color: var(--expreso-70);
}
.division-card__title {
  color: var(--expreso);
}
.division-card__desc {
  color: var(--expreso);
  opacity: 0.85;
  max-width: 40ch;
}
.division-card__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: var(--space-2);
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--expreso);
  border-bottom: 1px solid transparent;
  transition: border-color 220ms var(--ease);
}
.division-card__link:hover,
.division-card__link:focus-visible {
  border-color: var(--expreso);
}
.division-card__link svg {
  width: 16px;
  height: 16px;
  transition: transform 260ms var(--ease);
}
.division-card__link:hover svg,
.division-card__link:focus-visible svg {
  transform: translateX(4px);
}

/* --------------------------------------------------------------------------
   12. Process steps (reveal-on-scroll, NOT pinned)
   -------------------------------------------------------------------------- */
.process {
  display: grid;
  gap: var(--space-5);
}
.process__list {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.process-step {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: var(--space-3);
  padding: var(--space-4) 0;
  border-top: 1px solid var(--expreso-12);
}
.process-step:last-child {
  border-bottom: 1px solid var(--expreso-12);
}
.process-step__number {
  width: 34px;
  height: 34px;
  border: 1px solid var(--expreso);
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: 13px;
}
.process-step__when {
  display: block;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--expreso-70);
  margin-bottom: 6px;
}
.process-step__title {
  font-family: var(--ff-display);
  font-size: 22px;
  font-weight: 400;
  margin-bottom: 6px;
}
.process-step__desc {
  max-width: 58ch;
  color: var(--expreso);
  opacity: 0.85;
}

.process-preview {
  display: grid;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .process-preview {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-3);
  }
  .process-preview .process-step {
    grid-template-columns: 1fr;
    border-top: none;
    border-left: 1px solid var(--expreso-12);
    padding: 0 var(--space-3);
  }
  .process-preview .process-step:last-child {
    border-bottom: none;
  }
}

/* --------------------------------------------------------------------------
   13. Trust / confianza strip
   -------------------------------------------------------------------------- */
.trust-grid {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 640px) {
  .trust-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .trust-grid--5 {
    grid-template-columns: repeat(5, 1fr);
  }
  .trust-grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
}
.trust-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.trust-card__icon {
  width: 40px;
  height: 40px;
  color: var(--expreso);
}
.trust-card__icon svg {
  width: 100%;
  height: 100%;
}
.trust-card__title {
  font-family: var(--ff-display);
  font-size: 20px;
  font-weight: 400;
}
.trust-card__desc {
  color: var(--expreso);
  opacity: 0.85;
  font-size: var(--fs-meta);
  max-width: 34ch;
}
.section--expreso .trust-card__icon {
  color: var(--crema);
}
.section--expreso .trust-card__desc {
  color: var(--crema);
  opacity: 0.85;
}

/* --------------------------------------------------------------------------
   14. Commission block ("El encargo")
   -------------------------------------------------------------------------- */
.commission {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 1024px) {
  .commission {
    grid-template-columns: 0.9fr 1.1fr;
    gap: var(--space-6);
    align-items: start;
  }
}
.commission__list {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.commission__item {
  display: flex;
  gap: var(--space-2);
  border-top: 1px solid var(--expreso-12);
  padding-top: var(--space-2);
}
.commission__item-title {
  font-family: var(--ff-body);
  font-weight: 600;
  min-width: 30%;
}
.commission__item-desc {
  color: var(--expreso);
  opacity: 0.85;
}

/* --------------------------------------------------------------------------
   15. CTA band
   -------------------------------------------------------------------------- */
/* Breaks the CONFIANZA -> CTA -> footer run of three identical expreso
   blocks. The image lives on ::before (no background-attachment:fixed —
   iOS-safe), an expreso scrim on ::after guarantees text contrast,
   content is lifted to z-index:2. No parallax, no extra HTML markup —
   a single component rule, same as the hero's image-set() pattern.
   Asset: cand_02_niche-right (FLUX-generated, $0, no artwork/lettering/
   people — verified by image-creator and inspected directly). */
.cta-band {
  position: relative;
  overflow: hidden;
  background: var(--expreso); /* fallback while ::before paints / if it fails to load */
  color: var(--crema);
  padding: var(--space-6) 0;
  text-align: center;
}
.cta-band::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: url("../img/cta/cta-mobile.jpg");
  background-image: image-set(
    url("../img/cta/cta-mobile.webp") type("image/webp") 1x,
    url("../img/cta/cta-mobile.jpg") type("image/jpeg") 1x
  );
  background-size: cover;
  background-position: center center;
  background-repeat: no-repeat;
}
/* Scrim — measured, not guessed. Worst-case (brightest) pixel actually
   behind the rendered text at both 1366/1440 and 375 — sampled from the
   real crop + background-position math, not eyeballed — blended at 76%
   expreso clears WCAG AA (>=4.5:1) on every text layer. The crema
   subtitle/outline-button label is the binding constraint (5.4-5.5:1 at
   76%, its value sits close to the wall's); the white title clears
   easily (~9:1). This exceeds the 55-70% starting range — the wall
   render is lighter than that range covers; legibility wins. */
.cta-band::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(48, 30, 18, 0.76);
}
.cta-band .container {
  position: relative;
  z-index: 2;
}
.cta-band__title {
  color: var(--blanco);
  margin-bottom: var(--space-2);
}
.cta-band__sub {
  color: var(--crema);
  max-width: 52ch;
  margin: 0 auto var(--space-4);
}
.cta-band__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}
@media (min-width: 1024px) {
  .cta-band {
    padding: var(--space-8) 0;
  }
  .cta-band::before {
    background-image: url("../img/cta/cta-desktop.jpg");
    background-image: image-set(
      url("../img/cta/cta-desktop.webp") type("image/webp") 1x,
      url("../img/cta/cta-desktop.jpg") type("image/jpeg") 1x
    );
    /* At this container's aspect ratio, cover is width-bound (the full
       image width always maps to the full viewport width — there is no
       horizontal slack for background-position-x to act on). The niche
       was kept clear of the centered text by baking the shift into the
       source crop itself (66.9% -> ~78% across), not via position.
       Vertically there IS slack (image taller than the visible window),
       and 100%/bottom hid the niche's lit interior entirely while
       surfacing the floor. 78% is the compromise that keeps a strip of
       floor in frame at the base *and* keeps most of the niche box
       visible above it — full bottom loses the niche, full top loses
       the floor; the source's 732px height can't show both in full at
       this section's ~440px runway. */
    background-position: center 78%;
  }
}

/* --------------------------------------------------------------------------
   16. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--expreso);
  color: var(--crema);
  padding: var(--space-6) 0 var(--space-4);
}
.footer-grid {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 768px) {
  .footer-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5) var(--space-4);
  }
}
@media (min-width: 1024px) {
  .footer-grid {
    grid-template-columns: 1.5fr repeat(3, 0.9fr) 1.25fr;
    gap: var(--space-4);
  }
}
.footer-brand img {
  height: 30px;
  width: auto;
  margin-bottom: var(--space-3);
}
.footer-brand__desc {
  max-width: 34ch;
  color: var(--crema);
  opacity: 0.85;
  font-size: var(--fs-meta);
}
.footer-col__title {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blanco);
  margin-bottom: var(--space-3);
}
.footer-links {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.footer-links a {
  font-size: var(--fs-meta);
  color: var(--crema);
  opacity: 0.9;
  transition: opacity 220ms var(--ease);
}
.footer-links a:hover {
  opacity: 1;
  text-decoration: underline;
}
.footer-bottom {
  margin-top: var(--space-6);
  padding-top: var(--space-3);
  border-top: 1px solid rgba(214, 196, 176, 0.18);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: space-between;
  font-size: var(--fs-meta);
  color: var(--crema);
  opacity: 0.75;
}

/* --------------------------------------------------------------------------
   17. Atelier — espacio / credenciales / authenticity block
   -------------------------------------------------------------------------- */
.credenciales-grid {
  display: grid;
  gap: var(--space-3);
}
@media (min-width: 640px) {
  .credenciales-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
.credenciales-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.credenciales-card__caption {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  color: var(--expreso-70);
}

.espacio {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}
@media (min-width: 1024px) {
  .espacio {
    grid-template-columns: 1fr 0.8fr;
    gap: var(--space-6);
  }
}
.espacio__figure {
  border: 1px solid var(--expreso-12);
  overflow: hidden;
  max-width: 340px;
  /* Matches the photo's intrinsic ratio (460/510) so the resting frame
     size is unchanged from before — only the image inside now oversizes
     and gets clipped, giving parallax headroom without exposing a gap. */
  aspect-ratio: 460 / 510;
}
.espacio__figure img {
  width: 100%;
  height: 112%;
  margin-top: -6%;
  object-fit: cover;
  display: block;
}
.espacio__caption {
  font-size: var(--fs-meta);
  color: var(--expreso-70);
  margin-top: var(--space-2);
}

/* --------------------------------------------------------------------------
   18. Form (reservar.html)
   -------------------------------------------------------------------------- */
.booking-grid {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1024px) {
  .booking-grid {
    grid-template-columns: 1.1fr 0.9fr;
    gap: var(--space-6);
    align-items: start;
  }
}

.form-card,
.whatsapp-card {
  background: var(--blanco);
  border: 1px solid var(--expreso-12);
  padding: var(--space-4);
}
@media (min-width: 768px) {
  .form-card,
  .whatsapp-card {
    padding: var(--space-5);
  }
}

.form-card__title,
.whatsapp-card__title {
  margin-bottom: var(--space-1);
}
.form-card__sub,
.whatsapp-card__sub {
  color: var(--expreso);
  opacity: 0.8;
  margin-bottom: var(--space-4);
}

.field {
  margin-bottom: var(--space-3);
}
.field label {
  display: block;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.03em;
  margin-bottom: 8px;
}
.field__required {
  color: var(--expreso-70);
  font-weight: 400;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  background: var(--blanco);
  border: 1px solid var(--expreso-30);
  border-radius: var(--radius);
  padding: 13px 14px;
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  color: var(--expreso);
  transition: border-color 220ms var(--ease);
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--expreso);
}
.field textarea {
  resize: vertical;
  min-height: 110px;
}
.field--error input,
.field--error select,
.field--error textarea {
  border-color: var(--expreso);
  border-width: 2px;
}
.field__error-msg {
  display: none;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: var(--fs-meta);
  color: var(--expreso);
  font-weight: 500;
}
.field__error-msg svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.field--error .field__error-msg {
  display: flex;
}

.form-submit-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}

.form-note {
  font-size: var(--fs-meta);
  color: var(--expreso-70);
  max-width: 42ch;
}

.form-success {
  display: none;
  border: 1px solid var(--expreso);
  padding: var(--space-3);
  margin-top: var(--space-4);
  font-size: var(--fs-meta);
}
.form-success.is-visible {
  display: block;
}
.form-success strong {
  display: block;
  font-family: var(--ff-display);
  font-size: 20px;
  font-weight: 400;
  margin-bottom: 6px;
}

.whatsapp-card {
  display: flex;
  flex-direction: column;
  height: auto;
  align-self: start;
}
.whatsapp-card__body {
  flex: 1;
}
.whatsapp-card__list {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0 var(--space-4);
}
.whatsapp-card__list li {
  display: flex;
  gap: 10px;
  font-size: var(--fs-meta);
}
.whatsapp-card__list svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-top: 3px;
  color: var(--expreso);
}

/* Ubicación + horario */
.location-grid {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1024px) {
  .location-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
  }
}
.location-list {
  display: grid;
  gap: var(--space-3);
  margin: var(--space-3) 0 var(--space-4);
}
.location-item dt {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--expreso-70);
  margin-bottom: 4px;
}
.location-item dd {
  font-size: var(--fs-body);
}

/* --------------------------------------------------------------------------
   19. Reveal-on-scroll
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Kicker role — eyebrow labels. Smaller visual mass, thinnest line of
   text — arrives first and fastest, shorter travel than body copy (8px,
   not 12px — 12px reads oversized for a single thin line). Compound
   selector so every existing `.eyebrow.reveal` in the markup picks this
   up automatically, no per-instance class needed. */
.eyebrow.reveal {
  transform: translateY(8px);
  transition-duration: var(--dur-kicker);
}
/* Explicit modifier, kept for parity with the spec and for any future
   kicker-role element that doesn't carry `.eyebrow`. */
.reveal.reveal--kicker {
  transform: translateY(8px);
  transition-duration: var(--dur-kicker);
}

/* Mask reveal — wraps a heading-display element. The wrapper never moves
   or fades; it only clips (a static overflow:hidden box). The heading
   inside is the thing that travels, transform-only — satisfies the
   transform/opacity-only performance constraint while producing the same
   "curtain rising from the bottom" result as an animated clip-path,
   without the ambiguous-compositing risk of animating clip-path itself. */
.reveal.reveal-veil {
  opacity: 1;
  transform: none;
  overflow: hidden;
  transition: none;
}
.reveal.reveal-veil > * {
  display: block;
  transform: translateY(100%);
  transition: transform var(--dur-veil) var(--ease-veil);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal.reveal-veil.is-visible > * {
  transform: translateY(0);
}

/* --------------------------------------------------------------------------
   20. Utilities
   -------------------------------------------------------------------------- */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.text-center { text-align: center; }
.mx-auto { margin-left: auto; margin-right: auto; }

/* --------------------------------------------------------------------------
   21. Status marks — divisiones sin fecha de apertura
   --------------------------------------------------------------------------
   Registro de marca, no insignia: versalitas Poppins muy espaciadas, sin
   fondo, sin color propio (hereda el del contexto). Se lee como una nota
   al margen del editor, no como una etiqueta promocional. Usadas en el
   sub-hero, la navegación (desktop + panel móvil), el pie de página y la
   retícula de divisiones del inicio. La misma pieza en los seis sitios:
   si una superficie dice "Próximamente" y otra no, la peor de las dos
   manda.
   -------------------------------------------------------------------------- */
.status-mark {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: inherit;
  opacity: 0.72;
  white-space: nowrap;
}
/* Dentro de un .eyebrow (sub-hero): el gap del inline-flex ya separa; la
   hairline izquierda lo distingue del propio texto del eyebrow sin
   introducir un cuarto color. */
.eyebrow .status-mark {
  padding-left: var(--space-2);
  border-left: 1px solid currentColor;
}

/* Navegación desktop — micro-etiqueta bajo el enlace. Absoluta a propósito:
   inline ensancharía el item ~70px y con seis items el nav se desborda por
   debajo de 1024px. El nav-bar tiene 70px de alto y el enlace ~26px
   centrado, así que hay holgura de sobra bajo la línea base. */
.nav-link--soon {
  position: relative;
}
.nav-link__soon {
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 8px;
  letter-spacing: 0.16em;
  font-weight: 500;
  text-transform: uppercase;
  opacity: 0.6;
  white-space: nowrap;
  pointer-events: none;
}

/* Panel móvil — aquí sí cabe en línea: los enlaces son de 20px y ocupan
   una fila entera cada uno. */
.nav-panel__soon {
  display: block;
  margin-top: 4px;
  font-size: 9px;
  letter-spacing: 0.24em;
  font-weight: 500;
  text-transform: uppercase;
  opacity: 0.6;
}

/* Pie de página — el enlace y su marca comparten fila. */
.footer-links .footer-link-row {
  display: flex;
  align-items: baseline;
  /* La columna del pie es estrecha: la etiqueta larga en español envuelve
     casi siempre. Row-gap corto para que, al envolver, la marca quede
     pegada a su enlace y no flote como una línea suelta de la lista. */
  gap: 2px var(--space-2);
  flex-wrap: wrap;
}
.footer-links .footer-link-row .status-mark {
  font-size: 9px;
  letter-spacing: 0.24em;
  color: var(--crema);
  /* 0.6 dejaba la mezcla en 4.27:1 sobre expreso — por debajo de AA para
     texto de 9 px. 0.75 da 5.89:1 y sigue leyéndose subordinada al enlace. */
  opacity: 0.75;
}

/* Retícula de divisiones del inicio — la línea de estado va bajo el número
   de división, en la misma familia tipográfica que él. Sin punto de color,
   sin pastilla: la jerarquía la dicta la palabra, no el adorno. La
   variante --soon retrocede un paso de opacidad; es toda la diferencia
   que necesita. */
.division-card__status {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 0.625rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--expreso);
  /* 10 px sobre crema: 0.62 daba 3.69:1. 0.78 da 5.4:1. El fondo más claro
     de los dos en que vive este componente (crema) es el que manda. */
  opacity: 0.78;
  margin-top: -14px;
}
.division-card__status--soon {
  /* El estado no se marca bajando la tinta hasta lo ilegible: 0.42 era
     2.6:1. La diferencia con el estado activo la carga la palabra, no el
     gris. 0.72 sobre crema = 4.7:1. */
  opacity: 0.72;
}

/* Los títulos display sobre expreso van en blanco — antes vivía como
   style inline en index.html. */
.section--expreso .heading-display {
  color: var(--blanco);
}

/* --------------------------------------------------------------------------
   22. Labio — página de anticipación (.anticipation / .waitlist)
   --------------------------------------------------------------------------
   La división no está abierta y no tiene fecha. La página no es una ficha
   de servicio: es una página de preparación. Por eso no reutiliza ni
   .process (no hay sesión que cronometrar) ni .commission (no hay encargo
   que tomar) ni .trust-grid (el rigor se argumenta en prosa, no en cinco
   iconos).
   -------------------------------------------------------------------------- */
.anticipation {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1024px) {
  .anticipation {
    grid-template-columns: 0.85fr 1.15fr;
    gap: var(--space-7);
    align-items: start;
  }
  /* La declaración es corta y las notas son largas: sin sticky la columna
     izquierda deja una zanja vacía de media pantalla. Sticky (no es
     animación, no toca transform ni opacity) la mantiene a la vista
     mientras las notas pasan por delante. */
  .anticipation__statement {
    position: sticky;
    top: 120px;
  }
}
.anticipation__statement {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.35;
  color: var(--expreso);
  max-width: 24ch;
}
.anticipation__notes {
  display: grid;
  gap: var(--space-4);
}
.anticipation__note {
  max-width: 48ch;
  color: var(--expreso);
  line-height: 1.65;
}
.anticipation__note-title {
  display: block;
  font-family: var(--ff-body);
  font-weight: 600;
  margin-bottom: 6px;
}
.anticipation__note-body {
  opacity: 0.85;
}

/* Lista de espera — ficha de registro sobre crema. Blanca y encuadrada a
   propósito: se lee como un libro de registro abierto sobre la mesa del
   atelier, no como un formulario de consuelo. */
.waitlist {
  background: var(--blanco);
  border: 1px solid var(--expreso-12);
  padding: var(--space-5) var(--space-4);
  max-width: 720px;
}
@media (min-width: 768px) {
  .waitlist {
    padding: var(--space-6);
  }
}
.waitlist__title {
  color: var(--expreso);
  margin-bottom: var(--space-3);
}
.waitlist__body {
  color: var(--expreso);
  line-height: 1.65;
  max-width: 52ch;
}
.waitlist__cta {
  margin-top: var(--space-4);
}
.waitlist__reason {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--expreso-12);
  font-size: var(--fs-meta);
  color: var(--expreso-70);
  max-width: 52ch;
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   23. Capilar — discreción y objeciones (.discretion / .objection)
   --------------------------------------------------------------------------
   Comprador distinto: lo que evalúa no es la obra sino si se nota. No hay
   galería ni línea de tiempo en esta página — hay comparación de criterio
   y respuesta directa a la objeción.
   -------------------------------------------------------------------------- */
.discretion {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 768px) {
  .discretion {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-5);
  }
}
.discretion__col {
  border-top: 2px solid var(--expreso);
  padding-top: var(--space-3);
}
.discretion__col--tell {
  border-top-color: var(--expreso-30);
}
.discretion__label {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
}
.discretion__col--tell .discretion__label {
  opacity: 0.7;
}
.discretion__list {
  list-style: none;
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-3);
}
.discretion__list li {
  position: relative;
  padding-left: var(--space-3);
  color: var(--expreso);
  line-height: 1.6;
}
.discretion__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.7em;
  width: 12px;
  height: 1px;
  background: var(--expreso);
  opacity: 0.5;
}
.discretion__col--tell .discretion__list li {
  opacity: 0.75;
}

.objection {
  display: grid;
  gap: 0;
}
.objection__item {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) 0;
  border-top: 1px solid var(--expreso-30);
}
.objection__item:last-child {
  border-bottom: 1px solid var(--expreso-30);
}
@media (min-width: 1024px) {
  .objection__item {
    grid-template-columns: 0.8fr 1.2fr;
    gap: var(--space-5);
    align-items: start;
  }
}
.objection__q {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--expreso);
}
.objection__a {
  color: var(--expreso);
  opacity: 0.85;
  line-height: 1.65;
  max-width: 54ch;
}

/* --------------------------------------------------------------------------
   24. Tatuajes — autoría (.authorship)
   --------------------------------------------------------------------------
   Lo que se decide en esta división es confiarle la piel a una mano
   concreta. Ninguna otra página necesita nombrarla; ésta sí.
   -------------------------------------------------------------------------- */
.authorship {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1024px) {
  .authorship {
    grid-template-columns: 1.15fr 0.85fr;
    gap: var(--space-7);
    align-items: start;
  }
}
/* La cita ES el título de la sección — tamaño y color los pone
   .heading-display--h2 + .section--expreso; aquí sólo la medida. */
.authorship__quote {
  line-height: 1.15;
  max-width: 20ch;
}
.authorship__body {
  color: var(--crema);
  opacity: 0.9;
  line-height: 1.65;
  max-width: 48ch;
}
.authorship__sign {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid rgba(214, 196, 176, 0.24);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--crema);
  opacity: 0.8;
}

/* Enlaces dentro de notas de formulario */
.form-note a {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Enlaces dentro de bloques de datos — teléfono, correo, mapa.
   Sin subrayado no se distinguían del texto que los rodea y el teléfono es
   justamente lo que hay que poder tocar desde el móvil. */
.facts__val a,
.location-item dd a,
.axis__desc a,
.ledger__desc a {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.axis__desc a {
  color: var(--crema);
}

/* --------------------------------------------------------------------------
   26. Navegación v2 — desplegables desktop + acordeón móvil
   --------------------------------------------------------------------------
   El árbol pasa de 6 ítems sueltos a 5 padres + botón. El breakpoint de la
   barra sube de 900 a 1080 px (ver §12 más abajo): con MICROPIGMENTACIÓN en
   la lista, los 6 ítems medidos ya no caben por debajo de ~1000 px y es
   preferible una hamburguesa a 1000 px que cinco desplegables amontonados.
   Panel en BLANCO con hairline expreso al 12 %, sin sombra (regla v1).
   -------------------------------------------------------------------------- */
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
}

/* El disparador comparte tipografía y subrayado con .nav-link. Cuando es un
   <button> hay que anular los estilos de agente de usuario a mano. */
.nav-link--parent {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  font: inherit;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: inherit;
  cursor: pointer;
  padding: 4px 0;
}
.nav-link__caret {
  width: 9px;
  height: 9px;
  flex: none;
  transition: transform 220ms var(--ease);
}
.nav-item.is-open .nav-link__caret {
  transform: rotate(180deg);
}

.nav-drop {
  position: absolute;
  top: 100%;
  left: -18px;
  margin-top: 14px;
  min-width: 232px;
  background: var(--blanco);
  border: 1px solid var(--expreso-12);
  padding: 10px 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms var(--ease), visibility 0s linear 120ms;
  z-index: 120;
}
.nav-item.is-open .nav-drop {
  opacity: 1;
  visibility: visible;
  transition: opacity 120ms var(--ease), visibility 0s;
}
/* Puente invisible entre el disparador y el panel: sin él, el hueco de
   14 px cierra el desplegable a mitad de recorrido del cursor. */
.nav-drop::before {
  content: "";
  position: absolute;
  top: -16px;
  left: 0;
  right: 0;
  height: 16px;
}
.nav-drop__link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 9px 18px;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 12.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--expreso);
  transition: background 180ms var(--ease);
}
.nav-drop__link:hover,
.nav-drop__link:focus-visible {
  background: var(--expreso-06);
}
.nav-drop__link.is-active {
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* PRÓXIMAMENTE inline a la derecha de la fila — deja de colgar bajo la
   palabra como descendente huérfano. */
.nav-drop__soon {
  flex: none;
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--expreso);
  /* 0.55 sobre blanco = 3.71:1, falla AA a 9 px. 0.72 = 6.41:1. */
  opacity: 0.72;
}

/* Botón RESERVAR de la barra. Sobre hero transparente va en crema; sobre la
   barra sólida, en expreso. Es la única acción persistente del sitio. */
.nav-cta {
  display: none;
  align-items: center;
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 10px 20px;
  background: var(--expreso);
  color: var(--blanco);
  border: 1px solid var(--expreso);
  transition: background 220ms var(--ease), color 220ms var(--ease),
    border-color 220ms var(--ease);
}
.nav-cta:hover {
  background: transparent;
  color: var(--expreso);
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-cta {
  background: var(--crema);
  color: var(--expreso);
  border-color: var(--crema);
}
.site-header:not(.is-scrolled):not(.is-solid-default) .nav-cta:hover {
  background: transparent;
  color: var(--crema);
}
@media (min-width: 600px) {
  .nav-cta {
    display: inline-flex;
  }
}

/* Barra desktop: el ancho medido de la lista nueva (~650 px) más isotipo,
   selector y botón pide ~1.000 px de mínimo. 1080 deja respiración. */
@media (min-width: 1080px) {
  .nav-list {
    display: flex;
    gap: var(--space-4);
  }
  .nav-toggle {
    display: none;
  }
  .nav-panel,
  .nav-scrim {
    display: none;
  }
  .lang-toggle {
    display: flex;
  }
}

/* Panel móvil — acordeón. MICROPIGMENTACIÓN es enlace + acordeón; TATUAJES,
   EL ENCARGO y ATELIER son cabeceras de sección no enlazables (no tienen
   página propia: son agrupadores). */
.nav-panel__group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.nav-panel__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.nav-panel__acc {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: var(--space-2);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 20px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crema);
  text-align: left;
  cursor: pointer;
}
.nav-panel__acc-btn {
  flex: none;
  background: none;
  border: 0;
  padding: 6px;
  margin: -6px;
  color: var(--crema);
  cursor: pointer;
  line-height: 0;
}
.nav-panel__caret {
  width: 12px;
  height: 12px;
  flex: none;
  transition: transform 220ms var(--ease);
}
.nav-panel__group.is-open .nav-panel__caret {
  transform: rotate(180deg);
}
.nav-panel__sub {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-left: var(--space-2);
  border-left: 1px solid var(--crema-30);
}
.nav-panel__sub a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-family: var(--ff-body);
  font-weight: 400;
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crema);
  opacity: 0.85;
}
.nav-panel__sub a:hover {
  opacity: 1;
}
.nav-panel__sub .status-mark {
  position: static;
  font-size: 9px;
  letter-spacing: 0.24em;
  color: var(--crema);
  opacity: 0.75;
}
/* Reservar dentro del panel: última fila del listado, encuadrada para que
   se lea como acción y no como un enlace más. */
.nav-panel__link--cta {
  display: inline-block;
  border: 1px solid var(--crema);
  padding: 10px 18px;
  font-size: 15px;
}
.nav-panel__list {
  overflow-y: auto;
}

/* --------------------------------------------------------------------------
   27. Cabecera reducida — páginas de aterrizaje
   --------------------------------------------------------------------------
   Una página de aterrizaje no reparte salidas: isotipo, selector de idioma y
   el botón. Sin menú, sin hamburguesa, sin panel.
   -------------------------------------------------------------------------- */
.site-header--landing .nav-right {
  gap: var(--space-3);
}
.site-header--landing .lang-toggle {
  display: flex;
}
.site-header--landing .nav-cta {
  display: inline-flex;
}

/* --------------------------------------------------------------------------
   28. Índice de categoría — libro de estado de las divisiones
   --------------------------------------------------------------------------
   No es una rejilla de tarjetas: es un registro. Tres filas, cada una con su
   estado declarado. La comparación entre las dos listas de espera se dice
   aquí y en ningún otro sitio.
   -------------------------------------------------------------------------- */
.ledger {
  border-top: 1px solid var(--expreso-30);
}
.ledger__row {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--expreso-30);
}
@media (min-width: 900px) {
  .ledger__row {
    grid-template-columns: 0.9fr 1.4fr auto;
    gap: var(--space-4);
    align-items: baseline;
  }
}
.ledger__name {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--expreso);
}
.ledger__desc {
  color: var(--expreso);
  opacity: 0.85;
  line-height: 1.65;
  max-width: 52ch;
}
.ledger__state {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--expreso);
  white-space: nowrap;
}
.ledger__state--soon {
  /* Mismo cálculo que .nav-drop__soon: 0.55 sobre blanco no llega a AA. */
  opacity: 0.72;
}
.ledger__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--space-1);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--expreso);
  border-bottom: 1px solid var(--expreso-30);
  padding-bottom: 3px;
  transition: border-color 220ms var(--ease);
}
.ledger__link:hover {
  border-color: var(--expreso);
}
.ledger__link svg {
  width: 15px;
  height: 15px;
  transition: transform 220ms var(--ease);
}
.ledger__link:hover svg {
  transform: translateX(3px);
}

/* --------------------------------------------------------------------------
   29. Lista de espera — modificador oscuro (cejas)
   --------------------------------------------------------------------------
   Mismo componente que labio, relleno invertido: labio es tarjeta blanca
   sobre crema, cejas es tarjeta expreso sobre blanco. El estado no lo carga
   un gris ni una opacidad: lo cargan la marca de estado y la ausencia del
   CTA de reserva.
   -------------------------------------------------------------------------- */
.waitlist--dark {
  background: var(--expreso);
  border-color: var(--expreso);
  color: var(--crema);
}
.waitlist--dark .waitlist__title {
  color: var(--blanco);
}
.waitlist--dark .waitlist__body {
  color: var(--crema);
}
.waitlist--dark .waitlist__reason {
  color: var(--crema);
  opacity: 0.8;
  border-top-color: rgba(214, 196, 176, 0.24);
}

/* --------------------------------------------------------------------------
   30. El encargo — ejes de alcance
   --------------------------------------------------------------------------
   La pregunta del dinero se contesta con gravedad: banda expresa, cinco
   ejes numerados, sin tarifa, sin "desde $", sin calculadora. Lo que se da
   es lo que mueve el alcance, que es lo que la persona necesita para saber
   si está en el rango correcto.
   -------------------------------------------------------------------------- */
.axes {
  display: grid;
  gap: 0;
  border-top: 1px solid rgba(214, 196, 176, 0.24);
}
.axis {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4) 0;
  border-bottom: 1px solid rgba(214, 196, 176, 0.24);
}
@media (min-width: 900px) {
  .axis {
    grid-template-columns: 56px 0.8fr 1.4fr;
    gap: var(--space-4);
    align-items: baseline;
  }
}
.axis__number {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.22em;
  color: var(--crema);
  /* 0.6 sobre expreso = 4.27:1. 0.7 = 5.31:1. */
  opacity: 0.7;
}
.axis__name {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--blanco);
}
.axis__desc {
  color: var(--crema);
  opacity: 0.88;
  line-height: 1.65;
  max-width: 54ch;
}

/* --------------------------------------------------------------------------
   31. Cuidado posterior — pasos críticos y bloques por disciplina
   --------------------------------------------------------------------------
   Contexto de lectura distinto al del resto del sitio: alguien que vuelve
   desde el móvil con la piel recién trabajada. Cuerpo un punto
   más grande, lo urgente arriba y sin animación de entrada (esa parte la
   pone el HTML: la sección 1 no lleva .reveal).
   -------------------------------------------------------------------------- */
.care-steps {
  display: grid;
  gap: var(--space-4);
  counter-reset: care;
}
@media (min-width: 768px) {
  .care-steps {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5);
  }
}
.care-step {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: var(--space-3);
  align-items: start;
}
.care-step__number {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--expreso);
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: 14px;
  color: var(--expreso);
}
.care-step__title {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: calc(var(--fs-body) + 1px);
  color: var(--expreso);
  margin-bottom: 4px;
}
.care-step__desc {
  font-size: calc(var(--fs-body) + 1px);
  line-height: 1.7;
  color: var(--expreso);
  max-width: 46ch;
}

.care-split {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 900px) {
  .care-split {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
  }
}
.care-block {
  border-top: 2px solid var(--expreso);
  padding-top: var(--space-3);
}
.care-block__label {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
  margin-bottom: var(--space-3);
}
.care-list {
  list-style: none;
  display: grid;
  gap: var(--space-3);
}
.care-list li {
  position: relative;
  padding-left: var(--space-3);
  color: var(--expreso);
  line-height: 1.7;
  font-size: calc(var(--fs-body) + 1px);
}
.care-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.75em;
  width: 12px;
  height: 1px;
  background: var(--expreso);
  opacity: 0.5;
}
/* Señales de alarma — encuadrada, sin color de alerta. La paleta son tres
   tonos y no se añade un cuarto ni siquiera para una advertencia: el peso
   lo pone el marco, no un rojo. */
.care-alert {
  margin-top: var(--space-5);
  border: 1px solid var(--expreso);
  padding: var(--space-4);
  background: var(--blanco);
}
.care-alert__title {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: calc(var(--fs-body) + 1px);
  color: var(--expreso);
  margin-bottom: var(--space-2);
}

.care-alert__note {
  margin-top: var(--space-3);
  color: var(--expreso);
  line-height: 1.7;
  font-size: calc(var(--fs-body) + 1px);
}

/* --------------------------------------------------------------------------
   32. Galería — rejilla de obra + filtro de categoría
   --------------------------------------------------------------------------
   Prueba, no persuasión. Piezas reales del portafolio. Filtro por
   categoría: 5 chips fijas — Todo · Línea fina · Botánico · Realismo ·
   Micro-realismo — sin
   pill/radondeado: --radius (2px) es el mismo de botones y tarjetas en
   todo el sistema, esta UI no inaugura una curva nueva.
   -------------------------------------------------------------------------- */
.gallery-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 768px) {
  .gallery-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-4);
  }
}
.gallery-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: var(--space-4) 0 var(--space-5);
}
.gallery-filter {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 9px 18px;
  border: 1px solid var(--expreso-30);
  border-radius: var(--radius);
  background: transparent;
  color: var(--expreso);
  cursor: pointer;
  transition: background 200ms var(--ease), color 200ms var(--ease),
    border-color 200ms var(--ease);
}
.gallery-filter:hover {
  border-color: var(--expreso);
}
.gallery-filter[aria-pressed="true"] {
  background: var(--expreso);
  color: var(--crema);
  border-color: var(--expreso);
}
/* Estado vacío para cualquier categoría del filtro que todavía no tenga
   ninguna pieza publicada. Oculto por defecto; main.js lo revela cuando esa
   chip queda activa y no hay ningún .gallery-slot visible. */
.gallery-empty-state {
  grid-column: 1 / -1;
  color: var(--expreso);
  opacity: 0.7;
  font-size: var(--fs-body);
  padding: var(--space-4) 0;
}
.gallery-slot {
  position: relative;
}
.gallery-slot__caption {
  margin-top: 10px;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--expreso);
  opacity: 0.72;
}
.gallery-note {
  max-width: 60ch;
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--expreso-12);
  font-size: var(--fs-meta);
  color: var(--expreso);
  opacity: 0.75;
  line-height: 1.65;
}

/* --------------------------------------------------------------------------
   33. Diario — índice fechado y entrada
   --------------------------------------------------------------------------
   Sin categorías, sin etiquetas, sin buscador y sin paginación hasta pasar
   de 24 entradas. Las entradas están fechadas y la más nueva manda.
   -------------------------------------------------------------------------- */
.journal-list {
  border-top: 1px solid var(--expreso-30);
}
.journal-item {
  display: grid;
  gap: 6px;
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--expreso-30);
}
@media (min-width: 900px) {
  .journal-item {
    grid-template-columns: 150px 120px 1fr;
    gap: var(--space-4);
    align-items: baseline;
  }
}
.journal-item__date,
.journal-item__kind {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
  opacity: 0.7;
}
.journal-item__title {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--expreso);
}
.journal-empty {
  border: 1px solid var(--expreso-12);
  padding: var(--space-5) var(--space-4);
  max-width: 640px;
}
@media (min-width: 768px) {
  .journal-empty {
    padding: var(--space-6);
  }
}
.journal-empty__body {
  color: var(--expreso);
  line-height: 1.65;
  max-width: 52ch;
}

.entry {
  max-width: 680px;
}
.entry__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
  opacity: 0.7;
  margin-bottom: var(--space-3);
}
.entry__body > p {
  color: var(--expreso);
  line-height: 1.75;
  margin-bottom: var(--space-3);
}
/* Las entradas de largo formato del diario
   (arquetipo Nota, pero con subtítulos H2) — el molde de plantilla-entrada
   solo preveía párrafos sueltos. Mismo tratamiento de título que .door__name
   / .axis__name: font-family display, sin heading-display--h2 (ese es para
   títulos de sección, no de subtítulo dentro de cuerpo de texto). */
.entry__body h2 {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.3;
  color: var(--expreso);
  margin: var(--space-5) 0 var(--space-2);
}
.entry__body .lede {
  margin-bottom: var(--space-4);
}
.entry__body .btn {
  margin-top: var(--space-2);
}
.entry__body ul {
  margin: 0 0 var(--space-3);
  padding-left: 1.3em;
  color: var(--expreso);
  line-height: 1.75;
}
.entry__body ul li {
  margin-bottom: var(--space-1);
}
.entry__body ul li:last-child {
  margin-bottom: 0;
}
.entry__figure {
  margin: var(--space-4) 0;
}
.entry__figure img,
.entry__figure video {
  width: 100%;
  height: auto;
  display: block;
}
/* Foto real reutilizada de la galería (mismo criterio de "anticipo" que ya
   usa el home con tatuaje-01/capilar-03) — se limita el ancho para que no
   domine el arranque de una entrada de texto largo. */
.entry__figure--photo {
  max-width: 340px;
}
.entry__transcript {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--expreso-12);
  font-size: var(--fs-meta);
  color: var(--expreso);
  opacity: 0.8;
  line-height: 1.7;
}
.entry__back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--space-5);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--expreso);
  border-bottom: 1px solid var(--expreso-30);
  padding-bottom: 3px;
}

/* --------------------------------------------------------------------------
   34. Ubicación — dato duro y llegada
   --------------------------------------------------------------------------
   El dato primero: dirección, piso, horario y contacto antes que cualquier
   argumento. La foto real de la señalética se recorta al cartel, nunca a
   sangre. "Cómo llegar" enlaza a Maps — sin iframe, regla v1 vigente.
   -------------------------------------------------------------------------- */
.facts {
  display: grid;
  gap: 0;
  border-top: 1px solid var(--expreso-30);
}
.facts__row {
  display: grid;
  gap: 4px;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--expreso-30);
}
@media (min-width: 768px) {
  .facts__row {
    grid-template-columns: 190px 1fr;
    gap: var(--space-4);
    align-items: baseline;
  }
}
.facts__key {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
  /* .facts vive en dos superficies: crema en ubicacion, blanco en
     melody-goris. 0.65 daba 5.1:1 sobre blanco pero sólo 3.98:1 sobre crema
     —el fondo más claro es el que manda—. 0.8 deja 5.8:1 sobre crema y
     8.4:1 sobre blanco. */
  opacity: 0.8;
}
.facts__val {
  color: var(--expreso);
  line-height: 1.65;
  font-size: calc(var(--fs-body) + 1px);
}

.arrival {
  display: grid;
  gap: var(--space-3);
  counter-reset: arrival;
}
.arrival__step {
  display: grid;
  grid-template-columns: 28px 1fr;
  gap: var(--space-3);
  align-items: start;
}
.arrival__bullet {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 1px solid var(--expreso-30);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: 12px;
  color: var(--expreso);
}
.arrival__text {
  color: var(--expreso);
  line-height: 1.7;
  max-width: 48ch;
}

/* --------------------------------------------------------------------------
   35. Página de aterrizaje — tres puertas
   --------------------------------------------------------------------------
   No cuatro tarjetas iguales: tres puertas de tatuaje y una línea discreta
   al pie que enlaza micropigmentación capilar. Quien entra por esta página
   llega buscando tatuaje, así que ésa es la jerarquía.
   -------------------------------------------------------------------------- */
.doors {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 900px) {
  .doors {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-5);
  }
}
.door {
  border-top: 2px solid var(--expreso);
  padding-top: var(--space-3);
}
.door__name {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--expreso);
  margin-bottom: var(--space-2);
}
.door__desc {
  color: var(--expreso);
  opacity: 0.85;
  line-height: 1.65;
}
.door-aside {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--expreso-12);
  font-size: var(--fs-meta);
  color: var(--expreso);
  opacity: 0.8;
}
.door-aside a {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Página de aterrizaje: el hero es de conversión, no de marca — la altura
   completa de pantalla del inicio empujaría el formulario demasiado abajo
   para una primera visita. */
.hero--landing {
  min-height: 0;
  padding-block: 140px var(--space-6);
  align-items: flex-end;
}
@media (min-width: 768px) {
  .hero--landing {
    min-height: 78svh;
    padding-block: 160px var(--space-7);
    align-items: center;
  }
}
.hero--landing .hero__title {
  max-width: 18ch;
}

/* Formulario de la landing sobre crema: tarjeta blanca, y el bloque de
   WhatsApp con igual peso visual (no una nota al pie). */
.section--crema .form-card,
.section--crema .whatsapp-card {
  background: var(--blanco);
}

/* --------------------------------------------------------------------------
   36. Utilidades de composición del titular
   --------------------------------------------------------------------------
   `Santo Domingo.` viaja entero a cualquier ancho: un topónimo partido a
   mitad lee como accidente de maquetación. Garantía dura, no dependiente
   del breakpoint.
   -------------------------------------------------------------------------- */
.nowrap {
  white-space: nowrap;
}
/* Corte controlado del H1. Se hace con <br class="br-*"> y no con
   text-wrap:balance: balance busca renglones de largo parecido, que es
   justo el rag en cuña que queremos evitar. */
.br-sm {
  display: inline;
}
.br-md {
  display: none;
}
/* Trozos de texto que cambian de renglón según el ancho. El H1 en inglés los
   necesita: la preposición "in" tiene que ir con la ciudad a partir de 768 px
   (`IN SANTO DOMINGO.`) pero por debajo no cabe en el mismo renglón que
   `Santo Domingo.`, y dejarla suelta en un renglón propio es la preposición
   huérfana que este H1 justamente evita. Así viaja al final del renglón 2 en
   móvil y encabeza el renglón 2 en desktop. */
.only-sm {
  display: inline;
}
.only-md {
  display: none;
}
@media (min-width: 768px) {
  .br-sm {
    display: none;
  }
  .br-md {
    display: inline;
  }
  .only-sm {
    display: none;
  }
  .only-md {
    display: inline;
  }
}

/* --------------------------------------------------------------------------
   37. Impresión — cuidado posterior
   --------------------------------------------------------------------------
   Es la página que se imprime o se guarda en captura. NO se usa
   `* { transform: none }`: eso rompe cualquier centrado por translate y
   desplaza medio elemento en el PDF. Se apagan sólo los transforms de
   reveal, que es lo único que aquí los usa.
   -------------------------------------------------------------------------- */
@media print {
  .site-header,
  .nav-panel,
  .nav-scrim,
  .cta-band,
  .site-footer,
  .skip-link,
  .wa-float,
  .map-embed {
    display: none !important;
  }
  /* El botón de Instagram y el pop-up tampoco se imprimen.
     Van en reglas propias y NO añadidos al grupo de arriba, a propósito:
     .ig-float no debe compartir ningún selector con .wa-float en toda la
     hoja, porque .wa-float es disparador de generate_lead en GTM y la regla
     de oro de este lote es que ningún selector pueda alcanzar a los dos. La
     duplicación es barata; el falso positivo de conversión, no. */
  .ig-float {
    display: none !important;
  }
  .gate {
    display: none !important;
  }
  .sub-hero {
    padding: 0 0 24px;
    background: none;
    color: #000;
  }
  .sub-hero::before,
  .sub-hero__bg {
    display: none;
  }
  .sub-hero__title,
  .sub-hero__desc,
  .sub-hero__eyebrow {
    color: #000;
  }
  .section {
    padding: 24px 0;
    background: none !important;
  }
  .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
  .reveal.reveal-veil > * {
    transform: none !important;
  }
  .care-alert {
    border: 1px solid #000;
  }
  .care-split {
    grid-template-columns: 1fr;
  }
  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 10px;
  }
}


/* --------------------------------------------------------------------------
   38. Respiro — declaración en Velista dentro del cuerpo (.statement)
   --------------------------------------------------------------------------
   No es una sección: es una pausa DENTRO de una sección. Se añade porque la
   pauta editorial pide romper el ritmo de cuerpo cada dos o tres bloques sin
   inventar secciones nuevas — el mapa tonal de §11 (R1–R4) cuenta secciones,
   así que un arquetipo nuevo de sección habría reescrito las 15 cadenas.
   Hereda el hairline de 28 px de .eyebrow::before para no introducir un
   recurso gráfico que la marca no tenga ya.
   -------------------------------------------------------------------------- */
.statement {
  font-family: var(--ff-display);
  font-weight: 400;
  font-style: normal; /* Velista no tiene itálica — nunca se finge */
  font-size: clamp(1.375rem, 1.05rem + 1.5vw, 2.125rem);
  line-height: 1.25;
  letter-spacing: 0.005em;
  color: var(--expreso);
  max-width: 22ch;
  margin: var(--space-6) 0;
}
.statement::before {
  content: "";
  display: block;
  width: 28px;
  height: 1px;
  background: var(--expreso-30);
  margin-bottom: var(--space-3);
}
.section--expreso .statement,
.statement--on-dark {
  color: var(--crema);
}
.section--expreso .statement::before,
.statement--on-dark::before {
  background: var(--crema-50);
}

/* --------------------------------------------------------------------------
   39. Retrato — primer activo humano real del sitio (.portrait)
   --------------------------------------------------------------------------
   829×1035 (4:5), el mismo ratio que texture-slot, así que el retrato entra
   en el sistema en lugar de abrir uno nuevo. Marco con hairline como
   .espacio__figure; sin parallax (el sujeto es una cara: moverla la abarata).
   -------------------------------------------------------------------------- */
.portrait {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}
@media (min-width: 900px) {
  .portrait {
    grid-template-columns: 0.85fr 1.15fr;
    gap: var(--space-6);
  }
  .portrait--right {
    grid-template-columns: 1.15fr 0.85fr;
  }
  .portrait--right .portrait__figure {
    order: 2;
    margin-left: auto;
  }
}
.portrait__figure {
  margin: 0;
  max-width: 420px;
  width: 100%;
  border: 1px solid var(--expreso-12);
  overflow: hidden;
}
.portrait__figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 829 / 1035;
  object-fit: cover;
}
.portrait__caption {
  margin-top: var(--space-2);
  font-size: var(--fs-meta);
  color: var(--expreso-70);
  line-height: 1.5;
}
.section--expreso .portrait__figure {
  border-color: var(--crema-30);
}
.section--expreso .portrait__caption {
  color: var(--crema);
  opacity: 0.75;
}

/* --------------------------------------------------------------------------
   40. Instagram como superficie (.ig-panel / .ig-line)
   --------------------------------------------------------------------------
   Mientras no haya galería con obra, Instagram ES la galería. Enlace tratado
   con diseño, NUNCA widget: incrustar el feed son scripts de terceros, rompe
   la política de autocontenido y cuesta LCP. Regla §12.4 del memo intacta.
   -------------------------------------------------------------------------- */
.ig-panel {
  display: block;
  background: var(--expreso);
  border: 1px solid var(--expreso);
  color: var(--crema);
  padding: var(--space-5) var(--space-4);
  transition: background 260ms var(--ease);
}
@media (min-width: 768px) {
  .ig-panel {
    padding: var(--space-6);
  }
}
.ig-panel:hover,
.ig-panel:focus-visible {
  background: #241609;
}
.ig-panel__label {
  display: block;
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--crema);
  opacity: 0.75;
}
.ig-panel__title {
  display: block;
  margin-top: var(--space-2);
  color: var(--blanco);
  max-width: 18ch;
}
.ig-panel__desc {
  display: block;
  margin-top: var(--space-3);
  max-width: 52ch;
  line-height: 1.65;
  color: var(--crema);
  opacity: 0.9;
}
.ig-panel__handle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid rgba(214, 196, 176, 0.24);
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  color: var(--blanco);
}
.ig-panel__handle svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  transition: transform 260ms var(--ease);
}
.ig-panel:hover .ig-panel__handle svg {
  transform: translateX(4px);
}

/* Remate de una línea — inicio, dentro de la cta-band. */
.ig-line {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid rgba(214, 196, 176, 0.24);
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  color: var(--crema);
}
.ig-line:hover,
.ig-line:focus-visible {
  color: var(--blanco);
}
.ig-line svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  transition: transform 260ms var(--ease);
}
.ig-line:hover svg {
  transform: translateX(4px);
}

/* --------------------------------------------------------------------------
   41. Mapa — fachada estática que carga el iframe al hacer clic (.map-embed)
   --------------------------------------------------------------------------
   El mapa entra como fachada estática, nunca como iframe de carga directa.
   Ésa es la forma que respeta el presupuesto de red: CERO peticiones a
   Google hasta que el visitante pulsa. Sin clic no hay iframe en el DOM, así
   que no hay coste de red, ni de LCP, ni cookie de terceros al cargar.
   -------------------------------------------------------------------------- */
.map-embed {
  position: relative;
  border: 1px solid var(--expreso-12);
  background: var(--expreso);
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
@media (min-width: 768px) {
  /* 2.2:1 y no 16:9: a 1200 px de contenedor, 16:9 son 675 px de agujero
     oscuro sin abrir. Sigue siendo un mapa legible al desplegarse. */
  .map-embed {
    aspect-ratio: 2.2 / 1;
  }
}
.map-embed__facade {
  position: absolute;
  inset: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--expreso);
  color: var(--crema);
  text-align: center;
  cursor: pointer;
  border: 0;
  transition: background 260ms var(--ease);
}
.map-embed__facade:hover {
  background: #241609;
}
.map-embed__mark {
  width: 40px;
  height: auto;
  opacity: 0.9;
}
.map-embed__label {
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--blanco);
}
.map-embed__note {
  font-family: var(--ff-body);
  font-size: var(--fs-meta);
  color: var(--crema);
  opacity: 0.7;
  max-width: 34ch;
  line-height: 1.5;
  text-transform: none;
  letter-spacing: 0;
}
.map-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.map-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* --------------------------------------------------------------------------
   42. WhatsApp persistente (.wa-float)
   --------------------------------------------------------------------------
   Registro de atelier, no de e-commerce: expreso sobre crema, pequeño, sin
   globo verde y sin icono de teléfono de marca. Nace oculto y aparece al
   bajar (main.js) — no salta encima de nadie al cargar. El prefill lo pone
   CADA página en su href: en labio y cejas es el de lista de espera, no el
   de reserva.

   WD-18/D5 (26-08): el memo prohíbe dos rellenos sólidos visibles a la vez, y
   con RESERVAR en el nav conviven dos. El botón persistente del sitio debe
   ser RESERVAR, así que este baja de relleno sólido (var(--expreso)) a
   translúcido (var(--expreso-70), token ya existente — el mismo scrim que
   garantiza contraste de texto sobre foto en otras secciones) con borde
   crema sólido. Sigue siendo legible fijo sobre cualquier fondo con el que
   se cruce al hacer scroll, pero ya no lee como un segundo CTA sólido. */
.wa-float {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  background: var(--expreso-70);
  color: var(--crema);
  border: 1px solid var(--crema);
  border-radius: var(--radius);
  font-family: var(--ff-body);
  font-weight: 600;
  font-size: var(--fs-meta);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: 0 2px 12px rgba(48, 30, 18, 0.18);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 320ms var(--ease), transform 320ms var(--ease),
    visibility 320ms var(--ease), background 260ms var(--ease);
}
@media (min-width: 768px) {
  .wa-float {
    right: 24px;
    bottom: 24px;
  }
}
.wa-float.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.wa-float:hover,
.wa-float:focus-visible {
  background: var(--crema);
  color: var(--expreso);
  border-color: var(--crema);
}
.wa-float svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   43. Prueba social — construida y comentada (.reviews)
   --------------------------------------------------------------------------
   Cero testimonios hoy: el atelier no ha abierto. La fuente natural son las
   reseñas del perfil de Google una vez esté abierto. Mismo precedente que el
   hueco de foto cicatrizada en capilar: el marcado queda definitivo y
   COMENTADO en melody-goris.html (ES/EN); el día que haya reseñas se
   descomenta y es un cambio de contenido, no un rediseño. Estas reglas viven
   aquí a propósito para que ese día no haya que tocar el CSS.
   -------------------------------------------------------------------------- */
.reviews {
  display: grid;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .reviews {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-4);
  }
}
.review {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--blanco);
  border: 1px solid var(--expreso-12);
}
.review__body {
  font-family: var(--ff-display);
  font-size: var(--fs-h3);
  line-height: 1.3;
  color: var(--expreso);
}
.review__meta {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--expreso-12);
  font-family: var(--ff-body);
  font-size: var(--fs-meta);
  color: var(--expreso-70);
}
.review__name {
  display: block;
  font-weight: 600;
  color: var(--expreso);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.reviews-note {
  margin-top: var(--space-4);
  font-size: var(--fs-meta);
  color: var(--expreso-70);
}


/* --------------------------------------------------------------------------
   44. Sub-hero — fondo fotográfico (.sub-hero__bg)
   --------------------------------------------------------------------------
   La restricción es literal: la banda NO cambia de altura. Por eso la imagen
   entra como capa `position:absolute; inset:0` y NO toca el padding, que sigue
   siendo 168px/208px exactamente igual que antes. Medido antes y después en
   todas las páginas con sub-hero: mismo alto al píxel.

   Orden de capas: __bg (0, foto + velo) · ::before (1, el radial crema que ya
   existía) · __inner (2, el texto).

   Fuente: renders de Higgsfield del roadmap de contenido — mundo sensorial,
   NUNCA obra tatuada fabricada. `gen-05-dj-night` queda fuera (no dialoga con
   "retiro") y `gen-01` va recortada por debajo del 40 % de su alto porque
   arriba hay un antebrazo con un tatuaje generado por IA, que el canon prohíbe.

   Recortes: dos por imagen, elegidos mirando cada una. Escritorio 1440x540
   (2,667:1, el ancho completo del original); móvil 760x840 (0,905:1), que es la
   proporción real de la banda a 390 px. Un recorte central ciego se comía la
   composición en 6 de las 9.
   -------------------------------------------------------------------------- */
.sub-hero__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: var(--expreso);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Velo. En móvil es plano y fuerte: el texto ocupa todo el ancho, así que no
   hay lado que proteger y sí un contraste que garantizar sobre cualquier píxel
   de la foto. Los valores exactos salen de medir el píxel real detrás de cada
   texto en cada página con sub-hero, no de estimarlos — ver el comentario de .cta-band. */
.sub-hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(48, 30, 18, 0.86) 0%,
    rgba(48, 30, 18, 0.82) 55%,
    rgba(48, 30, 18, 0.9) 100%
  );
}
@media (min-width: 768px) {
  /* En escritorio el texto se alinea a la izquierda y la foto respira a la
     derecha: velo pesado en la columna de texto, más ligero en el resto. */
  .sub-hero__bg::after {
    background: linear-gradient(
      100deg,
      rgba(48, 30, 18, 0.9) 0%,
      rgba(48, 30, 18, 0.86) 42%,
      rgba(48, 30, 18, 0.68) 100%
    );
  }
}
.sub-hero:has(.sub-hero__bg)::before {
  z-index: 1;
}
.sub-hero:has(.sub-hero__bg) .sub-hero__inner {
  z-index: 2;
}

/* Reparto por página. Móvil primero, escritorio a partir de 768px — el mismo
   breakpoint y el mismo patrón image-set() que .hero__bg. */
.sub-hero__bg--tatuajes { background-image: url("../img/subhero/sh-01-hands-inkset-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-01-hands-inkset-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-01-hands-inkset-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--diario { background-image: url("../img/subhero/sh-02-welcome-drinks-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-02-welcome-drinks-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-02-welcome-drinks-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--capilar { background-image: url("../img/subhero/sh-03-guest-station-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-03-guest-station-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-03-guest-station-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--labio { background-image: url("../img/subhero/sh-04-sculpture-bust-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-04-sculpture-bust-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-04-sculpture-bust-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--ubicacion { background-image: url("../img/subhero/sh-06-reception-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-06-reception-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-06-reception-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--galeria { background-image: url("../img/subhero/sh-07-lounge-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-07-lounge-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-07-lounge-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--encargo { background-image: url("../img/subhero/sh-08-design-table-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-08-design-table-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-08-design-table-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--cejas { background-image: url("../img/subhero/sh-09-texture-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-09-texture-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-09-texture-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--micropigmentacion { background-image: url("../img/subhero/sh-10-oct-sterile-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-10-oct-sterile-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-10-oct-sterile-mobile.jpg") type("image/jpeg") 1x); }
.sub-hero__bg--melody { background-image: url("../img/subhero/sh-melody-mobile.jpg"); background-image: image-set(url("../img/subhero/sh-melody-mobile.webp") type("image/webp") 1x, url("../img/subhero/sh-melody-mobile.jpg") type("image/jpeg") 1x); }

@media (min-width: 768px) {
  .sub-hero__bg--tatuajes { background-image: url("../img/subhero/sh-01-hands-inkset-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-01-hands-inkset-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-01-hands-inkset-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--diario { background-image: url("../img/subhero/sh-02-welcome-drinks-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-02-welcome-drinks-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-02-welcome-drinks-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--capilar { background-image: url("../img/subhero/sh-03-guest-station-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-03-guest-station-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-03-guest-station-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--labio { background-image: url("../img/subhero/sh-04-sculpture-bust-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-04-sculpture-bust-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-04-sculpture-bust-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--ubicacion { background-image: url("../img/subhero/sh-06-reception-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-06-reception-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-06-reception-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--galeria { background-image: url("../img/subhero/sh-07-lounge-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-07-lounge-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-07-lounge-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--encargo { background-image: url("../img/subhero/sh-08-design-table-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-08-design-table-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-08-design-table-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--cejas { background-image: url("../img/subhero/sh-09-texture-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-09-texture-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-09-texture-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--micropigmentacion { background-image: url("../img/subhero/sh-10-oct-sterile-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-10-oct-sterile-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-10-oct-sterile-desktop.jpg") type("image/jpeg") 1x); }
  .sub-hero__bg--melody { background-image: url("../img/subhero/sh-melody-desktop.jpg"); background-image: image-set(url("../img/subhero/sh-melody-desktop.webp") type("image/webp") 1x, url("../img/subhero/sh-melody-desktop.jpg") type("image/jpeg") 1x); }
}

/* --------------------------------------------------------------------------
   38. Foto real dentro de un slot — modificador del sistema zero-portfolio
   --------------------------------------------------------------------------
   Una sola pieza para los tres sitios donde entra obra real: tarjetas de
   división, galería y prueba de capilar. El hueco lo sigue definiendo
   `.texture-slot--4x5`, que ya trae `position: relative`, `overflow: hidden`
   y el ratio; esto sólo dice cómo se comporta la foto dentro.

   El <img> va en posición ABSOLUTA a propósito, y ésa es la parte que
   importa: `.division-card__figure` es un ítem flex dentro de
   `.division-card` (columna). Un <img> en flujo con altura intrínseca de
   ~2400 px se convierte en la altura mínima automática de ese ítem flex y
   estira la tarjeta ignorando el `aspect-ratio` del contenedor. Sacándolo
   del flujo, el recorte lo hace siempre el slot y la foto no puede empujar
   nada. Sin `border-radius`: en este sistema los huecos son rectos.
   -------------------------------------------------------------------------- */
.texture-slot--photo {
  background: var(--crema);
}
.texture-slot--photo picture {
  position: absolute;
  inset: 0;
  display: block;
}
.texture-slot--photo img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center;
}
/* Encuadre: se elige por foto, no por componente. `--alto` sube el recorte
   cuando el sujeto vive en el tercio superior del original. */
.texture-slot--photo img.is-top {
  object-position: center 22%;
}
.texture-slot--photo img.is-upper {
  object-position: center 32%;
}
/* La marca del hueco vacío no debe verse por debajo de una foto. */
.texture-slot--photo .texture-slot__mark {
  display: none;
}

/* --------------------------------------------------------------------------
   39. Prueba fotográfica — capilar
   --------------------------------------------------------------------------
   Dos piezas en fila dentro del ancho de texto de la sección, no a sangre.
   El pie usa tinta expreso: `--muted` no existe en este sistema y su valor
   de reserva metía un cuarto color en una paleta de tres.
   -------------------------------------------------------------------------- */
.proof-shot-row {
  display: grid;
  gap: var(--space-4);
  max-width: 720px;
  margin-top: var(--space-5);
}
@media (min-width: 640px) {
  .proof-shot-row {
    grid-template-columns: repeat(2, 1fr);
  }
}
.proof-shot {
  margin: 0;
}
.proof-shot__caption {
  margin-top: 10px;
  font-family: var(--ff-body);
  font-size: var(--fs-meta);
  line-height: 1.5;
  color: var(--expreso);
  opacity: 0.75;
}
/* la prueba de capilar vuelve con UNA foto
   (capilar-03-cicatrizado), no dos — el modificador fuerza una columna en
   vez de dejar la mitad derecha de la fila vacía. Misma especificidad que
   la regla de arriba, definida después: gana por cascada, sin !important. */
@media (min-width: 640px) {
  .proof-shot-row--single {
    grid-template-columns: 1fr;
    max-width: 340px;
  }
}

/* --------------------------------------------------------------------------
   45. Pop-up «solo con cita» (.gate)
   --------------------------------------------------------------------------
   Regla del atelier: no se recibe sin cita, y esa regla tiene
   que llegar antes de que alguien se plante en la puerta. Es un aviso, no un
   captador: no pide correo, no ofrece descuento y no vuelve a salir en la
   misma sesión (sessionStorage, nunca localStorage — quien cierra la pestaña
   y vuelve mañana es otra visita, pero quien navega 8 páginas seguidas no
   tiene que cerrarlo 8 veces).

   Tres garantías duras de este bloque:

   1. CERO CLS. El nodo nace con el atributo `hidden` y, cuando se abre, es
      `position: fixed` — nunca participa del flujo, así que no puede empujar
      nada. La regla `.gate[hidden]` va DESPUÉS de `.gate{display:flex}` a
      propósito: si no, `display:flex` le gana al `display:none` del user
      agent y el diálogo se vería sin JS.

   2. CERO CAPTURA DE CLICS EN REPOSO. Mientras no lleva `.is-open` el
      contenedor está en `pointer-events: none`. Aunque el atributo `hidden`
      ya lo saca de la página, esto cubre la ventana de un frame entre quitar
      `hidden` y pintar la transición, y la simétrica al cerrar. Ningún CTA
      de WhatsApp, teléfono o formulario puede quedar tapado por este velo.

   3. POR ENCIMA DEL BOTÓN PERSISTENTE. `.wa-float` vive en z-index 60 y la
      barra en 100/120; el diálogo entra en 300 para quedar por encima de
      todo lo fijo sin pelearse con nada.

   Medición: este bloque NO empuja nada al dataLayer. `generate_lead` se
   dispara desde los CTA reales (WhatsApp / llamada / formulario) y un aviso
   de horario no es una conversión. Si algún día se quiere medir, va como
   evento propio y NUNCA reutilizando data-track de los CTA.
   -------------------------------------------------------------------------- */
.gate {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  opacity: 0;
  pointer-events: none;
  overscroll-behavior: contain;
  transition: opacity 300ms var(--ease);
}
.gate.is-open {
  opacity: 1;
  pointer-events: auto;
}
/* Ver garantía (1) arriba: esta regla tiene que ir después de la de display. */
.gate[hidden] {
  display: none;
}

.gate__backdrop {
  position: absolute;
  inset: 0;
  background: var(--expreso-70);
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  /* Es un <button> para que el clic en el velo sea un control real y no un
     manejador colgado de un <div>. Se queda fuera del recorrido de Tab
     (tabindex="-1" en el markup) porque el botón de cierre ya cumple ese
     papel y dos salidas seguidas confunden al lector de pantalla. */
}

.gate__dialog {
  position: relative;
  width: 100%;
  max-width: 460px;
  max-height: calc(100vh - var(--space-5));
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--blanco);
  border: 1px solid var(--expreso-12);
  border-radius: var(--radius);
  padding: var(--space-5) var(--space-4) var(--space-4);
  box-shadow: 0 18px 48px rgba(48, 30, 18, 0.22);
  transform: translateY(14px);
  transition: transform 380ms var(--ease-veil);
}
.gate.is-open .gate__dialog {
  transform: translateY(0);
}
/* El diálogo recibe el foco al abrirse para que el lector de pantalla anuncie
   el título antes que cualquier acción, y por eso lleva tabindex="-1". Pero
   es un contenedor, no un control: la regla de foco visible del sitio (línea
   ~203) sólo cubre a/button/input/select/textarea, así que Chrome le pintaba
   encima su anillo azul por defecto — un cuarto color, fuera de los tres del
   canon, alrededor de toda la caja. Se apaga aquí. Los controles de dentro
   (aspa, Entendido, Reservar) conservan su anillo expreso intacto, que es lo
   que exige el criterio 2.4.7. */
.gate__dialog:focus,
.gate__dialog:focus-visible {
  outline: none;
}
@media (min-width: 768px) {
  .gate__dialog {
    padding: var(--space-6) var(--space-5) var(--space-5);
  }
}

.gate__eyebrow {
  margin-bottom: var(--space-3);
}
.gate__title {
  color: var(--expreso);
  margin-bottom: var(--space-2);
  max-width: 18ch;
}
.gate__body {
  color: var(--expreso);
  font-size: var(--fs-body);
  line-height: 1.65;
  max-width: 46ch;
}
.gate__body + .gate__body {
  margin-top: var(--space-2);
}
.gate__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* Cierre — 44 px de área táctil aunque el aspa mida 16. */
.gate__close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  color: var(--expreso);
  opacity: 0.7;
  transition: opacity 200ms var(--ease), background 200ms var(--ease);
}
.gate__close:hover,
.gate__close:focus-visible {
  opacity: 1;
  background: var(--expreso-06);
}
.gate__close svg {
  width: 16px;
  height: 16px;
}

/* --------------------------------------------------------------------------
   46. Instagram persistente (.ig-float)
   --------------------------------------------------------------------------
   Mismo lenguaje que .wa-float (sección 42):
   expreso translúcido, borde crema, radio 2 px, misma sombra y la misma
   entrada al bajar. Cambia el formato: cuadrado y sin rótulo. Dos rótulos
   apilados en la esquina leen como barra de herramientas de e-commerce; el
   registro del atelier es contención, así que el segundo botón se reduce al
   isotipo y el nombre viaja en el aria-label.

   POSICIÓN EN LA PILA — va ARRIBA del de WhatsApp. La separación no se
   adivina: --wa-float-h la escribe main.js midiendo la altura real de
   .wa-float en cada carga y en cada resize (el rótulo "WhatsApp" cambia de
   alto con el clamp de --fs-meta). El 48px del fallback es la medida a
   1440 px, para que la pila siga bien colocada aunque el JS no llegue a
   ejecutarse.

   MEDICIÓN — este botón NO lleva data-track y NO comparte clase, atributo ni
   contenedor con .wa-float. Es deliberado: `.wa-float` es disparador de
   generate_lead y un clic a Instagram no es un lead. Cualquier selector de
   GTM que apunte a .wa-float / [data-track="whatsapp_click"] no puede
   alcanzar a .ig-float, y no existe en todo el CSS/JS ninguna regla que los
   agrupe (ni .float-cta, ni [href*="instagram"]). Verificado por grep.
   -------------------------------------------------------------------------- */
.ig-float {
  position: fixed;
  right: 16px;
  bottom: calc(16px + var(--wa-float-h, 48px) + 12px);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  background: var(--expreso-70);
  color: var(--crema);
  border: 1px solid var(--crema);
  border-radius: var(--radius);
  box-shadow: 0 2px 12px rgba(48, 30, 18, 0.18);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity 320ms var(--ease), transform 320ms var(--ease),
    visibility 320ms var(--ease), background 260ms var(--ease),
    color 260ms var(--ease);
}
@media (min-width: 768px) {
  .ig-float {
    right: 24px;
    bottom: calc(24px + var(--wa-float-h, 48px) + 12px);
  }
}
.ig-float.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.ig-float:hover,
.ig-float:focus-visible {
  background: var(--crema);
  color: var(--expreso);
  border-color: var(--crema);
}
.ig-float svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* --------------------------------------------------------------------------
   47. Diario — rejilla de tarjetas con foto (.journal-grid / .journal-card)
   --------------------------------------------------------------------------
   el índice pasa de filas de texto (.journal-item, sección 33,
   que sigue viva hasta que migre el HTML de las dos lenguas) a tarjetas con
   imagen, de tres en tres.

   Reparto: 1 columna hasta 640, 2 hasta 1024, 3 arriba. El corte de 2→3 se
   pone en 1024 y no en 768 porque el título de una entrada es una frase
   larga, no un rótulo: a 768 con tres columnas la caja mide ~215 px y los
   títulos caían a seis y siete renglones.

   Sin CLS: la figura reserva su caja con `aspect-ratio: 4 / 5` ANTES de que
   la imagen cargue, y el markup del snippet lleva width/height + loading
   ="lazy" en cada <img>. Si una entrada aún no tiene foto, la figura vacía
   enseña la textura del sistema (mismo degradado que .texture-slot) y la
   rejilla no se descuadra.
   -------------------------------------------------------------------------- */
.journal-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5) var(--space-4);
}
@media (min-width: 640px) {
  .journal-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .journal-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-6) var(--space-4);
  }
}

.journal-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  color: var(--expreso);
}
.journal-card__figure {
  position: relative;
  margin: 0 0 var(--space-1);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  isolation: isolate; /* contiene el contexto de apilado del velo */
  border: 1px solid var(--expreso-12);
  background: radial-gradient(
      circle at 20% 20%,
      var(--crema-30),
      transparent 60%
    ),
    radial-gradient(circle at 80% 70%, var(--expreso-08), transparent 55%),
    var(--blanco);
}
.journal-card__figure img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform 620ms var(--ease);
}

/* Velo de esquina — mismo patrón y mismo token que .division-card (sección
   11): translúcido, no tapón, y sólo donde hay puntero de verdad. Sin la
   compuerta (hover:hover)(pointer:fine) un toque en móvil deja el velo
   pegado hasta que se toca otra cosa. */
@media (hover: hover) and (pointer: fine) {
  .journal-card__figure::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 0;
    height: 0;
    background: var(--expreso-30);
    z-index: 1;
    transition: width 520ms var(--ease), height 520ms var(--ease);
  }
  .journal-card:hover .journal-card__figure::after,
  .journal-card:focus-visible .journal-card__figure::after {
    width: 100%;
    height: 100%;
  }
  .journal-card:hover .journal-card__figure img,
  .journal-card:focus-visible .journal-card__figure img {
    transform: scale(1.03);
  }
}

.journal-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
}
.journal-card__date,
.journal-card__kind {
  font-family: var(--ff-body);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--expreso);
  opacity: 0.7;
}
/* El título de tarjeta NO usa --fs-h3: ese tamaño es de título de sección y
   en una columna de ~350 px una frase de 80 caracteres se iba a seis
   renglones. Escala propia, un peldaño por debajo. */
.journal-card__title {
  display: block;
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.25rem);
  line-height: 1.3;
  color: var(--expreso);
  /* Se compone como titular: equilibrado y sin partir con guion. Ver
     sección 48 — aquí va explícito porque es un <span>, no un h1/h2/h3. */
  text-wrap: balance;
  hyphens: none;
  -webkit-hyphens: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: border-color 220ms var(--ease);
}
.journal-card:hover .journal-card__title,
.journal-card:focus-visible .journal-card__title {
  border-color: var(--expreso-30);
}

/* --------------------------------------------------------------------------
   48. Composición del párrafo — justificado con medida controlada
   --------------------------------------------------------------------------
   Requisito editorial: los finales de línea van al margen. Se cumple con
   `text-align: justify`, pero justificar sin más es cambiar un borde
   irregular por ríos de espacio en blanco. Tres condiciones, las tres
   obligatorias:

   1. PARTICIÓN ACTIVA. `hyphens: auto` reparte el sobrante entre más puntos
      de corte. Depende del diccionario del navegador, que se elige por el
      atributo `lang` — ya correcto en todo el sitio (`es` en las páginas
      castellanas, `en` en las inglesas). Sin `lang` correcto
      esta regla NO funciona: es el único requisito que vive en el HTML.

   2. MEDIDA CONTROLADA. Objetivo 60–75 caracteres. Las clases que ya traían
      su tope en `ch` lo conservan (.lede 60ch, .sub-hero__desc 56ch); las
      que no lo tenían reciben 68ch aquí.

   3. ANCHO DE CORTE — 768 px. Por debajo NO se justifica. Es el punto donde
      el sitio pasa de --pad-base (20px) a --pad-tablet (32px): a 375 px de
      ventana la caja de texto mide 335 px, que en Poppins a 16 px son ~45
      caracteres. Justificar 45 caracteres produce exactamente los ríos que
      esta corrección quiere evitar, así que en móvil el párrafo se queda
      alineado a la izquierda. El justificado entra a 768 px, donde la caja
      ya da para 60 caracteres o más.

   `.justified` queda como utilidad de reserva para los cuerpos de texto que
   el HTML añada más adelante sin clase propia.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
  .lede,
  .sub-hero__desc,
  .entry__body > p,
  .journal-empty__body,
  .justified {
    text-align: justify;
    text-justify: inter-word;
    hyphens: auto;
    -webkit-hyphens: auto;
    /* Freno a la partición. Verificado en diario.html a 1440: la columna de
       texto mide 62 caracteres y el diccionario español partía cuatro
       renglones de quince, dos de ellos seguidos. Estos límites prohíben
       partir palabras de menos de 6 letras y dejar menos de 3 antes o
       después del guion, que es lo que elimina las particiones baratas
       («ma-terial»); y el tope de dos renglones seguidos evita la escalera
       de guiones en el borde derecho.

       Aviso al que venga después: `hyphenate-limit-lines` HOY sólo lo
       aplica WebKit. Chrome lo ignora, y medido en diario.html a 1440 sigue
       partiendo dos renglones seguidos («ter-minadas» / «mate-rial»). No es
       un fallo de esta regla ni hay forma declarativa de arreglarlo: la
       alternativa sería un guionado manual con &shy; en el copy, que no
       compensa. El borde derecho queda recto, que es lo que pedía la
       corrección, y el espaciado entre palabras no abre ríos. Todas estas
       propiedades son progresivas: donde no se conocen se ignoran y queda
       el justificado de arriba, nunca un fallo. */
    hyphenate-limit-chars: 6 3 3;
    -webkit-hyphenate-limit-before: 3;
    -webkit-hyphenate-limit-after: 3;
    hyphenate-limit-lines: 2;
    -webkit-hyphenate-limit-lines: 2;
  }
}
/* Medida: los que ya tenían tope en `ch` no se tocan (ver .lede y
   .sub-hero__desc en las secciones 5 y 8). */
.entry__body > p,
.journal-empty__body,
.justified {
  max-width: 68ch;
}

/* Titulares — equilibrados y sin guion. SIN EXCEPCIONES DE GUIONADO.
   Se declara sobre los ELEMENTOS h1/h2/h3, no sobre .heading-display, con
   especificidad de elemento (0,0,1) para que cualquier clase pueda ajustar
   el REPARTO de renglones si lo necesita (hoy sólo .hero__title lo hace).

   El guionado, en cambio, ya no admite excepciones: ninguna clase debe
   reintroducir `hyphens: auto` sobre un titular. Si un título largo no cabe,
   la palanca es la MEDIDA (ver la rampa de ancho estrecho en la sección 8),
   nunca partir la palabra. */
h1,
h2,
h3 {
  text-wrap: balance;
  hyphens: none;
  -webkit-hyphens: none;
}
/* Una sola exclusión, y sólo del reparto.
   .hero__title lleva corte manual con <br class="br-*"> + .nowrap (ver
   sección 36): `balance` re-reparte los renglones dentro de cada trozo y
   deshace ese corte medido, así que conserva `text-wrap: initial`.

   El guionado NO se excluye: cortes manuales sí, guiones automáticos no.
   `hyphens: none` va aquí explícito para que la exclusión del reparto no se
   lea nunca como permiso para partir palabras.

   .sub-hero__title ya NO se excluye — se compone equilibrado y sin guion
   como el resto (ver sección 8). */
.hero__title {
  text-wrap: initial;
  hyphens: none;
  -webkit-hyphens: none;
}

/* ==========================================================================
   25. prefers-reduced-motion — MUST STAY LAST IN THIS FILE.
   .rule.reveal beats .reveal on specificity; placing this override anywhere
   but last risks leaving revealed content permanently at opacity: 0.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .btn:hover {
    transform: none;
  }

  /* Kicker eyebrows — same specificity as the rule that defines them */
  .eyebrow.reveal { transform: none; }
  .reveal.reveal--kicker { transform: none; }

  /* Mask-reveal headings — same specificity as the rules that define them */
  .reveal.reveal-veil { overflow: visible; }
  .reveal.reveal-veil > * { transform: none; transition: none; }

  /* Kicker/heading travel distance collapses along with the universal
     transition-duration:0.01ms rule below — the explicit transform:none
     overrides above exist so content is guaranteed fully visible even if
     JS never runs at all (is-visible never gets added), not just fast. */

  /* Division-card wash hover — flip state instantly instead of sweeping */
  .division-card__figure::after { transition: none; }
  .division-card__figure .texture-slot__mark { transition: none; }

  /* WhatsApp persistente — sigue apareciendo al bajar (es una decisión de
     contenido, no una animación), pero sin recorrido ni transición. */
  .wa-float { transform: none; transition: none; }
  .wa-float.is-visible { transform: none; }

  /* Instagram persistente (sección 46) — mismo criterio que .wa-float:
     sigue apareciendo al bajar, pero sin recorrido ni transición. */
  .ig-float { transform: none; transition: none; }
  .ig-float.is-visible { transform: none; }

  /* Pop-up «solo con cita» (sección 45) — aparece y desaparece de golpe.
     El aviso es contenido, no animación: se apaga el desvanecido y el
     desplazamiento del diálogo, no su visibilidad. */
  .gate { transition: none; }
  .gate__dialog { transform: none; transition: none; }
  .gate.is-open .gate__dialog { transform: none; }

  /* Tarjetas del diario (sección 47) — el velo de esquina y el acercamiento
     de la foto pasan a cambio instantáneo. */
  .journal-card__figure::after { transition: none; }
  .journal-card__figure img { transition: none; }
  .journal-card:hover .journal-card__figure img,
  .journal-card:focus-visible .journal-card__figure img { transform: none; }

  /* Flechas de Instagram — el desplazamiento al hover se apaga */
  .ig-panel__handle svg,
  .ig-line svg { transition: none; }
  .ig-panel:hover .ig-panel__handle svg,
  .ig-line:hover svg { transform: none; }

  /* Parallax needs NO CSS override — the JS controller checks
     matchMedia('(prefers-reduced-motion: reduce)') at init and never
     attaches the scroll listener at all if it matches. A CSS transition
     override can't touch a directly-set inline transform applied by JS on
     each animation frame, so this is handled entirely in main.js. This
     comment exists so the next person doesn't go looking for a rule that
     was never supposed to exist here. */

  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
