/* ==========================================================================
   ORIXE STUDIO — index-revamp.html visual overhaul
   --------------------------------------------------------------------------
   Loaded AFTER style.css in the COPY only. Re-skins the existing DOM with a
   new "kinetic editorial" design language inspired by the Codrops typography
   collection (see codrops/tag/typography — 6 pages):

     · Type shuffle / scramble        (TypeShuffleAnimation, 2023)
     · Blurry text reveal on scroll   (ScrollBlurTypography, 2024)
     · On-scroll char/word reveals    (OnScrollTypographyAnimations, 2023-25)
     · CSS-only marquee + outline type(CSSMarqueeMenu, 2020)
     · Evervault gradient-mask hover  (AnimatedCodeBackground, 2023)
     · Staggered 3D grid entrance     (Staggered3DGridAnimations, 2024)
     · Terminal hover typography      (LineTextHoverAnimations, 2024)
     · Film grain overlay             (CSSMarqueeMenu / OnScrollTextHighlight)
     · Custom cursor                  (TypographyMotion, 2020 / 3DLetters, 2021)

   No text or markup of the original index.html is modified: every effect
   here is pure CSS (pseudo-element text stays out of the DOM) or a visual
   class added at runtime by revamp.js. All `data-lang-*` content, headings,
   links, forms, SEO/geo schema and JSON-LD are untouched.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. DESIGN TOKENS
   -------------------------------------------------------------------------- */
:root {
    /* Override the original palette to a deep "insight pad" + aurora field */
    --rv-bg: #060609;
    --rv-bg-2: #0a0a12;
    --rv-ink: #f3f1ff;
    --rv-mut: rgba(228, 224, 248, .62);
    --rv-dim: rgba(228, 224, 248, .38);
    --rv-vio: #a78bfa;
    --rv-cy:  #67e8f9;
    --rv-pin: #f0abfc;
    --rv-grad: linear-gradient(92deg, var(--rv-vio) 0%, var(--rv-cy) 48%, var(--rv-pin) 100%);
    --rv-grad-soft: linear-gradient(92deg, rgba(167,139,250,.85), rgba(103,232,249,.85) 48%, rgba(240,171,252,.85));

    /* Typography: Syne for display (kinetic editorial), Manrope body stays,
       IBM Plex Mono for terminal accents. style.css font vars are re-pointed. */
    --font-display: 'Syne', 'Unbounded', sans-serif;
    --font-body: 'Manrope', 'Inter', sans-serif;
    --font-ui: 'Space Grotesk', 'Plus Jakarta Sans', sans-serif;
    --rv-font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;
    --rv-mono: var(--rv-font-mono);

    --rv-m1: "VÍDEO CORPORATIVO ✦ DRONES ✦ 3D & VFX ✦ IA ✦ DESARROLLO WEB ✦ FOTOGRAFÍA ✦ EVENTOS ✦ ";
    --rv-m2: "ORIXE STUDIO ✦ CALDAS DE REIS ✦ GALICIA ✦ ORIGEN ✦ ";

    --background-color: var(--rv-bg);
    --primary-color: #050507;
    --text-color: var(--rv-ink);
    --accent-color: var(--rv-ink);
    --card-background: rgba(255, 255, 255, .035);
    --glass-background: rgba(14, 14, 22, .62);
    --glass-border-color: rgba(255, 255, 255, .09);
    --border-color: rgba(255, 255, 255, .09);
}

/* --------------------------------------------------------------------------
   2. PAGE ATMOSPHERE — background field, aurora, selection
   -------------------------------------------------------------------------- */
html { scrollbar-color: #3b3650 #060609; }

/* Aurora field — painted directly on <body> with background-attachment: fixed.
   Static and layer-free (no animated fixed pseudo-element = no per-frame
   repaint of a viewport-size layer). */
body {
    background-color: var(--rv-bg);
    color: var(--rv-ink);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    background-image:
        radial-gradient(38% 34% at 14% 10%, rgba(124, 58, 237, .17), transparent 70%),
        radial-gradient(34% 30% at 86% 6%,  rgba(34, 211, 238, .13), transparent 70%),
        radial-gradient(30% 26% at 84% 88%, rgba(236, 72, 153, .10), transparent 70%),
        radial-gradient(42% 40% at 10% 88%, rgba(99, 102, 241, .13), transparent 70%);
    background-attachment: fixed;
}

::selection { background: rgba(167, 139, 250, .45); color: #fff; }

/* Scrollbar */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: #08080d; }
::-webkit-scrollbar-thumb {
    background: linear-gradient(180deg, #4c3d8f, #1f6f85);
    border-radius: 8px;
    border: 2px solid #08080d;
}

/* --------------------------------------------------------------------------
   3. TYPOGRAPHY — single big voice
   -------------------------------------------------------------------------- */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: -0.025em;
}

.section-title {
    font-size: clamp(2.1rem, 5.4vw, 4.2rem);
    font-weight: 800;
    letter-spacing: -0.02em;
    line-height: 1.04;
    position: relative;
    padding-bottom: .55em;
    color: var(--rv-ink);
}
.section-title::after {
    content: '';
    display: block;
    width: 64px;
    height: 3px;
    margin: .35em auto 0;
    border-radius: 3px;
    background: var(--rv-grad);
    transform: scaleX(0);
    transform-origin: center;
    transition: transform 1s cubic-bezier(.16, 1, .3, 1) .45s;
}
.section-title.rv-in::after { transform: scaleX(1); }

.section-subtitle {
    font-family: var(--font-body);
    color: var(--rv-mut);
    font-size: 1.12rem;
    letter-spacing: 0;
    max-width: 640px;
}

/* Kickers in mono (terminal accent) */
.press-kicker,
.product-badge {
    font-family: var(--rv-font-mono) !important;
    letter-spacing: .34em !important;
    text-transform: uppercase;
}
.product-badge {
    background: linear-gradient(90deg, rgba(167,139,250,.14), rgba(103,232,249,.12));
    border: 1px solid rgba(167, 139, 250, .35);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* --------------------------------------------------------------------------
   4. SPLIT-TEXT ANIMATION PRIMITIVES (applied by revamp.js)
   -------------------------------------------------------------------------- */
.rv-w, .rv-c { display: inline-block; }

/* Chars: blur + rise + rotate into place (ScrollBlurTypography / fx2) */
.rv-js .rv-reveal.rv-out .rv-c {
    opacity: 0;
    transform: translateY(85%) rotate(6deg);
    filter: blur(10px) brightness(15%);
}
.rv-js .rv-reveal.rv-in .rv-c {
    opacity: 1;
    transform: none;
    filter: blur(0) brightness(100%);
    transition:
        opacity .9s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 24ms),
        transform 1.1s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 24ms),
        filter 1.1s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 24ms);
}
/* Words: softer version for paragraphs */
.rv-js .rv-reveal-w.rv-out .rv-w {
    opacity: 0;
    transform: translateY(26px);
    filter: blur(7px);
}
.rv-js .rv-reveal-w.rv-in .rv-w {
    opacity: 1;
    transform: none;
    filter: blur(0);
    transition:
        opacity .9s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 22ms),
        transform .9s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 22ms),
        filter .9s cubic-bezier(.16, 1, .3, 1) calc(var(--i) * 22ms);
}

/* Hero title: OUTLINE base (always legible, zero JS dependency, works on
   touch/no-hover the same as with a mouse) + a cursor-reactive gradient
   halo layered on top — see .rv-hero-fill below and buildHeroHalo() in
   revamp.js. Replaces the old static gradient-clip fill. text-stroke on
   transparent-fill spans keeps the same "nothing animates a background
   every frame" performance property the old comment called out. */
.rv-js .hero-title {
    position: relative;
    padding-bottom: .12em;
    /* Reserve space for 2 lines up front (line-height 1.04 from style.css,
       so 2 * 1.04 = 2.08em — scales with the same clamp() font-size the
       title itself uses, at every breakpoint). This makes whatever the
       title does internally while it settles (font swap, scramble, split
       spans measuring slightly differently than plain text) irrelevant to
       everything below it: the box stays the same size even if the content
       inside wobbles for a moment, so .hero-subtitle can't be pushed up and
       down by it. Belt-and-suspenders fix — it doesn't require knowing the
       exact cause, just makes that class of shift impossible. On very wide
       viewports where the title happens to fit on one line, this leaves a
       little empty space below it instead — a much smaller trade-off than
       visible jitter on every load. */
    min-height: 2.08em;
}
/* The base title carries letter-spacing:-0.035em (style.css), which at the
   full 4.4rem size is about -2.5px of overlap between glyphs. That's fine
   for solid text, but every character here is outlined, and -webkit-text-
   stroke centres the stroke on the glyph path — half of it paints OUTSIDE
   the letter. Tight tracking plus two half-strokes meant neighbouring
   letters' outlines ran into each other, which is the "lines going into
   other parts of the letter" effect. Neutralising the negative tracking
   gives the strokes room. */
.rv-js .hero-title { letter-spacing: 0; }
.rv-js .hero-title .rv-c {
    color: transparent;
    /* 1.1px, not 1.4: the inward half of a thick stroke also closes up the
       counters (the holes in e, o, g) at this weight. */
    -webkit-text-stroke: 1.1px rgba(243, 241, 255, .82);
    transition: opacity .8s cubic-bezier(.16,1,.3,1) calc(var(--i) * 16ms),
                transform .9s cubic-bezier(.16,1,.3,1) calc(var(--i) * 16ms);
}
.rv-js .hero-title:not(.rv-scrambled) .rv-c {
    opacity: 0;
    transform: translateY(70%) rotate(5deg);
    filter: blur(14px);
}

/* Ghost layer: same text, gradient-filled, revealed only within a small
   radius around the pointer through a masked radial gradient — a "torch"
   that shows the color exactly where the cursor passes over the outline.
   Absolute + inset:0 over its .hero-title parent, inheriting that parent's
   font metrics so glyphs line up on top of the outline underneath.
   aria-hidden + pointer-events:none in the markup/JS: purely decorative,
   never part of the accessible content or the click target. Fine-pointer
   only and skipped under reduced motion (see buildHeroHalo in revamp.js) —
   on touch, this layer exists but its mask never gets a real --hx/--hy, so
   it just never shows; the outline alone carries the design there. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
/* Wrapper: sized/positioned to exactly overlap .hero-title (inset:0) and
   masked as ONE unit, so the spotlight radius is correct regardless of how
   many characters/lines are inside it. */
.rv-hero-fill {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity .4s ease;
    -webkit-mask-image: radial-gradient(130px circle at var(--hx, -999px) var(--hy, -999px), #000 0%, transparent 72%);
    mask-image: radial-gradient(130px circle at var(--hx, -999px) var(--hy, -999px), #000 0%, transparent 72%);
}
.hero-title.rv-halo-active .rv-hero-fill { opacity: 1; }
/* Gradient fill on the CLONED characters specifically, not the wrapper:
   same Chromium constraint noted above for the outline — background-clip:
   text doesn't clip through transformed/filtered descendants, so it has to
   sit on the innermost .rv-c, same as the outline treatment does. Also
   cancels the stroke these clones would otherwise inherit from the
   .hero-title .rv-c rule above (no double-outline under the fill).
   Selector is 4 classes deep (.rv-js .hero-title .rv-hero-fill .rv-c) on
   purpose: the outline rule above it is .rv-js .hero-title .rv-c (3
   classes), and since the clones are ALSO nested under .hero-title, that
   rule matches them too. A same-or-lower-specificity override here would
   lose that fight regardless of source order and the stroke would win,
   which is exactly what happened before this got fixed. */
.rv-js .hero-title .rv-hero-fill .rv-c {
    -webkit-text-stroke: 0;
    background: var(--rv-grad);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
}
} /* end @supports */

/* Hero subtitle: word reveal */
.rv-js .hero-subtitle .rv-w {
    opacity: 0;
    transform: translateY(18px);
    filter: blur(6px);
    transition: opacity 1s cubic-bezier(.16,1,.3,1) calc(var(--i) * 26ms),
                transform 1s cubic-bezier(.16,1,.3,1) calc(var(--i) * 26ms),
                filter 1s cubic-bezier(.16,1,.3,1) calc(var(--i) * 26ms);
}
.rv-js .hero-subtitle .rv-w.rv-show {
    opacity: 1;
    transform: none;
    filter: blur(0);
}

/* Terminal hover translate per nav link (LineTextHoverAnimations) */
.rv-js .rv-hover .rv-c {
    display: inline-block;
    transition: transform .18s cubic-bezier(.22,1,.36,1) calc(var(--i) * 14ms),
                color .18s ease calc(var(--i) * 14ms);
}
.rv-js .rv-hover.rv-over .rv-c {
    transform: translateY(-4px);
    color: #dcd6ff;
}

/* --------------------------------------------------------------------------
   5. HEADER & NAV — intentionally UNCHANGED
   The user requested the navigation bar stay pixel-identical to the original
   site. No visual rules from style.css are overridden here; the root
   variables are re-scoped below so the pill, links, underline, CTA,
   language button and dropdown render exactly like the original.
   -------------------------------------------------------------------------- */
.main-header {
    --font-ui: 'Plus Jakarta Sans', 'Inter', sans-serif;
    --accent-color: #ffffff;
    --text-color: #f5f5f7;
    --glass-background: rgba(28, 28, 30, 0.6);
    --glass-border-color: rgba(255, 255, 255, 0.08);
    --border-color: rgba(255, 255, 255, 0.08);
    --background-color: #000000;
    color: #f5f5f7;
}

/* --------------------------------------------------------------------------
   6. HERO — giant outline word, vignette, scroll cue
   -------------------------------------------------------------------------- */
.hero-section { min-height: 100svh; }

/* Nudge the spinning logo up a little so it's not glued to the title below
   it — transform only (no margin/gap change), so it doesn't touch layout or
   push .hero-title down; a pure visual offset. */
.hero-content img { filter: drop-shadow(0 0 34px rgba(167, 139, 250, .42)); transform: translateY(-16px); }

.hero-video { filter: saturate(.9) contrast(1.06) brightness(.92); }

.hero-overlay {
    background:
        radial-gradient(120% 95% at 50% 8%, rgba(6, 6, 10, .30), rgba(6, 6, 10, .80) 80%),
        linear-gradient(180deg, rgba(6, 6, 10, .62) 0%, rgba(6, 6, 10, .38) 42%, rgba(6, 6, 10, .94) 100%);
}

/* Massive stroked word behind content (CSS content: never in the DOM) */
.hero-section::before {
    content: "ORIXE";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -46%);
    z-index: 0;
    font-family: var(--font-display);
    font-weight: 800;
    font-size: clamp(6rem, 24vw, 22rem);
    letter-spacing: -0.02em;
    line-height: 1;
    white-space: nowrap;
    color: transparent;
    -webkit-text-stroke: 1px rgba(196, 181, 253, .16);
    pointer-events: none;
    user-select: none;
}

/* Scroll cue */
.hero-content::after {
    content: "▼ ▼ ▼";
    position: absolute;
    bottom: -84px;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--rv-font-mono);
    font-size: 11px;
    letter-spacing: .42em;
    color: var(--rv-dim);
    animation: rvBob 2.4s ease-in-out infinite;
}
@keyframes rvBob {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50%      { transform: translateX(-50%) translateY(7px); }
}

@media (max-width: 992px) { .hero-content::after { display: none; } }

/* --------------------------------------------------------------------------
   7. MARQUEE RIBBONS (outline type, CSS-only loop)
   -------------------------------------------------------------------------- */
.rv-marquee {
    position: relative;
    overflow: hidden;
    width: 100%;
    padding: 1.1rem 0;
    border-top: 1px solid rgba(255, 255, 255, .07);
    border-bottom: 1px solid rgba(255, 255, 255, .07);
    background: rgba(255, 255, 255, .012);
}
.rv-marquee::before {
    display: block;
    width: max-content;
    white-space: nowrap;
    content: var(--rv-m1) var(--rv-m1);
    font-family: var(--font-display);
    font-weight: 800;
    font-size: clamp(1.35rem, 3.6vw, 2.6rem);
    /* Same outlined-text caveat as the hero title: the stroke's inward half
       eats into the letterforms, so this needs looser tracking than solid
       text would. */
    letter-spacing: .06em;
    text-transform: uppercase;
    color: transparent;
    -webkit-text-stroke: 0.9px rgba(203, 191, 255, .34);
    animation: rvMarquee 46s linear infinite;
    will-change: transform; /* promotes to its own compositor layer; transform-only anim = cheap */
}
.rv-marquee--rev::before { animation-direction: reverse; }
@keyframes rvMarquee {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

/* --------------------------------------------------------------------------
   8. SERVICES — Evervault code-hover, numbered categories, 3D grid entrance
   -------------------------------------------------------------------------- */
#services { padding-top: 7rem; }

.category-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: clamp(1.25rem, 2.3vw, 1.75rem);
    letter-spacing: -0.01em;
    border-bottom: 1px solid rgba(255, 255, 255, .10);
    padding-bottom: 1rem;
    position: relative;
}

/* Category headings ("Desarrollo Web y Diseño", "Producción Audiovisual"...):
   per-word 3D flip-in, triggered by the SAME .rv-in class the underline
   below already uses, so both land together. Distinct from the per-char
   blur treatment on .section-title so the two heading tiers read as
   different moments rather than a repeat of the same effect.
   perspective lives on the inner <span>, not the <h3>: that span is the
   actual parent of the .rv-w words once splitInto() runs (icon and text
   are separate elements under .category-title). */
.category-title span { perspective: 900px; }
.rv-js .category-title .rv-w {
    transform-origin: 50% 100%;
    transition: transform .85s cubic-bezier(.16,1,.3,1) calc(var(--i) * 70ms),
                opacity .6s ease calc(var(--i) * 70ms);
}
.rv-js .category-title:not(.rv-in) .rv-w {
    opacity: 0;
    transform: rotateX(-85deg) translateY(14px);
}
.rv-js .category-title.rv-in .rv-w {
    opacity: 1;
    transform: rotateX(0) translateY(0);
}
/* Icon: a quick scale+rotate pop, slightly ahead of the words */
.rv-js .category-title i {
    display: inline-block;
    transition: transform .7s cubic-bezier(.34,1.56,.64,1) .05s,
                opacity .5s ease .05s;
}
.rv-js .category-title:not(.rv-in) i {
    opacity: 0;
    transform: scale(.4) rotate(-35deg);
}
.rv-js .category-title.rv-in i {
    opacity: 1;
    transform: scale(1) rotate(0);
}
.category-title::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -1px;
    width: 72px;
    height: 1px;
    background: var(--rv-grad);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .9s cubic-bezier(.16,1,.3,1) .2s;
}
.rv-in .category-title::after,
.category-title.rv-in::after { transform: scaleX(1); }
/* "01 /" "02 /" numbered prefix removed per client request — back to the
   plain icon + label the original design used. */

/* Cards: graphite panels, gradient icon fill, code reveal on hover */
.service-grid { perspective: 1400px; }

.service-card {
    background: linear-gradient(168deg, rgba(255,255,255,.055), rgba(255,255,255,.012) 55%);
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 22px;
    box-shadow: 0 10px 34px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255,255,255,.05);
    transform: none;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
}
.service-card .card-content,
.service-card .card-details,
.service-card .card-indicator { position: relative; z-index: 3; }

/* Line the expand chevrons up along the bottom of every card. The grid
   already stretches cards in a row to equal height, but each card's text
   is a different length, so the chevron sat right under the copy and ended
   up at a different height in each one. Making the card a flex column and
   giving the chevron margin-top:auto pushes it to the floor of whatever
   height the card ended up with. .card-details keeps its max-height
   expand transition — it's still just a normal block in the column. */
.service-card {
    display: flex;
    flex-direction: column;
}
.service-card .card-indicator { margin-top: auto; }

.service-card:hover {
    transform: translateY(-6px);
    border-color: rgba(167, 139, 250, .42);
    background: linear-gradient(168deg, rgba(124,58,237,.14), rgba(34,211,238,.05));
    box-shadow: 0 26px 60px rgba(0, 0, 0, .55), 0 0 0 1px rgba(167,139,250,.18), 0 0 44px rgba(124,58,237,.14);
}

/* 3D staggered entrance (rotateX + skew + rise + blur -> clear) */
.rv-js .rv-grid .service-card {
    animation: rvCardIn 1.15s cubic-bezier(.19, 1, .22, 1) backwards;
    animation-delay: calc(var(--i) * 95ms);
}
.rv-js .rv-grid.rv-in .service-card { animation-play-state: running; }
@keyframes rvCardIn {
    from {
        opacity: 0;
        transform: perspective(1400px) rotateX(34deg) rotateZ(var(--rz, -2deg)) translateY(90px) skewX(-12deg);
        filter: blur(9px) brightness(.5);
    }
    to   {
        opacity: 1;
        transform: perspective(1400px) rotateX(0) rotateZ(0) translateY(0) skewX(0);
        filter: blur(0) brightness(1);
    }
}
/* Note: `backwards` (not `both`) so the fill never sticks — the hover
   transform of the card keeps working after the entrance finishes. */

/* Themed hover layer (per-topic glyph cloud, accent-colored) + soft glow.
   Driven by revamp.js via --x/--y and --theme-rgb. It sits BEHIND
   .card-content (z-index 3) so the card text is always readable. */
.rv-code {
    position: absolute;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    font-family: var(--rv-font-mono);
    font-size: 9px;
    line-height: 1.35;
    letter-spacing: .08em;
    color: rgba(var(--theme-rgb, 222, 216, 255), .72);
    white-space: pre-wrap;
    word-break: break-all;
    opacity: 0;
    transition: opacity .35s ease;
    pointer-events: none;
    -webkit-mask-image: radial-gradient(260px circle at var(--x, 50%) var(--y, 50%), #000 12%, rgba(0,0,0,.25) 48%, transparent 72%);
    mask-image: radial-gradient(260px circle at var(--x, 50%) var(--y, 50%), #000 12%, rgba(0,0,0,.25) 48%, transparent 72%);
}
.service-card:hover .rv-code { opacity: 1; }

.rv-spot {
    position: absolute;
    inset: 0;
    z-index: 0;
    opacity: 0;
    transition: opacity .35s ease;
    pointer-events: none;
    background: radial-gradient(280px circle at var(--x, 50%) var(--y, 50%),
                rgba(var(--theme-rgb, 139, 92, 246), .22), rgba(var(--theme-rgb, 139, 92, 246), .06) 48%, transparent 70%);
}
.service-card:hover .rv-spot { opacity: 1; }

/* Per-card 3D wireframe (see buildCardWireframes in revamp.js) — genuine
   3D, not a flat icon with a fake-perspective trick: perspective on the
   box, transform-style:preserve-3d on the scene, and each "line" is a real
   <div> positioned in 3D space between two real points. Deliberately low
   opacity (texture, not something to read) and pointer-events:none, so it
   never interferes with reading the card or clicking it — the pointer
   listeners that drive the drone's tilt live on .service-card itself. */
.rv-3d {
    position: absolute;
    right: 4px;
    bottom: 4px;
    width: 190px;
    height: 190px;
    display: grid;
    place-items: center;
    perspective: 900px;
    opacity: 0;
    transition: opacity .45s ease;
    /* Alpha is baked into the LINE COLOUR (was .95 with the container held
       at opacity:.5). A group opacity below 1 on a box with ~200 children
       forces the browser to rasterise the whole subtree into an offscreen
       buffer and composite it every single frame — the single most
       expensive thing here, and the main reason it stuttered on phones.
       Ending the fade at opacity:1 removes that buffer entirely while
       looking the same. */
    color: rgba(var(--theme-rgb, 222, 216, 255), .48);
    pointer-events: none;
    z-index: 0;
    /* Isolates this subtree's layout from the rest of the card, so moving
       ~200 absolutely-positioned edges can't trigger layout work outside
       the box. NOT `contain: paint` — that would clip the wireframe, which
       deliberately overflows its 190px box in 3D. */
    contain: layout;
}
/* Only on hover, same as the code layer — it's an accent that rewards
   exploring a card, not permanent furniture. */
.service-card:hover .rv-3d { opacity: 1; }
.rv-3d-scene {
    position: relative;
    width: 0;
    height: 0;
    transform-style: preserve-3d;
}
/* Every edge starts at the scene's origin (0,0,0 in the vertex data);
   translate3d/rotateY/rotateZ (set inline, per-edge, in JS) take it from
   there to its real 3D position. The scene is a 0x0 point centered by the
   parent grid, so top/left 0 here IS the shape's center — no offset math
   needed, and the JS assumes exactly this. */
.rv-edge {
    position: absolute;
    top: 0;
    left: 0;
    height: 1.5px;
    background: currentColor;
    border-radius: 2px;
}
/* Glow only on the card you're actually on. A box-shadow per edge across
   every built shape meant up to ~1,600 shadowed elements to paint; scoped
   to :hover it's one card's worth (~210 max) and only while visible.
   4px rather than 6px: blur cost scales with radius and at this line
   thickness the difference isn't perceptible, but it's ~200 shadows being
   painted every frame the shape rotates. */
.service-card:hover .rv-edge { box-shadow: 0 0 4px currentColor; }
/* Graphic design gem: nobody's pointer is on it most of the time, so it
   gets a slow idle turntable spin instead — still genuinely 3D, just not
   pointer-driven. The drone (.rv-3d-pointer) gets its rotation from JS
   instead (see buildCardWireframes), reacting to the mouse as asked. */
/* Pencil that rides the sphere's draw (Graphic Design card). Its own
   preserve-3d group so it can travel independently of the shape. */
.rv-pencil {
    position: absolute;
    top: 0;
    left: 0;
    transform-style: preserve-3d;
    opacity: 0;
}
/* Readable brand mark at the centre of the sphere. Sits on .rv-3d rather
   than inside the rotating scene, so it stays square to the viewer while
   the sphere turns around it. Fades in after the rings have closed. */
/* Sub-assembly wrapper (gears, orbits): a 0x0 point that can be rotated as
   a rigid body, with its pivot moved onto the part's own centre by
   transform-origin (set inline from shape.groupOrigin). */
.rv-grp {
    position: absolute;
    top: 0;
    left: 0;
    transform-style: preserve-3d;
}
/* Camera-flash wash. Sits over the whole card (behind the text, which is at
   z-index 3) so a shutter fire lights the card rather than just the corner
   the wireframe lives in. Opacity is driven per-frame from the shape's
   animate(); it's 0 the rest of the time, so it costs nothing. */
.rv-flash {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    border-radius: inherit;
    background: radial-gradient(120% 90% at 72% 78%,
        rgba(var(--theme-rgb, 248, 113, 113), .85),
        rgba(255, 255, 255, .35) 38%,
        transparent 72%);
}
@keyframes rvGearCW  { to { transform: rotate(360deg); } }
@keyframes rvGearCCW { to { transform: rotate(-360deg); } }
/* Propellers spin about a VERTICAL axis, so rotateY — not the flat rotate()
   the gears use. Needs transform-origin with a Z component too (set inline
   from shape.groupOrigin) or they'd swing around the airframe centre. */
@keyframes rvPropCW  { to { transform: rotateY(360deg); } }
@keyframes rvPropCCW { to { transform: rotateY(-360deg); } }
/* Film reels lie flat on the camera top, so they turn about Y as well. */
@keyframes rvReel    { to { transform: rotateY(360deg); } }

.rv-3d-logo {
    position: absolute;
    top: 50%;
    left: 50%;
    /* Show ONLY the "orixe" half of the wordmark. The gap between the two
       words was measured by rasterising the SVG and scanning for the empty
       column band: it sits at 43.9%-47.6% of the width, so cropping at 46%
       cuts cleanly between them. The extra translateX re-centres what's
       left, since clipping doesn't change the element's own box. */
    width: 176px;
    height: auto;
    clip-path: inset(0 54% 0 0);
    transform: translate(-50%, -50%) translateX(27%) scale(.82);
    opacity: 0;
    /* No transition-delay here: the "wait for the sphere to close" timing
       is done in JS (see drawIn) instead. Relying on a CSS delay meant the
       element was created and given .rv-on in the same tick, so the browser
       had no committed start value to transition FROM and the property just
       stayed at 0. */
    transition: opacity .5s ease, transform .7s cubic-bezier(.22,1,.36,1);
    filter: drop-shadow(0 0 10px rgba(var(--theme-rgb, 222, 216, 255), .55));
}
.rv-3d-logo.rv-on { opacity: .95; transform: translate(-50%, -50%) translateX(27%) scale(1); }

/* Idle turntable spin — ONLY while the card is hovered. Running it
   unconditionally kept 6 scenes (769 edge divs) animating forever, off
   screen and at opacity 0: continuous compositor work for something nobody
   could see. Scoped to :hover it costs nothing until you're looking at it. */
.service-card:hover .rv-3d-idle .rv-3d-scene { animation: rv3dSpin 16s linear infinite; }
@keyframes rv3dSpin {
    from { transform: rotateX(16deg) rotateY(0deg); }
    to   { transform: rotateX(16deg) rotateY(360deg); }
}

/* Icon: gradient fill + glow */
.card-icon {
    background: var(--rv-grad-soft);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    filter: drop-shadow(0 0 16px rgba(167, 139, 250, .4));
}

.card-title { letter-spacing: -0.01em; }
.card-description { color: var(--rv-mut); }

.card-details { background-color: rgba(5, 5, 9, .5); }
.card-details ul li { border-color: rgba(255, 255, 255, .08); }
.card-details ul li a { color: var(--rv-cy) !important; }

/* --------------------------------------------------------------------------
   9. PRODUCTS (LocalNode / GastroNode)
   -------------------------------------------------------------------------- */
.products-section { position: relative; }

.product-video-wrapper {
    border-radius: 26px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, .55), 0 0 60px rgba(124, 58, 237, .10);
}

.product-glow {
    background: radial-gradient(circle at 50% 42%, rgba(167, 139, 250, .16) 0%, transparent 64%);
    animation: rvPulse 5.5s ease-in-out infinite;
}
@keyframes rvPulse {
    0%, 100% { opacity: .7; transform: translate(-50%, -50%) scale(1); }
    50%      { opacity: 1;  transform: translate(-50%, -50%) scale(1.12); }
}

.p-feature {
    font-family: var(--font-body);
    color: var(--rv-ink);
    border-left: 1px solid rgba(255,255,255,.12);
    padding-left: 1rem;
    transition: border-color .4s ease, transform .4s ease;
}
.p-feature:hover { border-color: var(--rv-vio); transform: translateX(4px); }
.p-feature i {
    background: var(--rv-grad-soft);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* Product headings: split reveal + word fill */
.products-section .section-title {
    font-size: clamp(2.4rem, 6vw, 4.8rem);
    font-weight: 800;
}

/* --------------------------------------------------------------------------
   10. ABOUT — paper block + ink marker underlines
   -------------------------------------------------------------------------- */
#about {
    background-color: #f7f5ef;
    background-image:
        radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, .9), transparent 60%),
        radial-gradient(90% 70% at 50% 100%, rgba(214, 205, 235, .34), transparent 70%);
}
#about .section-title,
#about .section-description { color: #17151f; }
#about .section-title { color: #17151f; }
#about .section-title::after { background: linear-gradient(90deg, #7c3aed, #0891b2, #db2777); }

.section-description {
    color: #3c3849;
    font-size: 1.06rem;
    line-height: 1.75;
    text-align: center;
}
/* (the old marker/ink sweep behind this paragraph was removed: it painted a
   violet band across the text and made it look offset) */

#about a { color: #7c3aed; text-decoration: none; border-bottom: 1px solid rgba(124, 58, 237, .35); transition: color .3s ease, border-color .3s ease; }
#about a:hover { color: #0891b2; border-color: #0891b2; }

.team-member-name { color: #17151f; }
.team-member-title { color: #4a4558; }

/* --------------------------------------------------------------------------
   11. REVIEWS
   -------------------------------------------------------------------------- */
.review-card {
    background: linear-gradient(168deg, rgba(255,255,255,.05), rgba(255,255,255,.012));
    border: 1px solid rgba(255, 255, 255, .09);
    border-radius: 18px;
    box-shadow: 0 14px 40px rgba(0, 0, 0, .35);
}
/* Thin gradient tick above each review (replaces the old quote glyph) */
.review-card::before {
    content: '';
    display: block;
    width: 34px;
    height: 2px;
    border-radius: 2px;
    margin-bottom: .9rem;
    background: var(--rv-grad);
}
.review-card:hover {
    transform: translateY(-6px);
    border-color: rgba(240, 171, 252, .35);
    box-shadow: 0 22px 54px rgba(0, 0, 0, .5), 0 0 34px rgba(236, 72, 153, .12);
}
/* .reviewer-name is an <h4> in the markup, so without this it would pick up
   the global `h1..h6 { font-family: var(--font-display) }` rule below and
   render in Syne — not what "the original font" means here. `inherit`
   makes it fall through to the normal body font (Manrope) like the rest of
   the review text, same as the pre-revamp site. Size/weight/color are
   untouched, from style.css's own .reviewer-name rule. */
.reviewer-name { font-family: inherit; }
.review-text { color: var(--rv-mut); }

/* --------------------------------------------------------------------------
   12. FAQ — terminal panel
   -------------------------------------------------------------------------- */
.faq-item {
    background: rgba(255, 255, 255, .03);
    border: 1px solid rgba(255, 255, 255, .08);
    border-left: 2px solid transparent;
    border-radius: 14px;
    transition: border-color .35s ease, border-left-color .35s ease, box-shadow .35s ease;
}
.faq-item:hover {
    background: rgba(255, 255, 255, .05);
    border-color: rgba(167, 139, 250, .3);
}
.faq-item.active {
    border-color: rgba(167, 139, 250, .35);
    border-left-color: var(--rv-vio);
    box-shadow: 0 18px 44px rgba(0, 0, 0, .4), 0 0 30px rgba(124, 58, 237, .10);
}
.faq-question {
    font-family: var(--rv-font-mono);
    font-size: .98rem;
    letter-spacing: .01em;
    text-transform: none;
    color: var(--rv-ink);
}
.faq-question > span::before {
    content: "> ";
    font-family: var(--rv-font-mono);
    color: transparent;
    background: var(--rv-grad);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    font-weight: 700;
}
.faq-icon {
    background: var(--rv-grad-soft);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}
.faq-answer p { color: var(--rv-mut); }
.faq-answer strong { color: var(--rv-ink); }

/* --------------------------------------------------------------------------
   13. CONTACT
   -------------------------------------------------------------------------- */
.contact-option {
    border-radius: 14px;
    background: linear-gradient(168deg, rgba(255,255,255,.05), rgba(255,255,255,.015));
    border: 1px solid rgba(255, 255, 255, .1);
    font-family: var(--font-ui);
}
.contact-option:hover {
    transform: translateY(-4px);
    border-color: rgba(167, 139, 250, .5);
    background: rgba(167, 139, 250, .08);
    box-shadow: 0 16px 36px rgba(0, 0, 0, .4), 0 0 24px rgba(124, 58, 237, .18);
}
.contact-option.whatsapp { background-color: #17b45e; border-color: rgba(37, 211, 102, .5); }
.contact-option.whatsapp:hover { background-color: #0f9c4f; }
.contact-option.call { background-color: #1187b8; border-color: rgba(37, 163, 216, .5); }
.contact-option.call:hover { background-color: #0d6f99; }

.contact-separator { font-family: var(--rv-font-mono); letter-spacing: .22em; text-transform: uppercase; font-size: .78rem; color: var(--rv-dim); }
.contact-separator::before, .contact-separator::after { background: linear-gradient(90deg, transparent, rgba(255,255,255,.25)); }

.contact-person-name { letter-spacing: .01em; }

/* Form: graphite fields, gradient focus */
.of-field input,
.of-field textarea {
    background: rgba(255, 255, 255, .035);
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: 12px;
}
.of-field input:focus,
.of-field textarea:focus {
    border-color: rgba(167, 139, 250, .6);
    background: rgba(167, 139, 250, .05);
    box-shadow: 0 0 0 3px rgba(167, 139, 250, .14), 0 0 24px rgba(124, 58, 237, .12);
}
.of-field label { font-family: var(--rv-font-mono); letter-spacing: .12em; text-transform: uppercase; font-size: 11px; color: var(--rv-dim); }
.of-legal a { color: var(--rv-cy) !important; }
/* White, like every other page's .btn-primary — client asked for it to
   match, not stand out with the gradient. .btn-primary already gives white
   bg / black text via --accent-color / --primary-color, but revamp's :root
   re-tints --accent-color to a near-white lavender (--rv-ink) for the rest
   of the page, so this pins it back to pure white specifically here. */
.of-submit {
    background: #ffffff !important;
    color: #000000 !important;
    -webkit-text-fill-color: #000000 !important;
}
.of-submit:hover { background: transparent !important; color: #ffffff !important; -webkit-text-fill-color: #ffffff !important; border-color: #ffffff; }

/* --------------------------------------------------------------------------
   14. FOOTER — intentionally UNCHANGED
   The user requested the footer stay pixel-identical to the original site.
   No visual rule from style.css is overridden here; the root variables are
   re-scoped so background, borders, link colors and social icons render
   exactly like the original.
   -------------------------------------------------------------------------- */
.main-footer {
    --primary-color: #000000;
    --accent-color: #ffffff;
    --text-color: #f5f5f7;
    --border-color: rgba(255, 255, 255, 0.08);
    --background-color: #000000;
    color: #f5f5f7;
}

/* --------------------------------------------------------------------------
   15. FLOATING UI — progress bar, cursor, loader, badge
   -------------------------------------------------------------------------- */
/* Scroll progress */
.rv-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    z-index: 12000;
    pointer-events: none;
    background: transparent;
    transform-origin: 0 50%;
    transform: scaleX(0);
    background: var(--rv-grad);
}

/* Custom cursor (fine pointers only; injected by revamp.js)
   NOTE: no mix-blend-mode here — blending forces the browser to composite
   the whole viewport against the pointer every frame. Plain RGBA instead. */
.rv-cursor {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 13000;
    pointer-events: none;
    will-change: transform;
}
.rv-cursor-dot {
    position: absolute;
    top: 0;
    left: 0;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(240, 236, 255, .9);
    transform: translate(-50%, -50%);
}
.rv-cursor-ring {
    position: absolute;
    top: 0;
    left: 0;
    width: 34px;
    height: 34px;
    border: 1.5px solid rgba(240, 236, 255, .55);
    border-radius: 50%;
    transform: translate(-50%, -50%);
    transition: transform .3s cubic-bezier(.22, 1, .36, 1), opacity .3s ease;
}
.rv-cursor.is-link .rv-cursor-ring { transform: translate(-50%, -50%) scale(1.65); opacity: .85; }
.rv-cursor.is-down .rv-cursor-ring { transform: translate(-50%, -50%) scale(.8); }

.rv-cursor-on,
.rv-cursor-on * { cursor: none !important; }
.rv-cursor-on input,
.rv-cursor-on textarea,
.rv-cursor-on select { cursor: text !important; }

/* Intro loader */
.rv-loader {
    position: fixed;
    inset: 0;
    z-index: 14000;
    display: grid;
    place-items: center;
    background: #060609;
    transition: opacity .55s ease .15s, transform .8s cubic-bezier(.76, 0, .24, 1);
}
.rv-loader.rv-done { opacity: 0; transform: translateY(-6%); pointer-events: none; }
.rv-loader-inner { text-align: center; padding: 0 1.5rem; }
.rv-loader-brand {
    display: block;
    margin-bottom: 1.4rem;
}
.rv-loader-brand img {
    width: clamp(180px, 26vw, 250px);
    height: auto;
    display: block;
    margin: 0 auto;
    opacity: .92;
    filter: drop-shadow(0 0 26px rgba(167, 139, 250, .5));
    animation: rvLoaderLogo 2.4s ease-in-out infinite;
}
/* opacity-only animation (filter animation re-rasterizes the layer each frame) */
@keyframes rvLoaderLogo {
    0%, 100% { opacity: .8; }
    50%      { opacity: 1; }
}
.rv-loader-bar {
    display: block;
    width: min(240px, 64vw);
    height: 2px;
    margin: 0 auto;
    background: rgba(255, 255, 255, .09);
    border-radius: 2px;
    overflow: hidden;
    position: relative;
}
.rv-loader-bar::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 2px;
    background: var(--rv-grad);
    transform-origin: 0 50%;
    animation: rvLoad 1s cubic-bezier(.22, 1, .36, 1) forwards;
}
.rv-loader-meta {
    margin-top: 1.1rem;
    font-family: var(--rv-font-mono);
    font-size: 10.5px;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--rv-dim);
}
@keyframes rvLoad { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* --------------------------------------------------------------------------
   15b. CTA BUTTONS — "AURORA GLASS" (hero Ver Servicios + Discover
        LocalNode / GastroNode). Full override of the original .button-wrap
        system from style.css:
          · frosted-glass body (backdrop blur + saturate, inner highlights)
          · a light comet that orbits the border (transform-only, GPU cheap)
          · a diagonal sheen sweep that crosses the glass on a loop
          · a breathing radial aura behind the button
        Purely visual: markup and text untouched.
   -------------------------------------------------------------------------- */

/* Wrapper: reset the old pill/shadow scaffolding */
.button-wrap {
    border-radius: 999vw;
    background: transparent;
    pointer-events: none;
    transition: none;
}

/* ---------- Glass body ---------- */
.button-wrap button {
    all: unset;
    cursor: pointer;
    pointer-events: auto;
    position: relative;
    display: inline-block;
    overflow: hidden;               /* clips the sheen sweep */
    border-radius: 999vw;
    background:
        linear-gradient(160deg, rgba(255,255,255,.20) 0%, rgba(255,255,255,.06) 46%, rgba(255,255,255,.14) 100%);
    -webkit-backdrop-filter: blur(18px) saturate(180%);
    backdrop-filter: blur(18px) saturate(180%);
    border: 1px solid rgba(255, 255, 255, .30);
    box-shadow:
        0 12px 36px rgba(0, 0, 0, .45),
        inset 0 1px 0 rgba(255, 255, 255, .45),
        inset 0 -1px 0 rgba(0, 0, 0, .10);
    -webkit-tap-highlight-color: transparent;
    transform: translateZ(0);
    transition: transform .35s cubic-bezier(.22, 1, .36, 1),
                box-shadow .35s ease,
                border-color .35s ease,
                background .35s ease;
}

.button-wrap button:hover {
    transform: translateY(-2px) scale(1.03);
    background:
        linear-gradient(160deg, rgba(255,255,255,.26) 0%, rgba(255,255,255,.10) 46%, rgba(255,255,255,.18) 100%);
    border-color: rgba(255, 255, 255, .48);
    box-shadow:
        0 18px 46px rgba(0, 0, 0, .5),
        0 0 32px rgba(139, 92, 246, .28),
        inset 0 1px 0 rgba(255, 255, 255, .6),
        inset 0 -1px 0 rgba(0, 0, 0, .08);
}

.button-wrap button:active {
    transform: translateY(0) scale(.985);
    box-shadow:
        0 6px 18px rgba(0, 0, 0, .4),
        inset 0 2px 6px rgba(0, 0, 0, .18);
}

.button-wrap button:focus-visible {
    outline: 2px solid rgba(255, 255, 255, .8);
    outline-offset: 3px;
}

/* ---------- Label ---------- */
.button-wrap button span {
    position: relative;
    display: block;
    z-index: 3;
    user-select: none;
    -webkit-user-select: none;
    font-family: var(--font-ui);
    font-weight: 700;
    font-size: 1.02rem;
    letter-spacing: .02em;
    color: #fff;
    padding-inline: 1.9em;
    padding-block: 1.05em;
    text-shadow: 0 1px 8px rgba(0, 0, 0, .28);
    transition: letter-spacing .35s cubic-bezier(.22, 1, .36, 1);
}
.button-wrap button:hover span { letter-spacing: .075em; }
/* old white highlight pill inside the label — removed */
.button-wrap button span::after { content: none !important; }

/* ---------- Orbiting light ring (comet on the border) ---------- */
/* Animatable gradient angle (needed so the comet can travel ALONG the
   border instead of the ring shape rotating through the middle of the
   pill). Fallback: browsers without @property get a static gradient. */
@property --rv-ring-angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}

.button-wrap button::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1.5px;
    box-sizing: border-box;
    z-index: 4;
    pointer-events: none;
    background: conic-gradient(from var(--rv-ring-angle),
        transparent 0turn 0.52turn,
        rgba(167, 139, 250, .95) 0.62turn,
        rgba(103, 232, 249, .95) 0.72turn,
        rgba(240, 171, 252, .95) 0.80turn,
        rgba(255, 255, 255, .85) 0.86turn,
        transparent 0.93turn 1turn);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    /* ONLY the gradient angle animates — the ring never moves from the
       border, the comet just runs around the pill edge. */
    animation: rvRingTravel 3.6s linear infinite;
}
@keyframes rvRingTravel { to { --rv-ring-angle: 360deg; } }

/* ---------- Sheen sweep across the glass ---------- */
.button-wrap button::after {
    content: '';
    position: absolute;
    top: -55%;
    left: -85%;
    width: 48%;
    height: 210%;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(100deg,
        transparent 0%,
        rgba(255, 255, 255, .40) 50%,
        transparent 100%);
    transform: rotate(8deg) translateX(-80%);
    animation: rvSheen 5.2s cubic-bezier(.33, 0, .2, 1) infinite;
    will-change: transform;
}
@keyframes rvSheen {
    0%        { transform: rotate(8deg) translateX(-240%); }
    55%, 100% { transform: rotate(8deg) translateX(420%); }
}

/* ---------- Breathing aura (replaces the old .button-shadow) ---------- */
.button-shadow {
    position: absolute;
    inset: -8px;
    border-radius: 999vw;
    background: radial-gradient(58% 95% at 50% 50%,
        rgba(139, 92, 246, .50),
        rgba(34, 211, 238, .16) 52%,
        transparent 74%);
    filter: blur(14px);
    opacity: .6;
    animation: rvAuraPulse 3.8s ease-in-out infinite;
    pointer-events: none;
    z-index: -1;
    will-change: opacity;
    transition: none;
}
.button-shadow::after { content: none !important; }
@keyframes rvAuraPulse {
    0%, 100% { opacity: .45; transform: scale(.94); }
    50%      { opacity: .8;  transform: scale(1.07); }
}

/* Neutralize every old :has() state so the new design owns the states */
.button-wrap:has(button:hover) .button-shadow {
    filter: blur(14px);
    opacity: .9;
}
.button-wrap:has(button:hover) .button-shadow::after { content: none; }
.button-wrap:has(button:active) { transform: none; }
.button-wrap:has(button:active) button {
    /* same stroke we define on .button-wrap button:active, stated at the
       higher specificity the old :has() rule had */
    box-shadow: 0 6px 18px rgba(0, 0, 0, .4), inset 0 2px 6px rgba(0, 0, 0, .18);
}
.button-wrap:has(button:active) .button-shadow { filter: blur(14px); }
.button-wrap:has(button:active) .button-shadow::after { content: none; }
.button-wrap:has(button:active) span { text-shadow: none; }

/* Touch: no hover preview, keep everything rolling */
@media (hover: none) and (pointer: coarse) {
    .button-wrap button span::after,
    .button-wrap button:active span::after { content: none; }
    .button-wrap button:hover { transform: none; }
}

/* Smaller halo/typography on small screens */
@media (max-width: 480px) {
    .button-wrap button span { padding-inline: 1.5em; padding-block: .95em; font-size: .95rem; }
    .button-shadow { inset: -5px; }
}


/* Floating WhatsApp */
.floating-whatsapp { box-shadow: 0 10px 34px rgba(0, 0, 0, .55), 0 0 26px rgba(37, 211, 102, .35); }

/* Welcome modal */
.welcome-modal-content {
    background: rgba(12, 12, 20, .85);
    border: 1px solid rgba(167, 139, 250, .25);
    backdrop-filter: blur(18px) saturate(140%);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    border-radius: 18px;
}

/* --------------------------------------------------------------------------
   16. REDUCED MOTION — intentionally not handled.
       Client decision: every animation on this page should always play, on
       every device, regardless of the OS/browser "reduce motion" setting.
       revamp.js's REDUCED flag is hardcoded false for the same reason, so
       nothing here needs a @media (prefers-reduced-motion) block at all.
   -------------------------------------------------------------------------- */

/* Set by the IntersectionObserver in revamp.js on anything carrying an
   infinite animation once it scrolls out of view. Covers pseudo-elements and
   descendants because most of these loops live on ::before/::after (the
   marquee text, the CTA comet and sheen) or on a child (the press track
   inside .press-marquee). Purely a power/compositor saving — the element is
   off screen, so freezing it cannot be seen. */
.rv-off,
.rv-off::before,
.rv-off::after,
.rv-off *,
.rv-off *::before,
.rv-off *::after {
    animation-play-state: paused !important;
}

/* --------------------------------------------------------------------------
   17. TOUCH / MOBILE BUDGET
       The animations stay, but the expensive scenery around them goes. On a
       phone the wireframes were competing with effects that cost far more
       than they add at that size:

       · backdrop-filter on the cards is the big one. Every one of the 14
         cards was asking the compositor to snapshot and gaussian-blur the
         page behind it (blur(20px) from style.css plus blur(14px) here),
         every frame the 3D shape on top of it moved. Mobile GPUs choke on
         stacked backdrop filters; a flat translucent panel is visually
         near-identical at phone size.
       · a box-shadow on each of ~200 edges is ~200 extra blur passes per
         frame. The lines are still bright without it.
       · the glyph-cloud layer re-lays out hundreds of characters behind a
         radial mask, and it's a hover affordance nobody sees on touch.
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
    .service-card {
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
        background: linear-gradient(168deg, rgba(255,255,255,.06), rgba(255,255,255,.02) 55%);
    }
    .service-card:hover .rv-edge { box-shadow: none; }
    /* .rv-code stays: hiding it didn't buy measurable performance and the
       glyph cloud is part of the look. Only .rv-spot goes, because it's a
       pointer-tracked highlight and there's no pointer to track. */
    .rv-spot { display: none; }
    /* slightly smaller stage: fewer pixels to composite per frame */
    .rv-3d { width: 150px; height: 150px; }

    /* No continuous scene rotation on touch. Re-aiming a preserve-3d scene
       makes the compositor re-transform every one of its ~200 children, and
       iOS rasterises each 3D-transformed child separately — so a permanent
       16s turntable was ~200 layers being recomposited forever, for a card
       nobody is pointing at. The per-shape animations (smoke, beams,
       waveform...) still run; only the whole-scene spin stops. */
    .rv-3d-idle .rv-3d-scene,
    .service-card:hover .rv-3d-idle .rv-3d-scene { animation: none; }

    /* Rounded ends on ~200 one-and-a-half-pixel lines cost real masking work
       and are invisible at this size. */
    .rv-edge { border-radius: 0; }

    /* The glyph cloud is normally revealed through a small circle that
       follows the pointer — meaningless on touch, where there's nothing to
       follow and no :hover to rely on. On a phone it's anchored to the top
       corner instead and fades out diagonally, so it reads as a texture in
       that corner rather than a wall of characters over the whole card.
       The fade is part of the mask, so the glyphs outside it aren't just
       invisible — they're not composited at all. */
    .service-card.expanded .rv-code {
        opacity: 1;
        -webkit-mask-image: radial-gradient(150px 130px at 12% 6%, #000 0%, rgba(0,0,0,.45) 45%, transparent 78%);
        mask-image: radial-gradient(150px 130px at 12% 6%, #000 0%, rgba(0,0,0,.45) 45%, transparent 78%);
    }
    /* Same for the wireframe: tied to the card being open rather than to a
       hover state the device can't provide. */
    .service-card.expanded .rv-3d { opacity: 1; }

    /* Neutralise the card's :hover styling on touch. iOS keeps a "sticky"
       hover on the last element you touched when that element has hover
       rules, and the next tap elsewhere can be spent clearing it instead of
       activating the new target. Since nothing here should react to hover on
       a phone anyway, making it a no-op removes that trap entirely. */
    .service-card:hover {
        transform: none;
        border-color: rgba(255, 255, 255, .09);
        background: linear-gradient(168deg, rgba(255,255,255,.06), rgba(255,255,255,.02) 55%);
        box-shadow: 0 10px 34px rgba(0, 0, 0, .35), inset 0 1px 0 rgba(255,255,255,.05);
    }

    /* The glyph layer only shows in a corner here, so it doesn't need to be
       a full-card box: a smaller element means less text to lay out and a
       smaller mask surface to composite. Same visible result. */
    .rv-code {
        inset: 0 auto auto 0;
        width: 62%;
        height: 55%;
    }
}
