first commit

This commit is contained in:
WinniePatGG
2026-05-03 18:35:00 +00:00
commit d4397263ee
9 changed files with 1854 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# My Profile Website Repository
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

+382
View File
@@ -0,0 +1,382 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Winnie | Developer & Gamer</title>
<meta name="description" content="Hi, I'm Winnie - a passionate developer building digital experiences with code and creativity. Check out my projects and get in touch!"/>
<meta property="og:title" content="Winnie | Developer & Gamer" />
<meta property="og:description" content="Building digital experiences with code and creativity. Passionate about crafting elegant solutions and open-source projects."/>
<meta property="og:url" content="https://winniepat.de" />
<link rel="canonical" href="https://winniepat.de" />
<link rel="icon" href="favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css" />
</head>
<body class="noise-bg">
<div class="animated-background" aria-hidden="true">
<div class="bg-grid"></div>
<div class="orb orb-primary"></div>
<div class="orb orb-accent"></div>
<div class="orb orb-primary-small"></div>
<div class="particles" id="particles"></div>
</div>
<header class="site-header" id="site-header">
<div class="container header-inner">
<a class="logo" href="#">
<img class="avatar" src="profile-image.png" alt="Winnie" />
<span>winnie</span>
</a>
<nav class="nav-links" aria-label="Primary">
<a href="#about">About</a>
<a href="#projects">Projects</a>
<a href="#skills">Skills</a>
<a href="#contact">Contact</a>
</nav>
<a class="btn btn-primary desktop-only" href="#contact">Get in touch</a>
<button
class="menu-toggle"
id="menu-toggle"
aria-expanded="false"
aria-controls="mobile-menu"
>
<span class="sr-only">Toggle menu</span>
<i class="icon" data-lucide="menu"></i>
<i class="icon icon-close" data-lucide="x"></i>
</button>
</div>
<div class="mobile-menu" id="mobile-menu">
<nav class="mobile-nav" aria-label="Mobile">
<a href="#about">About</a>
<a href="#projects">Projects</a>
<a href="#skills">Skills</a>
<a href="#contact">Contact</a>
<a class="btn btn-primary" href="#contact">Get in touch</a>
</nav>
</div>
</header>
<main>
<section class="hero" id="top">
<div class="container hero-inner">
<div class="hero-content">
<h1 class="hero-title reveal" data-reveal style="--delay: 0s;">
Hey, I'm <span class="text-gradient">Winnie</span>
</h1>
<p class="hero-subtitle reveal" data-reveal style="--delay: 0.1s;">
<span class="mono text-primary">&lt;</span>
Developer &amp; Gamer
<span class="mono text-primary">/&gt;</span>
</p>
<p class="hero-description reveal" data-reveal style="--delay: 0.2s;">
Building cool software with code and creativity. Passionate about
learning new things and open-source projects.
</p>
<div class="hero-social reveal" data-reveal style="--delay: 0.3s;">
<a
class="social-link social-link--github"
href="https://git.winniepat.de/winnie"
target="_blank"
rel="noopener noreferrer"
aria-label="Gitea"
>
<i class="icon" data-lucide="git-branch"></i>
</a>
<a
class="social-link"
href="mailto:winniepatgg@web.de"
aria-label="Email"
>
<i class="icon" data-lucide="mail"></i>
</a>
</div>
</div>
<a class="scroll-indicator" href="#projects">
<span class="mono">scroll</span>
<i class="icon" data-lucide="arrow-down"></i>
</a>
</div>
</section>
<section class="section" id="about">
<div class="container">
<div class="section-header reveal" data-reveal>
<span class="mono text-primary">// about me</span>
<h2>Who <span class="text-gradient">I Am</span></h2>
</div>
<div class="about-grid">
<div class="about-text reveal" data-reveal style="--delay: 0.1s;">
<p>
Hi! I'm a developer who loves turning ideas into reality through
code. I specialize in building minecraft plugins and web
applications with a focus on user experiences.
</p>
<p>
When I'm not coding, you'll find me exploring new repositories,
pushing to docker hub, or enjoying a good can of monster while
brainstorming the next big idea.
</p>
<p>
I believe in writing code that not only works but is also clean,
scalable, and elegant. Every project is an opportunity to learn
and grow.
</p>
<div class="facts">
<span class="tag">
<i class="icon" data-lucide="map-pin"></i>
Based in Germany
</span>
<span class="tag">
<i class="icon" data-lucide="coffee"></i>
Monster Energy Enthusiast
</span>
<span class="tag">
<i class="icon" data-lucide="sparkles"></i>
Open Source Lover
</span>
</div>
</div>
<div class="stats-card reveal" data-reveal style="--delay: 0.2s;">
<div class="stats-grid">
<div class="stat">
<div class="stat-value text-gradient">2+</div>
<div class="stat-label">Years Coding</div>
</div>
<div class="stat">
<div class="stat-value text-gradient" id="stat-repos">...</div>
<div class="stat-label">Projects</div>
</div>
<div class="stat">
<div class="stat-value text-gradient" id="stat-commits">
...
</div>
<div class="stat-label">Commits</div>
</div>
<div class="stat">
<div class="stat-value text-gradient">&infin;</div>
<div class="stat-label">Monster Energy</div>
</div>
</div>
<div class="terminal">
<div class="terminal-bar">
<span class="dot dot-red"></span>
<span class="dot dot-yellow"></span>
<span class="dot dot-green"></span>
</div>
<code>
<span class="text-primary">const</span> winnie = {
<br />
&nbsp;&nbsp;passion:
<span class="terminal-string">"coding"</span>,
<br />
&nbsp;&nbsp;focus:
<span class="terminal-string">"web dev"</span>,
<br />
&nbsp;&nbsp;learning:
<span class="terminal-boolean">true</span>
<br />
};
</code>
</div>
</div>
</div>
</div>
</section>
<section class="section" id="projects">
<div class="container">
<div class="section-header reveal" data-reveal>
<span class="mono text-primary">// my work</span>
<h2>Open Source <span class="text-gradient">Projects</span></h2>
<p class="section-subtitle">
A collection of my public repositories on Gitea
</p>
</div>
<div class="repos-state" id="repos-loading">
<div class="spinner"></div>
</div>
<div class="repos-state hidden" id="repos-error">
<p>Unable to load repositories. Please try again later.</p>
</div>
<div class="repo-grid" id="repo-grid"></div>
<div class="section-footer reveal" data-reveal style="--delay: 0.2s;">
<a
class="btn btn-ghost"
href="https://git.winniepat.de/winnie"
target="_blank"
rel="noopener noreferrer"
>
View all on Gitea
<i class="icon" data-lucide="external-link"></i>
</a>
</div>
</div>
</section>
<section class="section" id="skills">
<div class="container">
<div class="section-header reveal" data-reveal>
<span class="mono text-primary">// skills &amp; tools</span>
<h2>Tech <span class="text-gradient">Stack</span></h2>
<p class="section-subtitle">
Technologies and tools I work with daily
</p>
</div>
<div class="skills-grid">
<div class="skill-card reveal" data-reveal>
<div class="skill-header">
<span class="skill-icon">
<i class="icon" data-lucide="globe"></i>
</span>
<h3>Frontend</h3>
</div>
<ul>
<li>HTML</li>
<li>CSS</li>
<li>Next.js</li>
</ul>
</div>
<div class="skill-card reveal" data-reveal style="--delay: 0.05s;">
<div class="skill-header">
<span class="skill-icon">
<i class="icon" data-lucide="database"></i>
</span>
<h3>Backend</h3>
</div>
<ul>
<li>Node.js</li>
<li>Python</li>
<li>Express.js</li>
<li>Flask</li>
</ul>
</div>
<div class="skill-card reveal" data-reveal style="--delay: 0.1s;">
<div class="skill-header">
<span class="skill-icon">
<i class="icon" data-lucide="cloud"></i>
</span>
<h3>DevOps</h3>
</div>
<ul>
<li>Docker</li>
<li>Portainer</li>
<li>Nginx</li>
<li>Dash.</li>
<li>Ubuntu</li>
</ul>
</div>
<div class="skill-card reveal" data-reveal style="--delay: 0.15s;">
<div class="skill-header">
<span class="skill-icon">
<i class="icon" data-lucide="terminal"></i>
</span>
<h3>Tools</h3>
</div>
<ul>
<li>Git</li>
<li>VSCode</li>
<li>Webstorm</li>
<li>Intellij</li>
<li>Termius</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section" id="contact">
<div class="container">
<div class="section-header reveal" data-reveal>
<span class="mono text-primary">// get in touch</span>
<h2>Let's <span class="text-gradient">Connect</span></h2>
<p class="section-subtitle">
Have a project in mind or just want to say hello? I'm always open
to discussing new opportunities and ideas.
</p>
</div>
<div class="cta-actions reveal" data-reveal style="--delay: 0.1s;">
<a class="btn btn-primary" href="mailto:winniepatgg@web.de">
<i class="icon" data-lucide="mail"></i>
Send an Email
<i class="icon icon-sm" data-lucide="send"></i>
</a>
<a
class="btn btn-outline btn-github"
href="https://git.winniepat.de/winnie"
target="_blank"
rel="noopener noreferrer"
>
<i class="icon" data-lucide="git-branch"></i>
Follow on Gitea
</a>
</div>
<div class="divider reveal" data-reveal style="--delay: 0.2s;"></div>
<div class="email-pill reveal" data-reveal style="--delay: 0.3s;">
<i class="icon" data-lucide="message-square"></i>
<span class="mono">winniepatgg@web.de</span>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-content">
<div class="footer-brand">
<img class="avatar avatar--sm" src="profile-image.png" alt="Winnie" />
<span>winniepat.de</span>
</div>
<nav class="footer-links" aria-label="Footer">
<a href="https://status.winniepat.de">Status</a>
<a href="https://server.winniepat.de">Server Load</a>
</nav>
<div class="footer-social">
<a
href="https://git.winniepat.de/winnie"
target="_blank"
rel="noopener noreferrer"
aria-label="Gitea"
class="footer-social-link footer-social-link--github"
>
<i class="icon" data-lucide="git-branch"></i>
</a>
<a
href="mailto:winniepatgg@web.de"
aria-label="Email"
class="footer-social-link"
>
<i class="icon" data-lucide="mail"></i>
</a>
</div>
</div>
<div class="footer-bottom">
<p>
&copy; <span id="current-year"></span> WinniePatGG. Built with 🍯 and lots of Monster Energy.
</p>
</div>
</footer>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="main.js"></script>
</body>
</html>
+358
View File
@@ -0,0 +1,358 @@
const LANGUAGE_COLORS = {
TypeScript: "#3b82f6",
JavaScript: "#facc15",
Python: "#22c55e",
Rust: "#f97316",
Go: "#22d3ee",
Java: "#ef4444",
HTML: "#f97316",
CSS: "#a855f7",
Vue: "#10b981",
React: "#22d3ee",
};
const GITEA_BASE_URL = "https://git.winniepat.de";
const GITEA_USER = "winnie";
const GITEA_FEED_URL = "/gitea-feed";
const GITEA_REPO_FEED_BASE = "/gitea-repo-feed";
const FALLBACK_STATS = { totalRepos: 10, totalCommits: 500 };
const initNavbar = () => {
const header = document.getElementById("site-header");
if (!header) return;
const updateHeader = () => {
header.classList.toggle("scrolled", window.scrollY > 50);
};
updateHeader();
window.addEventListener("scroll", updateHeader);
};
const initMobileMenu = () => {
const toggle = document.getElementById("menu-toggle");
const menu = document.getElementById("mobile-menu");
if (!toggle || !menu) return;
const closeMenu = () => {
document.body.classList.remove("menu-open");
menu.classList.remove("open");
toggle.setAttribute("aria-expanded", "false");
};
toggle.addEventListener("click", () => {
const isOpen = document.body.classList.toggle("menu-open");
menu.classList.toggle("open", isOpen);
toggle.setAttribute("aria-expanded", String(isOpen));
});
menu.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", closeMenu);
});
};
const initParticles = () => {
const particleWrap = document.getElementById("particles");
if (!particleWrap) return;
for (let i = 0; i < 20; i += 1) {
const dot = document.createElement("span");
dot.style.left = `${Math.random() * 100}%`;
dot.style.top = `${Math.random() * 100}%`;
dot.style.animationDuration = `${3 + Math.random() * 4}s`;
dot.style.animationDelay = `${Math.random() * 2}s`;
particleWrap.appendChild(dot);
}
};
const initReveal = () => {
const elements = document.querySelectorAll("[data-reveal]");
if (!elements.length) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
elements.forEach((el) => observer.observe(el));
};
const initCardGlow = () => {
document.querySelectorAll(".card-glow").forEach((card) => {
card.addEventListener("mousemove", (event) => {
const rect = card.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
card.style.setProperty("--mouse-x", `${x}px`);
card.style.setProperty("--mouse-y", `${y}px`);
});
});
};
const updateYear = () => {
const year = document.getElementById("current-year");
if (year) {
year.textContent = new Date().getFullYear();
}
};
const toCount = (value) => {
if (value === null || value === undefined) return null;
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue : null;
};
const htmlToText = (value) => {
if (!value) return "";
const textarea = document.createElement("textarea");
textarea.innerHTML = value;
const decoded = textarea.value;
const doc = new DOMParser().parseFromString(decoded, "text/html");
return doc.body.textContent || "";
};
const stripSha = (value) =>
value.replace(/\b[0-9a-f]{7,40}\b/gi, "").replace(/\s+/g, " ").trim();
const isActivityText = (value) =>
/created branch|created repository|pushed to|opened pull request|merged pull request|opened issue|closed issue/i.test(
value
);
const getItemText = (item, tagName) => {
const node = item.getElementsByTagName(tagName)[0];
return node ? node.textContent : "";
};
const parseItemDate = (value) => {
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
};
const getRepoInfoFromLink = (link) => {
if (!link) return null;
try {
const url = new URL(link);
const segments = url.pathname.split("/").filter(Boolean);
if (segments.length < 2) return null;
const [owner, repo] = segments;
return {
owner,
repo,
full_name: `${owner}/${repo}`,
html_url: `${GITEA_BASE_URL}/${owner}/${repo}`,
};
} catch (error) {
return null;
}
};
const buildRepoDescription = (item) => {
const rawDescription =
getItemText(item, "content:encoded") || getItemText(item, "description");
const descriptionText = stripSha(htmlToText(rawDescription));
if (descriptionText && !isActivityText(descriptionText)) return descriptionText;
const titleText = stripSha(htmlToText(getItemText(item, "title")));
if (titleText && !isActivityText(titleText)) return titleText;
return "";
};
const getChannelText = (xml, tagName) => {
const channel = xml.getElementsByTagName("channel")[0];
if (!channel) return "";
const node = channel.getElementsByTagName(tagName)[0];
return node ? node.textContent : "";
};
const fetchRepoFeedDescription = async (repoInfo) => {
try {
const repoPath = `${encodeURIComponent(
repoInfo.owner
)}/${encodeURIComponent(repoInfo.repo)}`;
const response = await fetch(`${GITEA_REPO_FEED_BASE}/${repoPath}`);
if (!response.ok) return "";
const xmlText = await response.text();
const xml = new DOMParser().parseFromString(xmlText, "text/xml");
const description = stripSha(htmlToText(getChannelText(xml, "description")));
return description.trim();
} catch (error) {
return "";
}
};
const updateStats = (stats) => {
const reposEl = document.getElementById("stat-repos");
const commitsEl = document.getElementById("stat-commits");
if (reposEl) reposEl.textContent = stats.totalRepos.toString();
if (commitsEl)
commitsEl.textContent = stats.totalCommits.toLocaleString();
};
const createRepoCard = (repo) => {
const card = document.createElement("a");
card.href = repo.html_url;
card.target = "_blank";
card.rel = "noopener noreferrer";
card.className = "repo-card card-glow";
const languageColor = LANGUAGE_COLORS[repo.language] || "#6b7280";
const description = repo.description || "No description available";
const stars = toCount(repo.stars_count ?? repo.stargazers_count);
const forks = toCount(repo.forks_count);
const statsItems = [
stars !== null
? `<span><i class="icon icon-sm" data-lucide="star"></i> ${stars}</span>`
: "",
forks !== null
? `<span><i class="icon icon-sm" data-lucide="git-fork"></i> ${forks}</span>`
: "",
].filter(Boolean);
const statsMarkup = statsItems.length
? `<div class="repo-stats">${statsItems.join("")}</div>`
: "";
const languageMarkup = repo.language
? `<span class="repo-language"><span class="language-dot" style="background:${languageColor}"></span>${repo.language}</span>`
: "";
const metaMarkup =
statsMarkup || languageMarkup
? `<div class="repo-meta">${statsMarkup}${languageMarkup}</div>`
: "";
card.innerHTML = `
<div class="repo-header">
<i class="icon icon-badge" data-lucide="code-2"></i>
<i class="icon icon-fade" data-lucide="external-link"></i>
</div>
<h3>${repo.name}</h3>
<p>${description}</p>
${metaMarkup}
`;
return card;
};
const renderRepos = (repos) => {
const grid = document.getElementById("repo-grid");
const loading = document.getElementById("repos-loading");
const error = document.getElementById("repos-error");
if (!grid || !loading || !error) return;
grid.innerHTML = "";
loading.classList.add("hidden");
error.classList.add("hidden");
repos.forEach((repo) => {
grid.appendChild(createRepoCard(repo));
});
initCardGlow();
if (window.lucide) {
window.lucide.createIcons();
}
};
const renderRepoError = () => {
const loading = document.getElementById("repos-loading");
const error = document.getElementById("repos-error");
if (!loading || !error) return;
loading.classList.add("hidden");
error.classList.remove("hidden");
};
const sortRepos = (repos) =>
repos
.slice()
.sort(
(a, b) => new Date(b.updated_at || 0) - new Date(a.updated_at || 0)
)
.slice(0, 12);
const fetchGiteaFeed = async () => {
const response = await fetch(GITEA_FEED_URL);
if (!response.ok) throw new Error("Failed to fetch feed");
const xmlText = await response.text();
const xml = new DOMParser().parseFromString(xmlText, "text/xml");
const items = Array.from(xml.getElementsByTagName("item"));
if (!items.length) throw new Error("No feed items");
const repoMap = new Map();
let commitCount = 0;
items.forEach((item) => {
const link = getItemText(item, "link");
const titleText = htmlToText(getItemText(item, "title"));
const repoInfo = getRepoInfoFromLink(link);
if (!repoInfo) return;
const pubDate = parseItemDate(getItemText(item, "pubDate")) || new Date(0);
const description = buildRepoDescription(item);
const existing = repoMap.get(repoInfo.full_name);
if (!existing || pubDate > new Date(existing.updated_at || 0)) {
repoMap.set(repoInfo.full_name, {
...repoInfo,
name: repoInfo.repo,
description,
updated_at: pubDate.toISOString(),
});
}
if (/pushed to/i.test(titleText) || /\/commit\//i.test(link)) {
commitCount += 1;
}
});
const repos = Array.from(repoMap.values());
const enrichedRepos = await Promise.all(
repos.map(async (repo) => {
const description = await fetchRepoFeedDescription(repo);
return description ? { ...repo, description } : repo;
})
);
const hasItems = items.length > 0;
const totalRepos =
enrichedRepos.length || (hasItems ? 0 : FALLBACK_STATS.totalRepos);
const totalCommits = hasItems ? commitCount : FALLBACK_STATS.totalCommits;
return {
repos: enrichedRepos,
stats: { totalRepos, totalCommits },
};
};
const loadGiteaFeed = async () => {
try {
const { repos, stats } = await fetchGiteaFeed();
renderRepos(sortRepos(repos));
updateStats(stats);
} catch (error) {
renderRepoError();
updateStats(FALLBACK_STATS);
}
};
const initIcons = () => {
if (window.lucide) {
window.lucide.createIcons();
}
};
document.addEventListener("DOMContentLoaded", () => {
initIcons();
initNavbar();
initMobileMenu();
initParticles();
initReveal();
initCardGlow();
updateYear();
loadGiteaFeed();
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

+14
View File
@@ -0,0 +1,14 @@
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: Twitterbot
Allow: /
User-agent: facebookexternalhit
Allow: /
User-agent: *
Allow: /
+1037
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
services:
web:
image: nginx:alpine
ports:
- "4001:80"
volumes:
- ./app:/usr/share/nginx/html:ro
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
+54
View File
@@ -0,0 +1,54 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /gitea-feed {
proxy_pass https://git.winniepat.de/winnie.rss;
proxy_set_header Host git.winniepat.de;
proxy_ssl_server_name on;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
if ($request_method = OPTIONS) {
return 204;
}
}
location /gitea-repo-feed/ {
rewrite ^/gitea-repo-feed/(.+)$ /$1.rss break;
proxy_pass https://git.winniepat.de;
proxy_set_header Host git.winniepat.de;
proxy_ssl_server_name on;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
if ($request_method = OPTIONS) {
return 204;
}
}
location /gitea-api/ {
proxy_pass https://git.winniepat.de/api/v1/;
proxy_set_header Host git.winniepat.de;
proxy_ssl_server_name on;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
if ($request_method = OPTIONS) {
return 204;
}
}
}