ZICUA
0123456789
0123456789
100

The internet isn’t finished yet.

White-label web development · for creative agencies

You send the approved Figma. We build it in WordPress or as a custom front end, including GSAP motion, page transitions, responsive behavior and cross-browser QA. Your team keeps the client relationship and the credit.

:root { --leadly-butler: "Butler Ultra Light", "Butler", Georgia, serif; --leadly-sans: "Nunito Sans", Arial, sans-serif; --leadly-page-bg: #060707; --leadly-video-bg: #01020e; --leadly-text: #E1D8CA; /* Se recalculan automáticamente con JavaScript */ --leadly-video-focus-x: 42%; --leadly-marquee-top: 110px; --leadly-cue-size: 150px; --leadly-scene-height: 100vh; } @supports (height: 100svh) { :root { /* * SVH evita saltos cuando aparece o desaparece * la barra del navegador en celular. */ --leadly-scene-height: 100svh; } } /* ===================================================== ESCENA PRINCIPAL ===================================================== */ #video-scroll-container { position: relative; isolation: isolate; width: 100%; height: var(--leadly-scene-height) !important; min-height: var(--leadly-scene-height) !important; margin: 0 !important; padding: 0 !important; overflow: hidden !important; overscroll-behavior: auto; background: var(--leadly-page-bg); contain: layout paint; } /* ===================================================== VIDEO DE ELEMENTOR ===================================================== */ #video-scroll-container .elementor-widget-video { position: absolute !important; inset: 0 !important; z-index: 1; width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; pointer-events: none; } #video-scroll-container .elementor-widget-video .elementor-widget-container, #video-scroll-container .elementor-wrapper, #video-scroll-container .elementor-fit-aspect-ratio { width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; } #video-scroll-container .elementor-widget-video video { display: block; width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; object-fit: cover; object-position: var(--leadly-video-focus-x) center; background: var(--leadly-video-bg); transform: translateZ(0); backface-visibility: hidden; pointer-events: none; } /* ===================================================== CAPA OSCURA ===================================================== */ .leadly-video-shade { position: absolute; inset: 0; z-index: 2; pointer-events: none; background-color: rgba(6, 7, 7, 0.38); will-change: background-color; } /* ===================================================== MARQUEE SUPERIOR ===================================================== */ .leadly-top-marquee { position: absolute; top: var(--leadly-marquee-top); left: 0; z-index: 4; width: 100%; overflow: hidden; opacity: 0; visibility: hidden; pointer-events: none; -webkit-mask-image: linear-gradient( to right, transparent 0%, black 6%, black 94%, transparent 100% ); mask-image: linear-gradient( to right, transparent 0%, black 6%, black 94%, transparent 100% ); will-change: opacity; } .leadly-top-marquee__track { display: flex; align-items: center; width: max-content; animation: leadlyMarqueeMove clamp(13s, 1.2vw + 13s, 18s) linear infinite; will-change: transform; } .leadly-top-marquee__group { display: flex; flex-shrink: 0; align-items: center; } .leadly-top-marquee__item { display: inline-flex; align-items: center; padding-right: clamp( 2.5rem, 4.5vw, 5.75rem ); color: var(--leadly-text); font-family: var(--leadly-sans); font-size: clamp( 0.68rem, 0.62rem + 0.25vw, 0.95rem ); font-weight: 500; line-height: 1; letter-spacing: clamp( 0.25em, 0.31em, 0.38em ); text-transform: uppercase; white-space: nowrap; } .leadly-top-marquee__item::after { content: "//"; margin-left: clamp( 2.5rem, 4.5vw, 5.75rem ); font-size: 0.95em; font-weight: 400; letter-spacing: 0.08em; opacity: 0.75; } @keyframes leadlyMarqueeMove { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-50%, 0, 0); } } /* ===================================================== TEXTOS ===================================================== */ .leadly-copy-layer { position: absolute; inset: 0; z-index: 3; overflow: hidden; pointer-events: none; } .leadly-copy { position: absolute; top: 50%; width: auto; max-width: min( 58vw, 1050px ); margin: 0; color: var(--leadly-text); font-family: var(--leadly-butler); font-size: clamp( 3.2rem, 1.7rem + 6.3vw, 9.75rem ); font-weight: 200; line-height: 0.93; letter-spacing: -0.043em; opacity: 0 !important; visibility: hidden !important; text-wrap: balance; text-shadow: 0 4px 36px rgba(0, 0, 0, 0.34); will-change: transform, opacity; } .leadly-copy--left { left: clamp( 1.4rem, 5.8vw, 7.2rem ); text-align: left; transform: translateY( calc(-50% + 110px) ); } .leadly-copy--right { right: clamp( 1.4rem, 5.8vw, 7.2rem ); text-align: right; transform: translateY( calc(-50% + 110px) ); } /* ===================================================== REMATE FINAL ===================================================== */ .leadly-copy--final { top: 50%; left: 50%; right: auto; width: min( 1320px, calc(100vw - 3rem) ); max-width: none; font-size: clamp( 3.6rem, 1.8rem + 7.2vw, 10.8rem ); line-height: 1.12; letter-spacing: -0.038em; text-align: center; transform: translate( -50%, calc(-50% + 100px) ); } /* ===================================================== INDICADOR CIRCULAR ===================================================== */ .leadly-scroll-cue { position: absolute; right: clamp( 0.9rem, 2.8vw, 3rem ); bottom: max( clamp( 1rem, 2.5vw, 2.7rem ), env(safe-area-inset-bottom) ); z-index: 5; width: var(--leadly-cue-size); height: var(--leadly-cue-size); color: var(--leadly-text); opacity: 0; visibility: hidden; pointer-events: none; filter: drop-shadow( 0 4px 20px rgba(0, 0, 0, 0.42) ); will-change: opacity, visibility, transform; } .leadly-scroll-cue svg { display: block; width: 100%; height: 100%; overflow: visible; } .leadly-scroll-cue__rotor, .leadly-scroll-cue__asterisk { transform-origin: 60px 60px; will-change: transform; } .leadly-scroll-cue__word { fill: currentColor; font-family: var(--leadly-sans); font-size: 11px; font-weight: 700; letter-spacing: 2.3px; text-transform: uppercase; } .leadly-scroll-cue__asterisk line { stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; } /* ===================================================== WIDGET HTML DE ELEMENTOR ===================================================== */ #video-scroll-container .elementor-widget-html, #video-scroll-container .elementor-widget-html > .elementor-widget-container { position: static !important; width: auto !important; height: auto !important; margin: 0 !important; padding: 0 !important; } /* ===================================================== ACCESIBILIDAD ===================================================== */ .leadly-sr-only { position: absolute !important; width: 1px !important; height: 1px !important; margin: -1px !important; padding: 0 !important; overflow: hidden !important; clip: rect( 0, 0, 0, 0 ) !important; white-space: nowrap !important; border: 0 !important; } /* ===================================================== PANTALLAS VERTICALES No depende de un modelo concreto de celular. Se activa por proporción y espacio disponible. ===================================================== */ @media (max-aspect-ratio: 4 / 5) { .leadly-copy { top: 54%; left: 1.35rem !important; right: 1.35rem !important; max-width: none; font-size: clamp( 2.65rem, 7vw + 1.1rem, 5.25rem ); line-height: 0.98; letter-spacing: -0.034em; text-align: left; } .leadly-copy--final { top: 52%; left: 50% !important; right: auto !important; width: calc( 100vw - 2rem ); font-size: clamp( 3rem, 8vw + 0.9rem, 5.75rem ); line-height: 1.12; text-align: center; } .leadly-video-shade { background-color: rgba(6, 7, 7, 0.45); } .leadly-top-marquee__item { font-size: clamp( 0.65rem, 2.6vw, 0.76rem ); letter-spacing: 0.29em; } } /* ===================================================== PANTALLAS BAJAS EN HORIZONTAL ===================================================== */ @media ( orientation: landscape ) and ( max-height: 650px ) { .leadly-copy { top: 53%; font-size: clamp( 2.8rem, 7.2vw, 6.3rem ); max-width: 62vw; } .leadly-copy--final { top: 52%; font-size: clamp( 3rem, 7.8vw, 6.7rem ); } .leadly-scroll-cue { width: min( var(--leadly-cue-size), 118px ); height: min( var(--leadly-cue-size), 118px ); } }

We read the intent. We remove the noise. Structure takes shape. Motion connects the states. Code makes it real. Ready for production.

(function () { "use strict"; const GSAP_URL = "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"; const SCROLLTRIGGER_URL = "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"; const TRIGGER_ID = "leadly-video-scroll-fluid-v1"; const VIDEO_FPS = 30; function loadScript(url, globalName) { return new Promise(function (resolve, reject) { if (window[globalName]) { resolve(); return; } const existing = document.querySelector( 'script[src="' + url + '"]' ); if (existing) { existing.addEventListener( "load", resolve, { once: true } ); existing.addEventListener( "error", reject, { once: true } ); return; } const script = document.createElement("script"); script.src = url; script.async = true; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } async function loadLibraries() { if (!window.gsap) { await loadScript( GSAP_URL, "gsap" ); } if (!window.ScrollTrigger) { await loadScript( SCROLLTRIGGER_URL, "ScrollTrigger" ); } } function clamp(value, min, max) { return Math.min( Math.max(value, min), max ); } function lerp(start, end, progress) { return ( start + (end - start) * progress ); } function smoothstep(value) { const t = clamp(value, 0, 1); return ( t * t * (3 - 2 * t) ); } function getViewportProfile(duration) { const width = window.innerWidth; const height = window.innerHeight; const widthProgress = clamp( (width - 360) / (1600 - 360), 0, 1 ); const portrait = height > width; /* * Todo se interpola de forma continua. * No hay una cantidad fija de scroll por dispositivo. */ let screens = lerp( portrait ? 5.7 : 6.3, 8.8, widthProgress ); screens *= clamp( duration / 10, 0.88, 1.12 ); const firstText = lerp( 0.057, 0.075, widthProgress ); const stageDuration = lerp( 0.124, 0.118, widthProgress ); const stageGap = lerp( 0.015, 0.021, widthProgress ); const lastStageEnd = firstText + (stageDuration + stageGap) * 4 + stageDuration; const finalStart = clamp( lastStageEnd + 0.055, 0.80, 0.835 ); return { scrollDistance: Math.round( height * screens ), firstText: firstText, stageDuration: stageDuration, stageGap: stageGap, finalStart: finalStart, finalEnterEnd: finalStart + 0.055, cueFadeStart: firstText * 0.28, cueFadeEnd: firstText * 0.82, shadeStart: finalStart - 0.07, shadeEnd: finalStart + 0.045, marqueeFadeStart: finalStart - 0.11, marqueeFadeEnd: finalStart - 0.02 }; } function getVisibleHeaderHeight() { const selectors = [ ".elementor-location-header", "header.elementor", "header" ]; for ( let index = 0; index < selectors.length; index += 1 ) { const elements = Array.from( document.querySelectorAll( selectors[index] ) ); for ( let item = 0; item 0 && rect.bottom > 0; if (visible) { return rect.height; } } } return 0; } function setFluidCssVariables(container) { const width = window.innerWidth; const height = window.innerHeight; const widthProgress = clamp( (width - 360) / (1600 - 360), 0, 1 ); const portrait = height > width; /* * Menor porcentaje desplaza visualmente * el contenido hacia la derecha. */ let videoFocus = lerp( 35, 44.5, widthProgress ); if (portrait) { videoFocus -= 1; } const headerHeight = getVisibleHeaderHeight(); const marqueeExtra = lerp( portrait ? 22 : 15, 26, widthProgress ); const marqueeTop = headerHeight + marqueeExtra; const cueSize = lerp( portrait ? 112 : 122, 178, widthProgress ); container.style.setProperty( "--leadly-video-focus-x", videoFocus.toFixed(2) + "%" ); container.style.setProperty( "--leadly-marquee-top", Math.round(marqueeTop) + "px" ); container.style.setProperty( "--leadly-cue-size", Math.round(cueSize) + "px" ); } function configureVideo(video) { video.muted = true; video.defaultMuted = true; video.playsInline = true; video.preload = "auto"; video.autoplay = false; video.loop = false; video.controls = false; video.disablePictureInPicture = true; video.setAttribute("muted", ""); video.setAttribute("playsinline", ""); video.setAttribute("preload", "auto"); video.setAttribute( "x-webkit-airplay", "deny" ); video.removeAttribute("autoplay"); video.removeAttribute("loop"); video.removeAttribute("controls"); video.pause(); } function waitForVideo(video) { return new Promise(function (resolve, reject) { if ( video.readyState >= 1 && Number.isFinite(video.duration) && video.duration > 0 ) { resolve(); return; } function loaded() { cleanup(); resolve(); } function failed() { cleanup(); reject( new Error( "No fue posible cargar los metadatos del video." ) ); } function cleanup() { video.removeEventListener( "loadedmetadata", loaded ); video.removeEventListener( "error", failed ); } video.addEventListener( "loadedmetadata", loaded, { once: true } ); video.addEventListener( "error", failed, { once: true } ); video.load(); }); } /* * El video ya no es un tween GSAP. * Su tiempo se asigna desde el progreso real * y puede avanzar o retroceder. */ function createVideoScrubber( video, endTime ) { let targetTime = 0; let frameRequest = null; let lastAppliedTime = -1; const frameDuration = 1 / VIDEO_FPS; function applyTime(force) { frameRequest = null; const snappedTime = clamp( Math.round( targetTime * VIDEO_FPS ) / VIDEO_FPS, 0, endTime ); const changedEnough = Math.abs( snappedTime - lastAppliedTime ) >= frameDuration * 0.8; if ( !force && !changedEnough ) { return; } try { video.currentTime = snappedTime; lastAppliedTime = snappedTime; } catch (error) { /* * El siguiente frame vuelve a intentarlo. */ } } function setProgress( progress, force ) { targetTime = clamp( progress, 0, 1 ) * endTime; if (force) { if (frameRequest) { cancelAnimationFrame( frameRequest ); frameRequest = null; } applyTime(true); return; } if (!frameRequest) { frameRequest = requestAnimationFrame( function () { applyTime(false); } ); } } function destroy() { if (frameRequest) { cancelAnimationFrame( frameRequest ); frameRequest = null; } } return { setProgress: setProgress, destroy: destroy }; } function hideText(text) { text.style.setProperty( "opacity", "0", "important" ); text.style.setProperty( "visibility", "hidden", "important" ); } function showRegularText( text, opacity, y ) { text.style.setProperty( "opacity", String(opacity), "important" ); text.style.setProperty( "visibility", opacity > 0.001 ? "visible" : "hidden", "important" ); text.style.transform = "translateY(calc(-50% + " + y + "px))"; } function showFinalText( text, opacity, y ) { text.style.setProperty( "opacity", String(opacity), "important" ); text.style.setProperty( "visibility", opacity > 0.001 ? "visible" : "hidden", "important" ); text.style.transform = "translate(-50%, calc(-50% + " + y + "px))"; } function hideAllTexts( regularTexts, finalText ) { regularTexts.forEach( hideText ); hideText(finalText); } function updateRegularText( text, localProgress ) { let opacity; let y; if (localProgress < 0.24) { const progress = smoothstep( localProgress / 0.24 ); opacity = progress; y = 105 * (1 - progress); } else if ( localProgress < 0.76 ) { const progress = ( localProgress - 0.24 ) / 0.52; opacity = 1; y = -12 * progress; } else { const progress = smoothstep( ( localProgress - 0.76 ) / 0.24 ); opacity = 1 - progress; y = -12 - 112 * progress; } showRegularText( text, opacity, y ); } function buildStages(profile) { const stages = []; let start = profile.firstText; for ( let index = 0; index < 5; index += 1 ) { stages.push({ start: start, end: start + profile.stageDuration }); start += profile.stageDuration + profile.stageGap; } return stages; } function updateFinalText( finalText, progress, profile ) { if ( progress < profile.finalStart ) { hideText(finalText); return; } if ( progress profile.cueFadeStart ) { opacity = 1 - smoothstep( ( progress - profile.cueFadeStart ) / ( profile.cueFadeEnd - profile.cueFadeStart ) ); } opacity = clamp( opacity, 0, 1 ); cue.style.opacity = String(opacity); cue.style.visibility = opacity > 0.001 ? "visible" : "hidden"; cue.style.transform = "translate3d(0, " + ( -6 * smoothstep( progress / profile.cueFadeEnd ) ) + "px, 0)"; rotor.style.transform = "rotate(" + progress * 3600 + "deg)"; asterisk.style.transform = "rotate(" + progress * -5400 + "deg)"; } function updateMarquee( marquee, progress, active, profile ) { if (!marquee || !active) { if (marquee) { marquee.style.opacity = "0"; marquee.style.visibility = "hidden"; } return; } const portrait = window.innerHeight > window.innerWidth; const baseOpacity = portrait ? 0.18 : 0.145; let opacity = baseOpacity; if ( progress > profile.marqueeFadeStart ) { opacity = baseOpacity * ( 1 - smoothstep( ( progress - profile.marqueeFadeStart ) / ( profile.marqueeFadeEnd - profile.marqueeFadeStart ) ) ); } opacity = clamp( opacity, 0, baseOpacity ); marquee.style.opacity = String(opacity); marquee.style.visibility = opacity > 0.001 ? "visible" : "hidden"; } function updateShade( shade, progress, profile ) { const portrait = window.innerHeight > window.innerWidth; const baseOpacity = portrait ? 0.45 : 0.38; let opacity = baseOpacity; if ( progress > profile.shadeStart ) { opacity = baseOpacity + ( 1 - baseOpacity ) * smoothstep( ( progress - profile.shadeStart ) / ( profile.shadeEnd - profile.shadeStart ) ); } shade.style.backgroundColor = "rgba(6, 7, 7, " + clamp( opacity, baseOpacity, 1 ) + ")"; } function updateScene( progress, active, profile, regularTexts, finalText, shade, cue, rotor, asterisk, marquee ) { hideAllTexts( regularTexts, finalText ); const stages = buildStages(profile); stages.forEach( function (stage, index) { if ( progress >= stage.start && progress 16; if (!widthChanged) { return; } lastViewportWidth = window.innerWidth; window.clearTimeout( resizeTimer ); resizeTimer = window.setTimeout( function () { ScrollTrigger.refresh(); }, 220 ); } window.addEventListener( "resize", handleResponsiveChange, { passive: true } ); window.addEventListener( "orientationchange", function () { window.setTimeout( function () { lastViewportWidth = window.innerWidth; setFluidCssVariables( container ); ScrollTrigger.refresh(); }, 350 ); }, { passive: true } ); /* * Desbloquea el video en iPhone/iPad. */ function unlockVideo() { const playPromise = video.play(); if ( playPromise && typeof playPromise.then === "function" ) { playPromise .then(function () { video.pause(); scrubber.setProgress( trigger.progress, true ); }) .catch(function () {}); } } document.addEventListener( "touchstart", unlockVideo, { once: true, passive: true } ); document.addEventListener( "pointerdown", unlockVideo, { once: true, passive: true } ); if ( document.fonts && document.fonts.ready ) { document.fonts.ready.then( function () { setFluidCssVariables( container ); ScrollTrigger.refresh(); } ); } window.setTimeout( function () { setFluidCssVariables( container ); ScrollTrigger.refresh(); }, 450 ); } catch (error) { const fallbackContainer = document.querySelector( "#video-scroll-container" ); if (fallbackContainer) { const fallbackVideo = fallbackContainer.querySelector( ".elementor-widget-video video" ); const fallbackRegularTexts = Array.from( fallbackContainer.querySelectorAll( ".leadly-copy:not(.leadly-copy--final)" ) ); const fallbackFinalText = fallbackContainer.querySelector( ".leadly-copy--final" ); fallbackContainer.classList.add( "leadly-video-fallback" ); fallbackContainer.setAttribute( "data-scroll-video-mode", "fallback" ); fallbackRegularTexts.forEach( hideText ); if (fallbackFinalText) { showFinalText( fallbackFinalText, 1, 0 ); } if (fallbackVideo) { fallbackVideo.style.display = "none"; } window.__zicuaScrollVideoState = { mode: "fallback", reason: error && error.message ? error.message : "scene-initialization-failed" }; } } } function boot() { window.setTimeout( initScene, 120 ); } if ( document.readyState === "loading" ) { document.addEventListener( "DOMContentLoaded", boot, { once: true } ); } else { boot(); } })();

Selected work

2023 to 2026

Capabilities

Mukani botanical oils arranged on sand
RBD performing live, from the Switch Sound Music archive.
Leadly homepage
Portrait of Fer Gimeno for Mujer Raíz

Technical direction01

Motion systems02

WordPress & front-end03

Experience QA04

BRIEF · PROTOTYPE · BUILD · REFINEMENT · QA

YOUR CREATIVE DIRECTION · OUR CODE · YOUR CREDIT
01 / Capabilities

You lead the client.
We handle the build.

Bring us in when the approved design depends on the front end. We build the interaction, motion and production code without stepping into the client relationship.

  1. 01

    Motion Sprint

    We isolate and build the part your team should not have to simplify: a key interaction, page transition or scroll sequence.

  2. 02

    Creative Front-end Build

    We turn an approved design into a complete site with CMS, responsive behavior, accessibility and QA. What reaches the browser keeps the hierarchy and intent of the original file.

  3. 03

    Hero Experience

    We prototype and build the interaction that defines a campaign or launch, then make sure it supports the page instead of taking it over.

  4. 04

    Pitch Prototype

    We turn a presentation idea into a working prototype so your team can test feasibility and let the client experience it before committing to production.

02 / Process

A build process
your team can see and control.

We agree on ownership, checkpoints and handoff before work begins. Delivery can remain under your agency’s name.

01Review

We review the Figma, scope, timing, stack and constraints. If something does not fit, we say so before touching the code.

02Prototype

We test the motion that defines the experience early. Your team approves rhythm, states and behavior while changes are still inexpensive.

03Build

We develop components, transitions, responsive behavior and CMS support within the agreed stack, without forcing an unnecessary rebuild.

04Refine

We treat revisions with the same care as the first pass and flag every decision that affects scope, timing or performance.

05Hand off

We test devices, browsers, keyboard use, reduced motion and performance. Your team receives production-ready code and documentation.

03 / Start here

Show us the part
the design depends on.

Send the Figma, deadline and motion reference. We review the difficult interaction, flag the risks and reply with a clear next step.

Send the brief Available for focused sprints or full white-label builds.