1
0

refactor: create animation helper functions to disable anims on webkit

This commit is contained in:
2026-03-25 12:53:03 +01:00
parent 4166a51c6c
commit 85e36e1d46
18 changed files with 603 additions and 729 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
"@astrojs/partytown": "^2.1.5", "@astrojs/partytown": "^2.1.5",
"@astrojs/sitemap": "^3.7.1", "@astrojs/sitemap": "^3.7.1",
"@astrolib/seo": "1.0.0-beta.8", "@astrolib/seo": "1.0.0-beta.8",
"astro": "^6.0.2", "astro": "^6.0.8",
"astro-robots-txt": "^1.0.0", "astro-robots-txt": "^1.0.0",
"gsap": "^3.14.2" "gsap": "^3.14.2"
} }
+300 -300
View File
File diff suppressed because it is too large Load Diff
+12 -15
View File
@@ -66,7 +66,7 @@ function getCurrentYear() {
<span class="made">Made with ❤️ in Vienna.</span> <span class="made">Made with ❤️ in Vienna.</span>
<div class="no-ai">All content made by humans.</div> <div class="no-ai">Code and content made by humans.</div>
</div> </div>
{/* Logo, commented out because of gradient issues */} {/* Logo, commented out because of gradient issues */}
@@ -146,6 +146,7 @@ function getCurrentYear() {
} }
@media screen and (max-width: 44.9375rem) { @media screen and (max-width: 44.9375rem) {
text-align: center;
margin-top: 5rem; margin-top: 5rem;
} }
} }
@@ -159,11 +160,7 @@ function getCurrentYear() {
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import { customAnimate } from "../scripts/animations";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(SplitText);
gsap.registerPlugin(ScrollTrigger);
const footer = document.querySelector("footer"); const footer = document.querySelector("footer");
@@ -171,16 +168,16 @@ function getCurrentYear() {
const mm = gsap.matchMedia(); const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(footer, { customAnimate({
scrollTrigger: { target: footer,
trigger: footer, animationProps: {
// start: "top bottom", scrollTrigger: {
trigger: footer,
},
duration: 2,
opacity: 0,
yPercent: 10,
}, },
duration: 2,
opacity: 0,
yPercent: 10,
// yPercent: 10,
ease: "expo.out",
}); });
}); });
} }
+8
View File
@@ -0,0 +1,8 @@
<script>
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(SplitText);
gsap.registerPlugin(ScrollTrigger);
</script>
+5 -1
View File
@@ -38,7 +38,7 @@
--tracking-narrow: -0.09375rem; --tracking-narrow: -0.09375rem;
--base-grid: repeat(12, 1fr); --base-grid: repeat(12, 1fr);
--max-content-width: 1600px; --max-content-width: 112.5rem;
--padding-x: 1.5rem; --padding-x: 1.5rem;
@media screen and (min-width: 50rem) { @media screen and (min-width: 50rem) {
@@ -94,4 +94,8 @@
border-radius: 0.25rem; border-radius: 0.25rem;
border: 2px solid var(--clr-ts-red-400); border: 2px solid var(--clr-ts-red-400);
} }
.char, .line {
will-change: transform, opacity;
}
</style> </style>
+15 -16
View File
@@ -8,12 +8,15 @@ import SkipNavLink from "./SkipNavLink.astro";
<SkipNavLink contentId="main-content" /> <SkipNavLink contentId="main-content" />
{/* Logo */} {/* Logo */}
<a class="header-logo" href="/"> <a class="header-logo" href="/" aria-label="Tideshift Logo">
<Logo class="logo" /> <Logo class="logo" />
</a> </a>
{/* Contact Link */} {/* Contact Link */}
<a class="header-contact plausible-event-name=Header+Contact" href="mailto:office@tideshiftdigital.com"> <a
class="header-contact plausible-event-name=Header+Contact"
href="mailto:office@tideshiftdigital.com"
>
<span class="heading-gradient">Contact us</span> <span class="heading-gradient">Contact us</span>
<ArrowUpRight class="icon char" /> <ArrowUpRight class="icon char" />
</a> </a>
@@ -48,7 +51,7 @@ import SkipNavLink from "./SkipNavLink.astro";
.logo { .logo {
width: 8rem; width: 8rem;
transform: translate3d(-0.75rem, 0, 0); transform: translate3d(-0.75rem, 0, 0);
@media screen and (min-width: 35rem) { @media screen and (min-width: 35rem) {
grid-column: 1 / 3; grid-column: 1 / 3;
width: 12.5rem; width: 12.5rem;
@@ -86,11 +89,7 @@ import SkipNavLink from "./SkipNavLink.astro";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import { customAnimate } from "../scripts/animations";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(SplitText);
gsap.registerPlugin(ScrollTrigger);
const header = document.querySelector("header"); const header = document.querySelector("header");
@@ -98,14 +97,14 @@ import SkipNavLink from "./SkipNavLink.astro";
const mm = gsap.matchMedia(); const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
const tl = gsap.timeline(); customAnimate({
target: header,
tl.from(header.children, { animationProps: {
duration: 1, duration: 1,
yPercent: -120, yPercent: -60,
opacity: 0, opacity: 0,
stagger: 0.2, stagger: 0.2,
ease: "expo.out", },
}); });
}); });
} }
+25 -69
View File
@@ -86,6 +86,7 @@ import { Picture } from "astro:assets";
> p { > p {
margin-top: 1.5rem; margin-top: 1.5rem;
will-change: opacity, transform;
@media screen and (max-width: 63.9375rem) { @media screen and (max-width: 63.9375rem) {
max-width: 52ch; max-width: 52ch;
@@ -130,6 +131,7 @@ import { Picture } from "astro:assets";
font-size: clamp(1.5rem, 2vw + 0.5rem, 2rem); font-size: clamp(1.5rem, 2vw + 0.5rem, 2rem);
line-height: var(--leading-paragraph); line-height: var(--leading-paragraph);
z-index: 1; z-index: 1;
will-change: opacity, transform;
@media screen and (max-width: 63.9375rem) { @media screen and (max-width: 63.9375rem) {
padding: 3rem; padding: 3rem;
@@ -140,11 +142,11 @@ import { Picture } from "astro:assets";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import {
import { ScrollTrigger } from "gsap/ScrollTrigger"; animateHeading,
animateText,
gsap.registerPlugin(SplitText); customAnimate,
gsap.registerPlugin(ScrollTrigger); } from "../../scripts/animations";
const aboutSection = document.querySelector(".about"); const aboutSection = document.querySelector(".about");
@@ -164,75 +166,29 @@ import { Picture } from "astro:assets";
tl.addLabel("image", 0.8); tl.addLabel("image", 0.8);
tl.addLabel("image-text", 1.1); tl.addLabel("image-text", 1.1);
new SplitText(aboutSection.querySelector("h2"), { animateHeading({
type: "words, chars", target: aboutSection.querySelector("h2"),
autoSplit: true, timeline: tl,
mask: "chars", label: "heading",
charsClass: "char",
onSplit: (self) => {
tl.from(
self.chars,
{
duration: 0.9,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
},
"heading",
);
},
}); });
new SplitText(aboutSection.querySelectorAll(".heading p"), { animateText({
type: "lines, words", target: aboutSection.querySelectorAll(".heading p"),
autoSplit: true, timeline: tl,
mask: "lines", label: "heading-text",
linesClass: "line",
onSplit: (self) => {
tl.from(
self.lines,
{
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"heading-text",
);
},
}); });
tl.from( customAnimate({
aboutSection.querySelector(".background"), target: aboutSection.querySelector(".background"),
{ animationProps: { duration: 2, opacity: 0, yPercent: 3 },
duration: 2, timeline: tl,
opacity: 0, label: "image",
ease: "expo.out", });
},
"image",
);
new SplitText(aboutSection.querySelectorAll(".image > p"), { animateText({
type: "lines, words", target: aboutSection.querySelectorAll(".image > p"),
autoSplit: true, timeline: tl,
mask: "lines", label: "image-text",
linesClass: "line",
onSplit: (self) => {
tl.from(
self.lines,
{
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"image-text",
);
},
}); });
}); });
} }
+52 -79
View File
@@ -11,7 +11,7 @@ import ArrowUpRight from "../icons/ArrowUpRight.astro";
<Picture <Picture
class="image" class="image"
src={ContactImage} src={ContactImage}
alt="An abstract image of a black and gold background" alt="Abstract shape in black and gold for decoration"
/> />
<div class="content"> <div class="content">
@@ -142,11 +142,11 @@ import ArrowUpRight from "../icons/ArrowUpRight.astro";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import {
import { ScrollTrigger } from "gsap/ScrollTrigger"; animateHeading,
animateText,
gsap.registerPlugin(SplitText); customAnimate,
gsap.registerPlugin(ScrollTrigger); } from "../../scripts/animations";
const contactSection = document.querySelector(".contact"); const contactSection = document.querySelector(".contact");
@@ -166,89 +166,62 @@ import ArrowUpRight from "../icons/ArrowUpRight.astro";
tl.addLabel("text", 0.6); tl.addLabel("text", 0.6);
tl.addLabel("arrow", 0.6); tl.addLabel("arrow", 0.6);
new SplitText(contactSection.querySelector("h2"), { animateHeading({
type: "words, chars", target: contactSection.querySelector("h2"),
autoSplit: true, timeline: tl,
mask: "chars", label: "heading",
charsClass: "char", });
onSplit: (self) => {
tl.from( animateText({
self.chars, target: contactSection.querySelector("p"),
{ timeline: tl,
duration: 0.9, label: "text",
yPercent: -120, });
scale: 1.2,
stagger: 0.015, customAnimate({
ease: "expo.out", target: contactSection.querySelector(".image"),
onComplete: () => self.revert(), animationProps: { duration: 1, opacity: 0, xPercent: -2 },
}, timeline: tl,
"heading", label: "image",
); });
animateHeading({
target: contactSection.querySelector("h2"),
timeline: tl,
label: "heading",
animationProps: {
scrollTrigger: {
trigger: contactSection,
start: "top 50%",
},
}, },
}); });
new SplitText(contactSection.querySelector("p"), { const link = contactSection.querySelector("a");
type: "lines, words",
autoSplit: true, animateHeading({
mask: "lines", target: link,
linesClass: "line", animationProps: {
onSplit: (self) => { scrollTrigger: {
tl.from( trigger: link,
self.lines, start: "top 70%",
{ },
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"text",
);
}, },
}); });
tl.from( customAnimate({
contactSection.querySelector(".image"), target: contactSection.querySelector("a > svg"),
{ animationProps: {
duration: 2, scrollTrigger: {
opacity: 0, trigger: link,
xPercent: -10, start: "top 70%",
ease: "expo.out", },
}, delay: 0.7,
"image",
);
new SplitText(contactSection.querySelector("a"), {
type: "words, chars",
autoSplit: true,
mask: "chars",
charsClass: "char",
onSplit: (self) => {
gsap.from(self.chars, {
scrollTrigger: {
trigger: contactSection,
start: "top 50%",
},
duration: 0.9,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
});
},
});
tl.from(
contactSection.querySelector("a > svg"),
{
duration: 1, duration: 1,
opacity: 0, opacity: 0,
yPercent: -10, yPercent: -10,
ease: "expo.out",
}, },
"arrow", });
);
}); });
} }
</script> </script>
+35 -63
View File
@@ -1,19 +1,20 @@
--- ---
import Picture from "astro/components/Picture.astro"; import Picture from "astro/components/Picture.astro";
import HeroImage from "../../assets/img/hero.jpg"; import HeroImage from "../../assets/img/hero.jpg";
import ArrowDown from "../icons/ArrowDown.astro";
import BlurryOrb from "../BlurryOrb.astro"; import BlurryOrb from "../BlurryOrb.astro";
import ArrowDown from "../icons/ArrowDown.astro";
--- ---
<div class="hero" aria-label="Hero section"> <div class="hero" aria-label="Hero section">
<BlurryOrb top="50%" left="5%" size="30rem" opacity="0.15" /> <BlurryOrb top="50%" left="15%" size="30rem" opacity="0.15" />
<div class="image-wrapper"> <div class="image-wrapper">
<Picture <Picture
class="image" class="image"
src={HeroImage} src={HeroImage}
formats={["jpg", "jpeg", "webp"]} formats={["jpg", "jpeg", "webp"]}
alt="todo" alt="Abstract shape in red for decoration"
loading="eager"
/> />
</div> </div>
@@ -56,6 +57,7 @@ import BlurryOrb from "../BlurryOrb.astro";
max-height: 40vh; max-height: 40vh;
position: relative; position: relative;
z-index: -1; z-index: -1;
will-change: opacity, transform;
@media screen and (max-width: 49.9375rem) { @media screen and (max-width: 49.9375rem) {
margin-top: 1.5rem; margin-top: 1.5rem;
@@ -192,11 +194,11 @@ import BlurryOrb from "../BlurryOrb.astro";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import {
import { ScrollTrigger } from "gsap/ScrollTrigger"; customAnimate,
animateHeading,
gsap.registerPlugin(SplitText); animateText,
gsap.registerPlugin(ScrollTrigger); } from "../../scripts/animations";
const heroSection = document.querySelector(".hero"); const heroSection = document.querySelector(".hero");
@@ -204,75 +206,45 @@ import BlurryOrb from "../BlurryOrb.astro";
const mm = gsap.matchMedia(); const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
const tl = gsap.timeline().pause(); const tl = gsap.timeline({ paused: true });
tl.addLabel("heading", 0); tl.addLabel("heading", 0);
tl.addLabel("image", 0.2); tl.addLabel("image", 0.2);
tl.addLabel("text", 0.8); tl.addLabel("text", 0.8);
tl.addLabel("arrow", 1.5); tl.addLabel("arrow", 1.5);
tl.from( customAnimate({
heroSection.querySelector(".image-wrapper"), target: heroSection.querySelector(".image-wrapper"),
{ animationProps: { duration: 2, opacity: 0, xPercent: 10 },
duration: 2, timeline: tl,
opacity: 0, label: "image",
xPercent: 10,
ease: "expo.out",
},
"image",
);
new SplitText(heroSection.querySelector("h1"), {
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(heroSection.querySelector("p"), { animateHeading({
type: "lines, words", target: heroSection.querySelector("h1"),
autoSplit: true, timeline: tl,
mask: "lines", label: "heading",
linesClass: "line",
onSplit: (self) => {
tl.from(
self.lines,
{
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"text",
).play();
},
}); });
tl.from( animateText({
heroSection.querySelector(".down"), target: heroSection.querySelector("p"),
{ timeline: tl,
label: "text",
});
customAnimate({
target: heroSection.querySelector(".down"),
animationProps: {
duration: 1, duration: 1,
opacity: 0, opacity: 0,
translateY: "-100%", yPercent: -100,
ease: "bounce.out", ease: "bounce.out",
}, },
"arrow", timeline: tl,
); label: "arrow",
});
tl.play();
}); });
} }
</script> </script>
@@ -34,6 +34,7 @@ const { title, image, items } = Astro.props;
display: grid; display: grid;
grid-template-rows: auto 1fr auto; grid-template-rows: auto 1fr auto;
max-height: 24rem; max-height: 24rem;
will-change: transform, opacity;
& > * { & > * {
grid-column: 1; grid-column: 1;
@@ -72,6 +73,7 @@ const { title, image, items } = Astro.props;
transition: all; transition: all;
transition-duration: 300ms; transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
will-change: filter;
} }
.image { .image {
+22 -50
View File
@@ -10,7 +10,7 @@ const services: Array<ComponentProps<typeof ServicesCard>> = [
title: "Innovative Solutions", title: "Innovative Solutions",
image: { image: {
src: Image1, src: Image1,
alt: "A picture showing an abstract form in teal", alt: "Abstract shape in teal for decoration",
}, },
items: [ items: [
"Modern Websites", "Modern Websites",
@@ -22,7 +22,7 @@ const services: Array<ComponentProps<typeof ServicesCard>> = [
title: "Tailored Designs", title: "Tailored Designs",
image: { image: {
src: Image2, src: Image2,
alt: "A picture showing an abstract form in blue/purple", alt: "Abstract shape in blue/purple for decoration",
}, },
items: ["Webdesign", "App Design", "Brand/Identity Design"], items: ["Webdesign", "App Design", "Brand/Identity Design"],
}, },
@@ -30,7 +30,7 @@ const services: Array<ComponentProps<typeof ServicesCard>> = [
title: "Flexible Projects", title: "Flexible Projects",
image: { image: {
src: Image3, src: Image3,
alt: "A picture showing an abstract form in red", alt: "Abstract shape in red for decoration",
}, },
items: ["Digital Sovereignty", "Freelancing", "Consulting"], items: ["Digital Sovereignty", "Freelancing", "Consulting"],
}, },
@@ -123,11 +123,11 @@ const services: Array<ComponentProps<typeof ServicesCard>> = [
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import {
import { ScrollTrigger } from "gsap/ScrollTrigger"; animateHeading,
animateText,
gsap.registerPlugin(SplitText); customAnimate,
gsap.registerPlugin(ScrollTrigger); } from "../../scripts/animations";
const servicesSection = document.querySelector(".services"); const servicesSection = document.querySelector(".services");
@@ -146,58 +146,30 @@ const services: Array<ComponentProps<typeof ServicesCard>> = [
tl.addLabel("text", 0.3); tl.addLabel("text", 0.3);
tl.addLabel("cards", 0.5); tl.addLabel("cards", 0.5);
new SplitText(servicesSection.querySelector("h2"), { animateHeading({
type: "words, chars", target: servicesSection.querySelector("h2"),
autoSplit: true, timeline: tl,
mask: "chars", label: "heading",
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"), { animateText({
type: "lines, words", target: servicesSection.querySelector("p"),
autoSplit: true, timeline: tl,
mask: "lines", label: "text",
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( customAnimate({
servicesSection.querySelectorAll(".cards > *"), target: ".cards > *",
{ animationProps: {
duration: 1, duration: 1,
opacity: 0, opacity: 0,
yPercent: 5, yPercent: 5,
stagger: 0.15, stagger: 0.15,
ease: "expo.out", ease: "expo.out",
}, },
"cards", timeline: tl,
); label: "cards",
});
}); });
} }
</script> </script>
+25 -81
View File
@@ -137,11 +137,7 @@ function formatIndex(index: number) {
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import { animateHeading, animateText } from "../../scripts/animations";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(SplitText);
gsap.registerPlugin(ScrollTrigger);
const valuesSection = document.querySelector(".values"); const valuesSection = document.querySelector(".values");
@@ -151,99 +147,47 @@ function formatIndex(index: number) {
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
const heading = valuesSection.querySelector("h2"); const heading = valuesSection.querySelector("h2");
new SplitText(heading, { animateHeading({
type: "words, chars", target: heading,
autoSplit: true, animationProps: {
mask: "chars", scrollTrigger: {
charsClass: "char", trigger: heading,
onSplit: (self) => { start: "top 70%",
gsap.from(self.chars, { },
scrollTrigger: {
trigger: heading,
start: "top 70%",
},
duration: 0.9,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
});
}, },
}); });
const items = valuesSection.querySelectorAll("li"); const items = valuesSection.querySelectorAll("li");
items.forEach((item) => { items.forEach((item) => {
const itemTl = gsap.timeline({ const tl = gsap.timeline({
scrollTrigger: { scrollTrigger: {
trigger: item, trigger: item,
start: "top 70%", start: "top 70%",
}, },
}); });
itemTl.addLabel("number", 0); tl.addLabel("number", 0);
itemTl.addLabel("heading", 0.2); tl.addLabel("heading", 0.2);
itemTl.addLabel("text", 0.5); tl.addLabel("text", 0.5);
new SplitText(item.querySelector("span"), { animateHeading({
type: "words, chars", target: item.querySelector("span"),
autoSplit: true, timeline: tl,
mask: "chars", label: "number",
charsClass: "char", animationProps: { stagger: 0.25 },
onSplit: (self) => {
itemTl.from(
self.chars,
{
duration: 0.9,
yPercent: -120,
stagger: 0.25,
ease: "expo.out",
onComplete: () => self.revert(),
},
"number",
);
},
}); });
new SplitText(item.querySelector("h3"), { animateHeading({
type: "words, chars", target: item.querySelector("h3"),
autoSplit: true, timeline: tl,
mask: "chars", label: "heading",
charsClass: "char",
onSplit: (self) => {
itemTl.from(
self.chars,
{
duration: 0.9,
yPercent: -120,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
},
"heading",
);
},
}); });
new SplitText(item.querySelector("p"), { animateText({
type: "lines, words", target: item.querySelector("p"),
autoSplit: true, timeline: tl,
mask: "lines", label: "text",
linesClass: "line",
onSplit: (self) => {
itemTl.from(
self.lines,
{
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
onComplete: () => self.revert(),
},
"text",
);
},
}); });
}); });
}); });
+3 -1
View File
@@ -1,5 +1,6 @@
--- ---
import Footer from "../components/Footer.astro"; import Footer from "../components/Footer.astro";
import GlobalScripts from "../components/GlobalScripts.astro";
import GlobalStyles from "../components/GlobalStyles.astro"; import GlobalStyles from "../components/GlobalStyles.astro";
import Header from "../components/Header.astro"; import Header from "../components/Header.astro";
import PlausibleAnalytics from "../components/PlausibleAnalytics.astro"; import PlausibleAnalytics from "../components/PlausibleAnalytics.astro";
@@ -15,7 +16,7 @@ const title = pageTitle
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{title}</title> <title>{title}</title>
@@ -51,6 +52,7 @@ const title = pageTitle
</head> </head>
<body> <body>
<GlobalStyles /> <GlobalStyles />
<GlobalScripts />
<Header /> <Header />
+1 -1
View File
@@ -8,7 +8,7 @@ import PlausibleAnalytics from "../components/PlausibleAnalytics.astro";
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>Oops, this is weird</title> <title>Oops, this is weird</title>
+11 -26
View File
@@ -69,9 +69,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import { animateHeading, customAnimate } from "../scripts/animations";
gsap.registerPlugin(SplitText);
const page = document.querySelector(".imprint"); const page = document.querySelector(".imprint");
@@ -79,30 +77,17 @@ import BaseLayout from "../layouts/BaseLayout.astro";
const mm = gsap.matchMedia(); const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
new SplitText(page.querySelector("h1"), { animateHeading({ target: page.querySelector("h1") });
type: "words, chars",
autoSplit: true,
mask: "chars",
charsClass: "char",
onSplit: (self) => {
gsap.from(self.chars, {
duration: 1,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
});
},
});
gsap.from(page.querySelectorAll(":scope > div"), { customAnimate({
duration: 1, target: page.querySelectorAll(":scope > div"),
delay: 0.3, animationProps: {
opacity: 0, duration: 1,
yPercent: -20, delay: 0.3,
ease: "expo.out", opacity: 0,
stagger: 0.1, translateY: -16,
stagger: 0.1,
},
}); });
}); });
} }
+11 -26
View File
@@ -132,9 +132,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
<script> <script>
import { gsap } from "gsap"; import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText"; import { animateHeading, customAnimate } from "../scripts/animations";
gsap.registerPlugin(SplitText);
const page = document.querySelector(".privacy"); const page = document.querySelector(".privacy");
@@ -142,30 +140,17 @@ import BaseLayout from "../layouts/BaseLayout.astro";
const mm = gsap.matchMedia(); const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => { mm.add("(prefers-reduced-motion: no-preference)", () => {
new SplitText(page.querySelector("h1"), { animateHeading({ target: page.querySelector("h1") });
type: "words, chars",
autoSplit: true,
mask: "chars",
charsClass: "char",
onSplit: (self) => {
gsap.from(self.chars, {
duration: 1,
yPercent: -120,
scale: 1.2,
stagger: 0.015,
ease: "expo.out",
onComplete: () => self.revert(),
});
},
});
gsap.from(page.querySelectorAll(".content > *"), { customAnimate({
duration: 1, target: page.querySelectorAll(":scope > div"),
delay: 0.3, animationProps: {
opacity: 0, duration: 1,
yPercent: -20, delay: 0.3,
ease: "expo.out", opacity: 0,
stagger: 0.1, translateY: -16,
stagger: 0.1,
},
}); });
}); });
} }
+72
View File
@@ -0,0 +1,72 @@
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
import { isWebkit } from "./utils";
export function animateHeading(params: {
target: gsap.DOMTarget,
timeline?: gsap.core.Timeline,
label?: string
animationProps?: gsap.TweenVars
}) {
if (isWebkit()) return;
const { target, timeline, label, animationProps = {} } = params;
new SplitText(target, {
type: "words, chars",
autoSplit: true,
mask: "chars",
charsClass: "char",
onSplit: (self) => {
(timeline ? timeline : gsap).from(self.chars, {
duration: 1,
yPercent: -120,
stagger: 0.015,
ease: "expo.out",
...animationProps,
onComplete: () => self.revert(),
}, label);
},
});
}
export function animateText(params: {
target: gsap.DOMTarget,
timeline?: gsap.core.Timeline,
label?: string,
animationProps?: gsap.TweenVars
}) {
if (isWebkit()) return;
const { target, timeline, label, animationProps = {} } = params;
new SplitText(target, {
type: "lines, words",
autoSplit: true,
mask: "lines",
linesClass: "line",
onSplit: (self) => {
(timeline ? timeline : gsap).from(self.lines, {
duration: 0.9,
yPercent: 105,
stagger: 0.06,
ease: "expo.out",
...animationProps,
onComplete: () => self.revert(),
}, label);
},
});
}
export function customAnimate(params: {
target: gsap.DOMTarget,
animationProps: gsap.TweenVars
timeline?: gsap.core.Timeline,
label?: string,
}) {
if (isWebkit()) return;
const { target, animationProps, timeline, label } = params;
(timeline ? timeline : gsap).from(target, {
ease: "expo.out",
...animationProps
}, label);
}
+3
View File
@@ -0,0 +1,3 @@
export function isWebkit() {
return "GestureEvent" in window;
}