1
0
Files
tideshift-website/src/components/landing/ServicesSection.astro
T
2026-03-11 11:52:31 +01:00

200 lines
5.4 KiB
Plaintext

---
import type { ComponentProps } from "astro/types";
import ServicesCard from "./ServicesCard.astro";
import Image1 from "../../assets/img/services/1.jpg";
import Image2 from "../../assets/img/services/2.jpg";
import Image3 from "../../assets/img/services/3.jpg";
const services: Array<ComponentProps<typeof ServicesCard>> = [
{
title: "Innovative Solutions",
image: {
src: Image1,
alt: "A picture showing an abstract form in teal",
},
items: [
"Modern Websites",
"Specialized Web Applications",
"Cross-platform Mobile Apps",
],
},
{
title: "Tailored Designs",
image: {
src: Image2,
alt: "A picture showing an abstract form in blue/purple",
},
items: ["Webdesign", "App Design", "Brand/Identity Design"],
},
{
title: "Flexible Projects",
image: {
src: Image3,
alt: "A picture showing an abstract form in red",
},
items: ["Digital Sovereignty", "Freelancing", "Consulting"],
},
];
---
<div class="services" aria-label="Services section">
<div class="heading">
<h2 class="heading-gradient">Our Services.</h2>
<p>
Together as partners, we will tackle your digital challenge and turn
your vision into reality.
</p>
</div>
<ul class="cards">
{services.map((service) => <ServicesCard {...service} />)}
</ul>
</div>
<style>
.services {
display: grid;
grid-template-columns: var(--base-grid);
max-width: var(--max-content-width);
margin-inline: auto;
padding-block: 8rem;
padding-inline: var(--padding-x);
}
.heading {
display: grid;
grid-column: 1 / -1;
@media screen and (max-width: 63.9375rem) {
row-gap: 1rem;
}
@media screen and (min-width: 64rem) {
grid-template-columns: subgrid;
}
& h2 {
grid-column: 1 / -1;
height: max-content;
font-size: var(--fs-xl);
line-height: var(--leading-title);
letter-spacing: var(--tracking-narrow);
@media screen and (min-width: 64rem) {
grid-column: 1 / 7;
}
}
& p {
grid-column: 1 / -1;
max-width: 46ch;
font-size: var(--fs-lg);
line-height: var(--leading-paragraph);
@media screen and (min-width: 64rem) {
grid-column: -7 / -1;
max-width: 32ch;
justify-self: end;
}
}
}
.cards {
display: grid;
gap: 2rem;
grid-column: 1 / -1;
list-style: none;
margin-top: 4rem;
@media (min-width: 75rem) {
grid-template-columns: repeat(3, 1fr);
margin-top: 7rem;
}
}
</style>
<script>
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(SplitText);
gsap.registerPlugin(ScrollTrigger);
const servicesSection = document.querySelector(".services");
if (servicesSection) {
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: servicesSection,
start: "top 60%",
},
});
tl.addLabel("heading", 0);
tl.addLabel("text", 0.3);
tl.addLabel("cards", 0.5);
new SplitText(servicesSection.querySelector("h2"), {
type: "words, chars",
autoSplit: true,
mask: "chars",
charsClass: "char",
onSplit: (self) => {
tl.from(
self.chars,
{
duration: 1,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
},
"heading",
);
},
});
new SplitText(servicesSection.querySelector("p"), {
type: "lines, words",
autoSplit: true,
mask: "lines",
linesClass: "line",
onSplit: (self) => {
tl.from(
self.lines,
{
duration: 1.5,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"text",
);
},
});
tl.from(
servicesSection.querySelectorAll(".cards > *"),
{
duration: 1,
opacity: 0,
yPercent: 5,
stagger: 0.15,
ease: "expo.out",
},
"cards",
);
});
}
</script>