molenda.dev

Hi, Hubert here đź‘‹

I’m Webflow-native, GSAP enjoyer, UX-capable. Web dev that can do design, improve SEO and speed scores. My core value is to make every pixel look perfect – sometimes with a bit of 3D magic. I’ve made sites for tech clients as well as boutique brands.

See my works and say hi!
gsap
custom interactions
Pre-release
Live site
motion.js
integrations
Live site
gsap
design: Me!
Live site
motion.js
3D: Me!
design: Me!
Work in progress
Favorites
molenda.dev
iCloud
Inbox
Drafts
Sent
Trash
Archived
Inbox
2137 messages, 6 unread
molenda.dev
Inbox - iCloud 8:10
Photo Dump
To: You
From: hubert@molenda.dev
molenda.dev
Inbox - iCloud 8:10
3D UI Examples
To: You
From: hubert@molenda.dev
molenda.dev
Inbox - iCloud 8:10
Festival Lineup UI
To: You
From: hubert@molenda.dev
molenda.dev
Inbox - iCloud 8:10
Photo Dump
To: You
From: hubert@molenda.dev
molenda.dev
Inbox - iCloud 8:10
Photo Dump
To: You
From: hubert@molenda.dev
gsap.registerPlugin(SplitText);
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');

function inView(selectors, animationFns, options = {}) {
  const observer = new IntersectionObserver((entries, obs) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting && !entry.target.dataset.animated) {
        entry.target.dataset.animated = 'true';
        const fns = Array.isArray(animationFns) ? animationFns : [animationFns];
        fns.forEach((fn) => fn(entry.target));
        obs.unobserve(entry.target);
      }
    });
  }, { threshold: 0, ...options });

  (Array.isArray(selectors) ? selectors : [selectors]).forEach((selector) => {
    document.querySelectorAll(selector).forEach((el) => observer.observe(el));
  });
}

const Presets = {
  fade: (y = 60, duration = 0.3, delay = 0, stagger = 0) => [
    { opacity: 0, autoAlpha: 0, y },
    { opacity: 1, autoAlpha: 1, y: 0, duration, stagger, delay, ease: 'power2.out' },
  ],
  opacity: (duration = 0.3, delay = 0, stagger = 0) => [
    { opacity: 0, autoAlpha: 0 },
    { opacity: 1, autoAlpha: 1, duration, stagger, delay, ease: 'power2.out' },
  ],
  headerAnim: (y = 80, duration = 0.5) => [
    { y, opacity: 0, autoAlpha: 0 },
    { y: 0, opacity: 1, autoAlpha: 1, duration, stagger: 0.02, ease: 'power2.out' },
  ],
  mask: () => [
    { maskSize: '0% 100%' },
    { maskSize: '100% 100%', duration: 0.6, delay: 0.2, ease: 'power1.out' },
  ],
};

const Handlers = {
  logo: (target) => {
    gsap.fromTo(
      target.querySelectorAll('[data-gsap="fade"]'),
      Presets.fade()[0],
      Presets.fade(60, .3, 1.3)[1]
    );
  },
  heading: (target) => {
    gsap.from(
      target.querySelectorAll('.line'), {
      rotationX: -10,
      rotationZ: -1,
      transformOrigin: "50% 50% -160px",
      opacity: 0,
      duration: 0.5,
      ease: "power3",
      stagger: 0.3
    })
  },
  button: (target) => {
    gsap.fromTo(
      target.querySelectorAll('[data-gsap="line"], [data-gsap="buttons"] a'),
      Presets.fade(-60)[0],
      Presets.fade(0, .3, 1.2, .25)[1]
    );
  },
  elements: (target) => {
    gsap.fromTo(
      target.querySelectorAll('[data-gsap="line"]'),
      Presets.fade()[0],
      Presets.fade(0, .3, 0, .15)[1]
    );
  },
  logo2: (target) => {
    gsap.fromTo(
      target.querySelectorAll('[data-gsap="fade"]'),
      Presets.fade()[0],
      Presets.fade(60, .3, .3)[1]
    );
  },
  mail: (target) => {
    gsap.fromTo(
      target.querySelector('[data-gsap="mask"]'),
      Presets.mask()[0],
      Presets.mask()[1]
    )
  },
  calendar: () => {
    gsap.to(".home_launch-calendar_weeks div", { y: -60, duration: 0 })
    let t1 = gsap.timeline({ defaults: { duration: .25, ease: "power3" } });
    t1.to(".home_launch-calendar_tile", { x: "0rem" })
      .to(".home_launch-calendar_tile", { x: "13.5rem" }, "+=.1")
      .to(".home_launch-calendar_weeks div", { y: 0, opacity: 1 }, "-=.35")
      .to(".home_launch-calendar_tile", { x: "40.5rem" }, "+=.1")
      .to(".home_launch-calendar_tile", { x: "54rem" }, "+=.1")
      // .to(".home_launch-calendar_tile", { x: "67.5rem" }, "+=.1")
      // .to(".home_launch-calendar_tile", { x: "81rem" }, "+=.1")
      .to(".home_launch-calendar_glow", { opacity: 1, duration: 1.6 }, "-=.1")
    gsap.to(".home_launch-calendar_tile", { opacity: 1, duration: .6 })
  },
  links: (target) => {
    gsap.fromTo(
      target.querySelectorAll('a'),
      Presets.fade(50)[0],
      Presets.fade(0, .3, 0, .06)[1]
    );
    gsap.fromTo(
      target.querySelectorAll('.hide-mobile-landscape'),
      Presets.fade(0)[0],
      Presets.fade(0, .3, .3, .06)[1]
    );
  },
  radar: (target) => {
    const resetTransform = { rotation: 0, skewX: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 }
    let t2 = gsap.timeline({ defaults: { duration: .4, ease: "power4.out" } });
    t2.to("#line-1", resetTransform)
      .to("#line-2", resetTransform, "-=.4")
      .to("#rect", { opacity: 1 }, "-=.1")
      .to("#circle-1", resetTransform, "-=.1")
      .to("#line-3, #line-5", resetTransform, "+=.1")
      .to("#line-4, #line-6", resetTransform, "-=.4")
      .to("#circle-2", resetTransform, "-=.2")

    gsap.fromTo(
      target.querySelectorAll('.dot'),
      Presets.opacity()[0],
      Presets.opacity(.3, .3, .1)[1]
    );
  },
};

function gsapInit() {
  if (mediaQuery.matches) return;

  SplitText.create("[data-gsap='lines']", {
    type: "lines",
    linesClass: "line",
    tag: "span",
  });

  inView('#start', [Handlers.heading, Handlers.logo, Handlers.button]);
  inView('#about', Handlers.heading, { threshold: 0.4 });
  inView('#waitlist', [Handlers.elements, Handlers.mail], { threshold: 0.4 })
  inView('#launch', [Handlers.elements, Handlers.calendar], { threshold: 0.4 })
  inView('#footer', [Handlers.elements, Handlers.logo2, Handlers.links, Handlers.radar], { threshold: 0.4 })
}

document.addEventListener('DOMContentLoaded', () => {
  let eyeInstance = eye();
  gsapInit()
  eyeInstance
  inView("#timer", timer, { threshold: 0.4 })
  radarInit()
});