feat: add cookie consent banner and privacy policy page; update dependencies and improve animations
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer
|
||||
className="p-10 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl text-center text-gray-800 dark:text-white">
|
||||
<div className="flex justify-center space-x-4 mt-4">
|
||||
<Link href="https://github.com/Denshooter" target="_blank">
|
||||
<svg
|
||||
className="w-6 h-6 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.387.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.746.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.997.108-.774.42-1.305.763-1.605-2.665-.305-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.527.117-3.18 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.4 3-.405 1.02.005 2.043.14 3 .405 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.877.118 3.18.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.62-5.475 5.92.43.37.823 1.1.823 2.22v3.293c0 .32.218.694.825.577C20.565 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link href="https://linkedin.com/in/dkonkol" target="_blank">
|
||||
<svg
|
||||
className="w-6 h-6 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M19 0h-14c-2.76 0-5 2.24-5 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5v-14c0-2.76-2.24-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5 11.5h-3v-5.5c0-1.38-1.12-2.5-2.5-2.5s-2.5 1.12-2.5 2.5v5.5h-3v-10h3v1.5c.83-1.17 2.17-1.5 3.5-1.5 2.48 0 4.5 2.02 4.5 4.5v5.5z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
<p className="mt-6">© Dennis Konkol 2025</p>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
import {useParams, useRouter} from "next/navigation";
|
||||
import {useEffect, useState} from "react";
|
||||
import Link from "next/link";
|
||||
import Header from "../../components/Header";
|
||||
import Footer from "./Footer";
|
||||
import Footer_Back from "../../components/Footer_Back";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
@@ -25,6 +24,13 @@ export default function ProjectDetail() {
|
||||
const router = useRouter();
|
||||
const {slug} = params as { slug: string };
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 150); // Delay to start the animation
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (slug) {
|
||||
@@ -65,7 +71,7 @@ export default function ProjectDetail() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-radiant">
|
||||
<div className={`min-h-screen flex flex-col bg-radiant ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<Header/>
|
||||
<div className="flex-grow p-10 pt-24">
|
||||
<div
|
||||
@@ -75,15 +81,9 @@ export default function ProjectDetail() {
|
||||
{project.text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
<div className={"mt-10"}>
|
||||
<button
|
||||
className={"md:w-1/6 p-3 text-white bg-gradient-to-r from-blue-500 to-purple-500 rounded-xl hover:from-blue-600 hover:to-purple-600 transition"}>
|
||||
<Link href="/">Back to Projects</Link>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer/>
|
||||
<Footer_Back/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
app/api/og/route.tsx
Normal file
76
app/api/og/route.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import {ImageResponse} from 'next/og';
|
||||
|
||||
export async function GET() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '1200px',
|
||||
height: '630px',
|
||||
background: 'radial-gradient(circle at 20% 20%, #ff8185, transparent 80%),' +
|
||||
'radial-gradient(circle at 80% 80%, #ffaa91, transparent 80%)',
|
||||
backgroundSize: '100% 100%',
|
||||
color: '#333',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
padding: '20px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
left: '20px',
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
padding: '10px',
|
||||
}}
|
||||
>
|
||||
Dennis Konkol | Portfolio
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'left',
|
||||
padding: '20px',
|
||||
backgroundColor: 'rgba(182,182,182,0.8)',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
|
||||
maxWidth: '60%',
|
||||
backdropFilter: 'blur(5px)',
|
||||
}}
|
||||
>
|
||||
<h1 style={{fontSize: '48px', margin: '0'}}>Hi, I’m Dennis</h1>
|
||||
<h2 style={{fontSize: '32px', margin: '10px 0'}}>Student & Software Engineer</h2>
|
||||
<p style={{fontSize: '24px', margin: '10px 0'}}>
|
||||
Based in Osnabrück, Germany
|
||||
</p>
|
||||
<p style={{fontSize: '20px', margin: '10px 0'}}>
|
||||
Passionate about technology, coding, and solving real-world problems.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
src="https://dki.one/images/me.jpg"
|
||||
alt="Image of Dennis"
|
||||
style={{
|
||||
width: '400px',
|
||||
height: '400px',
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -4,23 +4,31 @@ import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
|
||||
export async function GET() {
|
||||
const projectsDirectory = path.join(process.cwd(), 'public/projects');
|
||||
const filenames = fs.readdirSync(projectsDirectory);
|
||||
try {
|
||||
const projectsDirectory = path.join(process.cwd(), 'public/projects');
|
||||
const filenames = fs.readdirSync(projectsDirectory);
|
||||
|
||||
console.log('Filenames:', filenames);
|
||||
const projects = filenames
|
||||
.filter((filename) => {
|
||||
const filePath = path.join(projectsDirectory, filename);
|
||||
return fs.statSync(filePath).isFile();
|
||||
})
|
||||
.map((filename) => {
|
||||
const filePath = path.join(projectsDirectory, filename);
|
||||
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||
const {data} = matter(fileContents);
|
||||
|
||||
const projects = filenames.map((filename) => {
|
||||
const filePath = path.join(projectsDirectory, filename);
|
||||
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||
const {data} = matter(fileContents);
|
||||
return {
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
slug: filename.replace('.md', ''),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
slug: filename.replace('.md', ''),
|
||||
};
|
||||
});
|
||||
|
||||
return NextResponse.json(projects);
|
||||
return NextResponse.json(projects);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch projects:", error);
|
||||
return NextResponse.json({error: 'Failed to fetch projects'}, {status: 500});
|
||||
}
|
||||
}
|
||||
13
app/components/ClientCookieConsentBanner.tsx
Normal file
13
app/components/ClientCookieConsentBanner.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// app/components/ClientCookieConsentBanner.tsx
|
||||
|
||||
"use client";
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const CookieConsentBanner = dynamic(() => import('./CookieConsentBanner'), {ssr: false});
|
||||
|
||||
const ClientCookieConsentBanner = ({onConsentChange}: { onConsentChange: (consent: string) => void }) => {
|
||||
return <CookieConsentBanner onConsentChange={onConsentChange}/>;
|
||||
};
|
||||
|
||||
export default ClientCookieConsentBanner;
|
||||
@@ -1,55 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import {useState} from "react";
|
||||
// app/components/Contact.tsx
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
export default function Contact() {
|
||||
const [form, setForm] = useState({name: "", email: "", message: ""});
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
setForm({...form, [e.target.name]: e.target.value});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Replace this with actual form submission logic (e.g., API call)
|
||||
try {
|
||||
// Simulate a successful submission
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
setSuccess(true);
|
||||
setForm({name: "", email: "", message: ""});
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
setError("Failed to send message. Please try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 350); // Delay to start the animation after Projects
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section id="contact" className="p-10">
|
||||
<section id="contact" className={`p-10 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<h2 className="text-3xl font-bold text-center text-gray-800 dark:text-white">
|
||||
Contact Me
|
||||
</h2>
|
||||
<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 mx-auto mt-6">
|
||||
<form onSubmit={handleSubmit} className="w-full space-y-4">
|
||||
{success && (
|
||||
<p className="text-green-500">
|
||||
Your message has been sent successfully!
|
||||
</p>
|
||||
)}
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<form className="w-full space-y-4">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
placeholder="Name"
|
||||
className="w-full p-2 border rounded dark:text-white"
|
||||
required
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
@@ -57,8 +31,6 @@ export default function Contact() {
|
||||
placeholder="Email"
|
||||
className="w-full p-2 border rounded dark:text-white"
|
||||
required
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<textarea
|
||||
name="message"
|
||||
@@ -66,8 +38,6 @@ export default function Contact() {
|
||||
className="w-full p-2 border rounded dark:text-white"
|
||||
rows={5}
|
||||
required
|
||||
value={form.message}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
98
app/components/CookieConsentBanner.tsx
Normal file
98
app/components/CookieConsentBanner.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import CookieConsent from "react-cookie-consent";
|
||||
import Link from "next/link";
|
||||
|
||||
const CookieConsentBanner = ({onConsentChange}: { onConsentChange: (consent: string) => void }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isFadingIn, setIsFadingIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const consent = localStorage.getItem("CookieConsent");
|
||||
if (!consent) {
|
||||
setIsVisible(true);
|
||||
setTimeout(() => setIsFadingIn(true), 10); // Delay to trigger CSS transition
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
setIsFadingIn(false);
|
||||
setTimeout(() => {
|
||||
localStorage.setItem("CookieConsent", "accepted");
|
||||
setIsVisible(false);
|
||||
onConsentChange("accepted");
|
||||
}, 500); // Match the duration of the fade-out transition
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
setIsFadingIn(false);
|
||||
setTimeout(() => {
|
||||
localStorage.setItem("CookieConsent", "declined");
|
||||
setIsVisible(false);
|
||||
onConsentChange("declined");
|
||||
}, 500); // Match the duration of the fade-out transition
|
||||
};
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CookieConsent
|
||||
location="bottom"
|
||||
buttonText="Accept All"
|
||||
declineButtonText="Decline"
|
||||
enableDeclineButton
|
||||
cookieName="CookieConsent"
|
||||
containerClasses={`${isFadingIn ? 'fade-in' : 'fade-out'}`}
|
||||
style={{
|
||||
background: "rgba(211,211,211,0.44)",
|
||||
color: "#333",
|
||||
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.1)",
|
||||
padding: "1rem",
|
||||
borderRadius: "8px",
|
||||
backdropFilter: "blur(10px)",
|
||||
margin: "2rem",
|
||||
width: "calc(100% - 4rem)",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
textAlign: "left",
|
||||
transition: "opacity 0.5s ease",
|
||||
opacity: isFadingIn ? 1 : 0,
|
||||
}}
|
||||
buttonWrapperClasses="button-wrapper"
|
||||
buttonStyle={{
|
||||
backgroundColor: "#4CAF50",
|
||||
color: "#FFF",
|
||||
fontSize: "14px",
|
||||
borderRadius: "4px",
|
||||
padding: "0.5rem 1rem",
|
||||
margin: "0.5rem",
|
||||
width: "100%",
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
declineButtonStyle={{
|
||||
backgroundColor: "#f44336",
|
||||
color: "#FFF",
|
||||
fontSize: "14px",
|
||||
borderRadius: "4px",
|
||||
padding: "0.5rem 1rem",
|
||||
margin: "0.5rem",
|
||||
width: "100%",
|
||||
maxWidth: "200px",
|
||||
}}
|
||||
expires={90}
|
||||
onAccept={handleAccept}
|
||||
onDecline={handleDecline}
|
||||
>
|
||||
<div className="content-wrapper text-xl">
|
||||
This website uses cookies to enhance your experience. By using our website, you consent to the use of
|
||||
cookies.
|
||||
You can read more in our <Link href="/" className="text-blue-800 transition-underline">privacy
|
||||
policy</Link>.
|
||||
</div>
|
||||
</CookieConsent>
|
||||
);
|
||||
};
|
||||
|
||||
export default CookieConsentBanner;
|
||||
@@ -1,6 +1,15 @@
|
||||
import Link from "next/link";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
export default function Footer() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 450); // Delay to start the animation
|
||||
}, []);
|
||||
|
||||
const scrollToSection = (id: string) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
@@ -10,33 +19,40 @@ export default function Footer() {
|
||||
|
||||
return (
|
||||
<footer
|
||||
className="p-10 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl text-center text-gray-800 dark:text-white">
|
||||
<h1 className="text-3xl font-bold">Thank You for Visiting</h1>
|
||||
<p className="mt-4 text-xl">Connect with me on social platforms:</p>
|
||||
<div className="flex justify-center space-x-4 mt-4">
|
||||
<Link href="https://github.com/Denshooter" target="_blank">
|
||||
<svg
|
||||
className="w-6 h-6 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.387.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.746.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.997.108-.774.42-1.305.763-1.605-2.665-.305-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.527.117-3.18 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.4 3-.405 1.02.005 2.043.14 3 .405 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.877.118 3.18.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.62-5.475 5.92.43.37.823 1.1.823 2.22v3.293c0 .32.218.694.825.577C20.565 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link href="https://linkedin.com/in/dkonkol" target="_blank">
|
||||
<svg
|
||||
className="w-6 h-6 text-gray-700 dark:text-white hover:text-gray-900 dark:hover:text-gray-300 transition"
|
||||
fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19 0h-14c-2.76 0-5 2.24-5 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5v-14c0-2.76-2.24-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5 11.5h-3v-5.5c0-1.38-1.12-2.5-2.5-2.5s-2.5 1.12-2.5 2.5v5.5h-3v-10h3v1.5c.83-1.17 2.17-1.5 3.5-1.5 2.48 0 4.5 2.02 4.5 4.5v5.5z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
className={`p-3 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl text-center text-gray-800 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<div className={`flex flex-col md:flex-row items-center justify-between`}>
|
||||
<div className={`flex-col items-center`}>
|
||||
<h1 className="md:text-xl font-bold">Thank You for Visiting</h1>
|
||||
<p className="md:mt-1 text-lg">Connect with me on social platforms:</p>
|
||||
<div className="flex justify-center items-center space-x-4 mt-4">
|
||||
<Link href="https://github.com/Denshooter" target="_blank">
|
||||
<svg className="w-10 h-10" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.387.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.746.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.997.108-.774.42-1.305.763-1.605-2.665-.305-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.527.117-3.18 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.4 3-.405 1.02.005 2.043.14 3 .405 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.877.118 3.18.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.62-5.475 5.92.43.37.823 1.1.823 2.22v3.293c0 .32.218.694.825.577C20.565 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link href="https://linkedin.com/in/dkonkol" target="_blank">
|
||||
<svg className="w-10 h-10" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19 0h-14c-2.76 0-5 2.24-5 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5v-14c0-2.76-2.24-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5 11.5h-3v-5.5c0-1.38-1.12-2.5-2.5-2.5s-2.5 1.12-2.5 2.5v5.5h-3v-10h3v1.5c.83-1.17 2.17-1.5 3.5-1.5 2.48 0 4.5 2.02 4.5 4.5v5.5z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 md:absolute md:left-1/2 md:transform md:-translate-x-1/2">
|
||||
<button onClick={() => scrollToSection("about")}
|
||||
className="p-4 mt-4 md:px-4 md:my-6 text-white bg-gradient-to-r from-blue-500 to-purple-500 rounded-2xl hover:from-blue-600 hover:to-purple-600 transition">
|
||||
Back to Top
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-col">
|
||||
<div className="mt-4">
|
||||
<Link href="/privacy-policy" className="text-blue-800 transition-underline">Privacy
|
||||
Policy</Link>
|
||||
</div>
|
||||
<p className="md:mt-4">© Dennis Konkol 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-6">© Dennis Konkol 2025</p>
|
||||
<button
|
||||
onClick={() => scrollToSection("about")}
|
||||
className="mt-6 inline-block px-6 py-2 text-white bg-gradient-to-r from-blue-500 to-purple-500 rounded hover:from-blue-600 hover:to-purple-600 transition">
|
||||
Back to Top
|
||||
</button>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
50
app/components/Footer_Back.tsx
Normal file
50
app/components/Footer_Back.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import Link from "next/link";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
export default function Footer_Back() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 450); // Delay to start the animation
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<footer
|
||||
className={`p-3 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl text-center text-gray-800 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<div className={`flex flex-col md:flex-row items-center justify-between`}>
|
||||
<div className={`flex-col items-center`}>
|
||||
<p className="md:mt-1 text-lg">Connect with me on social platforms:</p>
|
||||
<div className="flex justify-center items-center space-x-4 mt-4">
|
||||
<Link href="https://github.com/Denshooter" target="_blank">
|
||||
<svg className="w-10 h-10" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.387.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.746.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.997.108-.774.42-1.305.763-1.605-2.665-.305-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.527.117-3.18 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.4 3-.405 1.02.005 2.043.14 3 .405 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.877.118 3.18.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.62-5.475 5.92.43.37.823 1.1.823 2.22v3.293c0 .32.218.694.825.577C20.565 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
<Link href="https://linkedin.com/in/dkonkol" target="_blank">
|
||||
<svg className="w-10 h-10" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19 0h-14c-2.76 0-5 2.24-5 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5v-14c0-2.76-2.24-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5 11.5h-3v-5.5c0-1.38-1.12-2.5-2.5-2.5s-2.5 1.12-2.5 2.5v5.5h-3v-10h3v1.5c.83-1.17 2.17-1.5 3.5-1.5 2.48 0 4.5 2.02 4.5 4.5v5.5z"/>
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 md:absolute md:left-1/2 md:transform md:-translate-x-1/2">
|
||||
<Link href={"/"}
|
||||
className="p-4 mt-4 md:px-4 md:my-6 text-white bg-gradient-to-r from-blue-500 to-purple-500 rounded-2xl hover:from-blue-600 hover:to-purple-600 transition">
|
||||
Back to main page
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex-col">
|
||||
<div className="mt-4">
|
||||
<Link href="/privacy-policy" className="text-blue-800 transition-underline">Privacy
|
||||
Policy</Link>
|
||||
</div>
|
||||
<p className="md:mt-4">© Dennis Konkol 2025</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import {useState} from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Header() {
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 50); // Delay to start the animation after Projects
|
||||
}, []);
|
||||
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
@@ -21,7 +30,7 @@ export default function Header() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className={`p-4 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<div
|
||||
className={`fixed top-4 left-4 right-4 p-4 bg-white/45 text-gray-700 backdrop-blur-md shadow-xl rounded-2xl z-50 ${isSidebarOpen ? 'transform -translate-y-full' : ''}`}>
|
||||
<header className="w-full">
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
// app/components/Hero.tsx
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Hero() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 150); // Delay to start the animation
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id="about"
|
||||
className="flex flex-col md:flex-row items-center justify-center pt-16 pb-16 px-6 text-gray-700">
|
||||
|
||||
{/* Left Section: Text */}
|
||||
<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">
|
||||
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'}`}>
|
||||
<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">
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-900">
|
||||
Hi, I’m Dennis
|
||||
</h1>
|
||||
@@ -17,19 +25,15 @@ export default function Hero() {
|
||||
<h3 className="mt-1 text-lg md:text-xl text-gray-600">
|
||||
Based in Osnabrück, Germany
|
||||
</h3>
|
||||
|
||||
<p className="mt-6 text-gray-800 text-lg leading-relaxed">
|
||||
Passionate about technology, coding, and solving real-world problems.
|
||||
I enjoy building innovative solutions and continuously expanding my knowledge.
|
||||
</p>
|
||||
|
||||
<p className="mt-4 text-gray-700 text-base">
|
||||
Currently working on exciting projects that merge creativity with functionality.
|
||||
Always eager to learn and collaborate!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right Section: Image */}
|
||||
<div className="flex mt-8 md:mt-0 md:ml-12">
|
||||
<Image
|
||||
src="/images/me.jpg"
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// app/components/Projects.tsx
|
||||
"use client";
|
||||
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Link from "next/link";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
@@ -13,37 +10,42 @@ interface Project {
|
||||
|
||||
export default function Projects() {
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/projects');
|
||||
try {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch projects");
|
||||
}
|
||||
} catch (error) {
|
||||
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();
|
||||
fetchProjects().then(r => r);
|
||||
}, []);
|
||||
const numberOfProjects = projects.length;
|
||||
|
||||
return (
|
||||
<section id="projects" className="p-10">
|
||||
<section id="projects" className={`p-10 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<h2 className="text-3xl font-bold text-center text-gray-800">
|
||||
Projects
|
||||
</h2>
|
||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{projects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
href={`/Projects/${project.slug}`}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
key={project.id}
|
||||
className="p-4 border shadow-lg bg-white/45 rounded-2xl"
|
||||
>
|
||||
{projects.map((project, index) => (
|
||||
<Link key={project.id} href={`/Projects/${project.slug}`} 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>
|
||||
@@ -53,6 +55,15 @@ export default function Projects() {
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -119,3 +119,68 @@ body {
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.react-cookie-consent .content-wrapper {
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.react-cookie-consent .button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.react-cookie-consent .button-wrapper {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.transition-underline {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.transition-underline::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -2px;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: currentColor;
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.transition-underline:hover::after {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
opacity: 1 !important;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes flyIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fly-in {
|
||||
animation: flyIn 1s ease-in-out;
|
||||
}
|
||||
@@ -1,32 +1,45 @@
|
||||
// app/layout.tsx
|
||||
|
||||
import type {Metadata} from "next";
|
||||
"use client";
|
||||
|
||||
import {SpeedInsights} from "@vercel/speed-insights/next";
|
||||
import {Analytics} from "@vercel/analytics/next";
|
||||
import "./globals.css";
|
||||
|
||||
import {Roboto} from 'next/font/google'
|
||||
import React from "react";
|
||||
import {Roboto} from 'next/font/google';
|
||||
import React, {useEffect, useState} from "react";
|
||||
import ClientCookieConsentBanner from "./components/ClientCookieConsentBanner";
|
||||
|
||||
|
||||
const roboto = Roboto({
|
||||
variable: '--font-roboto',
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dennis",
|
||||
description: "A portfolio website showcasing my work and skills.",
|
||||
};
|
||||
});
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [consent, setConsent] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const storedConsent = localStorage.getItem("CookieConsent");
|
||||
setConsent(storedConsent);
|
||||
}, []);
|
||||
|
||||
const handleConsentChange = (newConsent: string) => {
|
||||
setConsent(newConsent);
|
||||
};
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={roboto.variable}>
|
||||
<ClientCookieConsentBanner onConsentChange={handleConsentChange}/>
|
||||
{children}
|
||||
{consent === "accepted" && <SpeedInsights/>}
|
||||
{consent === "accepted" && <Analytics/>}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
31
app/metadata.tsx
Normal file
31
app/metadata.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// app/metadata.ts
|
||||
|
||||
import {Metadata} from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Dennis Konkol | Portfolio",
|
||||
description: "Portfolio of Dennis Konkol, a student and software engineer based in Osnabrück, Germany. Passionate about technology, coding, and solving real-world problems.",
|
||||
keywords: ["Dennis Konkol", "Software Engineer", "Portfolio", "Student"],
|
||||
authors: [{name: "Dennis Konkol", url: "https://dki.one"}],
|
||||
openGraph: {
|
||||
title: "Dennis Konkol | Portfolio",
|
||||
description: "Explore my projects and get in touch!",
|
||||
url: "https://dki.one",
|
||||
siteName: "Dennis Konkol Portfolio",
|
||||
images: [
|
||||
{
|
||||
url: "https://dki.one/api/og",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "Dennis Konkol Portfolio",
|
||||
},
|
||||
],
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Dennis Konkol | Portfolio",
|
||||
description: "Student & Software Engineer based in Osnabrück, Germany.",
|
||||
images: ["https://dki.one/api/og"],
|
||||
},
|
||||
};
|
||||
25
app/page.tsx
25
app/page.tsx
@@ -1,3 +1,4 @@
|
||||
// app/page.tsx
|
||||
"use client";
|
||||
|
||||
import Header from "./components/Header";
|
||||
@@ -5,10 +6,34 @@ import Hero from "./components/Hero";
|
||||
import Projects from "./components/Projects";
|
||||
import Contact from "./components/Contact";
|
||||
import Footer from "./components/Footer";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
<div className="min-h-screen flex flex-col bg-radiant-animated">
|
||||
<Script
|
||||
id={"structured-data"}
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Dennis Konkol",
|
||||
"url": "https://dki.one",
|
||||
"jobTitle": "Software Engineer",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "Osnabrück",
|
||||
"addressCountry": "Germany",
|
||||
},
|
||||
"sameAs": [
|
||||
"https://github.com/Denshooter",
|
||||
"https://linkedin.com/in/dkonkol",
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Header/>
|
||||
<div className="h-10"></div>
|
||||
<main>
|
||||
|
||||
66
app/privacy-policy/page.tsx
Normal file
66
app/privacy-policy/page.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Header from "../components/Header";
|
||||
import Footer_Back from "../components/Footer_Back";
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 350);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen flex flex-col bg-radiant-animated ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<Header/>
|
||||
<div className="h-10"></div>
|
||||
<main className="flex-grow p-10">
|
||||
<h1 className="text-3xl font-bold">Privacy Policy</h1>
|
||||
<p className="mt-4">
|
||||
This Privacy Policy explains how I collect, use, and protect your information when you use my
|
||||
website.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Information We Collect</h2>
|
||||
<p className="mt-2">
|
||||
I use Vercel's Speed Insights and Web Analytics to collect information about the usage of my
|
||||
website.
|
||||
This includes data such as page views, time spent on pages, interaction metrics, and performance
|
||||
insights.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">How I Use Your Information</h2>
|
||||
<p className="mt-2">
|
||||
The information collected is used to improve the performance, usability, and user experience of my
|
||||
website.
|
||||
I analyze this data to optimize content, and enhance site performance.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Third-Party Services</h2>
|
||||
<p className="mt-2">
|
||||
Vercel may process collected data in accordance with their own privacy policies. Please review
|
||||
<a href="https://vercel.com/legal/privacy-policy" className="text-blue-500 underline"> Vercel's
|
||||
Privacy Policy</a> for more details.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Data Protection</h2>
|
||||
<p className="mt-2">
|
||||
I take data security seriously. The information collected through Vercel's Speed Insights and Web
|
||||
Analytics
|
||||
is securely stored and only accessible to me.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Your Rights</h2>
|
||||
<p className="mt-2">
|
||||
You have the right to request information about your data, ask for corrections, or request its
|
||||
deletion.
|
||||
If you wish to exercise these rights, please contact me.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Contact Us</h2>
|
||||
<p className="mt-2">
|
||||
If you have any questions about this Privacy Policy, please contact me at info@dki.one or use the
|
||||
contact form.
|
||||
</p>
|
||||
</main>
|
||||
<Footer_Back/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
app/sitemap.tsx
Normal file
31
app/sitemap.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import {MetadataRoute} from "next";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = "https://dki.one";
|
||||
|
||||
// Static pages
|
||||
const staticRoutes = [
|
||||
{url: `${baseUrl}/`, lastModified: new Date().toISOString()},
|
||||
{url: `${baseUrl}/privacy-policy`, lastModified: new Date().toISOString()},
|
||||
];
|
||||
|
||||
// Read project markdown files from the public folder
|
||||
const projectsDirectory = path.join(process.cwd(), "public/projects");
|
||||
let projectRoutes: { url: string; lastModified: string; }[] = [];
|
||||
|
||||
if (fs.existsSync(projectsDirectory)) {
|
||||
const projectFiles = fs.readdirSync(projectsDirectory).filter(file => file.endsWith(".md"));
|
||||
|
||||
projectRoutes = projectFiles.map((file) => {
|
||||
const slug = file.replace(".md", "");
|
||||
return {
|
||||
url: `${baseUrl}/projects/${slug}`,
|
||||
lastModified: new Date().toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return [...staticRoutes, ...projectRoutes];
|
||||
}
|
||||
304
package-lock.json
generated
304
package-lock.json
generated
@@ -9,10 +9,14 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@vercel/analytics": "^1.4.1",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@vercel/speed-insights": "^1.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"next": "15.1.3",
|
||||
"prisma": "^6.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-cookie-consent": "^9.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^9.0.3",
|
||||
"rehype-raw": "^7.0.0",
|
||||
@@ -949,6 +953,15 @@
|
||||
"@prisma/debug": "6.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@resvg/resvg-wasm": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.0.tgz",
|
||||
"integrity": "sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==",
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@rtsao/scc": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||
@@ -963,6 +976,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@shuding/opentype.js": {
|
||||
"version": "1.4.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
||||
"integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fflate": "^0.7.3",
|
||||
"string.prototype.codepointat": "^0.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"ot": "bin/ot"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@@ -1317,6 +1346,93 @@
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"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": {
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.6.5.tgz",
|
||||
"integrity": "sha512-GFXtgid3+TcVHTd668a10vGpzAh4Ty/yBZPRxKf1UicI8Vi8EthfvSxcaLW0KvQBBe1+d7TcjecLZHRT8JzQ4g==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@resvg/resvg-wasm": "2.4.0",
|
||||
"satori": "0.12.1",
|
||||
"yoga-wasm-web": "0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"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": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
@@ -1658,6 +1774,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -1776,6 +1901,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001696",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz",
|
||||
@@ -1938,7 +2072,6 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
@@ -1994,6 +2127,47 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-background-parser": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
|
||||
"integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-box-shadow": {
|
||||
"version": "1.0.0-3",
|
||||
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
|
||||
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-gradient-parser": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz",
|
||||
"integrity": "sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -2432,6 +2606,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
@@ -3003,6 +3183,12 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -3607,6 +3793,18 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hex-rgb": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
|
||||
"integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -4226,6 +4424,12 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -4362,6 +4566,16 @@
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/linebreak": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "0.0.8",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
@@ -5686,6 +5900,12 @@
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -5699,6 +5919,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-css-color": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
|
||||
"integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.1.4",
|
||||
"hex-rgb": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-entities": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
|
||||
@@ -5976,7 +6206,6 @@
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
@@ -6078,6 +6307,21 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-cookie-consent": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie-consent/-/react-cookie-consent-9.0.0.tgz",
|
||||
"integrity": "sha512-Blyj+m+Zz7SFHYqT18p16EANgnSg2sIyU6Yp3vk83AnOnSW7qnehPkUe4+8+qxztJrNmCH5GP+VHsWzAKVOoZA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-cookie": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
@@ -6402,6 +6646,34 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/satori": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/satori/-/satori-0.12.1.tgz",
|
||||
"integrity": "sha512-0SbjchvDrDbeXeQgxWVtSWxww7qcFgk3DtSE2/blHOSlLsSHwIqO2fCrtVa/EudJ7Eqno8A33QNx56rUyGbLuw==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@shuding/opentype.js": "1.4.0-beta.0",
|
||||
"css-background-parser": "^0.1.0",
|
||||
"css-box-shadow": "1.0.0-3",
|
||||
"css-gradient-parser": "^0.0.16",
|
||||
"css-to-react-native": "^3.0.0",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"linebreak": "^1.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"yoga-wasm-web": "^0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/satori/node_modules/emoji-regex": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
@@ -6749,6 +7021,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.codepointat": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
||||
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string.prototype.includes": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
||||
@@ -7130,6 +7408,12 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -7333,6 +7617,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unified": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
||||
@@ -7724,6 +8018,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-wasm-web": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
|
||||
"integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||
|
||||
10
package.json
10
package.json
@@ -10,14 +10,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@vercel/analytics": "^1.4.1",
|
||||
"@vercel/speed-insights": "^1.1.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"react-cookie-consent": "^9.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"next": "15.1.3",
|
||||
"prisma": "^6.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^9.0.3",
|
||||
"gray-matter": "^4.0.3",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"rehype-raw": "^7.0.0"
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 MiB |
3
public/robots.txt
Normal file
3
public/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://dki.one/sitemap.xml
|
||||
Reference in New Issue
Block a user