From b4ce1cb6103cc68f74915bfb23a0358118ca3630 Mon Sep 17 00:00:00 2001 From: Patrick <147879351+WinniePatGG@users.noreply.github.com> Date: Fri, 1 May 2026 20:03:11 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + LICENSE | 21 + README.md | 33 ++ app.py | 11 + requirements.txt | 4 + routes/minecraft_routes.py | 112 +++++ routes/server_routes.py | 74 +++ static/login.js | 153 ++++++ static/script.js | 107 +++++ static/signup.js | 89 ++++ static/style.css | 923 +++++++++++++++++++++++++++++++++++++ templates/index.html | 386 ++++++++++++++++ 12 files changed, 1915 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app.py create mode 100644 requirements.txt create mode 100644 routes/minecraft_routes.py create mode 100644 routes/server_routes.py create mode 100644 static/login.js create mode 100644 static/script.js create mode 100644 static/signup.js create mode 100644 static/style.css create mode 100644 templates/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4edd750 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +__pycache__ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..958ab94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 WinniePatGG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e18c157 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Minecraft Server Monitor + +A web application to check the status of Minecraft servers. + +## Public Access +- https://api.winniepat.de +- https://api.winniepat.de/api/status/-ip- + +## Features +- Fast status checking +- Player count and list visualization +- Server version and MOTD display +- Latency measurement +- Responsive design + +## Installation + +### Linux +1. Clone this repository +2. Setup venv: `python -m venv .venv` +3. Install requirements: `.venv/bin/pip install -r requirements.txt` +4. Run the application: `.venv/bin/python app.py` +5. Access at `http://localhost:5000` + +### Windows +1. Clone this repository +2. Setup venv: `python -m venv .venv` +3. Install requirements: `.venv/Scripts/pip install -r requirements.txt` +4. Run the application: `.venv/Scripts/python app.py` +5. Access at `http://localhost:5000` + +## Usage +- Direct access with `http://localhost:5000/api/quick-status/-ip-` diff --git a/app.py b/app.py new file mode 100644 index 0000000..0a9abf2 --- /dev/null +++ b/app.py @@ -0,0 +1,11 @@ +from flask import Flask +from routes.server_routes import routes as server_routes +from routes.minecraft_routes import minecraft_routes + +app = Flask(__name__) + +app.register_blueprint(server_routes) +app.register_blueprint(minecraft_routes) + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9206f90 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask==3.0.0 +mcstatus==11.1.0 +Pillow==11.3.0 +requests==2.32.4 \ No newline at end of file diff --git a/routes/minecraft_routes.py b/routes/minecraft_routes.py new file mode 100644 index 0000000..ae682c4 --- /dev/null +++ b/routes/minecraft_routes.py @@ -0,0 +1,112 @@ +from flask import Blueprint, jsonify, Response, send_file +import requests +from io import BytesIO +from PIL import Image +import base64 +import json + +minecraft_routes = Blueprint('minecraft_routes', __name__) + +ASCII_CHARS = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,^`'. " + +def get_uuid(username): + r = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{username}") + if r.status_code != 200: + return None + return r.json()["id"] + +def get_avatar(uuid): + url = f"https://crafatar.com/avatars/{uuid}?size=512&default=MHF_Steve&overlay" + r = requests.get(url) + if r.status_code != 200: + return None + return Image.open(BytesIO(r.content)) + +def pixel_to_ascii(image): + image = image.convert("L") + pixels = image.getdata() + ascii_str = "" + for pixel in pixels: + ascii_str += ASCII_CHARS[int(pixel / 255 * (len(ASCII_CHARS)-1))] + return ascii_str + +def format_ascii(ascii_str, width): + lines = [ascii_str[i:i+width] for i in range(0, len(ascii_str), width)] + return "\n".join(lines) + +@minecraft_routes.route("/api/v1/ascii/face/") +def avatar_to_ascii(username): + uuid = get_uuid(username) + if not uuid: + return jsonify({"error": "Username not found"}), 404 + + avatar = get_avatar(uuid) + if not avatar: + return jsonify({"error": "Avatar not found"}), 404 + + width = 64 + height = int(width * 0.5) + avatar = avatar.resize((width, height)) + + ascii_str = pixel_to_ascii(avatar) + formatted = format_ascii(ascii_str, width=width) + + return Response(formatted, mimetype="text/plain") + +@minecraft_routes.route("/api/v1/ascii/fullskin/") +def fullskin_to_ascii(username): + uuid = get_uuid(username) + if not uuid: + return jsonify({"error": "Username not found"}), 404 + + url = f"https://crafatar.com/renders/body/{uuid}?scale=10" + r = requests.get(url) + if r.status_code != 200: + return jsonify({"error": "Full skin render not found"}), 404 + + skin = Image.open(BytesIO(r.content)) + + width = 64 + height = int(skin.height / skin.width * width * 0.5) + skin = skin.resize((width, height)) + + ascii_str = pixel_to_ascii(skin) + formatted = format_ascii(ascii_str, width=width) + + return Response(formatted, mimetype="text/plain") + +@minecraft_routes.route("/api/v1/uuid/") +def fetch_uuid(username): + uuid = get_uuid(username) + if not uuid: + return jsonify({"error": "Username not found"}), 404 + return jsonify({"username": username, "uuid": uuid}) + +@minecraft_routes.route("/api/v1/skinurl/") +def skin_url(username): + uuid = get_uuid(username) + if not uuid: + return jsonify({"error": "Username not found"}), 404 + + r = requests.get(f"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}") + if r.status_code != 200: + return jsonify({"error": "UUID not found"}), 404 + + data = r.json() + props = data["properties"][0]["value"] + decoded = base64.b64decode(props).decode() + url = json.loads(decoded)["textures"]["SKIN"]["url"] + return jsonify({"username": username, "skin_url": url}) + +@minecraft_routes.route("/api/v1/cape/") +def player_cape(username): + uuid = get_uuid(username) + if not uuid: + return jsonify({"error": "Username not found"}), 404 + + url = f"https://crafatar.com/capes/{uuid}" + r = requests.get(url) + if r.status_code != 200: + return jsonify({"error": "Cape not found"}), 404 + + return send_file(BytesIO(r.content), mimetype="image/png") \ No newline at end of file diff --git a/routes/server_routes.py b/routes/server_routes.py new file mode 100644 index 0000000..f487cd4 --- /dev/null +++ b/routes/server_routes.py @@ -0,0 +1,74 @@ +from flask import Blueprint, jsonify, render_template +from mcstatus import JavaServer +from datetime import datetime +import asyncio + +routes = Blueprint('routes', __name__) + +STATUS_TIMEOUT = 3 +QUERY_TIMEOUT = 2 + +from functools import wraps +def async_route(f): + @wraps(f) + def wrapper(*args, **kwargs): + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(f(*args, **kwargs)) + finally: + loop.close() + return wrapper + +async def check_server(server_ip): + start_time = datetime.now() + try: + try: + server = await JavaServer.async_lookup(server_ip, timeout=STATUS_TIMEOUT) + status = await server.async_status() + basic_response = { + "online": True, + "ip": server.address, + "version": status.version.name, + "players": status.players.online, + "max_players": status.players.max, + "motd": str(status.description), + "latency": status.latency, + "last_updated": datetime.now().isoformat(), + "response_time_ms": (datetime.now() - start_time).total_seconds() * 1000, + } + if status.players.online > 0: + asyncio.create_task(get_players_async(server_ip, basic_response)) + return basic_response + except Exception as e: + return { + "online": False, + "error": str(e), + "last_updated": datetime.now().isoformat(), + "response_time_ms": (datetime.now() - start_time).total_seconds() * 1000 + } + except Exception as e: + return { + "online": False, + "error": str(e), + "last_updated": datetime.now().isoformat(), + "response_time_ms": (datetime.now() - start_time).total_seconds() * 1000 + } + +async def get_players_async(server_ip, basic_response): + try: + server = await JavaServer.async_lookup(server_ip, timeout=QUERY_TIMEOUT) + query = await server.async_query() + basic_response["players_list"] = query.players.names + basic_response["player_query_completed"] = True + except: + basic_response["player_query_completed"] = False + +@routes.route('/') +def home(): + return render_template('index.html') + +@routes.route('/api/v1/status/') +@async_route +async def server_status(server_ip): + result = await check_server(server_ip) + return jsonify(result) \ No newline at end of file diff --git a/static/login.js b/static/login.js new file mode 100644 index 0000000..ba47b33 --- /dev/null +++ b/static/login.js @@ -0,0 +1,153 @@ +let currentUser = null; + +export function initializeLogin() { + const savedUser = localStorage.getItem('bigapi_user'); + if (savedUser) { + currentUser = JSON.parse(savedUser); + updateAuthUI(); + } +} + +export function showLoginModal() { + const modal = document.createElement('div'); + modal.className = 'login-modal'; + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + document.body.style.overflow = 'hidden'; + + modal.querySelector('.close-modal').addEventListener('click', () => closeModal(modal)); + modal.addEventListener('click', (e) => e.target === modal && closeModal(modal)); + + const loginForm = modal.querySelector('#login-form'); + loginForm.addEventListener('submit', async (e) => { + e.preventDefault(); + await handleLoginSubmit(); + }); + + modal.querySelector('#switch-to-signup').addEventListener('click', (e) => { + e.preventDefault(); + closeModal(modal); + showSignupModal(); + }); +} + +async function handleLoginSubmit() { + const email = document.getElementById('login-email').value; + const password = document.getElementById('login-password').value; + const rememberMe = document.getElementById('remember-me').checked; + const errorElement = document.getElementById('login-error'); + + errorElement.textContent = ''; + + try { + const user = await authenticateUser(email, password); + + currentUser = user; + if (rememberMe) { + localStorage.setItem('bigapi_user', JSON.stringify(user)); + } else { + sessionStorage.setItem('bigapi_user', JSON.stringify(user)); + } + + updateAuthUI(); + closeModal(document.querySelector('.login-modal')); + showToast('Login successful!'); + } catch (error) { + errorElement.textContent = error.message; + } +} + +async function authenticateUser(email, password) { + await new Promise(resolve => setTimeout(resolve, 800)); + const user = users.find(u => u.email === email); + + if (!user) { + throw new Error('User not found'); + } + + if (user.password !== password) { + throw new Error('Incorrect password'); + } + + const { password: _, ...userData } = user; + return userData; +} + +function updateAuthUI() { + const authButtons = document.querySelector('.auth-buttons'); + if (!authButtons) return; + + if (currentUser) { + authButtons.innerHTML = ` +
+ + +
+ `; + + document.getElementById('logout-btn').addEventListener('click', logout); + } else { + authButtons.innerHTML = ` + + + `; + + document.querySelector('.login-btn').addEventListener('click', showLoginModal); + document.querySelector('.signup-btn').addEventListener('click', showSignupModal); + } +} + +function logout() { + currentUser = null; + localStorage.removeItem('bigapi_user'); + sessionStorage.removeItem('bigapi_user'); + updateAuthUI(); + showToast('Logged out successfully'); +} + +function showToast(message) { + const toast = document.createElement('div'); + toast.className = 'toast'; + toast.textContent = message; + document.body.appendChild(toast); + + setTimeout(() => { + toast.classList.add('show'); + setTimeout(() => { + toast.classList.remove('show'); + setTimeout(() => { + document.body.removeChild(toast); + }, 300); + }, 3000); + }, 100); +} \ No newline at end of file diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..a54374f --- /dev/null +++ b/static/script.js @@ -0,0 +1,107 @@ +import { initializeSignup } from './signup.js'; +import { initializeLogin, showLoginModal } from './login.js'; + +document.addEventListener('DOMContentLoaded', function() { + initializeSignup(); + initializeLogin(); + + const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); + const navLinks = document.querySelector('.nav-links'); + + mobileMenuBtn.addEventListener('click', function() { + navLinks.classList.toggle('active'); + mobileMenuBtn.innerHTML = navLinks.classList.contains('active') ? + '' : ''; + }); + + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + e.preventDefault(); + + if (navLinks.classList.contains('active')) { + navLinks.classList.remove('active'); + mobileMenuBtn.innerHTML = ''; + } + + const targetId = this.getAttribute('href'); + const targetElement = document.querySelector(targetId); + + if (targetElement) { + window.scrollTo({ + top: targetElement.offsetTop - 80, + behavior: 'smooth' + }); + } + }); + }); + + const tabBtns = document.querySelectorAll('.tab-btn'); + const tabPanes = document.querySelectorAll('.tab-pane'); + + tabBtns.forEach(btn => { + btn.addEventListener('click', function() { + const tabId = this.getAttribute('data-tab'); + + tabBtns.forEach(btn => btn.classList.remove('active')); + tabPanes.forEach(pane => pane.classList.remove('active')); + + this.classList.add('active'); + document.getElementById(tabId).classList.add('active'); + }); + }); + + const copyBtns = document.querySelectorAll('.copy-btn'); + + copyBtns.forEach(btn => { + btn.addEventListener('click', function() { + const endpointUrl = this.parentElement.querySelector('h3').textContent; + navigator.clipboard.writeText(endpointUrl).then(() => { + const originalIcon = this.innerHTML; + this.innerHTML = ''; + setTimeout(() => { + this.innerHTML = originalIcon; + }, 2000); + }); + }); + }); + + const elementsToFloat = document.querySelectorAll('.feature-card, .price-card'); + elementsToFloat.forEach((el, index) => { + if (index % 2 === 0) { + el.classList.add('floating'); + el.style.animationDelay = `${index * 0.2}s`; + } + }); + + const contactForm = document.querySelector('.contact-form'); + if (contactForm) { + contactForm.addEventListener('submit', function(e) { + e.preventDefault(); + alert('Thank you for your message! We will get back to you soon.'); + this.reset(); + }); + } + + const animateOnScroll = function() { + const elements = document.querySelectorAll('.feature-card, .section-header, .endpoint-card, .price-card'); + + elements.forEach(element => { + const elementPosition = element.getBoundingClientRect().top; + const windowHeight = window.innerHeight; + + if (elementPosition < windowHeight - 100) { + element.style.opacity = '1'; + element.style.transform = 'translateY(0)'; + } + }); + }; + + document.querySelectorAll('.feature-card, .section-header, .endpoint-card, .price-card').forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(30px)'; + el.style.transition = 'all 0.6s ease'; + }); + + animateOnScroll(); + window.addEventListener('scroll', animateOnScroll); +}); \ No newline at end of file diff --git a/static/signup.js b/static/signup.js new file mode 100644 index 0000000..ee132e1 --- /dev/null +++ b/static/signup.js @@ -0,0 +1,89 @@ +export function initializeSignup() { + const getStartedButtons = document.querySelectorAll('.cta-button, .secondary-button'); + + getStartedButtons.forEach(button => { + button.addEventListener('click', function(e) { + e.preventDefault(); + + const buttonText = this.textContent.trim(); + + if (buttonText === 'Get Started' || buttonText === 'Start Free Trial') { + showSignupModal(); + } else if (buttonText === 'View Documentation') { + document.querySelector('#documentation').scrollIntoView({ behavior: 'smooth' }); + } else if (buttonText === 'Contact Sales') { + document.querySelector('#contact').scrollIntoView({ behavior: 'smooth' }); + } + }); + }); +} + +function showSignupModal() { + const modal = document.createElement('div'); + modal.className = 'signup-modal'; + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + document.body.style.overflow = 'hidden'; + + modal.querySelector('.close-modal').addEventListener('click', () => closeModal(modal)); + modal.addEventListener('click', (e) => e.target === modal && closeModal(modal)); + + const signupForm = modal.querySelector('#signup-form'); + signupForm.addEventListener('submit', handleSignupSubmit); + + modal.querySelector('#switch-to-login').addEventListener('click', (e) => { + e.preventDefault(); + closeModal(modal); + showLoginModal(); + }); +} + +function handleSignupSubmit(e) { + e.preventDefault(); + + const formData = { + name: document.getElementById('signup-name').value, + email: document.getElementById('signup-email').value, + password: document.getElementById('signup-password').value, + plan: document.getElementById('signup-plan').value + }; + + console.log('Signup form submitted:', formData); + alert('Account created successfully! Redirecting to dashboard...'); + + closeModal(document.querySelector('.signup-modal')); +} + +function closeModal(modal) { + if (modal) { + document.body.removeChild(modal); + document.body.style.overflow = 'auto'; + } +} \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..7ce20a5 --- /dev/null +++ b/static/style.css @@ -0,0 +1,923 @@ +:root { + --primary: #6c5ce7; + --primary-dark: #5649c0; + --secondary: #00cec9; + --dark: #1a1a2e; + --darker: #16213e; + --light: #f8f9fa; + --gray: #e2e8f0; + --dark-gray: #4a4a4a; + --success: #00b894; + --warning: #fdcb6e; + --danger: #d63031; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +body { + background-color: var(--dark); + color: var(--light); + line-height: 1.6; + overflow-x: hidden; +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--darker); +} + +::-webkit-scrollbar-thumb { + background: var(--primary); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--primary-dark); +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-10px); } + 100% { transform: translateY(0px); } +} + +@keyframes gradientBG { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +header { + background: linear-gradient(135deg, var(--darker), var(--dark)); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3); + position: fixed; + width: 100%; + z-index: 1000; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.navbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem 5%; + max-width: 1400px; + margin: 0 auto; +} + +.logo { + display: flex; + align-items: center; + font-size: 1.8rem; + font-weight: 700; + color: var(--light); + text-decoration: none; +} + +.logo span { + color: var(--primary); +} + +.logo i { + margin-right: 10px; + color: var(--primary); +} + +.nav-links { + display: flex; + gap: 2rem; +} + +.nav-links a { + color: var(--gray); + text-decoration: none; + font-weight: 500; + transition: all 0.3s ease; + position: relative; +} + +.nav-links a:hover { + color: var(--primary); +} + +.nav-links a::after { + content: ''; + position: absolute; + width: 0; + height: 2px; + background: var(--primary); + bottom: -5px; + left: 0; + transition: width 0.3s ease; +} + +.nav-links a:hover::after { + width: 100%; +} + +.cta-button { + background: var(--primary); + color: white; + padding: 0.6rem 1.5rem; + border-radius: 30px; + font-weight: 600; + transition: all 0.3s ease; + border: none; + cursor: pointer; + font-size: 1rem; +} + +.cta-button:hover { + background: var(--primary-dark); + transform: translateY(-2px); + box-shadow: 0 10px 20px rgba(108, 92, 231, 0.3); +} + +.mobile-menu-btn { + display: none; + background: none; + border: none; + color: var(--light); + font-size: 1.5rem; + cursor: pointer; +} + +.hero { + padding: 10rem 5% 5rem; + text-align: center; + background: linear-gradient(-45deg, #1a1a2e, #16213e, #0f3460, #16213e); + background-size: 400% 400%; + animation: gradientBG 15s ease infinite; + position: relative; + overflow: hidden; +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPjxkZWZzPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InJvdGF0ZSg0NSkiPjxyZWN0IHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0icmdiYSgyNTUsMjU1LDI1NSwwLjAzKSIvPjwvcGF0dGVybj48L2RlZnM+PHJlY3QgZmlsbD0idXJsKCNwYXR0ZXJuKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg=='); + opacity: 0.3; +} + +.hero-content { + max-width: 800px; + margin: 0 auto; + position: relative; + z-index: 1; + animation: fadeIn 1s ease forwards; +} + +.hero h1 { + font-size: 3.5rem; + margin-bottom: 1.5rem; + background: linear-gradient(to right, var(--primary), var(--secondary)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + line-height: 1.2; +} + +.hero p { + font-size: 1.2rem; + color: var(--gray); + margin-bottom: 2.5rem; + opacity: 0.9; +} + +.hero-buttons { + display: flex; + justify-content: center; + gap: 1.5rem; + margin-top: 2rem; +} + +.secondary-button { + background: transparent; + color: var(--light); + padding: 0.6rem 1.5rem; + border-radius: 30px; + font-weight: 600; + transition: all 0.3s ease; + border: 2px solid var(--primary); + cursor: pointer; +} + +.secondary-button:hover { + background: rgba(108, 92, 231, 0.1); + transform: translateY(-2px); +} + +section { + padding: 5rem 5%; + position: relative; +} + +.section-header { + text-align: center; + margin-bottom: 4rem; +} + +.section-header h2 { + font-size: 2.5rem; + margin-bottom: 1rem; + color: var(--light); +} + +.section-header p { + color: var(--gray); + max-width: 700px; + margin: 0 auto; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.feature-card { + background: rgba(255, 255, 255, 0.05); + border-radius: 15px; + padding: 2rem; + transition: all 0.3s ease; + border: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); +} + +.feature-card:hover { + transform: translateY(-10px); + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3); + border-color: var(--primary); +} + +.feature-icon { + width: 60px; + height: 60px; + background: rgba(108, 92, 231, 0.1); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1.5rem; + color: var(--primary); + font-size: 1.5rem; +} + +.feature-card h3 { + font-size: 1.5rem; + margin-bottom: 1rem; + color: var(--light); +} + +.feature-card p { + color: var(--gray); +} + +.endpoint-tabs { + display: flex; + gap: 1rem; + margin-bottom: 2rem; + flex-wrap: wrap; + justify-content: center; +} + +.tab-btn { + background: transparent; + color: var(--gray); + padding: 0.5rem 1.5rem; + border-radius: 30px; + font-weight: 600; + transition: all 0.3s ease; + border: 2px solid var(--dark-gray); + cursor: pointer; +} + +.tab-btn:hover { + border-color: var(--primary); + color: var(--primary); +} + +.tab-btn.active { + background: var(--primary); + border-color: var(--primary); + color: white; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; + animation: fadeIn 0.5s ease; +} + +.endpoint-card { + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; + padding: 1.5rem; + margin-bottom: 1.5rem; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.endpoint-header { + display: flex; + align-items: center; + margin-bottom: 1rem; + gap: 1rem; + flex-wrap: wrap; +} + +.method { + padding: 0.3rem 0.8rem; + border-radius: 5px; + font-weight: 600; + font-size: 0.8rem; + text-transform: uppercase; +} + +.method.get { + background: rgba(0, 184, 148, 0.2); + color: var(--success); +} + +.method.post { + background: rgba(253, 203, 110, 0.2); + color: var(--warning); +} + +.method.put { + background: rgba(108, 92, 231, 0.2); + color: var(--primary); +} + +.method.delete { + background: rgba(214, 48, 49, 0.2); + color: var(--danger); +} + +.endpoint-header h3 { + font-family: 'Courier New', monospace; + color: var(--light); +} + +.copy-btn { + background: transparent; + border: none; + color: var(--gray); + cursor: pointer; + margin-left: auto; + font-size: 1rem; + transition: all 0.3s ease; +} + +.copy-btn:hover { + color: var(--primary); +} + +.endpoint-details { + margin-top: 1.5rem; +} + +.endpoint-details h4 { + margin-bottom: 1rem; + color: var(--light); +} + +table { + width: 100%; + border-collapse: collapse; + margin-top: 1rem; +} + +th, td { + padding: 0.8rem; + text-align: left; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +th { + color: var(--primary); + font-weight: 600; +} + +td { + color: var(--gray); +} + +.pricing-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.price-card { + background: rgba(255, 255, 255, 0.05); + border-radius: 15px; + padding: 2rem; + transition: all 0.3s ease; + border: 1px solid rgba(255, 255, 255, 0.1); + position: relative; +} + +.price-card:hover { + transform: translateY(-10px); + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3); +} + +.price-card.featured { + border-color: var(--primary); + transform: translateY(-10px); +} + +.popular-badge { + position: absolute; + top: -15px; + right: 20px; + background: var(--primary); + color: white; + padding: 0.3rem 1rem; + border-radius: 30px; + font-size: 0.8rem; + font-weight: 600; +} + +.price-card h3 { + font-size: 1.5rem; + margin-bottom: 1rem; + color: var(--light); +} + +.price { + font-size: 3rem; + font-weight: 700; + margin: 1.5rem 0; + color: var(--light); +} + +.price span { + font-size: 1rem; + color: var(--gray); +} + +.price-card ul { + list-style: none; + margin: 2rem 0; +} + +.price-card ul li { + margin-bottom: 1rem; + color: var(--gray); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.price-card ul li i { + color: var(--primary); +} + +.price-card button { + width: 100%; +} + +.contact-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 3rem; + max-width: 1200px; + margin: 0 auto; +} + +.contact-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.form-group input, +.form-group textarea { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 0.8rem 1rem; + color: var(--light); + font-size: 1rem; + transition: all 0.3s ease; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2); +} + +.form-group textarea { + resize: vertical; +} + +.contact-info { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.info-item { + display: flex; + align-items: center; + gap: 1rem; +} + +.info-item i { + width: 50px; + height: 50px; + background: rgba(108, 92, 231, 0.1); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: var(--primary); + font-size: 1.2rem; +} + +.info-item p { + color: var(--gray); +} + +footer { + background: var(--darker); + padding: 5rem 5% 2rem; +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 3rem; + max-width: 1200px; + margin: 0 auto 3rem; +} + +.footer-logo { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.footer-logo p { + color: var(--gray); +} + +.social-links { + display: flex; + gap: 1rem; +} + +.social-links a { + width: 40px; + height: 40px; + background: rgba(255, 255, 255, 0.05); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: var(--gray); + transition: all 0.3s ease; +} + +.social-links a:hover { + background: var(--primary); + color: white; + transform: translateY(-3px); +} + +.footer-links { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.footer-links h4 { + color: var(--light); + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.footer-links a { + color: var(--gray); + text-decoration: none; + transition: all 0.3s ease; +} + +.footer-links a:hover { + color: var(--primary); + padding-left: 5px; +} + +.footer-bottom { + text-align: center; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: var(--gray); +} + +@media (max-width: 768px) { + .navbar { + padding: 1rem 5%; + } + + .nav-links { + position: fixed; + top: 80px; + left: -100%; + width: 100%; + height: calc(100vh - 80px); + background: var(--darker); + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2rem; + transition: all 0.5s ease; + } + + .nav-links.active { + left: 0; + } + + .mobile-menu-btn { + display: block; + } + + .hero h1 { + font-size: 2.5rem; + } + + .hero p { + font-size: 1rem; + } + + .hero-buttons { + flex-direction: column; + gap: 1rem; + } + + .section-header h2 { + font-size: 2rem; + } +} + +.floating { + animation: float 6s ease-in-out infinite; +} + +/* Auth Modals */ +.signup-modal, +.login-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(5px); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; + opacity: 0; + animation: fadeIn 0.3s ease forwards; +} + +.modal-content { + background: var(--darker); + padding: 2rem; + border-radius: 15px; + width: 90%; + max-width: 500px; + position: relative; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); +} + +.modal-content h2 { + margin-bottom: 1.5rem; + text-align: center; + color: var(--light); +} + +.close-modal { + position: absolute; + top: 15px; + right: 15px; + font-size: 1.5rem; + color: var(--gray); + cursor: pointer; + transition: all 0.3s ease; +} + +.close-modal:hover { + color: var(--primary); +} + +#signup-form, +#login-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +#signup-form select { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 0.8rem 1rem; + color: var(--light); + font-size: 1rem; + width: 100%; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 1rem; +} + +.remember-me { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.remember-me input { + width: auto; +} + +.auth-link { + text-align: center; + margin-top: 1.5rem; + color: var(--gray); +} + +.auth-link a { + color: var(--primary); + text-decoration: none; + transition: all 0.3s ease; +} + +.auth-link a:hover { + text-decoration: underline; +} + +.forgot-password { + text-align: center; + margin-top: 0.5rem; +} + +.forgot-password a { + color: var(--gray); + text-decoration: none; + font-size: 0.9rem; + transition: all 0.3s ease; +} + +.forgot-password a:hover { + color: var(--primary); +} + +/* User Dropdown */ +.user-dropdown { + position: relative; + display: inline-block; +} + +.user-menu-btn { + background: rgba(108, 92, 231, 0.2); + color: var(--light); + border: none; + padding: 0.6rem 1.2rem; + border-radius: 30px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.3s ease; +} + +.user-menu-btn:hover { + background: rgba(108, 92, 231, 0.3); +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background: var(--darker); + min-width: 200px; + border-radius: 8px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); + z-index: 1; + border: 1px solid rgba(255, 255, 255, 0.1); + overflow: hidden; + margin-top: 0.5rem; +} + +.dropdown-content a { + color: var(--gray); + padding: 0.8rem 1rem; + text-decoration: none; + display: flex; + align-items: center; + gap: 0.8rem; + transition: all 0.3s ease; +} + +.dropdown-content a:hover { + background: rgba(108, 92, 231, 0.1); + color: var(--primary); +} + +.dropdown-content a i { + width: 20px; + text-align: center; +} + +.user-dropdown:hover .dropdown-content { + display: block; + animation: fadeIn 0.3s ease; +} + +/* Error Message */ +.error-message { + color: var(--danger); + background: rgba(214, 48, 49, 0.1); + padding: 0.8rem; + border-radius: 8px; + margin-bottom: 1rem; + font-size: 0.9rem; +} + +.toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: var(--primary); + color: white; + padding: 1rem 2rem; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + z-index: 3000; + opacity: 0; + transition: all 0.3s ease; +} + +.toast.show { + opacity: 1; + bottom: 30px; +} + +select { + background: rgba(255, 255, 255, 0.05) !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: var(--light) !important; +} + +select option { + background: var(--darker); + color: var(--light); + border: none; + padding: 0.5rem; +} + +select:focus { + border-color: var(--primary) !important; + box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.2) !important; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..1ea5c76 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,386 @@ + + + + + + WinnieAPI + + + + +
+ +
+ +
+
+

The Most Powerful API for Your Needs

+

WinnieAPI provides developers with a comprehensive set of tools to build scalable, efficient applications with minimal effort.

+
+ +
+
+
+ +
+
+

Why Choose WinnieAPI

+

Discover the features that make our API stand out from the competition

+
+
+
+
+ +
+

Lightning Fast

+

Our optimized infrastructure ensures response times under 50ms globally.

+
+
+
+ +
+

Secure

+

We try to collect as much data about you as possible.

+
+
+
+ +
+

Flexible

+

If you ask nicely, I may add another endpoint.

+
+
+
+ +
+

Scalable

+

Handles from 1 to 1 million requests per second without breaking a sweat.

+
+
+
+ +
+
+

API Documentation

+

Explore all available endpoints and their functionality

+
+
+
+ + +
+
+
+
+
+ GET +

/api/v1/status/-serverIp-

+ +
+

Retrieve data about a Minecraft Server.

+
+

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
ipStringServer Address
last_updatedStringTime of the last update
latencyStringLatency to Server
max_playersStringMaximum amount of players
motdStringMessage Of The Day
onlineStringOnline indicator
playersStringCurrent online Players
response_time_msStringTime until the Server responded
versionStringVersion the server is running on
+
+
+
+
+
+
+ GET +

/api/v1/ascii/face/-username-

+ +
+

Shows ascii art of the players skin head.

+
+

Parameters

+ + + + + + + + + + + +
NameTypeDescription
Ascii RenderAscii RenderRender of the skins head
+
+
+ +
+
+ GET +

/api/v1/ascii/fullskin/-username-

+ +
+

Shows ascii art of the players skin.

+
+

Parameters

+ + + + + + + + + + + +
NameTypeDescription
Ascii RenderAscii RenderRender of the skins
+
+
+ +
+
+ GET +

/api/v1/uuid/-username-

+ +
+

Shows the uuid of a entered username.

+
+

Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
usernameStringEntered Username
uuidStringUUID of entered username
+
+
+ +
+
+ GET +

/api/v1/skinurl/-username-

+ +
+

Shows a link to the players skin

+
+

Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
skin_urlStringURL to players skin
usernameStringEntered username
+
+
+ +
+
+ GET +

/api/v1/cape/-username-

+ +
+

Displays the cape of the player

+
+

Parameters

+ + + + + + + + + + + +
NameTypeDescription
capecapeCape texture
+
+
+
+
+
+
+ +
+
+

Simple, Transparent Pricing

+

Choose the plan that fits your needs

+
+
+
+

Starter

+
0€/month
+
    +
  • 1,000 requests/month
  • +
  • Basic analytics
  • +
  • Email support
  • +
+ +
+ +
+

Enterprise

+
Custom (0€)
+
    +
  • Unlimited requests
  • +
  • Dedicated infrastructure
  • +
  • 24/7 support
  • +
  • SLA guarantee
  • +
+ +
+
+
+ +
+
+

Get In Touch

+

Have questions? We're here to help!

+
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +

winniepatgg@web.de

+
+
+
+
+ + + + + + \ No newline at end of file