1
0

Compare commits

...

5 Commits

Author SHA1 Message Date
mrieger 74c407f906 feat: add business card page 2026-04-22 15:25:28 +02:00
mrieger b29c913ad1 chore: remove unused files 2026-04-08 15:28:11 +02:00
mrieger 3681576f14 fix: Cleanup poppins.css and file import after switching to Astro fonts 2026-03-25 12:56:30 +01:00
mrieger 85e36e1d46 refactor: create animation helper functions to disable anims on webkit 2026-03-25 12:53:03 +01:00
mrieger 4166a51c6c feat: Add License and update Readme
Build stite / Build-static-Astro-site (24.x) (push) Has been cancelled
2026-03-18 16:59:09 +01:00
26 changed files with 62765 additions and 931 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Tideshift Digital e.U.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+17 -35
View File
@@ -1,46 +1,28 @@
# Astro Starter Kit: Basics # Tideshift Website
```sh This is the official Tideshift website built with Astro.
pnpm create astro@latest -- --template basics
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! ## License
## 🚀 Project Structure ### Code
Inside of your Astro project, you'll see the following folders and files: All source code in this repository is licensed under the MIT License (see LICENSE file for details).
```text You are free to use, modify, and distribute the code for personal or commercial purposes.
/
├── public/
│ └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── Welcome.astro
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│   └── index.astro
└── package.json
```
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). ### Assets & Branding (Not Licensed)
## 🧞 Commands All non-code assets in this repository are excluded from the MIT License and remain the intellectual property of *Tideshift Digital e.U.*, unless otherwise stated.
All commands are run from the root of the project, from a terminal: This includes, but is not limited to:
- Logos and brand assets
- Design elements unique to our brand
- Images, graphics, and other visual media
| Command | Action | **These assets may not be copied, reproduced, modified, distributed, or used in any project without explicit written permission.**
| :------------------------ | :----------------------------------------------- |
| `pnpm install` | Installs dependencies |
| `pnpm dev` | Starts local dev server at `localhost:4321` |
| `pnpm build` | Build your production site to `./dist/` |
| `pnpm preview` | Preview your build locally, before deploying |
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
| `pnpm astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more? ### Decorative Images
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). Some decorative images used in this repository were created by [Pawel Czerwinski](https://unsplash.com/@pawel_czerwinski) on [Unsplash](https://unsplash.com/) and are subject to the [Unsplash License](https://unsplash.com/license).
You are responsible for reviewing and complying with the terms of that license when using them.
+4 -4
View File
@@ -9,11 +9,11 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/partytown": "^2.1.5", "@astrojs/partytown": "^2.1.7",
"@astrojs/sitemap": "^3.7.1", "@astrojs/sitemap": "^3.7.2",
"@astrolib/seo": "1.0.0-beta.8", "@astrolib/seo": "1.0.0-beta.8",
"astro": "^6.0.2", "astro": "^6.1.8",
"astro-robots-txt": "^1.0.0", "astro-robots-txt": "^1.0.0",
"gsap": "^3.14.2" "gsap": "^3.15.0"
} }
} }
+406 -380
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 594 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 594 KiB

+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>
+6 -3
View File
@@ -1,6 +1,4 @@
<style is:global> <style is:global>
@import url("../styles/poppins.css");
:root { :root {
--clr-light-400: oklch(100% 0.00011 271.152); --clr-light-400: oklch(100% 0.00011 271.152);
--clr-light-500: oklch(from var(--clr-light-400) calc(l - 0.1) c h); --clr-light-500: oklch(from var(--clr-light-400) calc(l - 0.1) c h);
@@ -38,7 +36,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 +92,9 @@
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",
);
},
}); });
}); });
}); });
-13
View File
@@ -1,13 +0,0 @@
// 1. Import utilities from `astro:content`
import { defineCollection, z } from 'astro:content';
// 2. Import loader(s)
import { glob, file } from 'astro/loaders';
// 3. Define your collection(s)
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
});
// 4. Export a single `collections` object to register your collection(s)
export const collections = { blog };
-3
View File
@@ -1,3 +0,0 @@
---
title: My first article
---
+4 -2
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,13 +16,13 @@ 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>
<meta <meta
name="description" name="description"
content="From concept to launch - websites, apps, and digital solutions that work hard, so you can play hard. Personal sites, ecommerce platforms, and custom web apps built by Michael Rieger in Vienna." content="From concept to launch - websites, apps, and digital solutions that work hard, so you can play hard. Personal sites, e-commerce platforms, and custom web apps built by Michael Rieger in Vienna."
/> />
<meta <meta
name="keywords" name="keywords"
@@ -51,6 +52,7 @@ const title = pageTitle
</head> </head>
<body> <body>
<GlobalStyles /> <GlobalStyles />
<GlobalScripts />
<Header /> <Header />
+2 -2
View File
@@ -8,13 +8,13 @@ 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>
<meta <meta
name="description" name="description"
content="From concept to launch - websites, apps, and digital solutions that work hard, so you can play hard. Personal sites, ecommerce platforms, and custom web apps built by Michael Rieger in Vienna." content="From concept to launch - websites, apps, and digital solutions that work hard, so you can play hard. Personal sites, e-commerce platforms, and custom web apps built by Michael Rieger in Vienna."
/> />
<meta <meta
name="keywords" name="keywords"
+53
View File
@@ -0,0 +1,53 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import QrCode from "../assets/img/bc-qrcode-white.svg";
---
<BaseLayout pageTitle="Business Card">
<div class="business-card">
<h1 class="heading-gradient">Business Card</h1>
<div class="qrcode">
<QrCode class="qrcode-img" />
</div>
</div>
</BaseLayout>
<style>
.business-card {
padding-inline: var(--padding-x);
max-width: var(--max-content-width);
margin-inline: auto;
text-align: center;
}
h1 {
margin-top: 7rem;
font-size: var(--fs-page-title);
line-height: var(--leading-title);
letter-spacing: var(--tracking-narrow);
font-weight: 600;
@media screen and (min-width: 25rem) {
margin-top: 10rem;
}
}
.qrcode {
display: flex;
justify-content: center;
margin-top: 4rem;
@media screen and (min-width: 25rem) {
margin-top: 6rem;
}
}
.qrcode-img {
width: 100%;
height: 100%;
max-width: 30rem;
max-height: 30rem;
border-radius: 0.25rem;
}
</style>
+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;
}
-64
View File
@@ -1,64 +0,0 @@
@font-face {
font-family: "Poppins";
src: url("../assets/fonts/poppins/Poppins-Regular.eot");
src:
url("../assets/fonts/poppins/Poppins-Regular.eot?#iefix")
format("embedded-opentype"),
url("../assets/fonts/poppins/Poppins-Regular.woff2") format("woff2"),
url("../assets/fonts/poppins/Poppins-Regular.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Poppins";
src: url("../assets/fonts/poppins/Poppins-Italic.eot");
src:
url("../assets/fonts/poppins/Poppins-Italic.eot?#iefix")
format("embedded-opentype"),
url("../assets/fonts/poppins/Poppins-Italic.woff2") format("woff2"),
url("../assets/fonts/poppins/Poppins-Italic.woff") format("woff");
font-weight: normal;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Poppins";
src: url("../assets/fonts/poppins/Poppins-SemiBold.eot");
src:
url("../assets/fonts/poppins/Poppins-SemiBold.eot?#iefix")
format("embedded-opentype"),
url("../assets/fonts/poppins/Poppins-SemiBold.woff2") format("woff2"),
url("../assets/fonts/poppins/Poppins-SemiBold.woff") format("woff");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Poppins";
src: url("../assets/fonts/poppins/Poppins-SemiBoldItalic.eot");
src:
url("../assets/fonts/poppins/Poppins-SemiBoldItalic.eot?#iefix")
format("embedded-opentype"),
url("../assets/fonts/poppins/Poppins-SemiBoldItalic.woff2") format("woff2"),
url("../assets/fonts/poppins/Poppins-SemiBoldItalic.woff") format("woff");
font-weight: 600;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Poppins";
src: url("../assets/fonts/poppins/Poppins-Bold.eot");
src:
url("../assets/fonts/poppins/Poppins-Bold.eot?#iefix")
format("embedded-opentype"),
url("../assets/fonts/poppins/Poppins-Bold.woff2") format("woff2"),
url("../assets/fonts/poppins/Poppins-Bold.woff") format("woff");
font-weight: bold;
font-style: normal;
font-display: swap;
}