first commit

This commit is contained in:
Patrick
2026-05-01 20:03:11 +02:00
commit b4ce1cb610
12 changed files with 1915 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
.vscode
__pycache__
+21
View File
@@ -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.
+33
View File
@@ -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-`
+11
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
flask==3.0.0
mcstatus==11.1.0
Pillow==11.3.0
requests==2.32.4
+112
View File
@@ -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/<username>")
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/<username>")
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/<username>")
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/<username>")
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/<username>")
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")
+74
View File
@@ -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/<server_ip>')
@async_route
async def server_status(server_ip):
result = await check_server(server_ip)
return jsonify(result)
+153
View File
@@ -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 = `
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2>Welcome Back</h2>
<div id="login-error" class="error-message"></div>
<form id="login-form">
<div class="form-group">
<input type="email" id="login-email" placeholder="Email Address" required>
</div>
<div class="form-group">
<input type="password" id="login-password" placeholder="Password" required>
</div>
<div class="form-group remember-me">
<input type="checkbox" id="remember-me">
<label for="remember-me">Remember me</label>
</div>
<button type="submit" class="cta-button">Log In</button>
</form>
<p class="auth-link">Don't have an account? <a href="#" id="switch-to-signup">Sign up</a></p>
<p class="forgot-password"><a href="#">Forgot password?</a></p>
</div>
`;
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 = `
<div class="user-dropdown">
<button class="user-menu-btn">
<i class="fas fa-user-circle"></i>
${currentUser.name}
<i class="fas fa-caret-down"></i>
</button>
<div class="dropdown-content">
<a href="#"><i class="fas fa-user"></i> Profile</a>
<a href="#"><i class="fas fa-cog"></i> Settings</a>
<a href="#" id="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</a>
</div>
</div>
`;
document.getElementById('logout-btn').addEventListener('click', logout);
} else {
authButtons.innerHTML = `
<button class="secondary-button login-btn">Log In</button>
<button class="cta-button signup-btn">Sign Up</button>
`;
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);
}
+107
View File
@@ -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') ?
'<i class="fas fa-times"></i>' : '<i class="fas fa-bars"></i>';
});
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
if (navLinks.classList.contains('active')) {
navLinks.classList.remove('active');
mobileMenuBtn.innerHTML = '<i class="fas fa-bars"></i>';
}
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 = '<i class="fas fa-check"></i>';
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);
});
+89
View File
@@ -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 = `
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2>Get Started with BigAPI</h2>
<form id="signup-form">
<div class="form-group">
<input type="text" id="signup-name" placeholder="Full Name" required>
</div>
<div class="form-group">
<input type="email" id="signup-email" placeholder="Email Address" required>
</div>
<div class="form-group">
<input type="password" id="signup-password" placeholder="Create Password" required>
</div>
<div class="form-group">
<select id="signup-plan" required>
<option value="">Select Plan</option>
<option value="starter">Starter (Free)</option>
<option value="pro">Pro (0€/month)</option>
<option value="enterprise">Enterprise (0€)</option>
</select>
</div>
<button type="submit" class="cta-button">Create Account</button>
</form>
<p class="auth-link">Already have an account? <a href="#" id="switch-to-login">Log in</a></p>
</div>
`;
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';
}
}
+923
View File
@@ -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;
}
+386
View File
@@ -0,0 +1,386 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WinnieAPI</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="../static/style.css">
</head>
<body>
<header>
<nav class="navbar">
<a href="#" class="logo">
<i class="fas fa-bolt"></i>Winnie<span>API</span>
</a>
<div class="nav-links">
<a href="#features">Features</a>
<a href="#documentation">Documentation</a>
<a href="#pricing">Pricing</a>
<a href="#contact">Contact</a>
</div>
<button class="mobile-menu-btn">
<i class="fas fa-bars"></i>
</button>
</nav>
</header>
<section class="hero">
<div class="hero-content">
<h1>The Most Powerful API for Your Needs</h1>
<p>WinnieAPI provides developers with a comprehensive set of tools to build scalable, efficient applications with minimal effort.</p>
<div class="hero-buttons">
<button class="cta-button" onclick="scammed();">Start Free Trial</button>
</div>
</div>
</section>
<section id="features" class="features">
<div class="section-header">
<h2>Why Choose WinnieAPI</h2>
<p>Discover the features that make our API stand out from the competition</p>
</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-tachometer-alt"></i>
</div>
<h3>Lightning Fast</h3>
<p>Our optimized infrastructure ensures response times under 50ms globally.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-shield-alt"></i>
</div>
<h3>Secure</h3>
<p>We try to collect as much data about you as possible.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-sliders-h"></i>
</div>
<h3>Flexible</h3>
<p>If you ask nicely, I may add another endpoint.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-chart-line"></i>
</div>
<h3>Scalable</h3>
<p>Handles from 1 to 1 million requests per second without breaking a sweat.</p>
</div>
</div>
</section>
<section id="documentation" class="documentation">
<div class="section-header">
<h2>API Documentation</h2>
<p>Explore all available endpoints and their functionality</p>
</div>
<div class="endpoints-container">
<div class="endpoint-tabs">
<button class="tab-btn active" data-tab="users">Minecraft Server</button>
<button class="tab-btn" data-tab="products">Minecraft Routes</button>
</div>
<div class="endpoint-content">
<div id="users" class="tab-pane active">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/status/-serverIp-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Retrieve data about a Minecraft Server.</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>ip</td>
<td>String</td>
<td>Server Address</td>
</tr>
<tr>
<td>last_updated</td>
<td>String</td>
<td>Time of the last update</td>
</tr>
<tr>
<td>latency</td>
<td>String</td>
<td>Latency to Server</td>
</tr>
<tr>
<td>max_players</td>
<td>String</td>
<td>Maximum amount of players</td>
</tr>
<tr>
<td>motd</td>
<td>String</td>
<td>Message Of The Day</td>
</tr>
<tr>
<td>online</td>
<td>String</td>
<td>Online indicator</td>
</tr>
<tr>
<td>players</td>
<td>String</td>
<td>Current online Players</td>
</tr>
<tr>
<td>response_time_ms</td>
<td>String</td>
<td>Time until the Server responded</td>
</tr>
<tr>
<td>version</td>
<td>String</td>
<td>Version the server is running on</td>
</tr>
</table>
</div>
</div>
</div>
<div id="products" class="tab-pane">
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/ascii/face/-username-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Shows ascii art of the players skin head.</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<th>Ascii Render</th>
<th>Ascii Render</th>
<th>Render of the skins head</th>
</tr>
</table>
</div>
</div>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/ascii/fullskin/-username-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Shows ascii art of the players skin.</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<th>Ascii Render</th>
<th>Ascii Render</th>
<th>Render of the skins</th>
</tr>
</table>
</div>
</div>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/uuid/-username-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Shows the uuid of a entered username.</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<th>username</th>
<th>String</th>
<th>Entered Username</th>
</tr>
<tr>
<th>uuid</th>
<th>String</th>
<th>UUID of entered username</th>
</tr>
</table>
</div>
</div>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/skinurl/-username-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Shows a link to the players skin</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<th>skin_url</th>
<th>String</th>
<th>URL to players skin</th>
</tr>
<tr>
<th>username</th>
<th>String</th>
<th>Entered username</th>
</tr>
</table>
</div>
</div>
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method get">GET</span>
<h3>/api/v1/cape/-username-</h3>
<button class="copy-btn"><i class="far fa-copy"></i></button>
</div>
<p>Displays the cape of the player</p>
<div class="endpoint-details">
<h4>Parameters</h4>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<th>cape</th>
<th>cape</th>
<th>Cape texture</th>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="pricing" class="pricing">
<div class="section-header">
<h2>Simple, Transparent Pricing</h2>
<p>Choose the plan that fits your needs</p>
</div>
<div class="pricing-cards">
<div class="price-card">
<h3>Starter</h3>
<div class="price">0€<span>/month</span></div>
<ul>
<li><i class="fas fa-check"></i> 1,000 requests/month</li>
<li><i class="fas fa-check"></i> Basic analytics</li>
<li><i class="fas fa-check"></i> Email support</li>
</ul>
<button class="secondary-button">Get Started</button>
</div>
<div class="price-card featured">
<div class="popular-badge">Most Popular</div>
<h3>Pro</h3>
<div class="price">0€<span>/month</span></div>
<ul>
<li><i class="fas fa-check"></i> 50,000 requests/month</li>
<li><i class="fas fa-check"></i> Advanced analytics</li>
<li><i class="fas fa-check"></i> Priority support</li>
<li><i class="fas fa-check"></i> Webhooks</li>
</ul>
<button class="cta-button">Get Started</button>
</div>
<div class="price-card">
<h3>Enterprise</h3>
<div class="price">Custom (0€)</div>
<ul>
<li><i class="fas fa-check"></i> Unlimited requests</li>
<li><i class="fas fa-check"></i> Dedicated infrastructure</li>
<li><i class="fas fa-check"></i> 24/7 support</li>
<li><i class="fas fa-check"></i> SLA guarantee</li>
</ul>
<button class="secondary-button">Contact Sales</button>
</div>
</div>
</section>
<section id="contact" class="contact">
<div class="section-header">
<h2>Get In Touch</h2>
<p>Have questions? We're here to help!</p>
</div>
<div class="contact-container">
<form class="contact-form">
<div class="form-group">
<input type="text" placeholder="Your Name" required>
</div>
<div class="form-group">
<input type="email" placeholder="Your Email" required>
</div>
<div class="form-group">
<textarea placeholder="Your Message" rows="5" required></textarea>
</div>
<button type="submit" class="cta-button">Send Message</button>
</form>
<div class="contact-info">
<div class="info-item">
<i class="fas fa-envelope"></i>
<p>winniepatgg@web.de</p>
</div>
</div>
</div>
</section>
<footer>
<div class="footer-content">
<div class="footer-logo">
<a href="#" class="logo">
<i class="fas fa-bolt"></i>Winnie<span>API</span>
</a>
<p>Powering the next generation of applications</p>
<div class="social-links">
<a href="https://github.com/WinniePatGG" target="_blank"><i class="fab fa-github"></i></a>
<a href="https://discord.gg/6Kg6eKkZSe" target="_blank"><i class="fab fa-discord"></i></a>
</div>
</div>
<div class="footer-links">
<h4>Product</h4>
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
<a href="#documentation">Documentation</a>
<a href="#contact">Contact</a>
</div>
<div class="footer-links">
<h4>Developer</h4>
<a href="https://winniepat.de" target="_blank">About</a>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 WinnieAPI. All rights reserved.</p>
</div>
</footer>
<script type="module" src="../static/script.js"></script>
</body>
</html>