/* ==========================================================================
   Silvero Studios — MK4
   Format from quantixxai.com, executed LIGHT and in Spanish.

   THEME (Raül's call, 2026-07-17): light everywhere. White backgrounds, dark
   type, brand accents, on every page including the service pages. The footer
   (and the closing CTA band) are the only dark surfaces — an anchor at the
   bottom. QuantiX's own inner pages are near-black; we deliberately don't copy
   that, because Raül wants the logo on white throughout.

   VOICE: a studio/company ("diseñamos", "trabajamos", "nuestro equipo"), never
   one person. An earlier version was written around a single person who picks
   up the phone; that was removed — it reads as small, and it undercuts trust.
   Do not reintroduce first-person-singular or "one person" framing.

   PROVENANCE of the numbers: read from quantixxai.com's live stylesheet
   (/assets/app-CNna3xmo.css) and DOM on 2026-07-17. Oddly specific values
   (-.018em, 11vw, 3px 5px 30px, clamp maxes) are theirs, verbatim. The colours
   are NOT theirs — accent is the logo's #8338ec, not their #5e0ed7.

   CONTRAST: every text node must pass WCAG AA. The tokens below are measured
   minimums; don't lower them for "subtlety". Verified with verify.js.
   ========================================================================== */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Tokens ------------------------------------------------------- */

:root {
  /* Brand — the logo triad. Accent as TEXT on white is 5.61:1 (AA pass), so
     unlike the dark build there's no "lifted" variant needed on light. On the
     dark footer it would be 3.56:1 (fail), so the footer overrides to lift. */
  --accent: #8338ec;
  --accent-lift: #a678f5;   /* accent as text on the dark footer only */
  --accent-text: var(--accent);

  /* "Passing" green, used only by the Core Web Vitals meter. 5.4:1 on white, so
     the metric values pass AA as text. --ok-soft is a bar-fill gradient stop
     only (never text), so it carries no contrast requirement. */
  --ok: #0f7a43;
  --ok-soft: #4fbf82;

  /* Surfaces */
  --paper: #ffffff;
  --surface: #f6f4fa;       /* cards, callouts, the CTA band */
  --surface-2: #efeaf7;     /* recessed / table header */
  --ink: #0b0710;           /* footer + closing band background */

  /* Type on white AND on the tinted surface (#f6f4fa). The muted token was
     .55 → 4.45:1 on white and 4.35:1 on the tint (both AA fails); raised so
     both pass. Body must stay darker than muted, so it moved too. Measured
     with verify_all.js on the actual composited backgrounds, not eyeballed. */
  --fg-1: #0b0710;
  --fg-2: rgba(11, 7, 16, .74);
  --fg-3: rgba(11, 7, 16, .66);   /* body */
  --fg-4: rgba(11, 7, 16, .60);   /* small-caps labels, muted, table "neg" */
  --line: rgba(11, 7, 16, .12);
  --line-2: rgba(11, 7, 16, .08);

  /* Type on the dark footer/band */
  --on-dark-1: #ffffff;
  --on-dark-2: rgba(255, 255, 255, .78);
  --on-dark-3: rgba(255, 255, 255, .62);
  --line-dark: rgba(255, 255, 255, .15);

  /* Type scale — from their CSS. The clamp MINIMUMS are lowered from theirs
     (1.9→1.7, 2.4→2.0): Spanish compounds like "AUTOMATIZACIÓN & CRM" are far
     longer than QuantiX's words and overflowed a 320px viewport at their mins.
     The max (desktop) is theirs, verbatim.

     Each is wrapped in min(…, Nvw). A clamp's FLOOR is in rem, so it scales with
     the root font size: a user at root 28px got 1.7rem = 47.6px of uppercase on
     a 320px screen and the line ran off-canvas. The vw cap bounds the floor
     against the viewport. Chosen so that at root 16px the vw term is always the
     larger of the two — i.e. the design is byte-identical at default zoom, and
     these only engage on narrow + zoomed-in. Verified by the
     320→1920 × root-16/20/24/28 sweep; do not drop the min() wrappers. */
  --fs-hero: min(clamp(1.7rem, 5.2vw, 4.05rem), 9.5vw);
  --fs-page: min(clamp(2rem, 6vw, 4.8rem), 11vw);   /* inner-page hero title */
  --fs-display: min(clamp(1.8rem, 5vw, 4rem), 10vw);
  --fs-faq: min(clamp(1.8rem, 4.5vw, 3.4rem), 10vw);
  --fs-h3: min(clamp(1.4rem, 3vw, 2.4rem), 7.5vw);
  --fs-stat: min(clamp(1.4rem, 4vw, 2.1rem), 8vw);
  --fs-ghost: min(clamp(4.5rem, 11vw, 10rem), 24vw);

  --ease: cubic-bezier(.22, 1, .36, 1);
  --shadow-card: 0 1px 2px rgba(11, 7, 16, .04), 0 12px 32px rgba(11, 7, 16, .07);
  --shadow-product: 0 8px 40px rgba(11, 7, 16, .14);

  --pad: 20px;
  --nav-h: 56px;
}

@media (min-width: 640px) { :root { --pad: 32px; } }
@media (min-width: 768px) { :root { --pad: 48px; } }

/* ---------- Reset -------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  background: var(--paper);
  color: var(--fg-1);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip, not hidden: `overflow:hidden` on body makes it a scroll container,
     which breaks `position:sticky` on the hero scrub stage. `overflow-x:clip`
     stops horizontal scroll without creating that container. */
  overflow-x: clip;
}

h1, h2, h3, h4, h5, p, figure { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, canvas, svg, picture { display: block; max-width: 100%; }
/* height:auto is load-bearing: imgs carry width/height attrs to reserve space
   (no CLS), but max-width alone clamps only width and the img would stretch. */
img { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, select, textarea { font: inherit; color: inherit; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: 12px 18px; background: var(--accent); color: #fff;
  font-size: 13px; font-weight: 600;
}
.skip:focus { left: 8px; top: 8px; }

.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- Shared atoms ------------------------------------------------- */

.kicker {
  display: block; font-size: 11px; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--accent-text);
}
@media (min-width: 640px) { .kicker { font-size: 12px; } }

.display {
  font-size: var(--fs-display); line-height: .95; letter-spacing: -.018em;
  font-weight: 600; text-transform: uppercase;
}

/* Long uppercase Spanish words must be allowed to break at very narrow widths,
   or a single compound overflows the viewport (checked at 320px). */
.display, .hero__line, .phero h1, .duo h2, .intro h2, .feature h2, .callout p {
  overflow-wrap: break-word;
}

/* Line-mask reveal wrapper — their <span class="block overflow-hidden"> idiom.
   Needs clip headroom for diacritics: Spanish caps carry a tilde/acute ABOVE
   the cap height (Ñ Á Í Ó É) and overflow:hidden shaves them off.

   The padding/margin pair MUST sit on .mask, not on the span. Round 3 put both
   on the span, where they cancel exactly: padding-top pushes the glyph down
   .22em inside the span, margin-top:-.22em lifts the span's box by the same
   .22em, so the glyph top lands right back on the clip edge and the Ñ of
   DISEÑO was still decapitated. Padding on .mask genuinely extends the clip
   box upward; the negative margin then restores the layout position. */
.mask { display: block; overflow: hidden; padding-top: .22em; margin-top: -.22em; }
.mask > span { display: block; }

.rule { height: 1px; width: 100%; background: var(--line); border: 0; }

/* Breadcrumb (inner pages): Inicio › Servicios › X */
.crumbs {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  font-size: 12px; font-weight: 500; color: var(--fg-4);
}
.crumbs a:hover { color: var(--accent-text); }
.crumbs span[aria-current] { color: var(--fg-2); }
.crumbs .sep { opacity: .5; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 16px 28px; border-radius: 2px; font-size: 12px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; text-align: center;
  /* NOT nowrap: long Spanish labels ("Reservar diagnóstico gratis (20 min)")
     are wider than a 320px column and, held on one line, forced the whole
     contact grid to overflow. Let them wrap to two lines on tiny screens. */
  white-space: normal;
  transition: background-color .3s var(--ease), color .3s var(--ease), box-shadow .3s var(--ease);
}
.btn svg { width: 16px; height: 16px; flex: none; }

.btn--dark { background: var(--ink); color: #fff; }
.btn--dark:hover { background: var(--accent); }

.btn--accent { background: var(--accent); color: #fff; }
.btn--accent:hover { background: var(--ink); color: #fff; }

/* Outline button for use on white */
.btn--outline { background: #fff; color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); }
.btn--outline:hover { box-shadow: inset 0 0 0 1px var(--ink); }

/* Ghost button for the dark closing band */
.btn--ghost { background: transparent; color: #fff; box-shadow: inset 0 0 0 1px rgba(255,255,255,.25); }
.btn--ghost:hover { background: #fff; color: var(--ink); }

.btn--sm { padding: 8px 16px; font-size: 11px; }
.btn--sm svg { width: 14px; height: 14px; }

.alink {
  display: inline-flex; align-items: center; gap: 6px; font-size: 13px;
  font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--accent-text); transition: opacity .3s var(--ease);
}
.alink:hover { opacity: .72; }
.alink svg { width: 18px; height: 18px; flex: none; transition: transform .3s var(--ease); }
.alink:hover svg { transform: translate(2px, -2px); }
@media (min-width: 768px) { .alink { font-size: 15px; } }

.section { padding: 40px var(--pad); background: var(--paper); }
@media (min-width: 640px) { .section { padding-block: 80px; } }
@media (min-width: 768px) { .section { padding-block: 120px; } }
.section--tint { background: var(--surface); }

.section__head { margin-bottom: 24px; }
@media (min-width: 640px) { .section__head { margin-bottom: 44px; } }
@media (min-width: 768px) { .section__head { margin-bottom: 72px; } }
.section__head .display { margin-top: 12px; }
@media (min-width: 640px) { .section__head .display { margin-top: 16px; } }

.wrap { max-width: 1200px; margin: 0 auto; }

/* Small spacing utilities. These exist as CLASSES, not inline style="…" —
   the CSP ships `style-src 'self'`, which strips inline style ATTRIBUTES in
   production, silently. Any spacing/size tweak on a page must come from here. */
.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 20px; }
.mt-4 { margin-top: 32px; }
.mb-2 { margin-bottom: 16px; }
.display--sm { font-size: min(clamp(1.7rem, 4vw, 3rem), 8.75vw); }

/* ==========================================================================
   Nav — LIGHT. Transparent with dark type at the top; a white translucent
   blur bar (still dark type) once scrolled. Because the whole page is light,
   the type never needs to flip colour — the bar only gains a background so it
   doesn't sit on top of content. site.js toggles .is-stuck from a sentinel.
   ========================================================================== */

.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50; height: var(--nav-h);
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding-inline: var(--pad); border-bottom: 1px solid transparent;
  background: transparent; color: var(--ink);
  transition: background-color .4s var(--ease), border-color .4s var(--ease), box-shadow .4s var(--ease);
}
.nav.is-stuck {
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}
/* No-JS: ship the solid bar from the start (the sentinel toggle never fires).
   A <noscript><style> is blocked by style-src 'self', so use @media scripting. */
@media (scripting: none) {
  .nav { background: rgba(255, 255, 255, .92); border-bottom-color: var(--line);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
}

.nav__brand { display: flex; align-items: center; gap: 8px; flex: none; }
.nav__brand img { height: 26px; width: auto; }
.nav__brand span { font-size: 14px; font-weight: 600; letter-spacing: -.01em; }

.nav__links { display: none; align-items: center; gap: 22px; }
@media (min-width: 1024px) { .nav__links { display: flex; } }
.nav__links a {
  font-size: 12px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fg-3); transition: color .25s var(--ease);
}
.nav__links a:hover, .nav__links a[aria-current] { color: var(--ink); }

.nav__right { display: flex; align-items: center; gap: 8px; flex: none; }
.nav__toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 999px; flex: none;
}
.nav__toggle svg { width: 16px; height: 16px; }
@media (min-width: 1024px) { .nav__toggle { display: none; } }

.nav__cta-long { display: none; }
.nav__cta-short { display: inline; }
@media (min-width: 640px) { .nav__cta-long { display: inline; } .nav__cta-short { display: none; } }
@media (max-width: 420px) { .nav__brand span { display: none; } }

.nav__panel {
  position: fixed; inset: var(--nav-h) 0 auto 0; z-index: 49; display: none;
  padding: 8px var(--pad) 20px; background: var(--paper);
  border-bottom: 1px solid var(--line);
}
.nav__panel.is-open { display: block; }
@media (min-width: 1024px) { .nav__panel, .nav__panel.is-open { display: none; } }
.nav__panel a {
  display: block; padding: 14px 0; font-size: 13px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--fg-2);
  border-bottom: 1px solid var(--line-2);
}
.nav__panel a:last-child { border-bottom: 0; }

/* ==========================================================================
   HERO (homepage) — LIGHT. The 3D mark is CONTAINED in the left column: fully
   in frame, never bleeding off an edge, never under the text. Raül wants the
   logo showcased whole on white. width is capped so its right edge stays left
   of the text column (which begins ~x=474 at 1194).
   ========================================================================== */

/* .hero is the SCROLL TRACK; .hero__stage is the visible panel. On desktop
   with the scrub active, the track grows to 200vh and the stage sticks to the
   top for 100vh — that 100vh of scroll is what drives the mark from scrambled
   (frame 1) to assembled (frame 149). Off desktop / reduced-motion / no-JS the
   track is just the stage's height and nothing pins. */
.hero { position: relative; background: var(--paper); }
.hero__stage {
  position: relative; display: flex; flex-direction: column;
  min-height: 100svh; width: 100%; overflow: hidden;
  background: var(--paper); color: var(--ink);
}

/* The pin needs room for the mark AND the copy inside one viewport. On a
   667px-tall phone the copy alone is 519px, leaving 84px for a 248px mark —
   it got clipped under the nav. So the pin is gated on viewport HEIGHT too;
   short screens keep the old stacked hero, which fits fine. */
@media (prefers-reduced-motion: no-preference) and (min-height: 720px) {
  /* scrub-on is set by site.js only when it actually runs the scrub. Gating
     the tall track on it means no-JS (and any device where the scrub bailed)
     never gets a screen of dead scroll.

     Phones get this too now, with a lighter frame set — the mark used to slide
     straight past on mobile while desktop pinned, which read as two different
     websites. The track is shorter on phones (160vh): the same 200vh that
     feels deliberate with a mouse feels like a stuck page under a thumb. */
  :root.scrub-on .hero { height: 160vh; }
  :root.scrub-on .hero__stage { position: sticky; top: 0; height: 100vh; min-height: 0; }
}
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) and (min-height: 720px) {
  :root.scrub-on .hero { height: 200vh; }
}

.hero__media { position: absolute; inset: 0; }

/* The mark sits on plain white. There WAS a soft violet radial bloom behind it
   (.hero__bloom); Raül removed it 2026-07-18 — the frames are transparent webp
   with their own colour, and the tint behind them just read as a dirty
   background. Do not reintroduce it without asking. */

/* CONTAINED mark. left:var(--pad) + capped width keeps the right edge < 440px
   at 1194 (text starts ~474). Square asset sized off width, vertically
   centred — no vertical bleed either (height << 100svh). */
.hero__mark, .hero__canvas {
  position: absolute; z-index: 1; top: 50%; left: var(--pad);
  width: min(34%, 430px); height: auto; aspect-ratio: 1/1;
  transform: translateY(-50%);
}
.hero__canvas { opacity: 0; }
:root.scrub-on .hero__canvas { opacity: 1; }
:root.scrub-on .hero__mark { opacity: 0; }

/* MOBILE: don't overlay the mark on the text at all. The mark becomes an
   in-flow block at the top (fixed-height media panel), and the copy flows
   below it — guaranteed no overlap, which is what Raül asked for. The desktop
   overlay layout (absolute media + text column beside it) only applies ≥768. */
/* Stacked hero: phones AND portrait tablets. The overlay layout below only
   works with landscape-ish width — measured at 912x1368 (Surface Pro) the
   mark overlapped the paragraph by 70px, at 820x1180 (iPad Air) by 131px.
   Anything under 1200px gets the mark stacked above the copy instead. */
@media (max-width: 1199px) {
  .hero__stage { min-height: auto; }
  /* overflow:hidden clips the mark to its 300px block — nothing bleeds down
     onto the copy, even the soft bloom.

     margin-top clears the nav. The nav is position:fixed, so it is out of flow
     and paints OVER whatever starts at y=0 — and this block did, so the top
     ~30px of the mark sat behind the bar on every phone size (measured: 30px
     on iPhone SE, 35px on iPhone 14, 25px at 360px wide). The inner pages
     already reserve this space via .phero/.article padding-top; the hero was
     the one place that did not. */
  .hero__media { position: relative; height: 300px; margin-top: var(--nav-h); overflow: hidden; }
  /* When the scrub is live the stage owns the viewport, so the media has to
     fill it instead of sitting in a 300px strip. */
  /* The pinned media box only exists where the hero actually pins — the
     min-height gate below must match the one on .hero above, or short phones
     get the squeezed box with none of the pinning that justifies it. */
  @media (min-height: 720px) {
    /* flex:none or the copy steals the space. A flat vh also fails: the copy
       is ~519px whatever the screen, so 46vh left the stage 63px taller than
       the viewport on an 844px phone and the stats row fell off the bottom.
       Take what is actually left after the nav and the copy. */
    :root.scrub-on .hero__media {
      height: clamp(120px, calc(100vh - var(--nav-h) - 540px), 34vh);
      flex: none; margin-top: calc(var(--nav-h) + 8px);
    }
    /* Size the mark to its BOX, not to the viewport width. Sizing by width
       (min(66%, 280px)) meant a 248px mark inside a 144px box on a 740px
       phone — it overflowed upward and slid under the nav. Height-first, and
       it can never outgrow the space it was given. */
    :root.scrub-on .hero__mark,
    :root.scrub-on .hero__canvas {
      height: 100%; width: auto; max-width: min(66%, 280px);
      top: 50%; left: 50%; transform: translate(-50%, -50%);
    }
  }
  /* The 56px bottom padding is breathing room before the next section —
     but when the hero is pinned the next section is a scroll away, and
     that padding was pushing the stats row 14px past the fold on a 740px
     phone. Trim it only while pinned. */
  :root.scrub-on .hero .hero__content { padding-top: 8px; padding-bottom: 12px; }
  .hero__mark, .hero__canvas {
    top: 50%; left: 50%; width: min(66%, 280px); transform: translate(-50%, -50%);
  }
  .hero__bloom { display: none; }   /* the tint bled onto the heading; drop it on mobile */
  .hero__scrim { display: none; }
  /* .hero prefix is load-bearing: this media query sits EARLIER in the file
     than the base `.hero__content { justify-content: flex-end }`, and at
     equal specificity the later rule wins. Without it the copy stayed
     bottom-aligned and left 320px of dead space under the mark on a tall
     tablet. Same class of silent loss as the .cbox figure and the
     .article__body measure. */
  .hero .hero__content { justify-content: flex-start; padding: 28px var(--pad) 56px; }
}

/* Desktop scrim only (the mobile fade is unnecessary now — mark is above text). */
.hero__scrim { position: absolute; inset: 0; z-index: 2; pointer-events: none; }

.hero__content {
  position: relative; z-index: 10; display: flex; flex: 1; flex-direction: column;
  justify-content: flex-end; width: 100%; padding: 96px var(--pad) 40px;
}
@media (min-width: 1200px) { .hero__content { justify-content: center; } }

.hero__col { display: flex; flex-direction: column; align-items: flex-start; width: 100%; }
@media (min-width: 1200px) { .hero__col { margin-left: auto; max-width: 36rem; } }
@media (min-width: 1024px) { .hero__col { max-width: 42rem; } }

.hero h1 { margin-top: 12px; font-weight: 600; text-transform: uppercase; }
@media (min-width: 640px) { .hero h1 { margin-top: 16px; } }
.hero h1 .hero__line { font-size: var(--fs-hero); line-height: 1; letter-spacing: -.02em; }

.hero__sub {
  padding-top: 8px; font-size: 18px; font-weight: 500; line-height: 1.375;
  letter-spacing: normal; text-transform: none; color: var(--fg-2);
}
@media (min-width: 640px) { .hero__sub { padding-top: 12px; font-size: 24px; } }
@media (min-width: 768px) { .hero__sub { font-size: 1.65rem; } }

.hero__lede { margin-top: 16px; max-width: 30rem; font-size: 14px; line-height: 1.6; color: var(--fg-3); }
@media (min-width: 640px) { .hero__lede { margin-top: 20px; font-size: 16px; } }

.hero__ctas { display: flex; flex-direction: column; gap: 16px; width: 100%; margin-top: 24px; }
@media (min-width: 640px) {
  .hero__ctas { flex-direction: row; align-items: center; gap: 24px; width: auto; margin-top: 32px; }
}
.hero__ctas .btn { width: 100%; }
@media (min-width: 640px) { .hero__ctas .btn { width: auto; } }

.hero__stats { width: 100%; margin-top: 32px; }
@media (min-width: 640px) { .hero__stats { margin-top: 40px; } }
.hero__figs { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-top: 16px; }
@media (min-width: 640px) { .hero__figs { justify-content: flex-start; gap: 48px; } }
.hero__fig b { display: block; font-size: var(--fs-stat); font-weight: 600; line-height: 1; }
.hero__fig b i { font-style: normal; font-size: .6em; color: var(--accent-text); }
.hero__fig span {
  display: block; margin-top: 6px; font-size: 10px; font-weight: 600; line-height: 1.25;
  letter-spacing: .1em; text-transform: uppercase; color: var(--fg-4); white-space: pre-line;
}
@media (min-width: 640px) { .hero__fig span { font-size: 11px; } }
.hero__loc {
  margin-top: 20px; font-size: 10px; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--fg-4);
}

/* ==========================================================================
   INNER-PAGE HERO — breadcrumb, kicker, big title, subtitle, 2 CTAs, then a
   product mockup below. Light. The mockup is a framed card.
   ========================================================================== */

.phero { padding: calc(var(--nav-h) + 40px) var(--pad) 0; background: var(--paper); }
@media (min-width: 768px) { .phero { padding-top: calc(var(--nav-h) + 72px); } }
.phero__head { max-width: 1200px; margin: 0 auto; }
.phero .kicker { margin-top: 28px; }
.phero h1 {
  margin-top: 12px; font-size: var(--fs-page); line-height: .95;
  letter-spacing: -.02em; font-weight: 600; text-transform: uppercase;
}
.phero__sub { margin-top: 14px; font-size: min(clamp(1.05rem, 2.2vw, 1.5rem), 5.5vw); color: var(--fg-2); line-height: 1.35; }
.phero__ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* --- SPLIT HERO (.phero--split, Diseño Web only) --------------------------
   Stacked, the copy block (265px) plus the 1280px-wide deck (774px) ran to
   1231px — 330-460px below the fold on every common laptop. Side by side, the
   two sit in the taller of the two columns instead of summing, which lands the
   whole thing inside one screen from 1366x768 up.

   The deck gets the larger share: it is the proof of the work, and narrowing it
   would undo the "make it bigger" pass. It still ends up wider here than the
   two-panel layout it replaced.

   Below 1100px this does nothing and the hero stacks as before — at that width
   a 34% text column would be too narrow to set the display type in. */
@media (min-width: 1100px) {
  .phero--split {
    display: grid; align-items: center;
    /* 36%: at 30% the long Spanish compounds had nowhere to go and
       "AUTOMATIZACIÓN" broke mid-word across two lines. The demo panel gives
       up the width — it was the larger of the two and reads fine narrower. */
    grid-template-columns: minmax(320px, 36%) 1fr;
    gap: clamp(28px, 3vw, 56px);
    max-width: 1680px; margin-inline: auto;
    padding-top: calc(var(--nav-h) + 32px); padding-bottom: 32px;
  }
  .phero--split .phero__head { max-width: none; margin: 0; min-width: 0; }
  /* Capped against the column, not the viewport. The longest word on these
     pages is AUTOMATIZACIÓN (14 uppercase chars, ~8.7em wide); at 5.4vw that
     is ~670px inside a ~480px track, so it broke mid-word. 3.7vw keeps the
     longest word on one line at every width from 1100 up. Do not raise it
     without re-checking that page. */
  .phero--split h1 { font-size: min(var(--fs-page), 3.5vw); }
  .phero--split .phero__ctas { margin-top: 24px; }
  .phero--split .slideshow { max-width: none; margin: 0; }

  /* The split hero already ends on the deck's own breathing room (32px of
     section padding plus the dots' 20px margin). Stacking the next section's
     full 120px on top of that left ~180px of dead white before the first line
     of copy. Halve it here only — the rhythm between the later sections is
     right, it is just this one seam that doubled up. */
  .phero--split { padding-bottom: 16px; }
  .phero--split + .section { padding-top: 56px; }
}

/* ==========================================================================
   NAV CONTACT ICONS + LANGUAGE SWITCH
   Phone / email / Instagram sit left of the booking CTA. They are hidden below
   1024px, where the hamburger takes over and the panel carries them instead —
   at phone widths there is not room for six targets in a 56px bar without
   dropping under the 44px minimum tap size.
   ========================================================================== */
.nav__icons { display: none; align-items: center; gap: 2px; }
@media (min-width: 1024px) { .nav__icons { display: flex; } }
.nav__icon {
  display: grid; place-items: center; width: 36px; height: 36px;
  border-radius: 9px; color: var(--fg-3);
  transition: color .25s var(--ease), background-color .25s var(--ease);
}
.nav__icon svg { width: 17px; height: 17px; }
.nav__icon:hover { color: var(--accent-text); background: rgba(131,56,236,.09); }

/* Language toggle — a real two-state switch, not a text button. It is an <a>,
   not a <button>: it navigates to the other language's URL, so it must be a
   link (right-click, open-in-new-tab, and crawlers all depend on that). The
   knob slides; the two labels sit either side and the active one darkens. */
.lang {
  position: relative; display: inline-flex; align-items: center;
  flex: none; padding: 3px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: 11px; font-weight: 700; letter-spacing: .06em; line-height: 1;
  user-select: none;
}
.lang__knob {
  position: absolute; top: 3px; left: 3px; z-index: 0;
  width: calc(50% - 3px); height: calc(100% - 6px);
  border-radius: 999px; background: var(--paper);
  box-shadow: 0 1px 3px rgba(11,7,16,.16);
  transition: transform .28s var(--ease);
}
.lang[data-lang="en"] .lang__knob { transform: translateX(100%); }
/* :not(.lang__knob) is load-bearing. The knob is a <span> too, so a bare
   `.lang span` (0,1,1) outranks `.lang__knob` (0,1,0) and overrode its
   position:absolute — the knob dropped into the flow and sat to the LEFT of
   the labels instead of behind them. */
.lang > span:not(.lang__knob) {
  position: relative; z-index: 1; padding: 6px 9px;
  color: var(--fg-4); transition: color .25s var(--ease);
}
.lang > span.is-on { color: var(--fg-1); }
.lang:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .lang__knob { transition: none; } }

/* Contact row inside the mobile panel — the icons' home below 1024px. */
.nav__panel-contact { display: flex; gap: 8px; padding: 14px 0 2px; }
.nav__panel-contact a {
  display: grid; place-items: center; width: 44px; height: 44px;   /* tap target */
  border-radius: 11px; color: var(--fg-2); border: 1px solid var(--line);
}
.nav__panel-contact a svg { width: 18px; height: 18px; }

/* ==========================================================================
   FLOATING WHATSAPP
   Fixed bottom-right on every page. href is a placeholder until Raül's
   WhatsApp Business link exists — see the note in the markup. Sits below the
   nav's z-index so an open mobile panel covers it rather than fighting it.
   ========================================================================== */
.wa {
  position: fixed; right: 18px; bottom: 18px; z-index: 45;
  display: grid; place-items: center; width: 56px; height: 56px;
  border-radius: 50%; background: #25d366; color: #fff;
  box-shadow: 0 6px 22px rgba(11,7,16,.22);
  transition: transform .25s var(--ease), box-shadow .25s var(--ease);
}
.wa svg { width: 30px; height: 30px; }
.wa:hover { transform: translateY(-2px); box-shadow: 0 10px 28px rgba(11,7,16,.28); }
@media (prefers-reduced-motion: reduce) { .wa:hover { transform: none; } }
@media (min-width: 768px) { .wa { right: 26px; bottom: 26px; } }

/* ==========================================================================
   HERO DEMO PANEL (Agentes IA / Automatización)
   A product-shaped mock built in HTML/CSS, sat beside a shrunken 3D icon.

   THIS IS NOT A SCREENSHOT AND MUST NEVER PRETEND TO BE ONE. It carries a
   visible "Ejemplo" tag, uses obviously fictional customer names, and states
   no performance figure about Silvero. The reference this was modelled on
   (quantixxai.com) ships AI-generated fake dashboards — the giveaway is the
   garbled Spanish in their labels. Do not copy that: an invented metric like
   "87% resolución automática" is a claim about a business, not decoration.
   Swap this whole block for real captures as soon as they exist.
   ========================================================================== */
.demo {
  border-radius: 16px; overflow: hidden; background: var(--ink);
  border: 1px solid rgba(255,255,255,.10); box-shadow: var(--shadow-product);
  font-size: 12px; line-height: 1.35;
}
.demo__bar {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px;
  background: rgba(255,255,255,.05); border-bottom: 1px solid rgba(255,255,255,.09);
}
.demo__dots { display: flex; gap: 6px; flex: none; }
.demo__dots i { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.22); }
.demo__title { font-size: 11.5px; font-weight: 600; color: var(--on-dark-2); letter-spacing: .02em; }
.demo__tag {
  margin-left: auto; font-size: 9.5px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--on-dark-1);
  background: var(--accent); border-radius: 999px; padding: 3px 9px;
}

.demo__body { display: grid; }
@media (min-width: 600px) { .demo__body--split { grid-template-columns: 38% 1fr; } }

/* --- left rail: conversation / object list --- */
.demo__rail { padding: 12px; border-right: 1px solid rgba(255,255,255,.08); display: grid; gap: 4px; align-content: start; }
.demo__row { display: grid; gap: 2px; padding: 9px 10px; border-radius: 9px; }
.demo__row.is-on { background: rgba(131,56,236,.24); }
.demo__who { font-weight: 600; color: var(--on-dark-1); }
.demo__snip { color: var(--on-dark-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- right: thread --- */
.demo__thread { padding: 14px; display: grid; gap: 9px; align-content: start; }
.demo__msg { max-width: 88%; padding: 8px 11px; border-radius: 12px; }
.demo__msg--in { background: rgba(255,255,255,.09); color: var(--on-dark-1); border-bottom-left-radius: 4px; }
.demo__msg--out { background: var(--accent); color: #fff; margin-left: auto; border-bottom-right-radius: 4px; }
.demo__chip {
  display: inline-flex; align-items: center; gap: 6px; align-self: start;
  padding: 6px 10px; border-radius: 8px; color: var(--on-dark-2);
  background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.10);
}
.demo__chip svg { width: 13px; height: 13px; flex: none; color: var(--accent-lift); }

/* --- table variant (Automatización) --- */
.demo__table { width: 100%; border-collapse: collapse; }
.demo__table th, .demo__table td { text-align: left; padding: 9px 12px; border-bottom: 1px solid rgba(255,255,255,.07); }
.demo__table th { font-size: 10px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--on-dark-3); }
.demo__table td { color: var(--on-dark-1); }
.demo__table tr:last-child td { border-bottom: 0; }
.demo__state { display: inline-block; padding: 3px 9px; border-radius: 999px; font-size: 10.5px; font-weight: 600; }
.demo__state--ok { background: rgba(79,191,130,.20); color: #8fe0b0; }
.demo__state--go { background: rgba(131,56,236,.28); color: #cbb0ff; }
.demo__state--wait { background: rgba(255,255,255,.10); color: var(--on-dark-2); }
.demo__foot {
  display: flex; align-items: center; gap: 8px; padding: 10px 14px;
  border-top: 1px solid rgba(255,255,255,.08); color: var(--on-dark-3); font-size: 11px;
}
.demo__pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--ok-soft); flex: none; }

/* The demo panel and the badge share one grid column — without this wrapper the
   badge would be a third grid item and drop onto a second row under the copy. */
.phero__demo { display: grid; gap: 0; align-content: center; min-width: 0; }

/* --- the shrunken icon + its caption, sitting under the demo panel --- */
.phero__badge { display: flex; align-items: center; gap: 14px; margin-top: 20px; }
.phero__badge img { width: 72px; height: 72px; flex: none; }
.phero__badge p { font-size: 13px; line-height: 1.5; color: var(--fg-3); }

@media (min-width: 1100px) {
  /* Keep the whole hero in the first screen — same goal as diseno-web. At the
     default sizes the demo ran 35-53px past the fold, so the panel and the
     badge both tighten here rather than shipping a hero you have to scroll. */
  .phero--split .demo { font-size: 11px; }
  .phero--split .demo__thread { gap: 7px; padding: 11px; }
  .phero--split .demo__rail { padding: 9px; }
  .phero--split .demo__row { padding: 7px 9px; }
  .phero--split .demo__msg { padding: 7px 10px; }
  .phero--split .demo__table th,
  .phero--split .demo__table td { padding: 7px 11px; }
  .phero--split .phero__badge { margin-top: 24px; padding-top: 18px; }
  .phero--split .phero__badge img { width: 62px; height: 62px; }
}

/* Product mockup frame */
.mock {
  max-width: 1200px; margin: 40px auto 0; border-radius: 16px; overflow: hidden;
  background: var(--surface); box-shadow: var(--shadow-product);
  border: 1px solid var(--line);
}
@media (min-width: 768px) { .mock { margin-top: 64px; } }
.mock--browser { padding-top: 38px; position: relative; }
.mock--browser::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 38px;
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
/* three faux traffic-light dots */
.mock--browser::after {
  content: ''; position: absolute; top: 15px; left: 16px; width: 8px; height: 8px;
  border-radius: 50%; background: #e0655c;
  box-shadow: 16px 0 0 #e6b04a, 32px 0 0 #56b96a;
}
.mock img { width: 100%; height: auto; display: block; }

/* ---------- Two-panel work slideshow -----------------------------------
   ONE browser-framed panel cross-fading through every client site. Was two
   side-by-side panels; merged 2026-07-18 so each shot gets the full width.

   Every slide is an <a> to the live site. Inactive slides therefore MUST leave
   the tab order or keyboard users tab into invisible links — that is what the
   visibility:hidden below is for (opacity alone does not do it). site.js also
   sets aria-hidden/tabindex to match.

   Sources are 1920×1044 (2× captures of the live sites). At the 1280px cap
   that is a 1.5× downscale, so it stays sharp; EBO is 1837px because the new
   design is not live yet and its only source is a screenshot. Never let the
   displayed width exceed the intrinsic width. */
.slideshow { position: relative; max-width: 1280px; margin: 40px auto 0; }
@media (min-width: 768px) { .slideshow { margin-top: 64px; } }

.slide-panel {
  position: relative; aspect-ratio: 1280 / 745; padding-top: 38px;
  border-radius: 16px; overflow: hidden; background: var(--surface);
  border: 1px solid var(--line); box-shadow: var(--shadow-product);
}
.slide-panel::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 38px; z-index: 2;
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.slide-panel::after {
  content: ''; position: absolute; top: 15px; left: 16px; z-index: 3;
  width: 9px; height: 9px; border-radius: 50%; background: #e0655c;
  box-shadow: 15px 0 0 #e6b04a, 30px 0 0 #56b96a;
}
.slide {
  position: absolute; inset: 38px 0 0 0; display: block;
  opacity: 0; visibility: hidden;
  transition: opacity .7s var(--ease), visibility 0s linear .7s;
}
.slide.is-active { opacity: 1; visibility: visible; transition: opacity .7s var(--ease), visibility 0s; }
.slide img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.slide__cap {
  position: absolute; left: 14px; bottom: 14px; z-index: 4;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; letter-spacing: .03em; color: var(--fg-2);
  background: var(--paper); border: 1px solid var(--line);
  border-radius: 999px; padding: 8px 15px;
  /* Both slides are semi-opaque mid-crossfade, and the two pills sit at the
     same coordinates — so the labels overlapped and read as doubled, smeared
     text. Drop the outgoing one fast and bring the incoming one in late, so
     only one is ever legible. */
  opacity: 0;
  transition: opacity .18s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.slide.is-active .slide__cap { opacity: 1; transition-delay: .45s, 0s, 0s; }
.slide__cap svg { width: 13px; height: 13px; opacity: .55; transition: opacity .25s var(--ease), transform .25s var(--ease); }
.slide:hover .slide__cap { color: var(--accent-text); border-color: var(--accent); }
.slide:hover .slide__cap svg { opacity: 1; transform: translate(1px, -1px); }
.slide:focus-visible { outline: none; }
.slide:focus-visible .slide__cap { outline: 2px solid var(--accent); outline-offset: 3px; }

/* --- Controls. Hidden without JS: the arrows would do nothing, and the deck
   degrades to one static screenshot. --- */
.slide-nav, .slide-dots { display: none; }
:root.js-on .slide-nav { display: grid; }
:root.js-on .slide-dots { display: flex; }

.slide-nav {
  position: absolute; top: 50%; z-index: 5; transform: translateY(-50%);
  place-items: center; width: 44px; height: 44px; padding: 0;
  border-radius: 50%; cursor: pointer; color: var(--fg-1);
  background: var(--paper); border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  transition: background-color .25s var(--ease), color .25s var(--ease), transform .25s var(--ease);
}
.slide-nav svg { width: 20px; height: 20px; }
.slide-nav--prev { left: 12px; }
.slide-nav--next { right: 12px; }
@media (min-width: 900px) {
  /* Sit them just outside the frame so they never cover the screenshot. */
  .slide-nav--prev { left: -22px; }
  .slide-nav--next { right: -22px; }
}
.slide-nav:hover { background: var(--accent); color: #fff; }
.slide-nav:active { transform: translateY(-50%) scale(.94); }
@media (prefers-reduced-motion: reduce) { .slide-nav:active { transform: translateY(-50%); } }

.slide-dots { justify-content: center; gap: 9px; margin-top: 20px; }
.slide-dot {
  width: 9px; height: 9px; padding: 0; border-radius: 50%; cursor: pointer;
  background: var(--line); border: 0;
  transition: background-color .25s var(--ease), transform .25s var(--ease);
}
.slide-dot:hover { background: var(--fg-4); }
.slide-dot.is-on { background: var(--accent); transform: scale(1.25); }

/* Icon-led hero (Agentes / Automatización): the bare 3D icon, no panel.
   It used to be a framed .mock with a violet radial behind it; removed
   2026-07-18 on Raül's call — same reasoning as .card above. The overrides
   below cancel what it inherits from .mock, so keep them if .mock changes. */
.mock--icon {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 5 / 2; padding: 0;
  background: none; background-image: none;
  border: 0; border-radius: 0; box-shadow: none;
}
.mock--icon img { width: auto; height: min(96%, 340px); }

/* ==========================================================================
   INTRO (homepage) — first content block.
   ========================================================================== */
/* PINNED INTRO — the same device as the hero: the section becomes a 200vh
   scroll track and .intro__stage sticks for one viewport, so scrolling drives
   the arrow instead of moving the page. Only engages once :root.arrow-on is
   set (desktop + motion + frames decoded), so no-JS, mobile and reduced-motion
   never inherit a screen of empty scroll. Padding drops to 0 while pinned or
   the track gains 240px of dead space at the seams. */
:root.arrow-on .intro--pin { padding-block: 0; height: 175vh; }
:root.arrow-on .intro--pin .intro__stage {
  position: sticky; top: 0; height: 100vh;
  display: flex; align-items: center;
}
/* A phone has to fit the copy AND the arrow inside one pinned screen, so the
   track is shorter and the copy tightens. Above 1200px the two sit side by
   side and there is room to spare. */
@media (min-width: 1200px) { :root.arrow-on .intro--pin { height: 200vh; } }
@media (max-width: 1199px) {
  :root.arrow-on .intro--pin .intro__stage { align-items: flex-start; padding-top: calc(var(--nav-h) + 12px); }
  :root.arrow-on .intro__split { gap: 4px; }
  :root.arrow-on .intro__arrow { width: min(62%, 240px); }
}
.intro__stage { width: 100%; }

/* Intro: copy on the left, the scrubbing arrow on the right pointing back at
   it. Stacks below 900px, where a side-by-side would squeeze the paragraph to
   an unreadable measure — and where the arrow frames are not loaded anyway. */
.intro__split { display: grid; gap: 32px; }
@media (min-width: 900px) {
  .intro__split { grid-template-columns: 1fr minmax(260px, 34%); gap: clamp(32px, 4vw, 72px); align-items: center; }
}
.intro__arrow { position: relative; justify-self: center; width: min(100%, 380px); aspect-ratio: 1 / 1; }
.intro__arrow img, .intro__arrow canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* No-JS / no-scrub: the still shows and the canvas stays blank. Mirrors the
   hero's contract exactly — the still is the default, the canvas is the
   upgrade, and the swap only happens once frame 1 has decoded. */
.arrow__canvas { opacity: 0; }
:root.arrow-on .arrow__canvas { opacity: 1; }
:root.arrow-on .arrow__still { opacity: 0; }
:root.arrow-on .arrow__still { opacity: 0; }
/* Below 900px the arrow shows as a STILL under the copy — visible, but no
   frame sequence: 121 frames is desktop-only bandwidth. It was display:none,
   which meant phone users never saw it at all. */
@media (max-width: 1199px) {
  /* Was 230px at the foot of a tall gap and half-hidden behind the cookie
     bar — it read as missing. Bigger, tighter to the copy, and the notice
     now clears it. The canvas is NOT hidden here any more: phones run the
     scrub too, off a lighter frame set. */
  .intro__arrow { width: min(78%, 300px); margin: 0 auto; justify-self: center; }
  .intro__split { gap: 8px; }
}

.intro__grid { max-width: 48rem; }
.intro h2 {
  margin-top: 12px; font-size: min(clamp(1.7rem, 4vw, 3rem), 8.75vw); font-weight: 600;
  line-height: 1.1; letter-spacing: -.02em;
}
.intro__lede { margin-top: 20px; font-size: 16px; line-height: 1.7; color: var(--fg-3); }
@media (min-width: 768px) { .intro__lede { font-size: 18px; } }
.intro__note { margin-top: 18px; font-size: 14px; line-height: 1.7; color: var(--fg-4); }
.intro__note strong { color: var(--fg-1); font-weight: 600; }

/* Two-column intro used on inner pages (big headline / paragraph). */
.duo { display: grid; gap: 20px; }
@media (min-width: 768px) { .duo { grid-template-columns: 1fr 1fr; gap: 56px; align-items: start; } }
.duo h2 { font-size: min(clamp(1.6rem, 3.4vw, 2.6rem), 8.25vw); font-weight: 600; line-height: 1.1; letter-spacing: -.02em; }
.duo p { font-size: 16px; line-height: 1.7; color: var(--fg-3); }
@media (min-width: 768px) { .duo p { font-size: 17px; } }

/* Prose block (¿qué incluye?) */
.prose { max-width: 52rem; }
.prose .lead { font-size: min(clamp(1.15rem, 2vw, 1.4rem), 6vw); line-height: 1.5; color: var(--fg-1); font-weight: 500; }
.prose p + p { margin-top: 18px; }
.prose p { font-size: 16px; line-height: 1.75; color: var(--fg-3); }

/* EN BREVE callout + generic 2-col callout boxes */
.callout {
  border: 1px solid var(--line); border-radius: 14px; background: var(--surface);
  padding: 24px; box-shadow: var(--shadow-card);
}
@media (min-width: 768px) { .callout { padding: 32px; } }
.callout .kicker { margin-bottom: 12px; }
.callout p { font-size: min(clamp(1.05rem, 1.8vw, 1.3rem), 5.5vw); line-height: 1.6; color: var(--fg-2); }
.cboxes { display: grid; gap: 16px; }
@media (min-width: 768px) { .cboxes { grid-template-columns: 1fr 1fr; } }
.cbox { border: 1px solid var(--line); border-radius: 14px; padding: 22px; background: var(--paper); }
.cbox p { font-size: 15px; line-height: 1.6; color: var(--fg-3); }
.cbox strong { color: var(--fg-1); font-weight: 600; }

/* --- Stat variant: the two standalone claims on Diseño Web were walls of grey
   15px text. Leading each with the figure it is actually about gives the eye
   somewhere to land and turns a paragraph into a fact. --------------------- */
.cbox--stat { display: grid; gap: 12px; }
/* `.cbox p` is (0,1,1) and would beat a bare `.cbox__fig` (0,1,0), silently
   rendering the figure at 15px body size. Qualify it so it actually wins. */
.cbox p.cbox__fig {
  font-size: min(clamp(2.2rem, 5.5vw, 3.4rem), 11vw); font-weight: 600; line-height: .95;
  letter-spacing: -.03em; color: var(--fg-1);
}
.cbox__fig i { font-style: normal; color: var(--accent-text); }
.cbox__fig small { font-size: .42em; font-weight: 600; letter-spacing: -.01em; color: var(--fg-4); }

/* --- Icon variant: small tile + heading row, for the two SEO cards. -------- */
.cbox--ico { display: grid; gap: 14px; }
.cbox__ico {
  display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: 11px; background: rgba(131, 56, 236, .10); color: var(--accent-text);
}
.cbox__ico svg { width: 19px; height: 19px; }

/* ==========================================================================
   CORE WEB VITALS METER — the "¿Qué incluye?" section argued about speed in
   prose only. This shows the three metrics Google actually measures, with each
   bar filling to where a passing score sits inside its own threshold.

   The figures are the public web.dev "good" thresholds (LCP ≤2.5s, INP ≤200ms,
   CLS ≤0.1) plus a representative passing value — NOT a measured claim about a
   specific client site, so nothing here is a fabricated stat.

   No-JS/reduced-motion: bars render already filled (project reveal contract —
   the no-JS default is always the visible one).
   ========================================================================== */
.vitals {
  display: grid; gap: 18px; margin-top: 32px; padding: 24px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--paper);
}
@media (min-width: 700px) { .vitals { grid-template-columns: repeat(3, 1fr); gap: 32px; padding: 32px; } }

.vital__top { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.vital__name { font-size: 12px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-4); }
.vital__val { font-size: 15px; font-weight: 600; color: var(--ok); font-variant-numeric: tabular-nums; }
.vital__track {
  position: relative; height: 7px; margin-top: 12px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
}
.vital__fill {
  height: 100%; border-radius: 999px; width: var(--pct);
  background: linear-gradient(90deg, var(--ok-soft), var(--ok));
}
.vital__note { margin-top: 10px; font-size: 12.5px; line-height: 1.5; color: var(--fg-4); }
.vital--lcp { --pct: 46%; }
.vital--inp { --pct: 33%; }
.vital--cls { --pct: 28%; }

:root.js-on .vital__fill { width: 0; transition: width 1.1s var(--ease); }
:root.js-on .rv.v .vital__fill { width: var(--pct); }
@media (prefers-reduced-motion: reduce) {
  :root.js-on .vital__fill { width: var(--pct); transition: none; }
}

/* ==========================================================================
   CHAT MOCK (Agentes IA) — the "¿Qué hace un agente?" section described a
   conversation in three paragraphs of prose. Showing one is worth more than
   describing it, and it demonstrates the "nunca inventa" rule in the last
   exchange rather than just asserting it.

   Illustrative sample dialogue, not a transcript of a real customer.
   ========================================================================== */
.chat {
  display: grid; gap: 12px; margin-top: 32px; padding: 22px;
  max-width: 52rem;   /* match .prose — full 1200px would give absurd line lengths */
  border: 1px solid var(--line); border-radius: 16px; background: var(--paper);
  box-shadow: var(--shadow-product);
}
@media (min-width: 768px) { .chat { padding: 28px; gap: 14px; } }

.chat__row { display: flex; }
.chat__row--out { justify-content: flex-end; }
.bubble {
  max-width: 80%; padding: 12px 15px; border-radius: 16px;
  font-size: 14.5px; line-height: 1.5;
}
.bubble--in { background: var(--surface-2); color: var(--fg-2); border-bottom-left-radius: 5px; }
/* white on --accent measures 5.6:1 — AA for body text. */
.bubble--out { background: var(--accent); color: #fff; border-bottom-right-radius: 5px; }
.chat__meta {
  display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: 12px; color: var(--fg-4);
}
.chat__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); flex: none; }

/* Bubbles land one after another, like the conversation is happening. */
:root.js-on .rv .chat__row { opacity: 0; transform: translateY(10px); transition: opacity .5s var(--ease), transform .5s var(--ease); }
:root.js-on .rv.v .chat__row { opacity: 1; transform: none; }
:root.js-on .rv.v .chat__row:nth-child(2) { transition-delay: .18s; }
:root.js-on .rv.v .chat__row:nth-child(3) { transition-delay: .36s; }
:root.js-on .rv.v .chat__row:nth-child(4) { transition-delay: .54s; }
:root.js-on .rv.v .chat__row:nth-child(5) { transition-delay: .72s; }
@media (prefers-reduced-motion: reduce) {
  :root.js-on .rv .chat__row { opacity: 1; transform: none; transition: none; }
}

/* ==========================================================================
   FLOW (Automatización) — same problem, same fix: the section explained a
   pipeline in prose. This draws it. Trigger → steps → outcome, with the
   connector chevrons sitting in the grid gap on desktop.
   ========================================================================== */
.flow { display: grid; gap: 14px; margin-top: 32px; }
@media (min-width: 900px) { .flow { grid-template-columns: repeat(4, 1fr); gap: 28px; } }

.flow__node {
  position: relative; padding: 20px; border-radius: 14px;
  border: 1px solid var(--line); background: var(--paper);
}
.flow__node--trigger { background: var(--surface); border-color: var(--accent); }
.flow__ico {
  display: grid; place-items: center; width: 34px; height: 34px; margin-bottom: 12px;
  border-radius: 10px; background: rgba(131, 56, 236, .10); color: var(--accent-text);
}
.flow__ico svg { width: 17px; height: 17px; }
.flow__node h3 { font-size: 14px; font-weight: 600; letter-spacing: .02em; }
.flow__node p { margin-top: 6px; font-size: 13px; line-height: 1.55; color: var(--fg-3); }
.flow__tag {
  display: inline-block; margin-bottom: 10px; font-size: 10.5px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent-text);
}

/* Connector: chevron centred in the gap. Hidden on the stacked mobile layout
   and after the last node. */
.flow__node::after {
  content: ''; position: absolute; z-index: 1; display: none;
  top: 50%; right: -19px; width: 10px; height: 10px;
  border-top: 2px solid var(--line); border-right: 2px solid var(--line);
  transform: translateY(-50%) rotate(45deg);
}
@media (min-width: 900px) {
  .flow__node::after { display: block; }
  .flow__node:last-child::after { display: none; }
}

/* ==========================================================================
   SERVICES rows (homepage) — 01..0n. Hairline, ghost numeral, then content.
   ========================================================================== */
.svc__list { display: flex; flex-direction: column; gap: 28px; }
@media (min-width: 640px) { .svc__list { gap: 64px; } }
@media (min-width: 768px) { .svc__list { gap: 104px; } }
.svc__row { position: relative; }

.ghost {
  position: absolute; top: -12px; right: 0; z-index: 0; font-size: var(--fs-ghost);
  font-weight: 700; line-height: 1; color: rgba(11, 7, 16, .05);
  pointer-events: none; user-select: none;
}
.svc__head {
  position: relative; z-index: 1; display: flex; flex-direction: column; gap: 6px;
  padding-top: 8px; margin-bottom: 14px;
}
@media (min-width: 640px) { .svc__head { gap: 8px; padding-top: 20px; margin-bottom: 24px; } }
@media (min-width: 768px) { .svc__head { flex-direction: row; align-items: baseline; gap: 32px; } }
.svc__n { font-size: 14px; font-weight: 600; letter-spacing: .1em; color: var(--accent-text); }
.svc__head h3 { font-size: var(--fs-h3); font-weight: 600; line-height: 1.05; letter-spacing: -.02em; text-transform: uppercase; }
@media (min-width: 768px) { .svc__head h3 { width: 30%; } }
.svc__head p { font-size: 15px; line-height: 1.6; color: var(--fg-3); }
@media (min-width: 768px) { .svc__head p { flex: 1; font-size: 16px; } }

.svc__split { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr; gap: 16px; padding-top: 8px; }
@media (min-width: 768px) { .svc__split { grid-template-columns: 1fr 1fr; align-items: center; gap: 56px; padding-top: 20px; }
  .svc__split--flip .svc__copy { order: 2; } }
/* min-width:0 lets the grid track actually shrink; without it a single long
   uppercase word (AUTOMATIZACIÓN) sets a min-content floor that widens the
   track past the viewport. break-word is the last resort if one still can't
   fit — better a broken word than a page that scrolls sideways. */
.svc__copy { display: flex; flex-direction: column; gap: 8px; min-width: 0; overflow-wrap: break-word; }
.svc__copy h3 { font-size: var(--fs-h3); font-weight: 600; line-height: 1.05; letter-spacing: -.02em; text-transform: uppercase; }
.svc__copy > p { font-size: 15px; line-height: 1.6; color: var(--fg-3); }
@media (min-width: 768px) { .svc__copy > p { font-size: 16px; } }
.svc__copy ul { display: flex; flex-direction: column; gap: 8px; margin-top: 6px; }
.svc__copy li { position: relative; padding-left: 20px; font-size: 14px; line-height: 1.55; color: var(--fg-3); }
.svc__copy li::before { content: ''; position: absolute; left: 0; top: .5em; width: 7px; height: 7px; border-radius: 999px; background: var(--accent); }

/* Icon-led service visual (uses the 3D icons small + clean, on a light card). */
.svc__media { position: relative; z-index: 1; display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.svc__media--single { grid-template-columns: 1fr; }
/* No frame, no tint. The 3D icons are rendered on transparent and carry their
   own colour and lighting; a bordered card with a violet radial behind them
   just boxed them in and muddied the render. Raül removed it 2026-07-18 —
   the icon sits directly on the page. Do not reintroduce a card/tint here. */
.card {
  position: relative; display: flex; align-items: center; justify-content: center;
  aspect-ratio: 16/11;
}
/* Bigger now that no box constrains it — 460px source at ~280px stays crisp. */
.card img { width: auto; height: 86%; max-height: 280px; }

.svc__row .alink { margin-top: 14px; }
@media (min-width: 640px) { .svc__row .alink { margin-top: 24px; } }

/* Chips (más servicios) */
.chips { position: relative; z-index: 1; display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
  display: inline-flex; align-items: center; padding: 10px 16px; border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--line); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--fg-2);
  transition: box-shadow .25s var(--ease), color .25s var(--ease), background-color .25s var(--ease);
}
.chip:hover { box-shadow: inset 0 0 0 1px var(--accent); background: rgba(131,56,236,.08); color: var(--ink); }
@media (min-width: 640px) { .chip { font-size: 12px; } }
.more__link { margin-top: 22px; font-size: 16px; }
@media (min-width: 768px) { .more__link { margin-top: 40px; font-size: 18px; } }

/* ==========================================================================
   LO QUE GANAS — 2x2 feature grid (bordered cards).
   ========================================================================== */
.gains { display: grid; gap: 0; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--paper); }
@media (min-width: 700px) { .gains { grid-template-columns: 1fr 1fr; } }
.gain { padding: 24px; border-bottom: 1px solid var(--line); transition: background-color .3s var(--ease); }
.gain:hover { background: var(--surface); }
/* Line icon in a soft violet tile. Inline SVG, not the 3D .webp set: at 40px a
   460px render is wasted bytes, and the flat stroke matches the nav/btn icons. */
.gain__ico {
  display: grid; place-items: center; width: 42px; height: 42px; margin-bottom: 16px;
  border-radius: 12px; background: rgba(131, 56, 236, .10); color: var(--accent-text);
  transition: background-color .3s var(--ease), transform .3s var(--ease);
}
.gain:hover .gain__ico { background: rgba(131, 56, 236, .18); transform: translateY(-2px); }
.gain__ico svg { width: 21px; height: 21px; }
@media (prefers-reduced-motion: reduce) { .gain:hover .gain__ico { transform: none; } }
@media (min-width: 700px) { .gain:nth-child(odd) { border-right: 1px solid var(--line); } .gain:nth-last-child(-n+2) { border-bottom: 0; } }
@media (max-width: 699px) { .gain:last-child { border-bottom: 0; } }
@media (min-width: 768px) { .gain { padding: 32px; } }
.gain h3 { font-size: 18px; font-weight: 600; }
.gain p { margin-top: 8px; font-size: 14px; line-height: 1.6; color: var(--fg-3); }

/* ==========================================================================
   LA DIFERENCIA — comparison table.
   ========================================================================== */
.compare { width: 100%; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.compare table { width: 100%; border-collapse: collapse; }
.compare th, .compare td { text-align: left; padding: 16px 20px; font-size: 15px; border-bottom: 1px solid var(--line); }
.compare thead th { background: var(--surface-2); font-weight: 600; }
.compare thead th:last-child { color: var(--accent-text); }
.compare tbody tr:last-child td { border-bottom: 0; }
.compare td:first-child, .compare th:first-child { font-weight: 600; color: var(--fg-1); width: 26%; }
.compare .neg { color: var(--fg-4); }
.compare .pos { color: var(--fg-1); }
/* Both columns get a glyph, so the eye reads the contrast as a pair rather than
   as one decorated column next to one bare one. */
.compare .neg::before { content: '✕'; color: var(--fg-4); font-weight: 700; margin-right: 8px; }
.compare .pos::before { content: '✓'; color: var(--accent); font-weight: 700; margin-right: 8px; }
/* Tint the "Con Silvero" column the whole way down so the answer column reads
   as the destination. Kept very light — text on it still measures >13:1. */
.compare td:last-child, .compare thead th:last-child { background: rgba(131, 56, 236, .05); }

/* ==========================================================================
   CÓMO TRABAJAMOS — numbered steps (homepage + inner pages).
   ========================================================================== */
.steps { display: grid; gap: 24px; }
@media (min-width: 768px) { .steps { grid-template-columns: repeat(3, 1fr); gap: 32px; } }
.step { position: relative; padding-top: 18px; border-top: 1px solid var(--line); }
.step .ghost { top: -10px; font-size: min(clamp(3rem, 6vw, 5.5rem), 15.25vw); }
.step em { position: relative; z-index: 1; font-style: normal; display: block; font-size: 11px; font-weight: 600; letter-spacing: .1em; color: var(--accent-text); }
.step h3 { position: relative; z-index: 1; margin-top: 12px; font-size: 16px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.step p { position: relative; z-index: 1; margin-top: 8px; font-size: 14px; line-height: 1.65; color: var(--fg-3); }

/* ==========================================================================
   FAQ — native <details>. Works with JS dead; keyboard-operable for free.
   ========================================================================== */
.faq__list { margin: 0 auto; max-width: 56rem; }
.faq__item { border-top: 1px solid var(--line); }
.faq__item:last-child { border-bottom: 1px solid var(--line); }
.faq__q {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  width: 100%; padding: 18px 0; text-align: left; cursor: pointer; list-style: none;
  font-size: 17px; font-weight: 600; line-height: 1.35;
}
.faq__q::-webkit-details-marker { display: none; }
@media (min-width: 768px) { .faq__q { padding: 26px 0; font-size: 19px; } }
.faq__icon { position: relative; flex: none; width: 16px; height: 16px; }
.faq__icon::before, .faq__icon::after { content: ''; position: absolute; left: 50%; top: 50%; background: var(--fg-2); transition: transform .3s var(--ease); }
.faq__icon::before { width: 14px; height: 1.5px; transform: translate(-50%, -50%); }
.faq__icon::after { width: 1.5px; height: 14px; transform: translate(-50%, -50%); }
.faq__item[open] .faq__icon::before { transform: translate(-50%, -50%) rotate(135deg); }
.faq__item[open] .faq__icon::after { transform: translate(-50%, -50%) rotate(135deg); }
.faq__panel p { padding-bottom: 18px; font-size: 15px; line-height: 1.7; color: var(--fg-3); }
@media (min-width: 768px) { .faq__panel p { padding-bottom: 28px; font-size: 16px; } }
.faq__panel a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 3px; }
@supports selector(::details-content) {
  .faq__item::details-content { block-size: 0; overflow: hidden; opacity: 0;
    transition: block-size .3s ease-in-out, opacity .3s ease-in-out, content-visibility .3s allow-discrete; }
  .faq__item[open]::details-content { block-size: auto; opacity: 1; }
}

/* ==========================================================================
   SERVICIOS RELACIONADOS — 3 link cards.
   ========================================================================== */
.rel { display: grid; gap: 16px; }
@media (min-width: 768px) { .rel { grid-template-columns: repeat(3, 1fr); } }
.rel__card {
  display: flex; flex-direction: column; gap: 10px; padding: 24px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--paper);
  transition: box-shadow .25s var(--ease), transform .25s var(--ease);
}
.rel__card:hover { box-shadow: var(--shadow-card); transform: translateY(-2px); }
.rel__card img { width: 68px; height: 68px; }
/* work-gallery card uses a full-width screenshot instead of a small icon */
.rel__card .rel__thumb { width: 100%; height: auto; aspect-ratio: 16/10; object-fit: cover; border-radius: 8px; }
.rel__card h3 { font-size: 18px; font-weight: 600; }
.rel__card p { font-size: 14px; line-height: 1.55; color: var(--fg-3); flex: 1; }
.rel__card .alink { font-size: 12px; }

/* ==========================================================================
   BLOG.
   ========================================================================== */
.blogfilters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 32px; }
.pill {
  padding: 9px 18px; border-radius: 999px; font-size: 12px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; box-shadow: inset 0 0 0 1px var(--line); color: var(--fg-2);
}
.pill[aria-current] { background: var(--accent); color: #fff; box-shadow: none; }

.feature {
  display: grid; grid-template-columns: 1fr; gap: 0; border: 1px solid var(--line);
  border-radius: 16px; overflow: hidden; margin-bottom: 24px; background: var(--paper);
}
@media (min-width: 768px) { .feature { grid-template-columns: 1.1fr 1fr; } }
.feature__img { aspect-ratio: 16/10; background: linear-gradient(135deg, #8338ec22, #4a7bff22); overflow: hidden; }
.feature__img img, .post__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.feature__body { padding: 28px; display: flex; flex-direction: column; justify-content: center; }
@media (min-width: 768px) { .feature__body { padding: 44px; } }
.feature__meta { font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-4); }
.feature__meta b { color: var(--accent-text); }
.feature h2 { margin-top: 12px; font-size: min(clamp(1.4rem, 3vw, 2.2rem), 7.25vw); font-weight: 600; line-height: 1.1; letter-spacing: -.01em; }
.feature p { margin-top: 12px; font-size: 15px; line-height: 1.6; color: var(--fg-3); }
.feature .alink { margin-top: 20px; }

.posts { display: grid; gap: 20px; }
@media (min-width: 640px) { .posts { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .posts { grid-template-columns: repeat(3, 1fr); } }
.post {
  display: flex; flex-direction: column; border: 1px solid var(--line);
  border-radius: 14px; overflow: hidden; background: var(--paper);
  transition: box-shadow .25s var(--ease), transform .25s var(--ease);
}
.post:hover { box-shadow: var(--shadow-card); transform: translateY(-2px); }
.post__img { aspect-ratio: 16/10; background: linear-gradient(135deg, #8338ec18, #4a7bff18); overflow: hidden; }
.post__body { padding: 20px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
.post__tag { font-size: 10px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-text); }
.post h3 { font-size: 17px; font-weight: 600; line-height: 1.25; }
.post p { font-size: 13px; line-height: 1.55; color: var(--fg-3); flex: 1; }
.post__meta { font-size: 11px; color: var(--fg-4); letter-spacing: .04em; }

/* ==========================================================================
   ARTICLE (blog post) — readable measure, cited sources.
   ========================================================================== */
.article { padding: calc(var(--nav-h) + 32px) var(--pad) 0; }
@media (min-width: 768px) { .article { padding-top: calc(var(--nav-h) + 56px); } }
/* NOTE on the min(clamp(…), Nvw) pattern used throughout this file: a clamp's
   FLOOR is in rem, so it grows with the user's root font size and can exceed a
   narrow viewport — .article h1 overflowed 320px at root 24px. Every clamp with
   a rem floor is capped in vw, sized so the vw term only wins once the root
   font is enlarged. Keep the wrapper when editing any of these sizes. */
.article__head { max-width: 46rem; margin: 0 auto; }
.article__meta { margin-top: 20px; font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--fg-4); }
.article__meta b { color: var(--accent-text); }
.article h1 { margin-top: 12px; font-size: min(clamp(1.9rem, 4.6vw, 3.4rem), 9.75vw); font-weight: 600; line-height: 1.08; letter-spacing: -.02em; }
.article__lede { margin-top: 18px; font-size: min(clamp(1.1rem, 2vw, 1.35rem), 5.75vw); line-height: 1.5; color: var(--fg-2); }
/* Sits to the RIGHT of the title block, vertically centred against it — it was
   a full-width banner under the lede, then stacked above the title, which read
   too stiff. No card, border or shadow: the 3D renders carry their own colour
   and a tinted panel just boxed them in (same call as .card and .mock--icon).
   The image files also had a grey backdrop baked in; that was normalised to
   pure white so nothing draws a square on the page.
   Small on purpose — ~280px against an 880px source is a 3× downscale. */
.article__hero { margin: 20px 0 0; max-width: 22rem; }
.article__hero img { width: 100%; height: auto; display: block; aspect-ratio: 880/491; }

.article__intro { display: grid; gap: 4px; }
@media (min-width: 900px) {
  /* Two columns: the title block leads, the image sits beside it at matched
     height. Below 900px it stacks — titles first, image after — because a
     ~250px column cannot hold the display type. */
  .article__intro { grid-template-columns: 1fr minmax(200px, 32%); gap: clamp(28px, 3vw, 52px); align-items: center; }
  .article__titles { min-width: 0; }
  .article__hero { margin: 0; max-width: none; }
  /* The head widens for the split so the h1 is not squeezed into ~440px.
     (The matching .article__body override lives AFTER its base rule below —
     media queries add no specificity, so putting it here would silently lose
     to the plain `.article__body { max-width: 42rem }` further down.) */
  .article__head { max-width: 58rem; }
  /* Capped harder than the full-width --fs-page: this column is ~600px, and
     the uncapped 3.4rem set long Spanish headlines to six or seven lines. */
  .article h1 { font-size: min(clamp(1.7rem, 3.4vw, 2.6rem), 7vw); }
}
.article__body { max-width: 42rem; margin: 40px auto 0; }
@media (min-width: 768px) { .article__body { margin-top: 56px; } }
/* With the split head at 58rem, a 42rem centred body sits visibly indented
   under the title. Share the head's container and flush the prose left inside
   it; the children keep the 42rem reading measure, only the alignment moves. */
@media (min-width: 900px) {
  .article__body { max-width: 58rem; }
  .article__body > * { max-width: 42rem; }
}
.article__body h2 { margin: 40px 0 14px; font-size: min(clamp(1.3rem, 2.6vw, 1.8rem), 6.75vw); font-weight: 600; letter-spacing: -.01em; line-height: 1.15; }
.article__body h3 { margin: 28px 0 10px; font-size: 1.15rem; font-weight: 600; }
.article__body p { font-size: 1.02rem; line-height: 1.75; color: var(--fg-2); margin-bottom: 16px; }
.article__body a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
.article__body ul { margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.article__body li { position: relative; padding-left: 20px; font-size: 1.02rem; line-height: 1.6; color: var(--fg-2); }
.article__body li::before { content: ''; position: absolute; left: 0; top: .6em; width: 7px; height: 7px; border-radius: 999px; background: var(--accent); }
.article__body blockquote { margin: 24px 0; padding: 4px 0 4px 20px; border-left: 3px solid var(--accent); font-size: 1.1rem; line-height: 1.5; color: var(--fg-1); }
.article__sources { max-width: 42rem; margin: 40px auto 0; padding: 24px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); }
.article__sources h2 { margin: 0 0 12px; font-size: 13px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-4); }
.article__sources ol { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
.article__sources li { font-size: 13px; line-height: 1.5; color: var(--fg-3); }
.article__sources a { color: var(--accent-text); text-decoration: underline; word-break: break-word; }
.article__back { display: inline-flex; align-items: center; gap: 6px; margin: 40px auto 0; max-width: 42rem; font-size: 13px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--accent-text); }

/* ==========================================================================
   CONTACT — light. Underline inputs, dark submit.
   ========================================================================== */
.contact { background: var(--paper); color: var(--ink); padding: 48px var(--pad); }
@media (min-width: 640px) { .contact { padding-block: 96px; } }
@media (min-width: 768px) { .contact { padding-block: 132px; } }
.contact__grid { display: grid; gap: 40px; max-width: 1200px; margin: 0 auto; }
@media (min-width: 768px) { .contact__grid { grid-template-columns: 1fr 1fr; gap: 64px; } }
@media (min-width: 1024px) { .contact__grid { gap: 96px; } }
.contact__lede { margin-top: 20px; max-width: 26rem; font-size: 15px; line-height: 1.7; color: var(--fg-3); }
.contact__meta { margin-top: 28px; display: flex; flex-direction: column; gap: 6px; }
.contact__meta a, .contact__meta span { font-size: 13px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.contact__meta a:first-child { color: var(--accent-text); }
.contact__meta span { color: var(--fg-4); }
.contact__meta + .btn { margin-top: 28px; }
.contact form { display: flex; flex-direction: column; gap: 24px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 10px; font-weight: 600; letter-spacing: .15em; text-transform: uppercase; color: var(--fg-4); }
.field label i { font-style: normal; color: var(--accent-text); }
.field input, .field select, .field textarea {
  width: 100%; padding: 9px 0; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; background: transparent; font-size: 15px; color: var(--ink);
  transition: border-color .3s var(--ease);
}
.field textarea { resize: vertical; min-height: 88px; }
.field input::placeholder, .field textarea::placeholder { color: rgba(11,7,16,.4); }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-bottom-color: var(--accent); }
.field select {
  appearance: none; -webkit-appearance: none; padding-right: 24px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230b0710' stroke-opacity='.5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 2px center; background-size: 16px;
}
.contact form .btn { align-self: flex-start; }

/* ==========================================================================
   CLOSING CTA BAND + FOOTER — the dark anchor.
   ========================================================================== */
.cta {
  --accent-text: var(--accent-lift);
  background: var(--ink); color: #fff; padding: 56px var(--pad);
}
@media (min-width: 768px) { .cta { padding-block: 96px; } }
.cta__grid { display: flex; flex-direction: column; gap: 28px; max-width: 1200px; margin: 0 auto; }
@media (min-width: 768px) { .cta__grid { flex-direction: row; align-items: center; justify-content: space-between; gap: 40px; } }
.cta__grid .display { flex: 1 1 auto; min-width: 0; }
.cta__btns { display: flex; flex-wrap: wrap; gap: 12px; }
@media (min-width: 768px) { .cta__btns { flex: none; flex-wrap: nowrap; } }

.foot { --accent-text: var(--accent-lift); background: var(--ink); color: #fff; padding: 48px var(--pad) 24px; }
.foot__grid { display: grid; gap: 32px; max-width: 1200px; margin: 0 auto; }
@media (min-width: 768px) { .foot__grid { grid-template-columns: 1.4fr 1fr 1fr; gap: 48px; } }
.foot__brand { display: flex; align-items: center; gap: 8px; }
.foot__brand img { height: 24px; width: auto; }
.foot__brand span { font-size: 14px; font-weight: 600; }
.foot__about { margin-top: 14px; max-width: 24rem; font-size: 13px; line-height: 1.7; color: var(--on-dark-3); }
.foot__contact { margin-top: 16px; display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.foot__contact a { font-size: 13px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; }
.foot__contact a:first-child { color: var(--accent-text); }
/* Two numbers: the Spanish one is the company line, the Irish one is kept for
   Irish callers who would rather not dial abroad. The "(Ireland)" label is
   dialled back so it reads as the alternative, not a second equal option.

   CONTEXT MATTERS: this block appears BOTH in the dark footer AND in the light
   .contact section on the homepage. Styling the label with the dark-surface
   token alone made it white-on-white in the contact block — invisible, and it
   failed the contrast sweep at 1.00:1. Light is the default; the footer
   overrides. No blanket opacity on the link either: dimming an already
   AA-borderline colour is how you fail the audit without seeing it. */
.tel-ie span { font-weight: 500; letter-spacing: .04em; color: var(--fg-4); }
.foot .tel-ie span { color: var(--on-dark-3); }

/* Instagram: icon + handle on one line. align-items:flex-start on the parent
   stops the inline-flex stretching to the full column width. */
.foot__social { display: inline-flex; align-items: center; gap: 8px; margin-top: 6px; }
.foot__social svg { width: 16px; height: 16px; flex: none; }
.foot__social span { text-transform: none; letter-spacing: .03em; }
.foot__col h3 { margin: 0 0 14px; font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--on-dark-3); }
.foot__col li { margin-bottom: 9px; }
.foot__col a { font-size: 13px; color: var(--on-dark-2); transition: color .25s var(--ease); }
.foot__col a:hover { color: var(--accent-text); }
.foot__base {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px;
  max-width: 1200px; margin: 40px auto 0; padding-top: 20px; border-top: 1px solid var(--line-dark);
  font-size: 12px; color: var(--on-dark-3);
}

/* ==========================================================================
   SCROLL-READ — a native rebuild of a React/motion "ClipText" component (which
   can't run here: static site, CSP `script-src 'self'`, no npm). The text is a
   two-tone gradient clipped to the glyphs; as the element scrolls up through
   the viewport the solid half sweeps across, so the line "reads in".

   Pure CSS via `animation-timeline: view()` — no JS, no scroll handler, CSP-safe.
   FALLBACK is solid, readable --fg-1: without scroll-timeline support OR with
   reduced motion, `.read` is just normal text. So it always passes contrast;
   the audit excludes it only because mid-sweep it is intentionally two-tone.
   ========================================================================== */

.read { color: var(--fg-1); }   /* the fallback, and the resting colour */

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .read {
      color: transparent;
      background-image: linear-gradient(90deg, var(--fg-1) 50%, rgba(11, 7, 16, .26) 50%);
      background-size: 200% 100%;
      background-position: 100% 0;
      background-repeat: no-repeat;
      -webkit-background-clip: text;
      background-clip: text;
      -webkit-text-fill-color: transparent;
      animation: read-in linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 42%;
    }
  }
}
@keyframes read-in { to { background-position: 0 0; } }

/* ==========================================================================
   HIGHLIGHTER — a native rebuild of the React "TextHighlighter" (marker sweeps
   behind a phrase when it enters view). A soft brand-violet highlight grows
   left→right on the first scroll into view. site.js adds .v via an observer.
   No-JS: background-size stays 0 → plain readable text (never hidden). Colour
   is a light tint, so black text over it stays ~15:1.
   ========================================================================== */

mark.hl {
  --hl: rgba(131, 56, 236, .20);
  color: inherit;
  /* kill the UA <mark> yellow — otherwise it shows through the semi-transparent
     violet gradient and reads muddy-gold. */
  background-color: transparent;
  background-image: linear-gradient(var(--hl), var(--hl));
  background-repeat: no-repeat;
  background-position: 0 0;
  background-size: 0% 100%;
  padding: 0 .06em;
  border-radius: .14em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size .6s var(--ease);
}
:root.js-on mark.hl.v { background-size: 100% 100%; }

/* ==========================================================================
   Reveals. CONTRACT: no-JS default is VISIBLE. site.js adds .js-on, only then
   does .rv hide. Never invert.
   ========================================================================== */
:root.js-on .rv { opacity: 0; transform: translateY(28px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
:root.js-on .rv.v { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { :root.js-on .rv { opacity: 1; transform: none; transition: none; } }

/* ==========================================================================
   COOKIE / STORAGE NOTICE
   The site sets no analytics, advertising or social cookies — only two
   functional keys in localStorage (language preference, and the fact this
   notice was dismissed). Under LSSI-CE art. 22.2 that is exempt from prior
   consent, so this banner INFORMS rather than gating the page: there is no
   "reject" because there is nothing non-essential to reject.

   If analytics is ever added, this must become a real consent gate with an
   explicit reject option, and nothing may load before a choice is made.
   ========================================================================== */
.cookie {
  position: fixed; z-index: 60; left: 16px; right: 16px; bottom: 16px;
  display: none; gap: 16px; align-items: center; flex-wrap: wrap;
  max-width: 640px; padding: 18px 20px;
  background: var(--paper); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: 0 12px 40px rgba(11, 7, 16, .18);
}
:root.js-on .cookie[data-open="1"] { display: flex; }
@media (min-width: 768px) { .cookie { left: 26px; right: auto; bottom: 26px; } }
.cookie__text { flex: 1 1 240px; font-size: 13.5px; line-height: 1.55; color: var(--fg-3); }
.cookie__text b { color: var(--fg-1); font-weight: 600; }
.cookie__actions { display: flex; gap: 10px; flex-wrap: wrap; }
.cookie__more {
  display: inline-flex; align-items: center; padding: 10px 16px;
  font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  border-radius: 999px; color: var(--fg-1); border: 1px solid var(--line);
  transition: border-color .25s var(--ease), color .25s var(--ease);
}
.cookie__more:hover { color: var(--accent-text); border-color: var(--accent); }
.cookie__ok {
  padding: 10px 18px; font-size: 12px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; border-radius: 999px; cursor: pointer;
  color: #fff; background: var(--accent); border: 1px solid var(--accent);
  transition: background-color .25s var(--ease);
}
.cookie__ok:hover { background: var(--ink); border-color: var(--ink); }
/* Sits above the WhatsApp button, which would otherwise overlap it. */
@media (max-width: 767px) { .cookie { bottom: 84px; } }

/* Legal links row in the footer base */
.foot__legal { display: flex; flex-wrap: wrap; gap: 14px; }
.foot__legal a { font-size: 12px; color: var(--on-dark-3); transition: color .25s var(--ease); }
.foot__legal a:hover { color: var(--on-dark-1); }

/* Legal pages carry long unbroken strings — an email address, a full fiscal
   address, policy URLs — inside list items. At 320px with the root font at
   24px those exceed the 280px column and push the page sideways (measured:
   316px of content in a 280px box). break-word only splits when there is no
   other option, so normal prose is unaffected. */
.legal .article__body li,
.legal .article__body p,
.legal .article__body a { overflow-wrap: break-word; }
.legal .article__body ul { padding-left: 0; }

/* Honeypot. Hidden with CSS, never with [hidden] or type=hidden — bots read
   those and skip the field, which is exactly what we do not want. Kept out of
   the tab order and off the accessibility tree. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Contact form status line */
.form-note { margin-top: 14px; font-size: 14px; line-height: 1.5; min-height: 1.2em; }
.form-note[data-state="busy"] { color: var(--fg-3); }
.form-note[data-state="ok"]   { color: var(--ok); font-weight: 600; }
.form-note[data-state="warn"] { color: var(--fg-2); }
.contact [type="submit"][data-busy] { opacity: .6; cursor: progress; }

/* ==========================================================================
   Reviews — Google-style band, home page only.
   Pure-CSS marquee: the track holds two identical .rev-set halves (the second
   aria-hidden), so translateX(-50%) loops seamlessly. Gap lives INSIDE each
   set (gap + padding-right) so the -50% offset lands exactly on the seam.
   Pauses on hover/focus; reduced-motion swaps to a plain scroller and hides
   the duplicate half so keyboard/scroll users never meet cloned content. */
.reviews { overflow: clip; }

.reviews__head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 18px; }

/* Rating chip: white pill, official G, five stars. Numbers pass AA (--fg). */
.gchip {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--paper); border: 1px solid var(--line-2); border-radius: 999px;
  padding: 10px 18px; box-shadow: var(--shadow-card);
}
.gchip__g { width: 22px; height: 22px; flex: none; }
.gchip strong { font-size: 15px; letter-spacing: .01em; }
.gchip__stars { display: inline-flex; gap: 2px; }
.gchip__stars svg, .rev-card__stars svg { width: 15px; height: 15px; fill: #fbbc04; }
.gchip small { color: var(--fg-3); font-size: 12.5px; }

.rev-viewport {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}
.rev-track { display: flex; width: max-content; padding-block: 10px 26px; animation: rev-scroll 48s linear infinite; }
.rev-viewport:hover .rev-track,
.rev-viewport:focus-within .rev-track { animation-play-state: paused; }
@keyframes rev-scroll { to { transform: translateX(-50%); } }

.rev-set { display: flex; gap: 18px; padding-right: 18px; }

.rev-card {
  width: min(360px, 82vw); flex: none;
  display: flex; flex-direction: column; gap: 13px;
  background: var(--paper); border: 1px solid var(--line-2); border-radius: 18px;
  padding: 22px 24px; box-shadow: var(--shadow-card);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.rev-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-product); }

.rev-card__top { display: flex; align-items: center; gap: 12px; }
.rev-card__avatar {
  width: 42px; height: 42px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  font-weight: 700; font-size: 17px;
}
/* Brand-triad avatars, rotating per card position inside each set. Tints stay
   ≥AA because the letter is the full-strength brand colour on a 14% tint. */
.rev-set > :nth-child(3n + 1) .rev-card__avatar { background: rgba(131, 56, 236, .14); color: #6c2bc4; }
.rev-set > :nth-child(3n + 2) .rev-card__avatar { background: rgba(255, 93, 177, .16); color: #b3316f; }
.rev-set > :nth-child(3n + 3) .rev-card__avatar { background: rgba(74, 123, 255, .14); color: #2f5bd0; }
.rev-card__who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rev-card__who strong { font-size: 15px; }
.rev-card__who span { color: var(--fg-3); font-size: 13px; }
.rev-card__top > .rev-card__g { width: 20px; height: 20px; margin-left: auto; flex: none; }

.rev-card__stars { display: inline-flex; gap: 2px; }
.rev-card p { color: var(--fg-2); font-size: 15px; line-height: 1.6; margin: 0; }
.rev-card__src { color: var(--fg-3); font-size: 12.5px; margin-top: auto; }

@media (prefers-reduced-motion: reduce) {
  .rev-track { animation: none; width: auto; }
  .rev-viewport { overflow-x: auto; -webkit-mask-image: none; mask-image: none; scroll-snap-type: x proximity; }
  .rev-card { scroll-snap-align: start; }
  .rev-set[aria-hidden="true"] { display: none; }
}

/* Long reviews (Damien's runs ~700 chars) get clamped so the deck keeps an
   even rhythm; the full text lives on the Google listing. */
.rev-card p {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 9;
  overflow: hidden;
}

/* Review dialog — click any card to read the full text. Native <dialog>:
   focus trap, Esc and backdrop click come free. */
.rev-card { cursor: pointer; }
.rev-card__more { color: var(--accent-text); font-weight: 600; }

.rev-dialog {
  border: 0; padding: 0; background: transparent; max-width: none;
}
.rev-dialog::backdrop { background: rgba(11, 7, 16, .45); backdrop-filter: blur(6px); }
.rev-dialog__box {
  width: min(560px, 92vw); max-height: 82vh; overflow: auto;
  background: var(--paper); border-radius: 22px; box-shadow: var(--shadow-product);
  padding: 30px 32px 26px; position: relative;
  display: flex; flex-direction: column; gap: 14px;
}
.rev-dialog[open] .rev-dialog__box { animation: rev-pop .32s var(--ease); }
@keyframes rev-pop { from { opacity: 0; transform: translateY(14px) scale(.97); } }
@media (prefers-reduced-motion: reduce) { .rev-dialog[open] .rev-dialog__box { animation: none; } }

.rev-dialog__close {
  position: absolute; top: 14px; right: 14px;
  width: 36px; height: 36px; border: 0; border-radius: 50%;
  background: var(--surface); color: var(--fg-2); cursor: pointer;
  display: grid; place-items: center; font-size: 20px; line-height: 1;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}
.rev-dialog__close:hover { background: var(--surface-2); color: var(--fg); }

.rev-dialog__box .rev-card__avatar { background: rgba(131, 56, 236, .14); color: #6c2bc4; }
.rev-dialog__text { color: var(--fg-2); font-size: 16px; line-height: 1.7; margin: 0; white-space: pre-line; }
/* While the dialog is open the deck behind holds still. */
:root.rev-open .rev-track { animation-play-state: paused; }
