Init
This commit is contained in:
parent
27189d8a22
commit
ee9f04bf46
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/certbot
|
10
api/Dockerfile
Normal file
10
api/Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY server.py .
|
||||||
|
|
||||||
|
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]
|
2
api/requirements.txt
Normal file
2
api/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fastapi>=0.68.0
|
||||||
|
uvicorn>=0.15.0
|
38
api/server.py
Normal file
38
api/server.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import socket
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Configure CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def check_port(host: str, port: int, timeout: float = 2.0) -> bool:
|
||||||
|
try:
|
||||||
|
# Create async socket connection
|
||||||
|
reader, writer = await asyncio.wait_for(
|
||||||
|
asyncio.open_connection(host, port),
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
return True
|
||||||
|
except (socket.gaierror, ConnectionRefusedError, asyncio.TimeoutError):
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@app.get("/check-port")
|
||||||
|
async def port_check(host: str, port: int):
|
||||||
|
is_alive = await check_port(host, port)
|
||||||
|
return {"status": "online" if is_alive else "offline"}
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "ok"}
|
41
compose.yml
Normal file
41
compose.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: nginx
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||||
|
- ./html:/usr/share/nginx/html
|
||||||
|
- ./certbot/www:/var/www/certbot
|
||||||
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
|
networks:
|
||||||
|
- web-network
|
||||||
|
depends_on:
|
||||||
|
- certbot
|
||||||
|
- port-checker
|
||||||
|
|
||||||
|
certbot:
|
||||||
|
image: certbot/certbot
|
||||||
|
container_name: certbot
|
||||||
|
volumes:
|
||||||
|
- ./certbot/www:/var/www/certbot
|
||||||
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
|
networks:
|
||||||
|
- web-network
|
||||||
|
command: certonly --webroot --webroot-path=/var/www/certbot --email p.keier@beyerstedt-it.de --agree-tos --no-eff-email --force-renewal -d garde-studios.de -d www.garde-studios.de
|
||||||
|
|
||||||
|
port-checker:
|
||||||
|
container_name: port-checker
|
||||||
|
build: ./api
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
networks:
|
||||||
|
- web-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web-network:
|
||||||
|
driver: bridge
|
145
html/css/catppuccin.css
Normal file
145
html/css/catppuccin.css
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
[data-theme="latte"] {
|
||||||
|
--theme-rosewater: #dc8a78;
|
||||||
|
--theme-flamingo: #dd7878;
|
||||||
|
--theme-pink: #ea76cb;
|
||||||
|
--theme-mauve: #8839ef;
|
||||||
|
--theme-red: #d20f39;
|
||||||
|
--theme-maroon: #e64553;
|
||||||
|
--theme-peach: #fe640b;
|
||||||
|
--theme-yellow: #df8e1d;
|
||||||
|
--theme-green: #40a02b;
|
||||||
|
--theme-teal: #179299;
|
||||||
|
--theme-sky: #04a5e5;
|
||||||
|
--theme-sapphire: #209fb5;
|
||||||
|
--theme-blue: #1e66f5;
|
||||||
|
--theme-lavender: #7287fd;
|
||||||
|
--theme-text: #4c4f69;
|
||||||
|
--theme-subtext1: #5c5f77;
|
||||||
|
--theme-subtext0: #6c6f85;
|
||||||
|
--theme-overlay2: #7c7f93;
|
||||||
|
--theme-overlay1: #8c8fa1;
|
||||||
|
--theme-overlay0: #9ca0b0;
|
||||||
|
--theme-surface2: #acb0be;
|
||||||
|
--theme-surface1: #bcc0cc;
|
||||||
|
--theme-surface0: #ccd0da;
|
||||||
|
--theme-base: #eff1f5;
|
||||||
|
--theme-mantle: #e6e9ef;
|
||||||
|
--theme-crust: #dce0e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="frappe"] {
|
||||||
|
--theme-rosewater: #f2d5cf;
|
||||||
|
--theme-flamingo: #eebebe;
|
||||||
|
--theme-pink: #f4b8e4;
|
||||||
|
--theme-mauve: #ca9ee6;
|
||||||
|
--theme-red: #e78284;
|
||||||
|
--theme-maroon: #ea999c;
|
||||||
|
--theme-peach: #ef9f76;
|
||||||
|
--theme-yellow: #e5c890;
|
||||||
|
--theme-green: #a6d189;
|
||||||
|
--theme-teal: #81c8be;
|
||||||
|
--theme-sky: #99d1db;
|
||||||
|
--theme-sapphire: #85c1dc;
|
||||||
|
--theme-blue: #8caaee;
|
||||||
|
--theme-lavender: #babbf1;
|
||||||
|
--theme-text: #c6d0f5;
|
||||||
|
--theme-subtext1: #b5bfe2;
|
||||||
|
--theme-subtext0: #a5adce;
|
||||||
|
--theme-overlay2: #949cbb;
|
||||||
|
--theme-overlay1: #838ba7;
|
||||||
|
--theme-overlay0: #737994;
|
||||||
|
--theme-surface2: #626880;
|
||||||
|
--theme-surface1: #51576d;
|
||||||
|
--theme-surface0: #414559;
|
||||||
|
--theme-base: #303446;
|
||||||
|
--theme-mantle: #292c3c;
|
||||||
|
--theme-crust: #232634;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="macchiato"] {
|
||||||
|
--theme-rosewater: #f4dbd6;
|
||||||
|
--theme-flamingo: #f0c6c6;
|
||||||
|
--theme-pink: #f5bde6;
|
||||||
|
--theme-mauve: #c6a0f6;
|
||||||
|
--theme-red: #ed8796;
|
||||||
|
--theme-maroon: #ee99a0;
|
||||||
|
--theme-peach: #f5a97f;
|
||||||
|
--theme-yellow: #eed49f;
|
||||||
|
--theme-green: #a6da95;
|
||||||
|
--theme-teal: #8bd5ca;
|
||||||
|
--theme-sky: #91d7e3;
|
||||||
|
--theme-sapphire: #7dc4e4;
|
||||||
|
--theme-blue: #8aadf4;
|
||||||
|
--theme-lavender: #b7bdf8;
|
||||||
|
--theme-text: #cad3f5;
|
||||||
|
--theme-subtext1: #b8c0e0;
|
||||||
|
--theme-subtext0: #a5adcb;
|
||||||
|
--theme-overlay2: #939ab7;
|
||||||
|
--theme-overlay1: #8087a2;
|
||||||
|
--theme-overlay0: #6e738d;
|
||||||
|
--theme-surface2: #5b6078;
|
||||||
|
--theme-surface1: #494d64;
|
||||||
|
--theme-surface0: #363a4f;
|
||||||
|
--theme-base: #24273a;
|
||||||
|
--theme-mantle: #1e2030;
|
||||||
|
--theme-crust: #181926;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="mocha"] {
|
||||||
|
--theme-rosewater: #f5e0dc;
|
||||||
|
--theme-flamingo: #f2cdcd;
|
||||||
|
--theme-pink: #f5c2e7;
|
||||||
|
--theme-mauve: #cba6f7;
|
||||||
|
--theme-red: #f38ba8;
|
||||||
|
--theme-maroon: #eba0ac;
|
||||||
|
--theme-peach: #fab387;
|
||||||
|
--theme-yellow: #f9e2af;
|
||||||
|
--theme-green: #a6e3a1;
|
||||||
|
--theme-teal: #94e2d5;
|
||||||
|
--theme-sky: #89dceb;
|
||||||
|
--theme-sapphire: #74c7ec;
|
||||||
|
--theme-blue: #89b4fa;
|
||||||
|
--theme-lavender: #b4befe;
|
||||||
|
--theme-text: #cdd6f4;
|
||||||
|
--theme-subtext1: #bac2de;
|
||||||
|
--theme-subtext0: #a6adc8;
|
||||||
|
--theme-overlay2: #9399b2;
|
||||||
|
--theme-overlay1: #7f849c;
|
||||||
|
--theme-overlay0: #6c7086;
|
||||||
|
--theme-surface2: #585b70;
|
||||||
|
--theme-surface1: #45475a;
|
||||||
|
--theme-surface0: #313244;
|
||||||
|
--theme-base: #1e1e2e;
|
||||||
|
--theme-mantle: #181825;
|
||||||
|
--theme-crust: #11111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color indicators for each theme */
|
||||||
|
[data-theme-color="latte"] { background: var(--theme-red); }
|
||||||
|
[data-theme-color="frappe"] { background: var(--theme-yellow); }
|
||||||
|
[data-theme-color="macchiato"] { background: var(--theme-green); }
|
||||||
|
[data-theme-color="mocha"] { background: var(--theme-blue); }
|
||||||
|
|
||||||
|
/* Theme Switcher Styles */
|
||||||
|
.theme-switcher {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option .active {
|
||||||
|
border-color: var(--theme-text);
|
||||||
|
}
|
224
html/css/style.css
Normal file
224
html/css/style.css
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 80px; /* Offset for fixed navbar */
|
||||||
|
background-color: var(--theme-base);
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--theme-red);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--theme-peach);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar Styles */
|
||||||
|
.navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--theme-base);
|
||||||
|
box-shadow: 0 2px 10px var(--theme-blue);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--theme-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo:hover {
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links ul {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a {
|
||||||
|
color: var(--theme-sky);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links a:hover {
|
||||||
|
color: var(--theme-lavender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Menu */
|
||||||
|
.hamburger {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger span {
|
||||||
|
display: block;
|
||||||
|
width: 25px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--theme-blue);
|
||||||
|
margin: 5px 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hero Section */
|
||||||
|
.hero {
|
||||||
|
height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.nav-links {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 80px);
|
||||||
|
background-color: var(--theme-base);
|
||||||
|
transition: left 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links.active {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links ul {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.active span:nth-child(1) {
|
||||||
|
transform: rotate(45deg) translate(5px, 5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.active span:nth-child(2) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.active span:nth-child(3) {
|
||||||
|
transform: rotate(-45deg) translate(5px, -5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Styles */
|
||||||
|
.section {
|
||||||
|
padding: 5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Styling */
|
||||||
|
#games .container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Server Card Styling */
|
||||||
|
.server-card {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--theme-base);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px var(--theme-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card h3 {
|
||||||
|
color: var(--theme-text);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Server Info Styling (keep your existing styles) */
|
||||||
|
.server-info {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info p {
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
margin: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
margin: 12px 0;
|
||||||
|
color: var(--theme-subtext0);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status indicators (keep your existing styles) */
|
||||||
|
.status.online {
|
||||||
|
color: var(--theme-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.offline {
|
||||||
|
color: var(--theme-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.unknown {
|
||||||
|
color: var(--theme-yellow);
|
||||||
|
}
|
151
html/index.html
Normal file
151
html/index.html
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Garde Studios</title>
|
||||||
|
<link rel="stylesheet" href="css/catppuccin.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="navbar">
|
||||||
|
<div class="container">
|
||||||
|
<a href="/" class="logo">Garde Studios</a>
|
||||||
|
<nav class="nav-links">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#games">Games</a></li>
|
||||||
|
<li>
|
||||||
|
<div class="theme-switcher">
|
||||||
|
<div class="theme-option active" data-theme-color="latte" data-theme="latte"></div>
|
||||||
|
<div class="theme-option" data-theme-color="frappe" data-theme="frappe"></div>
|
||||||
|
<div class="theme-option" data-theme-color="macchiato" data-theme="macchiato"></div>
|
||||||
|
<div class="theme-option" data-theme-color="mocha" data-theme="mocha"></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<button class="hamburger" aria-label="Menu">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Previous content remains until <main> -->
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!--<section class="hero">
|
||||||
|
<h1>Garde Studios</h1>
|
||||||
|
<p>Digital experiences reimagined</p>
|
||||||
|
</section>-->
|
||||||
|
|
||||||
|
<section id="games" class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Games @ Garde Studios</h2>
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="1234">
|
||||||
|
<h3>OpenRA - Red Alert</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die Alliierten vertrauen auf fortschrittliche Technologie und schnelle Aufklärung, während die Sowjets mit roher Gewalt und überwältigenden Zahlen dominieren – in diesem zeitlosen Konflikt entscheiden Taktik und Ressourcenkontrolle über den Sieg.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://www.openra.net/">openra.net</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">1234</span></p>
|
||||||
|
|
||||||
|
<p><span class="arrow">>></span> Password: <span class="highlight">garde-studios</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="1235">
|
||||||
|
<h3>OpenRA - Command & Conquer: Tiberian Dawn</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die GDI setzen auf hochwertige Technologie und schwere Panzerung, während die Bruderschaft von Nod mit Guerilla-Taktik und Tiberium-Waffen kämpft – doch beide Fraktionen müssen sich gegen die tödliche Verseuchung des Tiberiums behaupten.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://www.openra.net/">openra.net</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">1235</span></p>
|
||||||
|
|
||||||
|
<p><span class="arrow">>></span> Password: <span class="highlight">garde-studios</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="1236">
|
||||||
|
<h3>OpenRA - Dune 2000</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die Atreides setzen auf Elite-Truppen und Präzision, die Harkonnen überrollen Feinde mit brutaler Feuerkraft, und die Ordos kämpfen mit hinterhältigen Söldnertaktiken – doch alle müssen um das Melange kämpfen, die Quelle der Macht auf Arrakis.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://www.openra.net/">openra.net</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">1236</span></p>
|
||||||
|
|
||||||
|
<p><span class="arrow">>></span> Password: <span class="highlight">garde-studios</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="14004">
|
||||||
|
<h3>Veloren</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die Abenteurer erkunden eine pixelige Fantasy-Welt mit komplexen Kämpfen und tiefen Handwerkssystemen, während dunkele Kreaturen und tödliche Höhlen auf unvorsichtige Helden lauern – doch der größte Feind ist oft die eigene Gier nach legendärer Beute.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://www.veloren.net/">veloren.net</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">14004</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="34197">
|
||||||
|
<h3>Factorio (No Space Age DLC)</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die Ingenieure optimieren Fabriken mit Präzision und Logistik, während die Biters mit schierer Zahl und evolutionärer Anpassung drohen – doch das wahre Chaos entsteht, wenn die Produktionsketten ins Stolpern geraten.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://factorio.com/">factorio.com</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">34197</span></p>
|
||||||
|
<p><span class="arrow">>></span> Password: <span class="highlight">garde-studios</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-card" data-ip="garde-studios.de" data-port="16261">
|
||||||
|
<h3>Project Zomboid</h3>
|
||||||
|
<div class="card-description">
|
||||||
|
<p>Die Überlebenden horten Vorräte und planen Fluchtwege, während die Horden durch Geräusche und Blutgeruch angelockt werden – doch der gefährlichste Feind bleibt die eigene Unvorsichtigkeit.</p>
|
||||||
|
</div>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Link: <span class="highlight"><a href="https://projectzomboid.com/">projectzomboid.com</a></span></p>
|
||||||
|
<p><span class="arrow">>></span> Domain/IP: <span class="highlight">garde-studios.de</span></p>
|
||||||
|
<p><span class="arrow">>></span> Port: <span class="highlight">16261</span></p>
|
||||||
|
<div class="server-info">
|
||||||
|
<p><span class="arrow">>></span> Status: <span class="status unknown">Unknown</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
<script src="js/theme-switcher.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
74
html/js/main.js
Normal file
74
html/js/main.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Mobile menu toggle
|
||||||
|
const hamburger = document.querySelector('.hamburger');
|
||||||
|
const navLinks = document.querySelector('.nav-links');
|
||||||
|
|
||||||
|
hamburger.addEventListener('click', function() {
|
||||||
|
this.classList.toggle('active');
|
||||||
|
navLinks.classList.toggle('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking a link (mobile)
|
||||||
|
document.querySelectorAll('.nav-links a').forEach(link => {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
hamburger.classList.remove('active');
|
||||||
|
navLinks.classList.remove('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth scrolling for anchor links
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||||
|
anchor.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const targetId = this.getAttribute('href');
|
||||||
|
if (targetId === '#') return;
|
||||||
|
|
||||||
|
const targetElement = document.querySelector(targetId);
|
||||||
|
if (targetElement) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: targetElement.offsetTop - 80, // Adjusted for fixed header
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const serverCards = document.querySelectorAll('.server-card');
|
||||||
|
const API_URL = '/api/check-port'; // Change in production
|
||||||
|
|
||||||
|
async function checkServerStatus(card) {
|
||||||
|
const ip = card.dataset.ip;
|
||||||
|
const port = card.dataset.port;
|
||||||
|
const statusElement = card.querySelector('.status');
|
||||||
|
|
||||||
|
if (!ip || !port || !statusElement) return;
|
||||||
|
|
||||||
|
statusElement.textContent = 'Checking...';
|
||||||
|
statusElement.className = 'status unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_URL}?host=${encodeURIComponent(ip)}&port=${port}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
statusElement.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1);
|
||||||
|
statusElement.className = `status ${data.status}`;
|
||||||
|
} catch (e) {
|
||||||
|
statusElement.textContent = 'Error';
|
||||||
|
statusElement.className = 'status unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
serverCards.forEach(checkServerStatus);
|
||||||
|
|
||||||
|
// Click handler for manual refresh
|
||||||
|
serverCards.forEach(card => {
|
||||||
|
card.addEventListener('click', () => checkServerStatus(card));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Periodic checks every 5 minutes
|
||||||
|
setInterval(() => serverCards.forEach(checkServerStatus), 5 * 60 * 1000);
|
||||||
|
});
|
34
html/js/theme-switcher.js
Normal file
34
html/js/theme-switcher.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const themeOptions = document.querySelectorAll('.theme-option');
|
||||||
|
const html = document.documentElement;
|
||||||
|
|
||||||
|
// Set initial theme from localStorage or default to latte
|
||||||
|
const savedTheme = localStorage.getItem('catppuccin-theme') || 'latte';
|
||||||
|
html.setAttribute('data-theme', savedTheme);
|
||||||
|
updateActiveTheme(savedTheme);
|
||||||
|
|
||||||
|
// Theme switcher functionality
|
||||||
|
themeOptions.forEach(option => {
|
||||||
|
option.addEventListener('click', () => {
|
||||||
|
const theme = option.dataset.theme;
|
||||||
|
html.setAttribute('data-theme', theme);
|
||||||
|
localStorage.setItem('catppuccin-theme', theme);
|
||||||
|
updateActiveTheme(theme);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update active theme indicator
|
||||||
|
function updateActiveTheme(theme) {
|
||||||
|
themeOptions.forEach(opt => {
|
||||||
|
const isActive = opt.dataset.theme === theme;
|
||||||
|
opt.classList.toggle('active', isActive);
|
||||||
|
|
||||||
|
// Update border color immediately
|
||||||
|
if (isActive) {
|
||||||
|
opt.style.borderColor = `var(--theme-mantle)`;
|
||||||
|
} else {
|
||||||
|
opt.style.borderColor = 'transparent';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
58
nginx/conf.d/default.conf
Normal file
58
nginx/conf.d/default.conf
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name garde-studios.de www.garde-studios.de;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
charset_types text/html text/css application/javascript text/plain text/xml;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name garde-studios.de www.garde-studios.de;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
charset_types text/html text/css application/javascript text/plain text/xml;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/garde-studios.de/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/garde-studios.de/privkey.pem;
|
||||||
|
|
||||||
|
# SSL configuration
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
|
||||||
|
ssl_ecdh_curve secp384r1;
|
||||||
|
ssl_session_timeout 10m;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
ssl_stapling on;
|
||||||
|
ssl_stapling_verify on;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# API reverse proxy
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://port-checker:8000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# WebSocket support (if needed in future)
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Frontend static files
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user