Merge pull request #11 from Denshooter/d-branch-1

feat: update dependencies and enhance privacy policy
This commit is contained in:
Denshooter
2025-02-12 12:59:56 +01:00
committed by GitHub
14 changed files with 665 additions and 329 deletions

View File

@@ -1,89 +1,127 @@
// app/Projects/[slug]/page.tsx
"use client"; "use client";
import {useParams, useRouter} from "next/navigation"; import {
import {useEffect, useState} from "react"; useRouter,
import Header from "../../components/Header"; useSearchParams,
import Footer_Back from "../../components/Footer_Back"; useParams,
import ReactMarkdown from "react-markdown"; usePathname,
import remarkGfm from "remark-gfm"; } from "next/navigation";
import rehypeRaw from "rehype-raw"; import { useEffect, useState } from "react";
import matter from "gray-matter";
import Footer_Back from "@/app/components/Footer_Back";
import Header from "@/app/components/Header";
import Image from "next/image";
import "@/app/styles/ghostContent.css"; // Import the global styles
interface Project { interface Project {
id: string; slug: string;
title: string; id: string;
description: string; title: string;
text: string; feature_image: string;
slug: string; visibility: string;
image: string; published_at: string;
updated_at: string;
html: string;
reading_time: number;
meta_description: string;
} }
export default function ProjectDetail() { const ProjectDetails = () => {
const params = useParams(); const router = useRouter();
const router = useRouter(); const searchParams = useSearchParams();
const {slug} = params as { slug: string }; const params = useParams();
const [project, setProject] = useState<Project | null>(null); const pathname = usePathname();
const [isVisible, setIsVisible] = useState(false); const [project, setProject] = useState<Project | null>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
setIsVisible(true); setIsVisible(true);
}, 150); // Delay to start the animation }, 150); // Delay to start the animation
}, []); }, []);
useEffect(() => { useEffect(() => {
if (slug) { const projectData = searchParams.get("project");
fetch(`/projects/${slug}.md`) if (projectData) {
.then((res) => res.text()) setProject(JSON.parse(projectData));
.then((content) => { // Remove the project data from the URL without reloading the page
const {data, content: markdownContent} = matter(content); const url = new URL(window.location.href);
setProject({ url.searchParams.delete("project");
id: data.id, window.history.replaceState({}, "", url.toString());
title: data.title, } else {
description: data.description, // Fetch project data based on slug from URL
text: markdownContent, const slug = params.slug as string;
slug: slug, fetchProjectData(slug);
image: data.image,
});
// Log the project view
fetch("/api/stats", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "project_view",
projectId: data.id,
}),
}).catch((err) => console.error("Failed to log project view", err));
})
.catch(() => {
// Redirect to 404 if project not found
router.replace("/not-found");
});
}
}, [slug, router]);
if (!project) {
return <div className="p-10 text-center">Loading...</div>;
} }
}, [searchParams, router, params, pathname]);
const fetchProjectData = async (slug: string) => {
try {
const response = await fetch(`/api/fetchProject?slug=${slug}`);
if (!response.ok) {
throw new Error("Failed to fetch project data");
}
const projectData = await response.json();
setProject(projectData.posts[0]); // Assuming the API returns an array of posts
} catch (error) {
console.error("Failed to fetch project data:", error);
}
};
if (!project) {
return ( return (
<div className={`min-h-screen flex flex-col bg-radiant ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}> <div className="min-h-screen flex flex-col bg-radiant">
<Header/> <Header />
<div className="flex-grow p-10 pt-24"> <div className="flex-grow flex items-center justify-center">
<div <div className="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-32 w-32"></div>
className="flex flex-col p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl">
<div className="mt-4 text-gray-600 dark:text-gray-300 markdown">
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
{project.text}
</ReactMarkdown>
</div>
</div>
</div>
<Footer_Back/>
</div> </div>
<Footer_Back />
</div>
); );
} }
const featureImageUrl = `/api/fetchImage?url=${encodeURIComponent(project.feature_image)}`;
return (
<div
className={`min-h-screen flex flex-col bg-radiant ${isVisible ? "animate-fly-in" : "opacity-0"}`}
>
<Header />
<div className="flex-grow">
{/* Hero Section */}
<div className="flex justify-center md:mt-28 px-4 md:px-0">
<div className="relative w-full max-w-4xl h-0 pb-[56.25%] rounded-2xl overflow-hidden">
{" "}
{/* 16:9 Aspect Ratio */}
<Image
src={featureImageUrl}
alt={project.title}
fill
style={{ objectFit: "cover" }}
className="rounded-2xl"
priority={true}
/>
</div>
</div>
<div className="flex items-center justify-center mt-4">
<h1 className="text-4xl md:text-6xl font-bold text-white">
{project.title}
</h1>
</div>
{/* Project Content */}
<div className="p-10 pt-12">
<div className="flex flex-col p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl">
<div
className="content mt-4 text-gray-600 text-lg leading-relaxed"
dangerouslySetInnerHTML={{ __html: project.html }}
></div>
</div>
</div>
</div>
<Footer_Back />
</div>
);
};
export default ProjectDetails;

View File

@@ -0,0 +1,25 @@
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // Force Node runtime
const GHOST_API_URL = "http://192.168.179.31:2368";
const GHOST_API_KEY = "067b8434f2e7f2a771dfcc45a7"; // Replace with your actual key
export async function GET() {
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`,
);
if (!response.ok) {
throw new Error(`Failed to fetch posts: ${response.statusText}`);
}
const posts = await response.json();
return NextResponse.json(posts);
} catch (error) {
console.error("Failed to fetch posts from Ghost:", error);
return NextResponse.json(
{ error: "Failed to fetch projects" },
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,35 @@
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const url = searchParams.get("url");
if (!url) {
return NextResponse.json(
{ error: "Missing URL parameter" },
{ status: 400 },
);
}
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.statusText}`);
}
const contentType = response.headers.get("content-type");
const buffer = await response.arrayBuffer();
return new NextResponse(buffer, {
headers: {
"Content-Type": contentType || "application/octet-stream",
},
});
} catch (error) {
console.error("Failed to fetch image:", error);
return NextResponse.json(
{ error: "Failed to fetch image" },
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,32 @@
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // Force Node runtime
const GHOST_API_URL = "http://192.168.179.31:2368";
const GHOST_API_KEY = "067b8434f2e7f2a771dfcc45a7"; // Replace with your actual key
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get("slug");
if (!slug) {
return NextResponse.json({ error: "Slug is required" }, { status: 400 });
}
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/slug/${slug}/?key=${GHOST_API_KEY}`,
);
if (!response.ok) {
throw new Error(`Failed to fetch post: ${response.statusText}`);
}
const post = await response.json();
return NextResponse.json(post);
} catch (error) {
console.error("Failed to fetch post from Ghost:", error);
return NextResponse.json(
{ error: "Failed to fetch project" },
{ status: 500 },
);
}
}

View File

@@ -1,48 +1,52 @@
// app/components/Hero.tsx // app/components/Hero.tsx
import React, {useEffect, useState} from "react"; import React, { useEffect, useState } from "react";
import Image from "next/image"; import Image from "next/image";
export default function Hero() { export default function Hero() {
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
setIsVisible(true); setIsVisible(true);
}, 150); // Delay to start the animation }, 150); // Delay to start the animation
}, []); }, []);
return ( return (
<div id="about" <div
className={`flex flex-col md:flex-row items-center justify-center pt-16 pb-16 px-6 text-gray-700 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}> id="about"
<div className={`flex flex-col md:flex-row items-center justify-center pt-16 pb-16 px-6 text-gray-700 ${isVisible ? "animate-fly-in" : "opacity-0"}`}
className="flex flex-col items-center p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl max-w-lg text-center"> >
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-900"> <div className="flex flex-col items-center p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl max-w-lg text-center">
Hi, Im Dennis <h1 className="text-4xl md:text-5xl font-extrabold text-gray-900">
</h1> Hi, Im Dennis
<h2 className="mt-2 text-xl md:text-2xl font-semibold text-gray-700"> </h1>
Student & Software Engineer <h2 className="mt-2 text-xl md:text-2xl font-semibold text-gray-700">
</h2> Student & Software Engineer
<h3 className="mt-1 text-lg md:text-xl text-gray-600"> </h2>
Based in Osnabrück, Germany <h3 className="mt-1 text-lg md:text-xl text-gray-600">
</h3> Based in Osnabrück, Germany
<p className="mt-6 text-gray-800 text-lg leading-relaxed"> </h3>
Passionate about technology, coding, and solving real-world problems. <p className="mt-6 text-gray-800 text-lg leading-relaxed">
I enjoy building innovative solutions and continuously expanding my knowledge. Passionate about technology, coding, and solving real-world problems.
</p> I enjoy building innovative solutions and continuously expanding my
<p className="mt-4 text-gray-700 text-base"> knowledge.
Currently working on exciting projects that merge creativity with functionality. </p>
Always eager to learn and collaborate! <p className="mt-4 text-gray-700 text-base">
</p> Currently working on exciting projects that merge creativity with
</div> functionality. Always eager to learn and collaborate!
<div className="flex mt-8 md:mt-0 md:ml-12"> </p>
<Image </div>
src="/images/me.jpg" <div className="flex mt-8 md:mt-0 md:ml-12">
alt="Image of Dennis" <Image
width={400} src="/images/me.jpg"
height={400} alt="Image of Dennis"
className="rounded-2xl shadow-lg shadow-gray-700 object-cover" width={400}
/> height={400}
</div> className="rounded-2xl shadow-lg shadow-gray-700 object-cover"
</div> priority={true}
); style={{ width: "auto", height: "400px" }}
/>
</div>
</div>
);
} }

View File

@@ -1,70 +1,81 @@
import React, {useEffect, useState} from "react"; import React, { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
interface Project { interface Project {
id: string; slug: string;
title: string; id: string;
description: string; title: string;
slug: string; feature_image: string;
visibility: string;
published_at: string;
updated_at: string;
html: string;
reading_time: number;
meta_description: string;
} }
export default function Projects() { export default function Projects() {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
useEffect(() => { useEffect(() => {
const fetchProjects = async () => { const fetchProjects = async () => {
try { try {
const response = await fetch('/api/projects'); const response = await fetch("/api/fetchAllProjects");
try { if (!response.ok) {
if (!response.ok) { throw new Error("Failed to fetch projects from Ghost");
throw new Error("Failed to fetch projects"); }
} const projectsData = await response.json();
} catch (error) { setProjects(projectsData.posts);
console.error("Failed to fetch projects:", error);
}
const projectsData = await response.json();
setProjects(projectsData);
setTimeout(() => {
setIsVisible(true);
}, 250); // Delay to start the animation after Hero
} catch (error) {
console.error("Failed to fetch projects:", error);
}
};
fetchProjects().then(r => r);
}, []);
const numberOfProjects = projects.length;
return ( setTimeout(() => {
<section id="projects" className={`p-10 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}> setIsVisible(true);
<h2 className="text-3xl font-bold text-center text-gray-800"> }, 250); // Delay to start the animation after Hero
Projects } catch (error) {
</h2> console.error("Failed to fetch projects:", error);
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6"> }
{projects.map((project, index) => ( };
<Link key={project.id} href={`/Projects/${project.slug}`} className="cursor-pointer"> fetchProjects();
<div className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`} }, []);
style={{animationDelay: `${index * 0.1}s`}}>
<h3 className="text-2xl font-bold text-gray-800"> console.log(projects.at(0)?.feature_image);
{project.title}
</h3> const numberOfProjects = projects.length;
<p className="mt-2 text-gray-500"> return (
{project.description} <section
</p> id="projects"
</div> className={`p-10 ${isVisible ? "animate-fly-in" : "opacity-0"}`}
</Link> >
))} <h2 className="text-3xl font-bold text-center text-gray-800">Projects</h2>
<div className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`} <div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
style={{animationDelay: `${(numberOfProjects + 1) * 0.1}s`}}> {projects.map((project, index) => (
<h3 className="text-2xl font-bold text-gray-800"> <Link
More to come key={project.id}
</h3> href={{
<p className="mt-2 text-gray-500"> pathname: `/Projects/${project.slug}`,
... query: { project: JSON.stringify(project) },
</p> }}
</div> className="cursor-pointer"
>
<div
className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
style={{ animationDelay: `${index * 0.1}s` }}
>
<h3 className="text-2xl font-bold text-gray-800">
{project.title}
</h3>
<p className="mt-2 text-gray-500">{project.meta_description}</p>
</div> </div>
</section> </Link>
); ))}
<div
className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
style={{ animationDelay: `${(numberOfProjects + 1) * 0.1}s` }}
>
<h3 className="text-2xl font-bold text-gray-800">More to come</h3>
<p className="mt-2 text-gray-500">...</p>
</div>
</div>
</section>
);
} }

View File

@@ -4,29 +4,32 @@
import "./globals.css"; import "./globals.css";
import {Roboto} from 'next/font/google'; import { Roboto } from "next/font/google";
import React from "react"; import React from "react";
//import ClientCookieConsentBanner from "./components/ClientCookieConsentBanner"; //import ClientCookieConsentBanner from "./components/ClientCookieConsentBanner";
const roboto = Roboto({ const roboto = Roboto({
variable: '--font-roboto', variable: "--font-roboto",
weight: '400', weight: "400",
subsets: ['latin'], subsets: ["latin"],
}); });
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return (
return ( <html lang="en">
<html lang="en"> <head>
<script defer src="https://umami.denshooter.de/script.js" data-website-id="1f213877-deef-4238-8df1-71a5a3bcd142"></script> <script
<body className={roboto.variable}> defer
{children} src="https://umami.denshooter.de/script.js"
</body> data-website-id="1f213877-deef-4238-8df1-71a5a3bcd142"
</html> ></script>
); <meta charSet="utf-8" />
</head>
<body className={roboto.variable}>{children}</body>
</html>
);
} }

View File

@@ -27,11 +27,6 @@ export default function LegalNotice() {
info@dki.one info@dki.one
</Link>{" "} </Link>{" "}
<br /> <br />
<strong>Telefon:</strong>{" "}
<Link href={"tel:+4917612669990"} className="transition-underline">
+49 176 12669990
</Link>
<br />
<strong>Website:</strong>{" "} <strong>Website:</strong>{" "}
<Link href={"https://www.dki.one"} className="transition-underline"> <Link href={"https://www.dki.one"} className="transition-underline">
{" "} {" "}
@@ -44,7 +39,10 @@ export default function LegalNotice() {
Meine Website enthält Links auf externe Websites. Ich habe keinen Meine Website enthält Links auf externe Websites. Ich habe keinen
Einfluss auf die Inhalte dieser Websites und kann daher keine Gewähr Einfluss auf die Inhalte dieser Websites und kann daher keine Gewähr
übernehmen. Für die Inhalte der verlinkten Seiten ist stets der übernehmen. Für die Inhalte der verlinkten Seiten ist stets der
Betreiber oder Anbieter der Seiten verantwortlich. Betreiber oder Anbieter der Seiten verantwortlich. Jedoch überprüfe
ich die verlinkten Seiten zum Zeitpunkt der Verlinkung auf mögliche
Rechtsverstöße. Bei Bekanntwerden von Rechtsverletzungen werde ich
derartige Links umgehend entfernen.
</p> </p>
<h2 className="text-2xl font-semibold mt-6">Urheberrecht</h2> <h2 className="text-2xl font-semibold mt-6">Urheberrecht</h2>
@@ -60,7 +58,7 @@ export default function LegalNotice() {
Diensteanbieter kann ich keine Gewähr übernehmen für Schäden, die Diensteanbieter kann ich keine Gewähr übernehmen für Schäden, die
entstehen können, durch den Zugriff oder die Nutzung dieser Website. entstehen können, durch den Zugriff oder die Nutzung dieser Website.
</p> </p>
<p className="font-semibold mt-6">Letzte Aktualisierung: 11.02.2025</p> <p className="font-semibold mt-6">Letzte Aktualisierung: 12.02.2025</p>
</main> </main>
<Footer_Back /> <Footer_Back />
</div> </div>

View File

@@ -9,39 +9,38 @@ import Footer from "./components/Footer";
import Script from "next/script"; import Script from "next/script";
export default function Home() { export default function Home() {
return ( return (
<div className="min-h-screen flex flex-col bg-radiant-animated">
<div className="min-h-screen flex flex-col bg-radiant-animated"> <Script
<Script id={"structured-data"}
id={"structured-data"} type="application/ld+json"
type="application/ld+json" dangerouslySetInnerHTML={{
dangerouslySetInnerHTML={{ __html: JSON.stringify({
__html: JSON.stringify({ "@context": "https://schema.org",
"@context": "https://schema.org", "@type": "Person",
"@type": "Person", name: "Dennis Konkol",
"name": "Dennis Konkol", url: "https://dki.one",
"url": "https://dki.one", jobTitle: "Software Engineer",
"jobTitle": "Software Engineer", address: {
"address": { "@type": "PostalAddress",
"@type": "PostalAddress", addressLocality: "Osnabrück",
"addressLocality": "Osnabrück", addressCountry: "Germany",
"addressCountry": "Germany", },
}, sameAs: [
"sameAs": [ "https://github.com/Denshooter",
"https://github.com/Denshooter", "https://linkedin.com/in/dkonkol",
"https://linkedin.com/in/dkonkol", ],
], }),
}), }}
}} />
/> <Header />
<Header/> <div className="h-10"></div>
<div className="h-10"></div> <main>
<main> <Hero />
<Hero/> <Projects />
<Projects/> <Contact />
<Contact/> <Footer />
<Footer/> </main>
</main> </div>
</div> );
);
} }

View File

@@ -39,11 +39,6 @@ export default function PrivacyPolicy() {
info@dki.one info@dki.one
</Link>{" "} </Link>{" "}
<br /> <br />
<strong>Telefon:</strong>{" "}
<Link className="transition-underline" href={"tel:+4917612669990"}>
+49 176 12669990
</Link>
<br />
<strong>Website:</strong>{" "} <strong>Website:</strong>{" "}
<Link className="transition-underline" href={"https://www.dki.one"}> <Link className="transition-underline" href={"https://www.dki.one"}>
{" "} {" "}
@@ -57,7 +52,7 @@ export default function PrivacyPolicy() {
<h2 className="text-2xl font-semibold mt-6"> <h2 className="text-2xl font-semibold mt-6">
Erfassung allgemeiner Informationen beim Besuch meiner Website Erfassung allgemeiner Informationen beim Besuch meiner Website
</h2> </h2>
<p className="mt-2"> <div className="mt-2">
Beim Zugriff auf meiner Website werden automatisch Informationen Beim Zugriff auf meiner Website werden automatisch Informationen
allgemeiner Natur erfasst. Diese beinhalten unter anderem: allgemeiner Natur erfasst. Diese beinhalten unter anderem:
<ul className="list-disc list-inside mt-2"> <ul className="list-disc list-inside mt-2">
@@ -76,7 +71,7 @@ export default function PrivacyPolicy() {
<li>die Inhalte meiner Website zu optimieren,</li> <li>die Inhalte meiner Website zu optimieren,</li>
<li>die Systemsicherheit und -stabilität zu analysiern.</li> <li>die Systemsicherheit und -stabilität zu analysiern.</li>
</ul> </ul>
</p> </div>
<h2 className="text-2xl font-semibold mt-6">Cookies</h2> <h2 className="text-2xl font-semibold mt-6">Cookies</h2>
<p className="mt-2"> <p className="mt-2">
Meine Website verwendet keine Cookies. Daher ist kein Meine Website verwendet keine Cookies. Daher ist kein
@@ -129,7 +124,7 @@ export default function PrivacyPolicy() {
jeweiligen Anbieter. jeweiligen Anbieter.
</p> </p>
<h2 className="text-2xl font-semibold mt-6">Weitergabe von Daten</h2> <h2 className="text-2xl font-semibold mt-6">Weitergabe von Daten</h2>
<p className="mt-2"> <div className="mt-2">
Eine Weitergabe Ihrer personenbezogenen Daten erfolgt nur, wenn: Eine Weitergabe Ihrer personenbezogenen Daten erfolgt nur, wenn:
<ul className="list-disc list-inside mt-2"> <ul className="list-disc list-inside mt-2">
<li> <li>
@@ -149,7 +144,7 @@ export default function PrivacyPolicy() {
berechtigter Interessen erforderlich ist. berechtigter Interessen erforderlich ist.
</li> </li>
</ul> </ul>
</p> </div>
<h2 className="text-2xl font-semibold mt-6"> <h2 className="text-2xl font-semibold mt-6">
Speicherdauer und Löschung Speicherdauer und Löschung
</h2> </h2>
@@ -159,7 +154,7 @@ export default function PrivacyPolicy() {
werden Ihre Daten gelöscht. werden Ihre Daten gelöscht.
</p> </p>
<h2 className="text-2xl font-semibold mt-6">Ihre Rechte</h2> <h2 className="text-2xl font-semibold mt-6">Ihre Rechte</h2>
<p className="mt-2"> <div className="mt-2">
Sie haben gemäß DSGVO folgende Rechte: Sie haben gemäß DSGVO folgende Rechte:
<ul className="list-disc list-inside mt-2"> <ul className="list-disc list-inside mt-2">
<li> <li>
@@ -198,7 +193,7 @@ export default function PrivacyPolicy() {
> >
https://www.bfdi.bund.de/ https://www.bfdi.bund.de/
</Link> </Link>
</p> </div>
<h2 className="text-2xl font-semibold mt-6">Datensicherheit</h2> <h2 className="text-2xl font-semibold mt-6">Datensicherheit</h2>
<p className="mt-2"> <p className="mt-2">
Ich setze technische und organisatorische Maßnahmen ein, um Ihre Daten Ich setze technische und organisatorische Maßnahmen ein, um Ihre Daten
@@ -226,7 +221,7 @@ export default function PrivacyPolicy() {
berücksichtigen. Die jeweils aktuelle Datenschutzerklärung finden Sie berücksichtigen. Die jeweils aktuelle Datenschutzerklärung finden Sie
auf meiner Website. auf meiner Website.
</p> </p>
<p className="mt-6 font-bold">Letzte Aktualisierung: 11.02.2025</p> <p className="mt-6 font-bold">Letzte Aktualisierung: 12.02.2025</p>
</main> </main>
<Footer_Back /> <Footer_Back />
</div> </div>

View File

@@ -0,0 +1,70 @@
/* ghostContent.css */
.content {
font-family: "Arial", sans-serif;
line-height: 1.6;
color: #333;
}
.content h1,
.content h2,
.content h3,
.content h4,
.content h5,
.content h6 {
color: #222;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
.content p {
margin-bottom: 1em;
}
.content img {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
margin: 1em 0;
}
.content ul,
.content ol {
margin: 1em 0;
padding-left: 1.5em;
}
.content a {
color: #0070f3;
text-decoration: none;
}
.content a:hover {
text-decoration: underline;
}
.content blockquote {
border-left: 4px solid #ddd;
padding-left: 1em;
color: #666;
margin: 1em 0;
}
.content pre {
background: #f5f5f5;
padding: 1em;
border-radius: 8px;
overflow-x: auto;
}
.loader {
border-top-color: #3498db;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}

275
package-lock.json generated
View File

@@ -9,12 +9,12 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
"@vercel/analytics": "^1.4.1", "@tryghost/content-api": "^1.11.21",
"@vercel/og": "^0.6.5", "@vercel/og": "^0.6.5",
"@vercel/speed-insights": "^1.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"next": "15.1.3", "next": "15.1.3",
"node-fetch": "^3.3.2",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"prisma": "^6.1.0", "prisma": "^6.1.0",
"react": "^19.0.0", "react": "^19.0.0",
@@ -1010,6 +1010,15 @@
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
}, },
"node_modules/@tryghost/content-api": {
"version": "1.11.21",
"resolved": "https://registry.npmjs.org/@tryghost/content-api/-/content-api-1.11.21.tgz",
"integrity": "sha512-ozJqEMHDUO7D0SGxPbUnG+RvwBbzC3zmdGOW8cFvkcKzrhe7uOAmVKyq7/J3kRAM2QthTlmiDpqp7NEo9ZLlKg==",
"license": "MIT",
"dependencies": {
"axios": "^1.0.0"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -1359,44 +1368,6 @@
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/@vercel/analytics": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.4.1.tgz",
"integrity": "sha512-ekpL4ReX2TH3LnrRZTUKjHHNpNy9S1I7QmS+g/RQXoSUQ8ienzosuX7T9djZ/s8zPhBx1mpHP/Rw5875N+zQIQ==",
"license": "MPL-2.0",
"peerDependencies": {
"@remix-run/react": "^2",
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
"vue-router": "^4"
},
"peerDependenciesMeta": {
"@remix-run/react": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
},
"next": {
"optional": true
},
"react": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
},
"vue-router": {
"optional": true
}
}
},
"node_modules/@vercel/og": { "node_modules/@vercel/og": {
"version": "0.6.5", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.6.5.tgz", "resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.6.5.tgz",
@@ -1411,41 +1382,6 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/@vercel/speed-insights": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-1.1.0.tgz",
"integrity": "sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"peerDependencies": {
"@sveltejs/kit": "^1 || ^2",
"next": ">= 13",
"react": "^18 || ^19 || ^19.0.0-rc",
"svelte": ">= 4",
"vue": "^3",
"vue-router": "^4"
},
"peerDependenciesMeta": {
"@sveltejs/kit": {
"optional": true
},
"next": {
"optional": true
},
"react": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
},
"vue-router": {
"optional": true
}
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -1734,6 +1670,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -1760,6 +1702,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2098,6 +2051,18 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/comma-separated-tokens": { "node_modules/comma-separated-tokens": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -2207,6 +2172,15 @@
"dev": true, "dev": true,
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/data-view-buffer": { "node_modules/data-view-buffer": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -2334,6 +2308,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dequal": { "node_modules/dequal": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -3208,6 +3191,29 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/fflate": { "node_modules/fflate": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
@@ -3278,6 +3284,26 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz",
@@ -3311,6 +3337,32 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -5550,6 +5602,27 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -5708,6 +5781,43 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "6.10.0", "version": "6.10.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
@@ -6301,6 +6411,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -7817,6 +7933,15 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -10,12 +10,12 @@
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.1.0", "@prisma/client": "^6.1.0",
"@vercel/analytics": "^1.4.1", "@tryghost/content-api": "^1.11.21",
"@vercel/og": "^0.6.5", "@vercel/og": "^0.6.5",
"@vercel/speed-insights": "^1.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"next": "15.1.3", "next": "15.1.3",
"node-fetch": "^3.3.2",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"prisma": "^6.1.0", "prisma": "^6.1.0",
"react": "^19.0.0", "react": "^19.0.0",

View File

@@ -1,3 +1,4 @@
User-agent: * User-agent: *
Allow: / Allow: /
Disallow: ['/legal-notice', '/privacy-policy']
Sitemap: https://dki.one/sitemap.xml Sitemap: https://dki.one/sitemap.xml