Compare commits
5 Commits
8256a22a8c
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 74c407f906 | |||
| b29c913ad1 | |||
| 3681576f14 | |||
| 85e36e1d46 | |||
| 4166a51c6c |
@@ -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.
|
||||||
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+406
-380
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
@@ -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",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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
@@ -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",
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: My first article
|
|
||||||
---
|
|
||||||
@@ -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, e‑commerce 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 />
|
||||||
|
|
||||||
|
|||||||
@@ -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, e‑commerce 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"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function isWebkit() {
|
||||||
|
return "GestureEvent" in window;
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user