/* ============================================================
   layout.css — page structure: header/nav, section containers
   and grids, footer. Mobile-first: base rules are single-column
   phone layouts; min-width media queries progressively add the
   tablet/desktop grid arrangements. Depends on tokens in base.css.

   Color note: every rule here reads the semantic --bg/--text/
   --text-secondary/--text-muted/--border/--acento-grafico/
   --acento-texto tokens, never the raw --color-crema/--color-
   carbon/--color-lavanda, or the --lavanda-* / --neutro-* ramp. base.css
   re-points the section-relative tokens inside .site-header/
   .site-footer/.marquee — that's the only place light
   vs. dark is decided.

   Numbering note: per the brand direction, decorative section-level
   numbers are never used — every section kicker below is plain text.
   The only numbered markers on the page are the mobile/desktop nav
   index (.nav-link-num, legitimate because it mirrors the real
   section order) and the Servicios section-count ("01 — 04", a
   range label, not a per-item marker).
   ============================================================ */

/* Shared "kicker"/eyebrow treatment: Inter Medium, small, uppercase,
   wide tracking, accent-texto. Used above/beside headings throughout. */
.hero-tag-text,
.about-eyebrow,
.section-kicker,
.nav-link-num {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--text-kicker);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--acento-texto);
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  color: var(--text);
  border-bottom: 1px solid var(--border);
}

.header-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0.875rem var(--gutter);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1-5) var(--space-2-5);
}

.logo { display: flex; align-items: center; gap: 0.625rem; order: 1; }
.logo-dot { width: 0.8125rem; height: 0.8125rem; border-radius: 50%; background: var(--acento-grafico); flex-shrink: 0; }
.logo-text { font-family: var(--font-heading); font-size: 1.3125rem; font-weight: 600; letter-spacing: -0.01em; }

/* Hamburger toggle — only shown below the tablet breakpoint.
   Bars read the graphic accent: per the brand spec, icons are
   always the lavanda accent regardless of section. */
.nav-toggle {
  order: 2;
  margin-left: auto;
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: var(--tap-min);
  height: var(--tap-min);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
}
.nav-toggle-bar {
  width: 1.25rem;
  height: 2px;
  background: var(--acento-grafico);
  border-radius: 1px;
  transition: transform var(--duration-enter) var(--ease-out), opacity var(--duration-enter) var(--ease-out);
}
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Mobile menu: a full-screen charcoal panel styled as an editorial
   "índice" (table of contents) — numbered entries, hairline rules,
   wide margins — not a generic app drawer. Titles are Inter Medium,
   not Playfair: a nav link is a control, not a headline, per the
   brand system's type roles (Playfair stays reserved for headings
   and highlighted figures). The numbering is legitimate here
   specifically because the nav order mirrors the section order on
   the page; it's a real sequence, not decoration (contrast the "no
   decorative numbers" rule elsewhere).

   Nested inside .site-header, so --bg/--text/--acento-texto/
   --link-hover/--btn-primary-* are already the charcoal values with
   no extra selector needed. Two colors below are read as raw tokens
   on purpose rather than through the semantic layer — .nav-link and
   .main-nav-contact-link — because this panel is a fixed, single-
   context surface (always this exact charcoal, never scrolled
   against anything else) styled to an explicit reference (the brand
   manual's Índice page), not a component meant to adapt to a
   surface it's placed on. Same category of deliberate exception as
   .whatsapp-float in components.css.

   Closed by default; JS (js/components/mobileNav.js) flips
   data-state and manages the focus trap. Reset to the original
   inline row at 48em below — every property changed here needs an
   explicit reset there.

   Rhythm: the wordmark row and the link list are grouped tight at
   the top (a small fixed gap between them, plus the link list's own
   tighter internal padding — see .nav-link); the contact block and
   CTA are grouped tight at the bottom. margin-top: auto on the
   contact block pushes that whole bottom group down, so the slack
   left over by the smaller nav-link type collects as one deliberate
   gap in the middle rather than spreading evenly between every
   element — see .main-nav-links, .main-nav-contact, .main-nav-cta
   below for the actual values, and their :not(48em) desktop resets
   below that. */
.main-nav {
  order: 4;
  display: none;
}
.main-nav[data-state="open"] {
  display: flex;
  flex-direction: column;
  position: fixed;
  inset: 0;
  z-index: 60; /* above .site-header's 50 and .whatsapp-float's 40 (which also hides outright — see body.nav-open) */
  background: var(--bg);
  color: var(--text);
  padding: calc(var(--space-4) + env(safe-area-inset-top, 0px)) var(--gutter) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  opacity: 0;
}
.main-nav[data-state="open"] .main-nav-links { margin-top: 0; } /* gap now lives on .main-nav-top's margin-bottom — the wordmark's clear-space */
.main-nav[data-state="open"] .main-nav-contact { margin-top: auto; } /* pushes itself + the CTA after it to the bottom */
.main-nav[data-state="open"] .main-nav-cta { margin-top: var(--space-3); }
@media (prefers-reduced-motion: no-preference) {
  /* 200ms fade only — no slide/scale. Reduced motion doesn't need
     its own override: base.css's universal transition-duration
     kill switch already collapses this to effectively instant. */
  .main-nav[data-state="open"] { animation: nav-panel-in 200ms var(--ease-out) forwards; }
}
@keyframes nav-panel-in { from { opacity: 0; } to { opacity: 1; } }

.main-nav-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  /* Clear-space around the plain wordmark: no dot mark lives in this
     text-only instance to measure a literal circle diameter against,
     so this uses the site's largest spacing step as a generous stand-in
     — adjust if the brand manual's own figure differs. */
  margin-bottom: var(--space-6);
}
.main-nav-wordmark {
  font-family: var(--font-heading);
  font-size: 2rem; /* renders "Sundayness" at ~124px — clears the brand manual's 120px minimum for the plain wordmark */
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1;
}

/* Borderless by design — a mark, not a boxed button, so it reads as
   part of the same quiet typographic system as everything else in
   the panel rather than a piece of app chrome. Still a real 44px
   target (WCAG 2.5.5); only the visible glyph is small. */
.nav-close {
  display: flex; align-items: center; justify-content: center;
  width: var(--tap-min); height: var(--tap-min);
  margin-right: calc(-1 * (var(--tap-min) - 1.125rem) / 2); /* the glyph, not the invisible hit-area, sits on the gutter */
  border: 0;
  background: transparent;
  color: var(--text);
}
.nav-close svg { width: 1.125rem; height: 1.125rem; }

.main-nav-links {
  display: flex;
  flex-direction: column;
}

/* Each entry: a narrow number column (the shared kicker treatment,
   via .nav-link-num above) so every title lines up on one left
   edge, a thin --neutro-700 rule below matching the editorial
   dividers used elsewhere (.service-tags li, .how-point). No
   underline — the rules already separate the entries, and a nav
   link is a control, not a headline: Inter, not Playfair, per the
   brand system's type roles.
   rgb(254,254,248) i.e. crema at rest: 18.87:1, comfortably past the
   4.5:1 floor lavanda-anything would undercut at this text weight.
   Padding is tighter than the site's usual --space-3 rhythm — at
   this text size --space-2 top/bottom still clears the 44px tap
   target on its own (verified: ~60px per row), so the extra didn't
   buy anything but slack the smaller type doesn't need. */
.main-nav[data-state="open"] .nav-link {
  position: relative;
  display: block;
  padding: var(--space-2) 0;
  border-top: 1px solid var(--neutro-700);
  text-decoration: none;
  color: var(--color-crema);
}
.main-nav[data-state="open"] .nav-link:last-of-type {
  border-bottom: 1px solid var(--neutro-700);
}
.main-nav[data-state="open"] .nav-link-content {
  display: grid;
  grid-template-columns: 1.5rem 1fr; /* narrower than before — sized for the number, not a 2rem serif title */
  align-items: center;
  gap: var(--space-1-5);
}
.main-nav[data-state="open"] .nav-link-title {
  position: relative;
  justify-self: start; /* size to the text, not the full grid track, so the underline hugs the word */
  font-family: var(--font-body);
  font-size: 1.125rem;
  font-weight: 500;
}
/* Drawn-in underline: a 1px line, not text-decoration, so it can be
   animated on transform (scaleX) instead of a layout-triggering
   property. Sits a hair below the descenders, off at rest, and only
   ever scales — never repaints at a new width.
   Scoped to [data-state] (any value), not just "open": the desktop
   inline row uses this exact treatment too, so there is one
   implementation to change, not two. Colour reads --link-hover
   rather than a literal --color-crema per the "no hardcoded
   interactive colours" rule — on both surfaces this underline can
   sit on (the charcoal header and the charcoal panel) it resolves
   to crema anyway, but it now adapts if either ever changes. */
.main-nav[data-state] .nav-link-title::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.25em;
  height: 1px;
  background: var(--link-hover);
  transform: scaleX(0);
  transform-origin: left;
}
@media (prefers-reduced-motion: no-preference) {
  .main-nav[data-state] .nav-link-title::after {
    transition: transform var(--duration-exit) var(--ease-out);
  }
  .main-nav[data-state] .nav-link:hover .nav-link-title::after,
  .main-nav[data-state] .nav-link:focus-visible .nav-link-title::after {
    transition-duration: var(--duration-enter);
  }
}
@media (hover: hover) {
  .main-nav[data-state] .nav-link:hover .nav-link-title::after { transform: scaleX(1); }
}
.main-nav[data-state] .nav-link:focus-visible .nav-link-title::after {
  transform: scaleX(1);
}
@media (prefers-reduced-motion: no-preference) {
  /* Base transition is the fast exit (retract); hover/focus swap in
     the slower enter duration, so drawing in and snapping back don't
     share a speed — exiting faster reads as tidier than a mirrored
     ease. Same asymmetric-duration trick as .cta-arrow. */
  .main-nav[data-state="open"] .nav-link-title::after {
    transition: transform var(--duration-exit) var(--ease-out);
  }
  .main-nav[data-state="open"] .nav-link:hover .nav-link-title::after,
  .main-nav[data-state="open"] .nav-link:focus-visible .nav-link-title::after {
    transition-duration: var(--duration-enter);
  }
}
@media (hover: hover) {
  .main-nav[data-state="open"] .nav-link:hover .nav-link-title::after { transform: scaleX(1); }
}
.main-nav[data-state="open"] .nav-link:focus-visible .nav-link-title::after {
  transform: scaleX(1);
}
/* Anchored to the row's right edge (not the left, where it used to
   sit before the content shift that revealed it) so it never needs
   anything else to move out of its way — that shift was the second
   signal we removed to avoid stacking three cues on one hover. It
   only fades in place; no transform of its own, to keep it a single,
   calm cue alongside the underline. Titles here are short enough
   (see .main-nav-links content) that it never nears the title text. */
.main-nav[data-state="open"] .nav-link-arrow {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  opacity: 0;
  color: var(--lavanda-300);
  font-size: 1.25rem;
  line-height: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .main-nav[data-state="open"] .nav-link-arrow {
    transition: opacity var(--duration-exit) var(--ease-out);
  }
  .main-nav[data-state="open"] .nav-link:hover .nav-link-arrow,
  .main-nav[data-state="open"] .nav-link:focus-visible .nav-link-arrow {
    transition-duration: var(--duration-enter);
  }
}
@media (hover: hover) {
  .main-nav[data-state="open"] .nav-link:hover .nav-link-arrow { opacity: 1; }
}
.main-nav[data-state="open"] .nav-link:focus-visible .nav-link-arrow {
  opacity: 1;
}
/* Touch has no hover, so :active carries the same two signals —
   snapped, not eased, matching every other press state on the site.
   This also overrides the shared text-link :active{opacity:.7} from
   components.css (a dim reads as a mistake here, not feedback). */
.main-nav[data-state="open"] .nav-link:active {
  opacity: 1;
}
.main-nav[data-state="open"] .nav-link:active .nav-link-title::after {
  transform: scaleX(1);
}
.main-nav[data-state="open"] .nav-link:active .nav-link-arrow {
  opacity: 1;
}

.main-nav-contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
/* Small and secondary on purpose — this is a second way to reach us,
   not competing with the numbered entries above or the CTA below.
   --neutro-300 on carbon: ~10.4:1. */
.main-nav-contact-link {
  display: flex;
  align-items: center;
  min-height: var(--tap-min);
  font-family: var(--font-body);
  font-size: var(--text-small);
  color: var(--neutro-300);
  text-decoration: none;
}
@media (hover: hover) {
  .main-nav-contact-link:hover { color: var(--color-crema); }
}

@media (min-width: 48em) {
  .header-inner { flex-wrap: nowrap; justify-content: space-between; gap: 2rem; }
  .logo { order: initial; }
  .nav-toggle { display: none; }

  .main-nav {
    order: initial;
    display: flex !important;
  }
  .main-nav[data-state="open"] {
    position: static;
    inset: auto;
    z-index: auto;
    background: none;
    color: inherit;
    padding: 0;
    overflow: visible;
    opacity: 1;
    animation: none;
  }
  /* [data-state], not bare .main-nav — same reasoning as the other
     [data-state] resets below: the mobile margin-top rules above are
     scoped to [data-state="open"] at specificity (0,3,0); a bare
     .main-nav-links{margin-top:0} here is only (0,1,0) and would
     lose that fight if data-state is ever still "open" at this
     width. [data-state] (any value) ties it and lets source order
     (this is later in the file) decide instead. */
  .main-nav[data-state] .main-nav-links,
  .main-nav[data-state] .main-nav-contact,
  .main-nav[data-state] .main-nav-cta { margin-top: 0; }
  .main-nav-top, .main-nav-contact { display: none; }

  .main-nav {
    width: auto;
    flex-direction: row;
    align-items: center;
    /* Fluid, not the fixed --space-5: at 768px (nav CTA + arrow, 4
       links, and the logo all sharing one row) a flat 40px gap
       overflowed the viewport; scaling down at the tablet end of the
       range while keeping the original 40px at desktop keeps this
       from ever being a fixed-vs-fluid tradeoff again. */
    gap: clamp(0.75rem, 1vw + 0.5rem, var(--space-5));
    font-size: var(--text-small);
  }
  .main-nav-links {
    flex-direction: row;
    align-items: center;
    gap: clamp(0.75rem, 1vw + 0.5rem, var(--space-5));
  }
  /* [data-state] (any value) rather than plain .main-nav, on this
     rule and the two below it: the mobile-open rules for these same
     properties are scoped to [data-state="open"], at specificity
     (0,3,0)/(0,4,0) — a plain .main-nav .nav-link reset here is
     lower-specificity and would lose that fight if data-state is
     ever still "open" at desktop width (the mobileNav.js resize
     listener closes it, but this shouldn't depend on that timing).
     [data-state] matches "open" or "closed" alike and ties the
     specificity, so source order (this is later in the file) decides. */
    .main-nav[data-state] .nav-link {
    position: static;
    padding: 0;
    border-top: 0;
    /* No text-decoration here: the desktop row uses the same drawn-in
       ::after underline as the panel (see .nav-link-title::after
       above), so it is off at rest and only ever scales in. This
       overrides the shared text-link underline in components.css. */
    text-decoration: none;
    color: var(--acento-texto);
  }
  .main-nav[data-state] .nav-link:last-of-type { border-bottom: 0; }
  .main-nav[data-state] .nav-link-content { display: contents; }
  .main-nav[data-state] .nav-link-title {
    position: relative;
    display: inline-block;
    font-size: inherit;
  }
  .main-nav .nav-link-num,
  .main-nav .nav-link-arrow { display: none; }

  /* The CTA is a real .btn-primary everywhere else on the page; at
     this width it reverts to the small bordered button the header
     always had, by cancelling the letterpress block and the fill
     rather than maintaining a second look-alike component.

     .main-nav qualifies every selector below, even though
     .main-nav-cta alone would match the same element: components.css
     (where the generic .btn-primary rules live) loads AFTER
     layout.css in index.html, so at equal specificity its rules win
     regardless of which override "should" logically apply — e.g.
     .main-nav-cta::before vs .btn-primary::before are both exactly
     one class + one pseudo-element, a tie components.css would win
     by load order alone. The extra .main-nav ancestor makes these
     unambiguously more specific instead of relying on file order. */
  .main-nav .main-nav-cta::before { content: none; }
  .main-nav .main-nav-cta {
    /* Without this, the CTA is the largest flex-basis among the
       nav's two remaining items (the links wrapper + this) and so
       absorbs a disproportionate share of the shrink when the row
       is tight — at exactly 768px that was enough to wrap "Escríbenos
       por WhatsApp" onto three lines with room to spare elsewhere. */
    flex-shrink: 0;
  }
  .main-nav .main-nav-cta .btn-primary-face {
    min-height: var(--tap-min);
    padding: 0.5rem 0.875rem;
    gap: var(--space-1);
    font-size: var(--text-small);
    white-space: nowrap;
    background: transparent;
    color: inherit;
    border: 1px solid var(--text);
  }
  .main-nav .main-nav-cta:active .btn-primary-face { transform: none; }
}
@media (min-width: 48em) and (hover: hover) {
  .main-nav .main-nav-cta:hover .btn-primary-face {
    background: var(--text);
    color: var(--bg);
    border-color: var(--text);
    transform: none;
  }
}

/* ---------- Hero ---------- */
.hero { max-width: var(--container-max); margin: 0 auto; padding: var(--space-96) var(--gutter) var(--space-72); }

.hero-tag { display: flex; align-items: center; gap: var(--space-1-5); margin-bottom: var(--space-3); }
.hero-tag-line { flex: 1; height: 1px; background: var(--border); }

.hero-title {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 400;
  font-size: var(--text-hero);
  line-height: 1.05;
  letter-spacing: -0.02em;
  max-width: 16ch;
  text-wrap: balance;
}
.hero-title em { font-style: italic; color: var(--acento-grafico); }

.hero-content {
  display: grid;
  /* minmax(0, 1fr), not the plain 1fr shorthand for minmax(auto, 1fr):
     an auto-sized single track won't shrink below its content's
     max-content width, which caused real horizontal overflow at
     360px on the about/contact grids below before this was
     added everywhere a text-heavy grid child could force it wide. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  margin-top: var(--space-4);
}

/* One icon per service, linking straight to its card in Servicios.
   Bordered tiles (icon stacked over label), 2x2 on mobile / one row
   on desktop — enough presence to be worth tapping, but deliberately
   plain-Inter and capped narrower than the H1 so it never competes
   with it as the focal point. --max-width below is an eyeballed
   match to the H1's own rendered width at this max-width:16ch/
   text-hero combination; re-check if either changes. */
.hero-services {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-1-5);
  max-width: 26rem;
}
/* Parent never fades as a single block — .reveal-in here only flags
   "safe to animate", the fade+rise itself runs per-tile below so the
   four tiles stagger in instead of moving as one unit. */
.hero-services[data-reveal] { opacity: 1; transform: none; }

.hero-service-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 5.5rem;
  padding: var(--space-2) var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--text-body);
  color: var(--acento-texto);
  text-decoration: none;
  text-align: center;
  transition: border-color var(--duration-exit) var(--ease-out),
              color var(--duration-exit) var(--ease-out),
              transform var(--duration-exit) var(--ease-out);
}
.hero-service-icon {
  display: flex;
  color: var(--acento-grafico); /* icons always carry the graphic accent, regardless of the link's own text color */
  transition: transform var(--duration-exit) var(--ease-out);
}
.hero-service-icon svg { width: 1.75rem; height: 1.75rem; }

/* One-time stagger-in, gated the same way every other [data-reveal]
   moment is (see scrollReveal.js): no JS, or reduced motion, and
   .reveal-ready/.reveal-in never get added, so nothing here ever
   sets opacity — tiles are simply visible at rest.
   Deliberately `animation`, not `transition`+a persistent .reveal-in
   rule: scrollReveal.js never removes .reveal-in once added, and a
   .hero-services.reveal-in .hero-service-link{transform:...} rule
   would outrank (by specificity) the plain .hero-service-link:hover/
   :focus-visible rules below forever after, permanently blocking the
   lift. `backwards` fill-mode holds the "from" frame only during the
   staggered delay (so items 2-4 stay hidden until their turn); once
   each animation finishes it leaves no held value behind, so :hover/
   :focus-visible/:active fully govern transform again right after. */
@keyframes hero-service-in {
  from { opacity: 0; transform: translateY(0.75rem); }
  to   { opacity: 1; transform: translateY(0); }
}
.hero-services.reveal-ready:not(.reveal-in) .hero-service-link { opacity: 0; }
.hero-services.reveal-in .hero-service-link {
  animation: hero-service-in var(--duration-reveal) var(--ease-out) backwards;
}
.hero-services.reveal-in .hero-service-link:nth-child(2) { animation-delay: 80ms; }
.hero-services.reveal-in .hero-service-link:nth-child(3) { animation-delay: 160ms; }
.hero-services.reveal-in .hero-service-link:nth-child(4) { animation-delay: 240ms; }

@media (hover: hover) {
  .hero-service-link:hover {
    border-color: var(--acento-grafico);
    color: var(--link-hover);
    transform: translateY(-2px);
    transition-duration: var(--duration-enter);
  }
  .hero-service-link:hover .hero-service-icon {
    transform: scale(1.08);
    transition-duration: var(--duration-enter);
  }
}
.hero-service-link:focus-visible {
  border-color: var(--acento-grafico);
  color: var(--link-hover);
  transform: translateY(-2px);
  transition-duration: var(--duration-enter);
}
.hero-service-link:focus-visible .hero-service-icon {
  transform: scale(1.08);
  transition-duration: var(--duration-enter);
}
.hero-service-link:active {
  transform: translateY(0) scale(0.97);
  transition-duration: var(--duration-press);
}

@media (min-width: 64em) {
  .hero-services { grid-template-columns: repeat(4, 1fr); }
}

.hero-actions { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1-5); }

/* "What happens next" microline — see components.css .cta-followup
   for the shared type treatment. Only appears under the hero and
   contact primary CTAs, per the brand direction: repeating it under
   every CTA on the page would turn a reassurance into noise. */
.hero-actions .cta-followup { max-width: 34ch; }

@media (min-width: 64em) {
  .hero-content { grid-template-columns: 1.15fr 0.85fr; align-items: end; margin-top: var(--space-6); }
}

/* ---------- Stats ----------
   The credibility moment: kept to a plain, even grid (not the
   asymmetric label/content pattern used elsewhere) so it reads as
   a report strip, not a narrative. Generous top/bottom air makes
   the figures — now --text-display, larger than any heading on
   the page — the most confident element here. */
.stats { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.stats-grid {
  max-width: var(--container-max); margin: 0 auto; padding: var(--space-6) var(--gutter);
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
}

.stat { padding: var(--space-3) var(--space-2); border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.stat:nth-child(odd) { padding-left: 0; }
.stat:nth-child(even) { padding-right: 0; border-right: none; }
.stat:nth-last-child(-n+2) { border-bottom: none; }

.stat-value { font-family: var(--font-heading); font-weight: 400; font-size: var(--text-display); line-height: 1; letter-spacing: -0.01em; color: var(--acento-grafico); }
.stat-label { margin-top: var(--space-1); font-size: var(--text-xs); color: var(--text-secondary); }
.stat-sublabel { margin-top: var(--space-0-5); font-size: var(--text-xs); color: var(--text-muted); }

.stats-cta { max-width: var(--container-max); margin: 0 auto; padding: var(--space-4) var(--gutter) var(--space-5); display: flex; }

@media (min-width: 48em) {
  .stats-grid { grid-template-columns: repeat(4, 1fr); padding: var(--space-6) var(--gutter); }
  .stat { padding: var(--space-4) var(--space-2-5); border-bottom: none; }
  .stat:first-child { padding-left: 0; }
  .stat:last-child { padding-right: 0; border-right: none; }
  .stat:nth-child(odd) { padding-left: var(--space-2-5); }
  .stat:nth-child(even) { padding-right: var(--space-2-5); border-right: 1px solid var(--border); }
}

/* ---------- Section header: narrow label / wide content ----------
   Shared asymmetric pattern for Resultados and Servicios — a
   plain-text kicker in a narrow column beside the heading (and its
   count) in a wide one. Collapses to a stacked single column below
   desktop. Cómo lo logramos/Nosotros keep their own existing stacked
   eyebrow-above-heading treatment; this is a distinct, deliberately
   wider pairing used only for these two headers. */
.section-header {
  display: grid;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.section-header-main {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: var(--space-2);
}
.section-header-main h2 {
  margin: 0; font-family: var(--font-heading); font-weight: 400;
  font-size: var(--text-h2); line-height: 1.1; letter-spacing: -0.01em; max-width: 20ch;
}
.section-count {
  font-family: var(--font-body); font-weight: 500; font-size: var(--text-xs); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-muted); padding-bottom: 0.5rem;
}

/* Groups "3 casos" with the carousel's prev/next buttons so they
   read as one unit, top-right of the Resultados header — see
   .cases-nav in components.css for the buttons themselves. */
.cases-header-meta { display: flex; align-items: center; gap: var(--space-2); }

@media (min-width: 64em) {
  .section-header { grid-template-columns: 12rem 1fr; align-items: end; gap: var(--space-6); }
}

/* ---------- Resultados (moved ahead of Servicios: proof before
   the offering, see README/js/config.js for the reasoning) ---------- */
.cases { max-width: var(--container-max); margin: 0 auto; padding: var(--space-96) var(--gutter) var(--space-80); }
.cases-cta { display: flex; margin-top: var(--space-5); }

/* Carousel at every breakpoint — never a grid — so card sizing stays
   one fixed, modest rule everywhere instead of switching layout
   models at 64em. Visible count only changes via each card's own
   width: ~1 + a peek on mobile, exactly 2 at 48em, exactly 3 at
   64em+ (each capped at 360px so cards never grow "huge" on wide
   screens — see README's "Resultados carousel"). Native scroll-snap
   does the swipe/drag; js/components/caseCarousel.js only adds the
   optional prev/next buttons on top. */
.cases-carousel { position: relative; }
.cases-track {
  display: flex; gap: var(--space-3);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; -ms-overflow-style: none;
}
.cases-track::-webkit-scrollbar { display: none; }
.cases-track .case-card { flex: 0 0 auto; width: min(320px, 85%); scroll-snap-align: start; }

@media (prefers-reduced-motion: no-preference) {
  .cases-track { scroll-behavior: smooth; }
}

@media (min-width: 48em) {
  .cases-track .case-card { width: min(360px, calc((100% - var(--space-3)) / 2)); }
}
@media (min-width: 64em) {
  .cases-track .case-card { width: min(360px, calc((100% - 2 * var(--space-3)) / 3)); }
}

/* ---------- Servicios ---------- */
.services { max-width: var(--container-max); margin: 0 auto; padding: var(--space-80) var(--gutter) var(--space-96); }

.services-list { display: flex; flex-direction: column; border-top: 1px solid var(--border); }

.service-row {
  display: grid; grid-template-columns: 2.5rem 1fr; gap: var(--space-1-5) var(--space-3);
  padding: var(--space-4) 0; border-bottom: 1px solid var(--border);
  /* Hero icons (and any direct #servicio-* link) jump straight to a
     row mid-list — without this the sticky .site-header lands on top
     of .service-title. See --header-height in base.css. */
  scroll-margin-top: calc(var(--header-height) + var(--space-3));
  transition: border-color var(--duration-enter) var(--ease-out);
}
/* js/components/serviceJump.js toggles this after a hero-icon click
   to confirm the landing (replayable — see the class-removal/reflow
   trick there). Pure progressive enhancement: without JS the anchor
   still lands correctly via scroll-margin-top above, it just skips
   the flash. */
.service-row.is-jump-target { border-color: var(--acento-grafico); }
.service-row.is-jump-target .service-title { color: var(--acento-texto); }

.service-num { font-family: var(--font-body); font-weight: 500; font-size: var(--text-xs); color: var(--acento-texto); grid-row: 1; }
.service-title { grid-column: 2; margin: 0; font-family: var(--font-heading); font-weight: 400; font-size: var(--text-h4); line-height: 1.25; transition: color var(--duration-enter) var(--ease-out); }
.service-title-sub { font-family: var(--font-body); font-weight: 400; font-size: var(--text-small); color: var(--text-muted); }
.service-desc { grid-column: 1 / -1; margin: 0; font-size: var(--text-small); line-height: 1.65; color: var(--text-secondary); }
.service-tags {
  grid-column: 1 / -1; margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column;
  font-family: var(--font-body); font-size: var(--text-xs); color: var(--text-muted);
}
.service-tags li { padding: var(--space-1-5) 0; border-top: 1px solid var(--border); }
.service-tags li:last-child { border-bottom: 1px solid var(--border); }

/* One CTA for the whole section, not one per card: the section's
   own thesis ("cuatro servicios que funcionan como uno") contradicts
   inviting the reader to pick a single service. Cards keep equal
   visual weight — nothing below the tag list differs between them. */
.services-cta { display: flex; margin-top: var(--space-5); }

@media (min-width: 64em) {
  .service-row { grid-template-columns: 4rem 1.1fr 1fr 11rem; gap: var(--space-2) var(--space-4); align-items: start; padding: var(--space-5) 0; }
  .service-num { padding-top: 0.375rem; }
  .service-title { grid-column: auto; }
  .service-desc { grid-column: auto; }
  .service-tags { grid-column: auto; }
}

/* ---------- Cómo lo logramos ----------
   Same stacked eyebrow/heading + two-column layout Proceso used to
   have, minus the charcoal "beat" background — this section stays
   on the regular cream surface. */
.how { max-width: var(--container-max); margin: 0 auto; padding: var(--space-80) var(--gutter); }
.how-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }

.how-title {
  margin: var(--space-2) 0 var(--space-2-5); font-family: var(--font-heading); font-weight: 400;
  font-size: var(--text-h2); line-height: 1.1; letter-spacing: -0.01em; max-width: 18ch;
}
.how-desc { margin: 0; font-size: 1.03125rem; line-height: 1.65; color: var(--text-secondary); max-width: 38ch; }

.how-points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.how-point { padding: var(--space-3) 0; border-top: 1px solid var(--border); }
.how-points .how-point:last-child { border-bottom: 1px solid var(--border); }

.how-point-title { font-family: var(--font-heading); font-weight: 500; font-size: var(--text-h4); }
.how-point-desc { margin: var(--space-1) 0 0; font-size: var(--text-small); line-height: 1.65; color: var(--text-secondary); }

@media (min-width: 64em) {
  .how-grid { grid-template-columns: 0.9fr 1.1fr; gap: var(--space-6); align-items: start; }
}

/* ---------- Nosotros ---------- */
.about { max-width: var(--container-max); margin: 0 auto; padding: var(--space-96) var(--gutter); }
.about-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); align-items: center; }

/* Pull-quote treatment: a large decorative mark carries the visual
   weight instead of styling the quote text itself any larger than
   a normal H3 — keeps it readable while still reading as "the" quote
   on the page. */
.about-quote { position: relative; margin: var(--space-2-5) 0 0; padding-left: var(--space-4); }
.about-quote::before {
  content: "\201C";
  position: absolute; left: -0.25rem; top: -0.5rem;
  font-family: var(--font-heading); font-size: 4.5rem; line-height: 1; color: var(--acento-grafico);
  font-style: normal;
}
.about-quote p {
  margin: 0; font-family: var(--font-heading); font-weight: 400;
  font-size: var(--text-h3); line-height: 1.2; letter-spacing: -0.01em; text-wrap: pretty;
}

.about-gallery { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2-5); }

@media (min-width: 64em) {
  .about-grid { grid-template-columns: 1.15fr 0.85fr; gap: var(--space-6); }
}

/* ---------- Contacto ----------
   The one other section given the narrow-label / wide-content
   asymmetric treatment: the reference facts (WhatsApp/email/hours)
   sit in the narrow column like a colophon, the pitch and the CTA
   carry the wide one. Single column on mobile. */
.contact { border-top: 1px solid var(--border); }
.contact-content {
  max-width: var(--container-max); margin: 0 auto; padding: var(--space-88) var(--gutter) var(--space-96);
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4);
}

.contact-title {
  margin: 0; font-family: var(--font-heading); font-weight: 400;
  font-size: var(--text-h2); line-height: 1.1; letter-spacing: -0.01em; max-width: 14ch;
}
.contact-desc { margin: var(--space-2-5) 0 var(--space-4); font-size: 1.0625rem; line-height: 1.65; color: var(--text-secondary); max-width: 40ch; }

.contact-content .cta-followup { margin-top: var(--space-2); max-width: 40ch; }

.contact-list { display: flex; flex-direction: column; }
.contact-item {
  display: flex; flex-direction: column; justify-content: center; gap: 0.1875rem;
  min-height: var(--tap-min); padding: var(--space-2) 0; border-top: 1px solid var(--border);
  font-family: var(--font-body);
}
.contact-list .contact-item:last-child { border-bottom: 1px solid var(--border); }
.contact-item-label { font-size: var(--text-xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); }
.contact-item-value { font-size: var(--text-body); color: var(--text); }
a.contact-item:hover .contact-item-value { color: var(--link-hover); }

.contact-list-wrap { display: flex; flex-direction: column; gap: var(--space-1-5); }

@media (min-width: 64em) {
  .contact-content { grid-template-columns: 18rem 1fr; gap: var(--space-6); align-items: start; }
  .contact-list-wrap { order: -1; padding-top: var(--space-1); }
}

/* ---------- Footer ---------- */
.site-footer { background: var(--bg); color: var(--text-secondary); border-top: 1px solid var(--border); }
.footer-inner {
  max-width: var(--container-max); margin: 0 auto; padding: var(--space-4) var(--gutter);
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); font-size: var(--text-small);
}
.footer-logo { display: flex; align-items: center; gap: 0.625rem; color: var(--text); }
.footer-logo-dot { width: 0.6875rem; height: 0.6875rem; border-radius: 50%; background: var(--acento-grafico); }
.footer-logo-text { font-family: var(--font-heading); font-size: 1.125rem; }
.footer-links { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2) var(--space-3); }
.footer-links a { display: inline-flex; align-items: center; min-height: var(--tap-min); }
.footer-copy { font-family: var(--font-body); font-size: var(--text-xs); }

@media (min-width: 48em) {
  .footer-inner { flex-direction: row; justify-content: space-between; text-align: left; gap: var(--space-4); }
  .footer-links a { min-height: auto; }
}
