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
Binary file added apps/website/public/pic-web-dev-topics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions apps/website/public/shield-check-gold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions apps/website/public/stopwatch-gold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 27 additions & 27 deletions apps/website/src/components/ServiceCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,74 @@ interface Props {
idx: number;
title: string;
content: any;
styles: string;
styles?: string;
imgPath: string;
imgAlt: string;
iconPath: string;
classNames?: string | string[];
}

const { idx, title, content, styles, imgPath, imgAlt, classNames } =
const { idx, title, content, styles, imgPath, imgAlt, iconPath, classNames } =
Astro.props;
---

<div
class={classnames(
`
lg:[&[data-show="true"]]:opacity-100
stacked-card
animation-card
rounded-2xl
max-lg:col-span-6
md:gap-x-7
w-full
lg:absolute
lg:opacity-0
lg:h-screen
absolute
h-[710px]
overflow-hidden
will-change-transform
`,
styles
)}
data-show=""
>
<section
class={classnames(
`text-white
h-full
lg:h-screen
lg:h-[620px]
p-7
lg:p-12
grid
grid-cols-6
grid-rows-[min-content_min-content_auto]
pb-0
gap-x-3
gap-y-7
lg:gap-x-7
lg:gap-y-12
lg:grid-cols-2
lg:grid-rows-[min-content_auto]
backdrop-blur-[30px]
backdrop-blur-[15px]
`,
classNames
)}
>
<div
class="col-span-6 grid gap-7 lg:grid-cols-2 lg:col-span-2 lg:items-start justify-between"
class="col-span-6 grid max-lg:grid-rows-[min-content_min-content_auto] gap-7 lg:grid-cols-2 lg:col-span-2 lg:items-start justify-between"
>
<span
class="font-medium text-[28px] leading-8 lg:leading-[44px] lg:mt-[2px]"
>{`{0${idx}}`}</span
<span class="grid md:flex gap-7 items-center">
<img src={iconPath} class="lg:mt-[2px] h-8 w-8 md:h-12 md:w-12" />
<h4
class="font-medium text-xl leading-[22px] md:text-[28px] md:leading-[30px] text-left"
>
{title}
</h4>
</span>
<div
class="flex flex-col justify-center items-center row-start-3 lg:justify-start lg:col-span-1 lg:row-start-2"
>
<h3 class="font-medium text-[28px] lg:text-4xl leading-[44px]">
{title}
</h3>
<img src={imgPath} alt={imgAlt} />
</div>
<div
class="flex flex-col lg:flex-col row-start-2 lg:col-span-1 lg:col-start-2 lg:row-span-2 [&_p]:text-base [&_p]:leading-[26px] md:[&_p]:text-[22px] md:[&_p]:font-normal md:[&_p]:leading-8 text-left"
set:html={content.html}
/>
</div>
<div
class="flex flex-col justify-center col-span-6 row-start-3 lg:justify-start lg:col-span-1 lg:row-start-2"
>
<img src={imgPath} alt={imgAlt} />
</div>
<div
class="flex flex-col lg:flex-col col-span-6 row-start-2 lg:col-span-1 lg:col-start-2 lg:row-span-2 lg:[&_p]:text-[22px] [&_p]:leading-8"
set:html={content.html}
/>
</section>
</div>
226 changes: 25 additions & 201 deletions apps/website/src/components/Services.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import SectionTitle from "./SectionTitle.astro";
import ServiceCard from "./ServiceCard.astro";
import { grid_classes } from "./_grid";
import classNames from "classnames";

/* 560px => Height of cards in starting position. 4 cards x 140px of indentation */
const styles = [
`bg-[#5362DBE5] lg:[transition:opacity_400ms_ease-out_600ms,transform_800ms_cubic-bezier(.5,.99,.49,.99)] lg:top-[calc(100vh-560px)]`,
`bg-[#424C6DE5] lg:[transition:opacity_400ms_ease-out_400ms,transform_800ms_cubic-bezier(.5,.99,.49,.99)] lg:top-[calc(100vh-560px+140px)]`,
`bg-[#3C3843E5] lg:[transition:opacity_400ms_ease-out_200ms,transform_800ms_cubic-bezier(.5,.99,.49,.99)] lg:top-[calc(100vh-560px+140px+140px)]`,
`bg-[#1E1A1AE5] lg:[transition:opacity_400ms_ease-out,transform_800ms_cubic-bezier(.5,.99,.49,.99)] lg:top-[calc(100vh-560px+140px+140px+140px)]`,
];
import Pill from "./Pill.astro";

const servicesCollection = await getCollection("services");

Expand All @@ -26,214 +19,45 @@ for (const service of servicesCollection) {
path: service.data.imgPath,
alt: service.data.imgAlt,
},
icon: service.data.iconPath,
background: service.data.bgColor,
};
}
---

<Section
idx="discover-our-services"
className="w-full !overflow-visible lg:!px-0"
contentClassName={classNames(grid_classes, "lg:pt-12")}
className="w-full !overflow-visible px-4 md:px-7"
contentClassName={classNames(grid_classes, "!gap-y-0 lg:pt-12")}
>
<SectionTitle
idx="services-title"
className="lg:px-7 col-span-6 lg:col-start-2 lg:col-span-10"
<div
class="grid gap-4 col-span-6 md:col-span-10 md:col-start-2 md:text-center font-bold md:justify-items-center"
>
Our Services
</SectionTitle>
<div id="services-wrapper" class="col-span-6 md:col-span-12">
<div
id="animation-wrapper"
class="max-lg:grid max-md:grid-cols-6 max-lg:grid-cols-12 max-lg:gap-7 lg:h-screen lg:overflow-hidden lg:sticky lg:top-28 mt-7"
<Pill>What we can do for you</Pill>
<h1
class="text-[44px] leading-[48px] md:text-[74px] md:leading-[81px] font-medium tracking-tight mb-12 md:mb-[72px] lg:mb-24 text-[#3C3843]"
>
{
services.map((value, idx) => (
<ServiceCard
Discover our <span class="text-crocoder-green"> Services </span>
</h1>
</div>
<div id="services-wrapper" class="col-span-6 md:col-span-12">
<div id="animation-wrapper" class="animation-container relative">
<div class="animation-container-child">
{
services.map((value, idx) => (
<ServiceCard
idx={idx + 1}
title={value.title}
content={value.content}
styles={styles[idx]}
imgPath={value.img.path}
imgAlt={value.img.alt}
classNames="will-change-transform"
/>
))
}
iconPath={value.icon}
styles={classNames("will-change-transform", value.background)}
/>
))
}
</div>
</div>
<div id="end" class="absolute bottom-0"></div>
</div>
</Section>

<script is:inline>
let animationThresholds = [];
let animationFieldHeight = 0;
let handleAnimationOnScroll = null;
let lastScrollTopPos = 0;
let isScrolling = false;

function onScroll() {
if (!isScrolling) {
window.requestAnimationFrame(() => {
handleAnimationOnScroll();
isScrolling = false;
});
}
isScrolling = true;
}

function calculateThresholds(cardsIndentation, screenHeight, cards) {
const thresholds = [];
const startHeight = cards.length * cardsIndentation;

cards.forEach((card, i) => {
if (i === 0) {
thresholds.push({
startAt: 0,
distance: screenHeight - startHeight - 40,
endAt: screenHeight - startHeight,
});
} else {
const prev = thresholds[i - 1];
const cardRect = card.getBoundingClientRect();

thresholds.push({
startAt: prev.endAt + cardRect.height / 2,
distance: prev.distance + cardsIndentation,
endAt: prev.endAt + (prev.distance + cardsIndentation),
});
}
});

return {
animationThresholds: thresholds,
animationFieldHeight: thresholds[thresholds.length - 1].endAt,
};
}

function addCardsAnimation() {
const wrapperWidth = window.innerWidth;

const servicesWrapper = document.getElementById("services-wrapper");
const animationWrapper = document.getElementById("animation-wrapper");
const servicesTitle = document.getElementById("services-title");
const endDiv = document.getElementById("end");
const cards = document.querySelectorAll("[data-show]");

if (handleAnimationOnScroll) {
window.removeEventListener("scroll", onScroll);
}

if (wrapperWidth >= 1024) {
const screenHeight = window.innerHeight;
const cardsIndentation = 140;
const scrollThreshold = 1000;

const result = calculateThresholds(cardsIndentation, screenHeight, cards);
animationThresholds = result.animationThresholds;
animationFieldHeight = result.animationFieldHeight;

const firstCardAnimationRange = animationThresholds[0].distance;
const lastCardAnimationRange =
animationThresholds[animationThresholds.length - 1].distance;

const servicesWrapperHeight =
animationFieldHeight + screenHeight + 2 * scrollThreshold;

servicesWrapper.style.transform = `translate3d(0, -${firstCardAnimationRange}px, 0)`;
servicesWrapper.style.height = `${servicesWrapperHeight}px`;

animationWrapper.style.top = `${firstCardAnimationRange}px`;

if (servicesWrapper.parentElement) {
servicesWrapper.parentElement.style.maxHeight = `${
servicesWrapperHeight - (firstCardAnimationRange - 180)
}px`;
}

function animateSurroundingElems(fromTop) {
const servicesTitleTop = servicesTitle.offsetHeight;
if (!servicesTitle) return;

if (fromTop >= 0 && fromTop >= servicesTitle.offsetHeight * 3) {
servicesTitle.style.position = "sticky";
servicesTitle.style.top = `${servicesTitleTop}px`;
} else {
servicesTitle.style.position = "static";
}
}

function animateCards(fromTop, isScrollDown, fadeInOutThreshold) {
if (
fromTop < fadeInOutThreshold ||
fromTop > animationFieldHeight + screenHeight
) {
return;
}

if (isScrollDown) {
cards.forEach((el, i) => {
const { startAt, endAt, distance } = animationThresholds[i];

if (fromTop > fadeInOutThreshold) {
el.setAttribute("data-show", "true");
}
if (startAt <= fromTop && endAt > fromTop) {
el.style.transform = `translate3d(0, ${startAt - fromTop}px, 0)`;
} else if (startAt <= fromTop && endAt < fromTop) {
el.style.transform = `translate3d(0, -${distance}px, 0)`;
}

if (fromTop > endAt + 200 && i === cards.length - 1) {
if (!window.navScroll) {
endDiv.scrollIntoView({ behavior: "smooth" });
}
}
});
} else {
cards.forEach((el, i) => {
const { startAt, endAt } = animationThresholds[i];
if (fromTop < fadeInOutThreshold + cardsIndentation) {
el.setAttribute("data-show", "");
}

if (startAt <= fromTop && endAt > fromTop) {
const startDistance = endAt - startAt;
el.style.transform = `translate3d(0, -${
startDistance - (endAt - fromTop)
}px, 0)`;
} else if (endAt > fromTop && startAt > fromTop) {
el.style.transform = `translate3d(0, 0, 0)`;
}
});
}
}

handleAnimationOnScroll = () => {
const currentScrollTop = window.scrollY;
const fromTop = servicesWrapper.getBoundingClientRect().top * -1;

const isScrollDown = lastScrollTopPos < currentScrollTop;
lastScrollTopPos = currentScrollTop <= 0 ? 0 : currentScrollTop;
const fadeInOutThreshold = lastCardAnimationRange - screenHeight - 1000;

animateSurroundingElems(fromTop + 200);
animateCards(fromTop - 1000, isScrollDown, fadeInOutThreshold);
};

handleAnimationOnScroll();
window.addEventListener("scroll", onScroll);
} else {
if (servicesTitle) servicesTitle.style.position = "static";

servicesWrapper.style.height = "auto";
servicesWrapper.style.transform = "none";
cards.forEach((el) => {
el.style.transform = "none";
});
}
}

addCardsAnimation();

window.addEventListener("resize", addCardsAnimation);
</script>
Loading