diff --git a/index.html b/index.html index df1c6ff..27987ba 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/public/assets/svg/general/gateway.svg b/public/assets/svg/general/gateway.svg new file mode 100644 index 0000000..abee0a6 --- /dev/null +++ b/public/assets/svg/general/gateway.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/svg/general/mobile.svg b/public/assets/svg/general/mobile.svg new file mode 100644 index 0000000..464574a --- /dev/null +++ b/public/assets/svg/general/mobile.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/svg/general/tensiometer.svg b/public/assets/svg/general/tensiometer.svg new file mode 100644 index 0000000..9a9b45e --- /dev/null +++ b/public/assets/svg/general/tensiometer.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/assets/svg/general/wlan.svg b/public/assets/svg/general/wlan.svg new file mode 100644 index 0000000..983dd99 --- /dev/null +++ b/public/assets/svg/general/wlan.svg @@ -0,0 +1,3 @@ + diff --git a/src/tsx/components/cards/WelcomeCard.tsx b/src/tsx/components/cards/WelcomeCard.tsx new file mode 100644 index 0000000..da2c698 --- /dev/null +++ b/src/tsx/components/cards/WelcomeCard.tsx @@ -0,0 +1,68 @@ +import { useState, useEffect } from 'react'; +import Arrow from '../../icons/Arrow'; +import Lottie from 'lottie-react'; +import logoAnimation from '../../../json/logoAnimation.json'; +import { useOutsideClick } from '../../hooks/useOutsideClick'; +import { hasCookie } from '../../helper/cookies'; + +interface WelcomeCardProps { + onClose: () => void; + handleStartAnmiation: () => void; + delay: number; + isOverlayVisible: boolean; +} + +const WelcomeCard: React.FC = ({ onClose, handleStartAnmiation, delay, isOverlayVisible }) => { + const [isVisible, setIsVisible] = useState(false); + + const ref = useOutsideClick(() => { + if (isVisible) { + onClose(); + setIsVisible(false); + } + + }); + + const handleHideWelcomeCard = () => { + setIsVisible(false); + handleStartAnmiation(); + }; + + useEffect(() => { + if (! hasCookie('green_ecolution_initial_load') && isOverlayVisible) { + const timer = setTimeout(() => { setIsVisible(true) }, delay + 200); + return () => clearTimeout(timer); + } + }, [isOverlayVisible, delay]); + + return ( +
+
+
+ +
+

+ Willkommen auf der Projektseite von + Green Ecolution +

+

+ Bei diesem Projekt handelt es sich um ein Forschungsprojekt der Hochschule Flensburg. Master-Studierende untersuchen dabei, wie + smartes Grünflächenmanagement in der Stadt Flensburg aussehen kann. Start Sie die Animation, um mehr über das Vorgehen herauszufinden. +

+ +
+
+ ); +}; + +export default WelcomeCard; diff --git a/src/tsx/components/homepage/HomepageHero.tsx b/src/tsx/components/homepage/HomepageHero.tsx index 14cd281..cab37dd 100644 --- a/src/tsx/components/homepage/HomepageHero.tsx +++ b/src/tsx/components/homepage/HomepageHero.tsx @@ -1,51 +1,97 @@ -import Lottie from "lottie-react"; -import treeLightGreenAnimation from "../../../json/treeLightGreenAnimation.json"; -import treeMiddleGreenAnimation from "../../../json/treeMiddleGreenAnimation.json" -import treeDarkGreenAnimation from "../../../json/treeDarkGreenAnimation.json" +import { useState, useEffect } from "react"; +import Arrow from "../../icons/Arrow"; +import HomepageOverlay from "./HomepageOverlay"; +import HomepageHeroTrees from "./HomepageHeroTrees"; +import { setCookie, hasCookie } from "../../helper/cookies"; + +function HomepageHero() { + const [isOverlayVisible, setIsOverlayVisible] = useState(false); + const [isInitialLoad, setIsInitialLoad] = useState(false); + const handleOpenOverlay = () => { setIsOverlayVisible(true) }; + + const handleCloseOverlay = () => { + setIsOverlayVisible(false); + + if (isInitialLoad) { + setIsInitialLoad(false); + setCookie('green_ecolution_initial_load'); + } + }; + + useEffect(() => { + const handleResize = () => { + if (window.matchMedia('(max-width: 1279px)').matches) { + setIsOverlayVisible(false); + } + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + useEffect(() => { + isOverlayVisible + ? bodyLock() + : document.body.classList.remove('overflow-hidden'); + + return () => { document.body.classList.remove('overflow-hidden') }; + }, [isOverlayVisible]); + + useEffect(() => { + if (! hasCookie('green_ecolution_initial_load') + && ! isOverlayVisible + && window.matchMedia('(min-width: 1280px)').matches + ) { + setIsInitialLoad(true); + bodyLock(); + + const timer = setTimeout(() => { setIsOverlayVisible(true) }, 2000); + return () => clearTimeout(timer); + } + }, []); + + function bodyLock() { + window.scrollTo({ top: 0, behavior: 'smooth' }); + document.body.classList.add('overflow-hidden'); + } -function HompageHero() { return ( -
-
-
-

- Wir machen smarte Bewässerung von Bäumen und Beeten möglich! -

-

- Ut cillum minim eu duis cupidatat culpa proident voluptate sint aute mollit nulla velit voluptate. - Consequat occaecat adipisicing culpa. -

-
-
- - - - - - - - +
+
+
+
+

+ Wir machen smarte Bewässerung von Bäumen und Beeten möglich! +

+

+ Ut cillum minim eu duis cupidatat culpa proident voluptate sint aute mollit nulla velit voluptate. + Consequat occaecat adipisicing culpa. +

+ +
+
+ + +
+ +
); } -export default HompageHero; +export default HomepageHero; diff --git a/src/tsx/components/homepage/HomepageHeroTrees.tsx b/src/tsx/components/homepage/HomepageHeroTrees.tsx new file mode 100644 index 0000000..8b47b28 --- /dev/null +++ b/src/tsx/components/homepage/HomepageHeroTrees.tsx @@ -0,0 +1,39 @@ +import Lottie from "lottie-react"; +import treeLightGreenAnimation from "../../../json/treeLightGreenAnimation.json"; +import treeMiddleGreenAnimation from "../../../json/treeMiddleGreenAnimation.json"; +import treeDarkGreenAnimation from "../../../json/treeDarkGreenAnimation.json"; + +function HomepageHeroTrees() { + return ( +
+ + + + + + + +
+ ); +} + +export default HomepageHeroTrees; diff --git a/src/tsx/components/homepage/HomepageOverlay.tsx b/src/tsx/components/homepage/HomepageOverlay.tsx new file mode 100644 index 0000000..f4ea075 --- /dev/null +++ b/src/tsx/components/homepage/HomepageOverlay.tsx @@ -0,0 +1,141 @@ +import { useState, useEffect, useCallback } from "react"; +import Arrow from "../../icons/Arrow"; +import HomepageOverlayIcons from "./HomepageOverlayIcons"; +import WelcomeCard from "../cards/WelcomeCard"; + +interface Popup { + label: string; + shortName: string; + description: JSX.Element; +} + +interface HomepageOverlayProps { + initialLoad: boolean; + isOverlayVisible: boolean; + onClose: () => void; +} + +const popups: Popup[] = [ + { + label: "Messung des Bewässerungsstand", + shortName: "Bewässerungsstand", + description: ( + <> + Die Bodenfeuchte um den Wurzelballen herum wird in drei (30cm, 60cm und 90cm) unterschiedlichen Bodentiefen gemessen. Daraus lässt sich erschließen, wie feucht der Boden auch in tieferen Bodenschichten ist. + + ), + }, + { + label: "Übertragung der Daten", + shortName: "Datenübertragung", + description: ( + <> + Die Daten werden mithilfe von öffentlichen LoRaWAN (Long Range Wide Area Network) Zugängen übermittelt. + + ), + }, + { + label: "Handlungsempfehlungen", + shortName: "Handlungsempfehlungen", + description: ( + <> + Die gemessenen Sensordaten werden mittels wissenschaftlichen, mathematischen Daten interpretiert und in Empfehlungen umgewandelt. Dies wird alles auf ein Dashboard dargestellt. + Zu den Vorteilen + + ), + }, +]; + +const HomepageOverlay: React.FC = ({ initialLoad, isOverlayVisible, onClose }) => { + const [currentPopupIndex, setCurrentPopupIndex] = useState(0); + const [isPopupVisible, setIsPopupVisible] = useState(false); + const currentPopup = popups[currentPopupIndex]; + const delay = 1500; + + const startTimer = useCallback((callback: () => void, delay: number) => { + const timer = setTimeout(callback, delay); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + if (initialLoad) return; + setCurrentPopupIndex(0); + return startTimer(() => setIsPopupVisible(true), delay); + }, [isOverlayVisible, initialLoad, delay, startTimer]); + + useEffect(() => { + if (!isOverlayVisible) setIsPopupVisible(false); + }, [isOverlayVisible]); + + const handleNextClick = () => { + currentPopupIndex < popups.length - 1 + ? setCurrentPopupIndex(currentPopupIndex + 1) + : onClose(); + }; + + const handleStartAnimation = () => { + if (isOverlayVisible) { + return startTimer(() => setIsPopupVisible(true), delay); + } + }; + + return ( +
+
+ + + +
+
+ + Info {currentPopupIndex + 1} von {popups.length}: {currentPopup.shortName} + +

{currentPopup.label}

+

{currentPopup.description}

+ +
+ + +
+ + +
+
+ ); +} + +export default HomepageOverlay; diff --git a/src/tsx/components/homepage/HomepageOverlayIcons.tsx b/src/tsx/components/homepage/HomepageOverlayIcons.tsx new file mode 100644 index 0000000..84b5a92 --- /dev/null +++ b/src/tsx/components/homepage/HomepageOverlayIcons.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState } from "react"; + +interface HomepageOverlayIconsProps { + index: number; + delay: number; + areIconsVisible: boolean; +} + +const HomepageOverlayIcons: React.FC = ({ index, delay, areIconsVisible }) => { + const [initialDelayOver, setInitialDelayOver] = useState(false); + + useEffect(() => { + if (! areIconsVisible) { + setInitialDelayOver(false); + } else { + const timer = setTimeout(() => { setInitialDelayOver(true) }, delay); + return () => clearTimeout(timer); + } + }, [areIconsVisible, delay]); + + const icons = [ + { + figureClasses: "top-[20%] left-[10%] w-28 h-28 before:w-36 before:h-36 after:absolute after:top-[130%] after:w-1 after:h-[calc(80vh-28rem)]", + imageClasses: "w-14", + activeOnIndex: [1], + icon: "/assets/svg/general/wlan.svg", + }, + { + figureClasses: "top-[calc(20%+16rem)] left-[calc(10%+20rem)] w-28 h-28 before:w-36 before:h-36 after:absolute after:w-1 after:h-56 after:-rotate-[50deg] after:-top-[170%] after:-left-[90%]", + imageClasses: "h-12 w-12", + activeOnIndex: [1, 2], + icon: "/assets/svg/general/mobile.svg", + }, + { + figureClasses: "bottom-[10rem] left-[10%] w-28 h-28 ro before:w-36 before:h-36", + imageClasses: "w-14", + activeOnIndex: [1], + icon: "/assets/svg/general/gateway.svg", + }, + { + figureClasses: "bottom-[5rem] right-[45%] w-28 h-28 before:w-36 before:h-36 after:h-2 after:max-w-[35rem] after:w-[calc(41vw-14rem)] after:right-[130%] after:origin-bottom-right after:rotate-[8deg]", + imageClasses: "w-14 h-14", + activeOnIndex: [0], + icon: "/assets/svg/general/tensiometer.svg", + }, + { + figureClasses: "bottom-[6rem] right-[22%] w-20 h-20 before:w-28 before:h-28 2xl:right-[32%] after:h-2 after:w-[13.5vw] after:right-[130%] 2xl:after:w-[5vw] 3xl:after:w-28", + imageClasses: "w-12 h-12", + activeOnIndex: [0], + icon: "/assets/svg/general/tensiometer.svg", + }, + { + figureClasses: "bottom-[6rem] right-[10%] w-16 h-16 before:w-[5.5rem] before:h-[5.5rem] 2xl:right-[20%] 3xl:right-[24%] after:h-2 after:w-[4vw] after:rotate-[3deg] after:right-[130%] 2xl:after:w-[5.5vw] 3xl:after:w-12", + imageClasses: "w-10 h-10", + activeOnIndex: [0], + icon: "/assets/svg/general/tensiometer.svg", + }, + ]; + + return ( +
+ {icons.map((icon, key) => ( + + ))} +
+ ); +} + +export default HomepageOverlayIcons; diff --git a/src/tsx/components/sections/Advantages.tsx b/src/tsx/components/sections/Advantages.tsx index 11b9e65..e8dec16 100644 --- a/src/tsx/components/sections/Advantages.tsx +++ b/src/tsx/components/sections/Advantages.tsx @@ -20,7 +20,7 @@ function Advantages() { ]; return ( -
+

Alle weiteren Funktionen und Vorteile im Überblick. diff --git a/src/tsx/helper/cookies.ts b/src/tsx/helper/cookies.ts new file mode 100644 index 0000000..38a3255 --- /dev/null +++ b/src/tsx/helper/cookies.ts @@ -0,0 +1,32 @@ +export function setCookie(cookieName: string) { + const date = new Date(); + const cookieLifetimeInDays = 182; + const cookieDomain = window.location.hostname; + + date.setTime(date.getTime() + (cookieLifetimeInDays * 24 * 60 * 60 * 1000)); + + const cookie = cookieName + '=' + true + + ';expires=' + date.toUTCString() + + ';domain=' + cookieDomain + + ';path=/' + + ';samesite=' + 'lax'; + + + document.cookie = cookie; +} + +export function hasCookie(cookieName: string): boolean { + const name = cookieName + "="; + const decodedCookie = decodeURIComponent(document.cookie); + const cookieItems = decodedCookie.split(';'); + + for (let i = 0; i < cookieItems.length; i++) { + const cookie = cookieItems[i].trim(); + + if (cookie.indexOf(name) === 0) { + return true; + } + } + + return false; +} diff --git a/src/tsx/pages/ProjectPage.tsx b/src/tsx/pages/ProjectPage.tsx index 0f721bd..171bccf 100644 --- a/src/tsx/pages/ProjectPage.tsx +++ b/src/tsx/pages/ProjectPage.tsx @@ -10,6 +10,16 @@ function ProjectPage() { document.title = "Projekt | Green Ecolution | Smartes Grünflächenmanagement"; }, []); + useEffect(() => { + const hash = window.location.hash; + if (hash) { + const element = document.getElementById(hash.substring(1)); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + } + }, []); + const heroHeadline = "Alles wissenswerte über das Projekt"; const heroDescription = "Eu elit quis eiusmod proident officia aute tempor tempor qui commodo aute qui. Excepteur id ea laboris fugiat dolor exercitation ut pariatur ut commodo non. Eu deserunt laboris dolore elit. Aliquip magna do nostrud velit esse anim do. Dolor culpa duis laboris nisi ea nulla nulla magna" diff --git a/tailwind.config.js b/tailwind.config.js index 9d63e31..4d86284 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -72,6 +72,12 @@ export default { animation: { 'move': 'move 35s linear infinite', }, + transitionDelay: { + '1500': '1500ms', + }, + transitionDuration: { + '1500': '1500ms', + } }, }, plugins: [],