/* =============================================================
   1. Tokens
   ============================================================= */
:root {
  --bg: #E5E4E1;
  --bg-alt: #E5E4E1;
  --paper: #E5E4E1;
  --ink: #3C3C3B;
  --ink-soft: #3C3C3B;
  --ink-mute: rgba(60, 60, 59, 0.75);
  --line: rgba(60, 60, 59, 0.16);
  --line-strong: rgba(60, 60, 59, 0.32);

  --panel-bg: #E5E4E1;
  --panel-ink: #3C3C3B;
  --panel-ink-mute: rgba(60, 60, 59, 0.65);
  --panel-line: rgba(60, 60, 59, 0.18);

  --overlay: rgba(60, 60, 59, 0.4);

  --font-display: "Archivo", "Segoe UI", sans-serif;
  --font-body: "Inter", "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", "Consolas", monospace;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
  --ease-soft: cubic-bezier(0.25, 0.46, 0.45, 0.94);

  --container: min(1440px, 92vw);
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --nav-h: 84px;
}

@property --fill { syntax: "<percentage>"; inherits: false; initial-value: 0%; }

/* =============================================================
   2. Reset & base
   ============================================================= */
*, *::before, *::after { box-sizing: border-box; margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  tab-size: 2;
  overflow-x: clip;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
  overscroll-behavior-y: none;
}
img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
button { font: inherit; color: inherit; cursor: pointer; border: 0; background: none; }
a { color: inherit; text-decoration: none; }
p { text-wrap: pretty; }
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--ink);
  text-wrap: balance;
  line-height: 1.04;
  letter-spacing: -0.01em;
}
ul, ol { list-style: none; padding: 0; }
::selection { background: var(--ink); color: var(--bg); }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}
.side-panel :focus-visible,
.footer :focus-visible { outline-color: var(--ink); }

.skip-link {
  position: fixed; top: -100px; left: 1rem; z-index: 10001;
  padding: .7rem 1.1rem; background: var(--ink); color: var(--bg);
  font-family: var(--font-display); font-size: .85rem; font-weight: 600;
  transition: top .3s var(--ease-out);
}
.skip-link:focus { top: 1rem; }

/* =============================================================
   3. Utilities
   ============================================================= */
.eyebrow {
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin-bottom: .9rem;
}

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .6rem;
  font-family: var(--font-display); font-weight: 600; font-size: .82rem;
  letter-spacing: 0.06em; text-transform: uppercase;
  padding: 1rem 1.8rem;
  transition: background-color .4s var(--ease-out), color .4s var(--ease-out), border-color .4s var(--ease-out), transform .3s var(--ease-soft);
}
.btn-primary {
  background: var(--ink); color: var(--bg); position: relative; overflow: hidden;
  box-shadow: 0 1px 0 rgba(19,18,17,0);
}
.btn-primary:active { transform: translateY(-1px); transition-duration: .12s; }
.btn-outline {
  border: 1px solid currentColor;
}
/* Scoped to real hover capability — an unscoped :hover on a link/button
   makes iOS/iPadOS Safari treat the first tap as "show hover state" and
   require a second tap to actually activate it, which reads as "the link
   doesn't work" on touch devices. */
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover { transform: translateY(-3px); box-shadow: 0 18px 34px -12px rgba(19, 18, 17, 0.45); }
  .btn-outline:hover { background: var(--ink); color: var(--bg); border-color: var(--ink); }
}

/* =============================================================
   4. Reveal / typography effects
   ============================================================= */
[data-reveal], [data-reveal-hero] {
  opacity: 0; transform: translateY(34px) scale(.985);
  transition: opacity 1.8s cubic-bezier(0.16, 1, 0.3, 1), transform 1.8s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-reveal].is-revealed, [data-reveal-hero].is-revealed { opacity: 1; transform: none; }
[data-reveal-hero] {
  transform: translateY(44px) scale(.97);
  transition: opacity 2.2s cubic-bezier(0.16, 1, 0.3, 1), transform 2.2s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-reveal-mask] {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.8s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-reveal-mask].is-revealed { clip-path: inset(0); }

.split-word { display: inline-block; will-change: transform, opacity; }

/* =============================================================
   5. Loader — initial load AND every slide transition (replayable)
   ============================================================= */
.loader {
  position: fixed; inset: 0; z-index: 10000;
  display: grid; place-items: center;
  opacity: 1; pointer-events: auto;
  transition: opacity .4s var(--ease-out);
}
.loader.is-out { opacity: 0; pointer-events: none; }
   Must clear well after the JS-driven intro's own longest possible run
   (~3.8s, the first-load two-beat sequence) or this would cut that
   sequence off partway through on every single first visit. */
html:not(.is-ready) .loader { animation: loaderCssFailsafe 0s 8s forwards; }
@keyframes loaderCssFailsafe { to { opacity: 0; pointer-events: none; } }

/* The opaque background lives on its own full-screen panel instead of on
   .loader itself — that's what lets the exit wipe off to one side (right to
   left) instead of just fading out in place. Opaque by default, no
   transform, so it's still the very first thing painted — zero chance of a
   flash of the page underneath before JS runs. */
.loader-panel {
  position: absolute; inset: 0;
  background: var(--bg);
  transition: transform 2.2s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
/* Reveals right to left: the panel itself exits toward the left, so its
   right edge clears the viewport first and the uncovering sweeps leftward. */
.loader.is-hiding .loader-panel { transform: translateX(-100%); }
/* Slide-to-slide navigation (not the first-load intro) opens much faster —
   the intro only plays once; every click after that should feel snappy,
   not like re-watching it. */
.loader.is-quick .loader-panel { transition-duration: .6s; }
/* La transición de la cortina del inicio al home es ultra suave, pausada y elegante (2.2s). */
.loader.is-brief .loader-panel { transition-duration: 2.2s; transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }

/* Positioning context for the intro's stages below, which stack as
   absolutely-centered overlays (only one is ever visible at a time) rather
   than sitting in normal flow — that's what lets beat 2 occupy the exact
   same spot beat 1 just vacated, with no layout jump between them. Sized
   generously enough to fit either stage's stacked content (mark + tagline,
   or mark + wordmark) without clipping. */
.loader-inner { position: relative; width: min(90vw, 440px); min-height: clamp(230px, 46vw, 340px); }

/* Beat 1 (mark + tagline) and beat 2 (mark + wordmark) — two separate,
   self-contained stages (matching the two separate reference images), each
   a placeholder standing in for its own pending PNG (see the markup
   comments in index.html). Both share this one wrapper style; only one is
   ever .is-visible at a time. Once a real PNG replaces either one's inner
   markup, no CSS here needs to change — it's still just one element fading
   in and out. */
.loader-stage {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .85rem;
  opacity: 0; filter: blur(8px);
  transition: opacity 1s var(--ease-soft), filter 1s var(--ease-soft);
}
.loader-stage.is-visible { opacity: 1; filter: blur(0); }
.loader.is-brief .loader-stage { transition-duration: .5s; }
.loader-mark-img { width: clamp(156px, 31.2vw, 247px); height: auto; display: block; }

.loader-tagline {
  font-family: var(--font-display); font-weight: 700; font-size: clamp(.78rem, 2.5vw, 1.24rem);
  letter-spacing: 0.01em; text-transform: uppercase; white-space: nowrap;
  color: var(--ink); text-align: center; margin: 0;
}
/* Wordmark: a short rule above the letters, matching the brand's own
   wordmark artwork exactly — line + text together, shifted right of the
   mark's own center. Sized and positioned to sit under the H in the mark
   above (the H is 54.2% of the mark's width, centered 22.4% right of the
   mark's midline — measured directly from the logo file, not eyeballed). */
.loader-wordmark {
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  transform: translateX(clamp(35px, 7vw, 56px));
}
.loader-wordmark::before {
  content: ""; display: block; height: 1px; background: var(--ink);
  width: clamp(84.5px, 16.9vw, 134px);
}
.loader-name {
  display: flex; margin: 0; white-space: nowrap;
  font-family: var(--font-display); font-size: clamp(11px, 2.1vw, 16px); font-weight: 700;
  letter-spacing: 0.06em; color: var(--ink);
}

/* Slide-to-slide navigation only — the mark alone, overlaying the same
   centered spot the intro stages use. Already the real, final asset. */
.loader-mark {
  position: absolute; inset: 0; margin: auto;
  width: clamp(156px, 31.2vw, 247px); height: auto;
  opacity: 0; filter: blur(10px);
  transition: opacity 1s var(--ease-soft), filter 1s var(--ease-soft);
}
.loader-mark.is-visible { opacity: 1; filter: blur(0); }
.loader.is-quick .loader-mark { transition-duration: .3s; }
/* Deliberately NOT gated by prefers-reduced-motion: this is a one-time
   entrance sequence (not a loop, not autoplay video), and Windows ships
   reduced-motion ON by default in many setups — gating it would mean most
   Windows visitors never see it at all. */

/* =============================================================
   6. Header / nav
   ============================================================= */
.nav {
  position: fixed; top: 0; inset-inline: 0; z-index: 950;
  height: var(--nav-h);
  color: #E5E4E1;
  transition: background-color .5s var(--ease-out), color .5s var(--ease-out), box-shadow .5s var(--ease-out);
}
.nav.is-scrolled {
  background: rgba(229, 228, 225, 0.92);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  color: #3C3C3B;
  box-shadow: 0 1px 0 var(--line);
}
html[data-active-slide="2"] .nav.is-scrolled {
  background: #141311;
  backdrop-filter: none; -webkit-backdrop-filter: none;
  color: #E5E4E1;
  box-shadow: none;
}
.nav.is-panel-open {
  color: #E5E4E1 !important;
  background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none;
}
.nav-inner {
  height: 100%;
  max-width: var(--container); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between;
}
.nav-logo { display: flex; align-items: center; gap: .65rem; }
.nav-logo-jh {
  width: 80px;
  height: auto;
  display: block;
  transition: filter .5s var(--ease-out), opacity .5s var(--ease-out), width .5s var(--ease-out);
}
.nav:not(.is-scrolled) .nav-logo-jh,
html[data-active-slide="2"] .nav-logo-jh,
.nav.is-panel-open .nav-logo-jh {
  filter: invert(1) !important;
  opacity: 0.95 !important;
  width: 80px;
}
html[data-active-slide="1"] .nav-logo-jh,
html[data-active-slide="3"] .nav-logo-jh {
  filter: none !important;
  opacity: 0.78 !important;
}
.nav-logo-name { font-family: var(--font-mono); font-size: .62rem; letter-spacing: 0.16em; opacity: .82; display: none; }
@media (min-width: 540px) { .nav-logo-name { display: inline; } }

.nav-actions { display: flex; align-items: center; gap: .3rem; }

/* Separate "go to catálogo" button — its own control, own function, not
   part of the menu toggle. */
.nav-next-btn {
  position: relative; display: grid; place-items: center;
  width: 40px; height: 34px; padding: 0;
}
.nav-next-icon {
  width: 24px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  transition: transform .3s var(--ease-out);
}
.nav-next-btn:active .nav-next-icon { transform: scale(.92); }
@media (hover: hover) and (pointer: fine) {
  .nav-next-btn:hover .nav-next-icon { transform: scale(1.12); }
}

.nav-menu-btn {
  position: relative; display: grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
}
/* Real SVG icons (not a CSS morph) — the X is drawn, never "almost" an X. */
.nav-menu-icon {
  position: absolute; inset: 0; margin: auto;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
  transition: opacity .3s var(--ease-out), transform .35s var(--ease-out);
}
.nav-menu-icon--lines { width: 20px; height: 16px; opacity: 1; transform: scale(1) rotate(0deg); }
.nav-menu-icon--close { width: 22px; height: 22px; opacity: 0; transform: scale(.6) rotate(-45deg); }
@media (hover: hover) and (pointer: fine) {
  .nav-menu-btn:hover .nav-menu-icon--lines { transform: scale(1.1); }
}
.nav-menu-btn[aria-expanded="true"] .nav-menu-icon--lines { opacity: 0; transform: scale(.6) rotate(20deg); }
.nav-menu-btn[aria-expanded="true"] .nav-menu-icon--close { opacity: 1; transform: scale(1) rotate(0deg); }

/* =============================================================
   6.5 Auto-advance progress — fills while idle, then advances to the next slide
   ============================================================= */
.auto-progress {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
  height: 2px; background: transparent;
}
.auto-progress span {
  display: block; height: 100%; width: 0%;
  background: var(--ink);
  mix-blend-mode: difference;
}
html[data-active-slide="0"] .auto-progress span { background: var(--paper); mix-blend-mode: normal; }
.auto-progress.is-filling span { transition: width var(--auto-advance-duration, 7s) linear; width: 100%; }

/* =============================================================
   7. Panel backdrop + side panels
   ============================================================= */
.panel-backdrop {
  position: fixed; inset: 0; z-index: 850;
  background: var(--overlay);
  opacity: 0; pointer-events: none;
  transition: opacity .5s var(--ease-out);
  will-change: opacity;
}
.panel-backdrop.is-active { opacity: 1; pointer-events: auto; }

.side-panel {
  position: fixed; top: 0; right: 0; height: 100dvh; height: 100svh;
  color: var(--panel-ink);
  z-index: 900;
  overflow-y: auto;
}

/* Catalog viewer — full-viewport, one product at a time, arrow navigation */
.side-panel--catalog {
  inset: 0; width: 100%; left: 0;
  background: var(--panel-bg);
  z-index: 910;
  opacity: 0;
  pointer-events: none;
  /* This panel is full-viewport (inset: 0) even while "closed" — without the
     transform below, only opacity hides it, and the browser treats every
     product photo inside as on-screen, fetching all of them (500KB+) on
     first paint despite nothing being visible yet. Pushed far off-screen
     instead so native lazy-loading correctly defers them. The transform
     change is delayed until the opacity fade-out has fully finished, so it
     stays invisible-but-onscreen for the whole fade — visually identical to
     a plain opacity transition, just off-screen once nothing shows it. */
  transform: translateX(300vw);
  transition: opacity .9s var(--ease-soft), transform 0s .9s;
  /* Promotes this to its own compositor layer so the fade is a cheap alpha
     blend instead of the browser repainting all the product photos/sliders
     underneath on every frame — this is what made opening it feel janky. */
  will-change: opacity;
}
.side-panel--catalog.is-open {
  opacity: 1; pointer-events: auto; transform: none;
  transition: opacity .9s var(--ease-soft), transform 0s;
}

/* Menu — full-viewport dimmed overlay, background content visible through it */
.side-panel--menu {
  inset: 0; width: 100%; left: 0;
  background: rgba(14, 13, 12, 0.92);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  color: #E5E4E1;
  opacity: 0;
  pointer-events: none;
  transition: opacity .8s var(--ease-soft);
  will-change: opacity;
}
.side-panel--menu.is-open {
  opacity: 1;
  pointer-events: auto;
}

.side-panel-inner {
  min-height: 100%;
  padding: clamp(1.5rem, 4vw, 2.6rem);
  display: flex; flex-direction: column;
}
.side-panel--menu .side-panel-inner { padding-top: calc(var(--nav-h) + 1rem); justify-content: flex-start; }

/* Nav links center in the space above the footer note, which stays pinned to the bottom. */
.side-panel-nav-wrap { flex: 1; display: flex; align-items: center; min-height: 0; }

/* Menu list */
.menu-list { display: flex; flex-direction: column; gap: .2rem; max-width: 480px; margin-inline: auto; width: 100%; }
.menu-link {
  position: relative;
  display: flex; align-items: center;
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.3rem, 3.2vw, 1.9rem);
  letter-spacing: -0.01em;
  padding: .5rem 0;
  color: #E5E4E1 !important;
  transform-origin: left center;
  opacity: 0; transform: translateY(22px) scale(1);
  transition: opacity .7s var(--ease-out), transform .5s var(--ease-out), color .4s var(--ease-out), text-shadow .4s var(--ease-out);
}
.side-panel--menu.is-open .menu-link { opacity: 1; transform: translateY(0) scale(1); }
.side-panel--menu.is-open .menu-link:nth-child(1) { transition-delay: .3s; }
.side-panel--menu.is-open .menu-link:nth-child(2) { transition-delay: .38s; }
.side-panel--menu.is-open .menu-link:nth-child(3) { transition-delay: .46s; }
.side-panel--menu.is-open .menu-link:nth-child(4) { transition-delay: .54s; }

/* Don't show a link to the section you're already on. */
html[data-active-slide="0"] .menu-link[data-slide-link="0"],
html[data-active-slide="1"] .menu-link[data-slide-link="1"],
html[data-active-slide="2"] .menu-link[data-slide-link="2"],
html[data-active-slide="3"] .menu-link[data-slide-link="3"] { display: none; }

/* Spotlight: hovering one link dims + shrinks the others and "lights up" the one under the cursor. */
@media (hover: hover) and (pointer: fine) {
  .side-panel--menu.is-open .menu-list:hover .menu-link { color: rgba(229, 228, 225, 0.55) !important; opacity: .6; transform: translateY(0) scale(.92); }
  .side-panel--menu.is-open .menu-list .menu-link:hover {
    color: #E5E4E1 !important; opacity: 1; transform: translateY(0) scale(1.08);
    text-shadow: 0 0 24px rgba(229, 228, 225, 0.7);
  }
  .side-panel--menu.is-open .menu-list .menu-link:hover,
  .side-panel--menu.is-open .menu-list:hover .menu-link { transition-delay: 0s; }
}

.side-panel-bottom {
  margin-top: 3rem; padding-top: 1.6rem; border-top: 1px solid rgba(229, 228, 225, 0.2);
  opacity: 0; transition: opacity 1s var(--ease-out) .8s;
}
.side-panel--menu.is-open .side-panel-bottom { opacity: 1; }
.side-panel-note { font-size: .85rem; color: rgba(229, 228, 225, 0.85) !important; margin-bottom: .9rem; }
.side-panel-social { display: flex; gap: 1.4rem; }
.side-panel-social a { font-family: var(--font-mono); font-size: .72rem; letter-spacing: 0.08em; text-transform: uppercase; color: #E5E4E1 !important; position: relative; }
.side-panel-social a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform .4s var(--ease-out); }
/* Unscoped :hover on a link makes iPad/iPhone Safari swallow the first tap
   to "show" the hover state, needing a second tap to actually follow it —
   which is exactly the "Instagram link doesn't work on iPad" report. */
@media (hover: hover) and (pointer: fine) {
  .side-panel-social a:hover::after { transform: scaleX(1); transform-origin: left; }
}

.nav.is-panel-open {
  color: #E5E4E1 !important;
}
.nav.is-panel-open .nav-logo-jh {
  filter: invert(1) !important;
  width: 80px;
}

/* Catalog panel categories */
.side-panel-inner--catalog { padding-inline: 0; padding-top: calc(var(--nav-h) + 1rem); }
.cat-panel-category { display: none; flex: 1; flex-direction: column; min-height: 0; }
.cat-panel-category.is-active { display: flex; }
.cat-panel-header {
  padding-inline: clamp(1.5rem, 4vw, 2.6rem); margin-bottom: clamp(1.4rem, 4vh, 2.2rem);
  text-align: center; max-width: 720px; margin-inline: auto; width: 100%;
}
.cat-panel-eyebrow, .cat-panel-title, .cat-panel-desc {
  opacity: 0; transform: translateY(18px);
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
.side-panel--catalog.is-open .cat-panel-eyebrow { opacity: 1; transform: none; transition-delay: .32s; }
.side-panel--catalog.is-open .cat-panel-title { opacity: 1; transform: none; transition-delay: .4s; }
.side-panel--catalog.is-open .cat-panel-desc { opacity: 1; transform: none; transition-delay: .48s; }
.cat-panel-eyebrow { font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.14em; color: var(--panel-ink-mute); margin-bottom: .6rem; }
.cat-panel-title { font-size: clamp(1.8rem, 4.6vw, 2.6rem); margin-bottom: .7rem; font-weight: 500; }
.cat-panel-desc { color: var(--panel-ink-mute); font-size: .92rem; max-width: 42ch; margin-inline: auto; }

/* =============================================================
   8. Slider — full-screen, one product at a time
   ============================================================= */
.slider {
  position: relative; flex: 1; display: flex; flex-direction: column; justify-content: center; min-height: 0;
  opacity: 0; transition: opacity .8s var(--ease-out) .5s;
}
.side-panel--catalog.is-open .slider { opacity: 1; }
.slider-viewport {
  padding-inline: clamp(3.6rem, 9vw, 4.6rem);
  padding-block: .2rem clamp(2.4rem, 6vh, 3.2rem);
}
.slider-track {
  display: flex; overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  scrollbar-width: none; -ms-overflow-style: none;
  max-width: min(760px, 62vh, 100%); margin-inline: auto; width: 100%;
}
.slider-track::-webkit-scrollbar { display: none; }
/* scroll-snap-stop forces a fast swipe to land on the very next slide
   instead of flying past it — without this, a quick flick can leave the
   scroller resting mid-way between two products, showing a sliver of both
   (their swatch color and photo) at once instead of one clean product. */
/* background-color here is pure insurance, zero layout cost: if a sub-pixel
   scroll-snap seam ever exposes a hairline gap at a slide's edge on some
   device/browser, it shows this same dark panel color instead of whatever
   happens to be behind the track, so it reads as "nothing" instead of a
   visible foreign-colored line. */
.product-slide { flex: 0 0 100%; scroll-snap-align: start; scroll-snap-stop: always; min-width: 0; background: var(--panel-bg); }

.product-card { display: flex; flex-direction: column; }
.product-swatch { aspect-ratio: 3.2 / 1; border: 1px solid var(--panel-line); transition: transform .6s var(--ease-soft); }
.product-photos { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; margin-top: .7rem; overflow: hidden; }
/* Product photos stay put — no dimming for off-screen slides, no hover zoom.
   Zoom is reserved for the big catalog entry cards (.cat-card-media), not
   the photos inside a category once you're already browsing it. */
.product-photos img { aspect-ratio: 1 / 1; object-fit: cover; }
/* Some categories (currently: most of Azulejos Geométricos and all of
   Azulejos Clásicos) only have one real placeholder photo per product yet —
   both grid slots point at the exact same file (verified by content hash,
   not just filename) rather than a real wide shot + detail shot. Showing
   that file twice, identically, read as broken/duplicated. The source
   photos are themselves already square (550x550), matching this grid's own
   1:1 box exactly — so a plain object-position shift on the first slot has
   nothing to reposition (cover never crops when source and box share the
   same ratio). Scaling the second slot up around an off-center origin does
   still crop it, into a different part of the same photo, which is what
   actually makes the two slots read as a wide shot + a zoomed detail
   instead of an identical pair.
   The scaled img MUST be clipped by a wrapper of its own (.product-photo-clip),
   not by .product-photos' own overflow:hidden above — a transform enlarges
   the image in place without affecting layout, so with only the outer grid
   container clipping, the extra size spilled sideways past the gap into the
   neighboring cell, showing a sliver of this photo over the OTHER slot. A
   wrapper sized to exactly this one cell, clipped on its own, contains the
   zoom to itself no matter how the two cells are sized. Purely cosmetic:
   drop [data-duplicate-pair] once a real second photo exists and unwrap the
   plain <img> — both slots go back to plain object-fit:cover, no override. */
.product-photo-clip { display: block; aspect-ratio: 1 / 1; overflow: hidden; }
.product-photo-clip img { width: 100%; height: 100%; }
.product-photos[data-duplicate-pair] .product-photo-clip img { transform: scale(1.32); transform-origin: 66% 60%; }
.product-info { margin-top: 1.4rem; text-align: center; }
.product-name { font-size: clamp(1.25rem, 2.2vw, 1.6rem); font-weight: 600; margin-bottom: .45rem; }
.product-code { font-family: var(--font-mono); font-size: .8rem; color: var(--panel-ink-mute); letter-spacing: 0.08em; margin-bottom: 1.4rem; }
.product-cta { color: var(--panel-ink); border-color: var(--panel-line); min-width: 210px; }
@media (hover: hover) and (pointer: fine) {
  .product-cta:hover { background: var(--panel-ink); color: var(--panel-bg); border-color: var(--panel-ink); }
}

.slider-btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: clamp(44px, 5vw, 56px); height: clamp(44px, 5vw, 56px); flex-shrink: 0; border-radius: 50%;
  border: 1px solid var(--panel-line);
  background: var(--panel-bg);
  display: grid; place-items: center;
  font-size: 1.05rem;
  transition: background-color .35s var(--ease-out), color .35s var(--ease-out), border-color .35s, transform .35s var(--ease-out), opacity .35s var(--ease-out);
  z-index: 2;
  /* Sits visually on top of the swipeable track underneath it. Without this,
     a touch here can get read as the start of a pan/scroll gesture on the
     track instead of a tap on the button — the first touch "does nothing"
     and it takes a second, more deliberate tap to actually register as a
     click. This forces it to always resolve as a plain tap, immediately. */
  touch-action: manipulation;
}
.slider-prev { left: clamp(.8rem, 3vw, 2.2rem); }
.slider-next { right: clamp(.8rem, 3vw, 2.2rem); }
/* This unscoped :hover was very likely the real cause of the earlier
   "have to tap the arrow twice" reports — touch-action:manipulation above
   fixes the pan-vs-tap ambiguity, but a bare :hover on a button is a
   SEPARATE reason iOS/iPadOS Safari can eat the first tap (to show the
   hover state) and only act on the second. Both had to be fixed. */
@media (hover: hover) and (pointer: fine) {
  .slider-btn:hover { background: var(--panel-ink); color: var(--panel-bg); border-color: var(--panel-ink); transform: translateY(-50%) scale(1.08); }
}
.slider-btn.is-edge { opacity: 0; pointer-events: none; transform: translateY(-50%) scale(.85); }
.slider-dots {
  position: absolute; left: 50%; bottom: clamp(.6rem, 3vh, 1.4rem); transform: translateX(-50%);
  display: flex; align-items: center; gap: .45rem;
  max-width: min(90vw, 500px); overflow-x: auto; scrollbar-width: none;
}
.slider-dots::-webkit-scrollbar { display: none; }
.slider-dot {
  flex: 0 0 auto;
  width: 14px; height: 3px; background: var(--panel-line);
  transition: width .45s var(--ease-out), background-color .45s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .slider-dot:hover { background: var(--panel-ink-mute); }
}
.slider-dot.is-active { width: 32px; background: var(--panel-ink); }
@media (max-width: 539px) {
  .slider-viewport { padding-inline: clamp(3rem, 14vw, 3.6rem); }
  .product-cta { width: 100%; }
}

/* =============================================================
   8.5 Slides — one section visible at a time, no free scroll
   ============================================================= */
.slide {
  position: fixed; inset: 0; height: 100dvh; height: 100svh;
  opacity: 0; pointer-events: none;
  /* This swap always happens while the opaque loader still fully covers the
     screen, so it was never meant to be a visible crossfade — but the loader's
     own wipe-reveal (1.1s, see .loader.is-hiding) starts uncovering the top of
     the screen immediately, faster than this .7s fade could finish underneath
     it. That exposed a real mid-blend of the outgoing and incoming slide's
     full-bleed photos — two different images ghosted together right at the
     wipe boundary. Instant swap removes the window where that's visible. */
  transition: opacity 0s;
  z-index: 1;
}
.slide.is-active { opacity: 1; pointer-events: auto; z-index: 2; }
/* Only for the loader-free "go to catálogo" jump — a real crossfade this
   time, added just for that one transition and removed right after, so it
   never lingers to interfere with the instant loader-driven swap above. */
.slide.is-direct-swap { transition: opacity .6s var(--ease-soft); }
.slide-scroll {
  height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: calc(var(--nav-h) + 2rem) var(--gutter) clamp(3rem, 8vh, 5rem);
}
/* Catálogo is meant to read as one screen, no scroll needed to see the
   cards below the heading — tighter top/bottom padding than the default
   .slide-scroll leaves room for the cards to actually fit. */
.catalogo .slide-scroll {
  display: flex; flex-direction: column; justify-content: center;
  padding: calc(var(--nav-h) + .6rem) var(--gutter) 1rem;
}

/* =============================================================
   9. Hero
   ============================================================= */
.hero {
  display: flex; align-items: flex-end;
  overflow: hidden; isolation: isolate;
  background: transparent !important;
}
.hero-media { position: absolute; top: -18%; left: 0; right: 0; height: 136%; z-index: -2; overflow: hidden; background: transparent !important; }
/* Three photos stacked in the same box, crossfading in a loop (see
   startHeroLoop in main.js) — only one is opacity:1 ("is-active") at a
   time. Each still runs its own slow, continuous Ken Burns breathe
   regardless of visibility, so there's never a "just switched on, snapped
   to the start of the zoom" moment when it fades in.
   Duration deliberately shorter than one full loop cycle (HERO_LOOP_MS in
   main.js, 6.5s) — at 16s the zoom barely progressed before the photo
   crossfaded away again, reading as "not zooming" even though it was. */
.hero-media-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  object-position: center;
  opacity: 0;
  transition: opacity 1.6s var(--ease-soft);
  animation: heroZoomIn 8s var(--ease-soft) infinite alternate;
}
@media (max-width: 768px) {
  .hero-media-img {
    object-position: center !important;
  }
  /* On a narrow/portrait crop, centering this specific photo lands on the
     grey boundary wall and pool instead of the celosía itself — the wall
     that's actually the point of the photo sits in the left ~45% of the
     frame. Biasing the crop left keeps it in frame on mobile. */
  .hero-media-img[src*="hero-celosias"] {
    object-position: 15% center !important;
  }
}
.hero-media-img.is-active { opacity: 1; }
@keyframes heroZoomIn {
  0% { transform: scale(1.08); }
  100% { transform: scale(1.18); }
}
/* Shared-element ghost for the Hero → Catálogo jump (see
   heroToCatalogoTransition in main.js) — a plain <img> that starts sized
   exactly like the real hero photo and is animated (left/top/width/height,
   not transform) down into the Celosías card's exact box. Sits above
   everything, including the loader, while it flies. */
.hero-flip-ghost {
  position: fixed; z-index: 10500;
  object-fit: cover; pointer-events: none;
}
.hero-flip-ghost.is-flying {
  transition: left .9s var(--ease-soft), top .9s var(--ease-soft),
    width .9s var(--ease-soft), height .9s var(--ease-soft);
}
.hero-overlay {
  display: none !important;
}
.hero-content {
  position: relative;
  max-width: var(--container); margin-inline: auto; width: 100%;
  padding: 0 var(--gutter) clamp(4rem, 10vh, 7rem);
  color: #E5E4E1;
}
.hero-kicker {
  display: flex; align-items: center; gap: .9rem;
  font-family: var(--font-mono); font-size: .74rem; letter-spacing: 0.2em; text-transform: uppercase;
  margin-bottom: 1.3rem; color: #E5E4E1; opacity: .92;
}
.hero-kicker::before,
.hero-kicker::after { content: ""; display: inline-block; width: 34px; height: 1px; background: currentColor; opacity: .65; }
.hero-title {
  font-family: var(--font-display);
  font-size: clamp(1.8rem, 4vw, 3.2rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.12;
  max-width: 18ch;
  color: #E5E4E1;
  margin-bottom: 1.1rem;
  text-shadow: 0 4px 24px rgba(10, 9, 8, 0.5);
}
.hero-subtitle { font-size: clamp(0.92rem, 1.4vw, 1.1rem); font-weight: 300; max-width: 44ch; opacity: .92; }

/* =============================================================
   10. Nosotros
   ============================================================= */
#nosotros, #catalogo, #contacto { scroll-margin-top: var(--nav-h); }

.nosotros { background: var(--bg); }
.nosotros-grid {
  max-width: var(--container); margin-inline: auto;
  display: grid; gap: clamp(2.5rem, 6vw, 5.5rem);
}
@media (min-width: 960px) { .nosotros-grid { grid-template-columns: 0.9fr 1fr; align-items: center; } }
.nosotros-media {
  position: relative; aspect-ratio: 4 / 5; overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: 0 40px 80px -30px rgba(19, 18, 17, 0.25);
}
.nosotros-media img {
  width: 100%; height: 100%; object-fit: cover;
  transform: none !important;
}
.nosotros-media::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 38%;
  background: linear-gradient(to top, rgba(19, 18, 17, 0.4), transparent);
  pointer-events: none;
}
.nosotros-media-caption {
  position: absolute; z-index: 1; left: clamp(1.1rem, 3vw, 1.6rem); bottom: clamp(1rem, 3vw, 1.4rem);
  font-family: var(--font-mono); font-size: .7rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--panel-ink);
}
.nosotros-title { font-size: clamp(2.1rem, 4.4vw, 3.1rem); font-weight: 500; letter-spacing: -0.02em; margin-bottom: 1.7rem; }
.nosotros-text p { color: var(--ink-soft); max-width: 52ch; margin-bottom: 1.15rem; font-weight: 300; font-size: 1.05rem; }
.nosotros-stats-label {
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-mute); margin-top: 2.6rem; padding-top: 2.2rem; border-top: 1px solid var(--line);
}
.nosotros-stats { display: flex; flex-wrap: wrap; gap: 0; margin-top: 1.1rem; }
.stat { padding-right: clamp(1.8rem, 5vw, 3.2rem); margin-right: clamp(1.8rem, 5vw, 3.2rem); border-right: 1px solid var(--line); }
.stat:last-child { border-right: 0; padding-right: 0; margin-right: 0; }
.stat dt { font-family: var(--font-display); font-size: clamp(2.4rem, 4.4vw, 3.4rem); font-weight: 700; line-height: 1; letter-spacing: -0.02em; }
.stat dt::after { content: "+"; font-size: 1.2rem; margin-left: 1px; opacity: .55; }
.stat dd { font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-mute); margin-top: .5rem; }

/* =============================================================
   11. Catálogo
   ============================================================= */
.catalogo { background: #141311; color: #E5E4E1; }
.catalogo .slide-scroll { display: flex; flex-direction: column; justify-content: center; padding: calc(var(--nav-h) + 1.5rem) var(--gutter) clamp(1.8rem, 4vh, 3rem); }
.catalogo-head { max-width: var(--container); margin-inline: auto; width: 100%; text-align: center; margin-bottom: clamp(1rem, 2.2vh, 1.6rem); }
.catalogo-head .eyebrow { color: rgba(229, 228, 225, 0.7); font-family: var(--font-mono); font-size: .72rem; letter-spacing: 0.16em; text-transform: uppercase; margin-bottom: .3rem; }
.catalogo-head h2 { font-size: clamp(2rem, 4.4vw, 3.4rem); font-weight: 500; letter-spacing: -0.02em; max-width: 18ch; margin-inline: auto; color: #E5E4E1; margin-top: .1rem; }

/* Overview slider: each card only takes a fraction of the track's width, so
   the next/previous collection peeks in at the edges — a gallery you drag
   through with the mouse or scroll, no arrow buttons and no separate dot
   per collection, just one continuous bar showing where you are. */
/* width:100% is load-bearing here, not decorative — .slide-scroll is a
   column flex container (for the "fits on one screen" centering) and
   margin-inline:auto on a flex item defeats stretch, collapsing this down
   to its own shrink-to-fit size (the cards were rendering ~47px wide
   instead of filling the row) without an explicit width to override it. */
.slider--catalogo { width: 100%; max-width: var(--container); margin-inline: auto; }
/* No prev/next buttons here (unlike the product sliders), so the track
   doesn't need to leave room on either side for them — just a bit of
   breathing room for the peeking cards. */
.slider--catalogo .slider-viewport { padding-inline: clamp(1rem, 3vw, 1.6rem); }
.slider--catalogo .slider-track { max-width: 100%; gap: clamp(.8rem, 2vw, 1.4rem); }
.cat-slide { flex: 0 0 76%; }
@media (min-width: 640px) { .cat-slide { flex: 0 0 52%; } }
@media (min-width: 960px) { .cat-slide { flex: 0 0 34%; } }

/* One continuous, draggable bar instead of dots — click anywhere on it or
   drag the thumb to scrub through the three collections. Width/position of
   the thumb are set inline by JS from the track's real scroll position. */
.slider-scrollbar {
  position: relative; height: 3px; margin-top: clamp(1.6rem, 4vh, 2.4rem);
  background: var(--panel-line);
  border-radius: 2px; cursor: pointer; touch-action: none;
}
.slider-scrollbar-thumb {
  position: absolute; top: 0; left: 0; height: 100%;
  background: var(--panel-ink);
  border-radius: 2px;
}
@media (hover: hover) and (pointer: fine) {
  .slider-scrollbar:hover { height: 4px; }
}

.cat-card {
  position: relative; display: block; width: 100%;
  aspect-ratio: 3 / 4; max-height: clamp(280px, 48vh, 460px);
  overflow: hidden; isolation: isolate;
  text-align: left;
  touch-action: manipulation;
}
.cat-card-index {
  position: absolute; top: clamp(1.1rem, 2.4vw, 1.6rem); right: clamp(1.1rem, 2.4vw, 1.6rem); z-index: 1;
  font-family: var(--font-mono); font-size: .72rem; letter-spacing: 0.06em; color: var(--paper);
  opacity: .8;
}
.cat-card-media { position: absolute; inset: 0; background: var(--panel-bg); }
.cat-card-media img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.18);
  transition: transform 1.4s var(--ease-soft), filter .6s;
}
.cat-card.is-revealed .cat-card-media img { transform: scale(1); }
.cat-card-overlay {
  display: none !important;
}
.cat-card-copy {
  position: absolute; left: 0; right: 0; bottom: 0; padding: clamp(1.3rem, 3vw, 2rem);
  color: var(--paper);
  display: flex; flex-direction: column; align-items: flex-start; gap: .5rem;
}
/* Same treatment as the Hero title over its own photo — light text, no
   backing chip, no gradient overlay. */
.cat-card-eyebrow { font-family: var(--font-mono); font-size: .65rem; letter-spacing: 0.12em; text-transform: uppercase; opacity: .85; width: fit-content; }
.cat-card-name {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  position: relative; display: inline-block; width: fit-content;
}
.cat-card-name::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -.35rem; height: 1px; background: var(--paper);
  transform: scaleX(0); transform-origin: left;
  transition: transform .6s var(--ease-out);
}
.cat-card-more {
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.08em;
  opacity: 0; max-height: 0; transform: translateY(6px);
  transition: opacity .4s var(--ease-out), transform .4s var(--ease-out), max-height .4s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .cat-card:hover .cat-card-media img { transform: scale(1.09); filter: saturate(1.08); }
  .cat-card:hover .cat-card-name::after { transform: scaleX(1); }
  .cat-card:hover .cat-card-more { opacity: .85; max-height: 1.4em; transform: translateY(0); }
}

/* =============================================================
   12. Contacto
   ============================================================= */
.contacto { background: var(--bg); color: var(--ink); }
.contacto .eyebrow { color: var(--ink-mute); }
.contacto-info > p { color: var(--ink-mute); max-width: 46ch; font-weight: 300; margin-bottom: 2.2rem; }
.contacto-grid {
  max-width: var(--container); margin-inline: auto;
  display: grid; gap: clamp(3rem, 7vw, 6rem);
}
@media (min-width: 960px) { .contacto-grid { grid-template-columns: 0.85fr 1fr; align-items: start; } }
.contacto-info h2 { font-size: clamp(2.1rem, 4.6vw, 3.4rem); font-weight: 500; letter-spacing: -0.02em; margin-bottom: 1.4rem; }
.contacto-info > p { color: var(--panel-ink-mute); max-width: 46ch; font-weight: 300; margin-bottom: 2.2rem; }
.contacto-list { display: flex; flex-direction: column; margin-bottom: 0; }
.contacto-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  padding-block: 1rem; border-bottom: 1px solid var(--panel-line);
  transition: padding-inline .35s var(--ease-out);
}
.contacto-list li:first-child { padding-top: 0; }
.contacto-list a, .contacto-list li span:last-child { font-weight: 500; text-align: right; }
.contacto-list a, .footer-col a { position: relative; display: inline-flex; align-items: center; gap: .45rem; }
.contact-icon { flex-shrink: 0; display: inline-block; opacity: .9; }
.contacto-list a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform .4s var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .contacto-list li:hover { padding-inline: .4rem; }
  .contacto-list a:hover::after { transform: scaleX(1); transform-origin: left; }
}

.contacto-map-caption {
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--panel-ink-mute); margin-top: 2.4rem; padding-top: 2rem; border-top: 1px solid var(--panel-line);
  margin-bottom: 1.1rem;
}
.contacto-map {
  position: relative; aspect-ratio: 16 / 9; border: 1px solid var(--panel-line); overflow: hidden;
  background-image:
    linear-gradient(var(--panel-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--panel-line) 1px, transparent 1px);
  background-size: 34px 34px;
  background-color: rgba(244, 242, 236, 0.02);
}
.contacto-map::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(60% 70% at 46% 44%, transparent 0%, var(--panel-bg) 85%);
  pointer-events: none;
}
.contacto-map-pin { position: absolute; top: 44%; left: 46%; width: 11px; height: 11px; z-index: 1; }
.contacto-map-pin::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%; border: 1.5px solid var(--panel-ink); background: var(--panel-bg);
}
.contacto-map-pin::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--panel-ink);
  animation: pinPulse 2.4s var(--ease-soft) infinite;
}
@keyframes pinPulse {
  0% { transform: scale(1); opacity: .5; }
  70%, 100% { transform: scale(3.4); opacity: 0; }
}
.contacto-map-coords {
  position: absolute; left: 44%; top: 44%; margin: 1rem 0 0 1rem;
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: 0.04em;
  color: var(--panel-ink-mute); white-space: nowrap;
}
.contacto-map-label {
  position: absolute; left: clamp(1rem, 3vw, 1.6rem); bottom: clamp(1rem, 3vw, 1.6rem);
  font-family: var(--font-mono); font-size: .64rem; letter-spacing: 0.14em;
  color: var(--panel-ink-mute);
}
.contacto-signature {
  margin-top: 2.4rem; font-family: var(--font-mono); font-size: .82rem; letter-spacing: 0.02em;
  color: var(--panel-ink); display: flex; flex-direction: column; gap: .3rem;
}
.contacto-signature span {
  font-size: .66rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--panel-ink-mute);
}

.contacto-form {
  display: flex; flex-direction: column; gap: 1.3rem; position: relative;
  background: var(--paper); color: var(--ink); border: 0;
  box-shadow: 0 60px 100px -40px rgba(0, 0, 0, 0.55);
  padding: clamp(2.1rem, 5vw, 3.2rem);
}
.contacto-form-title { font-family: var(--font-display); font-weight: 500; font-size: 1.2rem; letter-spacing: -0.01em; color: var(--ink); margin-bottom: .1rem; }
.contacto-form-note { font-size: .78rem; color: var(--ink-mute); }
.field { position: relative; }
.field input, .field textarea {
  width: 100%; padding: 1.5rem 0 .6rem; border: 0; border-bottom: 1px solid var(--line-strong);
  background: transparent; color: var(--ink); font-family: var(--font-body); font-size: 1rem;
  resize: vertical; transition: border-color .3s var(--ease-out);
}
.field textarea { min-height: 5.5rem; }
.field input:focus, .field textarea:focus { outline: none; border-color: var(--ink); }
.field label {
  position: absolute; left: 0; top: 1.55rem;
  font-family: var(--font-mono); font-size: .8rem; color: var(--ink-mute);
  pointer-events: none; transition: all .25s var(--ease-out);
}
.field input:focus + label, .field input:not(:placeholder-shown) + label,
.field textarea:focus + label, .field textarea:not(:placeholder-shown) + label {
  top: .1rem; font-size: .65rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink);
}
.cta-submit { margin-top: .4rem; align-self: stretch; position: relative; min-width: 160px; }
.cta-submit .cta-form-spinner, .cta-submit .cta-form-check {
  position: absolute; inset: 0; display: grid; place-items: center; opacity: 0; pointer-events: none;
}
.contacto-form.is-sending .cta-form-label { opacity: 0; }
.contacto-form.is-sending .cta-form-spinner { opacity: 1; }
.cta-form-spinner::after {
  content: ""; width: 16px; height: 16px; border: 1.5px solid rgba(247,246,243,.35); border-top-color: var(--bg);
  border-radius: 50%; animation: spin .85s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.cta-form-check path { stroke: currentColor; stroke-width: 2; fill: none; stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .55s var(--ease-out); }
.contacto-form.is-sent .cta-form-check { opacity: 1; }
.contacto-form.is-sent .cta-form-check path { stroke-dashoffset: 0; }

.cta-success {
  position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: center; gap: .6rem;
  padding: clamp(1.8rem, 4.5vw, 2.8rem);
  background: var(--paper); opacity: 0; pointer-events: none; transform: translateY(10px);
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-soft);
}
.cta-success.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.cta-success h3 { font-size: 1.5rem; }
.cta-success p { color: var(--ink-mute); }

/* =============================================================
   13. Footer
   ============================================================= */
.footer { background: var(--panel-bg); color: var(--panel-ink); padding: clamp(3.5rem, 9vh, 6rem) var(--gutter) clamp(1.6rem, 4vh, 2.2rem); }
.slide-scroll .footer { border-top: 1px solid var(--panel-line); }
/* Footer lives inside .slide-scroll (Contacto slide) — bleed full width and touch the bottom edge. */
.slide-scroll .footer {
  margin-inline: calc(-1 * var(--gutter));
  width: calc(100% + 2 * var(--gutter));
  margin-top: clamp(3rem, 8vh, 5rem);
  margin-bottom: calc(-1 * clamp(3rem, 8vh, 5rem));
}
.footer-inner { max-width: var(--container); margin-inline: auto; }

/* Multi-column footer (index.html) */
.footer-top {
  display: grid; gap: 2.6rem;
  padding-bottom: clamp(2.4rem, 6vh, 3.6rem);
  border-bottom: 1px solid var(--line);
}
@media (min-width: 720px) { .footer-top { grid-template-columns: 1.6fr 1fr 1fr 1fr; } }
.footer-col { display: flex; flex-direction: column; gap: .75rem; }
.footer-col-title { font-family: var(--font-mono); font-size: .66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-mute); margin-bottom: .3rem; }
.footer-col a { font-size: .92rem; position: relative; width: fit-content; color: var(--ink); }
.footer-col a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: right; transition: transform .4s var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .footer-col a:hover::after { transform: scaleX(1); transform-origin: left; }
}
.footer-col--brand { gap: 1rem; }
.footer-col--brand .footer-note { font-size: .88rem; color: var(--ink-mute); max-width: 30ch; }

.footer-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; padding-top: clamp(1.4rem, 3vh, 2rem); }

/* Simple footer (creditos.html) */
.page-credits .footer-inner { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 1.1rem; }

.footer-logo { display: flex; flex-direction: row; align-items: center; gap: .7rem; }
.page-credits .footer-logo { flex-direction: column; gap: .6rem; }
.footer-logo img {
  display: block;
  width: 34px;
  height: auto;
  opacity: 0.78;
}
.footer-logo em { font-style: normal; font-family: var(--font-mono); font-size: .65rem; letter-spacing: 0.2em; color: var(--ink); }
.footer-note { font-size: .85rem; color: var(--ink-mute); }
.footer-social { display: flex; gap: 1.6rem; margin-top: .6rem; }
.footer-social a { font-family: var(--font-mono); font-size: .72rem; letter-spacing: 0.08em; text-transform: uppercase; position: relative; color: var(--ink); }
.footer-social a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: currentColor; transform: scaleX(0); transition: transform .4s var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .footer-social a:hover::after { transform: scaleX(1); }
}
.footer-copy { font-size: .72rem; color: var(--ink-mute); }
.footer-bottom .footer-copy:last-child { text-decoration: underline; text-underline-offset: 3px; }
.page-credits .footer-copy { margin-top: 1.6rem; }
.page-credits .footer-copy a { text-decoration: underline; text-underline-offset: 3px; }

/* =============================================================
   14. Credits page
   ============================================================= */
.credits-main { max-width: 760px; margin: 0 auto; padding: calc(var(--nav-h) + 4rem) var(--gutter) 6rem; }
.credits-main h1 { font-size: clamp(2rem, 5vw, 2.8rem); margin-bottom: 1.4rem; }
.credits-intro { color: var(--ink-soft); font-weight: 300; margin-bottom: 2.4rem; max-width: 60ch; }
.credits-list { display: flex; flex-direction: column; gap: 1rem; }
.credits-list li { font-size: .88rem; padding-bottom: 1rem; border-bottom: 1px solid var(--line); color: var(--ink-soft); }
.credits-list a { text-decoration: underline; text-underline-offset: 3px; }
.page-credits .footer { margin-top: 3rem; }

/* =============================================================
   15. Responsive refinements
   ============================================================= */
@media (max-width: 719px) {
  .contacto-list li { flex-direction: column; gap: .3rem; }
  .contacto-list a, .contacto-list li span:last-child { text-align: left; }
}

/* =============================================================
   16. Reduced motion — only intrusive effects
   ============================================================= */
@media (prefers-reduced-motion: reduce) {
  .contacto-map-pin::after { animation: none; }
  /* Deliberately NOT gating .hero-media-img's zoom here — same reasoning as
     startHeroLoop in main.js: Windows ships reduced-motion on by default in
     many setups, and gating this subtle continuous breathe would mean most
     Windows/mobile visitors never see it at all. */
}
