diff --git a/public/assets/images/filiale1.jpg b/public/assets/images/filiale1.jpg new file mode 100644 index 0000000..be554e8 Binary files /dev/null and b/public/assets/images/filiale1.jpg differ diff --git a/public/assets/images/filiale2.jpg b/public/assets/images/filiale2.jpg new file mode 100644 index 0000000..72ce2b0 Binary files /dev/null and b/public/assets/images/filiale2.jpg differ diff --git a/public/assets/images/presse.jpg b/public/assets/images/presse.jpg new file mode 100644 index 0000000..1b3aac7 Binary files /dev/null and b/public/assets/images/presse.jpg differ diff --git a/public/assets/images/purpl.jpg b/public/assets/images/purpl.jpg new file mode 100644 index 0000000..2938a50 Binary files /dev/null and b/public/assets/images/purpl.jpg differ diff --git a/src/App.js b/src/App.js index ecac0a7..037307f 100644 --- a/src/App.js +++ b/src/App.js @@ -18,13 +18,14 @@ import BugReportIcon from "@mui/icons-material/BugReport"; import SocketProvider from "./providers/SocketProvider.js"; import SocketContext from "./contexts/SocketContext.js"; +import { CarouselProvider } from "./contexts/CarouselContext.js"; import config from "./config.js"; import ScrollToTop from "./components/ScrollToTop.js"; //import TelemetryService from './services/telemetryService.js'; import Header from "./components/Header.js"; import Footer from "./components/Footer.js"; -import Home from "./pages/Home.js"; +import MainPageLayout from "./components/MainPageLayout.js"; // Lazy load all route components to reduce initial bundle size const Content = lazy(() => import(/* webpackChunkName: "content" */ "./components/Content.js")); @@ -50,6 +51,10 @@ const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./page const GrowTentKonfigurator = lazy(() => import(/* webpackChunkName: "konfigurator" */ "./pages/GrowTentKonfigurator.js")); const ChatAssistant = lazy(() => import(/* webpackChunkName: "chat" */ "./components/ChatAssistant.js")); +// Lazy load separate pages that are truly different +const PresseverleihPage = lazy(() => import(/* webpackChunkName: "presseverleih" */ "./pages/PresseverleihPage.js")); +const ThcTestPage = lazy(() => import(/* webpackChunkName: "thc-test" */ "./pages/ThcTestPage.js")); + // Import theme from separate file to reduce main bundle size import defaultTheme from "./theme.js"; // Lazy load theme customizer for development only @@ -195,60 +200,68 @@ const AppContent = ({ currentTheme, onThemeChange }) => { }> - - {/* Home page with text only */} - } /> + + + {/* Main pages using unified component */} + } /> + } /> + } /> - {/* Category page - Render Content in parallel */} - } - /> - {/* Single product page */} - } - /> + {/* Category page - Render Content in parallel */} + } + /> + {/* Single product page */} + } + /> - {/* Search page - Render Content in parallel */} - } /> + {/* Search page - Render Content in parallel */} + } /> - {/* Profile page */} - } /> + {/* Profile page */} + } /> - {/* Reset password page */} - } - /> + {/* Reset password page */} + } + /> - {/* Admin page */} - } /> - - {/* Admin Users page */} - } /> - - {/* Admin Server Logs page */} - } /> + {/* Admin page */} + } /> + + {/* Admin Users page */} + } /> + + {/* Admin Server Logs page */} + } /> - {/* Legal pages */} - } /> - } /> - } /> - } /> - } /> - } - /> - } /> + {/* Legal pages */} + } /> + } /> + } /> + } /> + } /> + } + /> + } /> - {/* Grow Tent Configurator */} - } /> + {/* Grow Tent Configurator */} + } /> - {/* Fallback for undefined routes */} - } /> - + {/* Separate pages that are truly different */} + } /> + } /> + + {/* Fallback for undefined routes */} + } /> + + {/* Conditionally render the Chat Assistant */} diff --git a/src/components/Header.js b/src/components/Header.js index 5ad6fa0..9941688 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -38,7 +38,7 @@ class Header extends Component { render() { // Get socket directly from context in render method const {socket,socketB} = this.context; - const { isHomePage, isProfilePage } = this.props; + const { isHomePage, isProfilePage, isAktionenPage, isFilialePage } = this.props; return ( @@ -94,7 +94,7 @@ class Header extends Component { - {(isHomePage || this.props.categoryId || isProfilePage) && } + {(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage) && } ); } @@ -105,10 +105,12 @@ const HeaderWithContext = (props) => { const location = useLocation(); const isHomePage = location.pathname === '/'; const isProfilePage = location.pathname === '/profile'; + const isAktionenPage = location.pathname === '/aktionen'; + const isFilialePage = location.pathname === '/filiale'; return ( - {({socket,socketB}) => } + {({socket,socketB}) => } ); }; diff --git a/src/components/MainPageLayout.js b/src/components/MainPageLayout.js new file mode 100644 index 0000000..ce9f41b --- /dev/null +++ b/src/components/MainPageLayout.js @@ -0,0 +1,349 @@ +import React from "react"; +import { useLocation } from "react-router-dom"; +import Container from "@mui/material/Container"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Paper from "@mui/material/Paper"; +import Grid from "@mui/material/Grid"; +import ChevronLeft from "@mui/icons-material/ChevronLeft"; +import ChevronRight from "@mui/icons-material/ChevronRight"; +import { Link } from "react-router-dom"; +import SharedCarousel from "./SharedCarousel.js"; +import { getCombinedAnimatedBorderStyles } from "../utils/animatedBorderStyles.js"; + +const MainPageLayout = () => { + const location = useLocation(); + const currentPath = location.pathname; + + // Determine which page we're on + const isHome = currentPath === "/"; + const isAktionen = currentPath === "/aktionen"; + const isFiliale = currentPath === "/filiale"; + + // Get navigation config based on current page + const getNavigationConfig = () => { + if (isHome) { + return { + leftNav: { text: "Aktionen", link: "/aktionen" }, + rightNav: { text: "Filiale", link: "/filiale" } + }; + } else if (isAktionen) { + return { + leftNav: { text: "Filiale", link: "/filiale" }, + rightNav: { text: "Home", link: "/" } + }; + } else if (isFiliale) { + return { + leftNav: { text: "Home", link: "/" }, + rightNav: { text: "Aktionen", link: "/aktionen" } + }; + } + return { leftNav: null, rightNav: null }; + }; + + // Define all titles for layered rendering + const allTitles = { + home: "ine annabis eeds & uttings", + aktionen: "Aktionen", + filiale: "Filiale" + }; + + // Define all content boxes for layered rendering + const allContentBoxes = { + home: [ + { + title: "Samen", + image: "/assets/images/seeds.jpg", + bgcolor: "#e1f0d3", + link: "/Kategorie/Samen" + }, + { + title: "Stecklinge", + image: "/assets/images/cutlings.jpg", + bgcolor: "#e8f5d6", + link: "/Kategorie/Stecklinge" + } + ], + aktionen: [ + { + title: "Ölpresse ausleihen", + image: "/assets/images/presse.jpg", + bgcolor: "#e1f0d3", + link: "/presseverleih" + }, + { + title: "THC Test", + image: "/assets/images/purpl.jpg", + bgcolor: "#e8f5d6", + link: "/thc-test" + } + ], + filiale: [ + { + title: "Trachenberger Straße 14", + image: "/assets/images/filiale1.jpg", + bgcolor: "#e1f0d3", + link: "/filiale" + }, + { + title: "01129 Dresden", + image: "/assets/images/filiale2.jpg", + bgcolor: "#e8f5d6", + link: "/filiale" + } + ] + }; + + // Get opacity for each page layer + const getOpacity = (pageType) => { + if (pageType === "home" && isHome) return 1; + if (pageType === "aktionen" && isAktionen) return 1; + if (pageType === "filiale" && isFiliale) return 1; + return 0; + }; + + const navConfig = getNavigationConfig(); + + return ( + + + + {/* Main Navigation Header */} + + {/* Left Navigation - Layered rendering */} + + {["Aktionen", "Filiale", "Home"].map((text, index) => { + const isActive = navConfig.leftNav && navConfig.leftNav.text === text; + const link = text === "Aktionen" ? "/aktionen" : text === "Filiale" ? "/filiale" : "/"; + + return ( + + + + {text} + + + ); + })} + + + {/* Center Title - Layered rendering - This defines the height for centering */} + + {Object.entries(allTitles).map(([pageType, title]) => ( + + {title} + + ))} + + + {/* Right Navigation - Layered rendering */} + + {["Aktionen", "Filiale", "Home"].map((text, index) => { + const isActive = navConfig.rightNav && navConfig.rightNav.text === text; + const link = text === "Aktionen" ? "/aktionen" : text === "Filiale" ? "/filiale" : "/"; + + return ( + + + {text} + + + + ); + })} + + + + {/* Content Boxes - Layered rendering */} + + {Object.entries(allContentBoxes).map(([pageType, contentBoxes]) => ( + + {contentBoxes.map((box, index) => ( + + + + + + + {box.title} + + + + + + + ))} + + ))} + + + {/* Shared Carousel */} + + + ); +}; + +export default MainPageLayout; \ No newline at end of file diff --git a/src/components/SharedCarousel.js b/src/components/SharedCarousel.js new file mode 100644 index 0000000..d53f1c6 --- /dev/null +++ b/src/components/SharedCarousel.js @@ -0,0 +1,229 @@ +import React, { useContext, useEffect, useState } from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import ChevronLeft from "@mui/icons-material/ChevronLeft"; +import ChevronRight from "@mui/icons-material/ChevronRight"; +import CategoryBox from "./CategoryBox.js"; +import SocketContext from "../contexts/SocketContext.js"; +import { useCarousel } from "../contexts/CarouselContext.js"; + +// Helper to process and set categories +const processCategoryTree = (categoryTree) => { + if ( + categoryTree && + categoryTree.id === 209 && + Array.isArray(categoryTree.children) + ) { + return categoryTree.children; + } else { + return []; + } +}; + +// Check for cached data +const getProductCache = () => { + if (typeof window !== "undefined" && window.productCache) { + return window.productCache; + } + if ( + typeof global !== "undefined" && + global.window && + global.window.productCache + ) { + return global.window.productCache; + } + return null; +}; + +// Initialize categories +const initializeCategories = () => { + const productCache = getProductCache(); + + if (productCache && productCache["categoryTree_209"]) { + const cached = productCache["categoryTree_209"]; + if (cached.categoryTree) { + return processCategoryTree(cached.categoryTree); + } + } + return []; +}; + +const SharedCarousel = () => { + const { carouselRef, filteredCategories, setFilteredCategories, moveCarousel } = useCarousel(); + const context = useContext(SocketContext); + const [rootCategories, setRootCategories] = useState([]); + + useEffect(() => { + const initialCategories = initializeCategories(); + setRootCategories(initialCategories); + }, []); + + useEffect(() => { + // Only fetch from socket if we don't already have categories + if ( + rootCategories.length === 0 && + context && context.socket && context.socket.connected && + typeof window !== "undefined" + ) { + context.socket.emit("categoryList", { categoryId: 209 }, (response) => { + if (response && response.categoryTree) { + // Store in cache + try { + if (!window.productCache) window.productCache = {}; + window.productCache["categoryTree_209"] = { + categoryTree: response.categoryTree, + timestamp: Date.now(), + }; + } catch (err) { + console.error(err); + } + setRootCategories(response.categoryTree.children || []); + } + }); + } + }, [context, context?.socket?.connected, rootCategories.length]); + + useEffect(() => { + const filtered = rootCategories.filter( + (cat) => cat.id !== 689 && cat.id !== 706 + ); + setFilteredCategories(filtered); + }, [rootCategories, setFilteredCategories]); + + // Create duplicated array for seamless scrolling + const displayCategories = [...filteredCategories, ...filteredCategories]; + + if (filteredCategories.length === 0) { + return null; + } + + return ( + + + Kategorien + + + + {/* Left Arrow */} + moveCarousel("left")} + aria-label="Vorherige Kategorien anzeigen" + style={{ + position: 'absolute', + top: '50%', + left: '8px', + transform: 'translateY(-50%)', + zIndex: 1200, + backgroundColor: 'rgba(255, 255, 255, 0.9)', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', + width: '48px', + height: '48px', + borderRadius: '50%' + }} + > + + + + {/* Right Arrow */} + moveCarousel("right")} + aria-label="Nächste Kategorien anzeigen" + style={{ + position: 'absolute', + top: '50%', + right: '8px', + transform: 'translateY(-50%)', + zIndex: 1200, + backgroundColor: 'rgba(255, 255, 255, 0.9)', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', + width: '48px', + height: '48px', + borderRadius: '50%' + }} + > + + + + + + {displayCategories.map((category, index) => ( + + + + ))} + + + + + ); +}; + +export default SharedCarousel; \ No newline at end of file diff --git a/src/contexts/CarouselContext.js b/src/contexts/CarouselContext.js new file mode 100644 index 0000000..b5fb347 --- /dev/null +++ b/src/contexts/CarouselContext.js @@ -0,0 +1,225 @@ +import React, { createContext, useContext, useRef, useEffect, useState } from 'react'; + +const CarouselContext = createContext(); + +export const useCarousel = () => { + const context = useContext(CarouselContext); + if (!context) { + throw new Error('useCarousel must be used within a CarouselProvider'); + } + return context; +}; + +export const CarouselProvider = ({ children }) => { + const carouselRef = useRef(null); + const scrollPositionRef = useRef(0); + const animationIdRef = useRef(null); + const isPausedRef = useRef(false); + const resumeTimeoutRef = useRef(null); + + const [filteredCategories, setFilteredCategories] = useState([]); + + // Initialize refs properly + useEffect(() => { + isPausedRef.current = false; + scrollPositionRef.current = 0; + }, []); + + // Auto-scroll effect + useEffect(() => { + if (filteredCategories.length === 0) return; + + const startAnimation = () => { + if (!carouselRef.current) { + return false; + } + + isPausedRef.current = false; + + const itemWidth = 146; // 130px + 16px gap + const totalWidth = filteredCategories.length * itemWidth; + + const animate = () => { + if (!animationIdRef.current || isPausedRef.current) { + return; + } + + scrollPositionRef.current += 0.5; + + if (scrollPositionRef.current >= totalWidth) { + scrollPositionRef.current = 0; + } + + if (carouselRef.current) { + const transform = `translateX(-${scrollPositionRef.current}px)`; + carouselRef.current.style.transform = transform; + } + + animationIdRef.current = requestAnimationFrame(animate); + }; + + if (!isPausedRef.current) { + animationIdRef.current = requestAnimationFrame(animate); + return true; + } + return false; + }; + + // Try immediately, then with increasing delays + if (!startAnimation()) { + const timeout1 = setTimeout(() => { + if (!startAnimation()) { + const timeout2 = setTimeout(() => { + if (!startAnimation()) { + const timeout3 = setTimeout(startAnimation, 2000); + return () => clearTimeout(timeout3); + } + }, 1000); + return () => clearTimeout(timeout2); + } + }, 100); + + return () => { + isPausedRef.current = true; + clearTimeout(timeout1); + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + } + if (resumeTimeoutRef.current) { + clearTimeout(resumeTimeoutRef.current); + } + }; + } + + return () => { + isPausedRef.current = true; + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + } + if (resumeTimeoutRef.current) { + clearTimeout(resumeTimeoutRef.current); + } + }; + }, [filteredCategories]); + + // Additional effect for when ref becomes available + useEffect(() => { + if (filteredCategories.length > 0 && carouselRef.current && !animationIdRef.current) { + isPausedRef.current = false; + + const itemWidth = 146; + const totalWidth = filteredCategories.length * itemWidth; + + const animate = () => { + if (!animationIdRef.current || isPausedRef.current) { + return; + } + + scrollPositionRef.current += 0.5; + + if (scrollPositionRef.current >= totalWidth) { + scrollPositionRef.current = 0; + } + + if (carouselRef.current) { + const transform = `translateX(-${scrollPositionRef.current}px)`; + carouselRef.current.style.transform = transform; + } + + animationIdRef.current = requestAnimationFrame(animate); + }; + + if (!isPausedRef.current) { + animationIdRef.current = requestAnimationFrame(animate); + } + } + }); + + // Manual navigation + const moveCarousel = (direction) => { + if (!carouselRef.current) return; + + // Pause auto-scroll + isPausedRef.current = true; + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + animationIdRef.current = null; + } + + const itemWidth = 146; + const moveAmount = itemWidth * 3; + const totalWidth = filteredCategories.length * itemWidth; + + if (direction === "left") { + scrollPositionRef.current -= moveAmount; + if (scrollPositionRef.current < 0) { + scrollPositionRef.current = totalWidth + scrollPositionRef.current; + } + } else { + scrollPositionRef.current += moveAmount; + if (scrollPositionRef.current >= totalWidth) { + scrollPositionRef.current = scrollPositionRef.current % totalWidth; + } + } + + // Apply smooth transition + carouselRef.current.style.transition = "transform 0.5s ease-in-out"; + carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`; + + // Remove transition after animation + setTimeout(() => { + if (carouselRef.current) { + carouselRef.current.style.transition = "none"; + } + }, 500); + + // Clear existing timeout + if (resumeTimeoutRef.current) { + clearTimeout(resumeTimeoutRef.current); + } + + // Resume auto-scroll after 3 seconds + resumeTimeoutRef.current = setTimeout(() => { + isPausedRef.current = false; + + const animate = () => { + if (!animationIdRef.current || isPausedRef.current) { + return; + } + + scrollPositionRef.current += 0.5; + + if (scrollPositionRef.current >= totalWidth) { + scrollPositionRef.current = 0; + } + + if (carouselRef.current) { + carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`; + } + + animationIdRef.current = requestAnimationFrame(animate); + }; + + animationIdRef.current = requestAnimationFrame(animate); + }, 3000); + }; + + const value = { + carouselRef, + scrollPositionRef, + animationIdRef, + isPausedRef, + resumeTimeoutRef, + filteredCategories, + setFilteredCategories, + moveCarousel + }; + + return ( + + {children} + + ); +}; + +export default CarouselContext; \ No newline at end of file diff --git a/src/pages/Home.js b/src/pages/Home.js deleted file mode 100644 index 8d95851..0000000 --- a/src/pages/Home.js +++ /dev/null @@ -1,652 +0,0 @@ -import React, { useState, useEffect, useContext, useRef } from "react"; -import Container from "@mui/material/Container"; -import Box from "@mui/material/Box"; -import Typography from "@mui/material/Typography"; -import Paper from "@mui/material/Paper"; -import Grid from "@mui/material/Grid"; -import IconButton from "@mui/material/IconButton"; -import ChevronLeft from "@mui/icons-material/ChevronLeft"; -import ChevronRight from "@mui/icons-material/ChevronRight"; -import { Link } from "react-router-dom"; -import CategoryBox from "../components/CategoryBox.js"; -import SocketContext from "../contexts/SocketContext.js"; -import { getCombinedAnimatedBorderStyles } from "../utils/animatedBorderStyles.js"; - -// @note SwashingtonCP font is now loaded globally via index.css - -// Carousel styles - Simple styles for JavaScript-based animation -const carouselStyles = ` - .carousel-wrapper { - position: relative; - overflow: hidden; - width: 100%; - max-width: 1200px; - margin: 0 auto; - padding: 0 20px; - z-index: 1; - } - - - .carousel-wrapper .carousel-container { - position: relative; - overflow: hidden; - padding: 20px 0; - width: 100%; - max-width: 1080px; - margin: 0 auto; - z-index: 1; - } - - .carousel-wrapper .home-carousel-track { - display: flex; - gap: 16px; - transition: none; - align-items: flex-start; - width: 1200px; - max-width: 100%; - overflow: visible; - position: relative; - z-index: 1; - } - - .carousel-wrapper .carousel-item { - flex: 0 0 130px; - width: 130px !important; - max-width: 130px; - min-width: 130px; - height: 130px !important; - max-height: 130px; - min-height: 130px; - box-sizing: border-box; - position: relative; - z-index: 2; - } - - .carousel-nav-button { - position: absolute; - top: 50%; - transform: translateY(-50%); - z-index: 20; - background-color: rgba(255, 255, 255, 0.9); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - width: 48px; - height: 48px; - } - - .carousel-nav-button:hover { - background-color: rgba(255, 255, 255, 1); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - } - - .carousel-nav-left { - left: 8px; - } - - .carousel-nav-right { - right: 8px; - } -`; - -// Generate combined styles for both seeds and cutlings cards -const animatedBorderStyle = getCombinedAnimatedBorderStyles([ - "seeds", - "cutlings", -]); - -const Home = () => { - const carouselRef = useRef(null); - const scrollPositionRef = useRef(0); - const animationIdRef = useRef(null); - const isPausedRef = useRef(false); - const resumeTimeoutRef = useRef(null); - - // @note Initialize refs properly - useEffect(() => { - isPausedRef.current = false; - scrollPositionRef.current = 0; - }, []); - // Helper to process and set categories - const processCategoryTree = (categoryTree) => { - if ( - categoryTree && - categoryTree.id === 209 && - Array.isArray(categoryTree.children) - ) { - return categoryTree.children; - } else { - return []; - } - }; - - // Check for cached data - handle both browser and prerender environments - const getProductCache = () => { - if (typeof window !== "undefined" && window.productCache) { - return window.productCache; - } - if ( - typeof global !== "undefined" && - global.window && - global.window.productCache - ) { - return global.window.productCache; - } - return null; - }; - - // Initialize rootCategories from cache if available (for prerendering) - const initializeCategories = () => { - const productCache = getProductCache(); - - if (productCache && productCache["categoryTree_209"]) { - const cached = productCache["categoryTree_209"]; - //const cacheAge = Date.now() - cached.timestamp; - //const tenMinutes = 10 * 60 * 1000; - if (/*cacheAge < tenMinutes &&*/ cached.categoryTree) { - return processCategoryTree(cached.categoryTree); - } - } - return []; - }; - - const [rootCategories, setRootCategories] = useState(() => - initializeCategories() - ); - const context = useContext(SocketContext); - - useEffect(() => { - // Only fetch from socket if we don't already have categories and we're in browser - if ( - rootCategories.length === 0 && - context && context.socket && context.socket.connected && - typeof window !== "undefined" - ) { - context.socket.emit("categoryList", { categoryId: 209 }, (response) => { - if (response && response.categoryTree) { - // Store in cache - try { - if (!window.productCache) window.productCache = {}; - window.productCache["categoryTree_209"] = { - categoryTree: response.categoryTree, - timestamp: Date.now(), - }; - } catch (err) { - console.error(err); - } - setRootCategories(response.categoryTree.children || []); - } - }); - } - }, [context, context?.socket?.connected, rootCategories.length]); - - // Filter categories (excluding specific IDs) - const filteredCategories = rootCategories.filter( - (cat) => cat.id !== 689 && cat.id !== 706 - ); - - // Create duplicated array for seamless scrolling - const displayCategories = [...filteredCategories, ...filteredCategories]; - - // Auto-scroll effect - useEffect(() => { - if (filteredCategories.length === 0) return; - - // @note Add a small delay to ensure DOM is ready after prerender - const startAnimation = () => { - if (!carouselRef.current) { - return false; - } - - // @note Reset paused state when starting animation - isPausedRef.current = false; - - const itemWidth = 146; // 130px + 16px gap - const totalWidth = filteredCategories.length * itemWidth; - - const animate = () => { - // Check if we should be animating - if (!animationIdRef.current || isPausedRef.current) { - return; - } - - scrollPositionRef.current += 0.5; // Speed of scrolling - - // Reset position for seamless loop - if (scrollPositionRef.current >= totalWidth) { - scrollPositionRef.current = 0; - } - - if (carouselRef.current) { - const transform = `translateX(-${scrollPositionRef.current}px)`; - carouselRef.current.style.transform = transform; - } - - animationIdRef.current = requestAnimationFrame(animate); - }; - - // Only start animation if not paused - if (!isPausedRef.current) { - animationIdRef.current = requestAnimationFrame(animate); - return true; - } - return false; - }; - - // Try immediately, then with increasing delays to handle prerender scenarios - if (!startAnimation()) { - const timeout1 = setTimeout(() => { - if (!startAnimation()) { - const timeout2 = setTimeout(() => { - if (!startAnimation()) { - const timeout3 = setTimeout(startAnimation, 2000); - return () => clearTimeout(timeout3); - } - }, 1000); - return () => clearTimeout(timeout2); - } - }, 100); - - return () => { - isPausedRef.current = true; - clearTimeout(timeout1); - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - } - if (resumeTimeoutRef.current) { - clearTimeout(resumeTimeoutRef.current); - } - }; - } - - return () => { - isPausedRef.current = true; - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - } - if (resumeTimeoutRef.current) { - clearTimeout(resumeTimeoutRef.current); - } - }; - }, [filteredCategories]); - - // Additional effect to handle cases where categories are available but ref wasn't ready - useEffect(() => { - if (filteredCategories.length > 0 && carouselRef.current && !animationIdRef.current) { - // @note Reset paused state when starting animation - isPausedRef.current = false; - - const itemWidth = 146; - const totalWidth = filteredCategories.length * itemWidth; - - const animate = () => { - if (!animationIdRef.current || isPausedRef.current) { - return; - } - - scrollPositionRef.current += 0.5; - - if (scrollPositionRef.current >= totalWidth) { - scrollPositionRef.current = 0; - } - - if (carouselRef.current) { - const transform = `translateX(-${scrollPositionRef.current}px)`; - carouselRef.current.style.transform = transform; - } - - animationIdRef.current = requestAnimationFrame(animate); - }; - - if (!isPausedRef.current) { - animationIdRef.current = requestAnimationFrame(animate); - } - } - }); - - // Manual navigation - const moveCarousel = (direction) => { - if (!carouselRef.current) return; - - // Pause auto-scroll - isPausedRef.current = true; - if (animationIdRef.current) { - cancelAnimationFrame(animationIdRef.current); - animationIdRef.current = null; - } - - const itemWidth = 146; - const moveAmount = itemWidth * 3; // Move 3 items at a time - const totalWidth = filteredCategories.length * itemWidth; - - if (direction === "left") { - scrollPositionRef.current -= moveAmount; - // Handle wrapping for infinite scroll - if (scrollPositionRef.current < 0) { - scrollPositionRef.current = totalWidth + scrollPositionRef.current; - } - } else { - scrollPositionRef.current += moveAmount; - // Handle wrapping for infinite scroll - if (scrollPositionRef.current >= totalWidth) { - scrollPositionRef.current = scrollPositionRef.current % totalWidth; - } - } - - // Apply smooth transition for manual navigation - carouselRef.current.style.transition = "transform 0.5s ease-in-out"; - carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`; - - // Remove transition after animation completes - setTimeout(() => { - if (carouselRef.current) { - carouselRef.current.style.transition = "none"; - } - }, 500); - - // Clear any existing resume timeout - if (resumeTimeoutRef.current) { - clearTimeout(resumeTimeoutRef.current); - } - - // Resume auto-scroll after 3 seconds - resumeTimeoutRef.current = setTimeout(() => { - isPausedRef.current = false; - - const animate = () => { - if (!animationIdRef.current || isPausedRef.current) { - return; - } - - scrollPositionRef.current += 0.5; - - if (scrollPositionRef.current >= totalWidth) { - scrollPositionRef.current = 0; - } - - if (carouselRef.current) { - carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`; - } - - animationIdRef.current = requestAnimationFrame(animate); - }; - - animationIdRef.current = requestAnimationFrame(animate); - }, 3000); - }; - - - return ( - - {/* Inject the animated border and carousel styles */} - - - - - ine annabis eeds & uttings - - - - {/* Seeds Category Box */} - - - - {/* Image Container - Place your seeds image here */} - - {/* Overlay text - optional */} - - - Seeds - - - - - - - - {/* Cutlings Category Box */} - - - - {/* Image Container - Place your cutlings image here */} - - {/* Overlay text - optional */} - - - Stecklinge - - - - - - - - - {/* Continuous Rotating Carousel for Categories */} - - - Kategorien - - - {filteredCategories.length > 0 && ( - - {/* Left Arrow */} - moveCarousel("left")} - aria-label="Vorherige Kategorien anzeigen" - style={{ - position: 'absolute', - top: '50%', - left: '8px', - transform: 'translateY(-50%)', - zIndex: 1200, - backgroundColor: 'rgba(255, 255, 255, 0.9)', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', - width: '48px', - height: '48px', - borderRadius: '50%' - }} - > - - - - {/* Right Arrow */} - moveCarousel("right")} - aria-label="Nächste Kategorien anzeigen" - style={{ - position: 'absolute', - top: '50%', - right: '8px', - transform: 'translateY(-50%)', - zIndex: 1200, - backgroundColor: 'rgba(255, 255, 255, 0.9)', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', - width: '48px', - height: '48px', - borderRadius: '50%' - }} - > - - - - - - {displayCategories.map((category, index) => ( - - - - ))} - - - - )} - - - ); -}; - -export default Home; diff --git a/src/pages/PresseverleihPage.js b/src/pages/PresseverleihPage.js new file mode 100644 index 0000000..5a09e2a --- /dev/null +++ b/src/pages/PresseverleihPage.js @@ -0,0 +1,46 @@ +import React from "react"; +import Container from "@mui/material/Container"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +const PresseverleihPage = () => { + return ( + + + Ölpresse ausleihen + + + + + Inhalt kommt bald... + + + + ); +}; + +export default PresseverleihPage; \ No newline at end of file diff --git a/src/pages/ThcTestPage.js b/src/pages/ThcTestPage.js new file mode 100644 index 0000000..7292f14 --- /dev/null +++ b/src/pages/ThcTestPage.js @@ -0,0 +1,46 @@ +import React from "react"; +import Container from "@mui/material/Container"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +const ThcTestPage = () => { + return ( + + + THC Test + + + + + Inhalt kommt bald... + + + + ); +}; + +export default ThcTestPage; \ No newline at end of file