{"id":6365,"date":"2026-05-06T15:53:03","date_gmt":"2026-05-06T15:53:03","guid":{"rendered":"https:\/\/lahuellastudio.com\/la-formula\/"},"modified":"2026-07-19T12:00:09","modified_gmt":"2026-07-19T12:00:09","slug":"la-formula","status":"publish","type":"page","link":"https:\/\/lahuellastudio.com\/en\/la-formula\/","title":{"rendered":"The Formula"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"6365\" class=\"elementor elementor-6365 elementor-bc-flex-widget\" data-elementor-post-type=\"page\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-lf00sec1 elementor-section-stretched elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"lf00sec1\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;stretch_section&quot;:&quot;section-stretched&quot;,&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-lf00col1\" data-id=\"lf00col1\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-lf00wid1 elementor-widget elementor-widget-html\" data-id=\"lf00wid1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<style>\n\/* Fix gap superior: hay un SVG inline en <body> que crea una line-box y empuja\n   el header+contenido 24px abajo. Lo paso a block. *\/\nbody > svg { display: block; }\n\n.lf-page {\n  --bg: #070707;\n  --fg: #FFFFFF;\n  --muted: #BDBDBD;\n  --accent: #DADADA;\n  --line: rgba(255,255,255,.12);\n  background: var(--bg);\n  color: var(--fg);\n  font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n  line-height: 1.5;\n  width: 100%;\n  position: relative;\n}\n.lf-page *, .lf-page *::before, .lf-page *::after { box-sizing: border-box; }\n.lf-page [hidden] { display: none !important; }\n\n.lf-page .pad, .lf-page .filler { background: var(--bg); padding: 14vh 6vw; }\n.lf-page .label { color: var(--muted); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; }\n.lf-page h1 { font-size: clamp(28px, 4vw, 56px); font-weight: 400; margin: .25em 0; line-height: 1.1; letter-spacing: -.04em; color: var(--fg); }\n.lf-page h2 { font-size: clamp(22px, 2.4vw, 36px); font-weight: 400; margin: .5em 0; line-height: 1.15; letter-spacing: -.03em; color: var(--fg); }\n.lf-page p { color: var(--muted); max-width: 50ch; font-weight: 300; }\n\n.lf-toggle {\n  position: fixed; top: 90px; right: 16px; z-index: 999;\n  background: rgba(0,0,0,.55); border: 1px solid var(--line); padding: 8px 12px; border-radius: 12px;\n  backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%);\n  font-size: 12px;\n}\n.lf-toggle a { color: var(--fg); text-decoration: none; padding: 4px 8px; border-radius: 6px; cursor: pointer; }\n.lf-toggle a.active { background: var(--accent); color: var(--bg); }\n\n\/* === Sticky section === *\/\n\/* Altura fija en vh para que el c\u00e1lculo de scroll progress vs. un-sticking del\n   pin est\u00e9 siempre sincronizado. Reducirla en m\u00f3vil causaba que la barra del\n   navegador (que cambia el vh din\u00e1micamente) desincronizara la animaci\u00f3n. *\/\n.lf-page .lf-sticky-section { position: relative; height: 320vh; }\n.lf-page .lf-sticky-inner {\n  position: sticky; top: 0;\n  width: 100vw; height: 100vh;\n  overflow: hidden;\n  background: var(--bg);\n  margin-left: calc(50% - 50vw);\n  \/* Promueve a capa de composici\u00f3n: el canvas\/video y los text-blocks\n     animan dentro, le damos al compositor su propio layer. *\/\n  will-change: transform;\n}\n\n.lf-page .lf-media-bg {\n  position: absolute; inset: 0;\n  width: 100%; height: 100%;\n  z-index: 0;\n}\n.lf-page .lf-media-bg canvas, .lf-page .lf-media-bg video {\n  display: block;\n  width: 100%; height: 100%;\n  object-fit: cover;\n  background: var(--bg);\n}\n.lf-page .lf-media-bg::after {\n  content: \"\";\n  position: absolute; inset: 0;\n  background: linear-gradient(90deg, rgba(7,7,7,.85) 0%, rgba(7,7,7,.45) 35%, rgba(7,7,7,0) 60%);\n  z-index: 1;\n  pointer-events: none;\n}\n@media (max-width: 1024px) {\n  .lf-page .lf-media-bg::after {\n    background:\n      linear-gradient(180deg, rgba(7,7,7,.9) 0%, rgba(7,7,7,.4) 28%, rgba(7,7,7,0) 50%, rgba(7,7,7,.4) 72%, rgba(7,7,7,.9) 100%);\n  }\n}\n\n.lf-page .lf-text-stack {\n  position: absolute; inset: 0;\n  z-index: 2;\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  grid-template-rows: 1fr;\n  align-items: center;\n  padding: 0 6vw;\n}\n.lf-page .lf-text-block {\n  grid-column: 1 \/ 2; grid-row: 1 \/ 2;\n  max-width: 460px;\n  transition: opacity .55s ease, transform .55s ease;\n  will-change: opacity, transform;\n}\n.lf-page .lf-text-block h2 { text-wrap: balance; }\n.lf-page .lf-text-block p { text-wrap: pretty; max-width: 42ch; }\n.lf-page .lf-text-block .label { color: var(--accent); }\n.lf-page .lf-text-block.is-hidden { opacity: 0; transform: translateY(20px); pointer-events: none; }\n.lf-page .lf-text-block.right-side { grid-column: 2 \/ 3; justify-self: end; text-align: right; }\n.lf-page .lf-text-block.right-side p { margin-left: auto; }\n\n@media (max-width: 1024px) {\n  .lf-page .lf-text-stack { display: block !important; padding: 0 !important; }\n  .lf-page .lf-text-block {\n    position: absolute !important;\n    left: 5vw !important; right: 5vw !important;\n    max-width: 100% !important;\n    grid-column: unset !important; grid-row: unset !important;\n    text-align: left !important;\n  }\n  \/* Top con clearance del header (que ocupa ~8-10vh con su margen). *\/\n  .lf-page .lf-text-block:not(.right-side) { top: 16vh; bottom: auto; }\n  .lf-page .lf-text-block.right-side { bottom: 14vh; top: auto; }\n  .lf-page .lf-text-block h2 { font-size: clamp(20px, 5vw, 28px); margin: .35em 0; }\n  .lf-page .lf-text-block .label { font-size: 10px; }\n  .lf-page .lf-text-block p {\n    font-size: 14px; max-width: 100%; margin: 0;\n    display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden;\n  }\n}\n\n\/* === Intro Hero === *\/\n.lf-page .lf-intro {\n  position: relative;\n  width: 100vw;\n  margin-left: calc(50% - 50vw);\n  background: var(--bg);\n}\n.lf-page .lf-intro-card {\n  position: relative;\n  width: 100%;\n  height: 95vh;\n  min-height: 660px;\n  overflow: hidden;\n  border-radius: 0 0 28px 28px;\n}\n.lf-page .lf-intro-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }\n.lf-page .lf-intro-overlay {\n  position: absolute; inset: 0;\n  background: linear-gradient(180deg, rgba(7,7,7,.45) 0%, rgba(7,7,7,.15) 30%, rgba(7,7,7,.45) 100%);\n  pointer-events: none;\n}\n.lf-page .lf-intro-content { position: absolute; bottom: 6vh; left: 5vw; right: 5vw; z-index: 2; }\n.lf-page .lf-intro-label {\n  display: block; color: var(--accent);\n  font-size: 12px; letter-spacing: .18em; text-transform: uppercase;\n  margin-bottom: 12px; font-weight: 400;\n}\n.lf-page .lf-intro-title {\n  color: var(--fg);\n  font-family: Inter, sans-serif;\n  font-weight: 400;\n  font-size: clamp(36px, 4.5vw, 64px);\n  letter-spacing: -.04em;\n  line-height: 1.05;\n  margin: 0;\n}\n.lf-page .lf-intro-text {\n  margin-top: 24px;\n  \/* Aproximadamente el ancho que ocupa el h1 a 64px (\"La f\u00f3rmula de\n     nuestros proyectos\" \u2248 32 chars \u00d7 30px). Mobile lo sobreescribe a 100%. *\/\n  max-width: 960px;\n  font-size: clamp(15px, 1.2vw, 18px);\n  line-height: 1.6;\n  color: rgba(255,255,255,.85);\n  text-wrap: pretty;\n}\n.lf-page .lf-intro-text p { margin: 0; max-width: none; color: inherit; font-weight: 400; }\n.lf-page .lf-intro-text p + p { margin-top: 1.1em; }\n\/* Forzar salto antes de \"para entregar...\" sin usar <br> *\/\n.lf-page .lf-intro-text-break { display: block; }\n@media (max-width: 1024px) {\n  .lf-page .lf-intro-card { height: 95vh; min-height: 560px; border-radius: 0 0 20px 20px; }\n  .lf-page .lf-intro-title { font-size: clamp(28px, 7vw, 44px); }\n  .lf-page .lf-intro-text { margin-top: 14px; font-size: 14px; max-width: 100%; line-height: 1.5; }\n  .lf-page .lf-intro-text p + p { margin-top: .7em; }\n}\n\n\/* Animaci\u00f3n de entrada del hero *\/\n.lf-page .lf-intro-bg {\n  transform: scale(1.08); opacity: 0;\n  animation: lfIntroBg 1.6s cubic-bezier(.2,.7,.2,1) .15s forwards;\n}\n.lf-page .lf-intro-label {\n  opacity: 0; transform: translateY(14px);\n  animation: lfIntroFade .9s cubic-bezier(.2,.7,.2,1) .55s forwards;\n}\n.lf-page .lf-intro-title {\n  opacity: 0; transform: translateY(22px);\n  animation: lfIntroFade 1.1s cubic-bezier(.2,.7,.2,1) .75s forwards;\n}\n.lf-page .lf-intro-text {\n  opacity: 0; transform: translateY(18px);\n  animation: lfIntroFade 1s cubic-bezier(.2,.7,.2,1) 1s forwards;\n}\n@keyframes lfIntroBg { to { transform: scale(1); opacity: 1; } }\n@keyframes lfIntroFade { to { opacity: 1; transform: translateY(0); } }\n@media (prefers-reduced-motion: reduce) {\n  .lf-page .lf-intro-bg, .lf-page .lf-intro-label, .lf-page .lf-intro-title, .lf-page .lf-intro-text {\n    animation: none; opacity: 1; transform: none;\n  }\n}\n\n\/* Fade in\/out elegante en viewport para secciones (sin intro) \u2014 solo opacity\n   (sin translateY para no desplazar las secciones sticky al entrar\/salir) *\/\n.lf-page .lf-fade {\n  opacity: 0;\n  transition: opacity 1.1s cubic-bezier(.2,.7,.2,1);\n  will-change: opacity;\n}\n.lf-page .lf-fade.in-view { opacity: 1; }\n@media (prefers-reduced-motion: reduce) {\n  .lf-page .lf-fade { opacity: 1; transition: none; }\n}\n\n\/* === Secci\u00f3n servicios === *\/\n.lf-page .lf-services-section {\n  position: relative;\n  \/* Desktop: 17 slides * 60vh + 100vh inicial *\/\n  height: calc(1020vh + 100vh);\n  background: var(--bg);\n  width: 100vw;\n  margin-left: calc(50% - 50vw);\n  --slide-h: 60vh;\n}\n@media (max-width: 1024px) {\n  .lf-page .lf-services-section {\n    \/* Mobile: 17 slides * 30vh + 100vh \u2014 m\u00e1s compacto que desktop. El snap\n       (definido m\u00e1s abajo) se encarga de que cada slide se sienta controlado\n       a pesar del menor recorrido. margin-top negativo solapa con la c\u00e1mara\n       para reducir el gap entre texto 2 saliendo y card entrando. *\/\n    height: calc(510vh + 100vh);\n    --slide-h: 30vh;\n    margin-top: -14vh;\n  }\n}\n\/* Scroll-snap (proximity) en la secci\u00f3n de servicios. Los .lf-services-snap\n   son 17 targets invisibles posicionados en el inicio de cada slide. Proximity\n   solo engancha al detenerse cerca \u2192 no bloquea scroll fuera de la secci\u00f3n.\n   Restringido a desktop: en m\u00f3vil el touch tiene inercia natural y el snap\n   recortaba cada swipe en seco. *\/\nhtml { overflow-x: clip; }\n@media (min-width: 1025px) {\n  html { scroll-snap-type: y proximity; }\n}\n.lf-page .lf-services-snap {\n  position: absolute;\n  left: 0;\n  width: 1px; height: 1px;\n  pointer-events: none;\n  scroll-snap-align: start;\n  top: calc(var(--n, 0) * var(--slide-h));\n}\n.lf-page .lf-services-pin {\n  position: sticky; top: 0;\n  width: 100vw; height: 100vh;\n  display: flex; flex-direction: column;\n  align-items: center; justify-content: center;\n  padding: 12vh 6vw 6vh;\n  gap: 2.6vh;\n  \/* Hint al compositor: el pin entero anima opacity en is-leaving + contiene\n     el card y el nav que tambi\u00e9n animan. *\/\n  will-change: opacity;\n}\n.lf-page .lf-services-card {\n  position: relative;\n  flex: 1 1 auto;\n  width: 100%;\n  min-height: 0;\n  border-radius: 32px;\n  overflow: hidden;\n  background: #111;\n  \/* Compositor layer: dentro hay 17 im\u00e1genes que cambian opacity por scroll. *\/\n  will-change: transform;\n  box-shadow: 0 50px 100px rgba(0,0,0,.65);\n}\n\n\/* Nav de servicios \u2014 desktop: 2 filas wrap; mobile: 1 fila scroll horizontal con mask *\/\n.lf-page .lf-services-nav {\n  flex: 0 0 auto;\n  width: 100%;\n  max-width: 100%;\n  display: flex;\n  gap: 8px;\n  flex-wrap: wrap;\n  justify-content: center;\n  padding: 4px 2px;\n}\n\/* Forzamos el wrap exacto entre la pill 10 y 11 (en JS se inserta un\n   .lf-services-nav-break con flex-basis: 100%) para repartir 10 + 7\n   en desktop. En m\u00f3vil el nav es scroll horizontal, el break se oculta. *\/\n.lf-page .lf-services-nav-break { flex-basis: 100%; height: 0; margin: 0; }\n@media (max-width: 1024px) {\n  .lf-page .lf-services-nav-break { display: none; }\n}\n.lf-page .lf-services-nav-btn {\n  flex: 0 0 auto;\n  background: rgba(255,255,255,.04);\n  border: 1px solid rgba(255,255,255,.16);\n  color: rgba(255,255,255,.78);\n  padding: 11px 19px;\n  border-radius: 999px;\n  font-family: Inter, sans-serif;\n  font-size: 13px;\n  font-weight: 400;\n  letter-spacing: .01em;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  backdrop-filter: blur(14px) saturate(140%);\n  -webkit-backdrop-filter: blur(14px) saturate(140%);\n  transition: background .35s ease, border-color .35s ease, color .35s ease;\n}\n.lf-page .lf-services-nav-btn:hover {\n  background: rgba(255,255,255,.08);\n  border-color: rgba(255,255,255,.28);\n  color: var(--fg);\n}\n.lf-page .lf-services-nav-btn.active {\n  background: rgba(255,255,255,.14);\n  border-color: rgba(255,255,255,.38);\n  color: var(--fg);\n}\n.lf-page .lf-services-up {\n  position: absolute;\n  top: 18px; right: 18px;\n  width: 44px; height: 44px;\n  display: flex; align-items: center; justify-content: center;\n  background: rgba(255,255,255,.04);\n  border: 1px solid rgba(255,255,255,.16);\n  color: rgba(255,255,255,.78);\n  border-radius: 12px;\n  cursor: pointer;\n  z-index: 10;\n  \/* iOS: respuesta t\u00e1ctil m\u00e1s r\u00e1pida y sin double-tap-zoom en el bot\u00f3n *\/\n  touch-action: manipulation;\n  -webkit-tap-highlight-color: transparent;\n  backdrop-filter: blur(14px) saturate(140%);\n  -webkit-backdrop-filter: blur(14px) saturate(140%);\n  transition: background .35s ease, border-color .35s ease, color .35s ease;\n}\n\/* El SVG no debe interceptar el tap \u2014 que llegue al <button>. *\/\n.lf-page .lf-services-up svg { pointer-events: none; }\n.lf-page .lf-services-up:hover {\n  background: rgba(255,255,255,.08);\n  border-color: rgba(255,255,255,.28);\n  color: var(--fg);\n}\n.lf-page .lf-services-up:focus-visible {\n  outline: 2px solid var(--accent);\n  outline-offset: 2px;\n}\n\/* Fade del pin durante el \"volver atr\u00e1s\": evita que se vean los 17 cards\n   pasando r\u00e1pido cuando hacemos el scroll a la secci\u00f3n anterior. *\/\n.lf-page .lf-services-pin {\n  transition: opacity .26s ease;\n}\n.lf-page .lf-services-pin.is-leaving {\n  opacity: 0;\n}\n@media (max-width: 1024px) {\n  \/* M\u00f3vil: quitamos backdrop-filter en pills\/botones (cada blur es repaint\n     pesado al scrollear) y reducimos blur radius del shadow del card.\n     Compensamos con bg ligeramente m\u00e1s opaco. *\/\n  .lf-page .lf-services-nav-btn,\n  .lf-page .lf-services-up {\n    backdrop-filter: none;\n    -webkit-backdrop-filter: none;\n    background: rgba(255,255,255,.07);\n  }\n  .lf-page .lf-services-card {\n    box-shadow: 0 30px 50px rgba(0,0,0,.55);\n  }\n  .lf-page .lf-services-pin { padding: 13vh 4vw 9vh; gap: 2vh; }\n  .lf-page .lf-services-nav {\n    flex-wrap: nowrap;\n    justify-content: flex-start;\n    overflow-x: auto;\n    overflow-y: hidden;\n    scrollbar-width: none;\n    -ms-overflow-style: none;\n    \/* Vertical pasa al body, horizontal se queda en el nav \u2014 evita el \"raro al subir\" *\/\n    touch-action: pan-x;\n    -webkit-overflow-scrolling: touch;\n    \/* Fade en los bordes para que no corte brusco *\/\n    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);\n    mask-image: linear-gradient(90deg, transparent 0, #000 22px, #000 calc(100% - 22px), transparent 100%);\n    padding-inline: 18px;\n  }\n  .lf-page .lf-services-nav::-webkit-scrollbar { display: none; }\n  .lf-page .lf-services-nav-btn { padding: 8px 13px; font-size: 11px; }\n}\n.lf-page .lf-services-bevel {\n  position: absolute; inset: 0;\n  pointer-events: none;\n  border-radius: inherit;\n  z-index: 2;\n}\n.lf-page .lf-services-bevel::before {\n  content: \"\"; position: absolute; inset: 0;\n  border-radius: inherit;\n  mix-blend-mode: multiply;\n  background:\n    linear-gradient(180deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,.32) 5%, rgba(0,0,0,.14) 14%, rgba(0,0,0,0) 24%),\n    linear-gradient(0deg,   rgba(0,0,0,.65) 0%, rgba(0,0,0,.38) 5%, rgba(0,0,0,.16) 14%, rgba(0,0,0,0) 26%),\n    linear-gradient(90deg,  rgba(0,0,0,.45) 0%, rgba(0,0,0,.20) 4%, rgba(0,0,0,0) 12%),\n    linear-gradient(270deg, rgba(0,0,0,.45) 0%, rgba(0,0,0,.20) 4%, rgba(0,0,0,0) 12%);\n}\n.lf-page .lf-services-stack { position: absolute; inset: 0; }\n.lf-page .lf-service-img {\n  position: absolute; inset: 0;\n  width: 100%; height: 100%;\n  object-fit: cover;\n  opacity: 0;\n  \/* Crossfade time-based, no scroll-driven (evita \"quedar a medio\"). *\/\n  transition: opacity 380ms cubic-bezier(.4, 0, .2, 1);\n  will-change: opacity;\n  transform: translateZ(0);\n}\n.lf-page .lf-services-overlay {\n  position: absolute; inset: 0;\n  pointer-events: none;\n  border-radius: inherit;\n  z-index: 1;\n  background:\n    linear-gradient(115deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.14) 25%, rgba(255,255,255,.08) 35%, rgba(255,255,255,0) 50%),\n    radial-gradient(ellipse 35% 25% at 22% 14%, rgba(255,255,255,.18) 0%, rgba(255,255,255,0) 70%),\n    radial-gradient(ellipse 30% 22% at 82% 88%, rgba(255,255,255,.08) 0%, rgba(255,255,255,0) 70%);\n  mix-blend-mode: overlay;\n}\n.lf-page .lf-services-title-wrap {\n  position: absolute; inset: 0;\n  display: flex; flex-direction: column; align-items: center; justify-content: center;\n  z-index: 3; text-align: center; padding: 0 6vw;\n}\n.lf-page .lf-services-title {\n  color: var(--fg);\n  font-family: Inter, sans-serif;\n  font-weight: 400;\n  font-size: clamp(24px, 2.8vw, 44px);\n  line-height: 1.1; margin: 0;\n  letter-spacing: -.04em;\n  text-shadow: 0 2px 24px rgba(0,0,0,.75);\n}\n.lf-page .lf-services-counter {\n  position: absolute; bottom: 4vh; right: 4vw;\n  z-index: 3; color: var(--fg);\n  font-family: Inter, sans-serif;\n  font-size: clamp(13px, 1.1vw, 16px);\n  letter-spacing: .12em; opacity: .85;\n  text-shadow: 0 1px 8px rgba(0,0,0,.6);\n}\n.lf-page .lf-services-counter .sep { margin: 0 6px; opacity: .4; }\n.lf-page .lf-services-counter .total { opacity: .55; }\n\n@media (max-width: 1024px) {\n  .lf-page .lf-services-card { border-radius: 22px; }\n  .lf-page .lf-services-title { font-size: clamp(24px, 7vw, 44px); }\n}\n\n.lf-page .lf-loader {\n  position: absolute; inset: 0; z-index: 5;\n  display: flex; flex-direction: column; align-items: center; justify-content: center;\n  background: var(--bg); gap: 12px;\n  transition: opacity .4s ease;\n}\n.lf-page .lf-loader.is-done { opacity: 0; pointer-events: none; }\n.lf-page .lf-loader-bar { width: 220px; height: 2px; background: rgba(255,255,255,.12); overflow: hidden; border-radius: 2px; }\n.lf-page .lf-loader-bar > span { display: block; height: 100%; width: 100%; background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform .15s ease; will-change: transform; }\n.lf-page .lf-loader-text { color: var(--muted); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; }\n<\/style>\n\n<div class=\"lf-page\">\n\n  <section class=\"lf-intro\">\n    <div class=\"lf-intro-card\">\n      <img decoding=\"async\" class=\"lf-intro-bg\" src=\"\/wp-content\/uploads\/la-formula\/intro-bg.webp\" alt=\"\" \/>\n      <div class=\"lf-intro-overlay\"><\/div>\n      <div class=\"lf-intro-content\">\n        <h1 class=\"lf-intro-title\">La f\u00f3rmula de nuestros proyectos<\/h1>\n        <div class=\"lf-intro-text\">\n          <p>Reunimos equipos de primer nivel en cada destino y los coordinamos desde una sola direcci\u00f3n de producci\u00f3n<span class=\"lf-intro-text-break\"> para entregar producciones audiovisuales \u00e1giles, coherentes y optimizadas, con un \u00fanico interlocutor de principio a fin.<\/span><\/p>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"lf-sticky-section lf-fade\">\n    <div class=\"lf-sticky-inner\">\n      <div class=\"lf-media-bg\">\n        <canvas id=\"lf-canvas\" width=\"1920\" height=\"1080\"><\/canvas>\n        <video id=\"lf-video\" muted playsinline preload=\"auto\" hidden>\n          <source id=\"lf-video-src\" src=\"\" type=\"video\/mp4\">\n        <\/video>\n      <\/div>\n      <div class=\"lf-text-stack\">\n        <div class=\"lf-text-block\" id=\"lf-text-left\">\n          <h2>Una \u00fanica visi\u00f3n creativa, en cualquier destino<\/h2>\n          <p>Centralizamos la direcci\u00f3n para asegurar coherencia y control, y activamos equipos locales a medida para producir con agilidad donde haga falta. El cliente trabaja con un \u00fanico interlocutor y una l\u00ednea clara de decisi\u00f3n.<\/p>\n        <\/div>\n        <div class=\"lf-text-block right-side is-hidden\" id=\"lf-text-right\">\n          <h2>Producci\u00f3n adaptable a cualquier escala<\/h2>\n          <p>Desde un spot con despliegue completo ARRI\/RED\/VENICE hasta contenido \u00e1gil para redes grabado con iPhone 17 Pro. La escala cambia; la direcci\u00f3n estrat\u00e9gica se mantiene.<\/p>\n        <\/div>\n      <\/div>\n      <div class=\"lf-loader\" id=\"lf-loader\">\n        <div class=\"lf-loader-text\" id=\"lf-loader-text\">Cargando 0%<\/div>\n        <div class=\"lf-loader-bar\"><span id=\"lf-loader-fill\"><\/span><\/div>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"lf-services-section lf-fade\" id=\"lf-services\">\n    <div class=\"lf-services-snap\" style=\"--n:0\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:1\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:2\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:3\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:4\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:5\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:6\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:7\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:8\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:9\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:10\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:11\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:12\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:13\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:14\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:15\"><\/div>\n    <div class=\"lf-services-snap\" style=\"--n:16\"><\/div>\n    <div class=\"lf-services-pin\">\n      <div class=\"lf-services-card\">\n        <div class=\"lf-services-stack\" id=\"lf-services-stack\"><\/div>\n        <div class=\"lf-services-overlay\"><\/div>\n        <div class=\"lf-services-bevel\"><\/div>\n        <div class=\"lf-services-title-wrap\">\n          <h2 class=\"lf-services-title\" id=\"lf-services-title\">Direcci\u00f3n<\/h2>\n        <\/div>\n        <div class=\"lf-services-counter\">\n          <span id=\"lf-services-counter-num\">01<\/span>\n          <span class=\"sep\">\/<\/span>\n          <span class=\"total\">17<\/span>\n        <\/div>\n        <button class=\"lf-services-up\" type=\"button\" aria-label=\"Secci\u00f3n anterior\">\n          <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\" focusable=\"false\">\n            <path d=\"M3 10 L8 5 L13 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n          <\/svg>\n        <\/button>\n      <\/div>\n      <nav class=\"lf-services-nav\" id=\"lf-services-nav\" aria-label=\"Servicios\"><\/nav>\n    <\/div>\n  <\/section>\n\n<\/div>\n\n<script>\n(() => {\n  if (window.__LF_INIT__) return; window.__LF_INIT__ = true;\n  const FRAMES = 301;\n  const ASSET_BASE = '\/wp-content\/uploads\/la-formula';\n  const isMobile = window.matchMedia('(max-width: 1024px)').matches;\n  const config = isMobile\n    ? { webpDir: 'webp-mobile', webpPrefix: 'm_', mp4: 'mp4\/la-formula-camera-mobile-scrub-444.mp4', canvasW: 1080, canvasH: 1920 }\n    : { webpDir: 'webp', webpPrefix: 'CAMERA_LIGHT_16_9_', mp4: 'mp4\/la-formula-camera-scrub-444.mp4', canvasW: 1920, canvasH: 1080 };\n\n  const canvas = document.getElementById('lf-canvas');\n  canvas.width = config.canvasW; canvas.height = config.canvasH;\n  const video = document.getElementById('lf-video');\n  document.getElementById('lf-video-src').src = ASSET_BASE + '\/' + config.mp4;\n  \/* video.load() omitido: canvas-only, no se usa el v\u00eddeo para scrub (2026-06) *\/\n\n  const ctx = canvas.getContext('2d');\n  const textLeft = document.getElementById('lf-text-left');\n  const textRight = document.getElementById('lf-text-right');\n  const section = document.querySelector('.lf-sticky-section');\n  const loader = document.getElementById('lf-loader');\n  const loaderFill = document.getElementById('lf-loader-fill');\n  const loaderText = document.getElementById('lf-loader-text');\n\n  const images = new Array(FRAMES);\n  let loadedCount = 0;\n  function preloadFrame(i) {\n    return new Promise((resolve) => {\n      const img = new Image();\n      const done = () => {\n        loadedCount++;\n        const pct = Math.round((loadedCount \/ FRAMES) * 100);\n        loaderFill.style.transform = 'scaleX(' + (pct \/ 100) + ')';\n        loaderText.textContent = 'Cargando ' + pct + '%';\n        resolve();\n      };\n      img.onload = done; img.onerror = done;\n      img.src = ASSET_BASE + '\/' + config.webpDir + '\/' + config.webpPrefix + String(i).padStart(5, '0') + '.webp';\n      images[i] = img;\n    });\n  }\n  function preloadAll() {\n    const BATCH = 30; let idx = 0;\n    function nextBatch() {\n      if (idx >= FRAMES) return Promise.resolve();\n      const batch = [];\n      for (let k = 0; k < BATCH && idx < FRAMES; k++, idx++) batch.push(preloadFrame(idx));\n      return Promise.all(batch).then(nextBatch);\n    }\n    return nextBatch();\n  }\n\n  \/\/ Modo fijo seg\u00fan viewport: desktop = image sequence (A), mobile = video scrub (B).\n  let mode = 'A';\n  function setMode(m) {\n    mode = m;\n    canvas.hidden = m !== 'A';\n    video.hidden = m !== 'B';\n    if (m === 'B') {\n      const tryPlay = () => video.play().then(() => video.pause()).catch(() => {});\n      if (video.readyState >= 1) tryPlay();\n      else video.addEventListener('loadedmetadata', tryPlay, { once: true });\n    }\n    update();\n  }\n\n  function getScrollProgress() {\n    const rect = section.getBoundingClientRect();\n    const sectionH = rect.height - window.innerHeight;\n    if (sectionH <= 0) return 0;\n    return Math.max(0, Math.min(1, -rect.top \/ sectionH));\n  }\n  function drawFrame(i) {\n    const img = images[i];\n    if (!img || !img.complete || img.naturalWidth === 0) return;\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n  }\n\n  let raf = false;\n  function update() {\n    if (raf) return;\n    raf = true;\n    requestAnimationFrame(() => {\n      raf = false;\n      const p = getScrollProgress();\n      const frame = Math.min(FRAMES - 1, Math.floor(p * FRAMES));\n      if (mode === 'A') drawFrame(frame);\n      else if (mode === 'B' && video.duration && !isNaN(video.duration)) video.currentTime = p * video.duration;\n      const showRight = p > 0.5;\n      textLeft.classList.toggle('is-hidden', showRight);\n      textRight.classList.toggle('is-hidden', !showRight);\n    });\n  }\n  window.addEventListener('scroll', update, { passive: true });\n  window.addEventListener('resize', update);\n  setMode(mode);\n\n  const SERVICES = [\n    { slot: '01', name: 'Direcci\u00f3n' }, { slot: '02', name: 'Producci\u00f3n' },\n    { slot: '03', name: 'C\u00e1mara \/ Steadicam' }, { slot: '04', name: 'Iluminaci\u00f3n' },\n    { slot: '05', name: 'Sonido' }, { slot: '06', name: 'DIT' },\n    { slot: '07', name: 'Edici\u00f3n' }, { slot: '08', name: 'Color' },\n    { slot: '09', name: 'VFX' }, { slot: '10', name: 'Motion \/ 3D' },\n    { slot: '11', name: 'Realizaci\u00f3n multic\u00e1mara' }, { slot: '12', name: 'Streaming' },\n    { slot: '13', name: 'Casting' }, { slot: '14', name: 'Stage \/ Escenograf\u00eda' },\n    { slot: '15', name: 'PM de eventos' }, { slot: '16', name: 'Local Service' },\n    { slot: '17', name: 'Rental Service' },\n  ];\n  const stack = document.getElementById('lf-services-stack');\n  const titleEl = document.getElementById('lf-services-title');\n  const counterEl = document.getElementById('lf-services-counter-num');\n  const servSection = document.querySelector('.lf-services-section');\n  const navEl = document.getElementById('lf-services-nav');\n\n  \/\/ Bot\u00f3n \"\u2191 secci\u00f3n anterior\": fade-out del pin \u2192 jump instant\u00e1neo \u2192 fade-in.\n  \/\/ As\u00ed no se ven los 17 cards desfilando al hacer el scroll de vuelta.\n  const upBtn = document.querySelector('.lf-services-up');\n  const pinEl = document.querySelector('.lf-services-pin');\n  if (upBtn && pinEl) {\n    let isLeaving = false;\n    upBtn.addEventListener('click', () => {\n      if (isLeaving) return;\n      const prev = document.querySelector('.lf-sticky-section');\n      if (!prev) return;\n      isLeaving = true;\n      pinEl.classList.add('is-leaving');\n      \/\/ Snap el estado de los text-blocks de la c\u00e1mara a \"inicio\" (left visible,\n      \/\/ right oculto) SIN transici\u00f3n. Si no, al aterrizar arriba ver\u00edamos el\n      \/\/ texto derecho hacer fade-out de 550ms. El usuario est\u00e1 a\u00fan viendo los\n      \/\/ cards (pin todav\u00eda en el viewport), as\u00ed que este cambio no se ve.\n      if (textLeft && textRight) {\n        textLeft.style.transition = 'none';\n        textRight.style.transition = 'none';\n        textLeft.classList.remove('is-hidden');\n        textRight.classList.add('is-hidden');\n        void textLeft.offsetWidth; \/\/ fuerza reflow para aplicar el \"sin transici\u00f3n\"\n      }\n      \/\/ Espera al final del fade (260ms) antes de saltar.\n      \/\/ Usamos window.scrollTo con behavior:'instant' (que S\u00cd est\u00e1 en la spec\n      \/\/ de scrollTo, a diferencia de scrollIntoView) para garantizar el salto\n      \/\/ sin animaci\u00f3n incluso si html tiene scroll-behavior: smooth.\n      setTimeout(() => {\n        const targetY = prev.getBoundingClientRect().top + window.scrollY;\n        \/\/ Forma legacy de 2 args: universalmente soportada y siempre instant.\n        \/\/ Evita problemas con el valor 'instant' del options object en m\u00f3vil.\n        window.scrollTo(0, targetY);\n        \/\/ Quita la clase tras un frame: el pin ya est\u00e1 fuera de viewport,\n        \/\/ as\u00ed que el fade-in no produce flash. Restauramos las transitions\n        \/\/ de los text-blocks para que el scroll natural vuelva a animarlas.\n        requestAnimationFrame(() => {\n          pinEl.classList.remove('is-leaving');\n          if (textLeft) textLeft.style.transition = '';\n          if (textRight) textRight.style.transition = '';\n          isLeaving = false;\n        });\n      }, 260);\n    });\n  }\n\n  SERVICES.forEach((s, i) => {\n    const img = document.createElement('img');\n    img.className = 'lf-service-img';\n    img.alt = s.name;\n    img.loading = i < 3 ? 'eager' : 'lazy';\n    img.decoding = 'async';\n    img.src = ASSET_BASE + '\/services\/service-' + s.slot + '.webp';\n    if (i === 0) img.style.opacity = '1';\n    stack.appendChild(img);\n  });\n\n  \/\/ Estado del salto: durante un click-jump bloqueamos updateServices y hacemos\n  \/\/ crossfade directo source\u2192target (sin renderizar las intermedias).\n  let isJumping = false;\n\n  \/\/ Construir nav pills + click-to-jump (crossfade directo)\n  const navBtns = SERVICES.map((s, i) => {\n    const b = document.createElement('button');\n    b.type = 'button';\n    b.className = 'lf-services-nav-btn' + (i === 0 ? ' active' : '');\n    b.dataset.idx = String(i);\n    b.textContent = s.name;\n    b.addEventListener('click', () => {\n      const rect = servSection.getBoundingClientRect();\n      const absTop = rect.top + window.scrollY;\n      const sectionH = rect.height - window.innerHeight;\n      if (sectionH <= 0) return;\n      const targetIdx = i;\n      const sourceIdx = lastTitleIdx >= 0 ? lastTitleIdx : 0;\n      if (sourceIdx === targetIdx) return;\n\n      isJumping = true;\n      const imgs = stack.children;\n\n      \/\/ Aplicar transici\u00f3n CSS sobre las im\u00e1genes para el crossfade.\n      for (let k = 0; k < imgs.length; k++) {\n        imgs[k].style.transition = 'opacity 700ms cubic-bezier(.2,.7,.2,1)';\n      }\n      \/\/ Reset al estado inicial del crossfade: solo source visible.\n      for (let k = 0; k < imgs.length; k++) {\n        imgs[k].style.opacity = (k === sourceIdx) ? '1' : '0';\n      }\n      \/\/ Reflow para que el navegador registre el estado anterior antes del cambio.\n      void stack.offsetWidth;\n      \/\/ Disparar crossfade: source\u21920, target\u21921.\n      imgs[sourceIdx].style.opacity = '0';\n      imgs[targetIdx].style.opacity = '1';\n\n      \/\/ Actualizar UI inmediatamente (t\u00edtulo\/contador\/pill activa).\n      titleEl.textContent = SERVICES[targetIdx].name;\n      counterEl.textContent = SERVICES[targetIdx].slot;\n      if (navBtns[sourceIdx]) navBtns[sourceIdx].classList.remove('active');\n      navBtns[targetIdx].classList.add('active');\n      lastTitleIdx = targetIdx;\n\n      \/\/ Saltar el scroll de la p\u00e1gina INSTANT\u00c1NEAMENTE al centro del slide destino,\n      \/\/ para que el resto del flujo (scroll manual posterior) quede natural desde ah\u00ed.\n      const target = absTop + sectionH * ((targetIdx + 0.5) \/ SERVICES.length);\n      window.scrollTo({ top: target, behavior: 'instant' });\n\n      \/\/ Tras el crossfade, restaurar transitions y desbloquear updateServices.\n      setTimeout(() => {\n        for (let k = 0; k < imgs.length; k++) {\n          imgs[k].style.transition = '';\n        }\n        isJumping = false;\n      }, 720);\n    });\n    navEl.appendChild(b);\n    \/\/ Tras la 10\u00aa pill (i=9 \u2192 \"Motion \/ 3D\") inserta un break flex para\n    \/\/ forzar que las pills siguientes vayan a la fila 2 en desktop.\n    \/\/ Reparte 10 + 7 que es lo m\u00e1s equilibrado en peso visual considerando\n    \/\/ que la 2\u00aa mitad lleva pills m\u00e1s anchas (Realizaci\u00f3n multic\u00e1mara, etc).\n    if (i === 9) {\n      const br = document.createElement('div');\n      br.className = 'lf-services-nav-break';\n      br.setAttribute('aria-hidden', 'true');\n      navEl.appendChild(br);\n    }\n    return b;\n  });\n\n  let lastTitleIdx = 0;\n  function updateServices() {\n    if (isJumping) return;\n    const rect = servSection.getBoundingClientRect();\n    const sectionH = rect.height - window.innerHeight;\n    if (sectionH <= 0) return;\n    const p = Math.max(0, Math.min(1, -rect.top \/ sectionH));\n    const N = SERVICES.length;\n    const floatPos = p * N;\n    \/\/ Hysteresis: una vez en slide N, te quedas hasta haber entrado claramente\n    \/\/ en territorio de N+1 (offset 0.35 del slot = ~21vh de scroll en desktop).\n    \/\/ Mismo tratamiento al volver atr\u00e1s. Evita \"saltos sin querer\" al pasar\n    \/\/ por el l\u00edmite con inercia de wheel\/trackpad.\n    const HYST = 0.35;\n    let idx = lastTitleIdx;\n    if (floatPos >= lastTitleIdx + 1 + HYST) {\n      idx = Math.min(N - 1, Math.floor(floatPos));\n    } else if (floatPos < lastTitleIdx - HYST) {\n      idx = Math.max(0, Math.floor(floatPos));\n    }\n    if (idx === lastTitleIdx) return;\n\n    \/\/ Crossfade time-based v\u00eda CSS transition (.lf-service-img). El JS solo\n    \/\/ marca cu\u00e1l es la \"visible\" (opacity 1) y el resto a 0.\n    const imgs = stack.children;\n    for (let i = 0; i < imgs.length; i++) {\n      imgs[i].style.opacity = (i === idx) ? '1' : '0';\n    }\n    titleEl.textContent = SERVICES[idx].name;\n    counterEl.textContent = SERVICES[idx].slot;\n    if (lastTitleIdx >= 0 && navBtns[lastTitleIdx]) navBtns[lastTitleIdx].classList.remove('active');\n    const btn = navBtns[idx];\n    if (btn) {\n      btn.classList.add('active');\n      if (navEl.scrollWidth > navEl.clientWidth) {\n        const target = btn.offsetLeft - (navEl.clientWidth - btn.offsetWidth) \/ 2;\n        navEl.scrollLeft = Math.max(0, target);\n      }\n    }\n    lastTitleIdx = idx;\n  }\n  let servRaf = false;\n  function onServScroll() {\n    if (servRaf) return;\n    servRaf = true;\n    requestAnimationFrame(() => { servRaf = false; updateServices(); });\n  }\n  window.addEventListener('scroll', onServScroll, { passive: true });\n  window.addEventListener('resize', updateServices);\n  updateServices();\n\n  const fadeTargets = document.querySelectorAll('.lf-page .lf-fade');\n  if ('IntersectionObserver' in window) {\n    \/\/ threshold:0 dispara al entrar\/salir cualquier p\u00edxel del viewport.\n    \/\/ Esto funciona bien con secciones muy altas (servicios=10080px) donde\n    \/\/ un threshold porcentual nunca se cumplir\u00eda aunque haya intersecci\u00f3n.\n    const io = new IntersectionObserver((entries) => {\n      entries.forEach((e) => { e.target.classList.toggle('in-view', e.isIntersecting); });\n    }, { threshold: 0 });\n    fadeTargets.forEach((el) => io.observe(el));\n  } else {\n    fadeTargets.forEach((el) => el.classList.add('in-view'));\n  }\n\n  if (mode === 'B') {\n    const ready = () => {\n      loader.classList.add('is-done');\n      setTimeout(() => { loader.style.display = 'none'; }, 300);\n      update();\n    };\n    if (video.readyState >= 1) ready();\n    else video.addEventListener('loadedmetadata', ready, { once: true });\n    \/\/ Android: video.currentTime scrub es janky en Chrome Android (el decoder\n    \/\/ hardware no busca frames suavemente). En cuanto los webp terminen el\n    \/\/ preload en background, pasamos a canvas (mode A) que s\u00ed es fluido.\n    \/\/ iOS hace scrub de video por hardware, ah\u00ed mode B se queda.\n    const isAndroid = \/Android\/i.test(navigator.userAgent);\n    if (isAndroid) {\n      preloadAll().then(() => setMode('A'));\n    } else {\n      preloadAll();\n    }\n  } else {\n    preloadAll().then(() => {\n      drawFrame(0);\n      update();\n      loader.classList.add('is-done');\n      setTimeout(() => { loader.style.display = 'none'; }, 500);\n    });\n  }\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":5,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-6365","page","type-page","status-publish","hentry"],"acf":{"full_width":false,"header_transparent":false,"header_white":false},"_links":{"self":[{"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/pages\/6365","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/comments?post=6365"}],"version-history":[{"count":1,"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/pages\/6365\/revisions"}],"predecessor-version":[{"id":6366,"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/pages\/6365\/revisions\/6366"}],"wp:attachment":[{"href":"https:\/\/lahuellastudio.com\/en\/wp-json\/wp\/v2\/media?parent=6365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}