foundry-component-library 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/ContactTeaser/index.tsx +1 -1
- package/lib/components/Footer/index.tsx +9 -3
- package/lib/components/Hero/Video.tsx +43 -0
- package/lib/components/Hero/index.tsx +28 -19
- package/lib/components/Hero/styles.module.scss +12 -0
- package/lib/components/HubsAccordion/Hub.tsx +98 -82
- package/lib/components/HubsAccordion/styles.module.scss +0 -10
- package/lib/components/ServiceHubsTeaser/Tile.tsx +3 -21
- package/lib/components/ServiceHubsTeaser/index.tsx +38 -9
- package/lib/components/ServiceHubsTeaser/styles.module.scss +1 -0
- package/lib/components/VideoTeaser/index.tsx +31 -83
- package/lib/components/VideoTeaser/styles.module.scss +3 -11
- package/lib/components/cases/Pagination/index.tsx +3 -5
- package/lib/components/contact/Contacts/index.tsx +29 -7
- package/lib/components/contact/Contacts/styles.module.scss +1 -0
- package/lib/queries/getAboutPage.ts +6 -0
- package/lib/queries/getCaseById.ts +3 -0
- package/lib/queries/getCaseBySlug.ts +3 -0
- package/lib/queries/getCasesPage.ts +3 -0
- package/lib/queries/getContactPage.ts +6 -0
- package/lib/queries/getHomePage.ts +6 -0
- package/lib/queries/getHubBySlug.ts +6 -0
- package/lib/queries/getHubsPage.ts +3 -0
- package/lib/queries/getNewsPage.ts +3 -0
- package/lib/queries/getPeoplePage.ts +6 -0
- package/lib/queries/getPerformanceHubPage.ts +6 -0
- package/lib/queries/getPostBySlug.ts +3 -0
- package/lib/types/index.ts +3 -0
- package/package.json +1 -1
|
@@ -7,6 +7,9 @@ import { NextLink } from "../../types";
|
|
|
7
7
|
function Footer({
|
|
8
8
|
details,
|
|
9
9
|
Link,
|
|
10
|
+
facebook,
|
|
11
|
+
linkedin,
|
|
12
|
+
instagram,
|
|
10
13
|
}: {
|
|
11
14
|
details: {
|
|
12
15
|
berlinEmail: string;
|
|
@@ -14,6 +17,9 @@ function Footer({
|
|
|
14
17
|
newyorkEmail: string;
|
|
15
18
|
};
|
|
16
19
|
Link: NextLink;
|
|
20
|
+
facebook: string;
|
|
21
|
+
linkedin: string;
|
|
22
|
+
instagram: string;
|
|
17
23
|
}) {
|
|
18
24
|
const { berlinEmail, zurichEmail, newyorkEmail } = details;
|
|
19
25
|
const year = new Date().getFullYear();
|
|
@@ -81,13 +87,13 @@ function Footer({
|
|
|
81
87
|
<div className={styles.socialHeading}>Follow Us</div>
|
|
82
88
|
<ul className={styles.menuSocial}>
|
|
83
89
|
<li className={styles.menuItem}>
|
|
84
|
-
<Link href=
|
|
90
|
+
<Link href={instagram}>Instagram</Link>
|
|
85
91
|
</li>
|
|
86
92
|
<li className={styles.menuItem}>
|
|
87
|
-
<Link href=
|
|
93
|
+
<Link href={facebook}>Facebook</Link>
|
|
88
94
|
</li>
|
|
89
95
|
<li className={styles.menuItem}>
|
|
90
|
-
<Link href=
|
|
96
|
+
<Link href={linkedin}>LinkedIn</Link>
|
|
91
97
|
</li>
|
|
92
98
|
</ul>
|
|
93
99
|
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { useOnScreen } from "../../hooks/useOnScreen";
|
|
4
|
+
import ReactPlayer from "react-player/lazy";
|
|
5
|
+
import styles from "./styles.module.scss";
|
|
6
|
+
|
|
7
|
+
const Video = ({ url }: { url: string }) => {
|
|
8
|
+
const sectionRef = useRef(null);
|
|
9
|
+
const onScreen = useOnScreen(sectionRef, "1000px");
|
|
10
|
+
// const [playing, setPlaying] = useState(false);
|
|
11
|
+
const [videoLoaded, setVideoLoaded] = useState(false);
|
|
12
|
+
const [hasWindow, setHasWindow] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (typeof window !== "undefined") {
|
|
16
|
+
setHasWindow(true);
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!videoLoaded && onScreen) {
|
|
22
|
+
setVideoLoaded(true);
|
|
23
|
+
}
|
|
24
|
+
}, [videoLoaded, onScreen]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={styles.videoWrapper} ref={sectionRef}>
|
|
28
|
+
{hasWindow && (
|
|
29
|
+
<ReactPlayer
|
|
30
|
+
playing
|
|
31
|
+
url={url}
|
|
32
|
+
width="100%"
|
|
33
|
+
height="100%"
|
|
34
|
+
loop
|
|
35
|
+
muted
|
|
36
|
+
playsinline
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default Video;
|
|
@@ -4,9 +4,11 @@ import styles from "./styles.module.scss";
|
|
|
4
4
|
import Container from "../Container";
|
|
5
5
|
import { NextImage, NextLink } from "../../types";
|
|
6
6
|
import { useOnScreen } from "../../hooks/useOnScreen";
|
|
7
|
+
import Video from "./Video";
|
|
7
8
|
|
|
8
9
|
const Hero = ({
|
|
9
10
|
image,
|
|
11
|
+
video,
|
|
10
12
|
text,
|
|
11
13
|
isFullWidth,
|
|
12
14
|
isFirst,
|
|
@@ -16,6 +18,7 @@ const Hero = ({
|
|
|
16
18
|
Image,
|
|
17
19
|
}: {
|
|
18
20
|
image: string;
|
|
21
|
+
video?: string;
|
|
19
22
|
text: string;
|
|
20
23
|
isFullWidth?: boolean;
|
|
21
24
|
isFirst?: boolean;
|
|
@@ -30,7 +33,9 @@ const Hero = ({
|
|
|
30
33
|
const sectionRef = useRef(null);
|
|
31
34
|
const onScreen = useOnScreen(sectionRef, "-50%");
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
console.log("pop", video);
|
|
37
|
+
|
|
38
|
+
if (!image && !video) return;
|
|
34
39
|
|
|
35
40
|
if (isFullWidth) {
|
|
36
41
|
return (
|
|
@@ -38,15 +43,17 @@ const Hero = ({
|
|
|
38
43
|
ref={sectionRef}
|
|
39
44
|
className={`${styles.hero} ${styles.isFullWidth} ${
|
|
40
45
|
noMarginBottom ? styles.noMarginBottom : ""
|
|
41
|
-
}`}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
}`}>
|
|
47
|
+
{image && !video && (
|
|
48
|
+
<Image
|
|
49
|
+
className={`${styles.background} ${onScreen ? styles.active : ""}`}
|
|
50
|
+
src={image}
|
|
51
|
+
width="1280"
|
|
52
|
+
height="600"
|
|
53
|
+
alt={text}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
{video && <Video url={video} />}
|
|
50
57
|
<div className={styles.texts}>
|
|
51
58
|
{text && <div className={styles.heading}>{text}</div>}
|
|
52
59
|
{btn && (
|
|
@@ -64,15 +71,17 @@ const Hero = ({
|
|
|
64
71
|
<div
|
|
65
72
|
className={`${styles.hero} ${isFirst ? styles.first : ""} ${
|
|
66
73
|
noMarginBottom ? styles.noMarginBottom : ""
|
|
67
|
-
}`}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
}`}>
|
|
75
|
+
{image && !video && (
|
|
76
|
+
<Image
|
|
77
|
+
className={`${styles.background} ${onScreen ? styles.active : ""}`}
|
|
78
|
+
src={image}
|
|
79
|
+
width="1280"
|
|
80
|
+
height="600"
|
|
81
|
+
alt={text}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
{video && <Video url={video} />}
|
|
76
85
|
<div className={styles.texts}>
|
|
77
86
|
{text && <div className={styles.heading}>{text}</div>}
|
|
78
87
|
{btn && (
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useRef,
|
|
4
|
+
useState,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
Dispatch,
|
|
7
|
+
SetStateAction,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
3
10
|
import { NextImage, NextLink, type Hub } from "../../types";
|
|
4
11
|
import styles from "./styles.module.scss";
|
|
5
12
|
import Arrow from "../../assets/svg/arrow.svg";
|
|
@@ -21,6 +28,8 @@ const Hub = ({
|
|
|
21
28
|
Image: NextImage;
|
|
22
29
|
}) => {
|
|
23
30
|
const casesRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
32
|
+
const [height, setHeight] = useState<number | "auto">(0);
|
|
24
33
|
|
|
25
34
|
const {
|
|
26
35
|
handleMouseDown,
|
|
@@ -33,6 +42,12 @@ const Hub = ({
|
|
|
33
42
|
const isActive = active === hub.slug;
|
|
34
43
|
const customFields = hub.customFieldsHub;
|
|
35
44
|
|
|
45
|
+
useLayoutEffect(() => {
|
|
46
|
+
if (contentRef.current) {
|
|
47
|
+
setHeight(contentRef.current.scrollHeight);
|
|
48
|
+
}
|
|
49
|
+
}, [isActive, customFields]);
|
|
50
|
+
|
|
36
51
|
return (
|
|
37
52
|
<div className={`${styles.hub} ${isActive ? styles.active : ""}`}>
|
|
38
53
|
<div className={styles.top}>
|
|
@@ -40,94 +55,95 @@ const Hub = ({
|
|
|
40
55
|
<div className={styles.text}>{customFields.subheading}</div>
|
|
41
56
|
<button
|
|
42
57
|
className={styles.icon}
|
|
43
|
-
onClick={() =>
|
|
44
|
-
|
|
45
|
-
setActive("");
|
|
46
|
-
} else {
|
|
47
|
-
setActive(hub.slug);
|
|
48
|
-
}
|
|
49
|
-
}}
|
|
50
|
-
>
|
|
51
|
-
{isActive && <Minus />}
|
|
52
|
-
{!isActive && <Plus />}
|
|
58
|
+
onClick={() => setActive(isActive ? "" : hub.slug)}>
|
|
59
|
+
{isActive ? <Minus /> : <Plus />}
|
|
53
60
|
</button>
|
|
54
61
|
</div>
|
|
55
|
-
<div className={styles.rows}>
|
|
56
|
-
<div className={styles.row}>
|
|
57
|
-
<div className={styles.rowWrapper}>
|
|
58
|
-
<div className={styles.subheading}>SERVICE</div>
|
|
59
|
-
<div className={styles.right}>
|
|
60
|
-
{customFields.tags && (
|
|
61
|
-
<div className={styles.tags}>
|
|
62
|
-
{customFields.tags.map((tag) => `${tag.tag} // `)}
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
<div className={styles.row}>
|
|
69
|
-
<div className={styles.rowWrapper}>
|
|
70
|
-
<div className={styles.subheading}>APPROACH</div>
|
|
71
|
-
<div className={styles.right}>
|
|
72
|
-
<div className={styles.paragraph}>{customFields.approach}</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
<div className={styles.row}>
|
|
77
|
-
<div className={styles.rowWrapper}>
|
|
78
|
-
<div className={styles.subheading}>RELATED WORK</div>
|
|
79
|
-
<div className={styles.right}>
|
|
80
|
-
{customFields.relatedWork && (
|
|
81
|
-
<div
|
|
82
|
-
className={styles.cases}
|
|
83
|
-
ref={casesRef}
|
|
84
|
-
onMouseDown={handleMouseDown}
|
|
85
|
-
onMouseMove={handleMouseMove}
|
|
86
|
-
onMouseUp={handleMouseUp}
|
|
87
|
-
onMouseLeave={handleMouseUp}
|
|
88
|
-
style={{
|
|
89
|
-
...(dragStyle as React.CSSProperties),
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
{customFields.relatedWork.map((item) => {
|
|
93
|
-
const { thumbnailImage, mainImage } = item.case;
|
|
94
62
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
63
|
+
<AnimatePresence initial={false}>
|
|
64
|
+
{(isActive || height !== 0) && (
|
|
65
|
+
<motion.div
|
|
66
|
+
key="content"
|
|
67
|
+
initial={{ height: 0, opacity: 0 }}
|
|
68
|
+
animate={{
|
|
69
|
+
height: isActive ? height : 0,
|
|
70
|
+
opacity: isActive ? 1 : 0,
|
|
71
|
+
}}
|
|
72
|
+
exit={{ height: 0, opacity: 0 }}
|
|
73
|
+
transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
|
|
74
|
+
style={{ overflow: "hidden" }}>
|
|
75
|
+
<div ref={contentRef}>
|
|
76
|
+
<div className={styles.rows}>
|
|
77
|
+
<div className={styles.row}>
|
|
78
|
+
<div className={styles.rowWrapper}>
|
|
79
|
+
<div className={styles.subheading}>SERVICE</div>
|
|
80
|
+
<div className={styles.right}>
|
|
81
|
+
{customFields.tags && (
|
|
82
|
+
<div className={styles.tags}>
|
|
83
|
+
{customFields.tags.map((tag) => `${tag.tag} // `)}
|
|
113
84
|
</div>
|
|
114
|
-
|
|
115
|
-
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className={styles.row}>
|
|
91
|
+
<div className={styles.rowWrapper}>
|
|
92
|
+
<div className={styles.subheading}>RELATED WORK</div>
|
|
93
|
+
<div className={styles.right}>
|
|
94
|
+
{customFields.relatedWork && (
|
|
95
|
+
<div
|
|
96
|
+
className={styles.cases}
|
|
97
|
+
ref={casesRef}
|
|
98
|
+
onMouseDown={handleMouseDown}
|
|
99
|
+
onMouseMove={handleMouseMove}
|
|
100
|
+
onMouseUp={handleMouseUp}
|
|
101
|
+
onMouseLeave={handleMouseUp}
|
|
102
|
+
style={{
|
|
103
|
+
...(dragStyle as React.CSSProperties),
|
|
104
|
+
}}>
|
|
105
|
+
{customFields.relatedWork.map((item) => {
|
|
106
|
+
const { thumbnailImage, mainImage } = item.case;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Link
|
|
110
|
+
href={item.uri}
|
|
111
|
+
key={item.id}
|
|
112
|
+
className={styles.case}
|
|
113
|
+
draggable={false}
|
|
114
|
+
onClick={preventedClick}>
|
|
115
|
+
<div className={styles.caseImage}>
|
|
116
|
+
<Image
|
|
117
|
+
src={
|
|
118
|
+
thumbnailImage?.sourceUrl ||
|
|
119
|
+
mainImage?.sourceUrl ||
|
|
120
|
+
""
|
|
121
|
+
}
|
|
122
|
+
alt={item.title}
|
|
123
|
+
fill
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
<div className={styles.caseTitle}>
|
|
127
|
+
{item.title} <Arrow />
|
|
128
|
+
</div>
|
|
129
|
+
</Link>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
116
132
|
</div>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div className={styles.more}>
|
|
137
|
+
<Link href={hub.uri}>
|
|
138
|
+
Learn More <Arrow />
|
|
139
|
+
</Link>
|
|
140
|
+
</div>
|
|
120
141
|
</div>
|
|
121
|
-
|
|
142
|
+
</div>
|
|
122
143
|
</div>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Learn More <Arrow />
|
|
127
|
-
</Link>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
144
|
+
</motion.div>
|
|
145
|
+
)}
|
|
146
|
+
</AnimatePresence>
|
|
131
147
|
</div>
|
|
132
148
|
);
|
|
133
149
|
};
|
|
@@ -12,12 +12,6 @@
|
|
|
12
12
|
color: $color-brown;
|
|
13
13
|
position: relative;
|
|
14
14
|
|
|
15
|
-
&.active {
|
|
16
|
-
.rows {
|
|
17
|
-
display: block;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
&:before {
|
|
22
16
|
content: "";
|
|
23
17
|
position: absolute;
|
|
@@ -105,10 +99,6 @@
|
|
|
105
99
|
}
|
|
106
100
|
}
|
|
107
101
|
|
|
108
|
-
.rows {
|
|
109
|
-
display: none;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
102
|
.row {
|
|
113
103
|
padding-bottom: 64px;
|
|
114
104
|
position: relative;
|
|
@@ -2,14 +2,12 @@ import { MouseEventHandler } from "react";
|
|
|
2
2
|
import styles from "./styles.module.scss";
|
|
3
3
|
import Plus from "../../assets/svg/plus.svg";
|
|
4
4
|
import { NextLink } from "../../types";
|
|
5
|
-
import { motion } from "framer-motion";
|
|
6
5
|
import Arrow from "../../assets/svg/arrow.svg";
|
|
7
6
|
|
|
8
7
|
const Tile = ({
|
|
9
8
|
text,
|
|
10
9
|
background,
|
|
11
10
|
href,
|
|
12
|
-
i,
|
|
13
11
|
onClick,
|
|
14
12
|
Link,
|
|
15
13
|
hoverText,
|
|
@@ -23,28 +21,12 @@ const Tile = ({
|
|
|
23
21
|
hoverText: string;
|
|
24
22
|
}) => {
|
|
25
23
|
return (
|
|
26
|
-
<
|
|
27
|
-
className={styles.tileWrapper}
|
|
28
|
-
whileHover={{
|
|
29
|
-
scale: 1.08,
|
|
30
|
-
rotate: i % 2 === 0 ? -3 : 3,
|
|
31
|
-
boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
|
|
32
|
-
y: [0, Math.random() * 12 - 6, 0],
|
|
33
|
-
transition: {
|
|
34
|
-
y: {
|
|
35
|
-
duration: 2,
|
|
36
|
-
repeat: Infinity,
|
|
37
|
-
ease: "easeInOut",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
}}
|
|
41
|
-
>
|
|
24
|
+
<div className={styles.tileWrapper}>
|
|
42
25
|
<Link
|
|
43
26
|
href={href}
|
|
44
27
|
onClick={onClick}
|
|
45
28
|
draggable="false"
|
|
46
|
-
className={`${styles.tile} ${styles[background]}`}
|
|
47
|
-
>
|
|
29
|
+
className={`${styles.tile} ${styles[background]}`}>
|
|
48
30
|
<div className={styles.face}>
|
|
49
31
|
<div className={styles.text}>{text}</div>
|
|
50
32
|
<div>
|
|
@@ -58,7 +40,7 @@ const Tile = ({
|
|
|
58
40
|
</div>
|
|
59
41
|
</div>
|
|
60
42
|
</Link>
|
|
61
|
-
</
|
|
43
|
+
</div>
|
|
62
44
|
);
|
|
63
45
|
};
|
|
64
46
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useRef } from "react";
|
|
2
|
+
import { useRef, useEffect } from "react";
|
|
3
3
|
import Container from "../Container";
|
|
4
4
|
import styles from "./styles.module.scss";
|
|
5
5
|
import TextSection from "../TextSection";
|
|
6
6
|
import Tile from "./Tile";
|
|
7
7
|
import { NextLink } from "../../types";
|
|
8
|
-
import { motion } from "framer-motion";
|
|
8
|
+
import { motion, useScroll, useMotionValue, useSpring } from "framer-motion";
|
|
9
|
+
|
|
10
|
+
const SCROLL_DISTANCE = -500;
|
|
9
11
|
|
|
10
12
|
const ServiceHubsTeaser = ({
|
|
11
13
|
caption,
|
|
@@ -27,9 +29,32 @@ const ServiceHubsTeaser = ({
|
|
|
27
29
|
}[];
|
|
28
30
|
Link: NextLink;
|
|
29
31
|
}) => {
|
|
30
|
-
const wrapperRef = useRef(null);
|
|
32
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
31
33
|
const dragStarted = useRef(false);
|
|
32
34
|
|
|
35
|
+
const x = useMotionValue(0);
|
|
36
|
+
|
|
37
|
+
const smoothX = useSpring(x, {
|
|
38
|
+
stiffness: 120,
|
|
39
|
+
damping: 20,
|
|
40
|
+
mass: 0.1,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const { scrollYProgress } = useScroll({
|
|
44
|
+
target: wrapperRef,
|
|
45
|
+
offset: ["start end", "end start"],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const unsubscribe = scrollYProgress.on("change", (latest) => {
|
|
50
|
+
const scrollOffset = latest * SCROLL_DISTANCE;
|
|
51
|
+
if (!dragStarted.current) {
|
|
52
|
+
x.set(scrollOffset);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return () => unsubscribe();
|
|
56
|
+
}, [scrollYProgress, x]);
|
|
57
|
+
|
|
33
58
|
return (
|
|
34
59
|
<div className={styles.benefits}>
|
|
35
60
|
<TextSection caption={caption} heading={heading} text={text} isSmall />
|
|
@@ -39,18 +64,22 @@ const ServiceHubsTeaser = ({
|
|
|
39
64
|
className={styles.tiles}
|
|
40
65
|
drag="x"
|
|
41
66
|
dragConstraints={wrapperRef}
|
|
67
|
+
style={{ x: smoothX }}
|
|
68
|
+
dragMomentum={true}
|
|
42
69
|
onDragStart={() => (dragStarted.current = true)}
|
|
43
70
|
onDragEnd={() => {
|
|
44
71
|
setTimeout(() => {
|
|
45
72
|
dragStarted.current = false;
|
|
46
73
|
}, 0);
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
74
|
+
}}>
|
|
49
75
|
{tiles.map((tile, i) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
const colors: Array<"pink" | "yellow" | "brown" | "blue"> = [
|
|
77
|
+
"pink",
|
|
78
|
+
"yellow",
|
|
79
|
+
"brown",
|
|
80
|
+
"blue",
|
|
81
|
+
];
|
|
82
|
+
const background = colors[i % colors.length];
|
|
54
83
|
|
|
55
84
|
return (
|
|
56
85
|
<Tile
|
|
@@ -5,107 +5,55 @@ import styles from "./styles.module.scss";
|
|
|
5
5
|
import Mute from "../../assets/svg/mute.svg";
|
|
6
6
|
import Muted from "../../assets/svg/muted.svg";
|
|
7
7
|
import PlayButton from "../../assets/svg/play-button.svg";
|
|
8
|
-
import {
|
|
9
|
-
motion,
|
|
10
|
-
useScroll,
|
|
11
|
-
useMotionValueEvent,
|
|
12
|
-
useTransform,
|
|
13
|
-
} from "motion/react";
|
|
14
8
|
import Logo from "../../assets/svg/footer-logo.svg";
|
|
15
9
|
|
|
16
10
|
function VideoTeaser({ url }: { url: string }) {
|
|
17
|
-
const [playing, setPlaying] = useState(
|
|
18
|
-
const [hasWindow, setHasWindow] = useState(false);
|
|
11
|
+
const [playing, setPlaying] = useState(true);
|
|
19
12
|
const [isMuted, setIsMuted] = useState(true);
|
|
20
|
-
const [
|
|
21
|
-
const { scrollY } = useScroll();
|
|
22
|
-
const [path, setPath] = useState(
|
|
23
|
-
`polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)`
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const progress = useTransform(scrollY, [100, 300], [0, 1]);
|
|
13
|
+
const [hasWindow, setHasWindow] = useState(false);
|
|
27
14
|
|
|
28
|
-
const
|
|
29
|
-
|
|
15
|
+
const handleClick = () => {
|
|
16
|
+
setPlaying(!playing);
|
|
30
17
|
};
|
|
31
18
|
|
|
32
|
-
useMotionValueEvent(scrollY, "change", () => {
|
|
33
|
-
setPath(
|
|
34
|
-
`polygon(
|
|
35
|
-
${lerp(50, 0, progress.get())}%
|
|
36
|
-
${lerp(50, 0, progress.get())}%,
|
|
37
|
-
${lerp(50, 100, progress.get())}%
|
|
38
|
-
${lerp(50, 0, progress.get())}%,
|
|
39
|
-
${lerp(50, 100, progress.get())}%
|
|
40
|
-
${lerp(50, 100, progress.get())}%,
|
|
41
|
-
${lerp(50, 0, progress.get())}%
|
|
42
|
-
${lerp(50, 100, progress.get())}%)`
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
if (progress.get() >= 1) {
|
|
46
|
-
setIsAnimated(true);
|
|
47
|
-
} else {
|
|
48
|
-
setIsAnimated(false);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
19
|
useEffect(() => {
|
|
53
20
|
if (typeof window !== "undefined") {
|
|
54
21
|
setHasWindow(true);
|
|
55
22
|
}
|
|
56
23
|
}, []);
|
|
57
24
|
|
|
58
|
-
const handleClick = () => {
|
|
59
|
-
setPlaying(!playing);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
25
|
if (!url) return;
|
|
63
26
|
|
|
64
27
|
return (
|
|
65
28
|
<>
|
|
66
|
-
<div
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<button
|
|
82
|
-
className={styles.btnMute}
|
|
83
|
-
onClick={() => setIsMuted(!isMuted)}
|
|
84
|
-
>
|
|
85
|
-
{isMuted ? <Muted /> : <Mute />}
|
|
86
|
-
</button>
|
|
87
|
-
<div className={styles.video} onClick={handleClick}>
|
|
88
|
-
<div
|
|
89
|
-
className={`${styles.playButton} ${
|
|
90
|
-
playing ? styles.playing : ""
|
|
91
|
-
} ${isAnimated ? styles.isAnimated : ""}`}
|
|
92
|
-
>
|
|
93
|
-
<PlayButton />
|
|
94
|
-
</div>
|
|
95
|
-
{hasWindow && (
|
|
96
|
-
<ReactPlayer
|
|
97
|
-
playing={playing}
|
|
98
|
-
url={url}
|
|
99
|
-
width="100%"
|
|
100
|
-
height="100%"
|
|
101
|
-
muted={isMuted}
|
|
102
|
-
config={{
|
|
103
|
-
file: { attributes: { poster: "/video-poster.jpg" } },
|
|
104
|
-
}}
|
|
105
|
-
/>
|
|
106
|
-
)}
|
|
29
|
+
<div className={styles.logoWrapper}>
|
|
30
|
+
<Logo />
|
|
31
|
+
</div>
|
|
32
|
+
<div
|
|
33
|
+
className={`${styles.wrapper} ${url ? styles.playCursor : ""} ${
|
|
34
|
+
playing ? styles.playing : ""
|
|
35
|
+
}`}>
|
|
36
|
+
<button className={styles.btnMute} onClick={() => setIsMuted(!isMuted)}>
|
|
37
|
+
{isMuted ? <Muted /> : <Mute />}
|
|
38
|
+
</button>
|
|
39
|
+
<div className={styles.video} onClick={handleClick}>
|
|
40
|
+
<div
|
|
41
|
+
className={`${styles.playButton} ${playing ? styles.playing : ""}`}>
|
|
42
|
+
<PlayButton />
|
|
107
43
|
</div>
|
|
108
|
-
|
|
44
|
+
{hasWindow && (
|
|
45
|
+
<ReactPlayer
|
|
46
|
+
playing={playing}
|
|
47
|
+
url={url}
|
|
48
|
+
width="100%"
|
|
49
|
+
height="100%"
|
|
50
|
+
muted={isMuted}
|
|
51
|
+
config={{
|
|
52
|
+
file: { attributes: { poster: "/video-poster.jpg" } },
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
109
57
|
</div>
|
|
110
58
|
</>
|
|
111
59
|
);
|
|
@@ -2,19 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
.wrapper {
|
|
4
4
|
background-color: $color-white;
|
|
5
|
-
background-size: cover;
|
|
6
|
-
background-position: center;
|
|
7
5
|
width: 100%;
|
|
8
6
|
height: 790px;
|
|
9
|
-
max-height: calc(100vh -
|
|
7
|
+
max-height: calc(100vh - 79px);
|
|
10
8
|
text-align: center;
|
|
11
9
|
box-sizing: border-box;
|
|
12
10
|
display: flex;
|
|
13
11
|
align-items: center;
|
|
14
|
-
|
|
15
|
-
top: 79px;
|
|
12
|
+
padding-top: 79px;
|
|
16
13
|
overflow: hidden;
|
|
17
|
-
|
|
14
|
+
position: relative;
|
|
18
15
|
|
|
19
16
|
@media #{$QUERY-md} {
|
|
20
17
|
padding: 100px 50px;
|
|
@@ -90,11 +87,6 @@
|
|
|
90
87
|
justify-content: center;
|
|
91
88
|
cursor: pointer;
|
|
92
89
|
transition: opacity 0.3s;
|
|
93
|
-
opacity: 0;
|
|
94
|
-
|
|
95
|
-
&.isAnimated {
|
|
96
|
-
opacity: 1;
|
|
97
|
-
}
|
|
98
90
|
|
|
99
91
|
&.playing {
|
|
100
92
|
opacity: 0;
|
|
@@ -23,7 +23,7 @@ const Pagination = ({
|
|
|
23
23
|
const handleClick = (page: number) => {
|
|
24
24
|
const params = new URLSearchParams(searchParams);
|
|
25
25
|
params.set("page", String(page));
|
|
26
|
-
router.push(`?${params.toString()}`, { scroll:
|
|
26
|
+
router.push(`?${params.toString()}`, { scroll: true });
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
return (
|
|
@@ -34,8 +34,7 @@ const Pagination = ({
|
|
|
34
34
|
return (
|
|
35
35
|
<span
|
|
36
36
|
key={page + 1}
|
|
37
|
-
className={`${styles.indicator} ${styles.current}`}
|
|
38
|
-
>
|
|
37
|
+
className={`${styles.indicator} ${styles.current}`}>
|
|
39
38
|
{page + 1}
|
|
40
39
|
</span>
|
|
41
40
|
);
|
|
@@ -47,8 +46,7 @@ const Pagination = ({
|
|
|
47
46
|
className={styles.indicator}
|
|
48
47
|
onClick={() => {
|
|
49
48
|
handleClick(page + 1);
|
|
50
|
-
}}
|
|
51
|
-
>
|
|
49
|
+
}}>
|
|
52
50
|
{page + 1}
|
|
53
51
|
</button>
|
|
54
52
|
);
|
|
@@ -17,12 +17,16 @@ function Contacts({
|
|
|
17
17
|
email: string;
|
|
18
18
|
}[];
|
|
19
19
|
}) {
|
|
20
|
-
const [
|
|
21
|
-
const
|
|
20
|
+
const [isTypeform1Open, setIsTypeform1Open] = useState(false);
|
|
21
|
+
const [isTypeform2Open, setIsTypeform2Open] = useState(false);
|
|
22
|
+
const ref = useClickOutside<HTMLDivElement>(() => {
|
|
23
|
+
setIsTypeform1Open(false);
|
|
24
|
+
setIsTypeform2Open(false);
|
|
25
|
+
});
|
|
22
26
|
if (!items) return;
|
|
23
27
|
|
|
24
28
|
return (
|
|
25
|
-
<section className={styles.section}>
|
|
29
|
+
<section className={styles.section} id="contact-us">
|
|
26
30
|
<Container>
|
|
27
31
|
<h2 className={styles.title}>{title}</h2>
|
|
28
32
|
<div className={styles.boxes}>
|
|
@@ -44,8 +48,13 @@ function Contacts({
|
|
|
44
48
|
</div>
|
|
45
49
|
<a
|
|
46
50
|
className={styles.buttonSecondary}
|
|
47
|
-
onClick={() =>
|
|
48
|
-
|
|
51
|
+
onClick={() => {
|
|
52
|
+
if (i === 2) {
|
|
53
|
+
setIsTypeform1Open(true);
|
|
54
|
+
} else {
|
|
55
|
+
setIsTypeform2Open(true);
|
|
56
|
+
}
|
|
57
|
+
}}>
|
|
49
58
|
{i === 0 && "Contact Account"}
|
|
50
59
|
{i === 1 && "General Contact"}
|
|
51
60
|
{i === 2 && "Contact HR"}
|
|
@@ -57,8 +66,7 @@ function Contacts({
|
|
|
57
66
|
</div>
|
|
58
67
|
<div
|
|
59
68
|
className={styles.typeform}
|
|
60
|
-
style={{ display:
|
|
61
|
-
>
|
|
69
|
+
style={{ display: isTypeform1Open ? "flex" : "none" }}>
|
|
62
70
|
<Script src="//embed.typeform.com/next/embed.js" />
|
|
63
71
|
<div ref={ref} className={styles.typeformWrapper}>
|
|
64
72
|
{/* <div data-tf-widget="qmv6Yk" data-tf-iframe-props="title=Foundry Website Contact Form" data-tf-medium="snippet" style={{ width: '100%', height: '400px' }} /> */}
|
|
@@ -70,6 +78,20 @@ function Contacts({
|
|
|
70
78
|
/>
|
|
71
79
|
</div>
|
|
72
80
|
</div>
|
|
81
|
+
<div
|
|
82
|
+
className={styles.typeform}
|
|
83
|
+
style={{ display: isTypeform2Open ? "flex" : "none" }}>
|
|
84
|
+
<Script src="//embed.typeform.com/next/embed.js" />
|
|
85
|
+
<div ref={ref} className={styles.typeformWrapper}>
|
|
86
|
+
{/* <div data-tf-widget="qmv6Yk" data-tf-iframe-props="title=Foundry Website Contact Form" data-tf-medium="snippet" style={{ width: '100%', height: '400px' }} /> */}
|
|
87
|
+
<div
|
|
88
|
+
data-tf-widget="qmv6Yk"
|
|
89
|
+
data-tf-iframe-props="title=Foundry Website Contact Form"
|
|
90
|
+
data-tf-medium="snippet"
|
|
91
|
+
style={{ width: "100%", height: "400px" }}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
73
95
|
</Container>
|
|
74
96
|
</section>
|
|
75
97
|
);
|
|
@@ -64,6 +64,9 @@ type AboutPage = {
|
|
|
64
64
|
};
|
|
65
65
|
contactPage: {
|
|
66
66
|
customFieldsContact: {
|
|
67
|
+
facebook: string;
|
|
68
|
+
instagram: string;
|
|
69
|
+
linkedin: string;
|
|
67
70
|
berlinImage?: {
|
|
68
71
|
sourceUrl: string;
|
|
69
72
|
};
|
|
@@ -165,6 +168,9 @@ export default async function getAboutPage({
|
|
|
165
168
|
}
|
|
166
169
|
contactPage: page(id: "${contactPage}", idType: URI) {
|
|
167
170
|
customFieldsContact {
|
|
171
|
+
facebook
|
|
172
|
+
instagram
|
|
173
|
+
linkedin
|
|
168
174
|
berlinImage {
|
|
169
175
|
sourceUrl
|
|
170
176
|
}
|
|
@@ -4,6 +4,9 @@ import client from "./client";
|
|
|
4
4
|
export type ContactPage = {
|
|
5
5
|
title: string;
|
|
6
6
|
customFieldsContact: {
|
|
7
|
+
facebook: string;
|
|
8
|
+
instagram: string;
|
|
9
|
+
linkedin: string;
|
|
7
10
|
topCaption: string;
|
|
8
11
|
topHeading: string;
|
|
9
12
|
berlinImage: {
|
|
@@ -57,6 +60,9 @@ export default async function getContactPage({
|
|
|
57
60
|
page(id: $slug, idType: URI) {
|
|
58
61
|
title
|
|
59
62
|
customFieldsContact {
|
|
63
|
+
facebook
|
|
64
|
+
instagram
|
|
65
|
+
linkedin
|
|
60
66
|
topCaption
|
|
61
67
|
topHeading
|
|
62
68
|
berlinImage {
|
|
@@ -78,6 +78,9 @@ type HomePage = {
|
|
|
78
78
|
posts: PostPreview[];
|
|
79
79
|
contactPage: {
|
|
80
80
|
customFieldsContact: {
|
|
81
|
+
facebook: string;
|
|
82
|
+
instagram: string;
|
|
83
|
+
linkedin: string;
|
|
81
84
|
berlinImage?: {
|
|
82
85
|
sourceUrl: string;
|
|
83
86
|
};
|
|
@@ -216,6 +219,9 @@ export default async function getHomePage({
|
|
|
216
219
|
}
|
|
217
220
|
contactPage: page(id: "${contactPage}", idType: URI) {
|
|
218
221
|
customFieldsContact {
|
|
222
|
+
facebook
|
|
223
|
+
instagram
|
|
224
|
+
linkedin
|
|
219
225
|
berlinImage {
|
|
220
226
|
sourceUrl
|
|
221
227
|
}
|
|
@@ -45,6 +45,9 @@ export default async function getHubBySlug({
|
|
|
45
45
|
mainimage {
|
|
46
46
|
sourceUrl
|
|
47
47
|
}
|
|
48
|
+
mainvideo{
|
|
49
|
+
mediaItemUrl
|
|
50
|
+
}
|
|
48
51
|
relatedWork {
|
|
49
52
|
... on Case {
|
|
50
53
|
title
|
|
@@ -84,6 +87,9 @@ export default async function getHubBySlug({
|
|
|
84
87
|
}
|
|
85
88
|
contactPage: page(id: "${contactPage}", idType: URI) {
|
|
86
89
|
customFieldsContact {
|
|
90
|
+
facebook
|
|
91
|
+
instagram
|
|
92
|
+
linkedin
|
|
87
93
|
berlinImage {
|
|
88
94
|
sourceUrl
|
|
89
95
|
}
|
|
@@ -40,6 +40,9 @@ type PeoplePage = {
|
|
|
40
40
|
};
|
|
41
41
|
contactPage: {
|
|
42
42
|
customFieldsContact: {
|
|
43
|
+
facebook: string;
|
|
44
|
+
instagram: string;
|
|
45
|
+
linkedin: string;
|
|
43
46
|
berlinImage?: {
|
|
44
47
|
sourceUrl: string;
|
|
45
48
|
};
|
|
@@ -123,6 +126,9 @@ export default async function getContactPage({
|
|
|
123
126
|
}
|
|
124
127
|
contactPage: page(id: "${contactPage}", idType: URI) {
|
|
125
128
|
customFieldsContact {
|
|
129
|
+
facebook
|
|
130
|
+
instagram
|
|
131
|
+
linkedin
|
|
126
132
|
berlinImage {
|
|
127
133
|
sourceUrl
|
|
128
134
|
}
|
|
@@ -50,6 +50,9 @@ type PerformancePage = {
|
|
|
50
50
|
};
|
|
51
51
|
contactPage: {
|
|
52
52
|
customFieldsContact: {
|
|
53
|
+
facebook: string;
|
|
54
|
+
instagram: string;
|
|
55
|
+
linkedin: string;
|
|
53
56
|
berlinImage?: {
|
|
54
57
|
sourceUrl: string;
|
|
55
58
|
};
|
|
@@ -128,6 +131,9 @@ export default async function getPerformanceHubPage(): Promise<PerformancePage>
|
|
|
128
131
|
}
|
|
129
132
|
contactPage: page(id: "contact", idType: URI) {
|
|
130
133
|
customFieldsContact {
|
|
134
|
+
facebook
|
|
135
|
+
instagram
|
|
136
|
+
linkedin
|
|
131
137
|
berlinImage {
|
|
132
138
|
sourceUrl
|
|
133
139
|
}
|
package/lib/types/index.ts
CHANGED