    /* ============================================================
       1. RESET
    ============================================================ */
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

    /* ============================================================
       2. CUSTOM PROPERTIES
    ============================================================ */
    /* DARK SYSTEM. The site is black-based; the tokens below describe that
       system directly — there is no light theme left to fall back to.

       Surfaces are a four-step ELEVATION scale, and the steps are FLAT: blocks
       are separated by a change of layer plus a hairline, never by a gradient.
       That is not a stylistic preference, it is the lesson from the hero's old
       radial spotlight: a ramp between two dark greys only spans ~12 distinct
       levels, so the browser paints it as hard-edged arcs (the "recortes en
       blanco" the client reported twice). Anything here that genuinely needs
       depth gets .grain on top to dither the steps — see section 3.

       Contrast measured (WCAG relative luminance), not eyeballed:
                          L0      L1      L2      L3
         --text-primary   17.99   16.74   15.48   13.75
         --text-secondary  8.14    7.57    7.01    6.22
         --text-muted      6.53    6.08    5.62    4.99
         --accent          5.97    5.55    5.14    4.56
       Nothing drops below 4.99:1 for text. --text-muted was picked for the L3
       column specifically: #8A8884 reads identically at a glance and fails
       there (4.28:1). */
    :root {
      /* ---- surfaces: flat elevation layers, never blended into each other ---- */
      --bg-base:        #0A0A0A;  /* L0 — page, hero, #automations, #clientes, footer */
      --bg-alt:         #141414;  /* L1 — alternating sections: #proceso, #diagnostico-limite, #implementacion, #faq, #contact, #team */
      --bg-surface:     #1C1C1C;  /* L2 — cards, panels, the terminal, the form */
      --bg-raised:      #262626;  /* L3 — hover surfaces, inputs, chips */

      /* Kept as the page-background alias so `body`/section rules read the
         same as before; it is L0, not a separate colour. */
      --bg-primary:     var(--bg-base);

      /* The crema that used to BE the page is now the ink on it — same hue,
         so the brand's warmth survives the inversion. */
      --text-primary:   #F5F4F0;
      --text-secondary: #A8A6A1;
      --text-muted:     #96948F;

      --border:         #262626;  /* hairline on L0/L1 */
      --border-strong:  #333333;  /* hairline on L2/L3, where #262626 vanishes */

      /* ---- accent — MINT (2026-07-28) ----
         The single hierarchy colour of the site: active states, underlines, the
         primary CTA, focus rings. Never body copy, never two accents in one
         section. (--accent used to be near-black and meant "maximum contrast",
         which is meaningless on a black page; then coral #FF4B3E; now mint.)

         ONE LEVEL, and that was a decision, not an omission. A two-step accent
         was proposed — #8effd7 for fills, a darker #4DC79E for thin marks —
         because the mint sits at 1.09:1 against --text-primary, i.e. the accent
         and the body ink have essentially the SAME luminance, where the old
         coral had 3.02:1. The worry was that a 2px underline or a 17px dot would
         read as "another light mark" rather than "the brand lit up".

         Rejected on evidence. WCAG contrast measures luminance only; it answers
         "is this text legible on this background", not "can I tell these two
         colours apart". The right metric there is CIEDE2000:

             #8effd7 vs --text-primary   ΔE 23.7   (chroma 42.4 vs 2.1)
             #4DC79E vs --text-primary   ΔE 28.4
             --text-secondary vs --text-primary   ΔE 19.0   <- for scale

         ΔE 1.0 is the just-noticeable difference. The mint is FURTHER from the
         body ink than --text-secondary is, and nobody struggles to tell those
         two apart: the separation comes from chroma, not luminance. The second
         level bought +20% on a distance that was already unmistakable.

         And it made things worse where it was supposed to help — screenshotted
         side by side, #4DC79E visibly dulls the 16px .auto-icon glyphs, the
         thin accent marks (the .dl-line-on underline, .ip-point-dot;
         at the time, #ps's active step dot) and the #clientes underline and
         arrow. It recovers
         separation from the INK at the cost of separation from the BACKGROUND,
         and background is what surrounds a thin mark. Don't reintroduce it
         without re-running that comparison.

         --accent-ink is the text colour ON a solid accent fill: it must be DARK
         (16.44:1). --accent-hover goes DOWN in luminance — #8effd7 is already at
         78% lightness, so lightening it just walks toward white. */
      --accent:         #8effd7;
      --accent-ink:     #0A0A0A;  /* 16.44:1 on --accent */
      --accent-hover:   #6FE6BE;  /* 12.95:1 on L0, ΔE 5.7 from --accent */
      --accent-dim:     rgba(142, 255, 215, 0.14);  /* chips / soft fills */

      /* Fine fractal noise as an inline SVG data-URI — no network request, no
         asset file. This is the dither used on every soft gradient in the
         page; see the .grain note in section 3 for why it is mandatory. */
      --grain-url:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");

      --ff-display:     'Space Grotesk', system-ui, sans-serif;
      --ff-body:        'DM Sans', system-ui, sans-serif;
      --radius:         16px;
      --ease:           cubic-bezier(0.4, 0, 0.2, 1);
      --t:              0.3s cubic-bezier(0.4, 0, 0.2, 1);
    }

    /* ============================================================
       3. BASE
    ============================================================ */
    html {
      font-size: 16px;
      scroll-behavior: auto;
      overflow-x: clip;
    }

    body {
      background-color: var(--bg-primary);
      color: var(--text-primary);
      font-family: var(--ff-body);
      font-weight: 400;
      line-height: 1.6;
      overflow-x: clip;
      -webkit-font-smoothing: antialiased;
    }

    ::selection { background: rgba(142, 255, 215, 0.28); color: var(--text-primary); }

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

    /* Tell the UA this is a dark document, so form controls, scrollbars and
       the like render their dark variants instead of light chrome on black. */
    :root { color-scheme: dark; }

    /* ---- .grain — the ONLY sanctioned way to add depth to a dark surface ----
       A dark-on-dark gradient spans so few distinct grey levels that the
       browser paints it as visible bands (see the note in section 2). Overlay
       this on top of such a gradient and the noise dithers the steps apart, so
       the ramp reads smooth instead of striped. Inline feTurbulence data-URI:
       no network request, no image asset, ~400 bytes.

       Usage: a positioned ::after or a child with .grain, inside anything that
       carries a soft gradient — currently .cl-hero-scrim, .faq-glow,
       .ct-form-glow, .demo-glow and #contact's own background.

       baseFrequency is high (0.8) so the grain is fine enough to read as film
       texture rather than as visible dots, and opacity stays low: it only has
       to break up ~1/255 steps, not be seen. */
    .grain::after,
    .grain-layer {
      content: '';
      position: absolute;
      inset: 0;
      pointer-events: none;
      opacity: 0.05;
      mix-blend-mode: screen;
      background-image: var(--grain-url);
    }

    /* Where the dither is actually applied, and why each one is a separate rule
       rather than a shared class: the noise CANNOT be a child of the element it
       dithers. Every one of these halos carries filter: blur(...) on itself, and
       that filter applies to descendants too — noise nested inside would simply
       be blurred away. So each layer is a sibling ::after painted after the glow
       and before the content.

       Measured, not assumed. Scanning a rendered column and counting flat runs
       between luminance steps, before adding these:
         #contact background   20 levels / 495px, bands averaging 21px
         .faq-glow             31 levels / 540px, bands averaging 12px
       Both are the same signature as the hero's old radial spotlight, which the
       client reported twice as "recortes en blanco". */
    .faq-stage::after,
    .ct-form-wrap::after,
    .demo-copy::after,
    .demo-cta-zone::after,
    .dc-wrap::after,
    #contact::before {
      content: '';
      position: absolute;
      z-index: 0;
      pointer-events: none;
      /* `screen`, not `overlay`. Overlay's formula collapses toward zero as the
         backdrop darkens (2ab for b<0.5), so on an L0/L1 surface it barely
         moves the pixel and leaves the bands intact — measured: it took
         #contact's average band run from 21px only to 10px. Screen is additive
         on dark (1-(1-a)(1-b) ≈ b when a≈0), which is exactly the ±1 level of
         variance a dither needs. */
      opacity: 0.05;
      mix-blend-mode: screen;
      background-image: var(--grain-url);
      /* Feather the edges. `screen` of a non-zero-mean noise uniformly lifts the
         whole rectangle a hair, which on pure black reads as a hard-edged
         lighter BOX around each glass card ("recuadro" the client flagged) — a
         band, then black. A radial mask fades the grain to transparent before
         its own edge, so it only dithers the glow's core (where banding is)
         and blends into the black instead of drawing a rectangle. */
      -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, #000 40%, transparent 78%);
      mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, #000 40%, transparent 78%);
    }
    /* Each one spans its own glow's box, which overflows its parent by a
       different amount. */
    .faq-stage::after    { inset: -90px; }
    .ct-form-wrap::after { inset: -70px; }
    .demo-copy::after    { inset: -80px; }
    .demo-cta-zone::after{ inset: 0; }
    .dc-wrap::after       { inset: -70px; }
    #contact::before     { inset: 0; }

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

    /* ============================================================
       4. TYPOGRAPHY
    ============================================================ */
    h1, h2, h3, h4 {
      font-family: var(--ff-display);
      font-weight: 700;
      line-height: 1.02;
      letter-spacing: -0.03em;
      color: var(--text-primary);
    }

    /* ============================================================
       5. LAYOUT
    ============================================================ */
    .wrap { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 28px; }

    /* ============================================================
       6. SCROLL REVEAL INITIAL STATES
    ============================================================ */
    /* Hidden-by-default only once script.js confirms GSAP/ScrollTrigger/Lenis
       loaded and tags <html> as .js-ready — if the CDN fails, these elements
       stay at their natural visible state instead of being stuck at opacity:0. */
    .js-ready .rv   { opacity: 0; transform: translateY(30px);  will-change: transform, opacity; }
    .js-ready .rv-l { opacity: 0; transform: translateX(-30px); will-change: transform, opacity; }
    .js-ready .rv-r { opacity: 0; transform: translateX(30px);  will-change: transform, opacity; }

    /* Title "ink" scroll reveal — words start as a pale ghost, script.js
       scrubs them to full color+opacity as the title scrolls through view.
       Same .js-ready gating as .rv above: without it, words sit at their
       normal color/opacity (no wrap needed either, but the class stays
       harmless if script.js never runs). */
    .tr-word { display: inline-block; }
    /* Ghost state of the ink reveal. Must stay in sync with the `from` values
       in script.js -> TITLE INK REVEAL (see the note there on why those are
       literal hex and not var()). Was #DBD9D3, a pale warm grey for the old
       light system. */
    .js-ready .tr-word-inner { display: inline-block; opacity: 0.35; color: #2E2E2E; }

    /* ============================================================
       7. NAVBAR
    ============================================================ */
    /* Translucent rather than a solid layer: the nav crosses L0 and L1 sections
       as you scroll, and any single flat colour would read as a seam against
       one of them. Blurred + tinted, it sits over both. The hairline is what
       actually separates it, not the fill. */
    #nav {
      position: fixed;
      top: 0; left: 0; right: 0;
      z-index: 1000;
      background-color: rgba(10, 10, 10, 0.72);
      backdrop-filter: blur(14px) saturate(140%);
      -webkit-backdrop-filter: blur(14px) saturate(140%);
      border-bottom: 1px solid var(--border);
    }
    @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
      #nav { background-color: #0D0D0D; }
    }

    .nav-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 32px;
      height: 64px;
    }

    .nav-logo {
      display: flex;
      align-items: center;
      gap: 10px;
      font-family: var(--ff-display);
      font-size: 18px;
      font-weight: 700;
      color: var(--text-primary);
      letter-spacing: -0.02em;
      white-space: nowrap;
    }
    /* No invert(1), and no recolouring of any kind. The mark is now
       gummalogo-tiffa.png (836x200): "gu" and "a" in white, "mm" in the
       brand mint — the same two-tone artwork the client supplied. It replaced
       the old flat light-grey wordmark, which was a single #BABABA. Any
       filter here (invert, brightness) would break the two-tone. */
    .nav-logo img {
      height: 32px;
      width: auto;
      display: block;
    }

    /* Logo puzzle entry — 5 spans, each a pixel-exact crop of
       assets/img/gummalogo-tiffa.png. Letter column bounds measured from the
       source PNG by scanning for alpha gaps: g[0,142] u[153,265] m[282,473]
       m[483,680] a[694,835]. Each tile runs to the NEXT letter's start so it
       carries its own trailing gap and the five tiles rebuild the image
       exactly when placed edge-to-edge. 836x200 @ 32px display = 0.16 scale,
       so the full mark is 133.76px wide. */
    .logo-puzzle {
      display: flex;
      height: 32px;
    }
    .logo-letter {
      display: block;
      height: 32px;
      background-image: url('assets/img/gummalogo-tiffa.png');
      background-repeat: no-repeat;
      background-size: 133.76px 32px;
      overflow: hidden;
      animation-duration: 0.6s;
      animation-timing-function: cubic-bezier(.2, .8, .2, 1);
      animation-fill-mode: both;
    }
    .logo-letter:nth-child(1) { width: 24.48px; background-position: 0 0;         animation-name: logo-drop; animation-delay: 0.1s; }
    .logo-letter:nth-child(2) { width: 20.64px; background-position: -24.48px 0;  animation-name: logo-rise; animation-delay: 0.22s; }
    .logo-letter:nth-child(3) { width: 32.16px; background-position: -45.12px 0;  animation-name: logo-drop; animation-delay: 0.34s; }
    .logo-letter:nth-child(4) { width: 33.76px; background-position: -77.28px 0;  animation-name: logo-rise; animation-delay: 0.46s; }
    .logo-letter:nth-child(5) { width: 22.72px; background-position: -111.04px 0; animation-name: logo-drop; animation-delay: 0.58s; }

    @keyframes logo-drop {
      from { transform: translateY(-140%); opacity: 0; }
      to   { transform: translateY(0);     opacity: 1; }
    }
    @keyframes logo-rise {
      from { transform: translateY(140%); opacity: 0; }
      to   { transform: translateY(0);    opacity: 1; }
    }
    @media (prefers-reduced-motion: reduce) {
      .logo-letter { animation: none; opacity: 1; transform: none; }
    }

    .nav-links {
      display: flex;
      align-items: center;
      gap: 32px;
      list-style: none;
    }
    .nav-links a {
      font-family: var(--ff-body);
      font-size: 14px;
      font-weight: 400;
      color: var(--text-secondary);
      transition: color 0.2s;
    }
    .nav-links a:hover,
    .nav-links a.active { color: var(--text-primary); }

    /* The one accent-filled control in the nav. Dark ink on the coral, not
       white: white on this hue measures 3.32:1 and fails. */
    .nav-btn {
      display: inline-flex;
      align-items: center;
      padding: 10px 24px;
      background: var(--accent);
      color: var(--accent-ink);
      border-radius: 100px;
      font-family: var(--ff-body);
      font-size: 14px;
      font-weight: 500;
      transition: opacity 0.2s, transform 0.2s;
      white-space: nowrap;
    }
    /* Explicit darker fill instead of the old `opacity: 0.8x`. Fading the
       button faded its --accent-ink LABEL with it, dropping the label's own
       contrast for no reason; --accent-hover changes only the surface. It
       goes DOWN in luminance because --accent is already at 78% lightness —
       lightening a mint that pale just walks it toward white. */
    .nav-btn:hover { background: var(--accent-hover); transform: translateY(-1px); }

    .nav-burger {
      display: none;
      flex-direction: column;
      gap: 5px;
      background: none;
      border: none;
      cursor: pointer;
      padding: 4px;
    }
    .nav-burger span {
      display: block;
      width: 22px;
      height: 2px;
      background: var(--text-primary);
      border-radius: 2px;
      transition: var(--t);
    }

    .mob-menu {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--bg-base);
      z-index: 998;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 28px;
    }
    .mob-menu.open { display: flex; }
    .mob-menu a {
      font-family: var(--ff-display);
      font-size: clamp(28px, 6vw, 48px);
      font-weight: 700;
      letter-spacing: -0.03em;
      color: var(--text-secondary);
      transition: color 0.2s;
    }
    .mob-menu a:hover { color: var(--text-primary); }
    .mob-close {
      position: absolute;
      top: 24px; right: 28px;
      background: none;
      border: none;
      color: var(--text-secondary);
      font-size: 26px;
      cursor: pointer;
    }
    .mob-close:hover { color: var(--text-primary); }

    /* ============================================================
       8. HERO
    ============================================================ */
    /* Scroll runway + pin for the WebGL laptop frame animation (script.js,
       "HERO — LAPTOP FRAME ANIMATION"). Purely additive: #hero itself is
       unchanged and, once pinned inside the 100vh sticky wrapper, looks
       exactly as it did before. .hero-scroll-off collapses both back to
       normal document flow — added by script.js whenever the animation
       won't run (prefers-reduced-motion, <768px, or Three.js/frames failing
       to load), so nobody ever scrolls 300vh of nothing. */
    /* The runway itself is painted black (2026-07-26, "recortes en blanco al
       scrollear"). The page's base background is the crema --bg-primary, i.e.
       almost white, and while a sticky box scrolls the browser rasterizes its
       new tiles asynchronously — any tile that hasn't landed yet shows the
       base background through. On the old crema hero that was invisible;
       against #0A0A0A it reads as white patches flashing around the laptop.
       Backing the whole 300vh runway in the hero's own black means a
       not-yet-painted tile is black on black. */
    /* The background here is no longer the white-flash patch it was created
       as — the page base is black now, so a late-rasterized sticky tile shows
       black regardless. It stays because it is still the correct paint source
       for the sticky box, costs nothing, and removing it would make this
       depend on body's paint order for no gain. */
    .hero-scroll { height: 300vh; background-color: var(--bg-base); }
    .hero-sticky {
      position: sticky;
      top: 0;
      height: 100vh;
      /* Hard guarantee that a pinned hero can never paint outside its 100vh
         box and bleed over the section below it (today #automations; it was
         #ps, then #proceso, when the bug was reported by the client). The
         rules in the min-width:768px block below are what actually make the
         hero FIT in that box, so nothing is clipped; this is just the safety
         net. */
      overflow: hidden;
    }
    .hero-scroll.hero-scroll-off { height: auto; }
    .hero-scroll.hero-scroll-off .hero-sticky {
      position: static;
      height: auto;
    }

    /* Dark hero (2026-07-23, explicit request). This is the ONE dark section
       above the fold — the site's palette is otherwise crema/near-black, and
       nothing here leaks out: every rule in this block is scoped to #hero, and
       the shared `.btn-primary`/`.btn-outline`/`.h-*` classes are overridden
       only via `#hero ...` descendants, so the same classes in #nav, #contact
       and the footer are untouched.
       FLAT #0A0A0A, no gradient (2026-07-26). It used to carry a radial
       spotlight centred on the laptop (#1a1a1a → #141414 → #0A0A0A). The idea
       was fine, the maths isn't: that whole ramp is only ~12 distinct grey
       levels spread over ~1000px, so the browser paints it as ~48px hard-edged
       bands (measured on a render: levels 10..22, one step every ~48px). On a
       decent dark screen those arcs read as light patches around the laptop —
       the "recortes en blanco / fondo gris" the client kept seeing, and the
       reason they thought a crema layer was still showing through. There is no
       cheap fix: any dark-on-dark gradient this shallow bands unless it's
       dithered with a noise overlay, which is a lot of machinery for a glow
       nobody asked for. If a spotlight ever comes back, it needs either a much
       wider luminance range or a noise/grain layer on top to break the steps.
       History: the gradient replaced a subtle grey diffusion over crema, which
       had itself replaced the fondomanos photo and, before that, the hands
       video's poster frame. */
    #hero {
      background-color: var(--bg-base);
      min-height: 100dvh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 140px 0 120px;
      position: relative;
      overflow: clip;
    }

    /* The `#hero ... { }` override block that used to live here is GONE.
       It existed because #hero was the one dark section on a light site, so
       every colour inside it had to be re-stated as a literal. Now that the
       tokens describe the dark system themselves, each of those seven rules
       was restating exactly what it already inherits — keeping them would
       have meant two sources of truth for the same colours. The base rules
       further down (.hero-h, .h-outline, .h-line-sub, .h-caret, .hero-sub,
       .btn-primary, .btn-outline) carry it now. */

    /* ---- Pinned hero: make it fit the viewport exactly ----
       The hero's natural height is its content + 260px of padding, which at
       1440x900 came to 993px — 93px TALLER than the 100vh sticky box, so the
       overflow spilled over the next section. Scoped to the pinned case only (>=768px and
       not .hero-scroll-off), so the mobile and reduced-motion heroes keep
       their normal, untouched flow layout. The vh term in the title clamp is
       what makes this hold up on short-but-wide viewports (e.g. 1920x800),
       where the poster type would otherwise still overflow; on a typical
       1440x900 it resolves to ~171px, i.e. what it already was. */
    @media (min-width: 768px) {
      .hero-scroll:not(.hero-scroll-off) #hero {
        height: 100%;
        min-height: 0;
        padding: clamp(84px, 11vh, 140px) 0 clamp(32px, 7vh, 120px);
      }
      /* Promote the pinned hero to its own compositor layer (2026-07-26).
         Its background is a full-viewport radial gradient, and a sticky box
         moves relative to its scroller on every scroll frame — without this,
         that gradient plus the poster type is re-rasterized as the visitor
         scrolls, which is both the async-paint source of the white flashes
         above and real main-thread cost while the laptop is animating. Painted
         once, then only moved by the compositor; the canvas inside keeps its
         own layer and updates independently. Scoped to the pinned case, so
         mobile / .hero-scroll-off don't pay for the extra layer. */
      .hero-scroll:not(.hero-scroll-off) .hero-sticky {
        will-change: transform;
      }
      /* .hero-frozen is the reduced-motion case: runway collapsed, but the
         laptop canvas is still on screen frozen on frame 1, so the headline
         needs the same smaller clamp or it runs into it (script.js adds the
         class; see the comment there). */
      .hero-scroll:not(.hero-scroll-off) .hero-h,
      .hero-scroll.hero-frozen .hero-h {
        /* Shrunk (2026-07-19) to give the laptop animation (script.js,
           LAPTOP_W_FRAC) more room/protagonism — smaller headline, bigger
           laptop, per explicit ask. Then stepped down once more (÷1.2, same
           factor as the base .hero-h) to keep the whole title scale one notch
           smaller everywhere. */
        font-size: clamp(37px, min(6.7vw, 10.8vh), 108px);
      }
    }

    /* WebGL layer: the dark/LED backdrop plus the laptop canvas. Sits behind
       the hero copy (.hero-body is position:relative; z-index:1) and above
       #hero's own crema background and retro grid — both show through, since
       the renderer is alpha:true and the frames are real transparent cutouts.
       Never intercepts clicks. */
    .hero-webgl {
      position: absolute;
      inset: 0;
      z-index: 0;
      pointer-events: none;
    }
    /* Switched off as a unit when there's nothing to show at all (narrow
       screen, Three.js missing, frames failed). NOT tied to .hero-scroll-off:
       under prefers-reduced-motion the runway collapses but this stays, with
       the canvas frozen on a single frame. */
    .hero-webgl.hero-webgl-off { display: none; }

    .hero-canvas {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      display: block;
    }


    .hero-body {
      position: relative;
      z-index: 1;
      /* Centered (2026-08-11): the laptop animation is disabled for now (see
         script.js's DISABLED flag), so there's nothing anchored to the right
         edge to leave room for. Left-align returns automatically whenever the
         laptop is active again, via the .hero-scroll:not(.hero-scroll-off)
         override below — same reversible scoping the rest of this hero's
         laptop-aware CSS already uses. */
      text-align: center;
      width: 100%;
    }
    .hero-scroll:not(.hero-scroll-off) .hero-body { text-align: left; }

    /* Poster-style title: 'Anton' (condensed, single-weight ultra-bold display
       face built for exactly this look — tight-set headline type) replaces
       the site's usual Space Grotesk for the h1 only, per an explicit request
       for an impactful street-poster feel. Loaded alongside the existing
       Google Fonts <link> in index.html's <head>. Scoped to .hero-h — no
       other heading on the site uses it. */
    .hero-h {
      font-family: 'Anton', var(--ff-display), sans-serif;
      /* One step down the type scale (÷1.2, minor-third) from the original
         clamp(60px,12vw,180px) — all three values scaled by the same factor
         so the responsive ratio is unchanged, per an explicit "un escalón más
         pequeño" ask. The pinned-variant and ≤480px overrides below were
         stepped down by the same 1.2 factor to stay consistent. */
      font-size: clamp(50px, 10vw, 150px);
      font-weight: 400;
      line-height: 1.12;
      letter-spacing: -0.01em;
      color: var(--text-primary);
      margin-bottom: 32px;
      padding-top: 0.18em;
      overflow-wrap: break-word;
      text-transform: uppercase;
    }
    .h-line {
      display: block;
      /* was overflow: hidden — clipped the outline stroke's own overshoot
         above the glyph tops on 'AUTOMATIZA' (h-outline below), especially
         at the tight line-height this poster title used before. Visible
         costs the hard-edged mask on the word-reveal GSAP timeline (script.js
         '.h-word' from y:108%) — words now fade+rise instead of wiping in
         behind a mask — but nothing about the stroke gets cut off. */
      overflow: visible;
      padding-bottom: 0.05em;
    }
    .h-word {
      display: inline-block;
      will-change: transform;
      padding-right: 0.06em;
    }
    .h-word:last-child { padding-right: 0; }
    /* Line 1 'AUTOMATIZA' — outline-only per the client spec: transparent
       fill + a solid stroke so the page's own bg-primary shows through the
       letterforms (color: white would be used instead on a dark backdrop —
       this hero is light, so transparent is correct here). */
    /* The stroke follows the TEXT colour, not --accent. It used to say
       var(--accent) back when that token was near-black, i.e. the text colour;
       now that --accent is coral, pointing at it would paint a 150px coral
       outline across the fold — the opposite of a dosified accent. */
    .h-outline {
      color: transparent;
      -webkit-text-stroke: 2px var(--text-primary);
    }
    /* Line 3, the original subtitle ('Tú solo tienes que crecer') — kept
       discreet on purpose, so it drops back to the site's normal display
       font/casing instead of inheriting Anton's shouting all-caps poster
       treatment from .hero-h. */
    .h-line-sub {
      font-family: var(--ff-display);
      font-size: 0.28em;
      font-weight: 700;
      letter-spacing: -0.01em;
      text-transform: none;
      /* Explicit token at full opacity rather than 0.55 off the headline fill:
         fading white into black gives a flat, muddy grey, whereas the warm
         --text-secondary keeps the hue and measures 8.14:1. */
      color: var(--text-secondary);
      opacity: 1;
      /* Pushed down (was 0.3em) to separate this line from 'TU NEGOCIO' and
         drop it clear of the busy robotic-hand area of the video, where its
         low-contrast grey text was hard to read against the moving footage. */
      margin-top: 0.9em;
    }

    /* Typewriter title reveal (script.js "HERO TITLE — typewriter"): each word
       is split into per-character .h-char spans, hidden until typed in one at
       a time behind a moving caret. Gated by .js-ready so a motion-lib CDN
       failure leaves the whole title visible and static (no typing, no hidden
       characters). Chars use display (not opacity) so the line's width grows
       as it types, selling the "being written" feel. */
    .js-ready .hero-h .h-char { display: none; }
    .js-ready .hero-h .h-char.is-typed { display: inline; }

    .h-caret {
      display: inline-block;
      width: 0.07em;
      height: 0.74em;
      margin-left: 0.02em;
      vertical-align: -0.04em;
      background: var(--accent);
      animation: h-caret-blink 0.75s steps(1) infinite;
    }
    .h-caret.h-caret-done {
      animation: none;
      opacity: 0;
      transition: opacity 0.4s ease;
    }
    @keyframes h-caret-blink { 50% { opacity: 0; } }
    @media (prefers-reduced-motion: reduce) { .h-caret { display: none; } }

    .hero-sub {
      font-family: var(--ff-body);
      font-size: 18px;
      font-weight: 400;
      color: var(--text-secondary);
      max-width: 520px;
      /* auto side margins center the block itself -- text-align on .hero-body
         only centers inline content, a block-level <p> with its own max-width
         still needs this to actually move. Reverts to left with the rest of
         the hero body when the laptop is active (see .hero-body above). */
      margin: 0 auto 48px;
      line-height: 1.72;
    }
    .hero-scroll:not(.hero-scroll-off) .hero-sub { margin: 0 0 48px; }
    .js-ready .hero-sub { opacity: 0; }

    .hero-ctas {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 12px;
      flex-wrap: wrap;
    }
    .hero-scroll:not(.hero-scroll-off) .hero-ctas { justify-content: flex-start; }
    .js-ready .hero-ctas { opacity: 0; }

    /* ============================================================
       9. BUTTONS
    ============================================================ */
    .btn {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      padding: 14px 30px;
      border-radius: 100px;
      font-family: var(--ff-body);
      font-size: 15px;
      font-weight: 500;
      transition: all 0.25s var(--ease);
      white-space: nowrap;
    }
    /* Primary = the accent fill, with DARK ink on it (--accent-ink). This is
       forced by measurement, not taste: white on any coral in this family tops
       out at 3.32:1, while #0A0A0A on --accent gives 5.97:1.
       These base rules now describe the dark system directly, which is why the
       whole `#hero .btn-*` override block that used to invert them is gone. */
    .btn-primary {
      background: var(--accent);
      color: var(--accent-ink);
    }
    .btn-primary:hover { background: var(--accent-hover); transform: translateY(-2px); }

    .btn-outline {
      background: transparent;
      color: var(--text-primary);
      border: 1px solid rgba(245, 244, 240, 0.28);
    }
    .btn-outline:hover { border-color: var(--text-primary); transform: translateY(-2px); }

    /* ============================================================
       11. EL MODELO — #proceso, #diagnostico, #diagnostico-limite,
           #entregable, #implementacion
    ============================================================ */
    /* Estas cinco bandas vivieron en index.html (entre el hero y
       #automations, sustituyendo a #ps) hasta que se movieron en bloque a
       su propia página, diagnostico.html — el cliente pidió que el
       diagnóstico no "estuviera en medio" del scroll de la home. Las
       reglas de abajo no cambiaron: son selectores por id genéricos
       (background-color/padding sobre los tokens de capa), así que
       renderizan igual sea cual sea el HTML que las use.

       En index.html el hero (L0) queda ahora pegado directamente a
       #automations (L0) — sin banda intermedia, ambos son el mismo negro
       plano, así que no hace falta hairline: el runway pineado del hero ya
       hace de transición. La restricción de paridad de capas que exigía un
       número IMPAR de bandas intermedias ya no aplica ahí, porque ya no
       hay bandas intermedias.

       Dentro de diagnostico.html, en cambio, SÍ hay alternancia propia y
       cierra sola sin ayuda de ningún hero:

         #proceso L1 -> #diagnostico L0 -> #diagnostico-limite L1 ->
         #entregable L0 -> #implementacion L1 -> footer L0

       ELEVACIÓN. Todas las tarjetas son L2 + hairline + un realce interior
       de 1px arriba. Ni una box-shadow oscura (sobre negro no dibuja nada)
       ni un degradado entre grises (bandea, es el bug de los "recortes en
       blanco"). Ninguna de estas bandas lleva halo, así que ninguna
       necesita el dither .grain. */

    /* ---- 11.0 Fondo de red de nodos (SOLO #proceso, diagnostico.html) ----
       Puntos que derivan despacio y se conectan con líneas finas al
       acercarse, monocromo con nodos menta sueltos como acento (script.js
       "DIAGNOSTICO — NODE NETWORK"). Rima con el tema de la página: mapear
       procesos. NO es un gradiente — son puntos y líneas discretas —, así
       que no bandea y no necesita el dither .grain.

       ESTUVO EN LAS CINCO BANDAS y se replegó al hero de la página. Detrás
       de todo el scroll dejaba de ser un gesto y pasaba a ser ruido de fondo
       permanente; además obligaba a que las cinco secciones renunciaran a su
       fill sólido y usaran un tinte translúcido para dejarlo asomar, lo que
       aplanaba el ritmo de capas L0/L1 que da estructura a la página. Ahora
       el canvas es ABSOLUTO dentro de #proceso (no fixed al viewport) y las
       cinco bandas recuperan sus tokens de capa opacos. El canvas se suelta
       en <768px y, bajo reduce-motion, queda como un frame estático (ver la
       guarda en script.js). */
    .dg-net {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      z-index: 0;
      pointer-events: none;
      display: block;
    }
    /* El contenido de #proceso se eleva sobre su propio canvas. Las otras
       cuatro bandas ya no necesitan z-index: no hay nada detrás de ellas. */
    #proceso > .wrap { position: relative; z-index: 1; }

    /* ---- 11.1 #proceso — dos ofertas, un orden ---- */
    #proceso {
      position: relative;                /* ancla del canvas de la red */
      overflow: hidden;                  /* el canvas no se sale de la banda */
      background-color: var(--bg-alt);   /* L1 */
      padding: 120px 0;
    }
    .pr-head-text {
      font-family: var(--ff-body);
      font-size: 17px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 18px 0 0;
      max-width: 60ch;
    }

    /* El orden ES el mensaje, así que la flecha es un elemento real y no un
       ::after decorativo: a <=900px rota 90 grados y sigue leyéndose, en vez
       de desaparecer con el layout de dos columnas. Mismo patrón que
       .cl-flow en #clientes. */
    .pr-flow {
      margin-top: 56px;
      display: grid;
      grid-template-columns: 1fr auto 1fr;
      align-items: stretch;
      gap: 28px;
    }
    .pr-card {
      background: var(--bg-surface);     /* L2 */
      border: 1px solid var(--border-strong);
      border-radius: 20px;
      padding: 36px 32px;
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.06);
    }
    .pr-name {
      font-family: var(--ff-display);
      font-size: clamp(22px, 2.4vw, 28px);
      font-weight: 700;
      color: var(--text-primary);
      margin: 0 0 14px;
    }
    .pr-text {
      font-family: var(--ff-body);
      font-size: 15px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 0;
    }
    .pr-arrow {
      align-self: center;
      display: flex;
      color: var(--accent);
    }
    .pr-arrow svg { width: 40px; height: 24px; }

    /* Marca de acento como regla, no como color de texto. */
    .pr-rule {
      margin: 44px 0 0;
      text-align: center;
      font-family: var(--ff-display);
      font-size: clamp(20px, 2.4vw, 26px);
      font-weight: 700;
      color: var(--text-primary);
    }
    .pr-rule::before {
      content: '';
      display: block;
      width: 40px;
      height: 2px;
      margin: 0 auto 18px;
      background: var(--accent);
    }

    /* ---- 11.2 #diagnostico — las 4 áreas + las 4 fases ---- */
    /* La sección principal de la página, y el reparto de peso lo dice: las
       4 áreas son un apunte previo (texto sobre fondo, hairlines) y las 4
       fases se llevan un runway pineado entero. Antes las dos cosas eran
       tarjetas L2 idénticas y todo pesaba lo mismo.

       Historia, por si alguien busca clases que ya no existen: la v1 eran
       cuatro tarjetas apiladas con un .dg-num gris gigante; la v2 las
       convirtió en línea de tiempo (.dg-fase + .dg-fase-node + raíl
       .dg-fases::before + pastilla .dg-callout). Las dos siguen siendo el
       mismo módulo repetido cuatro veces. La v3 es .dgf-* (más abajo) y
       borró todas esas clases — viven en el historial de git. */
    #diagnostico {
      background-color: var(--bg-base);   /* L0 */
      padding: 120px 0;
    }
    .dg-head { max-width: 720px; margin-bottom: 48px; }
    .dg-head-text {
      font-family: var(--ff-body);
      font-size: 17px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 18px 0 0;
    }

    /* ---- 4 áreas ---- */
    .dg-areas-label {
      display: flex;
      align-items: center;
      gap: 8px;
      font-family: var(--ff-body);
      font-size: 12px;
      font-weight: 600;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--text-secondary);
      margin: 0 0 18px;
    }
    .dg-areas-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }

    /* Las 4 áreas: TEXTO SOBRE FONDO, no tarjetas. Antes eran cuatro cajas
       L2 con el icono en su propio chip L3, exactamente el mismo tratamiento
       que las fases — y las fases son el contenido principal de la página,
       así que competían de tú a tú con ellas. Ahora se separan con un
       hairline superior y el icono queda como glifo suelto: se siguen
       leyendo de un vistazo, pero pesan lo que tienen que pesar. */
    .dg-areas {
      list-style: none;
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 8px 32px;
      margin-bottom: 24px;
    }
    .dg-area {
      display: flex;
      align-items: flex-start;
      gap: 12px;
      border-top: 1px solid var(--border);
      padding: 18px 0 0;
    }
    .dg-area-icon {
      flex: 0 0 auto;
      color: var(--accent);
      font-size: 18px;
      line-height: 1.35;
    }
    .dg-area-name {
      font-family: var(--ff-display);
      font-weight: 700;
      font-size: 15px;
      color: var(--text-primary);
      margin: 0 0 4px;
    }
    .dg-area-desc {
      font-family: var(--ff-body);
      font-size: 13px;
      line-height: 1.5;
      color: var(--text-muted);
      margin: 0;
    }

    /* ---- 4 fases: narrativa con scroll (.dgf-*) ----
       SUSTITUYE a la línea de tiempo de tarjetas (las clases .dg-fase… y
       .dg-callout, ya borradas — ojo al escribir comentarios aquí: un
       ".dg-fase*" seguido de "/" cierra el comentario y se come las reglas
       de debajo). Aquello era el mismo módulo repetido cuatro veces — círculo
       con icono, kicker, título, párrafo y pastilla mint — y un recurso
       usado cuatro veces deja de ser una firma y pasa a ser un formulario.
       Además las cuatro pastillas mint se anulaban entre sí: cuatro frases
       lapidarias con idéntico tratamiento no destacan ninguna.

       Ahora: runway pineado (mismo mecanismo que el difunto #ps de
       index.html, clases propias) con el raíl de fases a la izquierda y el
       esquema de la fase activa a la derecha. Una sola frase por fase, un
       solo destacado mint en toda la sección (fase 03).

       LA BASE DE ESTAS REGLAS ES EL LAYOUT COLAPSADO — lista vertical con
       los cuatro esquemas visibles. Es .dgf-on, que añade script.js solo en
       desktop y sin prefers-reduced-motion, quien activa runway, sticky y
       dos columnas. Al revés que el viejo .ps-scroll-off, y a propósito: sin
       JS queda una lista legible en vez de cuatro esquemas superpuestos. */
    .dgf-label {
      display: flex;
      align-items: center;
      gap: 8px;
      font-family: var(--ff-body);
      font-size: 12px;
      font-weight: 600;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--text-secondary);
      margin: 0 0 32px;
    }
    .dgf-label-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }

    .dgf-steps {
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 40px;
    }
    .dgf-step {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: 18px;
      align-items: baseline;
    }
    .dgf-step + .dgf-step { border-top: 1px solid var(--border); padding-top: 40px; }

    /* El número sustituye al círculo-con-icono: Anton, el mismo display de
       cartel del hero de index.html, cargado aquí solo para esto. */
    .dgf-num {
      font-family: 'Anton', var(--ff-display);
      font-weight: 400;
      font-size: clamp(32px, 3.4vw, 52px);
      line-height: 0.9;
      color: var(--text-muted);
      transition: color 0.4s var(--ease);
    }
    .dgf-body { min-width: 0; }
    .dgf-title {
      font-family: var(--ff-display);
      font-size: clamp(19px, 2vw, 23px);
      font-weight: 700;
      line-height: 1.3;
      color: var(--text-primary);
      margin: 0;
      transition: color 0.4s var(--ease);
    }

    .dgf-vis { margin: 22px 0 0; max-width: 560px; }
    .dgf-cap {
      font-family: var(--ff-body);
      font-size: 15px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin-top: 22px;
      max-width: 48ch;
    }
    /* EL ÚNICO destacado mint de la sección. Las frases lapidarias de las
       fases 01, 02 y 04 se integraron en su propia frase; esta se queda
       sola y gana peso precisamente por eso. Mint como FONDO tenue, texto en
       tinta normal: mint nunca es color de cuerpo. */
    .dgf-flag {
      display: flex;
      width: max-content;
      max-width: 100%;
      margin-top: 16px;
      background: var(--accent-dim);
      border: 1px solid rgba(142, 255, 215, 0.35);
      border-radius: 10px;
      padding: 10px 16px;
      font-family: var(--ff-display);
      font-weight: 700;
      font-size: 14.5px;
      line-height: 1.4;
      color: var(--text-primary);
    }

    /* ---- Los esquemas ----
       Dibujos honestos, no maquetas: ni una cifra, ni un nombre, ni una
       línea de texto falso. Las "etiquetas" de dentro de cada caja son dos
       barritas que dicen "aquí hay un texto" sin inventarse cuál. El color
       base del trazo viaja por currentColor desde el <svg> para que los
       marcadores de flecha (que heredan del root del svg, no del path que
       los referencia) salgan del mismo token. */
    .dgf-svg { display: block; width: 100%; height: auto; color: var(--border-strong); }
    .dgf-box  { fill: var(--bg-surface); stroke: currentColor; stroke-width: 1.5; }
    .dgf-lbl  { fill: var(--border-strong); }
    .dgf-lbl-on { fill: var(--accent-ink); opacity: 0.45; }
    .dgf-link { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
    .dgf-dash { stroke-dasharray: 4 4; }
    .dgf-dim  { opacity: 0.4; }
    .dgf-x    { fill: none; stroke: var(--text-muted); stroke-width: 1.8; stroke-linecap: round; }
    .dgf-mint { fill: none; stroke: var(--accent); stroke-width: 2; }
    .dgf-mint-fill { fill: var(--accent); }
    /* Barras del roadmap (fase 04). La primera va rellena de acento — es el
       "empieza por aquí" —, y por eso sus etiquetas van en --accent-ink:
       relleno de acento SIEMPRE lleva tinta oscura. */
    .dgf-bar    { fill: var(--bg-raised); stroke: currentColor; stroke-width: 1.5; }
    .dgf-bar-on { fill: var(--accent); }
    .dgf-dot  { fill: var(--bg-base); stroke: currentColor; stroke-width: 1.5; }
    /* El aviso se rellena con el fondo de la banda para punzar la línea que
       pasa por debajo — así se lee como una marca puesta encima del flujo. */
    .dgf-warn { fill: var(--bg-base); stroke: var(--text-primary); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }

    /* ---- .dgf-on: el modo pineado, solo desktop (lo pone script.js) ---- */
    .dgf-scroll.dgf-on { height: 360vh; }
    .dgf-on .dgf-sticky {
      position: sticky;
      top: 0;
      height: 100vh;
      display: flex;
      flex-direction: column;
      justify-content: center;
      overflow: hidden;
    }
    .dgf-on .dgf-layout { position: relative; min-height: 380px; }
    .dgf-on .dgf-steps { width: 42%; gap: 0; }
    .dgf-on .dgf-step {
      padding: 16px 0 16px 22px;
      border-top: 0;
      border-left: 2px solid transparent;
      transition: border-color 0.4s var(--ease);
    }
    .dgf-on .dgf-step.is-active { border-left-color: var(--accent); }
    .dgf-on .dgf-num   { color: var(--text-muted); }
    .dgf-on .dgf-title { color: var(--text-secondary); }
    .dgf-on .dgf-step.is-active .dgf-num   { color: var(--accent); }
    .dgf-on .dgf-step.is-active .dgf-title { color: var(--text-primary); }
    /* El visual se ancla a .dgf-layout, no a su <li>: los cuatro se
       superponen en la misma mitad derecha y solo se ve el activo. */
    .dgf-on .dgf-vis {
      position: absolute;
      top: 50%;
      right: 0;
      width: 52%;
      max-width: 560px;
      margin: 0;
      opacity: 0;
      visibility: hidden;
      transform: translateY(calc(-50% + 14px));
      transition: opacity 0.5s var(--ease), transform 0.5s var(--ease), visibility 0s linear 0.5s;
    }
    .dgf-on .dgf-step.is-active .dgf-vis {
      opacity: 1;
      visibility: visible;
      transform: translateY(-50%);
      transition: opacity 0.5s var(--ease), transform 0.5s var(--ease), visibility 0s;
    }

    /* ---- 11.3 #diagnostico-limite — banda de afirmación ---- */
    /* Cierra el diagnóstico. V2 (2026-08-09, corrección de estilo pedida
       por el cliente tras ver la V1 en vivo: "las líneas muy uniformes
       parecen como líneas rectas puestas de mala manera" + "la animación
       se produce muy rápido"):

       BUG REAL detrás de la primera queja, no solo gusto: .dl-statement
       es un flex-column, y sin align-items propio el valor por defecto es
       stretch — así que cada <span> (incluida .dl-neg) se ESTIRABA al
       ancho completo del bloque, y la barra ::after (left:0;right:0)
       tachaba ese ancho estirado, no el texto. Las tres negaciones tienen
       longitudes distintas ("No se ejecuta nada." vs "No se cambia ningún
       sistema.") pero las tres barras salían EXACTAMENTE iguales —de ahí
       "muy uniformes"— y centradas bajo texto más corto que la barra, de
       ahí "puestas de mala manera". Fix: `align-items: center` en
       .dl-statement, cada <span> vuelve a medir lo que mide su texto
       (shrink-to-fit) y lo que se dibuje encima por fin sigue la palabra.

       Con eso resuelto, el tachado en sí deja de ser una barra CSS recta
       (scaleX) y pasa a un trazo SVG a mano: un <path> ondulado con
       pathLength="100" (mismo truco que .cl-outline en el caso de
       estudio) animado por stroke-dashoffset 100→0, nunca scaleX de un
       rectángulo. Cada línea tiene su propia curva, grosor y una leve
       rotación (-0.8/0.5/-0.4deg) — un trazo real no sale perfectamente
       recto ni igual dos veces, y las tres curvas son intencionalmente
       distintas para que no se lean como el mismo elemento repetido tres
       veces. Sigue sin ser text-decoration: line-through — eso no se
       puede animar como un trazo y además leería mal para lectores de
       pantalla ("texto eliminado"); el <svg> es aria-hidden y puramente
       visual, el texto debajo sigue siendo texto real.

       Segunda queja, el ritmo: la V1 tachaba las tres negaciones casi a
       la vez (arranques a 200/450/700ms, 250ms de diferencia, trazo de
       solo 400ms) y encendía el halo en un opacity 0→1 seco de 900ms. V2
       separa el arranque de cada trazo ~650ms, alarga cada trazo a 750ms
       con una curva de deceleración marcada (cubic-bezier expo-out: una
       mano que frena al terminar el trazo, no un tween lineal), y el
       cierre —línea final con un ligero scale-in + halo que crece con su
       propia curva y luego respira suave en bucle— estira la secuencia
       completa de ~2.3s a ~3.6s. Dos capas de fondo sin cambios desde la
       V1:
         1. .dl-bg — textura de grano FIJA y constante en toda la sección
            (mismo --grain-url que .grain, pero aquí no dithera un
            degradado: es una textura ambiental de baja opacidad para que
            el negro deje de sentirse hueco).
         2. .dl-bloom — un halo mint (mismo tono que --accent, muy
            apagado) que nace en opacity:0 detrás de la línea final y solo
            se enciende cuando esa línea se ilumina — la luz representa la
            decisión, no decora la sección entera. Ahora además respira
            (dl-bloom-breathe, 7s, arranca cuando la transición de entrada
            ya ha terminado) en vez de quedarse estático una vez encendido.
       El disparo de la secuencia —añadir .dl-live— vive en un <script>
       inline al final de diagnostico.html, un IntersectionObserver
       independiente del sistema .rv genérico porque necesita orquestar
       una secuencia con temporización propia, no solo un fade-in. Ese
       script no cambió: toda la reescritura de ritmo vive en los
       transition-delay de aquí abajo. */
    #diagnostico-limite {
      position: relative;
      background-color: var(--bg-alt);   /* L1 */
      padding: 110px 0;
      overflow: hidden;
    }
    .dl-bg {
      position: absolute;
      inset: 0;
      pointer-events: none;
      opacity: 0.05;
      mix-blend-mode: screen;
      background-image: var(--grain-url);
    }
    .dl-wrap { position: relative; }
    .dl-bloom {
      position: absolute;
      left: 50%;
      top: 100%;
      width: 640px;
      height: 380px;
      transform: translate(-50%, -55%) scale(0.82);
      background: radial-gradient(closest-side, var(--accent-dim), transparent 72%);
      filter: blur(70px);
      opacity: 0;
      transition: opacity 1300ms var(--ease), transform 1300ms var(--ease);
      transition-delay: 2250ms;
      pointer-events: none;
    }
    /* La clase que activa la secuencia entera — la añade el pequeño
       IntersectionObserver al final del body cuando la sección entra en
       viewport. Un solo toggle, todo el ritmo vive en los transition-delay
       de abajo: nada de setTimeout ni timeline en JS. La respiración usa
       las MISMAS coordenadas finales que la transición de entrada (scale(1),
       opacity:1) en su 0%/100%, así no hay salto cuando la animation
       arranca al terminar la transición. */
    .dl-live .dl-bloom {
      opacity: 1;
      transform: translate(-50%, -55%) scale(1);
      animation: dl-bloom-breathe 7s ease-in-out 3600ms infinite;
    }
    @keyframes dl-bloom-breathe {
      0%, 100% { opacity: 1; transform: translate(-50%, -55%) scale(1); }
      50%      { opacity: 0.88; transform: translate(-50%, -55%) scale(1.05); }
    }

    .dl-statement {
      position: relative;
      z-index: 1;
      max-width: 900px;
      margin: 0 auto;
      text-align: center;
      display: flex;
      flex-direction: column;
      align-items: center;   /* cada <span> mide su propio texto, no el bloque entero — ver nota de arriba */
      gap: 16px;
      font-family: var(--ff-display);
      font-size: clamp(26px, 4vw, 44px);
      font-weight: 700;
      line-height: 1.24;
      color: var(--text-muted);
    }
    .dl-neg {
      position: relative;
      display: inline-block;
      padding: 0 4px;
      transition: opacity 550ms var(--ease);
    }
    .dl-strike {
      position: absolute;
      inset: -8% -3%;
      overflow: visible;
      pointer-events: none;
    }
    .dl-strike path {
      fill: none;
      stroke: var(--text-secondary);
      stroke-linecap: round;
      stroke-dasharray: 100;
      stroke-dashoffset: 100;
      transition: stroke-dashoffset 750ms cubic-bezier(0.16, 1, 0.3, 1);
    }
    .dl-live .dl-strike path { stroke-dashoffset: 0; }
    .dl-live .dl-neg { opacity: 0.45; }

    /* Grosor y rotación propios por línea (transform-origin: center, no
       desplaza el trazo fuera del texto) — la variación es lo que vende
       "a mano" en vez de "plantilla repetida". */
    .dl-neg[data-i="0"] .dl-strike { transform: rotate(-0.8deg); }
    .dl-neg[data-i="0"] .dl-strike path { stroke-width: 3.1; }
    .dl-neg[data-i="1"] .dl-strike { transform: rotate(0.5deg); }
    .dl-neg[data-i="1"] .dl-strike path { stroke-width: 2.6; }
    .dl-neg[data-i="2"] .dl-strike { transform: rotate(-0.4deg); }
    .dl-neg[data-i="2"] .dl-strike path { stroke-width: 3.4; }

    /* Arranque de cada trazo espaciado ~650ms — antes eran 200/450/700ms
       (250ms de diferencia, casi simultáneos); ahora hay tiempo real de
       ver una frase tachándose antes de que empiece la siguiente. La
       opacidad de cada negación baja ~400ms después de que arranca su
       propio trazo, para que el "apagado" llegue cuando el trazo ya se
       nota, no antes. */
    .dl-neg[data-i="0"] .dl-strike path { transition-delay: 300ms; }
    .dl-neg[data-i="1"] .dl-strike path { transition-delay: 950ms; }
    .dl-neg[data-i="2"] .dl-strike path { transition-delay: 1600ms; }
    .dl-neg[data-i="0"] { transition-delay: 700ms; }
    .dl-neg[data-i="1"] { transition-delay: 1350ms; }
    .dl-neg[data-i="2"] { transition-delay: 2000ms; }

    /* Las tres negaciones quedan en tinta apagada y la afirmación final es
       la única a plena tinta y con acento: la jerarquía cuenta la frase
       antes de leerla. El retraso (2500ms) llega justo cuando la tercera
       negación termina de atenuarse (2000+550=2550ms) — la conclusión
       aparece cuando las dudas ya están descartadas, no antes. Un ligero
       scale(0.98→1) además del cambio de tinta, para que la frase
       "llegue" en vez de solo cambiar de color en el sitio. */
    .dl-line-on {
      display: inline-block;
      color: var(--text-muted);
      margin-top: 20px;
      text-decoration: underline;
      text-decoration-color: transparent;
      text-decoration-thickness: 3px;
      text-underline-offset: 10px;
      transform: scale(0.98);
      transition: color 750ms var(--ease), text-decoration-color 750ms var(--ease), transform 750ms var(--ease);
      transition-delay: 2500ms;
    }
    .dl-live .dl-line-on {
      color: var(--text-primary);
      text-decoration-color: var(--accent);
      transform: scale(1);
    }
    /* Sin JS (o si el IntersectionObserver no llega a disparar): la
       sección se queda en su estado "antes" — líneas apagadas sin tachar,
       final sin iluminar. No es el efecto completo, pero es una UI
       legible y terminada, no algo a medias. */

    @media (prefers-reduced-motion: reduce) {
      .dl-strike path { transition: none; stroke-dashoffset: 0; }
      .dl-neg { transition: none; opacity: 0.45; }
      .dl-line-on { transition: none; color: var(--text-primary); text-decoration-color: var(--accent); transform: scale(1); }
      .dl-bloom { transition: none; opacity: 1; transform: translate(-50%, -55%) scale(1); animation: none; }
    }

    /* ---- 11.4 #entregable — qué te llevas ---- */
    #entregable {
      background-color: var(--bg-base);   /* L0 */
      padding: 120px 0;
    }
    /* Mismo criterio que .dg-areas: texto sobre fondo, no tarjetas L2. Eran
       tres cajas idénticas a las de las fases, así que el entregable pesaba
       igual que el proceso entero. Se separan con un hairline superior y el
       icono queda como glifo suelto. */
    .qt-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 0 40px;
    }
    .qt-item {
      border-top: 1px solid var(--border);
      padding: 26px 0 0;
    }
    .qt-icon {
      color: var(--accent);
      font-size: 22px;
      line-height: 1;
      margin-bottom: 18px;
    }
    .qt-title {
      font-family: var(--ff-display);
      font-size: 19px;
      font-weight: 700;
      color: var(--text-primary);
      margin: 0 0 10px;
    }
    .qt-text {
      font-family: var(--ff-body);
      font-size: 15px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 0;
    }
    .qt-remate {
      max-width: 780px;
      margin: 52px auto 0;
      text-align: center;
      font-family: var(--ff-display);
      font-size: clamp(20px, 2.6vw, 28px);
      font-weight: 700;
      line-height: 1.4;
      color: var(--text-primary);
    }

    /* ---- 11.5 #implementacion ---- */
    /* Breve a propósito: es la Oferta 2, y todo lo que viene después en la
       página (#automations, #clientes) ya es prueba de ejecución. */
    #implementacion {
      background-color: var(--bg-alt);    /* L1 */
      padding: 120px 0;
    }
    .ip-layout {
      display: grid;
      grid-template-columns: 1.15fr 1fr;
      gap: 64px;
      align-items: start;
    }
    .ip-text {
      font-family: var(--ff-body);
      font-size: 17px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 20px 0 30px;
      max-width: 54ch;
    }
    .ip-points { list-style: none; display: flex; flex-direction: column; gap: 14px; }
    .ip-point {
      display: flex;
      align-items: center;
      gap: 14px;
      font-family: var(--ff-body);
      font-size: 15px;
      color: var(--text-primary);
    }
    .ip-point-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--accent);
      flex-shrink: 0;
    }

    .ip-bonus {
      background: var(--bg-surface);     /* L2 */
      border: 1px solid var(--border-strong);
      border-radius: 20px;
      padding: 34px 32px;
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.06);
    }
    .ip-bonus-title {
      font-family: var(--ff-display);
      font-size: 19px;
      font-weight: 700;
      color: var(--text-primary);
      margin: 0 0 14px;
    }
    .ip-bonus-title::before {
      content: '';
      display: block;
      width: 32px;
      height: 2px;
      margin-bottom: 16px;
      background: var(--accent);
    }
    .ip-bonus-text {
      font-family: var(--ff-body);
      font-size: 15px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 0;
    }
    .ip-bonus-foot {
      margin: 16px 0 0;
      padding-top: 16px;
      border-top: 1px solid var(--border-strong);
      font-family: var(--ff-body);
      font-size: 14px;
      line-height: 1.6;
      color: var(--text-muted);
    }

    /* ---- 11.7 #diag-request — dos caminos: contacto directo o formulario ----
       Última sección de diagnostico.html antes del footer, 2026-08-09. Dos
       caminos, no uno: a la izquierda un enlace de texto al #contact
       genérico de index.html (para quien prefiere hablar directo), a la
       derecha un formulario propio que pide justo lo que un diagnóstico
       necesita (áreas a auditar + resumen), no un mensaje libre.

       #diagForm postea al MISMO webhook que #ctForm (ver "FORMS" en
       script.js) — el workflow de n8n vive en otro repo con columnas fijas
       (name/email/company/phone/message), así que las áreas marcadas y el
       resumen se combinan dentro de `message` en vez de pedir un campo
       nuevo en ese workflow. */
    #diag-request {
      background-color: var(--bg-base);  /* L0, sigue a #implementacion (L1) */
      padding: 120px 0;
    }
    .dr-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 80px;
      align-items: start;
    }
    .dr-left h2 { font-size: clamp(30px, 4vw, 44px); margin-bottom: 18px; }
    .dr-left p {
      font-family: var(--ff-body);
      font-size: 16px;
      color: var(--text-secondary);
      line-height: 1.7;
      margin-bottom: 30px;
      max-width: 42ch;
    }

    /* Mismo dark-glass que .ct-form/.dc-card — clases propias (.dr-*) porque
       vive en otra página, pero cero paleta nueva. */
    .dr-form-wrap { position: relative; }
    .dr-form-glow {
      position: absolute;
      inset: -70px;
      z-index: 0;
      pointer-events: none;
      overflow: hidden;
      border-radius: 40px;
      background:
        radial-gradient(closest-side, rgba(120, 96, 200, 0.28), transparent 70%) 12% 18%/60% 60% no-repeat,
        radial-gradient(closest-side, rgba(70, 190, 180, 0.26), transparent 70%) 88% 82%/62% 62% no-repeat;
      filter: blur(60px);
    }
    .dr-form {
      position: relative;
      z-index: 1;
      background: rgba(245, 244, 240, 0.055);
      backdrop-filter: blur(24px) saturate(160%);
      -webkit-backdrop-filter: blur(24px) saturate(160%);
      border: 1px solid rgba(245, 244, 240, 0.10);
      border-radius: 28px;
      padding: 44px;
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.14);
    }
    @supports not (backdrop-filter: blur(1px)) {
      .dr-form { background: var(--bg-surface); }
    }
    .dr-form-title {
      font-family: var(--ff-display);
      font-size: 21px;
      font-weight: 700;
      color: var(--text-primary);
      margin: 0 0 24px;
    }

    /* Áreas a auditar — checkboxes reales (accesibles, sin JS) ocultos
       visualmente detrás de una píldora; :checked + label hace el resto.
       Mismos 4 nombres que .dg-areas más arriba en la página. */
    .dr-areas { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
    .dr-area-input {
      position: absolute;
      width: 1px; height: 1px;
      opacity: 0;
      pointer-events: none;
    }
    .dr-area-pill {
      display: inline-flex;
      align-items: center;
      padding: 9px 16px;
      border-radius: 999px;
      background: rgba(10, 10, 10, 0.34);
      border: 1px solid rgba(245, 244, 240, 0.14);
      font-family: var(--ff-body);
      font-size: 13.5px;
      color: var(--text-secondary);
      cursor: pointer;
      transition: background var(--t), border-color var(--t), color var(--t);
    }
    .dr-area-input:checked + .dr-area-pill {
      background: var(--accent-dim);
      border-color: rgba(142, 255, 215, 0.4);
      color: var(--text-primary);
    }
    .dr-area-input:focus-visible + .dr-area-pill {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
    }

    @media (max-width: 900px) {
      .dr-grid { grid-template-columns: 1fr; gap: 48px; }
    }
    @media (max-width: 480px) {
      .dr-form { padding: 30px 24px; }
    }

    /* ---- 11.6 #diag-cta — mini-sección de entrada al diagnóstico ----
       Vive en index.html, entre el hero y #automations: es la conversión
       primaria del sitio, así que se diseña como un solo bloque-enlace, no
       un CTA perdido dentro de más texto. MISMA receta que .ct-form /
       .ct-form-glow (glass card blanco translúcido + halo apagado
       morado/teal detrás, .grain para el dithering) en vez de una paleta
       nueva — así queda coherente con el resto del sitio en lugar de una
       aurora multicolor suelta.

       L1 (`--bg-alt`), fijo: el hero (L0) queda antes y #automations (L0)
       después, así que esta banda cierra la alternancia hero→L1→automations
       en vez del hairline-menos que había con las dos secciones pegadas. */
    #diag-cta {
      background-color: var(--bg-alt);
      padding: 96px 0;
    }
    .dc-wrap { position: relative; max-width: 760px; margin: 0 auto; }
    .dc-glow {
      position: absolute;
      inset: -70px;
      z-index: 0;
      pointer-events: none;
      overflow: hidden;
      border-radius: 44px;
      background:
        radial-gradient(closest-side, rgba(120, 96, 200, 0.30), transparent 70%) 14% 20%/58% 58% no-repeat,
        radial-gradient(closest-side, rgba(70, 190, 180, 0.26), transparent 70%) 86% 78%/62% 62% no-repeat;
      filter: blur(60px);
    }
    /* Tarjeta-enlace entera: el objetivo es "entrar directamente", así que
       no hay un botón aparte al final — toda la superficie es clicable.
       Mismo DARK GLASS que .ct-form: tinte blanco bajo + borde ~2x ese
       tinte + realce interior solo en el borde superior, cero drop-shadow
       (no dibuja nada sobre negro). */
    .dc-card {
      display: block;
      position: relative;
      z-index: 1;
      background: rgba(245, 244, 240, 0.055);
      backdrop-filter: blur(24px) saturate(160%);
      -webkit-backdrop-filter: blur(24px) saturate(160%);
      border: 1px solid rgba(245, 244, 240, 0.10);
      border-radius: 28px;
      padding: 48px 44px;
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.14);
      text-decoration: none;
      transition: transform var(--t), border-color var(--t);
    }
    .dc-card:hover { transform: translateY(-4px); border-color: rgba(142, 255, 215, 0.32); }
    @supports not (backdrop-filter: blur(1px)) {
      .dc-card { background: var(--bg-surface); }
    }
    .dc-top {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 20px;
      margin-bottom: 20px;
    }
    .dc-eyebrow {
      font-family: var(--ff-body);
      font-size: 12px;
      font-weight: 600;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--text-secondary);
    }
    /* El acento aparece SOLO aquí (chip) y en el CTA de texto — dos usos,
       no dos colores: es el mismo mint marcando "esto es interactivo". */
    .dc-icon {
      flex-shrink: 0;
      width: 38px;
      height: 38px;
      border-radius: 50%;
      background: var(--accent-dim);
      border: 1px solid rgba(142, 255, 215, 0.35);
      color: var(--accent);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 16px;
      transition: transform var(--t);
    }
    .dc-card:hover .dc-icon { transform: translate(3px, -3px); }
    .dc-title {
      font-family: var(--ff-display);
      font-size: clamp(26px, 3.4vw, 38px);
      font-weight: 700;
      color: var(--text-primary);
      margin: 0 0 14px;
    }
    .dc-text {
      font-family: var(--ff-body);
      font-size: 15.5px;
      line-height: 1.7;
      color: var(--text-secondary);
      margin: 0 0 30px;
      max-width: 54ch;
    }
    .dc-cta {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-family: var(--ff-display);
      font-weight: 700;
      font-size: 15px;
      color: var(--accent);
    }
    .dc-cta i { font-size: 14px; transition: transform var(--t); }
    .dc-card:hover .dc-cta i { transform: translateX(4px); }

    @media (max-width: 480px) {
      #diag-cta { padding: 72px 0; }
      .dc-glow { inset: -48px; border-radius: 34px; }
      .dc-card { padding: 34px 26px; }
      .dc-text { max-width: none; }
    }

    /* ============================================================
       12. AUTOMATIONS
    ============================================================ */
    #automations {
      background-color: var(--bg-base);
      padding: 96px 0;
    }

    .sec-head { margin-bottom: 36px; }
    .sec-h2 { font-size: clamp(36px, 5vw, 60px); }

    .stack-wrap {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 24px;
    }

    /* Height trimmed from 630 -> 560 to bring the "Ver cómo funciona" CTA
       (right below .stack-dots) further up the section, so it's visible
       without scrolling almost the whole section first — reported by the
       client. Safe to shrink: .stack-card is absolutely positioned and
       centered via .stack-stage's flex, with overflow left visible, so this
       is pure layout space, not a clip box — reducing it doesn't resize or
       cut the coverflow cards (still 380x530 at full scale), it only tightens
       the reserved whitespace above/below them. Kept 15px above the active
       card's own 530px height on each side as a buffer, so the card's visual
       overflow past this box never eats into the .stack-wrap gap below it. */
    .stack {
      position: relative;
      width: 100%;
      max-width: 760px;
      height: 560px;
      outline: none;
    }
    .stack:focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 4px;
      border-radius: 8px;
    }

    .stack-stage {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      perspective: 1400px;
    }

    .stack-card {
      position: absolute;
      width: 380px;
      height: 530px;
      border-radius: 24px;
      overflow: hidden;
      border: 1px solid var(--border-strong);
      background: var(--bg-surface);
      cursor: pointer;
      will-change: transform, opacity;
      touch-action: pan-y;
      /* The four stacked rgba(10,10,10,…) shadows that used to live here are
         gone: a black shadow on a black page draws nothing. On dark, elevation
         is the surface step (L0 section -> L2 card) plus a hairline, and a
         1px inset top highlight to catch the 'lit from above' read a shadow
         used to give for free. */
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.06);
      /* Coverflow position/opacity/blur crossfade — ~650ms, site's standard ease. */
      transition: transform 650ms var(--ease), opacity 650ms var(--ease), filter 650ms var(--ease);
    }
    .stack-card.is-active { cursor: grab; z-index: 40; }
    .stack-card.is-active:active { cursor: grabbing; }

    /* ---- automation card content ---- */
    .auto-card {
      height: 100%;
      width: 100%;
      box-sizing: border-box;
      padding: 32px;
      display: flex;
      flex-direction: column;
      pointer-events: none;
      /* 3D tilt-follow-cursor + hover-lift live here (script.js), separate from
         .stack-card's own coverflow position transform so the two never fight
         over the same transition. Shorter/snappier than the coverflow's 650ms. */
      transition: transform 260ms var(--ease);
      transform-style: preserve-3d;
      will-change: transform;
    }

    /* Both cards now resolve to the SAME single site accent. They used to
       carry a hue each (acc-red #A8342F / acc-blue #185FA5, with pale
       #FBEAE9/#E6F1FB chip fills) which made the carousel a three-accent
       component on a one-accent site — and those pale chips would have become
       two bright patches on a black card. The cards are told apart by their
       icon and their copy, not by colour. The per-card custom properties are
       kept rather than inlined so a future card can still opt into its own
       hue in one line if that ever becomes a deliberate decision again. */
    .auto-card.acc-red,
    .auto-card.acc-blue   { --card-accent: var(--accent); --card-accent-soft: var(--accent-dim); --card-dot: var(--accent); }

    .auto-status { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
    .auto-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--card-dot); flex-shrink: 0; }
    .auto-status-label { font-family: var(--ff-body); font-size: 13px; color: var(--text-secondary); }

    .auto-title {
      font-family: var(--ff-display);
      font-size: 22px;
      font-weight: 700;
      line-height: 1.3;
      letter-spacing: -0.01em;
      color: var(--text-primary);
      margin: 0 0 8px;
    }
    .auto-title .accent { color: var(--card-accent); }

    .auto-desc {
      font-family: var(--ff-body);
      font-size: 14px;
      color: var(--text-secondary);
      line-height: 1.6;
      margin: 0 0 20px;
    }

    .auto-box {
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: center;
      background: var(--bg-raised);
      border-radius: 12px;
      padding: 20px;
      margin-bottom: 16px;
    }
    .auto-box-chart { padding: 20px 16px; }

    .auto-row { display: flex; align-items: center; gap: 10px; }
    .auto-row + .auto-row { margin-top: 12px; }
    .auto-icon {
      width: 32px; height: 32px;
      border-radius: 8px;
      background: var(--card-accent-soft);
      color: var(--card-accent);
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      font-size: 16px;
    }
    .auto-row-title { margin: 0; font-family: var(--ff-body); font-size: 13px; font-weight: 500; color: var(--text-primary); }
    .auto-row-sub   { margin: 2px 0 0; font-family: var(--ff-body); font-size: 12px; color: var(--text-muted); }

    .auto-stats { display: flex; gap: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
    /* La tarjeta del agente se quedó con un solo stat (el "0 / huecos vacíos
       tras cancelar" era una cifra de resultado sin dato real detrás). Un
       único bloque apilado dejaba media banda vacía al pie, así que el stat
       solitario se pone en línea: valor y etiqueta comparten renglón y la
       franja vuelve a ocupar el ancho de la tarjeta. */
    .auto-stats-solo > div { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
    .auto-stats-solo .auto-stat-label { margin: 0; }
    .auto-stat-value { margin: 0; font-family: var(--ff-display); font-size: 18px; font-weight: 700; color: var(--text-primary); }
    .auto-stat-label { margin: 2px 0 0; font-family: var(--ff-body); font-size: 12px; color: var(--text-muted); }

    .stack-dots { display: flex; align-items: center; justify-content: center; gap: 10px; }
    .stack-dot {
      width: 8px; height: 8px;
      border-radius: 50%;
      background: var(--bg-raised);
      border: none;
      padding: 0;
      cursor: pointer;
      transition: background 0.25s, transform 0.25s;
    }
    .stack-dot:hover { background: var(--text-muted); }
    .stack-dot.is-active { background: var(--accent); transform: scale(1.3); }

    .stack-arrow {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      width: 48px; height: 48px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: var(--bg-surface);
      border: 1px solid var(--border-strong);
      color: var(--text-primary);
      cursor: pointer;
      box-shadow: none;
      transition: background-color 0.2s var(--ease), border-color 0.2s var(--ease), color 0.2s var(--ease), transform 0.2s var(--ease);
      z-index: 50;
    }
    .stack-arrow:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); transform: translateY(-50%) translateY(-2px); }
    .stack-arrow:active { transform: translateY(-50%) translateY(0); }
    .stack-arrow-prev { left: -8px; }
    .stack-arrow-next { right: -8px; }

    /* ============================================================
       13B. FAQ — liquid-glass accordion
       Replaced the earlier client-quote section outright (client asked
       for a FAQ accordion here instead, adapted from a reference site's
       "Dudas Comunes" section — content/copy rewritten for gumma, not
       copied verbatim). A second reference (a frosted-glass phone-menu
       screenshot, exact design tokens supplied) asked for a "liquid
       glass" panel. An initial pass put this section on a dark
       (`var(--accent)`) stage so the glow blobs had contrast to pop
       against — reverted per client feedback that the black background
       broke the page's consistent crema look. Now sits on the site's
       normal `var(--bg-primary)` like every other section, reusing the
       *light* glass recipe already established for `.ct-form`/
       `.ct-form-glow` (CONTACT, further up this file) — white-tinted
       translucent panel + a muted monochrome/blush glow, not the
       saturated amber/teal/blue blobs the dark version used. One
       question open at a time, driven by the IIFE in script.js under
       "FAQ — ACCORDION".
    ============================================================ */
    #faq {
      background-color: var(--bg-alt);
      padding: 120px 0;
      overflow: clip;
    }

    .faq-head { max-width: 640px; margin: 0 auto 56px; text-align: center; }
    /* This used to be deliberately NOT a .tr-title: the ink reveal scrubbed a
       pale ghost to near-black, which only read on a light section, and #faq
       was dark at the time. Both halves of that exception are gone — the reveal
       now scrubs dark ghost -> light ink, and every section is dark — so
       .faq-title carries .tr-title like every other section heading and the
       special case is retired. */
    .faq-title {
      font-family: var(--ff-display);
      font-size: clamp(32px, 4.4vw, 52px);
      font-weight: 700;
      letter-spacing: -0.01em;
      color: var(--text-primary);
      margin: 0;
    }
    .faq-head-text {
      font-family: var(--ff-body);
      font-size: 17px;
      color: var(--text-secondary);
      line-height: 1.6;
      margin: 16px 0 0;
    }

    .faq-stage { position: relative; max-width: 560px; margin: 0 auto; }

    /* HALO PALETTE (shared with .ct-form-glow, .demo-glow, .demo-cta-zone::before
       and #contact's own background). The blob that used to lead these was the
       OLD brand coral. Left in place it read as a leftover of a colour that no
       longer appears anywhere else, and being red it is the complement of the
       mint accent — maximum tension directly behind an accent-coloured icon.
       Rearmonising the whole halo INTO the mint family was tried and rejected by
       screenshot: the accent then sits on a field of its own hue and stops
       standing out, which defeats the point of putting colour behind glass.
       Violet/teal/blue keeps the glass refracting something, drops the stale
       red, and leaves the mint the most distinct thing in the block. */
    .faq-glow {
      position: absolute;
      inset: -90px;
      z-index: 0;
      pointer-events: none;
      overflow: hidden;
      background:
        radial-gradient(closest-side, rgba(120, 96, 200, 0.30), transparent 70%) 10% 15%/60% 60% no-repeat,
        radial-gradient(closest-side, rgba(70, 190, 180, 0.26), transparent 70%) 60% 40%/68% 68% no-repeat,
        radial-gradient(closest-side, rgba(60, 150, 190, 0.26), transparent 70%) 92% 80%/60% 60% no-repeat;
      filter: blur(70px);
      animation: faq-glow-drift 12s ease-in-out infinite alternate;
      animation-play-state: paused; /* only runs while .faq-stage is on screen — see IntersectionObserver in script.js */
    }
    .faq-stage.faq-anim-live .faq-glow { animation-play-state: running; }
    @keyframes faq-glow-drift {
      0%   { transform: translate(0, 0) scale(1); }
      100% { transform: translate(-3%, 4%) scale(1.08); }
    }

    .faq-list {
      position: relative;
      z-index: 1;
      display: flex;
      flex-direction: column;
      gap: 4px;
      max-width: 560px;
      margin: 0 auto;
      padding: 24px 20px;
      border-radius: 36px;
      /* DARK GLASS. See the same recipe on .demo-panel for the full rationale:
         a low white TINT (not a near-white fill), a border at ~2x the tint, an
         inset highlight on the TOP EDGE ONLY, and no drop shadow — a black
         shadow on a black page draws nothing. The old values here
         (rgba(255,255,255,0.42) over crema) rendered as a milky slab. */
      background: rgba(245, 244, 240, 0.055);
      backdrop-filter: blur(24px) saturate(160%);
      -webkit-backdrop-filter: blur(24px) saturate(160%);
      border: 1px solid rgba(245, 244, 240, 0.10);
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.14);
    }
    /* Frosted glass needs backdrop-filter support to mean anything — without it
       the panel is a barely-there tint. Falls back to a solid L2 card. */
    @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
      .faq-list { background: var(--bg-surface); }
    }

    .faq-q {
      width: 100%;
      display: flex;
      align-items: center;
      gap: 14px;
      padding: 14px 14px;
      background: none;
      border: none;
      border-radius: 24px;
      cursor: pointer;
      text-align: left;
      font-family: var(--ff-body), var(--ff-display);
      font-size: 18px;
      font-weight: 500;
      color: var(--text-primary);
      transition: background 0.25s var(--ease), border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
    }
    .faq-item:not(.is-open) .faq-q:hover { background: rgba(245, 244, 240, 0.05); }
    /* The open question is the one pill-highlighted row. The accent lands on
       its ICON (see .faq-item.is-open .faq-q-icon-lead below), not on the row:
       an accent fill this wide would swamp the section, and a 2px left border
       on a 24px-radius pill renders as a curved coral sliver rather than a
       rule — tried, screenshotted, rejected. */
    .faq-item.is-open .faq-q {
      background: rgba(245, 244, 240, 0.08);
      border: 1px solid rgba(245, 244, 240, 0.12);
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.10);
      padding: 13px 13px; /* -1px to offset the 1px border so the row doesn't grow */
    }
    .faq-q:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    .faq-q-icon-lead {
      display: flex;
      flex-shrink: 0;
      width: 22px; height: 22px;
      font-size: 22px;
      line-height: 1;
      color: var(--text-muted);
      transition: color 0.25s var(--ease);
    }
    .faq-item.is-open .faq-q-icon-lead { color: var(--accent); }
    .faq-q-text { flex: 1; }
    .faq-q-chevron { flex-shrink: 0; transition: transform 0.3s var(--ease); }
    .faq-item.is-open .faq-q-chevron { transform: rotate(180deg); }

    .faq-a-wrap {
      display: grid;
      grid-template-rows: 0fr;
      transition: grid-template-rows 320ms cubic-bezier(0.32, 0.72, 0, 1);
    }
    .faq-item.is-open .faq-a-wrap { grid-template-rows: 1fr; }
    .faq-a-inner { overflow: hidden; }
    .faq-a {
      font-family: var(--ff-body);
      font-size: 15px;
      font-weight: 400;
      color: var(--text-secondary);
      line-height: 1.6;
      margin: 10px 14px 16px;
      max-width: 62ch;
      opacity: 0;
      transition: opacity 200ms var(--ease);
    }
    .faq-item.is-open .faq-a { opacity: 1; transition-delay: 60ms; }

    @media (prefers-reduced-motion: reduce) {
      .faq-glow { animation: none; }
      .faq-a-wrap, .faq-a, .faq-q-chevron, .faq-q { transition: none; }
    }

    /* ============================================================
       14B. CLIENTES — row-reveal portfolio ("Empresas que ya confían")
       Ported from a standalone deliverable (thecross/index.html) into the
       gumma site per request, adapted to gumma's crema/black monochrome
       system + Space Grotesk (the standalone used a white + vermilion
       editorial look; here the accent underline/arrow are gumma's near-black
       --accent, no color). Row-reveal mechanic: a row lights up on hover/
       focus — ghost giant name → solid black, crema active bg, black
       underline + arrow, filled number, demo-video slot. First row active by
       default; click opens an in-page case-study detail (no routing). All
       classes are cl-* prefixed to avoid colliding with the automations
       section's own .stack/.row-ish names. JS in script.js under "CLIENTES —
       ROW REVEAL". Below 900px it becomes a stacked-card layout, every row
       permanently active, no hover. The section is white between the crema
       #faq and #contact, so the reveal reads as white→crema.
    ============================================================ */
    #clientes {
      background-color: var(--bg-base);
      padding: 120px 0;
    }
    /* Own (wider) container than the site's 1200px .wrap — the full-width
       rows need the extra room so the giant name fits beside the desc + video. */
    .cl-inner { max-width: 1320px; margin: 0 auto; padding: 0 40px; }

    .cl-head { margin-bottom: 8px; }
    .cl-title {
      font-family: var(--ff-display);
      font-size: clamp(30px, 4vw, 48px);
      font-weight: 700;
      letter-spacing: -0.02em;
      line-height: 1.08;
      margin: 0;
    }
    .cl-head-text {
      font-family: var(--ff-body);
      font-size: 17px;
      color: var(--text-secondary);
      margin: 14px 0 0;
    }

    .cl-rows {
      margin-top: 48px;
      border-bottom: 1px solid var(--border);
      transition: opacity 0.3s var(--ease);
    }
    .cl-rows.is-fading { opacity: 0; }

    .cl-row {
      appearance: none; -webkit-appearance: none;
      font: inherit; color: inherit; text-align: left; cursor: pointer;
      width: 100%;
      position: relative; /* anchors the coral outline SVG + count-up metric */
      border: none;
      border-top: 1px solid var(--border);
      background: var(--bg-base);
      display: flex; align-items: center; gap: 32px;
      height: 340px;
      padding: 0 8px;
      transition: background 0.4s var(--ease);
    }
    .cl-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    .cl-num {
      flex: 0 0 auto;
      width: 44px; height: 44px;
      border: 1px solid var(--border);
      border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-family: var(--ff-body);
      font-size: 14px;
      color: var(--text-muted);
      transition: background 0.4s var(--ease), color 0.4s var(--ease), border-color 0.4s var(--ease);
    }

    .cl-desc {
      flex: 0 0 340px; width: 340px;
      font-family: var(--ff-body);
      font-size: 15px; line-height: 1.6;
      color: var(--text-muted);
      transition: color 0.4s var(--ease);
    }

    .cl-name { flex: 1 1 auto; min-width: 0; }
    .cl-name-inner {
      position: relative; display: inline-block;
      font-family: var(--ff-display);
      font-size: clamp(52px, 5.2vw, 100px);
      font-weight: 400;
      letter-spacing: -0.03em;
      line-height: 1;
      color: #3A3A38; /* ghost — was #D6D2CA (a light warm grey for the white rows). Sits just above L0 so an un-hovered name reads as un-inked rather than absent. */
      white-space: nowrap;
      transition: color 0.4s var(--ease);
    }
    /* A client with a real logo (currently only The Cross Experience) renders
       an <img> here instead of text — the same "ghost → solid" reveal becomes
       a low-opacity → full-opacity fade on the logo image, since `color`
       doesn't apply to a raster image. Height is a touch smaller than the
       text clamp: THECRXSS's wordmark is wide/short (~831×200), so matching
       the text clamp's value directly for height would read visually larger
       than the giant-name rows it sits alongside. */
    /* This is now the WHITE logo crop, not the black one — the rows are dark in
       both states, so the black mono wordmark would simply be invisible. 0.28
       rather than the old 0.22: a white mark at 0.22 on L0 is dimmer than a
       black mark at 0.22 was on white, so the resting ghost needed lifting to
       stay legible as a ghost. */
    .cl-name-logo {
      display: block;
      height: clamp(40px, 4.2vw, 78px);
      width: auto;
      opacity: 0.28;
      transition: opacity 0.4s var(--ease);
    }
    .cl-row.is-active .cl-name-logo { opacity: 1; }

    /* The mono→colour CROSSFADE is retired on the dark system, and this is the
       one piece of #clientes that genuinely lost a state rather than changing
       colour. It worked because the two ends were a black mark on a white row
       and a white mark on a black row. Both rows are dark now, so there is no
       second background to cross into — the white crop simply fades 0.28 -> 1.
       The rules and the .has-logo-dual hook are kept (not deleted) so a client
       who supplies a genuinely different active artwork can still opt in; with
       only one image supplied, .cl-name-logo-on is never rendered. */
    .cl-name-logo-on {
      position: absolute; left: 0; top: 0;
      height: 100%;
      opacity: 0;
    }
    .cl-row.is-active .has-logo-dual .cl-name-logo { opacity: 0; }
    .cl-row.is-active .has-logo-dual .cl-name-logo-on { opacity: 1; }
    .cl-underline {
      position: absolute; left: 0; bottom: -0.12em;
      width: 100%; height: 2px;
      background: var(--accent);
      transform: scaleX(0); transform-origin: left;
      transition: transform 0.4s var(--ease) 0.1s, background 0.4s var(--ease);
    }

    /* demo video slot — reserved so nothing reflows when a row activates */
    .cl-video {
      flex: 0 0 248px; width: 248px;
      aspect-ratio: 16 / 9;
      border: 1px solid var(--border);
      border-radius: 8px;
      background: var(--bg-alt);
      display: flex; align-items: center; justify-content: center;
      opacity: 0; transform: translateY(12px);
      transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
    }
    .cl-play {
      width: 0; height: 0;
      border-style: solid; border-width: 8px 0 8px 13px;
      border-color: transparent transparent transparent var(--text-muted);
    }

    .cl-arrow {
      flex: 0 0 auto;
      font-family: var(--ff-display);
      font-size: 72px; line-height: 1;
      color: var(--accent);
      opacity: 0; transform: translateX(40px);
      transition: opacity 0.4s var(--ease) 0.1s, transform 0.4s var(--ease) 0.1s;
    }

    /* Active state — JS-driven (.is-active), not :hover, so keyboard focus
       reveals a row identically. That mechanism is unchanged; the DIRECTION is
       inverted.

       It used to run light row -> near-black row, because the section was white
       and The Cross's colour logo has a pure-white wordmark that needed a dark
       backing. Now the rows rest at L0 and LIFT to L2 when active: the reveal
       is an elevation change, and the accent — underline, arrow, number — is
       what actually says "this one". A jump to a light surface was the other
       option considered and rejected: a full-bleed crema row flashing on hover
       inside a black page reads as a flashbang, and it would have been the only
       light surface left on the site.

       Consequence for the logos: the white variant is now the ONLY one used in
       a row (see .cl-name-logo), because both states are dark. The black mono
       crop stays in the CASOS data as the light-surface variant — see the note
       on `logoMono` in script.js. */
    .cl-row.is-active { background: var(--bg-surface); }
    .cl-row.is-active .cl-name-inner { color: var(--text-primary); }
    .cl-row.is-active .cl-underline { background: var(--accent); transform: scaleX(1); }
    .cl-row.is-active .cl-desc { color: var(--text-secondary); }
    .cl-row.is-active .cl-num { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
    .cl-row.is-active .cl-video {
      opacity: 1; transform: translateY(0);
      background: rgba(245, 244, 240, 0.05);
      border-color: rgba(245, 244, 240, 0.14);
    }
    .cl-row.is-active .cl-play { border-left-color: var(--text-secondary); }
    .cl-row.is-active .cl-arrow { color: var(--accent); opacity: 1; transform: translateX(0); }

    /* ---- hover/in-view flourish: coral outline that draws itself + count-up
       metric (JS toggles .cl-fx-on; see CLIENTES IIFE, gated on a caso's
       hoverMetric). The outline is a pathLength-normalised SVG rect so the
       draw is size-agnostic (works at any row width, no JS re-measure); the
       stroke colour is set inline per client, and now comes from the site
       accent (--accent) rather than the standalone #E8332E it used to hardcode.
       overflow:visible lets the 2px stroke sit centred on the row edge. ---- */
    .cl-outline {
      position: absolute; inset: 0;
      width: 100%; height: 100%;
      overflow: visible; pointer-events: none;
      z-index: 3;
    }
    .cl-outline rect {
      fill: none;
      stroke-width: 2;
      stroke-dasharray: 100;
      stroke-dashoffset: 100; /* fully hidden at rest */
      transition: stroke-dashoffset 0.9s var(--ease);
    }
    .cl-row.cl-fx-on .cl-outline rect { stroke-dashoffset: 0; } /* drawn */

    .cl-metric-hover {
      position: absolute; right: 36px; bottom: 30px; z-index: 2;
      display: flex; flex-direction: column; align-items: flex-end;
      text-align: right; pointer-events: none;
      opacity: 0; transform: translateY(12px) scale(0.94);
      transition: opacity 0.45s var(--ease) 0.1s, transform 0.45s var(--ease) 0.1s;
    }
    .cl-row.cl-fx-on .cl-metric-hover { opacity: 1; transform: none; }
    .cl-metric-hover-num {
      font-family: var(--ff-display);
      font-size: clamp(44px, 4.4vw, 78px);
      font-weight: 700; line-height: 1; letter-spacing: -0.02em;
      /* color set inline (brand coral) */
    }
    .cl-metric-hover-label {
      margin-top: 8px;
      font-family: var(--ff-body);
      font-size: 13px; line-height: 1.4;
      color: rgba(255, 255, 255, 0.7);
      max-width: 220px;
    }

    /* ---- case detail ---- */
    .cl-detail[hidden] { display: none; }
    .cl-detail { animation: cl-detail-in 0.4s var(--ease); }
    @keyframes cl-detail-in {
      from { opacity: 0; transform: translateY(12px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    .cl-detail-inner { max-width: 720px; margin: 0 auto; }

    .cl-volver {
      appearance: none; -webkit-appearance: none;
      font-family: var(--ff-body); font-size: 15px;
      color: var(--text-muted); background: none; border: none; cursor: pointer;
      padding: 0; margin-bottom: 48px;
      transition: color 0.4s var(--ease);
    }
    .cl-volver:hover { color: var(--text-primary); }
    .cl-volver:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    .cl-detail-name {
      font-family: var(--ff-display);
      font-size: clamp(44px, 6vw, 80px);
      font-weight: 400; letter-spacing: -0.02em; line-height: 1.04;
      margin: 0;
    }
    /* The WHITE crop, same as the rows. This used to be the black mono logo
       because the detail page was light; it is L0 now, so the black one would
       be invisible. (In practice The Cross renders the hero band instead and
       this path is the fallback for a caso with a logo but no `hero`.) */
    .cl-detail-logo { display: block; height: clamp(38px, 4.6vw, 62px); width: auto; }
    .cl-detail-meta { font-family: var(--ff-body); font-size: 16px; color: var(--text-muted); margin: 16px 0 0; }

    /* ---- case-study hero band (dark parallax header, only for a caso with
       c.hero — see renderHero / setupHero in script.js) ---- */
    .cl-hero {
      position: relative;
      height: clamp(380px, 56vh, 560px);
      border-radius: 24px;
      overflow: hidden;
      background-color: #0a0a0a;
      display: flex; align-items: center; justify-content: center;
    }
    /* Taller than the band + anchored past both edges, so the scroll-parallax
       translate (JS) can slide it without ever exposing a hard edge. Carries
       the photo as a CSS background too — instant first paint, and the fallback
       whenever the WebGL layer is absent (Pixi failed / reduced motion). */
    .cl-hero-media {
      position: absolute; left: 0; right: 0; top: -20%; height: 140%;
      background-position: center 42%; background-size: cover; background-repeat: no-repeat;
      will-change: transform;
    }
    .cl-hero-canvas { position: absolute; inset: 0; }
    .cl-hero-canvas canvas { display: block; width: 100%; height: 100%; pointer-events: none; }
    /* Legibility wash + vignette, fading at the bottom into the section's own
       background so the band melts into the content below instead of ending on
       a hard edge. That destination used to be #FFFFFF; it is L0 now.

       THIS IS THE HIGHEST BANDING RISK ON THE SITE and the reason .grain exists.
       The old ramp ended on white, so it spanned a wide luminance range and
       dithered itself. Ending on #0A0A0A instead makes the whole 380-560px band
       a dark-grey-into-dark-grey gradient — precisely the shape that painted
       visible arcs in the hero's old radial spotlight. Two mitigations, both
       needed: the bottom half is cut from four stops to two (fewer, longer
       ramps band less than several short ones), and .cl-hero-scrim::after lays
       fine noise over the whole band to dither the remaining steps. Do not
       re-add intermediate stops here without re-checking the banding. */
    .cl-hero-scrim {
      position: absolute; inset: 0; pointer-events: none;
      background:
        radial-gradient(115% 78% at 50% 45%, rgba(10,10,10,0) 42%, rgba(10,10,10,0.42) 100%),
        linear-gradient(to bottom, rgba(10,10,10,0.45) 0%, rgba(10,10,10,0.08) 34%, rgba(10,10,10,0.62) 82%, #0A0A0A 100%);
    }
    /* The dither layer. Slightly stronger than the base .grain opacity because
       this gradient is the largest uninterrupted dark ramp in the page. */
    .cl-hero-scrim::after {
      content: '';
      position: absolute; inset: 0; pointer-events: none;
      opacity: 0.07;
      mix-blend-mode: screen;   /* see the blend-mode note on the shared rule above */
      background-image: var(--grain-url);
    }
    .cl-hero-inner { position: relative; z-index: 2; text-align: center; padding: 0 24px; }
    .cl-hero-logo {
      display: block; margin: 0 auto; width: auto;
      height: clamp(48px, 6.6vw, 104px);
      filter: drop-shadow(0 6px 30px rgba(0,0,0,0.45));
    }
    .cl-hero-name {
      font-family: var(--ff-display); font-weight: 700;
      font-size: clamp(40px, 6vw, 84px); letter-spacing: -0.02em; line-height: 1.02;
      color: var(--text-primary); margin: 0;
    }
    .cl-hero-tagline {
      font-family: var(--ff-body); font-size: clamp(14px, 1.5vw, 17px);
      color: rgba(255,255,255,0.74); margin: 22px auto 0; max-width: 40ch;
    }
    .cl-volver-hero {
      position: absolute; top: 22px; left: 24px; z-index: 3;
      margin: 0; color: rgba(255,255,255,0.82);
    }
    .cl-volver-hero:hover { color: var(--text-primary); }
    /* hero already carries its own top padding via the band; the intro column
       just needs a little breathing room under it */
    .cl-detail-inner-hero { margin-top: 44px; }

    .cl-block { margin-top: 60px; }
    .cl-block h3 {
      font-family: var(--ff-body);
      font-size: 13px; font-weight: 500;
      text-transform: uppercase; letter-spacing: 0.08em;
      color: var(--text-muted);
      margin: 0 0 20px;
    }
    .cl-block > p { font-family: var(--ff-body); margin: 0; font-size: 19px; line-height: 1.6; color: var(--text-primary); }

    .cl-metrics {
      display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px;
      border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
      padding: 36px 0;
    }
    .cl-metric-value { font-family: var(--ff-display); font-size: 42px; font-weight: 500; letter-spacing: -0.02em; line-height: 1; color: var(--text-primary); }
    .cl-metric-label { font-family: var(--ff-body); font-size: 14px; color: var(--text-muted); margin-top: 12px; line-height: 1.4; }

    /* ---- 3(+)-stage flow, e.g. "Panel a medida → Supabase → Holded" ----
       (see renderFlujo() in script.js). Thin text arrow between stages,
       same character/weight as .cl-arrow used elsewhere in this section. */
    .cl-flow { display: flex; align-items: flex-start; gap: 22px; }
    .cl-flow-step { flex: 1; min-width: 0; }
    .cl-flow-name {
      font-family: var(--ff-display); font-size: 20px; font-weight: 500;
      letter-spacing: -0.01em; color: var(--text-primary); margin: 0 0 10px;
    }
    .cl-flow-desc { font-family: var(--ff-body); font-size: 15px; line-height: 1.55; color: var(--text-secondary); margin: 0; }
    .cl-flow-arrow {
      flex: none; align-self: center;
      font-family: var(--ff-display); font-size: 20px; color: var(--text-muted);
    }
    @media (max-width: 900px) {
      .cl-flow { flex-direction: column; gap: 28px; }
      .cl-flow-arrow { align-self: flex-start; transform: rotate(90deg); margin: -8px 0 -8px 2px; }
    }

    /* ---- automations showcase (scroll-driven, inside the case detail) ----
       Breaks out of the 720px .cl-detail-inner to a two-column layout: text
       blocks scroll on the left, a sticky video stage on the right crossfades
       between them. JS ("setupShowcase") drives which video is visible via
       IntersectionObserver — this CSS only defines the states it toggles. */
    .cl-show { max-width: 1120px; margin: 150px auto 0; }
    .cl-show-title {
      font-family: var(--ff-body);
      font-size: 13px; font-weight: 500;
      text-transform: uppercase; letter-spacing: 0.08em;
      color: var(--text-muted);
      margin: 0 0 4px;
    }
    .cl-show-grid {
      display: grid;
      grid-template-columns: 45% 55%;
      gap: 40px;
      /* stretch (not start): the media cell must grow to the full height of
         the text column so its sticky stage has scroll travel to stick over.
         With `start` the cell collapsed to the stage height and the sticky had
         nowhere to travel — it just scrolled off the top. */
      align-items: stretch;
    }
    .cl-show-text { position: relative; padding-left: 34px; }

    /* progress rail pinned to the left edge of the text column */
    .cl-show-progress { position: absolute; left: 0; top: 0; bottom: 0; width: 2px; }
    .cl-prog-track { position: absolute; inset: 0; background: var(--border); }
    .cl-prog-fill {
      position: absolute; left: 0; top: 0; width: 2px; height: 100%;
      background: var(--accent);
      transform: scaleY(0); transform-origin: top;
      transition: transform 0.5s var(--ease);
    }
    .cl-prog-dot {
      position: absolute; left: 1px; width: 9px; height: 9px;
      margin-left: -4px; margin-top: -4.5px;
      border-radius: 50%; background: var(--bg-raised);
      border: 1.5px solid var(--border);
      transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
    }
    .cl-prog-dot.is-on { background: var(--accent); border-color: var(--accent); }

    /* each text block is tall enough to give the sticky column scroll travel */
    .cl-case {
      min-height: 90vh;
      display: flex; flex-direction: column; justify-content: center;
      padding: 4vh 0;
    }
    .cl-case-num { font-family: var(--ff-display); font-size: 14px; font-weight: 500; color: var(--text-muted); letter-spacing: 0.04em; }
    .cl-case-title {
      font-family: var(--ff-display); font-size: clamp(26px, 3vw, 34px);
      font-weight: 500; letter-spacing: -0.02em; line-height: 1.1;
      color: var(--text-primary); margin: 14px 0 0;
    }
    .cl-case-desc { font-family: var(--ff-body); font-size: 17px; line-height: 1.6; color: var(--text-secondary); margin: 18px 0 0; max-width: 42ch; }
    .cl-case-metric { margin: 26px 0 0; }
    .cl-case-metric-value { display: block; font-family: var(--ff-display); font-size: 40px; font-weight: 500; letter-spacing: -0.02em; line-height: 1; color: var(--text-primary); }
    .cl-case-metric-label { display: block; font-family: var(--ff-body); font-size: 14px; color: var(--text-muted); margin-top: 8px; }

    /* Entrance stagger. Default state is VISIBLE (so the section reads fine if
       the JS never runs); JS adds `.cl-anim` to opt INTO hide-until-in. */
    .cl-case > * { transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1), transform 0.7s cubic-bezier(0.16,1,0.3,1); }
    .cl-anim .cl-case:not(.is-in) > * { opacity: 0; transform: translateY(20px); }
    .cl-case.is-in > * { opacity: 1; transform: none; }
    .cl-case.is-in > *:nth-child(1) { transition-delay: 0ms; }
    .cl-case.is-in > *:nth-child(2) { transition-delay: 80ms; }
    .cl-case.is-in > *:nth-child(3) { transition-delay: 160ms; }
    .cl-case.is-in > *:nth-child(4) { transition-delay: 240ms; }
    .cl-case.is-in > *:nth-child(5) { transition-delay: 320ms; }

    /* sticky video column — all videos overlaid, only .is-active visible.
       padding-top pushes the stage down so the sticky `translateY(-50%)`
       (which lets it escape above its cell) doesn't ride up over the intro
       metrics numbers when the showcase first scrolls into view. */
    .cl-show-media { min-width: 0; padding-top: 90px; }
    .cl-show-stage {
      position: sticky; top: 50%; transform: translateY(-50%);
      width: 100%; aspect-ratio: 900 / 560;
    }
    .cl-video-wrap {
      position: absolute; inset: 0;
      border: 1px solid var(--border); border-radius: var(--radius);
      overflow: hidden; background: var(--bg-surface);
      /* Bezel. The panel-demos iframes and their poster screenshots stay LIGHT
         on purpose (they are real product UI — see the note in
         .cl-video-frame), so without a frame they read as holes punched in
         the page. The hairline + inset highlight make them read as a screen. */
      border: 1px solid var(--border-strong);
      box-shadow: inset 0 0 0 1px rgba(10, 10, 10, 0.5);
      opacity: 0; transform: translateY(16px);
      transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
    }
    .cl-video-wrap.is-active { opacity: 1; transform: translateY(0); }
    .cl-video-el { display: block; width: 100%; height: 100%; object-fit: cover; }

    /* Live panel-demos/*.html recreation (see renderShowcase/setupShowcase
       in script.js): the poster (real screenshot) sits underneath as the
       always-present fallback; the iframe is a fixed 900×560 canvas scaled
       via an inline JS transform to match the wrap's rendered size, then
       fades in on top once activated. */
    .cl-video-poster { position: absolute; inset: 0; }
    .cl-video-frame {
      position: absolute; top: 0; left: 0; width: 900px; height: 560px;
      border: 0; transform-origin: top left; background: #F3F2EF; /* matches panel-demos' own --bg: those mockups stay light on purpose (real product UI), so this avoids a dark flash before the iframe paints */
      opacity: 0; transition: opacity 0.5s var(--ease);
    }
    .cl-video-frame.is-loaded { opacity: 1; }

    /* Dev-only "Pendiente" ribbon for a demo whose real capture hasn't been
       supplied yet (data marks it `pendiente: true` — see renderShowcase in
       script.js). Hidden by default; only shown when JS detects a local/dev
       environment (adds .is-dev-env to <html>) — never on the live site. */
    .cl-video-pending {
      display: none;
      position: absolute; top: 12px; right: 12px; z-index: 2;
      font-family: var(--ff-body); font-size: 11px; font-weight: 600;
      letter-spacing: 0.06em; text-transform: uppercase;
      color: var(--accent-ink); background: var(--accent);
      padding: 5px 11px; border-radius: 999px;
    }
    .is-dev-env .cl-video-pending { display: inline-block; }

    .cl-detail-foot { margin-top: 80px; }

    /* <1000px: single column, videos moved into each block by JS, no sticky */
    @media (max-width: 999px) {
      .cl-show { margin-top: 48px; }
      .cl-show-grid { grid-template-columns: 1fr; gap: 0; }
      .cl-show-text { padding-left: 0; }
      .cl-show-progress, .cl-show-media { display: none; }
      .cl-case { min-height: 0; padding: 32px 0; }
      .cl-video-wrap { position: relative; inset: auto; margin-top: 24px; aspect-ratio: 900 / 560; }
    }

    /* reduced motion: everything visible, stacked, no sticky/crossfade
       (JS additionally gives the videos native controls and drops loop) */
    @media (prefers-reduced-motion: reduce) {
      .cl-show-grid { grid-template-columns: 1fr; gap: 0; }
      .cl-show-text { padding-left: 0; }
      .cl-show-progress, .cl-show-media { display: none; }
      .cl-case { min-height: 0; padding: 28px 0; }
      .cl-case > * { opacity: 1 !important; transform: none !important; transition: none !important; }
      .cl-video-wrap { position: relative; inset: auto; margin-top: 24px; aspect-ratio: 900 / 560; opacity: 1 !important; transform: none !important; transition: none !important; }
      .cl-prog-fill { transition: none !important; }
    }

    .cl-quote {
      font-family: var(--ff-display);
      margin: 0; font-size: 26px; font-weight: 400; line-height: 1.4; letter-spacing: -0.01em; color: var(--text-primary);
    }
    .cl-quote-author { font-family: var(--ff-body); margin-top: 20px; font-size: 15px; color: var(--text-muted); }

    /* ---- below 900px: stacked cards, all permanently active, no hover ---- */
    @media (max-width: 900px) {
      #clientes { padding: 80px 0; }
      .cl-inner { padding: 0 24px; }
      .cl-rows { margin-top: 36px; }
      /* No hover on touch, so every row is permanently in the active state.
         This block MUST mirror .cl-row.is-active above — it is a second, full
         copy of the reveal's end state, not an override of it, so inverting the
         desktop direction without inverting this too would have left mobile
         reading backwards. Every value below is the desktop active value. */
      .cl-row {
        flex-direction: column; align-items: flex-start; gap: 18px;
        height: auto; padding: 36px 4px;
        background: var(--bg-surface);
      }
      .cl-num { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
      .cl-desc { flex: none; width: 100%; color: var(--text-secondary); order: 3; }
      .cl-name { width: 100%; order: 2; }
      .cl-name-inner { font-size: 44px; color: var(--text-primary); white-space: normal; }
      .cl-name-logo { height: 40px; opacity: 1; }
      .has-logo-dual .cl-name-logo { opacity: 0; }
      .has-logo-dual .cl-name-logo-on { height: 100%; opacity: 1; }
      .cl-underline { background: var(--accent); transform: scaleX(1); }
      .cl-video {
        order: 4; width: 100%; flex: none; opacity: 1; transform: none;
        background: rgba(245, 244, 240, 0.05);
        border-color: rgba(245, 244, 240, 0.14);
      }
      .cl-row .cl-play { border-left-color: var(--text-secondary); }
      .cl-arrow { order: 5; color: var(--accent); opacity: 1; transform: none; font-size: 52px; align-self: flex-end; }
      /* Metric goes into the stacked card's flow (not the desktop bottom-right
         corner) so it never overlaps the reflowed content. Order 4 = after the
         logo/paragraph, before the arrow. Outline SVG stays absolute (traces
         the taller card). Triggered by IntersectionObserver, not hover. */
      .cl-metric-hover {
        position: static; order: 4; align-self: flex-start;
        align-items: flex-start; text-align: left;
        right: auto; bottom: auto; margin-top: 4px; max-width: 100%;
      }
      .cl-metric-hover-num { font-size: 46px; }
      .cl-metric-hover-label { max-width: 100%; }
      .cl-block > p { font-size: 17px; }
      .cl-metrics { grid-template-columns: 1fr; gap: 26px; }
      /* hero band: shorter, no WebGL layer is loaded below 768px anyway (JS
         gates Pixi to desktop), so this is just the static photo + logo */
      .cl-hero { height: clamp(300px, 52vh, 420px); border-radius: 18px; }
      .cl-detail-inner-hero { margin-top: 32px; }
    }

    @media (prefers-reduced-motion: reduce) {
      .cl-rows, .cl-row, .cl-num, .cl-desc, .cl-name-inner, .cl-underline,
      .cl-video, .cl-arrow, .cl-volver, .cl-detail { transition: none; animation: none; }
      /* Flourish snaps to its end state instead of drawing/counting (JS also
         sets the number straight to its final value under reduced motion). */
      .cl-outline rect, .cl-metric-hover { transition: none; }
      /* static photo only — no scroll-parallax slide (JS also skips it) */
      .cl-hero-media { transform: none !important; }
    }

    /* ============================================================
       15. CONTACT
    ============================================================ */
    /* Soft ambient color wash across the whole section (flat --bg-primary
       alone is too close in value to the glass card's own white tint below —
       tested by screenshot, the card was nearly invisible without this —
       matching the reference's gradient backdrop, kept brand-muted/low-
       saturation rather than the reference's literal blue/pink). */
    #contact {
      position: relative;   /* anchors #contact::before, the dither layer */
      background-color: var(--bg-alt);
      background-image:
        radial-gradient(circle at 12% 15%, rgba(120, 96, 200, 0.10), transparent 55%),
        radial-gradient(circle at 90% 85%, rgba(70, 190, 180, 0.10), transparent 55%);
      padding: 120px 0;
    }

    .contact-grid {
      /* Lifted above #contact::before: a positioned pseudo-element paints over
         non-positioned in-flow content, so without this the noise would sit on
         top of the headline and the form. */
      position: relative;
      z-index: 1;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 80px;
      align-items: start;
    }

    .ct-left h2 { font-size: clamp(36px, 5vw, 56px); margin-bottom: 20px; }
    .ct-left p {
      font-family: var(--ff-body);
      font-size: 17px;
      color: var(--text-secondary);
      line-height: 1.72;
      margin-bottom: 36px;
    }

    .ct-benefits { list-style: none; display: flex; flex-direction: column; gap: 12px; }
    .ct-ben {
      display: flex;
      align-items: center;
      gap: 12px;
      font-family: var(--ff-body);
      font-size: 15px;
      color: var(--text-secondary);
    }
    /* Surface disc with an accent tick, not four solid coral discs: this is a
       static benefit list, not an active state, so it doesn't get to spend the
       page's one accent as a fill. */
    .ben-dot {
      width: 20px; height: 20px;
      border-radius: 50%;
      background: var(--bg-raised);
      border: 1px solid var(--border-strong);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 9px;
      font-weight: 700;
      color: var(--accent);
      flex-shrink: 0;
    }

    /* Glassmorphism form card — same "white glass + soft color halo behind
       it" recipe as .demo-panel/.demo-glow (see AUTOMATIONS DEMO in
       script.js/styles.css), kept monochrome/muted here (vs. that section's
       saturated halo) to match this site's restrained brand palette and the
       neutral gray/blush reference the client shared. */
    .ct-form-wrap { position: relative; }
    .ct-form-glow {
      position: absolute;
      inset: -70px;
      z-index: 0;
      pointer-events: none;
      overflow: hidden;
      border-radius: 40px;
      background:
        radial-gradient(closest-side, rgba(120, 96, 200, 0.28), transparent 70%) 12% 18%/60% 60% no-repeat,
        radial-gradient(closest-side, rgba(70, 190, 180, 0.26), transparent 70%) 88% 82%/62% 62% no-repeat;
      filter: blur(60px);
    }

    .ct-form {
      position: relative;
      z-index: 1;
      /* DARK GLASS. See the same recipe on .demo-panel for the full rationale:
         a low white TINT (not a near-white fill), a border at ~2x the tint, an
         inset highlight on the TOP EDGE ONLY, and no drop shadow — a black
         shadow on a black page draws nothing. The old values here
         (rgba(255,255,255,0.42) over crema) rendered as a milky slab. */
      background: rgba(245, 244, 240, 0.055);
      backdrop-filter: blur(24px) saturate(160%);
      -webkit-backdrop-filter: blur(24px) saturate(160%);
      border: 1px solid rgba(245, 244, 240, 0.10);
      border-radius: 28px;
      padding: 48px;
      box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.14);
    }
    /* No backdrop-filter support: fall back to a solid L2 card so the fields
       keep a surface instead of sitting straight on the glow. */
    @supports not (backdrop-filter: blur(1px)) {
      .ct-form { background: var(--bg-surface); }
    }

    .fg { margin-bottom: 20px; }
    .fl {
      display: block;
      font-family: var(--ff-body);
      font-size: 12px;
      font-weight: 500;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: var(--text-muted);
      margin-bottom: 8px;
    }
    .fi, .fta {
      width: 100%;
      padding: 14px 16px;
      background: rgba(10, 10, 10, 0.34);
      border: 1px solid rgba(245, 244, 240, 0.14);
      border-radius: 8px;
      font-family: var(--ff-body);
      font-size: 16px;
      color: var(--text-primary);
      outline: none;
      -webkit-appearance: none;
      transition: border-color 0.2s;
    }
    .fi::placeholder, .fta::placeholder { color: var(--text-muted); }
    /* Focus is the accent — one of its four sanctioned uses. */
    .fi:focus, .fta:focus { border-color: var(--accent); }
    .fta { height: 120px; resize: none; }
    .fg-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }

    .btn-grad {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 100%;
      height: 54px;
      background: var(--accent);
      border: none;
      border-radius: 100px;
      color: var(--accent-ink);
      font-family: var(--ff-body);
      font-size: 16px;
      font-weight: 500;
      cursor: pointer;
      transition: opacity 0.2s, transform 0.2s;
      margin-top: 8px;
    }
    .btn-grad:hover { background: var(--accent-hover); transform: translateY(-1px); }

    /* ============================================================
       16. TEAM / SOBRE NOSOTROS — slideshow (previous/next, crossfade)
    ============================================================ */
    .team-slideshow {
      position: relative;
      background: var(--bg-alt);
      /* 100px clears the 64px fixed #nav with room to spare; was 140px, which
         (with the tall 2/3 photo box below) pushed the team members off the
         first screen so you had to scroll to see them. */
      padding: 100px 0 0;
      overflow: hidden;
    }

    .team-slide-brand { padding: 0 8vw; margin-bottom: 16px; }
    /* No filter, same reason as .nav-logo img: gummalogo-tiffa.png is the
       two-tone white + mint artwork, so any invert/brightness would destroy
       it (the old grey wordmark tolerated one; this one does not). */
    .team-slide-brand-logo {
      height: 26px;
      width: auto;
      display: block;
    }

    .team-slide-stage {
      position: relative;
      display: flex;
      align-items: center;
      /* Fill the viewport left under the top padding + brand + thumbs bar,
         instead of a flat 78vh that overflowed once the photo box's own
         aspect-ratio made it taller than the screen. */
      min-height: calc(100dvh - 210px);
    }

    .team-slide-info {
      position: relative;
      z-index: 3;
      flex: 0 0 auto;
      width: 46%;
      padding: 0 8vw;
      box-sizing: border-box;
    }

    /* Sibling of .team-slide-info/.team-slide-photos (not nested inside the
       text column) so it can bleed rightward past the 46% column and sit
       BEHIND the photo silhouette (z-index 1 < photos' 2 < info's 3) — the
       silhouette is the protagonist, the ghost name only peeks through the
       transparent gaps around it. */
    .team-slide-ghost {
      position: absolute;
      left: 8vw;
      top: 50%;
      transform: translateY(-54%);
      font-family: 'Playfair Display', var(--ff-display), serif;
      font-weight: 900;
      font-size: clamp(80px, 15vw, 260px);
      line-height: 1;
      /* Was rgba(10,10,10,0.035) — black at 3.5% on a black section renders
         nothing at all. Light ink at the same near-invisible weight instead;
         5% rather than 3.5% because a light wash on dark carries less than a
         dark wash on light at equal alpha. */
      color: rgba(245, 244, 240, 0.05);
      white-space: nowrap;
      z-index: 1;
      pointer-events: none;
      user-select: none;
      transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
    }

    .team-slide-index,
    .team-slide-name,
    .team-slide-role,
    .team-slide-bio {
      position: relative;
      transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
    }

    .team-slide-index {
      display: block;
      font-family: var(--ff-body);
      font-size: 13px;
      font-weight: 500;
      letter-spacing: 0.08em;
      color: var(--text-muted);
      margin-bottom: 14px;
    }

    .team-slide-name {
      font-family: 'Playfair Display', var(--ff-display), serif;
      font-weight: 900;
      font-size: clamp(48px, 7.4vw, 104px);
      line-height: 1.02;
      letter-spacing: -0.015em;
      color: var(--text-primary);
      margin: 0 0 10px;
    }

    .team-slide-role {
      font-family: var(--ff-body);
      font-size: 14px;
      color: var(--text-secondary);
      margin: 0 0 22px;
    }

    .team-slide-bio {
      font-family: var(--ff-body);
      font-size: 15px;
      line-height: 1.65;
      color: var(--text-secondary);
      max-width: 360px;
      margin: 0 0 32px;
    }

    .team-slide-ghost.is-transitioning { opacity: 0; }
    .team-slide-info.is-transitioning .team-slide-index,
    .team-slide-info.is-transitioning .team-slide-name,
    .team-slide-info.is-transitioning .team-slide-role,
    .team-slide-info.is-transitioning .team-slide-bio {
      opacity: 0;
      transform: translateY(8px);
    }

    /* Thin rounded pill around previous/next, per the reference — a single
       bordered container with a hairline divider between the two buttons,
       rather than two free-floating text links. */
    .team-slide-nav {
      position: relative;
      z-index: 1;
      display: inline-flex;
      align-items: center;
      gap: 4px;
      border: 1px solid var(--border);
      border-radius: 999px;
      padding: 4px;
    }

    .team-slide-nav-divider {
      width: 1px;
      height: 16px;
      background: var(--border);
      flex-shrink: 0;
    }

    .team-slide-arrow {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      background: none;
      border: none;
      border-radius: 999px;
      font-family: var(--ff-body);
      font-size: 13px;
      font-weight: 500;
      color: var(--text-primary);
      cursor: pointer;
      padding: 8px 16px;
      transition: opacity 0.2s var(--ease), background-color 0.2s var(--ease);
    }
    .team-slide-arrow:hover { background: rgba(10, 10, 10, 0.05); }
    .team-slide-arrow span { font-size: 16px; line-height: 1; }

    /* aspect-ratio (not align-self: stretch) governs the height — width
       still resolves normally via flex-grow (54% of the stage, since
       .team-slide-info is a fixed width: 46%), then aspect-ratio derives the
       height from that width instead of stretching to the flex line's cross
       size. Without this, on a wide/short viewport (e.g. a 1920px-wide
       laptop with ~800px of usable height) a stretched item stayed exactly
       as wide as before while its height got capped by the old
       min-height:78vh's implied line size, so `cover` had to scale the
       (portrait) photo up a lot to fill that width — cropping most of the
       face out (confirmed by screenshot: only hair/eyebrows visible, nose/
       mouth/chin gone). Aspect close to a headshot's natural portrait ratio
       keeps cover's residual crop minimal at any width. Trade-off: on
       unusually short viewports the box
       is now taller than 78vh (e.g. ~1166px tall at 1440×900, vs. the photo
       column's own ~900px viewport height), so the very bottom of the photo
       needs a little scroll to reach — accepted deliberately, since a
       little scroll beats a face with the nose and mouth cropped off. */
    /* No backdrop panel here (removed 2026-08-11, client asked for the
       cutout to sit directly on the section's own background — the L2 panel
       that used to separate a dark-clothed silhouette from the dark section
       read as an unwanted square behind the photo). */
    .team-slide-photos {
      position: relative;
      z-index: 2;
      flex: 1 1 auto;
      aspect-ratio: 2 / 3;
      /* Cap the (otherwise width-driven, very tall) box to the viewport so the
         bottom-anchored cutout stays fully on the first screen — with
         `contain` the person just scales to fit, never gets pushed below the
         fold. Kept ~30px under the stage's own min-height so it stays centred. */
      max-height: calc(100dvh - 240px);
    }

    /* Real transparent-background cutouts (rembg / u2net_human_seg run on
       assets/img/marconuevo.png / assets/img/guillenuevo.png, cropped tight to their own alpha
       bounding box) — background-size/position are set inline in
       script.js (`photoFit`/`photoPosition`; 'contain' + 'center bottom'
       for both members currently) so the full, uncropped person sits
       anchored to the bottom of the box. Because the cutout is genuinely
       transparent, the section's own crema background (and the ghost name
       behind it) already shows through everywhere there's no person — no
       mask-image or gradient overlay is needed to fake that blend the way
       the earlier opaque-photo version required (removed both: a
       left-edge mask-image and a dark bottom-gradient ::after existed here
       when the photos were still plain opaque rectangles under `cover`;
       once real cutouts arrived, that mask/gradient combo just painted an
       ugly dark smudge across the now-transparent areas — confirmed by
       screenshot before removing them). No filter — full color, per an
       explicit ask to drop the earlier warm black-and-white treatment
       (grayscale(0.88) sepia(0.14) contrast(1.04) brightness(1.02)). */
    .team-slide-photo {
      position: absolute;
      inset: 0;
      background-repeat: no-repeat;
      background-size: cover;
      background-position: center 12%;
      opacity: 0;
      transition: opacity 0.55s var(--ease);
    }
    .team-slide-photo.is-active { opacity: 1; }

    /* Small caption + decorative line over the photo's lower-left, just
       above where the bottom gradient darkens — matches the reference's
       little paragraph sitting above Steve Jobs' chin. Light/white text
       since it sits on the dark end of the bottom gradient. */
    .team-slide-caption {
      position: absolute;
      z-index: 2;
      left: 7%;
      bottom: 14%;
      max-width: 230px;
      display: flex;
      align-items: flex-start;
      gap: 12px;
      pointer-events: none;
    }
    .team-slide-caption-line {
      flex-shrink: 0;
      width: 28px;
      height: 1px;
      margin-top: 8px;
      background: rgba(245, 244, 240, 0.45);
    }
    .team-slide-caption p {
      margin: 0;
      font-family: var(--ff-body);
      font-size: 12.5px;
      line-height: 1.55;
      color: var(--text-primary);
    }

    .team-slide-thumbs {
      position: relative;
      z-index: 2;
      display: flex;
      align-items: center;
      gap: 28px;
      /* L0 against the section's L1 — was var(--accent) back when that token
         meant near-black. A coral bar here would be the loudest thing on the
         page. */
      background: var(--bg-base);
      padding: 20px 8vw;
      margin-top: 48px;
    }

    .team-slide-more {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      flex-shrink: 0;
      background: none;
      border: none;
      font-family: var(--ff-body);
      font-size: 13px;
      font-weight: 500;
      color: var(--text-primary);
      cursor: pointer;
      padding: 8px 0;
      transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
    }
    .team-slide-more:hover { opacity: 0.7; transform: translateX(2px); }
    .team-slide-more span { font-size: 15px; line-height: 1; }

    .team-slide-thumb-list {
      display: flex;
      align-items: center;
      gap: 32px;
      padding-left: 28px;
      border-left: 1px solid rgba(255, 255, 255, 0.15);
      overflow-x: auto;
    }

    .team-thumb {
      display: flex;
      align-items: center;
      gap: 12px;
      background: none;
      border: none;
      cursor: pointer;
      padding: 6px 10px;
      border-radius: 999px;
      flex-shrink: 0;
      transition: background-color 0.2s var(--ease);
    }
    .team-thumb:hover { background: rgba(255, 255, 255, 0.06); }

    .team-thumb-photo {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      background-size: cover;
      background-position: center 20%;
      filter: grayscale(100%);
      opacity: 0.55;
      flex-shrink: 0;
      transition: filter 0.3s var(--ease), opacity 0.3s var(--ease), box-shadow 0.3s var(--ease);
    }
    .team-thumb.is-active .team-thumb-photo {
      filter: grayscale(0%);
      opacity: 1;
      box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.6);
    }

    .team-thumb-label {
      display: flex;
      flex-direction: column;
      text-align: left;
      font-family: var(--ff-body);
      font-size: 13px;
      font-weight: 500;
      color: rgba(255, 255, 255, 0.45);
      white-space: nowrap;
      transition: color 0.3s var(--ease);
    }
    .team-thumb.is-active .team-thumb-label { color: var(--text-primary); }
    .team-thumb-label small {
      display: block;
      font-size: 11px;
      font-weight: 400;
      color: rgba(255, 255, 255, 0.35);
      margin-top: 2px;
      transition: color 0.3s var(--ease);
    }
    .team-thumb.is-active .team-thumb-label small { color: rgba(255, 255, 255, 0.6); }

    @media (prefers-reduced-motion: reduce) {
      .team-slide-ghost,
      .team-slide-index,
      .team-slide-name,
      .team-slide-role,
      .team-slide-bio { transition: none; }
      .team-slide-photo { transition: none; }
    }

    /* ============================================================
       17. FOOTER
    ============================================================ */
    /* L0, same as the page base — so the hairline is what separates the
       footer from the last section, not a change of fill. (It used to be
       --accent, back when --accent meant near-black.) */
    footer {
      background-color: var(--bg-base);
      border-top: 1px solid var(--border);
      padding: 64px 0 40px;
    }

    .foot-inner {
      display: grid;
      grid-template-columns: 1fr auto auto;
      gap: 64px;
      align-items: start;
      padding-bottom: 48px;
      margin-bottom: 40px;
      border-bottom: 1px solid var(--border);
    }

    .foot-brand {
      display: flex;
      align-items: center;
      gap: 10px;
      font-family: var(--ff-display);
      font-size: 18px;
      font-weight: 700;
      color: var(--text-primary);
      margin-bottom: 14px;
    }
    .foot-brand img {
      height: 32px;
      width: auto;
    }
    .foot-tagline {
      font-family: var(--ff-body);
      font-size: 14px;
      color: var(--text-muted);
      line-height: 1.6;
      max-width: 240px;
    }

    .foot-col-title {
      font-family: var(--ff-body);
      font-size: 11px;
      font-weight: 500;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--text-muted);
      margin-bottom: 16px;
    }

    .foot-links { list-style: none; display: flex; flex-direction: column; gap: 10px; }
    .foot-links a {
      font-family: var(--ff-body);
      font-size: 14px;
      color: var(--text-muted);
      transition: color 0.2s;
    }
    .foot-links a:hover { color: var(--text-primary); }

    .foot-copy {
      font-family: var(--ff-body);
      font-size: 13px;
      color: var(--text-muted);
      text-align: center;
    }

    /* ============================================================
       18. RESPONSIVE
    ============================================================ */
    @media (max-width: 900px) {
      /* La flecha de #proceso rota en vez de desaparecer: el orden entre las
         dos ofertas es el mensaje de la sección, así que tiene que seguir
         leyéndose en una sola columna. */
      .pr-flow { grid-template-columns: 1fr; gap: 20px; }
      .pr-arrow { justify-content: center; transform: rotate(90deg); }
      .dg-areas { grid-template-columns: repeat(2, 1fr); gap: 4px 28px; }
      /* Las fases nunca llegan aquí en modo pineado: script.js solo pone
         .dgf-on a partir de 900px, así que por debajo mandan las reglas base
         (lista vertical con los cuatro esquemas visibles). */
      .dgf-steps { gap: 34px; }
      .dgf-step + .dgf-step { padding-top: 34px; }
      .dgf-cap { max-width: none; }
      .qt-grid { grid-template-columns: 1fr; gap: 0; }
      .ip-layout { grid-template-columns: 1fr; gap: 40px; }
      .ip-text { max-width: none; }
      .stack { height: 630px; }
      .stack-card { width: 320px; height: 540px; }
      .auto-card { padding: 26px; }
      .faq-list { padding: 20px 16px; border-radius: 28px; }
      .faq-q { padding: 12px; }
      .contact-grid { grid-template-columns: 1fr; gap: 48px; }
      .fg-2 { grid-template-columns: 1fr; }
      .foot-inner { grid-template-columns: 1fr; gap: 40px; }
      /* Stack: photo (order 1) → ghost (order 2, negative margin pulls the
         name up to overlap it) → text (order 3) — explicit `order` instead of
         `column-reverse` because the ghost is now a stage-level sibling, not
         nested inside .team-slide-info, so a simple row-reverse would push it
         to the very bottom instead of behind the name. */
      .team-slide-stage { flex-direction: column; min-height: 0; padding-bottom: 40px; }
      .team-slide-photos { order: 1; width: 100%; min-height: 46vh; }
      .team-slide-caption { display: none; }
      .team-slide-ghost {
        order: 2;
        position: static;
        transform: none;
        display: block;
        margin: 12px 6vw -46px;
        font-size: clamp(56px, 18vw, 130px);
      }
      .team-slide-info { order: 3; width: 100%; padding: 0 6vw 0; }
      .team-slide-bio { max-width: 100%; }
    }

    @media (max-width: 768px) {
      .nav-links, .nav-btn { display: none; }
      .nav-burger { display: flex; }
      .dg-areas { grid-template-columns: 1fr; }
      .stack { height: 575px; }
      .stack-card { width: 270px; height: 495px; border-radius: 16px; }
      .auto-card { padding: 20px; }
      .auto-title { font-size: 18px; }
      .auto-desc { font-size: 13px; margin-bottom: 14px; }
      .auto-box { padding: 14px; }
      .auto-row-title { font-size: 12px; }
      .auto-row-sub { font-size: 11px; }
      .ct-form { padding: 36px 24px; }
      .hero-ctas { flex-direction: column; align-items: center; }
      .team-slide-brand { padding: 0 6vw; }
      .team-slide-info { padding: 28px 6vw 0; }
      .team-slide-name { font-size: clamp(30px, 8vw, 42px); }
      .team-slide-bio { font-size: 14px; }
      .team-slide-thumbs { padding: 16px 6vw; gap: 18px; }
      .team-slide-more { font-size: 12px; }
      .team-slide-thumb-list { gap: 22px; padding-left: 18px; }
      .team-thumb-photo { width: 34px; height: 34px; }
      .stack-arrow { width: 40px; height: 40px; }
      .stack-arrow-prev { left: 0; }
      .stack-arrow-next { right: 0; }
    }

    @media (max-width: 480px) {
      /* Anton poster title (see .hero-h, section 8) is set from a large vw-
         dominant clamp so it fills wide screens; on narrow phones the same
         formula would make 'AUTOMATIZA' overflow the 28px .wrap gutters, so
         this drops the vw coefficient/ceiling for small viewports only. */
      .hero-h { font-size: clamp(25px, 11.7vw, 50px); }
      #proceso, #diagnostico, #diagnostico-limite, #entregable,
      #implementacion, #automations, #contact, #faq { padding: 80px 0; }
      .dgf-step { gap: 14px; }
      .dl-bloom { width: 340px; height: 240px; filter: blur(50px); }
      .pr-card, .ip-bonus { padding: 26px 22px; }
      .faq-head { margin-bottom: 40px; }
      .faq-q { font-size: 16px; }
      .stack { height: 570px; }
      .stack-card { width: 230px; height: 500px; }
      .stack-arrow { width: 36px; height: 36px; }
      .auto-card { padding: 16px; }
      .auto-status { margin-bottom: 12px; }
      .auto-title { font-size: 16px; margin-bottom: 6px; }
      .auto-desc { font-size: 12px; margin-bottom: 12px; }
      .auto-box { padding: 12px; }
      .auto-icon { width: 28px; height: 28px; font-size: 14px; }
      .auto-stats { padding-top: 10px; }
      .auto-stat-value { font-size: 16px; }
      .team-slide-brand { padding: 0 4vw; }
      .team-slide-info { padding: 24px 4vw 0; }
      .team-slide-photos { min-height: 38vh; }
      .team-slide-thumbs { padding: 14px 4vw; gap: 14px; }
      .team-slide-more span { display: none; }
      .team-slide-thumb-list { gap: 16px; padding-left: 14px; }
      .team-thumb-label { font-size: 12px; }
      .team-thumb-label small { font-size: 10px; }
    }

/* ============================================================
   19. AUTOMATIONS DEMO — glass CTA + iPhone WhatsApp reveal
   Prefixed demo- throughout to avoid colliding with any existing
   selector (.thread/.screen/etc. are common enough names to clash).
============================================================ */

/* ---- shell: wraps the CTA + reveal (positioning context only — the glow
   layers themselves are scoped separately below, one per glass surface) ---- */
.demo-shell { position: relative; }

@keyframes demo-drift {
  from { transform: translateY(2px) scale(1); }
  to   { transform: translateY(-6px) scale(1.05); }
}
/* Same drift, but for an element centered via transform: translateX(-50%)
   instead of left/margin — an animation's keyframes replace the whole
   `transform` property, they don't merge with a separately-declared static
   transform, so a keyframe list missing translateX(-50%) silently drops the
   centering the moment the animation starts. This variant keeps it in both
   keyframes so centering survives the whole animation. */
@keyframes demo-drift-centered {
  from { transform: translateX(-50%) translateY(2px) scale(1); }
  to   { transform: translateX(-50%) translateY(-4px) scale(1.05); }
}

/* ---- CTA zone + glass button — each glass surface gets its OWN halo ----
   A single halo stretched to cover both the button and the panel (tried
   first) dilutes the color too thin to read as glass behind either one —
   the button ended up looking flat gray instead of tinted. Keeping two
   separate, fully-saturated halos, each sized to its own surface, is what
   actually reads as color-through-glass at both spots. */
.demo-cta-zone {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  padding: 16px 0 0;
}
/* Shown only for whichever cards have a DEMOS entry (currently confirmación/
   cancelación/recordatorio/leads) — script.js toggles this class as the
   carousel's active card changes. */
.demo-cta-zone.demo-cta-hidden { display: none; }
.demo-cta-zone::before {
  content: '';
  position: absolute;
  top: 26px; left: 50%;
  width: 420px; height: 140px;
  transform: translateX(-50%);
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(closest-side, rgba(120, 96, 200, 0.50), transparent 72%) 2% 45%/50% 90% no-repeat,
    radial-gradient(closest-side, rgba(70, 190, 180, 0.45), transparent 72%) 48% 55%/58% 100% no-repeat,
    radial-gradient(closest-side, rgba(60, 150, 190, 0.45), transparent 72%) 98% 48%/52% 92% no-repeat;
  filter: blur(34px);
  opacity: 0.8;
  /* Paused by default — running only while .demo-shell is on screen (see
     the IntersectionObserver in the "AUTOMATIONS DEMO" IIFE in script.js).
     This sits directly under .demo-glass-btn's backdrop-filter, so an
     always-on animation here meant that blur was being recomputed every
     frame for the page's entire lifetime — the main cause of the lag
     scrolling through this section. */
  animation: demo-drift-centered 12s ease-in-out infinite alternate;
  animation-play-state: paused;
}
.demo-shell.demo-anim-live .demo-cta-zone::before { animation-play-state: running; }

.demo-glass-btn {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 18px 38px;
  font-family: var(--ff-body);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  cursor: pointer;
  border-radius: 999px;
  /* Inverted. This was a DARK glass pill (rgba(10,10,10,0.4)) precisely because
     the section around it was crema; on black that reads as a hole punched in
     the page. Now light-tinted glass — same construction, light source on the
     other side. Drop shadow and text-shadow both dropped: neither draws
     anything useful on black. */
  border: 1px solid rgba(245, 244, 240, 0.22);
  background: rgba(245, 244, 240, 0.10);
  backdrop-filter: blur(16px) saturate(190%);
  -webkit-backdrop-filter: blur(16px) saturate(190%);
  box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.20);
  transition: transform 0.35s var(--ease), background 0.3s;
}
/* No backdrop-filter support: solid raised pill, so the label keeps a surface
   instead of floating directly on the glow blobs. */
@supports not (backdrop-filter: blur(1px)) {
  .demo-glass-btn { background: var(--bg-raised); }
}
.demo-glass-btn:hover { transform: translateY(-2px) scale(1.02); background: rgba(245, 244, 240, 0.16); }
.demo-glass-btn:active { transform: scale(0.99); }
.demo-glass-btn:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 5px; }
.demo-chev { transition: transform 0.4s var(--ease); }
.demo-glass-btn[aria-expanded="true"] .demo-chev { transform: rotate(180deg); }

/* ---- collapsible reveal: 0fr -> 1fr grid, no hardcoded max-height ---- */
.demo-reveal {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.75s var(--ease);
  background: transparent;
}
.demo-reveal.demo-open { grid-template-rows: 1fr; }
.demo-reveal > div { overflow: hidden; background: transparent; }

.demo-inner {
  display: grid;
  grid-template-columns: 340px 1fr;
  gap: 64px;
  align-items: center;
  padding-top: 72px;
  background: transparent;
}

/* ---- panel's own halo — centered directly behind the panel, overflowing
   it by ~80px each side, so the white glass has real color to refract.
   .demo-copy is its positioning parent (not .demo-shell): scoping it tightly
   to the panel's own box, rather than spanning the whole demo section, is
   what keeps the color concentrated and visible instead of diluted. ---- */
.demo-copy { position: relative; }
/* Hidden via opacity (not left to the collapsed .demo-reveal's overflow:
   hidden ancestor) — a blurred, absolutely-positioned descendant of a
   grid row collapsed to 0fr/0px doesn't reliably get clipped by that
   ancestor in every browser (confirmed by inspecting the rendered rects:
   the 0-height wrapper measured exactly 0px tall, yet the glow still
   painted through). Gating on the same .demo-open class the rest of the
   reveal uses sidesteps that clipping/filter interaction entirely. */
.demo-glow {
  position: absolute;
  inset: -80px;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(closest-side, rgba(120, 96, 200, 0.46), transparent 70%) 10% 20%/55% 55% no-repeat,
    radial-gradient(closest-side, rgba(70, 190, 180, 0.42), transparent 70%) 55% 45%/65% 65% no-repeat,
    radial-gradient(closest-side, rgba(60, 150, 190, 0.42), transparent 70%) 90% 75%/58% 58% no-repeat;
  filter: blur(70px);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  /* Paused by default, same reasoning/mechanism as .demo-cta-zone::before
     above — running only while .demo-shell is on screen. */
  animation: demo-drift 12s ease-in-out infinite alternate;
  animation-play-state: paused;
}
.demo-shell.demo-anim-live .demo-glow { animation-play-state: running; }
.demo-reveal.demo-open .demo-glow { opacity: 0.75; }

/* ---- Liquid-glass text panel: WHITE glass (crema background, not a photo —
   a dark tint here reads as a dead opaque box; white glass with its own
   saturated halo behind it is what actually reads as "ice over color"). ---- */
.demo-panel {
  position: relative;
  z-index: 1;
  border-radius: 34px;
  padding: 40px;
  /* DARK GLASS — the recipe shared with .faq-list and .ct-form.
     The old one (0.55 white tint, 0.8 white border, black drop shadow,
     full-perimeter white inset) was derived for a crema section; on black it
     renders as a milky slab. Re-derived: a low white TINT instead of a
     near-white fill, a border at roughly twice the tint's strength, an inset
     highlight on the TOP EDGE ONLY (light comes from above; full-perimeter
     reads as moulded plastic), and no drop shadow at all. */
  background: rgba(245, 244, 240, 0.055);
  backdrop-filter: blur(30px) saturate(200%);
  -webkit-backdrop-filter: blur(30px) saturate(200%);
  border: 1px solid rgba(245, 244, 240, 0.10);
  box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.14);
}
/* No backdrop-filter support: solid L2 panel so the copy keeps a surface. */
@supports not (backdrop-filter: blur(1px)) {
  .demo-panel { background: var(--bg-surface); }
}

.demo-copy h3 {
  font-family: var(--ff-display);
  font-size: 28px;
  line-height: 1.2;
  letter-spacing: -0.02em;
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: 14px;
}
.demo-copy p {
  font-family: var(--ff-body);
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--text-secondary);
  margin-bottom: 26px;
  max-width: 44ch;
}

/* ---- step sub-cards: nested, lighter glass inside the panel's glass ---- */
.demo-steps {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-bottom: 28px;
}
.demo-step-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-radius: 20px;
  padding: 16px 18px;
  background: rgba(245, 244, 240, 0.05);
  border: 1px solid rgba(245, 244, 240, 0.09);
  box-shadow: inset 0 1px 0 rgba(245, 244, 240, 0.10);
}
.demo-step-label {
  font-family: var(--ff-body);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.demo-step-text {
  font-family: var(--ff-body);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text-primary);
}
/* Step 4 ("never post the bad review to Google") is a benefit, not an alarm
   — tinted a reassuring soft green rather than a warning red. */
.demo-step-highlight {
  background: rgba(37,211,102,0.16);
  border-color: rgba(37,211,102,0.3);
}

.demo-replay {
  margin-top: 4px;
  background: rgba(245, 244, 240, 0.07);
  border: 1px solid rgba(245, 244, 240, 0.16);
  color: var(--text-primary);
  font-family: var(--ff-body);
  font-size: 13px;
  padding: 10px 20px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.demo-replay:hover { background: rgba(245, 244, 240, 0.13); border-color: rgba(245, 244, 240, 0.3); }
.demo-replay:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ---- iPhone ---- */
/* No shadow/filter here at all, on this element or any ancestor of .demo-phone
   — confirmed by pixel-sampling a rendered screenshot (uniform background
   color everywhere outside the phone's own silhouette, zero variance) that
   this is the only setup with no visible box around the phone. Any visible
   shadow behind a large rounded-rectangle shape on a plain light background
   necessarily reads as a soft rectangle (uniform shading along the phone's
   long straight edges) — that's what earlier attempts here (box-shadow, then
   a softer box-shadow, then filter: drop-shadow) kept reintroducing. The
   phone's own dark bezel provides enough contrast against the crema
   background without needing an elevation shadow.
*/
.demo-stage {
  perspective: 1500px;
  display: flex;
  justify-content: center;
  background: transparent;
}
.demo-phone {
  position: relative;
  width: 300px; height: 618px;
  border-radius: 52px;
  background: linear-gradient(145deg, #e8e6e3 0%, #9b9894 12%, #5d5b58 30%, #3a3937 50%, #5d5b58 70%, #a9a6a2 88%, #dedbd7 100%);
  padding: 3px;
  transform-style: preserve-3d;
  transform: rotateY(12deg) rotateX(1deg);
  transition: transform 0.5s var(--ease);
  will-change: transform;
}
.demo-bezel { width: 100%; height: 100%; border-radius: 49px; background: #000; padding: 8px; position: relative; }
.demo-screen {
  width: 100%; height: 100%; border-radius: 42px; overflow: hidden;
  background: #efe7de;
  display: flex; flex-direction: column; position: relative;
}
.demo-phone::after {
  content: '';
  position: absolute; inset: 0; border-radius: 52px; pointer-events: none;
  background: linear-gradient(108deg, transparent 32%, rgba(255,255,255,0.22) 45%, rgba(255,255,255,0.05) 52%, transparent 62%);
  transform: translateZ(2px);
}
.demo-island {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  width: 90px; height: 26px; background: #000; border-radius: 18px; z-index: 5;
}
.demo-island::after {
  content: '';
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 7px; height: 7px; border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #2b3a4a, #080d12 70%);
}
.demo-btn-side { position: absolute; background: linear-gradient(180deg, #8e8b87, #4a4845); border-radius: 2px; }
.demo-b-silent { left: -2.5px; top: 102px; width: 3px;   height: 24px; }
.demo-b-up     { left: -3px;   top: 144px; width: 3.5px; height: 48px; }
.demo-b-down   { left: -3px;   top: 202px; width: 3.5px; height: 48px; }
.demo-b-power  { right: -3px;  top: 168px; width: 3.5px; height: 76px; }

.demo-statusbar {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 24px 4px;
  font-family: var(--ff-body);
  font-size: 13px; font-weight: 600; color: #000;
  background: #f0f2f5; flex: 0 0 auto; letter-spacing: -0.02em;
}
.demo-sb-right { display: flex; align-items: center; gap: 5px; }
.demo-bat { width: 22px; height: 11px; border: 1.2px solid rgba(0,0,0,0.4); border-radius: 3px; padding: 1.4px; position: relative; }
.demo-bat::after { content: ''; position: absolute; right: -3px; top: 3.4px; width: 1.6px; height: 4px; background: rgba(0,0,0,0.35); border-radius: 0 1px 1px 0; }
.demo-bat i { display: block; height: 100%; width: 78%; background: #000; border-radius: 1px; }

.demo-wa-head { display: flex; align-items: center; gap: 10px; padding: 6px 14px 11px; background: #f0f2f5; flex: 0 0 auto; }
.demo-back { color: #007aff; font-size: 20px; line-height: 1; }
.demo-avatar { width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, #25D366, #128c7e); display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 700; }
.demo-who { font-family: var(--ff-body); font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; color: #000; }
.demo-st { font-family: var(--ff-body); font-size: 11px; color: #8d9199; }

.demo-thread {
  flex: 1; padding: 14px 10px; display: flex; flex-direction: column; gap: 7px;
  overflow-y: auto;
  background: #efe7de;
  background-image: radial-gradient(rgba(0,0,0,0.028) 1px, transparent 1px);
  background-size: 20px 20px;
  scrollbar-width: none;
}
.demo-thread::-webkit-scrollbar { display: none; }

.demo-bubble {
  max-width: 80%; padding: 7px 10px; border-radius: 9px;
  font-family: var(--ff-body); font-size: 13px; line-height: 1.42; color: #111;
  box-shadow: 0 1px 1px rgba(0,0,0,0.09);
  opacity: 0; transform: translateY(9px) scale(0.96);
  animation: demo-pop 0.42s var(--ease) forwards;
}
.demo-in  { align-self: flex-start; background: #fff; }
.demo-out { align-self: flex-end;  background: #d9fdd3; }
.demo-t { display: block; text-align: right; font-size: 10px; color: #8d9199; margin-top: 2px; }
.demo-out .demo-t { color: #5fa77e; }
@keyframes demo-pop { to { opacity: 1; transform: none; } }

.demo-typing {
  align-self: flex-start; background: #fff; padding: 10px 12px; border-radius: 9px;
  display: flex; gap: 4px; box-shadow: 0 1px 1px rgba(0,0,0,0.09);
  opacity: 0; animation: demo-pop 0.3s var(--ease) forwards;
}
.demo-typing i { width: 6px; height: 6px; border-radius: 50%; background: #b3b7bd; animation: demo-blink 1.3s infinite; }
.demo-typing i:nth-child(2) { animation-delay: 0.2s; }
.demo-typing i:nth-child(3) { animation-delay: 0.4s; }
@keyframes demo-blink { 0%, 60%, 100% { opacity: 0.35; } 30% { opacity: 1; } }

.demo-review {
  align-self: center; width: 93%; margin-top: 5px; background: #fff; border-radius: 11px;
  padding: 12px 13px; box-shadow: 0 8px 22px -8px rgba(0,0,0,0.28);
  opacity: 0; transform: translateY(12px) scale(0.94);
  animation: demo-pop 0.55s var(--ease) forwards;
}
.demo-review-top { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.demo-review-g { font-family: var(--ff-body); font-size: 12px; font-weight: 700; letter-spacing: -0.01em; color: #111; }
.demo-review-stars { color: #fbbc04; font-size: 12px; letter-spacing: 1px; }
.demo-review p { font-family: var(--ff-body); font-size: 11.5px; line-height: 1.48; color: #3c4043; }
.demo-review-by { font-family: var(--ff-body); font-size: 10px; color: #80868b; margin-top: 5px; }

/* Generic payoff card (confirmación/cancelación/recordatorio demos) —
   reuses .demo-review's shell, swaps the Google+stars top row for a small
   WhatsApp-green icon badge + label. */
.demo-payoff-icon {
  width: 18px; height: 18px; border-radius: 50%; background: #25D366; color: #fff;
  display: grid; place-items: center; flex-shrink: 0; font-size: 10px;
}

.demo-wa-input { display: flex; align-items: center; gap: 8px; padding: 8px 10px 6px; background: #f0f2f5; flex: 0 0 auto; }
.demo-field { flex: 1; background: #fff; border-radius: 16px; padding: 7px 12px; font-family: var(--ff-body); font-size: 12px; color: #a5a9af; }
.demo-send { width: 28px; height: 28px; border-radius: 50%; background: #25D366; display: grid; place-items: center; flex: 0 0 auto; }
.demo-home-ind { height: 18px; background: #f0f2f5; display: grid; place-items: center; flex: 0 0 auto; }
.demo-home-ind i { width: 108px; height: 4.5px; border-radius: 3px; background: #000; opacity: 0.85; }

/* ======================================================================
   SOFTWARE DEMO — CSS MacBook that shows the a-medida invoicing panel.
   Shown instead of the iPhone whenever the active demo's layout is
   'macbook' (script.js toggles .demo-layout-soft on #demoReveal). All
   screen content is real DOM so it stays crisp when scaled/tilted.
   Blue accent (#185FA5) ties it to the acc-blue Facturación card.
   ====================================================================== */
.soft-stage { display: none; }
.demo-reveal.demo-layout-soft .demo-stage { display: none; }
.demo-reveal.demo-layout-soft .soft-stage {
  display: flex; justify-content: center; align-items: center;
  perspective: 1700px; background: transparent;
}
/* The MacBook is wide (landscape), so its column gets more room than the
   portrait phone's 340px. Scoped to the soft layout only. */
.demo-reveal.demo-layout-soft .demo-inner { grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); }

.soft-mac {
  position: relative; width: 560px; max-width: 100%;
  transform-style: preserve-3d;
  transform: rotateY(-13deg) rotateX(6deg);
  transition: transform 0.5s var(--ease);
  will-change: transform;
}
.soft-lid {
  width: 100%; aspect-ratio: 16 / 10;
  border-radius: 18px 18px 5px 5px; padding: 9px;
  background: linear-gradient(158deg, #e6e3df 0%, #a9a6a2 13%, #4c4b49 30%, #2c2b2a 50%, #4c4b49 70%, #b2afab 88%, #e8e5e1 100%);
  box-shadow: 0 34px 60px -34px rgba(0,0,0,0.5);
}
.soft-screen {
  width: 100%; height: 100%; border-radius: 7px; overflow: hidden;
  background: #F3F2EF; position: relative;
}
/* hinge / base of the laptop, slightly wider than the lid */
.soft-hinge {
  width: 116%; margin-left: -8%; height: 15px;
  border-radius: 0 0 13px 13px;
  background: linear-gradient(#cbc8c4, #ababa6 42%, #7f7c78);
  box-shadow: inset 0 2px 2px rgba(255,255,255,0.45), 0 10px 16px -8px rgba(0,0,0,0.45);
  position: relative;
}
.soft-notch {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 92px; height: 7px; background: #8d8a86; border-radius: 0 0 8px 8px;
}

/* ---- the app inside the screen ---- */
.soft-app { width: 100%; height: 100%; display: flex; flex-direction: column; font-family: var(--ff-body); color: #14151a; }
.soft-topbar {
  flex: 0 0 auto; height: 30px; display: flex; align-items: center; justify-content: space-between;
  padding: 0 12px; background: #fff; border-bottom: 1px solid var(--border);
}
.soft-brand { display: flex; align-items: center; gap: 6px; font-family: var(--ff-display); font-weight: 700; font-size: 11px; letter-spacing: -0.01em; }
.soft-brand-dot { width: 9px; height: 9px; border-radius: 3px; background: #185FA5; }
.soft-sync {
  display: flex; align-items: center; gap: 6px; font-size: 9.5px; font-weight: 600;
  color: #9a9a95; transition: color 0.3s var(--ease);
}
.soft-sync-dot { width: 7px; height: 7px; border-radius: 50%; background: #d9b23a; transition: background 0.3s var(--ease); }
.soft-sync.is-live { color: #1F8A4C; }
.soft-sync.is-live .soft-sync-dot { background: #25c368; }

.soft-body { flex: 1 1 auto; display: flex; min-height: 0; }
.soft-side { flex: 0 0 118px; background: #fff; border-right: 1px solid var(--border); padding: 10px 8px; display: flex; flex-direction: column; gap: 3px; }
.soft-nav {
  display: flex; align-items: center; gap: 7px; padding: 7px 9px; border-radius: 7px;
  font-size: 10.5px; font-weight: 500; color: #74736f;
  border-left: 2px solid transparent; transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.soft-nav i { font-size: 12px; }
.soft-nav.is-active { background: #EAF1FA; color: #185FA5; border-left-color: #185FA5; }

.soft-main { flex: 1 1 auto; position: relative; min-width: 0; }
.soft-view {
  position: absolute; inset: 0; padding: 13px 15px; overflow: hidden;
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.soft-view.is-active { opacity: 1; transform: none; pointer-events: auto; }
.soft-view-head { font-family: var(--ff-display); font-weight: 700; font-size: 12px; margin-bottom: 11px; display: flex; align-items: center; gap: 8px; }

/* dashboard */
.soft-cards { display: flex; gap: 9px; }
.soft-card { flex: 1 1 0; background: #fff; border: 1px solid var(--border); border-radius: 9px; padding: 9px 10px; display: flex; flex-direction: column; gap: 3px; }
.soft-card-label { font-size: 8.5px; font-weight: 600; letter-spacing: 0.03em; color: #9a9a95; text-transform: uppercase; }
.soft-card-value { font-family: var(--ff-display); font-weight: 700; font-size: 16px; letter-spacing: -0.01em; color: #14151a; }
.soft-card:first-child .soft-card-value { color: #185FA5; }
.soft-chart { display: flex; align-items: flex-end; gap: 7px; height: 74px; margin-top: 14px; padding: 0 2px; }
.soft-bar { flex: 1 1 0; height: 0; background: #d7dce3; border-radius: 3px 3px 0 0; transition: height 0.7s var(--ease); }
.soft-bar-hi { background: #185FA5; }
.soft-chart.is-grown .soft-bar { height: var(--h); }
.soft-chart-x { display: flex; gap: 7px; margin-top: 5px; padding: 0 2px; }
.soft-chart-x span { flex: 1 1 0; text-align: center; font-size: 7.5px; color: #b3b2ad; }

/* factura */
.soft-inv { background: #fff; border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; }
.soft-inv-id { font-family: var(--ff-body); font-weight: 600; font-size: 10px; color: #185FA5; background: #EAF1FA; padding: 2px 8px; border-radius: 999px; }
.soft-inv-top { display: flex; gap: 24px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.soft-inv-top > div { display: flex; flex-direction: column; gap: 2px; }
.soft-inv-k { font-size: 8.5px; font-weight: 600; letter-spacing: 0.03em; color: #9a9a95; text-transform: uppercase; }
.soft-inv-v { font-size: 11px; font-weight: 500; color: #14151a; }
.soft-inv-lines { padding: 8px 0; }
.soft-line {
  display: flex; justify-content: space-between; align-items: center; padding: 6px 0; font-size: 11px;
  opacity: 0; transform: translateX(-8px); transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.soft-line span:last-child { font-family: var(--ff-display); font-weight: 700; }
.soft-line.is-in { opacity: 1; transform: none; }
.soft-inv-tot {
  border-top: 1px solid var(--border); padding-top: 9px; margin-top: 2px;
  opacity: 0; transform: translateY(6px); transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.soft-inv-tot.is-in { opacity: 1; transform: none; }
.soft-tot-row { display: flex; justify-content: space-between; font-size: 10.5px; color: #74736f; padding: 3px 0; }
.soft-tot-total { font-family: var(--ff-display); font-weight: 700; font-size: 14px; color: #14151a; padding-top: 6px; }
.soft-tot-total span:last-child { color: #185FA5; }

/* pagos */
.soft-pay-list { display: flex; flex-direction: column; gap: 7px; }
.soft-pay-row { display: flex; align-items: center; justify-content: space-between; background: #fff; border: 1px solid var(--border); border-radius: 8px; padding: 9px 11px; font-size: 10.5px; }
.soft-pay-name { font-weight: 500; }
.soft-pay-target { transition: box-shadow 0.3s var(--ease); }
.soft-pay-target.is-paid { box-shadow: 0 0 0 1.5px rgba(31,138,76,0.35); }
.soft-pill { display: inline-flex; align-items: center; gap: 4px; font-size: 9px; font-weight: 700; padding: 3px 9px; border-radius: 999px; }
.soft-pill-wait { background: #FBEAE8; color: #C23B2E; }
.soft-pill-ok { background: #E7F5EC; color: #1F8A4C; }
.soft-toast {
  display: flex; align-items: center; gap: 6px; margin-top: 12px;
  background: #14151a; color: #fff; font-size: 10px; font-weight: 500;
  padding: 8px 12px; border-radius: 8px; width: fit-content;
  opacity: 0; transform: translateY(8px); transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.soft-toast i { color: #25c368; }
.soft-toast.is-in { opacity: 1; transform: none; }

@media (max-width: 920px) {
  .demo-inner { grid-template-columns: 1fr; gap: 44px; padding-top: 48px; }
  .demo-phone { transform: none !important; width: 270px; height: 556px; }
  .demo-stage { perspective: none; }
  .demo-panel { padding: 26px; }
  .demo-steps { grid-template-columns: 1fr; }
  /* MacBook: single column, no tilt/perspective on touch */
  .demo-reveal.demo-layout-soft .demo-inner { grid-template-columns: 1fr; }
  .demo-reveal.demo-layout-soft .soft-stage { perspective: none; }
  .soft-mac { transform: none !important; width: 100%; max-width: 460px; }
}

@media (prefers-reduced-motion: reduce) {
  .demo-glow, .demo-cta-zone::before { animation: none; }
  .demo-glass-btn, .demo-chev, .demo-reveal, .demo-replay, .demo-glow { transition: none; }
  .demo-bubble, .demo-review, .demo-typing { opacity: 1; transform: none; animation: none; }
  /* MacBook: no tilt, everything visible immediately (JS sets final state) */
  .soft-mac { transform: none !important; }
  .soft-view, .soft-line, .soft-inv-tot, .soft-toast, .soft-bar, .soft-nav, .soft-sync, .soft-sync-dot, .soft-pay-target { transition: none !important; }
}
