Initial commit: Game Night landing + Minecraft cheat sheet
Some checks failed
Build games-landing / build (push) Failing after 13s
Includes: - Game Night landing page with health checks - FTB StoneBlock 4 server info & command reference - Dockerfile for nginx deployment - Forgejo CI/CD workflow
22
.forgejo/workflows/build.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Build games-landing
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repo
|
||||
run: git clone --depth 1 https://git.joshuahirsig.xyz/docker/games-landing.git .
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: apt-get update -qq && apt-get install -y -qq ca-certificates curl gnupg >/dev/null && install -m 0755 -d /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list && apt-get update -qq && apt-get install -y -qq docker-ce-cli >/dev/null
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.joshuahirsig.xyz -u joshii --password-stdin
|
||||
docker build -t git.joshuahirsig.xyz/docker/games-landing:latest .
|
||||
docker push git.joshuahirsig.xyz/docker/games-landing:latest
|
||||
4
Dockerfile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
FROM nginx:alpine
|
||||
COPY . /usr/share/nginx/html/
|
||||
RUN rm -f /usr/share/nginx/html/Dockerfile /usr/share/nginx/html/README.md
|
||||
EXPOSE 80
|
||||
7
codenames-icon.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<rect x="15" y="25" width="70" height="50" rx="6" stroke="#a78bfa" stroke-width="4"/>
|
||||
<circle cx="50" cy="42" r="10" stroke="#a78bfa" stroke-width="3.5"/>
|
||||
<path d="M35 62c0-8.3 6.7-15 15-15s15 6.7 15 15" stroke="#a78bfa" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<line x1="60" y1="20" x2="68" y2="12" stroke="#c4b5fd" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="70" cy="10" r="4" fill="#c4b5fd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 501 B |
11
favicon.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<rect x="10" y="10" width="80" height="80" rx="16" fill="#18181b"/>
|
||||
<path d="M35 40h-8a4 4 0 00-4 4v4a4 4 0 004 4h8" stroke="#818cf8" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<path d="M65 40h8a4 4 0 014 4v4a4 4 0 01-4 4h-8" stroke="#818cf8" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<rect x="30" y="30" width="40" height="40" rx="8" stroke="#a78bfa" stroke-width="3.5"/>
|
||||
<line x1="42" y1="45" x2="42" y2="55" stroke="#c4b5fd" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="50" y1="45" x2="50" y2="55" stroke="#c4b5fd" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="58" y1="45" x2="58" y2="55" stroke="#c4b5fd" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="50" cy="22" r="3" fill="#818cf8"/>
|
||||
<circle cx="50" cy="78" r="3" fill="#818cf8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 861 B |
151
games.json
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"settings": {
|
||||
"healthChecks": true,
|
||||
"healthTimeoutMs": 6000
|
||||
},
|
||||
"games": [
|
||||
{
|
||||
"id": "minecraft",
|
||||
"name": "FTB StoneBlock 4",
|
||||
"url": "https://minecraft.joshuahirsig.xyz",
|
||||
"icon": "⛏️",
|
||||
"iconClass": "icon-emerald",
|
||||
"description": "Minecraft Modpack — grabe dich durch Stein, baue Maschinen & meistere die Technik!",
|
||||
"players": "1-20 Spieler",
|
||||
"tag": "Minecraft",
|
||||
"tagClass": "tag-board",
|
||||
"healthEnabled": true,
|
||||
"healthUrl": "https://minecraft.joshuahirsig.xyz/api/status"
|
||||
},
|
||||
{
|
||||
"id": "codenames",
|
||||
"name": "Codenames",
|
||||
"url": "https://codenames.joshuahirsig.xyz",
|
||||
"icon": "🕵️",
|
||||
"iconClass": "icon-purple",
|
||||
"description": "Finde die Agenten deines Teams anhand von Hinweisen!",
|
||||
"players": "4+ Spieler",
|
||||
"tag": "Teamspiel",
|
||||
"tagClass": "tag-team",
|
||||
"iconSvg": "/codenames-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "scribble",
|
||||
"name": "Scribble",
|
||||
"url": "https://scribble.joshuahirsig.xyz",
|
||||
"icon": "🎨",
|
||||
"iconClass": "icon-pink",
|
||||
"description": "Zeichne und errate Begriffe - wie Skribbl.io!",
|
||||
"players": "3+ Spieler",
|
||||
"tag": "Party",
|
||||
"tagClass": "tag-party",
|
||||
"iconSvg": "/scribble-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "cards",
|
||||
"name": "Massive Decks",
|
||||
"url": "https://cards.joshuahirsig.xyz",
|
||||
"icon": "🃏",
|
||||
"iconClass": "icon-blue",
|
||||
"description": "Cards Against Humanity - die besten schlechten Antworten gewinnen.",
|
||||
"players": "3+ Spieler",
|
||||
"tag": "Party",
|
||||
"tagClass": "tag-party",
|
||||
"iconSvg": "/massivedecks-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "trivia",
|
||||
"name": "Trivia",
|
||||
"url": "https://trivia.joshuahirsig.xyz",
|
||||
"icon": "🧠",
|
||||
"iconClass": "icon-amber",
|
||||
"description": "Quiz-Spiel wie Kahoot - erstelle eigene Fragen!",
|
||||
"players": "2+ Spieler",
|
||||
"tag": "Quiz",
|
||||
"tagClass": "tag-quiz",
|
||||
"iconSvg": "/trivia-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "tabletop",
|
||||
"name": "Virtual Tabletop",
|
||||
"url": "https://tabletop.joshuahirsig.xyz",
|
||||
"icon": "🎲",
|
||||
"iconClass": "icon-emerald",
|
||||
"description": "300+ Brettspiele, Kartenspiele und mehr.",
|
||||
"players": "2+ Spieler",
|
||||
"tag": "Brettspiel",
|
||||
"tagClass": "tag-board",
|
||||
"iconSvg": "/tabletop-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "tosios",
|
||||
"name": "TOSIOS",
|
||||
"url": "https://shooter.joshuahirsig.xyz",
|
||||
"icon": "🔫",
|
||||
"iconClass": "icon-red",
|
||||
"description": "Browser IO-Shooter - schnelle Multiplayer-Matches!",
|
||||
"players": "2+ Spieler",
|
||||
"tag": "Action",
|
||||
"tagClass": "tag-strategy",
|
||||
"iconSvg": "/tosios-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "geoquiz",
|
||||
"name": "Le Grand GeoQuiz",
|
||||
"url": "https://geoquiz.joshuahirsig.xyz",
|
||||
"icon": "🌍",
|
||||
"iconClass": "icon-emerald",
|
||||
"description": "Strategisches Geografie-Spiel - 8 Lander, 8 Kategorien, niedrigste Punktzahl gewinnt!",
|
||||
"players": "1 Spieler",
|
||||
"tag": "Quiz",
|
||||
"tagClass": "tag-quiz",
|
||||
"iconSvg": "/geoquiz-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "worldguessr",
|
||||
"name": "WorldGuessr",
|
||||
"url": "https://worldguessr.joshuahirsig.xyz",
|
||||
"icon": "🌎",
|
||||
"iconClass": "icon-emerald",
|
||||
"description": "GeoGuessr-Klon - errate Orte auf der ganzen Welt anhand von Street View!",
|
||||
"players": "1+ Spieler",
|
||||
"tag": "Geo",
|
||||
"tagClass": "tag-quiz",
|
||||
"iconSvg": "/worldguessr-icon.png"
|
||||
},
|
||||
{
|
||||
"id": "pokemon-military",
|
||||
"name": "Pokémon or Military?",
|
||||
"url": "https://pokemon.joshuahirsig.xyz",
|
||||
"icon": "⚔️",
|
||||
"iconClass": "icon-red",
|
||||
"description": "Ist es ein Pokémon-TCG-Set oder eine Militäroperation? Finde es heraus!",
|
||||
"players": "1 Spieler",
|
||||
"tag": "Quiz",
|
||||
"tagClass": "tag-quiz",
|
||||
"iconSvg": "/pokemon-icon.svg"
|
||||
},
|
||||
{
|
||||
"id": "bomberman",
|
||||
"name": "Bomberman (UniCT)",
|
||||
"url": "https://bomberman.joshuahirsig.xyz",
|
||||
"icon": "💣",
|
||||
"iconClass": "icon-amber",
|
||||
"description": "Klassisches Bomberman — 2-4 Spieler, private Räume, Power-ups und Chat!",
|
||||
"players": "2-4 Spieler",
|
||||
"tag": "Action",
|
||||
"tagClass": "tag-strategy"
|
||||
},
|
||||
{
|
||||
"id": "bomber",
|
||||
"name": "Bomber (Vasin)",
|
||||
"url": "https://bomber.joshuahirsig.xyz",
|
||||
"icon": "💥",
|
||||
"iconClass": "icon-red",
|
||||
"description": "Bomberman-Klon mit Phaser.js — bis zu 3 Spieler, 2 Maps, Skill-Upgrades.",
|
||||
"players": "2-3 Spieler",
|
||||
"tag": "Action",
|
||||
"tagClass": "tag-strategy"
|
||||
}
|
||||
]
|
||||
}
|
||||
82
geoquiz-icon.svg
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512.001 512.001" xml:space="preserve">
|
||||
<circle style="fill:#0792C7;" cx="247.935" cy="302.408" r="201.755"/>
|
||||
<path style="fill:#B2E763;" d="M417.403,334.683l-48.421-56.491l16.14-48.421l-16.14-8.07l32.28-40.35
|
||||
c0,0,52.324,31.93,48.421,137.191L417.403,334.683z"/>
|
||||
<path style="fill:#2C4D77;" d="M134.949,302.403c0-96.168,67.306-176.564,157.367-196.794c-14.288-3.209-29.128-4.958-44.385-4.958
|
||||
c-111.425,0-201.752,90.328-201.752,201.752s90.328,201.752,201.752,201.752c15.257,0,30.098-1.749,44.385-4.958
|
||||
C202.255,478.969,134.949,398.572,134.949,302.403z"/>
|
||||
<path style="fill:#00BA94;" d="M62.318,375.035l16.14-48.421l-32.28-40.35C46.177,286.263,43.587,356.16,62.318,375.035z"/>
|
||||
<g>
|
||||
<polyline style="fill:#B2E763;" points="231.791,165.212 199.51,197.492 175.299,181.352 151.089,197.492 126.879,245.913
|
||||
134.949,278.193 175.299,302.403 167.23,342.754 143.019,375.035 159.159,447.665 143.019,471.876 159.159,479.945 223.72,439.595
|
||||
215.65,391.175 272.141,294.333 231.791,253.982 288.281,213.632 256,165.212 "/>
|
||||
<path style="fill:#B2E763;" d="M344.772,479.945l-64.561-16.14l-40.35,40.35C239.86,504.155,289.89,512.169,344.772,479.945z"/>
|
||||
</g>
|
||||
<path style="fill:#F4F4F4;" d="M360.912,392.7L360.912,392.7c0-16.859,12.327-31.984,29.11-33.585
|
||||
c4.732-0.451,9.269,0.107,13.436,1.505c6.146-19.685,24.516-33.99,46.224-33.99c27.237,0,49.221,22.618,48.398,50.04
|
||||
c-0.795,26.5-23.915,47.06-50.429,47.06H304.421h25.599C347.082,423.73,360.912,409.762,360.912,392.7z"/>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M449.684,326.614c-7.184,0-13.934,1.687-20.064,4.492c17.018,7.874,28.715,25.436,28.111,45.556
|
||||
c-0.796,26.5-23.916,47.068-50.429,47.068h40.35c26.513,0,49.633-20.568,50.428-47.068
|
||||
C498.904,349.239,476.921,326.614,449.684,326.614z"/>
|
||||
<path style="fill:#FFFFFF;" d="M151.089,215.021L151.089,215.021c0-16.859-12.326-31.916-29.109-33.517
|
||||
c-4.732-0.451-9.269,0.14-13.436,1.539c-6.146-19.685-24.516-33.972-46.225-33.972c-27.237,0-49.221,22.49-48.398,49.912
|
||||
c0.795,26.5,23.915,46.933,50.429,46.933h143.232h-25.598C164.92,245.917,151.089,232.083,151.089,215.021z"/>
|
||||
</g>
|
||||
<path style="fill:#F4F4F4;" d="M54.271,198.984c-0.604-20.12,11.093-37.545,28.11-45.419c-6.129-2.806-12.881-4.493-20.063-4.493
|
||||
c-27.237,0-49.221,22.49-48.398,49.912c0.795,26.5,23.915,46.933,50.429,46.933h40.35C78.186,245.917,55.066,225.485,54.271,198.984
|
||||
z"/>
|
||||
<path style="fill:#F60A53;" d="M333.527,22.027c-18.91-18.91-49.567-18.91-68.477,0l-17.119,17.119l-17.119-17.119
|
||||
c-18.91-18.91-49.567-18.91-68.477,0c-18.91,18.909-18.91,49.567,0,68.476l85.596,85.596l64.561-64.561l21.035-21.035
|
||||
C352.437,71.594,352.437,40.936,333.527,22.027z"/>
|
||||
<path style="fill:#FE702D;" d="M247.931,39.146l-17.119-17.119c-18.91-18.91-49.567-18.91-68.477,0
|
||||
c-18.91,18.909-18.91,49.567,0,68.476l85.596,85.596"/>
|
||||
<g>
|
||||
<rect x="296.509" y="254.284" style="fill:#FFFFFF;" width="14.643" height="15.689"/>
|
||||
<rect x="272.452" y="327.501" style="fill:#FFFFFF;" width="15.689" height="15.689"/>
|
||||
<rect x="128.11" y="310.765" style="fill:#FFFFFF;" width="14.643" height="15.689"/>
|
||||
<rect x="103.007" y="335.869" style="fill:#FFFFFF;" width="15.689" height="15.689"/>
|
||||
<rect x="321.613" y="440.465" style="fill:#FFFFFF;" width="14.643" height="15.689"/>
|
||||
</g>
|
||||
<path d="M489.889,335.908c-9.002-9.275-20.544-15.025-33.053-16.62c0.447-5.609,0.692-11.26,0.692-16.885
|
||||
c0-85.77-51.979-162.272-130.603-194.205l12.149-12.149c10.628-10.627,16.481-24.756,16.481-39.785
|
||||
c0-15.028-5.853-29.158-16.48-39.785C328.446,5.852,314.316,0,299.288,0s-29.158,5.852-39.785,16.48l-11.571,11.571L236.358,16.48
|
||||
c-21.937-21.937-57.632-21.938-79.57,0c-10.628,10.627-16.481,24.757-16.481,39.785c0,15.029,5.853,29.158,16.48,39.785
|
||||
l12.158,12.158c-26.054,10.622-49.899,26.526-69.817,46.593c-9.953-8.59-22.879-13.608-36.811-13.608
|
||||
c-15.426,0-29.807,6.066-40.495,17.079C11.211,169.209,5.62,183.652,6.078,198.942c0.703,23.444,16.886,43.464,38.87,51.047
|
||||
c-4.389,17.05-6.616,34.65-6.616,52.415c0,115.572,94.025,209.597,209.598,209.597c33.526,0,65.561-7.68,95.215-22.827
|
||||
c26.814-13.696,50.721-33.373,69.43-57.075h35.077c31.222,0,57.362-24.634,58.269-54.914
|
||||
C506.387,361.701,500.693,347.043,489.889,335.908z M441.091,319.472c-7.252,1.103-14.092,3.582-20.208,7.218l-43.054-50.229
|
||||
l16.887-50.662l-13.545-6.773l24.073-30.092c23.254,32.265,36.595,71.677,36.595,113.47
|
||||
C441.837,308.092,441.583,313.809,441.091,319.472z M155.996,56.264c0-10.838,4.22-21.027,11.885-28.691
|
||||
c7.911-7.911,18.3-11.865,28.691-11.865c10.392,0,20.781,3.955,28.693,11.865l22.666,22.666l22.667-22.666
|
||||
c7.663-7.664,17.852-11.884,28.69-11.884c10.838,0,21.028,4.22,28.692,11.884h0.001c7.664,7.664,11.884,17.852,11.884,28.691
|
||||
s-4.22,21.028-11.885,28.692l-80.048,80.049l-80.049-80.049C160.217,77.292,155.996,67.103,155.996,56.264z M215.122,435.718
|
||||
L155.631,472.9c-0.947-0.515-1.881-1.046-2.817-1.575l14.73-22.094l-16.085-72.377l23.114-30.817l9.504-47.517l-42.304-25.383
|
||||
l-4.975-19.898h70.802v-15.689h-25.616c-12.708,0-23.049-10.223-23.049-22.789c0-4.121-0.61-8.124-1.731-11.916l18.097-12.065
|
||||
l25.211,16.807l33.907-33.907l13.514,13.514l7.979-7.979l21.632,32.448l-57.845,41.318l42.622,42.622l-54.872,94.065
|
||||
L215.122,435.718z M181.076,120.34l42.246,42.246l-24.812,24.812l-23.21-15.474l-25.327,16.885
|
||||
c-6.587-8.423-16.271-14.198-27.247-15.244c-3.105-0.296-6.222-0.221-9.325,0.223c-1.145-2.489-2.461-4.868-3.928-7.126
|
||||
C129.627,146.116,154.201,130.22,181.076,120.34z M21.762,198.471c-0.331-11.012,3.689-21.407,11.32-29.271
|
||||
c7.708-7.942,18.09-12.316,29.236-12.316c17.86,0,33.428,11.426,38.737,28.432l2.388,7.649l7.597-2.55
|
||||
c3.392-1.139,6.821-1.554,10.195-1.232c12.341,1.177,22.009,12.411,22.009,25.577c0,8.522,2.803,16.406,7.542,22.789H64.348
|
||||
C41.513,237.549,22.407,220.019,21.762,198.471z M54.138,308.771l15.528,19.411l-8.638,25.913
|
||||
C57.014,339.591,54.646,324.413,54.138,308.771z M69.07,377.284l0.69,0.23l17.49-52.47l-32.41-40.511
|
||||
c0.973-10.637,2.818-21.152,5.514-31.445c1.322,0.089,2.652,0.15,3.995,0.15h56.275l7.503,30.009l38.396,23.037l-6.638,33.185
|
||||
l-25.307,33.744l16.197,72.885l-11.31,16.964C108.417,442.035,83.763,412.249,69.07,377.284z M259.115,495.995l23.503-23.502
|
||||
l38.368,9.592C301.276,490.135,280.56,494.797,259.115,495.995z M343.007,471.418l-65.202-16.301l-40.875,40.875
|
||||
c-22.798-1.281-44.557-6.511-64.599-15.028l59.988-37.492l-8.465-50.792l58.11-99.617l-38.078-38.079l55.136-39.384l-31.799-47.698
|
||||
l47.566-47.567c32.313,11.853,60.066,31.716,81.316,56.891l-0.969-0.775l-38.342,47.928l18.734,9.368l-15.393,46.179l48.516,56.601
|
||||
c-4.09,4.366-7.511,9.421-10.081,15.034c-3.016-0.384-6.121-0.426-9.292-0.126c-20.305,1.937-36.21,20.177-36.21,41.526
|
||||
c0,12.929-10.339,23.448-23.048,23.448h-25.146v15.689h87.186C377.954,447.736,361.296,461.092,343.007,471.418z M490.239,376.713
|
||||
c-0.656,21.888-19.76,39.695-42.586,39.695h-86.637c4.86-6.539,7.74-14.66,7.74-23.448c0-13.35,9.668-24.73,22.01-25.906
|
||||
c3.624-0.344,7.055,0.014,10.195,1.068l7.597,2.55l2.388-7.649c5.325-17.057,20.892-28.515,38.736-28.515
|
||||
c10.952,0,21.233,4.378,28.947,12.327C486.453,354.898,490.576,365.508,490.239,376.713z"/>
|
||||
<g>
|
||||
<rect x="352.991" y="310.765" style="fill:#FFFFFF;" width="15.689" height="15.689"/>
|
||||
<rect x="336.256" y="157.009" style="fill:#FFFFFF;" width="16.735" height="15.689"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
502
index.html
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Game Night</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #07070f;
|
||||
color: #d0d0d0;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 60% at 15% 45%, rgba(124, 58, 237, 0.12) 0%, transparent 60%),
|
||||
radial-gradient(ellipse 60% 50% at 85% 25%, rgba(59, 130, 246, 0.08) 0%, transparent 55%),
|
||||
radial-gradient(ellipse 70% 50% at 50% 90%, rgba(168, 85, 247, 0.06) 0%, transparent 50%);
|
||||
}
|
||||
.bg::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background-image: radial-gradient(rgba(255,255,255,0.03) 1px, transparent 1px);
|
||||
background-size: 32px 32px;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative; z-index: 1;
|
||||
max-width: 960px; margin: 0 auto;
|
||||
padding: 3.5rem 1.5rem 4rem;
|
||||
}
|
||||
|
||||
header { text-align: center; margin-bottom: 2.5rem; }
|
||||
.logo { font-size: 3.2rem; margin-bottom: 0.3rem; filter: drop-shadow(0 0 24px rgba(139,92,246,0.3)); }
|
||||
h1 {
|
||||
font-size: 2.4rem; font-weight: 800; letter-spacing: -0.03em;
|
||||
background: linear-gradient(135deg, #c4b5fd 0%, #818cf8 40%, #60a5fa 100%);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.subtitle { color: #555; font-size: 0.9rem; margin-top: 0.4rem; }
|
||||
|
||||
.status-bar {
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
margin-top: 1.2rem; padding: 0.45rem 1.2rem;
|
||||
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 99px; font-size: 0.78rem; color: #777;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.status-bar .dot {
|
||||
width: 7px; height: 7px; border-radius: 50%; background: #555;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
.status-bar .dot.all-good { background: #22c55e; box-shadow: 0 0 8px rgba(34,197,94,0.5); }
|
||||
.status-bar .dot.some-down { background: #f59e0b; box-shadow: 0 0 8px rgba(245,158,11,0.4); }
|
||||
|
||||
.games {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.game {
|
||||
display: flex; flex-direction: column;
|
||||
background: rgba(255,255,255,0.025);
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
border-radius: 16px; padding: 1.4rem 1.5rem;
|
||||
text-decoration: none; color: inherit;
|
||||
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative; overflow: hidden;
|
||||
opacity: 0; transform: translateY(16px);
|
||||
animation: fadeUp 0.5s forwards;
|
||||
}
|
||||
.game { animation-delay: calc(0.05s * var(--i)); }
|
||||
|
||||
@keyframes fadeUp {
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.game::before {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(139,92,246,0.08), transparent 60%);
|
||||
opacity: 0; transition: opacity 0.4s;
|
||||
}
|
||||
.game:hover::before { opacity: 1; }
|
||||
.game:hover {
|
||||
transform: translateY(-6px);
|
||||
border-color: rgba(139,92,246,0.25);
|
||||
box-shadow: 0 24px 48px rgba(0,0,0,0.35), 0 0 0 1px rgba(139,92,246,0.1);
|
||||
}
|
||||
|
||||
.game-top { display: flex; align-items: flex-start; gap: 0.9rem; margin-bottom: 0.8rem; }
|
||||
|
||||
.game-icon {
|
||||
width: 52px; height: 52px; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.6rem; flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.game-icon::after {
|
||||
content: ''; position: absolute; inset: 0; border-radius: 14px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
|
||||
.icon-purple { background: rgba(139,92,246,0.12); }
|
||||
.icon-blue { background: rgba(59,130,246,0.12); }
|
||||
.icon-pink { background: rgba(236,72,153,0.12); }
|
||||
.icon-red { background: rgba(239,68,68,0.12); }
|
||||
.icon-amber { background: rgba(245,158,11,0.12); }
|
||||
.icon-emerald { background: rgba(16,185,129,0.12); }
|
||||
|
||||
.game-info { flex: 1; min-width: 0; }
|
||||
.game h2 { font-size: 1.1rem; font-weight: 700; color: #f0f0f0; line-height: 1.3; }
|
||||
|
||||
.game-meta {
|
||||
display: flex; align-items: center; gap: 0.6rem;
|
||||
margin-top: 0.25rem; flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.health {
|
||||
display: inline-flex; align-items: center; gap: 0.3rem;
|
||||
font-size: 0.7rem; font-weight: 500;
|
||||
}
|
||||
.health-dot {
|
||||
width: 6px; height: 6px; border-radius: 50%;
|
||||
background: #333; transition: all 0.4s;
|
||||
}
|
||||
.health-dot.online {
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 6px rgba(34,197,94,0.6);
|
||||
animation: glow 2.5s ease-in-out infinite;
|
||||
}
|
||||
.health-dot.offline {
|
||||
background: #ef4444;
|
||||
box-shadow: 0 0 4px rgba(239,68,68,0.4);
|
||||
}
|
||||
.health-dot.checking {
|
||||
background: #666;
|
||||
animation: blink 0.8s ease-in-out infinite;
|
||||
}
|
||||
.health-label { color: #666; }
|
||||
.health-label.online { color: #4ade80; }
|
||||
.health-label.offline { color: #f87171; }
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% { box-shadow: 0 0 4px rgba(34,197,94,0.4); }
|
||||
50% { box-shadow: 0 0 10px rgba(34,197,94,0.7); }
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.players-badge {
|
||||
font-size: 0.68rem; color: #555; font-weight: 500;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 0.6rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.6px; padding: 2px 7px; border-radius: 5px;
|
||||
}
|
||||
.tag-team { background: rgba(139,92,246,0.1); color: #a78bfa; }
|
||||
.tag-party { background: rgba(236,72,153,0.1); color: #f472b6; }
|
||||
.tag-strategy { background: rgba(239,68,68,0.1); color: #f87171; }
|
||||
.tag-quiz { background: rgba(245,158,11,0.1); color: #fbbf24; }
|
||||
.tag-board { background: rgba(16,185,129,0.1); color: #34d399; }
|
||||
|
||||
.game p {
|
||||
font-size: 0.82rem; color: #777; line-height: 1.55;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.game-arrow {
|
||||
position: absolute; bottom: 1.2rem; right: 1.4rem;
|
||||
color: #333; font-size: 1.1rem; transition: all 0.3s;
|
||||
}
|
||||
.game:hover .game-arrow { color: #8b5cf6; transform: translateX(3px); }
|
||||
|
||||
footer {
|
||||
text-align: center; margin-top: 3rem;
|
||||
font-size: 0.72rem; color: #333;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.logo { font-size: 2.4rem; }
|
||||
h1 { font-size: 1.8rem; }
|
||||
.games { grid-template-columns: 1fr; }
|
||||
.container { padding: 2rem 1rem 3rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg"></div>
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="logo">🎮</div>
|
||||
<h1>Game Night</h1>
|
||||
<p class="subtitle">Wähle ein Spiel und lade deine Freunde ein</p>
|
||||
<div class="status-bar">
|
||||
<span class="dot" id="summary-dot"></span>
|
||||
<span id="summary-text">Services werden geprüft...</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="games" id="games-grid"></div>
|
||||
|
||||
<footer>Hosted on joshuahirsig.xyz</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var DEFAULT_SETTINGS = {
|
||||
healthChecks: true,
|
||||
healthTimeoutMs: 6000
|
||||
};
|
||||
|
||||
var DEFAULT_GAMES = [
|
||||
{ id: "codenames", name: "Codenames", icon: "\uD83D\uDD75\uFE0F", iconClass: "icon-purple", description: "Finde die Agenten deines Teams anhand von Hinweisen!", players: "4+ Spieler", tag: "Teamspiel", tagClass: "tag-team", url: "https://codenames.joshuahirsig.xyz" },
|
||||
{ id: "scribble", name: "Scribble", icon: "\uD83C\uDFA8", iconClass: "icon-pink", description: "Zeichne und errate Begriffe \u2013 wie Skribbl.io!", players: "3+ Spieler", tag: "Party", tagClass: "tag-party", url: "https://scribble.joshuahirsig.xyz" },
|
||||
{ id: "cards", name: "Massive Decks", icon: "\uD83C\uDCCF", iconClass: "icon-blue", description: "Cards Against Humanity \u2013 die besten schlechten Antworten gewinnen.", players: "3+ Spieler", tag: "Party", tagClass: "tag-party", url: "https://cards.joshuahirsig.xyz" },
|
||||
{ id: "trivia", name: "Trivia", icon: "\uD83E\uDDE0", iconClass: "icon-amber", description: "Quiz-Spiel wie Kahoot \u2013 erstelle eigene Fragen!", players: "2+ Spieler", tag: "Quiz", tagClass: "tag-quiz", url: "https://trivia.joshuahirsig.xyz" },
|
||||
{ id: "tabletop", name: "Virtual Tabletop", icon: "\uD83C\uDFB2", iconClass: "icon-emerald", description: "300+ Brettspiele, Kartenspiele und mehr.", players: "2+ Spieler", tag: "Brettspiel", tagClass: "tag-board", url: "https://tabletop.joshuahirsig.xyz" },
|
||||
{ id: "tosios", name: "TOSIOS", icon: "\uD83D\uDD2B", iconClass: "icon-red", description: "Browser IO-Shooter \u2013 schnelle Multiplayer-Matches!", players: "2+ Spieler", tag: "Action", tagClass: "tag-strategy", url: "https://shooter.joshuahirsig.xyz" },
|
||||
{ id: "geoquiz", name: "Le Grand GeoQuiz", icon: "\uD83C\uDF0D", iconClass: "icon-emerald", description: "Strategisches Geografie-Spiel \u2013 8 L\u00e4nder, 8 Kategorien, niedrigste Punktzahl gewinnt!", players: "1 Spieler", tag: "Quiz", tagClass: "tag-quiz", url: "https://geoquiz.joshuahirsig.xyz" }
|
||||
];
|
||||
|
||||
var YAML_LOADER_URL = "https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js";
|
||||
var yamlLoaderPromise = null;
|
||||
var grid = document.getElementById("games-grid");
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function slugify(value) {
|
||||
var slug = String(value || "")
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
return slug || "game";
|
||||
}
|
||||
|
||||
function loadScript(src) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = function() { reject(new Error("Script konnte nicht geladen werden: " + src)); };
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureYamlParser() {
|
||||
if (window.jsyaml && typeof window.jsyaml.load === "function") {
|
||||
return Promise.resolve(window.jsyaml);
|
||||
}
|
||||
if (!yamlLoaderPromise) {
|
||||
yamlLoaderPromise = loadScript(YAML_LOADER_URL).then(function() {
|
||||
if (!window.jsyaml || typeof window.jsyaml.load !== "function") {
|
||||
throw new Error("YAML parser nicht verf\u00fcgbar");
|
||||
}
|
||||
return window.jsyaml;
|
||||
});
|
||||
}
|
||||
return yamlLoaderPromise;
|
||||
}
|
||||
|
||||
function normalizeGame(rawGame, index, usedIds) {
|
||||
var game = rawGame && typeof rawGame === "object" ? rawGame : {};
|
||||
var name = game.name ? String(game.name) : ("Game " + (index + 1));
|
||||
var id = game.id ? String(game.id) : slugify(name);
|
||||
|
||||
if (usedIds[id]) {
|
||||
var suffix = 2;
|
||||
while (usedIds[id + "-" + suffix]) {
|
||||
suffix++;
|
||||
}
|
||||
id = id + "-" + suffix;
|
||||
}
|
||||
usedIds[id] = true;
|
||||
|
||||
return {
|
||||
id: id,
|
||||
name: name,
|
||||
icon: game.icon ? String(game.icon) : "\uD83C\uDFAE",
|
||||
iconClass: game.iconClass ? String(game.iconClass) : "icon-blue",
|
||||
iconSvg: game.iconSvg ? String(game.iconSvg) : "",
|
||||
description: String(game.description || game.desc || ""),
|
||||
players: String(game.players || ""),
|
||||
tag: String(game.tag || ""),
|
||||
tagClass: String(game.tagClass || "tag-team"),
|
||||
url: String(game.url || "#"),
|
||||
healthUrl: String(game.healthUrl || game.health_url || ""),
|
||||
healthEnabled: game.healthEnabled !== false
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeConfig(rawConfig) {
|
||||
var configObject = Array.isArray(rawConfig)
|
||||
? { games: rawConfig }
|
||||
: (rawConfig && typeof rawConfig === "object" ? rawConfig : {});
|
||||
|
||||
var settings = Object.assign({}, DEFAULT_SETTINGS, configObject.settings || {});
|
||||
var timeoutMs = Number(settings.healthTimeoutMs);
|
||||
if (!Number.isFinite(timeoutMs) || timeoutMs < 1000) {
|
||||
timeoutMs = DEFAULT_SETTINGS.healthTimeoutMs;
|
||||
}
|
||||
settings.healthChecks = settings.healthChecks !== false;
|
||||
settings.healthTimeoutMs = timeoutMs;
|
||||
|
||||
var sourceGames = Array.isArray(configObject.games) ? configObject.games : [];
|
||||
if (!sourceGames.length) {
|
||||
sourceGames = DEFAULT_GAMES;
|
||||
}
|
||||
|
||||
var usedIds = {};
|
||||
var normalizedGames = sourceGames.map(function(game, index) {
|
||||
return normalizeGame(game, index, usedIds);
|
||||
});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
games: normalizedGames
|
||||
};
|
||||
}
|
||||
|
||||
async function loadExternalConfig() {
|
||||
var sources = [
|
||||
{ path: "/games.json", format: "json" },
|
||||
{ path: "/games.yaml", format: "yaml" },
|
||||
{ path: "/games.yml", format: "yaml" }
|
||||
];
|
||||
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
var source = sources[i];
|
||||
try {
|
||||
var response = await fetch(source.path, { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var parsed;
|
||||
if (source.format === "json") {
|
||||
parsed = await response.json();
|
||||
} else {
|
||||
var yaml = await ensureYamlParser();
|
||||
parsed = yaml.load(await response.text());
|
||||
}
|
||||
|
||||
console.info("Config geladen aus", source.path);
|
||||
return normalizeConfig(parsed);
|
||||
} catch (error) {
|
||||
console.warn("Config konnte nicht gelesen werden:", source.path, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.warn("Keine externe Config gefunden. Fallback wird verwendet.");
|
||||
return normalizeConfig({ games: DEFAULT_GAMES, settings: DEFAULT_SETTINGS });
|
||||
}
|
||||
|
||||
function buildMeta(game) {
|
||||
var parts = [
|
||||
'<span class="health" id="health-' + escapeHtml(game.id) + '">' +
|
||||
'<span class="health-dot checking"></span>' +
|
||||
'<span class="health-label">Pr\u00fcfe...</span>' +
|
||||
'</span>'
|
||||
];
|
||||
if (game.players) {
|
||||
parts.push('<span class="players-badge">' + escapeHtml(game.players) + '</span>');
|
||||
}
|
||||
if (game.tag) {
|
||||
parts.push('<span class="tag ' + escapeHtml(game.tagClass) + '">' + escapeHtml(game.tag) + '</span>');
|
||||
}
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function renderGames(games) {
|
||||
grid.innerHTML = "";
|
||||
games.forEach(function(game, index) {
|
||||
var card = document.createElement("a");
|
||||
card.className = "game";
|
||||
card.href = game.url;
|
||||
card.innerHTML =
|
||||
'<div class="game-top">' +
|
||||
'<div class="game-icon ' + escapeHtml(game.iconClass) + '">' + (game.iconSvg ? '<img src="' + escapeHtml(game.iconSvg) + '"' + ' style="width:32px;height:32px" alt="">' : escapeHtml(game.icon)) + '</div>' +
|
||||
'<div class="game-info">' +
|
||||
'<h2>' + escapeHtml(game.name) + '</h2>' +
|
||||
'<div class="game-meta">' + buildMeta(game) + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<p>' + escapeHtml(game.description) + '</p>' +
|
||||
'<span class="game-arrow">\u2192</span>';
|
||||
|
||||
card.addEventListener("mousemove", function(event) {
|
||||
var rect = card.getBoundingClientRect();
|
||||
card.style.setProperty("--mx", ((event.clientX - rect.left) / rect.width * 100) + "%");
|
||||
card.style.setProperty("--my", ((event.clientY - rect.top) / rect.height * 100) + "%");
|
||||
});
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function checkHealth(game, settings) {
|
||||
var el = document.getElementById("health-" + game.id);
|
||||
if (!el) {
|
||||
return Promise.resolve({ checked: false, online: false });
|
||||
}
|
||||
|
||||
if (!settings.healthChecks || game.healthEnabled === false) {
|
||||
el.innerHTML = '<span class="health-dot"></span><span class="health-label">Kein Check</span>';
|
||||
return Promise.resolve({ checked: false, online: false });
|
||||
}
|
||||
|
||||
var targetUrl = game.healthUrl || game.url;
|
||||
if (!targetUrl || targetUrl === "#") {
|
||||
el.innerHTML = '<span class="health-dot"></span><span class="health-label">Kein Check</span>';
|
||||
return Promise.resolve({ checked: false, online: false });
|
||||
}
|
||||
|
||||
var ctrl = new AbortController();
|
||||
var timer = setTimeout(function() { ctrl.abort(); }, settings.healthTimeoutMs);
|
||||
|
||||
return fetch(targetUrl, { mode: "no-cors", cache: "no-store", signal: ctrl.signal })
|
||||
.then(function() {
|
||||
clearTimeout(timer);
|
||||
el.innerHTML = '<span class="health-dot online"></span><span class="health-label online">Online</span>';
|
||||
return { checked: true, online: true };
|
||||
})
|
||||
.catch(function() {
|
||||
clearTimeout(timer);
|
||||
el.innerHTML = '<span class="health-dot offline"></span><span class="health-label offline">Offline</span>';
|
||||
return { checked: true, online: false };
|
||||
});
|
||||
}
|
||||
|
||||
function updateSummary(results, totalGames) {
|
||||
var dot = document.getElementById("summary-dot");
|
||||
var txt = document.getElementById("summary-text");
|
||||
var checked = 0;
|
||||
var online = 0;
|
||||
|
||||
results.forEach(function(result) {
|
||||
if (result.checked) {
|
||||
checked++;
|
||||
if (result.online) {
|
||||
online++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!totalGames) {
|
||||
dot.className = "dot";
|
||||
txt.textContent = "Keine Spiele konfiguriert";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checked) {
|
||||
dot.className = "dot";
|
||||
txt.textContent = "Health-Checks deaktiviert";
|
||||
return;
|
||||
}
|
||||
|
||||
if (online === checked) {
|
||||
dot.className = "dot all-good";
|
||||
txt.textContent = "Alle " + online + " Services online";
|
||||
} else {
|
||||
dot.className = "dot some-down";
|
||||
txt.textContent = online + "/" + checked + " Services online";
|
||||
}
|
||||
|
||||
if (checked < totalGames) {
|
||||
txt.textContent += " (" + (totalGames - checked) + " ohne Check)";
|
||||
}
|
||||
}
|
||||
|
||||
(async function init() {
|
||||
var config = await loadExternalConfig();
|
||||
renderGames(config.games);
|
||||
var healthResults = await Promise.all(
|
||||
config.games.map(function(game) {
|
||||
return checkHealth(game, config.settings);
|
||||
})
|
||||
);
|
||||
updateSummary(healthResults, config.games.length);
|
||||
})().catch(function(error) {
|
||||
console.error("Landing konnte nicht initialisiert werden:", error);
|
||||
document.getElementById("summary-text").textContent = "Fehler beim Laden";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
4
massivedecks-icon.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
||||
<path d="M273 20c-11 0-21 9-23 21l-9 88h-8L39 163c-12 2-21 15-19 27l50 283c2 12 15 21 27 19l194-34c12-3 21-15 18-27l-13-73 140 14c13 2 25-8 26-20l30-286c1-13-8-24-21-25L276 20h-3zm0 16h1l196 21c4 0 6 3 6 7l-30 286c0 4-4 7-8 6l-144-15-35-193-3-9 10-97c1-3 4-6 7-6zm47 106l-10 99 39 4c15 2 25 0 32-8 9-8 15-21 16-37 2-15-1-29-8-39-6-9-15-13-30-15zm19 20l18 1c16 2 22 14 20 35-3 22-11 32-27 31l-18-2zm-135 91l17 98-20 4-13-77-4 80-20 3-32-73 14 76-20 4-18-98 31-6 31 75 4-81z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 629 B |
176
minecraft.html
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FTB StoneBlock 4 - Server Info</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: #07070f; color: #d0d0d0; min-height: 100vh; }
|
||||
.bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; background: radial-gradient(ellipse 80% 60% at 15% 45%, rgba(16,185,129,0.12) 0%, transparent 60%), radial-gradient(ellipse 60% 50% at 85% 25%, rgba(59,130,246,0.08) 0%, transparent 55%); }
|
||||
.bg::after { content: ''; position: absolute; inset: 0; background-image: radial-gradient(rgba(255,255,255,0.03) 1px, transparent 1px); background-size: 32px 32px; }
|
||||
.container { position: relative; z-index: 1; max-width: 800px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; }
|
||||
header { text-align: center; margin-bottom: 2rem; }
|
||||
.logo { font-size: 3rem; margin-bottom: 0.3rem; color: #34d399; }
|
||||
h1 { font-size: 2.2rem; font-weight: 800; letter-spacing: -0.03em; background: linear-gradient(135deg, #6ee7b7 0%, #34d399 40%, #10b981 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
|
||||
.subtitle { color: #555; font-size: 0.9rem; margin-top: 0.4rem; }
|
||||
.server-info { display: flex; flex-wrap: wrap; gap: 0.8rem; justify-content: center; margin: 1.5rem 0 2rem; }
|
||||
.info-badge { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 1rem; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06); border-radius: 99px; font-size: 0.82rem; color: #aaa; }
|
||||
.info-badge strong { color: #f0f0f0; }
|
||||
.info-badge svg { width: 16px; height: 16px; color: #777; flex-shrink: 0; }
|
||||
.info-badge .copy-btn { background: none; border: none; color: #666; cursor: pointer; padding: 0 0.2rem; transition: color 0.2s; display: inline-flex; align-items: center; }
|
||||
.info-badge .copy-btn svg { width: 14px; height: 14px; }
|
||||
.info-badge .copy-btn:hover { color: #10b981; }
|
||||
.download-btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.7rem 1.5rem; margin: 0.5rem 0.5rem 2rem; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: #fff; font-weight: 600; font-size: 0.9rem; border: none; border-radius: 12px; cursor: pointer; text-decoration: none; transition: all 0.3s; }
|
||||
.download-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(16,185,129,0.3); }
|
||||
.download-btn.orange { background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); }
|
||||
.download-btn.orange:hover { box-shadow: 0 8px 24px rgba(245,158,11,0.3); }
|
||||
.steps { background: rgba(255,255,255,0.025); border: 1px solid rgba(255,255,255,0.05); border-radius: 16px; padding: 1.5rem; margin-bottom: 2rem; }
|
||||
.steps h2 { font-size: 1.1rem; font-weight: 700; color: #f0f0f0; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
||||
.steps h2 svg { width: 20px; height: 20px; color: #34d399; flex-shrink: 0; }
|
||||
.steps ol { padding-left: 1.4rem; }
|
||||
.steps li { margin-bottom: 0.7rem; font-size: 0.88rem; line-height: 1.6; color: #bbb; }
|
||||
.steps li strong { color: #f0f0f0; }
|
||||
.steps code { background: rgba(16,185,129,0.1); color: #6ee7b7; padding: 0.15rem 0.5rem; border-radius: 6px; font-size: 0.82rem; }
|
||||
h2.section-title { font-size: 1.3rem; font-weight: 800; color: #f0f0f0; margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid rgba(255,255,255,0.06); display: flex; align-items: center; gap: 0.5rem; }
|
||||
h2.section-title svg { width: 20px; height: 20px; color: #34d399; flex-shrink: 0; }
|
||||
.cmd-grid { display: grid; gap: 0.5rem; margin-bottom: 1.5rem; }
|
||||
.cmd-row { display: grid; grid-template-columns: 1fr 1.2fr; background: rgba(255,255,255,0.025); border: 1px solid rgba(255,255,255,0.04); border-radius: 10px; overflow: hidden; }
|
||||
.cmd-row:hover { border-color: rgba(16,185,129,0.2); }
|
||||
.cmd-row.admin { border-left: 3px solid rgba(239,68,68,0.4); }
|
||||
.cmd-row.player { border-left: 3px solid rgba(34,197,94,0.4); }
|
||||
.cmd-cmd { padding: 0.6rem 0.9rem; font-family: monospace; font-size: 0.78rem; color: #6ee7b7; background: rgba(16,185,129,0.04); display: flex; align-items: center; word-break: break-all; }
|
||||
.cmd-desc { padding: 0.6rem 0.9rem; font-size: 0.82rem; color: #999; display: flex; align-items: center; gap: 0.5rem; }
|
||||
.badge { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; padding: 2px 6px; border-radius: 4px; white-space: nowrap; }
|
||||
.badge-admin { background: rgba(239,68,68,0.15); color: #f87171; }
|
||||
.badge-player { background: rgba(34,197,94,0.15); color: #4ade80; }
|
||||
.legend { display: flex; gap: 1.2rem; justify-content: center; margin: 1rem 0 0.5rem; font-size: 0.78rem; color: #777; }
|
||||
.legend span { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
.legend-dot { width: 10px; height: 3px; border-radius: 2px; }
|
||||
.legend-dot.admin { background: rgba(239,68,68,0.6); }
|
||||
.legend-dot.player { background: rgba(34,197,94,0.6); }
|
||||
.back-link { display: inline-flex; align-items: center; gap: 0.4rem; color: #555; font-size: 0.8rem; text-decoration: none; margin-bottom: 1.5rem; transition: color 0.2s; }
|
||||
.back-link:hover { color: #10b981; }
|
||||
.mc-status { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.4rem 1rem; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06); border-radius: 99px; font-size: 0.78rem; color: #777; margin-bottom: 1rem; }
|
||||
.mc-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; transition: all 0.4s; }
|
||||
.mc-dot.online { background: #22c55e; box-shadow: 0 0 8px rgba(34,197,94,0.6); animation: glow 2.5s ease-in-out infinite; }
|
||||
.mc-dot.offline { background: #ef4444; box-shadow: 0 0 4px rgba(239,68,68,0.4); }
|
||||
@keyframes glow { 0%,100% { box-shadow: 0 0 4px rgba(34,197,94,0.4); } 50% { box-shadow: 0 0 10px rgba(34,197,94,0.7); } }
|
||||
footer { text-align: center; margin-top: 2rem; font-size: 0.72rem; color: #333; }
|
||||
@media (max-width: 640px) { h1 { font-size: 1.7rem; } .cmd-row { grid-template-columns: 1fr; } .container { padding: 1.5rem 1rem 3rem; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg"></div>
|
||||
<div class="container">
|
||||
<a href="https://games.joshuahirsig.xyz" class="back-link"><i data-lucide="arrow-left" style="width:14px;height:14px"></i> Zurück zu Game Night</a>
|
||||
<header>
|
||||
<div class="logo"><i data-lucide="pickaxe"></i></div>
|
||||
<h1>FTB StoneBlock 4</h1>
|
||||
<p class="subtitle">Minecraft Modpack Server</p>
|
||||
</header>
|
||||
<div style="text-align:center">
|
||||
<div class="mc-status"><span class="mc-dot" id="mc-dot"></span><span id="mc-status-text">Server wird geprüft...</span></div>
|
||||
<div class="server-info">
|
||||
<div class="info-badge"><i data-lucide="globe"></i> Server: <strong>joshuahirsig.xyz</strong> <button class="copy-btn" onclick="navigator.clipboard.writeText('joshuahirsig.xyz')" title="Kopieren"><i data-lucide="clipboard"></i></button></div>
|
||||
<div class="info-badge"><i data-lucide="lock"></i> Online Mode (Mojang-Account)</div>
|
||||
<div class="info-badge"><i data-lucide="users"></i> Max 20 Spieler</div>
|
||||
<div class="info-badge"><i data-lucide="cpu"></i> Minecraft 1.21.1 / NeoForge</div>
|
||||
</div>
|
||||
<a href="https://www.curseforge.com/minecraft/modpacks/ftb-stoneblock-4" target="_blank" class="download-btn"><i data-lucide="download"></i> Modpack bei CurseForge</a>
|
||||
<a href="https://www.feed-the-beast.com/modpacks/130-ftb-stoneblock-4" target="_blank" class="download-btn orange"><i data-lucide="download"></i> Modpack bei FTB App</a>
|
||||
</div>
|
||||
<div class="steps">
|
||||
<h2><i data-lucide="rocket"></i> So kommst du auf den Server</h2>
|
||||
<ol>
|
||||
<li><strong>FTB App</strong> oder <strong>CurseForge App</strong> installieren</li>
|
||||
<li><strong>FTB StoneBlock 4</strong> Modpack installieren und starten</li>
|
||||
<li>Im Hauptmenu: <strong>Multiplayer</strong> → <strong>Server hinzufügen</strong></li>
|
||||
<li>Server-Adresse eingeben: <code>joshuahirsig.xyz</code></li>
|
||||
<li>Verbinden — du landest in der <strong>Lobby</strong></li>
|
||||
<li>Durch ein <strong>Portal</strong> gehen und eine Base wählen (Stone, Cherry, Cave, etc.)</li>
|
||||
<li>Viel Spass beim Graben</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="legend"><span><span class="legend-dot player"></span> Alle Spieler</span><span><span class="legend-dot admin"></span> Nur Admin (OP)</span></div>
|
||||
|
||||
<h2 class="section-title"><i data-lucide="home"></i> FTB Team Bases</h2>
|
||||
<div class="cmd-grid">
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteams party create <Name></div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Neues Team erstellen</div></div>
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteams party invite <Name></div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Spieler ins Team einladen</div></div>
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteams party kick <Name></div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Spieler aus Team werfen</div></div>
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteams party leave</div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Team verlassen</div></div>
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteambases home</div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Zur eigenen Base teleportieren</div></div>
|
||||
<div class="cmd-row player"><div class="cmd-cmd">/ftbteambases lobby</div><div class="cmd-desc"><span class="badge badge-player">Alle</span> Zurück zur Lobby</div></div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title"><i data-lucide="shield"></i> Spieler-Verwaltung</h2>
|
||||
<div class="cmd-grid">
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/whitelist add <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler zur Whitelist hinzufügen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/whitelist remove <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler von Whitelist entfernen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/whitelist list</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Alle Spieler auf der Whitelist</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/kick <Name> [Grund]</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler kicken</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/ban <Name> [Grund]</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler permanent bannen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/pardon <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Ban aufheben</div></div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title"><i data-lucide="compass"></i> Teleport & Gamemode</h2>
|
||||
<div class="cmd-grid">
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/tp <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Zu Spieler teleportieren</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/tp <Name> <x> <y> <z></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler zu Koordinaten teleportieren</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/gamemode survival <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Überlebensmodus</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/gamemode creative <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Kreativmodus</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/gamemode spectator <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Zuschauermodus</div></div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title"><i data-lucide="settings"></i> Server-Verwaltung</h2>
|
||||
<div class="cmd-grid">
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/difficulty easy|normal|hard</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Schwierigkeit ändern</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/gamerule keepInventory true</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Inventar beim Tod behalten</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/gamerule doDaylightCycle false</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Tag/Nacht-Zyklus stoppen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/time set day</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Tageszeit auf Tag setzen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/weather clear</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Wetter auf klar setzen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/save-all</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Welt manuell speichern</div></div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title"><i data-lucide="package"></i> Items & Effekte</h2>
|
||||
<div class="cmd-grid">
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/give <Name> <Item> [Anzahl]</div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Item geben</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/clear <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Inventar leeren</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/effect give <Name> <Effekt> <Dauer></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Effekt geben</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/effect clear <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Alle Effekte entfernen</div></div>
|
||||
<div class="cmd-row admin"><div class="cmd-cmd">/kill <Name></div><div class="cmd-desc"><span class="badge badge-admin">Admin</span> Spieler/Entity töten</div></div>
|
||||
</div>
|
||||
<footer>Hosted on joshuahirsig.xyz</footer>
|
||||
</div>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
(function() {
|
||||
var dot = document.getElementById('mc-dot');
|
||||
var txt = document.getElementById('mc-status-text');
|
||||
fetch('https://minecraft.joshuahirsig.xyz/api/status')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.online) {
|
||||
dot.className = 'mc-dot online';
|
||||
var players = data.players + '/' + data.max + ' Spieler';
|
||||
txt.textContent = 'Server Online - ' + players;
|
||||
txt.style.color = '#4ade80';
|
||||
} else {
|
||||
dot.className = 'mc-dot offline';
|
||||
txt.textContent = 'Server Offline';
|
||||
txt.style.color = '#f87171';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
dot.className = 'mc-dot offline';
|
||||
txt.textContent = 'Status unbekannt';
|
||||
txt.style.color = '#f87171';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
7
pokemon-icon.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="48" fill="#cc1a1a" stroke="#111" stroke-width="4"/>
|
||||
<rect x="2" y="47" width="96" height="6" fill="#111"/>
|
||||
<circle cx="50" cy="50" r="12" fill="#111"/>
|
||||
<circle cx="50" cy="50" r="8" fill="#e8e8e8" stroke="#111" stroke-width="2"/>
|
||||
<text x="50" y="30" text-anchor="middle" font-size="22" font-weight="bold" fill="#fff" font-family="sans-serif">✛</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 460 B |
8
scribble-icon.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<path d="M65 15l20 20-45 45-25 5 5-25z" stroke="#ec4899" stroke-width="4" stroke-linejoin="round"/>
|
||||
<path d="M60 20l20 20" stroke="#ec4899" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M20 80l25-5" stroke="#ec4899" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="30" cy="55" r="4" fill="#f9a8d4"/>
|
||||
<circle cx="45" cy="40" r="3" fill="#f9a8d4"/>
|
||||
<circle cx="22" cy="68" r="3" fill="#f9a8d4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 498 B |
8
tabletop-icon.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<rect x="15" y="15" width="70" height="70" rx="12" stroke="#10b981" stroke-width="4"/>
|
||||
<circle cx="35" cy="35" r="5" fill="#6ee7b7"/>
|
||||
<circle cx="65" cy="35" r="5" fill="#6ee7b7"/>
|
||||
<circle cx="35" cy="65" r="5" fill="#6ee7b7"/>
|
||||
<circle cx="65" cy="65" r="5" fill="#6ee7b7"/>
|
||||
<circle cx="50" cy="50" r="5" fill="#6ee7b7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
9
tosios-icon.svg
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<circle cx="50" cy="50" r="35" stroke="#ef4444" stroke-width="4"/>
|
||||
<circle cx="50" cy="50" r="20" stroke="#ef4444" stroke-width="3"/>
|
||||
<circle cx="50" cy="50" r="5" fill="#fca5a5"/>
|
||||
<line x1="50" y1="8" x2="50" y2="22" stroke="#ef4444" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="50" y1="78" x2="50" y2="92" stroke="#ef4444" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="8" y1="50" x2="22" y2="50" stroke="#ef4444" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="78" y1="50" x2="92" y2="50" stroke="#ef4444" stroke-width="3" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 663 B |
7
trivia-icon.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
||||
<path d="M50 10C30 10 18 25 18 40c0 10 5 18 12 23v12h40V63c7-5 12-13 12-23C82 25 70 10 50 10z" stroke="#f59e0b" stroke-width="4" stroke-linejoin="round"/>
|
||||
<line x1="35" y1="85" x2="65" y2="85" stroke="#f59e0b" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="38" y1="93" x2="62" y2="93" stroke="#f59e0b" stroke-width="4" stroke-linecap="round"/>
|
||||
<line x1="50" y1="30" x2="50" y2="50" stroke="#fcd34d" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="50" cy="57" r="2.5" fill="#fcd34d"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
BIN
worldguessr-icon.png
Normal file
|
After Width: | Height: | Size: 77 KiB |