Heading 1

Heading 2

Heading 3

This is some text inside of a div block.
Turn scattered ideas into work that

runs itself.

DLJK LLC helps businesses and organizations build clear, credible systems through website development, digital services, business consulting, and organizational support.

Four ways to build momentum

Structured services that trade complexity and disarray for elegant workflows and functional design.

Website Development

Clean, credible digital homes for businesses with somewhere to go.

business sites
landing pages
redesigns
cms setup

Digital Services

Constructing the pipes and forms that make a digital operation run.

Digital organization
Workflows
Online systems

Business Consulting

A practical second set of eyes for the moving parts of your business.

Business organization
Digital strategy
Process design

Organizational support

Maintaining steady, usable systems behind the visible work.

Admin systems
Documentation
Coordination

Your Vision.
Our Support.

You don't need a big agency to move an idea forward. DLJK LLC. understands the goal, identifies the right pieces, and helps assemble them with care — creativity, technology, organization, and business support, in the proportion your work actually needs.

ideas → action
DLJK LLC.

A clear route from idea to motion

No overcomplication. Every engagement starts with understanding the real needs and ends with something you can use.

Understand

Learn your goals, needs, constraints, and what's currently getting in the way.

Plan

Identify the right tools, services, priorities, and next steps for the work.

Build

Create the website, system, resource, or structure that makes the plan tangible.

Support

Help you move forward practically with updates, guidance, and continued clarity.

Your business deserves a website that works for you

Your website is often the first impression of your business. DLJK LLC. builds clean, credible, professional sites that make it easier for the right people to understand what you do and take the next step.

New Websites
Website Redesigns
CMS Content
Ongoing Support
Explore Our Services

For the work that has somewhere to go

small businesses
Entrepreneurs
Startups
Nonprofits and organizations
Service providers
Community organizations
Professionals
organizational support
Don't see your business here?
Let's talk →

Built to be useful

Portfolio in progress
websites

Site & digital projects

Designed to make a business legible at a glance.
digital projects

Systems that stick

Resources and solutions that keep work moving.
support

Business support

Organization, workflows, consulting, and implementation.

Have an idea?
Let's talk.

Whether you need a new website, better business organization, or support with a digital project, start by sharing what you're working on.
Let's Talk

Let's work together

Have a project in mind? Tell us about your business, your goals, and where you need help. We'll take it from there.

Response window: 2–3 business days

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
document.addEventListener("DOMContentLoaded", () => {
  init();
});

function init() {
  gsap.registerPlugin(CustomEase);
  const smoothEase = CustomEase.create("smoothEase", ".32, .72, 0, 1");

  // Run both animations
  initSecondaryButtonTilt();
  initButtonDotHover(smoothEase);
  svgHover();
  tagsFallAnim();
  workAnim();
  frontCardsAnim();
}

function initButtonDotHover(smoothEase) {
  document.querySelectorAll(".btn").forEach((btn) => {
    const bg = btn.querySelector(".btn-bg");
    const dot = btn.querySelector(".btn-dot");
    const text = btn.querySelector(".btn-text");
    if (!bg || !dot || !text) return;

    const enterTl = gsap
      .timeline({ paused: true })
      .to(
        bg,
        { scaleX: 1.02, scaleY: 1.07, duration: 0.5, ease: smoothEase },
        0
      )
      .to(text, { x: -5, duration: 0.5, ease: smoothEase }, 0)
      .to(
        dot,
        {
          scaleX: 0.23,
          scaleY: 0.2,
          y: -19.2,
          duration: 0.2,
          ease: "power1.in",
        },
        0
      )
      .to(
        dot,
        {
          scaleX: 0.21,
          scaleY: 0.76,
          y: -1.6,
          borderRadius: 4,
          duration: 0.36,
          ease: "back.out(2.2)",
        },
        0.2
      );

    function playEnter() {
      enterTl.timeScale(1);
      enterTl.play();
    }
    function playLeave() {
      enterTl.timeScale(1.2).reverse();
    }

    btn.addEventListener("mouseenter", playEnter);
    btn.addEventListener("mouseleave", playLeave);
    btn.addEventListener("focus", playEnter);
    btn.addEventListener("blur", playLeave);
  });
}

function initSecondaryButtonTilt() {
  document.querySelectorAll(".btn.secondary").forEach((btn) => {
    const text = btn.querySelector(".btn-text");
    if (!text) return;

    const btn2Tl = gsap
      .timeline({ paused: true })
      .to(btn, { scale: 1.05, rotateZ: 10, duration: 0.4 })
      .to(text, { rotateZ: -10, duration: 0.4 }, "<");

    function playEnter() {
      btn2Tl.timeScale(1);
      btn2Tl.play();
    }
    function playLeave() {
      btn2Tl.timeScale(1.2).reverse();
    }

    btn.addEventListener("mouseenter", playEnter);
    btn.addEventListener("mouseleave", playLeave);
    btn.addEventListener("focus", playEnter);
    btn.addEventListener("blur", playLeave);
  });
}

function svgHover() {
  const mm = gsap.matchMedia();

  mm.add("(hover: hover) and (pointer: fine)", () => {
    const wrap = document.querySelector(".layout.websites");
    const items = gsap.utils.toArray(".hightlights", wrap);
    const imgs = gsap.utils.toArray(".highlights-img", wrap);

    const dist = 20;

    // Stack all images, show only the first
    gsap.set(imgs, { display: "block", autoAlpha: 0, y: dist });
    gsap.set(imgs[0], { autoAlpha: 1, y: 0 });

    let current = 0;
    let tl;

    const handlers = items.map((item, i) => {
      const handler = () => {
        if (i === current) return;

        // Stop any running sequence so fast hovering never stacks animations
        if (tl) tl.kill();

        tl = gsap.timeline();

        // 1. Everything else fades out and drifts up
        tl.to(
          imgs.filter((img) => img !== imgs[i]),
          {
            autoAlpha: 0,
            y: -dist,
            duration: 0.3,
            ease: "power2.in",
            overwrite: true,
          }
        );

        // 2. Only after that, the new image rises in from below
        tl.fromTo(
          imgs[i],
          { autoAlpha: 0, y: dist },
          {
            autoAlpha: 1,
            y: 0,
            duration: 0.6,
            ease: "power3.out",
            overwrite: true,
          }
        );

        current = i;
      };
      item.addEventListener("mouseenter", handler);
      return handler;
    });

    return () => {
      if (tl) tl.kill();
      items.forEach((item, i) =>
        item.removeEventListener("mouseenter", handlers[i])
      );
      gsap.set(imgs, { clearProps: "all" });
    };
  });
}

function tagsFallAnim() {
  const mm = gsap.matchMedia();

  // Pointer devices only. Reduced motion users get nothing.
  mm.add(
    "(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)",
    () => {
      const section = document.querySelector(".section.audience");
      const tags = gsap.utils.toArray(".audience-tag", section);

      const GROUND_PADDING = 12; // gap between the tags and the section's bottom edge
      const RETURN_DELAY = 1.2; // seconds spent lying on the ground
      const RETURN_TIME = 2; // slow return to place

      const state = new WeakMap(); // tag -> { falling: boolean, tl: timeline }

      const handlers = tags.map((tag) => {
        state.set(tag, { falling: false, tl: null });

        const onEnter = () => {
          const s = state.get(tag);
          if (s.falling) return; // already dropping, let it land
          if (s.tl) s.tl.kill(); // interrupted mid-return: fall again from where it is

          // Distance from the tag's natural position to the ground
          const currentY = gsap.getProperty(tag, "y");
          const naturalBottom = tag.getBoundingClientRect().bottom - currentY;
          const groundBottom =
            section.getBoundingClientRect().bottom - GROUND_PADDING;
          const dist = Math.max(0, groundBottom - naturalBottom);

          const fall = Math.max(1.2, Math.min(2.4, dist / 250)); // slower fall

          s.falling = true;
          s.tl = gsap.timeline();

          s.tl
            // Fall: bounce.out reads as gravity plus a bounce on impact
            .to(
              tag,
              {
                y: dist,
                x: gsap.utils.random(-25, 25),
                duration: fall,
                ease: "bounce.out",
                onComplete: () => {
                  s.falling = false;
                },
              },
              0
            )
            // Tilt while dropping (bounce.out first hits the ground at about 36% of the duration)
            .to(
              tag,
              {
                rotation: gsap.utils.random(-30, 30),
                duration: fall * 0.36,
                ease: "power1.in",
              },
              0
            )
            // Straighten out flat as it lands
            .to(
              tag,
              {
                rotation: 0,
                duration: fall * 0.3,
                ease: "power2.out",
              },
              fall * 0.36
            )
            // Rest on the ground, then drift slowly back home
            .to(
              tag,
              {
                y: 0,
                x: 0,
                rotation: 0,
                duration: RETURN_TIME,
                ease: "power3.inOut",
              },
              `+=${RETURN_DELAY}`
            );
        };

        tag.addEventListener("mouseenter", onEnter);
        return onEnter;
      });

      // Cleanup if the media query stops matching
      return () => {
        tags.forEach((tag, i) => {
          tag.removeEventListener("mouseenter", handlers[i]);
          const s = state.get(tag);
          if (s && s.tl) s.tl.kill();
        });
        gsap.set(tags, { clearProps: "all" });
      };
    }
  );
}

function frontCardsAnim() {
  const mm = gsap.matchMedia();

  // Pointer devices only, so touch devices never run this
  mm.add("(hover: hover) and (pointer: fine)", () => {
    const cards = gsap.utils.toArray(".front-card");

    const cleanups = cards.map((card) => {
      const anim = card.querySelector(".front-card-anim");
      const texts = card.querySelectorAll(".text-md"); // both texts, h3 stays visible

      const tl = gsap
        .timeline({ paused: true, defaults: { ease: "power3.out" } })
        .to(anim, { opacity: 1, duration: 0.3 })
        .to(anim, { scale: 1, duration: 0.5 }, "<")
        .to(texts, { opacity: 0, duration: 0.3 }, "<");

      const enter = () => tl.play();
      const leave = () => tl.reverse();
      card.addEventListener("mouseenter", enter);
      card.addEventListener("mouseleave", leave);

      return () => {
        card.removeEventListener("mouseenter", enter);
        card.removeEventListener("mouseleave", leave);
        tl.kill();
        gsap.set([anim, texts], { clearProps: "all" });
      };
    });

    return () => cleanups.forEach((fn) => fn());
  });
}

function workAnim() {
  const colors = [
    { bg: "#2e5d86", ink: "#fff" },
    { bg: "#6f8c7b", ink: "#fff" },
    { bg: "#1b2c46", ink: "#fff" },
    { bg: "#181511", ink: "#fff" },
  ];

  const items = [...document.querySelectorAll(".works-row")].map((row, i) => {
    const { bg, ink } = colors[i] || colors[0];

    const tl = gsap
      .timeline({
        paused: true,
        defaults: {
          duration: 0.2,
          ease: "power3.in",
          easeReverse: "power3.out",
          overwrite: "auto",
        },
      })
      .to(row, { backgroundColor: bg }, 0)
      .to(
        row.querySelectorAll(".text-sm, .text-works, .text-md, .work-tag"),
        { color: ink, borderColor: ink },
        0
      )
      .to(
        row.querySelector(".works-arrow"),
        { rotation: 45, filter: "invert(1)" },
        0
      );

    return { row, tl };
  });

  // Mouse and pen: hover in and out
  items.forEach(({ row, tl }) => {
    row.addEventListener(
      "pointerenter",
      (e) => e.pointerType !== "touch" && tl.play()
    );
    row.addEventListener(
      "pointerleave",
      (e) => e.pointerType !== "touch" && tl.reverse()
    );
  });

  // Touch: color whichever row is under the finger, even while scrolling
  let active = null;
  let release;

  const setActive = (item) => {
    if (item === active) return;
    active?.tl.reverse();
    item?.tl.play();
    active = item;
  };

  const track = (e) => {
    release?.kill();
    const t = e.touches[0];
    const el = document.elementFromPoint(t.clientX, t.clientY);
    const row = el && el.closest(".works-row");
    setActive(items.find((it) => it.row === row) || null);
  };

  const end = () => {
    release = gsap.delayedCall(0.3, () => setActive(null)); // brief hold so a tap registers
  };

  document.addEventListener("touchstart", track, { passive: true });
  document.addEventListener("touchmove", track, { passive: true });
  document.addEventListener("touchend", end);
  document.addEventListener("touchcancel", end);
}