/* global React */ // Shared building blocks for the Waterlemon site const { useState, useEffect, useRef, useLayoutEffect } = React; /* ─────────── WATERLEMON LOGO MARK (lemon + W) ─────────── */ // Geometry from official Watermelon_logo_editable-01.svg function LemonMark({ size = 24, color = "#FF2D87", inner = "#FFFCF5", outline = false }) { return ( ); } /* Full wordmark — lemon + WATERLEMON STUDIO */ function LemonWordmark({ height = 36, color = "var(--wl-ink)", accent = "var(--wl-accent)" }) { return ( WATERLEMON studio ); } /* Decorative lemon — a floating illustrated lemon (silhouette + W inside + leaf) */ function FloatingLemon({ size = 80, color = "var(--wl-accent)", style = {}, rotate = 0 }) { return (
{/* lemon body */} {/* leaf */} {/* W mark in cream inside */} {/* tiny shine */}
); } /* ─────────── CUSTOM CURSOR (limón sticker) ─────────── */ function WaterlemonCursor() { return null; } function _UnusedCursor({ enabled = true }) { const ref = useRef(null); const [hover, setHover] = useState(false); const [hidden, setHidden] = useState(true); useEffect(() => { if (!enabled) { document.body.classList.remove("has-wl-cursor"); return; } document.body.classList.add("has-wl-cursor"); let raf = null; let tx = 0, ty = 0, cx = 0, cy = 0; const onMove = (e) => { tx = e.clientX; ty = e.clientY; setHidden(false); if (!raf) { raf = requestAnimationFrame(loop); } }; const loop = () => { cx += (tx - cx) * 0.18; cy += (ty - cy) * 0.18; if (ref.current) { ref.current.style.transform = `translate(${cx}px, ${cy}px) translate(-50%, -50%)`; } if (Math.abs(tx - cx) > 0.4 || Math.abs(ty - cy) > 0.4) { raf = requestAnimationFrame(loop); } else { raf = null; } }; const onOver = (e) => { const t = e.target; if (t.closest && t.closest("a, button, .service-row, .case-card, .testi-card, .polaroid, [data-cursor='hover']")) { setHover(true); } }; const onOut = (e) => { const t = e.target; if (t.closest && t.closest("a, button, .service-row, .case-card, .testi-card, .polaroid, [data-cursor='hover']")) { setHover(false); } }; const onLeave = () => setHidden(true); const onEnter = () => setHidden(false); window.addEventListener("mousemove", onMove); window.addEventListener("mouseover", onOver); window.addEventListener("mouseout", onOut); document.addEventListener("mouseleave", onLeave); document.addEventListener("mouseenter", onEnter); return () => { document.body.classList.remove("has-wl-cursor"); window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseover", onOver); window.removeEventListener("mouseout", onOut); document.removeEventListener("mouseleave", onLeave); document.removeEventListener("mouseenter", onEnter); if (raf) cancelAnimationFrame(raf); }; }, [enabled]); if (!enabled) return null; return ( ); } /* ─────────── NAV ─────────── */ const NAV_SERVICES = [ { n: "01", slug: "redes-sociales", name: "Redes Sociales" }, { n: "02", slug: "contenido", name: "Creación de Contenido" }, { n: "03", slug: "ads-meta", name: "Paid Media (Meta)" }, { n: "04", slug: "seo", name: "SEO Orgánico" }, { n: "05", slug: "google-ads", name: "Google Ads" }, { n: "06", slug: "web", name: "Diseño Web" }, { n: "07", slug: "estrategia", name: "Estrategia 360" }, { n: "08", slug: "video", name: "Producción Audiovisual" }, { n: "09", slug: "consultoria", name: "Consultoría 1:1" }, ]; function Nav({ active = "home", onPink = false }) { const [scrolled, setScrolled] = useState(false); const [open, setOpen] = useState(false); const [ddOpen, setDdOpen] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 24); onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const links = [ { k: "home", label: "Inicio", href: "index.html" }, { k: "servicios", label: "Servicios", href: "servicios.html" }, { k: "casos", label: "Proyectos", href: "casos.html" }, { k: "equipo", label: "Equipo", href: "equipo.html" }, { k: "contacto", label: "Contacto", href: "contacto.html" }, ]; return (
{onPink && !scrolled ? Waterlemon Studio : Waterlemon Studio}
¿Hablamos?
{links.map(l => {l.label}{l.k === "servicios" &&
{NAV_SERVICES.map(s => {s.name})}
}
)}

contacto

info@waterlemonstudio.com

+34 698 38 65 56

); } /* ─────────── MARQUEE BAND ─────────── */ function MarqueeBand({ items, variant = "ink", speed = 38 }) { const list = (
{items.map((it, i) => ( {it.label} ))}
); return (
{list} {list}
); } /* ─────────── CLIENT TICKER ─────────── */ function ClientTicker() { const clients = [ { name: "Club Pilates", badge: "+25" }, { name: "O'Retorniño" }, { name: "Spandex" }, { name: "Orangetheory Valencia" }, { name: "ZEISS Vision Center" }, { name: "Flippo" }, { name: "The Digital Brands House" }, { name: "Centros de Artes Marciales" }, { name: "Ayres del Sur" }, { name: "Anita Gelato" }, { name: "CM Ibérica" }, { name: "Casa Carbó" }, { name: "Berga & Berga" }, ]; const row = (
{clients.concat(clients).map((c, i) => ( {c.name} {c.badge && {c.badge}} ♥ ))}
); return (
CLIENTES QUE NOS HAN FICHADO COMO PARTNERS
{row}
); } /* ─────────── FOOTER PHRASES ─────────── */ // Each phrase is an array of lines; each line is { text, color } // color: "ink" (default cream/bone), "accent" (pink), "out" (outlined) const FOOTER_PHRASES = { "hablemos": [ [{ t: "HABLEMOS " }, { t: "SIN PRISA, ", c: "accent" }, { t: "PERO CON GANAS.", c: "out" }], ], "water": [ [{ t: "SOMOS EL " }, { t: "WATER ", c: "accent" }, { t: "DE TU " }, { t: "LEMON.", c: "out" }], ], "obsesion": [ [{ t: "TU MARCA, " }], [{ t: "NUESTRA ", c: "out" }, { t: "OBSESIÓN.", c: "accent" }], ], "leads": [ [{ t: "MÁS LEADS. " }], [{ t: "MEJOR ", c: "accent" }, { t: "MARCA." }], [{ t: "EMPECEMOS.", c: "out" }], ], "ideas": [ [{ t: "IDEAS " }, { t: "REALES,", c: "accent" }], [{ t: "PARA MARCAS QUE ", c: "out" }, { t: "CRECEN." }], ], "llevemos": [ [{ t: "PROBABLEMENTE " }], [{ t: "NOS LLEVEMOS ", c: "accent" }, { t: "BIEN.", c: "out" }], ], "24h": [ [{ t: "¿HABLAMOS? " }], [{ t: "TE LEEMOS EN ", c: "out" }, { t: "24H.", c: "accent" }], ], }; function FooterPhrase({ phraseKey = "hablemos" }) { const lines = FOOTER_PHRASES[phraseKey] || FOOTER_PHRASES.hablemos; return (
{lines.map((line, i) => (
{line.map((seg, j) => ( {seg.t} ))}
))}
); } function Footer({ phrase = "hablemos" }) { return ( ); } /* ─────────── IN-VIEW HOOK ─────────── */ function useInView(options = {}) { const ref = useRef(null); const [inView, setInView] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; const obs = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setInView(true); obs.disconnect(); } }, { threshold: 0.18, ...options } ); obs.observe(el); return () => obs.disconnect(); }, []); return [ref, inView]; } /* ─────────── COUNTER ─────────── */ function Counter({ to, suffix = "", duration = 1600, decimals = 0 }) { const [ref, inView] = useInView(); const [val, setVal] = useState(0); useEffect(() => { if (!inView) return; const start = performance.now(); let raf; const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(eased * to); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [inView, to, duration]); const display = decimals > 0 ? val.toFixed(decimals) : Math.round(val).toLocaleString("es-ES"); return {display}{suffix}; } /* ─────────── SCRIBBLES & DOODLES ─────────── */ function CircleScribble({ style }) { const [ref, inView] = useInView(); return (
); } function DrawArrow({ style, color = "var(--wl-accent)" }) { const ref = useRef(null); const [ref2, inView] = useInView(); return (
); } function Sparkle({ style, size = 22, color = "currentColor" }) { return ( ); } function HeartScribble({ style, size = 28 }) { return ( ); } function StarDoodle({ style, size = 26 }) { return ( ); } function SmileyDoodle({ style, size = 28 }) { return ( ); } /* expose globally for sibling babel scripts */ Object.assign(window, { LemonMark, LemonWordmark, FloatingLemon, WaterlemonCursor, Nav, MarqueeBand, ClientTicker, Footer, FooterPhrase, FOOTER_PHRASES, useInView, Counter, CircleScribble, DrawArrow, Sparkle, HeartScribble, StarDoodle, SmileyDoodle });