Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.1.11",
"@tailwindcss/vite": "^4.1.13",
"astro": "5.13.7",
"astro-font": "^1.1.0",
"autoprefixer": "^10.4.21",
"classnames": "^2.5.1",
"date-fns": "^4.1.0",
"gsap": "^3.13.0",
"marked": "^16.1.2",
"posthog-js": "^1.222.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"tailwind": "^4.0.0",
"tailwind-bootstrap-grid": "^6.0.0"
"tailwind-bootstrap-grid": "^6.0.0",
"tailwindcss": "^4.1.13"
},
"scripts": {
"dev": "astro dev --port 4321",
Expand Down
255 changes: 132 additions & 123 deletions apps/website/src/components/navigation.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
import { getImage } from "astro:assets";
import crocoderLogo from "../assets/crocoder-logo.png";
import "../styles/navigation.css";

const optimizedLogo = await getImage({
src: crocoderLogo,
Expand Down Expand Up @@ -34,8 +33,6 @@ const optimizedLogo = await getImage({
md:rounded-r-lg
md:overflow-hidden
relative
gap:12
max-md:has-[input:checked]:bg-secondary
md:w-[680px]"
data-navhidden="false"
>
Expand Down Expand Up @@ -72,17 +69,17 @@ const optimizedLogo = await getImage({
</li>
</ul>

<label for="nav-menu-toggle" class="md:hidden flex flex-col gap-1.5">
<input
id="nav-menu-toggle"
type="checkbox"
readonly
class="hidden peer/menu-toggle"
/>
<button
id="nav-menu-toggle"
aria-expanded="false"
aria-controls="mobile-list"
aria-label="Toggle menu"
class="md:hidden flex flex-col gap-1.5"
>
<span id="top-line" class="h-0.5 w-6 bg-neutral-50 line line1"></span>
<span id="middle-line" class="h-0.5 w-6 bg-neutral-50 line line2"></span>
<span id="bottom-line" class="h-0.5 w-6 bg-neutral-50 line line3"></span>
</label>
</button>

<ul class="hidden items-center md:flex">
<li id="for-ctos" data-navhidden="true" class="m-2">
Expand All @@ -101,7 +98,7 @@ const optimizedLogo = await getImage({
Blog
</a>
</li>
<li id="book-a-call-container" class="ml-auto w-0">
<li id="book-a-call-container" class="ml-auto pr-2">
<a
data-navhidden="true"
id="book-a-call-action"
Expand Down Expand Up @@ -165,140 +162,152 @@ const optimizedLogo = await getImage({
</div>
</header>

<script is:inline>
let lastScrollTop = 0;
let isScroll = false;
<script>
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);
let mm = gsap.matchMedia();

const NAV_SHRINK_WIDTH = "377px";
const NAV_EXPAND_WIDTH = "680px";
const FRAME_DURATION = 0.425;

const allDataHidden = document.querySelectorAll("[data-navhidden]");
const navBar = document.getElementById("nav-bar");
const logoElem = document.getElementById("logo");
const forCtoElem = document.getElementById("for-ctos");
const blogElem = document.getElementById("blog");
const bookACallAction = document.getElementById("book-a-call-action");
const containerElement = document.getElementById("book-a-call-container");
const mobileBookACallAction = document.getElementById(
"mobile-book-a-call-action",
);
const navMenuToggle = document.getElementById("nav-menu-toggle");

const mobileContact = document.getElementById("mobile-contact");
const navElem = document.getElementById("nav-bar");
const logoElem = document.getElementById("logo");
const forCtoElem = document.getElementById("for-ctos");
const blogElem = document.getElementById("blog");
const containerElement = document.getElementById("book-a-call-container");
const mobileList = document.getElementById("mobile-list");
const navMenuToggle = document.getElementById("nav-menu-toggle");

function onScroll() {
const currentScrollTop =
window.scrollY || document.documentElement.scrollTop;
mm.add("(width >= 768px)", () => {
if (!navBar || !bookACallAction) return;

if (!isScroll) {
isScroll = true;
window.requestAnimationFrame(() => {
handleScroll(currentScrollTop);
isScroll = false;
});
}
}
const navHideItems = [forCtoElem, blogElem, logoElem];

function handleScroll(currentScrollTop) {
/* Since the animation shouldn't trigger on first render, all the animation
classes are added first time the handle scroll is triggered*/
if (!navElem.classList.contains("animated-navigation")) {
navElem.classList.add("animated-navigation");
navElem.classList.remove("md:w-[680px]");
forCtoElem.classList.add("animated-text");
blogElem.classList.add("animated-text");
bookACallAction.classList.add("animated-button", "w-fit");
containerElement.classList.remove("w-0");
containerElement.classList.add("pr-2");
handleClassChanges(allDataHidden);
}
if (
currentScrollTop > lastScrollTop &&
allDataHidden[0].getAttribute("data-navhidden") === "true"
) {
invertDataAttributte(allDataHidden);
handleClassChanges(allDataHidden);
} else if (
currentScrollTop <= lastScrollTop &&
allDataHidden[0].getAttribute("data-navhidden") === "false"
) {
invertDataAttributte(allDataHidden);
}
gsap.set(navBar, { width: NAV_EXPAND_WIDTH, overwrite: false });
gsap.set(navHideItems, {
autoAlpha: 1,
overflow: "hidden",
});
gsap.set(containerElement, { autoAlpha: 0 });
gsap.set(bookACallAction, {
autoAlpha: 0,
overflow: "hidden",
display: "none",
});

lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop;
}
const tl = gsap.timeline({
paused: true,
defaults: { duration: FRAME_DURATION, ease: "none" },
});

function invertDataAttributte(dataHiddenNodes) {
dataHiddenNodes.forEach((node) => {
if (node.closest("li")?.textContent?.includes("Contact us")) return;
tl.to(navHideItems, {
autoAlpha: 0,
stagger: 0.05,
overwrite: "auto",
display: "none",
}).to(navBar, { width: NAV_SHRINK_WIDTH }, "-=0.1");

const currentVal = node.getAttribute("data-navhidden");
node.setAttribute(
"data-navhidden",
currentVal === "true" ? "false" : "true",
);
tl.addLabel("showBookACall");

node.style.animationName = "none";
void node.offsetWidth;
node.style.animationName = "";
});
}
tl.to(
bookACallAction,
{ autoAlpha: 1, display: "list-item" },
"showBookACall",
);
tl.to(containerElement, { autoAlpha: 1 }, "showBookACall");

let lastDirection = 0;

function handleClassChanges(dataHiddenNodes) {
dataHiddenNodes.forEach((node) => {
let condition;
switch (node.getAttribute("id")) {
case "logo":
condition = node.getAttribute("data-navhidden") === "false";
logoElem.classList.toggle("md:hidden", condition);
logoElem.classList.toggle("w-0", condition);
break;
case "for-ctos":
condition = node.getAttribute("data-navhidden") === "true";
forCtoElem.classList.toggle("hidden", condition);
forCtoElem.classList.toggle("!w-0", condition);
break;
case "blog":
condition = node.getAttribute("data-navhidden") === "true";
blogElem.classList.toggle("hidden", condition);
blogElem.classList.toggle("!w-0", condition);
break;
case "book-a-call-action":
condition = node.getAttribute("data-navhidden") === "false";
bookACallAction.classList.toggle("hidden", condition);
bookACallAction.classList.toggle("!w-0", condition);
break;
default:
break;
}
const st = ScrollTrigger.create({
onUpdate(self) {
if (self.direction === 1 && lastDirection !== 1) {
tl.play();
lastDirection = 1;
} else if (self.direction === -1 && lastDirection !== -1) {
tl.reverse();
lastDirection = -1;
}
},
});
}

navMenuToggle.addEventListener("change", () => {
mobileContact.classList.toggle("hidden", navMenuToggle.checked);
mobileList.classList.toggle("hidden", !navMenuToggle.checked);
mobileList.classList.toggle("flex", navMenuToggle.checked);
return () => {
tl.kill();
st.kill();
gsap.killTweensOf([
navBar,
...navHideItems,
bookACallAction,
containerElement,
]);
};
});

navElem.addEventListener("animationend", (event) => {
mm.add("(width < 768px)", () => {
if (
event.animationName === "navigation-animation" &&
allDataHidden[0].getAttribute("data-navhidden") === "true"
) {
handleClassChanges(allDataHidden);
}
});
!navMenuToggle ||
!mobileList ||
!mobileContact ||
!mobileBookACallAction
)
return;

gsap.set(mobileList, {
height: 0,
autoAlpha: 0,
display: "flex",
overflow: "hidden",
});
gsap.set(mobileContact, { autoAlpha: 1 });

const tlBurger = gsap.timeline({
paused: true,
defaults: { duration: 0.3, ease: "power2.inOut" },
});

tlBurger
.to("#top-line", { rotation: 45, x: 0, y: 10 }, 0)
.to("#middle-line", { autoAlpha: 0 }, 0)
.to("#bottom-line", { rotation: -45, x: 0, y: -6 }, 0)
.to(navBar, { backgroundColor: "#3c3843" }, 0)
.to(mobileList, { height: "auto", autoAlpha: 1, duration: 0.4 }, 0)
.to(mobileContact, { autoAlpha: 0 }, 0);

const handleBurgerClick = () => {
const expanded = navMenuToggle.getAttribute("aria-expanded") === "true";
navMenuToggle.setAttribute("aria-expanded", String(!expanded));

if (expanded) tlBurger.reverse();
else tlBurger.play();
};

function handleBookACall() {
navMenuToggle.checked = false;
window.navScroll = true;
const handleBookACallClick = () => {
tlBurger.reverse();
navMenuToggle.setAttribute("aria-expanded", "false");
};

setTimeout(() => {
window.navScroll = false;
}, 1500);
}
mobileBookACallAction.addEventListener("click", handleBookACallClick);
navMenuToggle.addEventListener("click", handleBurgerClick);

window.addEventListener("scroll", onScroll, { passive: true });
bookACallAction.addEventListener("click", handleBookACall);
return () => {
navMenuToggle.removeEventListener("click", handleBurgerClick);
mobileBookACallAction.removeEventListener("click", handleBookACallClick);

mobileBookACallAction.addEventListener("click", () => {
navMenuToggle.click();
gsap.killTweensOf([
mobileList,
mobileContact,
"#top-line",
"#middle-line",
"#bottom-line",
]);
};
});
</script>
Loading