diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx
new file mode 100644
index 000000000..14d694c6e
--- /dev/null
+++ b/src/components/ThemeToggle.tsx
@@ -0,0 +1,22 @@
+import { useTheme } from "@/context/ThemeContext";
+import { Sun, Moon } from "lucide-react";
+
+const ThemeToggle = () => {
+ const { theme, toggleTheme } = useTheme();
+
+ return (
+
+ );
+};
+
+export default ThemeToggle;
diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx
new file mode 100644
index 000000000..680e837ce
--- /dev/null
+++ b/src/context/ThemeContext.tsx
@@ -0,0 +1,40 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "light" | "dark";
+
+interface ThemeContextType {
+ theme: Theme;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext
({
+ theme: "light",
+ toggleTheme: () => {},
+});
+
+export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
+ const [theme, setTheme] = useState("light");
+
+ useEffect(() => {
+ const stored = localStorage.getItem("theme") as Theme | null;
+ const system = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+ const initial = stored || system;
+ setTheme(initial);
+ document.documentElement.classList.toggle("dark", initial === "dark");
+ }, []);
+
+ const toggleTheme = () => {
+ const newTheme = theme === "dark" ? "light" : "dark";
+ setTheme(newTheme);
+ localStorage.setItem("theme", newTheme);
+ document.documentElement.classList.toggle("dark", newTheme === "dark");
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTheme = () => useContext(ThemeContext);
diff --git a/src/layout/Layout.tsx b/src/layout/Layout.tsx
index 6b008c4ce..31e2a74de 100644
--- a/src/layout/Layout.tsx
+++ b/src/layout/Layout.tsx
@@ -1,19 +1,30 @@
+import { ReactNode, useEffect } from "react";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
-import { ReactNode } from "react";
+import AOS from "aos";
+import "aos/dist/aos.css";
type LayoutProps = {
- children: ReactNode;
- };
-
- const Layout = ({ children }: LayoutProps) => {
- return (
-
-
- {children}
-
-
- );
- };
-
- export default Layout;
\ No newline at end of file
+ children: ReactNode;
+};
+
+const Layout = ({ children }: LayoutProps) => {
+ useEffect(() => {
+ AOS.init({
+ duration: 800, // Dauer der Animationen
+ once: true, // nur einmal pro Element
+ offset: 100, // wie weit gescrollt werden muss
+ easing: "ease-out-cubic",
+ });
+ }, []);
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default Layout;
diff --git a/src/lib/axios.ts b/src/lib/axios.ts
new file mode 100644
index 000000000..ea02df908
--- /dev/null
+++ b/src/lib/axios.ts
@@ -0,0 +1,7 @@
+import axios from "axios";
+
+const api = axios.create({
+ baseURL: import.meta.env.VITE_API_URL,
+});
+
+export default api;
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index c7fe1b841..2edf3db93 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -33,10 +33,14 @@ import Impressum from "./pages/Impressum";
import Satzung from "./pages/Satzung";
import Beitraege from "./pages/Beitraege";
+import { ThemeProvider } from "@/context/ThemeContext";
+import VideosPage from "./pages/VideosPage";
+
ReactDOM.createRoot(document.getElementById("root")!).render(
+
@@ -72,6 +76,9 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
} />
} />
} />
+ } />
+
+
} />
} />
} />
@@ -86,8 +93,10 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
} />
} />
+
+
);
\ No newline at end of file
diff --git a/src/pages/AlleNeuigkeiten.tsx b/src/pages/AlleNeuigkeiten.tsx
index a64ee3ace..f8f01e0a1 100644
--- a/src/pages/AlleNeuigkeiten.tsx
+++ b/src/pages/AlleNeuigkeiten.tsx
@@ -17,6 +17,25 @@ const AlleNeuigkeitenPage = () => {
const [news, setNews] = useState([]);
const [expandedIds, setExpandedIds] = useState([]);
const [activeCardId, setActiveCardId] = useState(null);
+ const [selectedTeam, setSelectedTeam] = useState("Alle Teams");
+
+ const defaultImage = "/images/tgl-ball.png";
+
+ //Filtern nach Teams
+ const teams = Array.from(new Set(news.map((n) => n.team).filter(Boolean)));
+
+ const filteredNews = selectedTeam === "Alle Teams" ? news : news.filter((n) => n.team === selectedTeam);
+
+ const groupedNews = filteredNews.reduce((acc: Record, item) => {
+ const year = new Date(item.created_at).getFullYear().toString();
+ if (!acc[year]) acc[year] = [];
+ acc[year].push(item);
+ return acc;
+ }, {});
+
+
+
+
useEffect(() => {
fetch(`${apiBase}/api/news`)
@@ -31,13 +50,6 @@ const AlleNeuigkeitenPage = () => {
};
- // Gruppieren nach Jahren
- const groupedNews = news.reduce((acc: Record, item) => {
- const year = new Date(item.created_at).getFullYear().toString();
- if (!acc[year]) acc[year] = [];
- acc[year].push(item);
- return acc;
- }, {});
//Toggle der Cards
const toggleExpand = (id: number) => {
@@ -47,8 +59,24 @@ const AlleNeuigkeitenPage = () => {
}
return (
-
-
Alle Neuigkeiten
+
+
Alle Neuigkeiten
+
+
+
+
+
{/* News nach Jahren gruppiert */}
{Object.entries(groupedNews).map(([year, items]) => (
@@ -69,7 +97,7 @@ const AlleNeuigkeitenPage = () => {
src={
item.image_url
? `${apiBase}${item.image_url}`
- : "https://via.placeholder.com/400x300?text=Kein+Bild"
+ : defaultImage
}
alt={item.title}
className="w-full h-full object-cover"
diff --git a/src/pages/VideosPage.tsx b/src/pages/VideosPage.tsx
new file mode 100644
index 000000000..e2d2857f1
--- /dev/null
+++ b/src/pages/VideosPage.tsx
@@ -0,0 +1,73 @@
+import { useRef } from "react";
+import { Rewind, FastForward, Pause, Play } from "lucide-react";
+
+const VideosPage = () => {
+ const videoRef = useRef
(null);
+
+ const skip = (seconds: number) => {
+ if (videoRef.current) {
+ videoRef.current.currentTime += seconds;
+ }
+ };
+
+ const togglePlayPause = () => {
+ const video = videoRef.current;
+ if (!video) return;
+ if (video.paused) {
+ video.play();
+ } else {
+ video.pause();
+ }
+ };
+
+ return (
+
+
+ Trainingsvideo: Satz 1
+
+
+
+
+
+ {/* Custom Steuerung */}
+
+
+
+
+
+
+
+ );
+};
+
+export default VideosPage;
diff --git a/tailwind.config.ts b/tailwind.config.ts
index dc69d7da5..a6568cdac 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -114,5 +114,6 @@ export default {
plugins: [
require("tailwindcss-animate"),
require("@tailwindcss/typography"),
+ require('@tailwindcss/aspect-ratio')
],
} satisfies Config;