935 lines
38 KiB
HTML
935 lines
38 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>AI GameJam — Human in the Loop · TechDay Bruchsal 2026</title>
|
||
<meta name="description" content="AI GameJam beim TechDay Bruchsal 2026: In zwei Stunden mit KI ein spielbares Game bauen. Human in the Loop." />
|
||
<link rel="canonical" href="https://tool-ctrl.de/gamejam">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:locale" content="de_DE">
|
||
<meta property="og:site_name" content="ToolCTRL">
|
||
<meta property="og:title" content="AI GameJam — Human in the Loop">
|
||
<meta property="og:description" content="Zwei Stunden, KI, ein spielbares Ergebnis: die AI GameJam beim TechDay Bruchsal 2026.">
|
||
<meta property="og:url" content="https://tool-ctrl.de/gamejam">
|
||
<meta property="og:image" content="https://tool-ctrl.de/assets/logo-a.webp">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="theme-color" content="#0c0c0e">
|
||
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
||
<link rel="preload" href="/assets/fonts/space-grotesk-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||
<style>
|
||
@font-face {
|
||
font-family: "Space Grotesk";
|
||
font-style: normal;
|
||
font-weight: 300 700;
|
||
font-display: swap;
|
||
src: url(/assets/fonts/space-grotesk-latin.woff2) format("woff2");
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
|
||
U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
|
||
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: "Space Grotesk";
|
||
font-style: normal;
|
||
font-weight: 300 700;
|
||
font-display: swap;
|
||
src: url(/assets/fonts/space-grotesk-latin-ext.woff2) format("woff2");
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
|
||
U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
|
||
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
|
||
U+A720-A7FF;
|
||
}
|
||
|
||
:root {
|
||
--bg: #ffffff;
|
||
--bg2: #f5f5f6;
|
||
--panel: #f3f3f4;
|
||
--panel2: #ededee;
|
||
--line: #e4e4e6;
|
||
--line2: #d2d2d6;
|
||
--text: #0c0c0e;
|
||
--muted: #6b6b73;
|
||
--ac: #0c0c0e;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
font-family: 'Space Grotesk', -apple-system, sans-serif;
|
||
background: var(--bg); color: var(--text);
|
||
line-height: 1.6; -webkit-font-smoothing: antialiased; overflow-x: hidden;
|
||
}
|
||
.mono { font-family: 'Space Mono', monospace; }
|
||
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 32px; }
|
||
svg.ic { width: 1em; height: 1em; stroke: currentColor; stroke-width: 1.8;
|
||
fill: none; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
|
||
|
||
section { padding: 100px 0; position: relative; }
|
||
section::before {
|
||
content: ""; position: absolute; top: 0; left: 32px; right: 32px;
|
||
height: 1px; background: var(--line);
|
||
}
|
||
.seclabel {
|
||
font-family: 'Space Mono', monospace; font-size: 12px; letter-spacing: 3px;
|
||
text-transform: uppercase; color: var(--ac); margin-bottom: 34px;
|
||
display: flex; align-items: center; gap: 12px;
|
||
}
|
||
.seclabel::before {
|
||
content: ""; width: 8px; height: 8px; background: var(--ac);
|
||
transform: rotate(45deg); flex-shrink: 0;
|
||
}
|
||
|
||
/* ===== TOPBAR ===== */
|
||
.topbar {
|
||
position: sticky; top: 0; z-index: 50;
|
||
background: rgba(255,255,255,0.85); backdrop-filter: blur(12px);
|
||
border-bottom: 1px solid var(--line);
|
||
}
|
||
.topbar .inner {
|
||
max-width: 1120px; margin: 0 auto; padding: 13px 32px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
}
|
||
.topbar img { display: block; }
|
||
.topbar .tc { height: 28px; }
|
||
.topbar .left { display: flex; align-items: center; gap: 18px; }
|
||
.topbar .back {
|
||
display: inline-flex; align-items: center; gap: 7px;
|
||
font-family: 'Space Mono', monospace; font-size: 11px;
|
||
letter-spacing: 1px; text-transform: uppercase;
|
||
color: var(--muted); text-decoration: none;
|
||
border-left: 1px solid var(--line); padding-left: 18px;
|
||
transition: color .15s;
|
||
}
|
||
.topbar .back:hover { color: var(--ac); }
|
||
.topbar .back svg.ic { width: 13px; height: 13px; }
|
||
.topbar .right { display: flex; align-items: center; gap: 20px; }
|
||
.topbar .mit { height: 26px; }
|
||
.topbar .ev {
|
||
font-family: 'Space Mono', monospace; font-size: 11px;
|
||
color: var(--muted); letter-spacing: 1px; text-transform: uppercase;
|
||
}
|
||
|
||
/* ===== HERO ===== */
|
||
.hero {
|
||
min-height: calc(100vh - 53px); display: flex; flex-direction: column;
|
||
justify-content: center; align-items: center; text-align: center;
|
||
padding: 80px 32px; position: relative; overflow: hidden;
|
||
}
|
||
.hero .bg {
|
||
position: absolute; inset: 0; z-index: 0;
|
||
background-image:
|
||
linear-gradient(rgba(0,0,0,0.045) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(0,0,0,0.045) 1px, transparent 1px);
|
||
background-size: 54px 54px;
|
||
mask-image: radial-gradient(ellipse 70% 65% at 50% 42%, #000 30%, transparent 80%);
|
||
-webkit-mask-image: radial-gradient(ellipse 70% 65% at 50% 42%, #000 30%, transparent 80%);
|
||
}
|
||
.hero > * { position: relative; z-index: 1; }
|
||
.hero .kicker {
|
||
font-family: 'Space Mono', monospace; font-size: 13px; letter-spacing: 4px;
|
||
text-transform: uppercase; color: var(--ac); margin-bottom: 26px;
|
||
animation: heroRise .24s cubic-bezier(.2, 0, 0, 1) both;
|
||
}
|
||
.hero h1 {
|
||
font-size: clamp(52px, 11vw, 142px); font-weight: 700; line-height: 0.93;
|
||
letter-spacing: -0.04em; text-transform: uppercase; margin-bottom: 24px;
|
||
animation: heroRise .24s cubic-bezier(.2, 0, 0, 1) .04s both;
|
||
}
|
||
.hero h1 .ln2 { color: var(--ac); }
|
||
.hero .hsub {
|
||
font-size: clamp(15px, 2vw, 19px); color: var(--muted);
|
||
max-width: 540px; margin-bottom: 56px;
|
||
animation: heroRise .24s cubic-bezier(.2, 0, 0, 1) .08s both;
|
||
}
|
||
.hero .hsub b { color: var(--text); font-weight: 600; }
|
||
|
||
/* HUD panel */
|
||
.hud {
|
||
position: relative; background: var(--panel);
|
||
border: 1px solid var(--line2); padding: 36px 56px;
|
||
min-width: min(560px, 92vw);
|
||
animation: heroRise .26s cubic-bezier(.2, 0, 0, 1) .12s both;
|
||
}
|
||
.hud .cn {
|
||
position: absolute; width: 14px; height: 14px;
|
||
border: 1.5px solid var(--ac);
|
||
}
|
||
.hud .cn.tl { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
|
||
.hud .cn.br { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }
|
||
.hud .live {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
font-family: 'Space Mono', monospace; font-size: 11px; letter-spacing: 2.5px;
|
||
text-transform: uppercase; color: var(--ac); margin-bottom: 16px;
|
||
}
|
||
.hud .live .dot {
|
||
width: 7px; height: 7px; background: var(--ac); border-radius: 50%;
|
||
animation: pulse 1.8s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse { 0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(0,0,0,.32);}
|
||
50%{opacity:.5;box-shadow:0 0 0 6px rgba(0,0,0,0);} }
|
||
.hud .pn {
|
||
font-size: clamp(26px, 4vw, 40px); font-weight: 700; line-height: 1.12;
|
||
letter-spacing: -0.02em; text-transform: uppercase; margin-bottom: 4px;
|
||
}
|
||
.hud .ps { color: var(--muted); font-size: 14px; margin-bottom: 26px; }
|
||
.hud .timer {
|
||
font-family: 'Space Mono', monospace; font-weight: 700;
|
||
font-size: clamp(54px, 11vw, 108px); line-height: 1;
|
||
letter-spacing: -0.02em; color: var(--text);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.hud .timer.tick {
|
||
animation: timerTick .16s cubic-bezier(.2, 0, 0, 1);
|
||
}
|
||
.hud .tl {
|
||
font-family: 'Space Mono', monospace; font-size: 11px; letter-spacing: 2px;
|
||
text-transform: uppercase; color: var(--muted); margin-top: 10px;
|
||
}
|
||
.hmeta {
|
||
margin-top: 44px; display: flex; gap: 14px; flex-wrap: wrap; justify-content: center;
|
||
animation: heroRise .24s cubic-bezier(.2, 0, 0, 1) .16s both;
|
||
}
|
||
.hmeta span {
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
font-family: 'Space Mono', monospace; font-size: 12px; color: var(--muted);
|
||
border: 1px solid var(--line); padding: 8px 14px; text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
}
|
||
.hmeta svg.ic { width: 14px; height: 14px; color: var(--ac); }
|
||
|
||
h2.big {
|
||
font-size: clamp(30px, 5vw, 52px); font-weight: 700; line-height: 1.05;
|
||
letter-spacing: -0.03em; text-transform: uppercase; margin-bottom: 18px;
|
||
}
|
||
.lead { font-size: 18px; color: var(--muted); max-width: 680px; }
|
||
.lead b { color: var(--text); font-weight: 600; }
|
||
|
||
/* ===== TIMELINE ===== */
|
||
.tl-row {
|
||
display: grid; grid-template-columns: 96px 1fr 26px; gap: 22px;
|
||
align-items: center; padding: 17px 20px;
|
||
border-left: 2px solid var(--line); margin-bottom: 2px;
|
||
transition: background .15s;
|
||
}
|
||
.tl-row .tt {
|
||
font-family: 'Space Mono', monospace; font-weight: 700; font-size: 15px;
|
||
color: var(--muted);
|
||
}
|
||
.tl-row .tn { font-weight: 600; font-size: 16px; text-transform: uppercase; letter-spacing: -0.01em; }
|
||
.tl-row .ts { color: var(--muted); font-size: 13px; }
|
||
.tl-row .tx { color: var(--ac); }
|
||
.tl-row.done { opacity: 0.38; }
|
||
.tl-row.active {
|
||
background: var(--panel); border-left-color: var(--ac);
|
||
box-shadow: inset 10px 0 18px -18px rgba(0,0,0,.45);
|
||
}
|
||
.tl-row.active .tt { color: var(--ac); }
|
||
.tl-row.active .tn { color: var(--text); }
|
||
|
||
/* ===== STEPS ===== */
|
||
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; }
|
||
.step {
|
||
position: relative; padding: 30px 34px 8px 0; overflow: hidden;
|
||
}
|
||
.step + .step { padding-left: 34px; border-left: 1px solid var(--line); }
|
||
.step .gn {
|
||
position: absolute; top: 6px; right: 14px; font-family: 'Space Mono', monospace;
|
||
font-size: 110px; font-weight: 700; line-height: 1;
|
||
color: transparent; -webkit-text-stroke: 1px var(--line2);
|
||
pointer-events: none; z-index: 0;
|
||
}
|
||
.step > *:not(.gn) { position: relative; z-index: 1; }
|
||
.step .sic { color: var(--ac); margin-bottom: 20px; }
|
||
.step .sic svg.ic { width: 28px; height: 28px; }
|
||
.step .sk {
|
||
font-family: 'Space Mono', monospace; font-size: 11px; letter-spacing: 2px;
|
||
text-transform: uppercase; color: var(--muted);
|
||
}
|
||
.step h3 {
|
||
font-size: 26px; font-weight: 700; letter-spacing: -0.02em;
|
||
text-transform: uppercase; margin: 6px 0 12px;
|
||
}
|
||
.step p { color: var(--muted); font-size: 15px; }
|
||
|
||
/* ===== CATEGORIES ===== */
|
||
.cats { display: grid; grid-template-columns: repeat(4, 1fr); }
|
||
.catcol { padding: 0 26px; border-left: 1px solid var(--line); }
|
||
.catcol:first-child { padding-left: 0; border-left: none; }
|
||
.catcol .cnum { font-family: 'Space Mono', monospace; font-size: 12px; color: var(--ac); }
|
||
.catcol .cn {
|
||
font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
|
||
text-transform: uppercase; margin: 8px 0 4px;
|
||
}
|
||
.catcol .cs { color: var(--muted); font-size: 14px; }
|
||
.catcol .cv {
|
||
font-family: 'Space Mono', monospace; font-size: 12px; color: var(--ac);
|
||
margin-top: 16px; letter-spacing: 1px;
|
||
}
|
||
|
||
/* ===== PRIZES ===== */
|
||
.prizes { display: grid; grid-template-columns: 1.4fr 1fr 1fr; }
|
||
.prizeitem {
|
||
padding: 36px 32px 36px 0; border-top: 2px solid var(--line);
|
||
}
|
||
.prizeitem + .prizeitem { padding-left: 32px; border-left: 1px solid var(--line); }
|
||
.prizeitem.first { border-top-color: var(--ac); }
|
||
.prizeitem .pr {
|
||
font-family: 'Space Mono', monospace; font-size: 12px; letter-spacing: 2px;
|
||
text-transform: uppercase; color: var(--muted);
|
||
}
|
||
.prizeitem.first .pr { color: var(--ac); }
|
||
.prizeitem h3 {
|
||
font-size: clamp(26px, 3vw, 40px); font-weight: 700; letter-spacing: -0.03em;
|
||
text-transform: uppercase; margin: 14px 0 6px; line-height: 1;
|
||
}
|
||
.prizeitem.first h3 { color: var(--ac); }
|
||
.prizeitem .dur {
|
||
font-family: 'Space Mono', monospace; font-size: 13px; color: var(--muted);
|
||
text-transform: uppercase; letter-spacing: 1px;
|
||
}
|
||
|
||
/* ===== UPLOAD ===== */
|
||
.uploadblock { max-width: 560px; }
|
||
.uploadblock h3 {
|
||
font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
|
||
text-transform: uppercase; margin-bottom: 20px;
|
||
}
|
||
.field { margin: 14px 0; }
|
||
.field label {
|
||
display: block; font-family: 'Space Mono', monospace; font-size: 11px;
|
||
color: var(--muted); margin-bottom: 7px; text-transform: uppercase; letter-spacing: 1.5px;
|
||
}
|
||
.field input {
|
||
width: 100%; background: var(--bg2); border: 1px solid var(--line2);
|
||
padding: 13px 15px; color: var(--text); font-size: 15px; font-family: inherit;
|
||
transition: border-color .15s ease, background .15s ease;
|
||
}
|
||
.field input:focus { outline: none; border-color: var(--ac); background: #fff; }
|
||
.dropzone {
|
||
border: 1.5px dashed var(--line2); padding: 40px 24px; text-align: center;
|
||
cursor: pointer;
|
||
transition: border-color .15s ease, background .15s ease, transform .15s ease;
|
||
background: var(--bg2);
|
||
}
|
||
.dropzone:hover, .dropzone.drag {
|
||
border-color: var(--ac);
|
||
background: var(--panel);
|
||
transform: translateY(-2px);
|
||
}
|
||
.dropzone .dzi { color: var(--ac); margin-bottom: 12px; }
|
||
.dropzone .dzi svg.ic { width: 28px; height: 28px; }
|
||
.dropzone .big { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
|
||
.dropzone .sm { color: var(--muted); font-size: 13px; font-family: 'Space Mono', monospace; }
|
||
.btn {
|
||
position: relative; isolation: isolate; overflow: visible;
|
||
background: linear-gradient(180deg, #3c3c3f 0%, #161617 60%, #070708 100%);
|
||
color: #ffffff; border: 2px solid #0c0c0e; border-radius: 7px;
|
||
padding: 13px 30px; font-size: 14px; font-weight: 700; cursor: pointer;
|
||
font-family: inherit; text-transform: uppercase; letter-spacing: 1px;
|
||
box-shadow: 0 7px 0 #000000, 0 11px 0 #343437, 0 17px 18px -10px rgba(0,0,0,.48),
|
||
inset 0 2px 0 rgba(255,255,255,.22);
|
||
transition: box-shadow .12s ease, transform .12s ease, background .15s ease;
|
||
}
|
||
.btn::after {
|
||
content: ""; position: absolute; left: 5px; right: -6px; bottom: -8px;
|
||
height: 8px; z-index: -1; border: 2px solid #0c0c0e; border-top: 0;
|
||
border-radius: 0 0 7px 7px; background: #0c0c0e;
|
||
transition: height .12s ease, bottom .12s ease;
|
||
}
|
||
.btn:hover { transform: translateY(-2px); box-shadow: 0 9px 0 #000000, 0 13px 0 #343437, 0 19px 20px -10px rgba(0,0,0,.5),
|
||
inset 0 2px 0 rgba(255,255,255,.22); }
|
||
.btn:active { transform: translateY(5px) scale(.985); box-shadow: 0 2px 0 #000000, 0 4px 0 #343437, 0 9px 14px -9px rgba(0,0,0,.45); }
|
||
.btn:active::after { bottom: -3px; height: 3px; }
|
||
.btn:disabled { background: var(--line2); color: var(--muted); cursor: not-allowed; box-shadow: none; }
|
||
.btn:disabled::after { display: none; }
|
||
.status {
|
||
font-family: 'Space Mono', monospace; font-size: 13px; margin-top: 16px; min-height: 20px;
|
||
}
|
||
.status.ok { color: var(--ac); }
|
||
.status.err { color: #0c0c0e; font-weight: 700; }
|
||
|
||
/* ===== GALLERY ===== */
|
||
.btn-ghost {
|
||
position: relative; isolation: isolate; overflow: visible;
|
||
background: linear-gradient(180deg, #ffffff 0%, #f1f1f2 62%, #dfdfe1 100%);
|
||
border: 2px solid var(--text); border-radius: 6px; color: var(--text);
|
||
padding: 10px 16px; font-size: 12px; font-weight: 600; cursor: pointer;
|
||
font-family: 'Space Mono', monospace; text-transform: uppercase; letter-spacing: 1px;
|
||
display: inline-flex; align-items: center; gap: 8px;
|
||
box-shadow: 0 5px 0 #000000, 0 8px 0 #bbbbbf, 0 13px 16px -10px rgba(0,0,0,.36);
|
||
transition: box-shadow .12s ease, transform .12s ease;
|
||
}
|
||
.btn-ghost::after {
|
||
content: ""; position: absolute; left: 4px; right: -5px; bottom: -7px;
|
||
height: 7px; z-index: -1; border: 2px solid var(--text); border-top: 0;
|
||
border-radius: 0 0 6px 6px; background: #d9d9dc;
|
||
transition: height .12s ease, bottom .12s ease;
|
||
}
|
||
.btn-ghost:hover { transform: translateY(-2px); box-shadow: 0 7px 0 #000000, 0 10px 0 #bbbbbf, 0 15px 18px -10px rgba(0,0,0,.38); }
|
||
.btn-ghost:active { transform: translateY(4px) scale(.985); box-shadow: 0 1px 0 #000000, 0 3px 0 #bbbbbf, 0 7px 12px -9px rgba(0,0,0,.34); }
|
||
.btn-ghost:active::after { bottom: -3px; height: 3px; }
|
||
.btn-ghost svg.ic { width: 13px; height: 13px; }
|
||
|
||
.btn,
|
||
.btn-ghost {
|
||
max-width: 100%;
|
||
min-height: 40px;
|
||
line-height: 1.1;
|
||
white-space: normal;
|
||
text-align: center;
|
||
justify-content: center;
|
||
}
|
||
.btn::after,
|
||
.btn-ghost::after { display: none; }
|
||
.btn {
|
||
display: inline-flex; align-items: center;
|
||
box-shadow: 0 5px 0 #000000, 0 8px 12px -8px rgba(0,0,0,.48),
|
||
inset 0 2px 0 rgba(255,255,255,.22);
|
||
}
|
||
.btn:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 6px 0 #000000, 0 10px 14px -9px rgba(0,0,0,.5),
|
||
inset 0 2px 0 rgba(255,255,255,.22);
|
||
}
|
||
.btn:active {
|
||
transform: translateY(3px) scale(.96);
|
||
box-shadow: 0 2px 0 #000000, 0 7px 10px -9px rgba(0,0,0,.45);
|
||
}
|
||
.btn-ghost {
|
||
box-shadow: 0 5px 0 #000000, 0 8px 12px -8px rgba(0,0,0,.32);
|
||
}
|
||
.btn-ghost:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 6px 0 #000000, 0 10px 14px -9px rgba(0,0,0,.34);
|
||
}
|
||
.btn-ghost:active {
|
||
transform: translateY(3px) scale(.96);
|
||
box-shadow: 0 2px 0 #000000, 0 7px 10px -9px rgba(0,0,0,.3);
|
||
}
|
||
|
||
.btn,
|
||
.btn-ghost {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
max-width: 100%;
|
||
min-height: 40px;
|
||
padding: 11px 20px;
|
||
border: 2px solid var(--text);
|
||
border-radius: 6px;
|
||
line-height: 1.1;
|
||
white-space: normal;
|
||
text-align: center;
|
||
overflow-wrap: anywhere;
|
||
transform: translateY(0);
|
||
box-shadow: 0 4px 0 #000000, 0 10px 16px -12px rgba(0,0,0,.42);
|
||
}
|
||
.btn::before,
|
||
.btn::after,
|
||
.btn-ghost::before,
|
||
.btn-ghost::after {
|
||
display: none;
|
||
}
|
||
.btn:hover,
|
||
.btn-ghost:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 5px 0 #000000, 0 12px 18px -12px rgba(0,0,0,.44);
|
||
}
|
||
.btn:active,
|
||
.btn-ghost:active {
|
||
transform: translateY(2px) scale(.98);
|
||
box-shadow: 0 1px 0 #000000, 0 6px 10px -10px rgba(0,0,0,.4);
|
||
}
|
||
|
||
.btn {
|
||
border-color: #ffffff;
|
||
box-shadow: 0 4px 0 #9f9f9f, 0 10px 16px -12px rgba(255,255,255,.38),
|
||
inset 0 2px 0 rgba(255,255,255,.24);
|
||
}
|
||
.btn:hover {
|
||
box-shadow: 0 5px 0 #bcbcbc, 0 12px 18px -12px rgba(255,255,255,.42),
|
||
inset 0 2px 0 rgba(255,255,255,.24);
|
||
}
|
||
.btn:active {
|
||
box-shadow: 0 1px 0 #bcbcbc, 0 6px 10px -10px rgba(255,255,255,.34);
|
||
}
|
||
|
||
.games { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 6px; }
|
||
.game { background: var(--panel); border: 1px solid var(--line); }
|
||
.game .frame { width: 100%; aspect-ratio: 16/10; border: none; background: var(--panel2); display: block; }
|
||
.game .gm {
|
||
padding: 15px 17px; display: flex; justify-content: space-between;
|
||
align-items: center; gap: 12px; border-top: 1px solid var(--line);
|
||
}
|
||
.game .gt { font-weight: 700; font-size: 15px; text-transform: uppercase; letter-spacing: -0.01em; }
|
||
.game .gte { font-family: 'Space Mono', monospace; font-size: 12px; color: var(--muted); }
|
||
.game .gm a {
|
||
color: var(--ac); font-family: 'Space Mono', monospace; font-size: 12px;
|
||
text-transform: uppercase; letter-spacing: 1px; text-decoration: none;
|
||
white-space: nowrap; display: inline-flex; align-items: center; gap: 5px;
|
||
}
|
||
.game .gm a svg.ic { width: 12px; height: 12px; }
|
||
.notice {
|
||
background: var(--bg2); border: 1px solid var(--line); border-left: 2px solid var(--ac);
|
||
padding: 15px 18px; color: var(--muted); font-size: 14px;
|
||
font-family: 'Space Mono', monospace;
|
||
}
|
||
|
||
/* ===== FOOTER ===== */
|
||
footer { border-top: 1px solid var(--line); padding: 54px 0; }
|
||
footer .inner { display: flex; flex-direction: column; align-items: center; gap: 24px; text-align: center; }
|
||
footer .logos { display: flex; align-items: center; gap: 28px; }
|
||
footer .logos img { height: 30px; opacity: 0.6; }
|
||
footer .logos .tc { height: 32px; }
|
||
footer .ft {
|
||
font-family: 'Space Mono', monospace; color: var(--muted); font-size: 12px;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
footer .links {
|
||
display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 14px;
|
||
font-family: 'Space Mono', monospace; font-size: 12px; letter-spacing: .5px;
|
||
color: var(--muted);
|
||
}
|
||
footer .links a { color: var(--muted); text-decoration: none; }
|
||
footer .links a:hover { color: var(--text); }
|
||
|
||
@keyframes heroRise {
|
||
from { opacity: 0; transform: translateY(6px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes timerTick {
|
||
from { opacity: .78; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
@media (max-width: 820px) {
|
||
.steps { grid-template-columns: 1fr; }
|
||
.step + .step { padding-left: 0; border-left: none; border-top: 1px solid var(--line); }
|
||
.step { padding-right: 0; }
|
||
.cats { grid-template-columns: 1fr 1fr; gap: 24px 0; }
|
||
.prizes { grid-template-columns: 1fr; }
|
||
.prizeitem, .prizeitem + .prizeitem { padding: 28px 0; border-left: none; }
|
||
.games { grid-template-columns: 1fr; }
|
||
.hud { padding: 30px 28px; }
|
||
section { padding: 70px 0; }
|
||
.topbar .ev { display: none; }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
html { scroll-behavior: auto; }
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
animation-duration: .001ms !important;
|
||
animation-iteration-count: 1 !important;
|
||
transition-duration: .001ms !important;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ===================== TOPBAR ===================== -->
|
||
<div class="topbar">
|
||
<div class="inner">
|
||
<div class="left">
|
||
<a href="/" aria-label="Zur ToolCTRL-Startseite"><img class="tc" src="/assets/logo-black.svg" alt="ToolCTRL"></a>
|
||
<a class="back" href="/"><svg class="ic" viewBox="0 0 24 24"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> Startseite</a>
|
||
</div>
|
||
<div class="right">
|
||
<span class="ev">TechDay Bruchsal · 27.05.2026</span>
|
||
<img class="mit" src="/assets/mitup-logo.webp" alt="mITup Bruchsal">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== HERO ===================== -->
|
||
<div class="hero">
|
||
<div class="bg"></div>
|
||
<div class="kicker">AI GameJam // TechDay Bruchsal 2026</div>
|
||
<h1>Human in<br><span class="ln2">the Loop</span></h1>
|
||
<div class="hsub">Ein Spiel bauen mit KI — in zwei Stunden. <b>Der Mensch steuert.</b></div>
|
||
|
||
<div class="hud">
|
||
<span class="cn tl"></span><span class="cn br"></span>
|
||
<div class="live" id="nowLabel"><span class="dot"></span> Jetzt läuft</div>
|
||
<div class="pn" id="phaseName">—</div>
|
||
<div class="ps" id="phaseSub">—</div>
|
||
<div class="timer" id="timer">--:--</div>
|
||
<div class="tl" id="timerLabel">bis zur nächsten Phase</div>
|
||
</div>
|
||
|
||
<div class="hmeta">
|
||
<span><svg class="ic" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="1"/><path d="M16 2v4M8 2v4M3 10h18"/></svg> 27. Mai 2026</span>
|
||
<span><svg class="ic" viewBox="0 0 24 24"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg> TechDay Bruchsal</span>
|
||
<span><svg class="ic" viewBox="0 0 24 24"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg> 15 Teilnehmer</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===================== ABLAUF ===================== -->
|
||
<section id="ablauf">
|
||
<div class="wrap">
|
||
<div class="seclabel">Ablauf</div>
|
||
<div id="schedule"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== THEME ===================== -->
|
||
<section id="theme">
|
||
<div class="wrap">
|
||
<div class="seclabel">Das Thema</div>
|
||
<h2 class="big">Human in the Loop</h2>
|
||
<p class="lead">
|
||
<b>Der Mensch bleibt am Steuer — die KI ist Werkzeug, nicht Autopilot.</b>
|
||
Heute baut ihr ein Spiel, indem ihr die KI führt: Konzept schärfen,
|
||
Entscheidungen treffen, iterieren. Lasst das Thema auch in euer Spiel
|
||
einfließen — die beste Interpretation fließt in die Bewertung ein.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== SO LÄUFT'S ===================== -->
|
||
<section id="howto">
|
||
<div class="wrap">
|
||
<div class="seclabel">So läuft's</div>
|
||
<div class="steps">
|
||
<div class="step">
|
||
<div class="gn">1</div>
|
||
<div class="sic"><svg class="ic" viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div>
|
||
<div class="sk">Schritt 1</div>
|
||
<h3>Planen</h3>
|
||
<p>Im Terminal startet ihr den GDD-Dialog. Die KI stellt gezielte Fragen — am Ende steht euer Game-Konzept.</p>
|
||
</div>
|
||
<div class="step">
|
||
<div class="gn">2</div>
|
||
<div class="sic"><svg class="ic" viewBox="0 0 24 24"><path d="m16 18 6-6-6-6M8 6l-6 6 6 6"/></svg></div>
|
||
<div class="sk">Schritt 2</div>
|
||
<h3>Bauen</h3>
|
||
<p>Dann wird gecodet: ihr beschreibt, die KI baut, ihr testet im Browser und iteriert.</p>
|
||
</div>
|
||
<div class="step">
|
||
<div class="gn">3</div>
|
||
<div class="sic"><svg class="ic" viewBox="0 0 24 24"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6M18 9h1.5a2.5 2.5 0 0 0 0-5H18M4 22h16M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg></div>
|
||
<div class="sk">Schritt 3</div>
|
||
<h3>Zeigen</h3>
|
||
<p>Game hochladen, Demo-Runde, die Jury bewertet. Die Besten gewinnen.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== BEWERTUNG ===================== -->
|
||
<section id="bewertung">
|
||
<div class="wrap">
|
||
<div class="seclabel">Bewertung</div>
|
||
<h2 class="big">Vier Kategorien</h2>
|
||
<p class="lead" style="margin-bottom:44px;">Je 0–10 Punkte. Der Durchschnitt ist die Gesamtnote — die Top 3 gewinnen.</p>
|
||
<div class="cats">
|
||
<div class="catcol"><div class="cnum">/01</div><div class="cn">Spielspaß</div><div class="cs">Macht es Spaß?</div><div class="cv">0 – 10 Punkte</div></div>
|
||
<div class="catcol"><div class="cnum">/02</div><div class="cn">Kreativität</div><div class="cs">Wie originell?</div><div class="cv">0 – 10 Punkte</div></div>
|
||
<div class="catcol"><div class="cnum">/03</div><div class="cn">Theme</div><div class="cs">Human in the Loop</div><div class="cv">0 – 10 Punkte</div></div>
|
||
<div class="catcol"><div class="cnum">/04</div><div class="cn">Look & Feel</div><div class="cs">Präsentation & Game Feel</div><div class="cv">0 – 10 Punkte</div></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== PREISE ===================== -->
|
||
<section id="preise">
|
||
<div class="wrap">
|
||
<div class="seclabel">Preise</div>
|
||
<div class="prizes">
|
||
<div class="prizeitem first">
|
||
<div class="pr">Erster Platz</div>
|
||
<h3>Claude Max 5x</h3>
|
||
<div class="dur">Ein Monat</div>
|
||
</div>
|
||
<div class="prizeitem">
|
||
<div class="pr">Zweiter Platz</div>
|
||
<h3>Claude Pro</h3>
|
||
<div class="dur">Ein Monat</div>
|
||
</div>
|
||
<div class="prizeitem">
|
||
<div class="pr">Dritter Platz</div>
|
||
<h3>Claude Pro</h3>
|
||
<div class="dur">Ein Monat</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== GAMES ===================== -->
|
||
<section id="games">
|
||
<div class="wrap">
|
||
<div class="seclabel">Games</div>
|
||
<h2 class="big">Spiel einreichen</h2>
|
||
|
||
<div class="uploadblock" style="margin:30px 0 60px;">
|
||
<div class="field">
|
||
<label>Teilnehmer</label>
|
||
<input id="teamName" type="text" placeholder="z. B. Anna Schmidt" />
|
||
</div>
|
||
<div class="field">
|
||
<label>Game-Name</label>
|
||
<input id="gameName" type="text" placeholder="z. B. Cosmic Snake" />
|
||
</div>
|
||
<div class="dropzone" id="dropzone">
|
||
<div class="dzi"><svg class="ic" viewBox="0 0 24 24"><path d="M12 13v8M16 16l-4-4-4 4M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg></div>
|
||
<div class="big">HTML-Datei hier ablegen</div>
|
||
<div class="sm">deine fertige .html — klicken zum Auswählen</div>
|
||
<input id="fileInput" type="file" accept=".html,.htm" hidden />
|
||
</div>
|
||
<div style="margin-top:20px;">
|
||
<button class="btn" id="uploadBtn" disabled>Einreichen</button>
|
||
</div>
|
||
<div class="status" id="uploadStatus"></div>
|
||
</div>
|
||
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;">
|
||
<span class="mono" style="color:var(--muted);font-size:13px;" id="galleryCount"></span>
|
||
<button class="btn-ghost" id="refreshBtn">
|
||
<svg class="ic" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5"/></svg> Aktualisieren
|
||
</button>
|
||
</div>
|
||
<div class="games" id="gallery"></div>
|
||
<div class="notice" id="galleryNotice" style="display:none;"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ===================== FOOTER ===================== -->
|
||
<footer>
|
||
<div class="inner">
|
||
<div class="logos">
|
||
<img class="tc" src="/assets/logo-black.svg" alt="ToolCTRL">
|
||
<img src="/assets/mitup-logo.webp" alt="mITup Bruchsal">
|
||
</div>
|
||
<div class="ft">AI GameJam · Human in the Loop · veranstaltet von ToolCTRL beim TechDay Bruchsal 2026</div>
|
||
<div class="links">
|
||
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
|
||
<span>·</span>
|
||
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
|
||
<span>·</span>
|
||
<a href="/impressum">Impressum</a>
|
||
<span>·</span>
|
||
<a href="/datenschutz">Datenschutz</a>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script type="module">
|
||
/* ===================== PHASEN-LOGIK ===================== */
|
||
const EVENT_DATE = "2026-05-27";
|
||
const PHASES = [
|
||
{ t: "17:00", name: "Begrüßung", sub: "Willkommen & Ablauf" },
|
||
{ t: "17:10", name: "Intro & Orientierung", sub: "AI-Tools & BMAD kennenlernen" },
|
||
{ t: "17:30", name: "Setup-Phase", sub: "Tools startklar machen" },
|
||
{ t: "17:40", name: "Phase 1 — GDD-Dialog", sub: "Game-Konzept mit der KI schärfen" },
|
||
{ t: "18:10", name: "Phase 2 — Coding", sub: "Jetzt wird das Game gebaut" },
|
||
{ t: "19:00", name: "Freeze & Abgabe", sub: "Deadline — Game hochladen" },
|
||
{ t: "19:10", name: "Demo-Runde", sub: "Jedes Team zeigt sein Game" },
|
||
{ t: "19:30", name: "Jury-Beratung", sub: "Die Top 3 werden ermittelt" },
|
||
{ t: "19:38", name: "Siegerehrung", sub: "Gewinner & Preise" },
|
||
{ t: "19:48", name: "Networking", sub: "Ausklang — Games spielen & austauschen" },
|
||
{ t: "20:10", name: "Ende", sub: "Danke fürs Dabeisein" },
|
||
];
|
||
|
||
function phaseDate(hhmm) { return new Date(`${EVENT_DATE}T${hhmm}:00`); }
|
||
function getNow() {
|
||
const sim = new URLSearchParams(location.search).get("sim");
|
||
if (sim && /^\d{1,2}:\d{2}$/.test(sim)) return phaseDate(sim.padStart(5, "0"));
|
||
return new Date();
|
||
}
|
||
function fmt(ms) {
|
||
if (ms < 0) ms = 0;
|
||
const s = Math.floor(ms / 1000);
|
||
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
|
||
const pad = n => String(n).padStart(2, "0");
|
||
return h > 0 ? `${pad(h)}:${pad(m)}:${pad(sec)}` : `${pad(m)}:${pad(sec)}`;
|
||
}
|
||
|
||
function renderHud() {
|
||
const now = getNow();
|
||
const start = phaseDate(PHASES[0].t);
|
||
const end = phaseDate(PHASES[PHASES.length - 1].t);
|
||
const nameEl = document.getElementById("phaseName");
|
||
const subEl = document.getElementById("phaseSub");
|
||
const timerEl = document.getElementById("timer");
|
||
const timerLabelEl = document.getElementById("timerLabel");
|
||
const nowLabelEl = document.getElementById("nowLabel");
|
||
const DOT = '<span class="dot"></span> ';
|
||
|
||
if (now < start) {
|
||
nowLabelEl.innerHTML = DOT + "Startet bald";
|
||
nameEl.textContent = "Gleich geht's los";
|
||
subEl.textContent = "Die AI GameJam beginnt um " + PHASES[0].t + " Uhr";
|
||
setTimerText(timerEl, fmt(start - now));
|
||
timerLabelEl.textContent = "bis Start";
|
||
} else if (now >= end) {
|
||
nowLabelEl.innerHTML = DOT + "Vorbei";
|
||
nameEl.textContent = "Event beendet";
|
||
subEl.textContent = "Danke an alle — die Games gibt's unten zum Spielen";
|
||
setTimerText(timerEl, "//");
|
||
timerLabelEl.textContent = "bis bald";
|
||
} else {
|
||
let idx = 0;
|
||
for (let i = 0; i < PHASES.length; i++) {
|
||
if (now >= phaseDate(PHASES[i].t)) idx = i;
|
||
}
|
||
const cur = PHASES[idx], next = PHASES[idx + 1];
|
||
nowLabelEl.innerHTML = DOT + "Jetzt läuft";
|
||
nameEl.textContent = cur.name;
|
||
subEl.textContent = cur.sub;
|
||
if (next) {
|
||
setTimerText(timerEl, fmt(phaseDate(next.t) - now));
|
||
timerLabelEl.textContent = "bis: " + next.name;
|
||
} else { setTimerText(timerEl, ""); timerLabelEl.textContent = ""; }
|
||
}
|
||
renderSchedule(now);
|
||
}
|
||
|
||
const CHECK = '<svg class="ic" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></svg>';
|
||
let lastTimerText = "";
|
||
|
||
function setTimerText(el, text) {
|
||
if (el.textContent === text) return;
|
||
el.textContent = text;
|
||
if (lastTimerText) {
|
||
el.classList.remove("tick");
|
||
void el.offsetWidth;
|
||
el.classList.add("tick");
|
||
}
|
||
lastTimerText = text;
|
||
}
|
||
|
||
function renderSchedule(now) {
|
||
const box = document.getElementById("schedule");
|
||
let html = "";
|
||
PHASES.forEach((p, i) => {
|
||
const startT = phaseDate(p.t);
|
||
const nextT = i < PHASES.length - 1 ? phaseDate(PHASES[i + 1].t) : startT;
|
||
let cls = "tl-row", mark = "";
|
||
if (now >= nextT && i < PHASES.length - 1) { cls += " done"; mark = CHECK; }
|
||
else if (now >= startT && now < nextT) { cls += " active"; }
|
||
html += `<div class="${cls}"><div class="tt">${p.t}</div>` +
|
||
`<div><div class="tn">${p.name}</div><div class="ts">${p.sub}</div></div>` +
|
||
`<div class="tx">${mark}</div></div>`;
|
||
});
|
||
box.innerHTML = html;
|
||
}
|
||
|
||
renderHud();
|
||
setInterval(renderHud, 1000);
|
||
|
||
/* ===================== UPLOAD + GALERIE ===================== */
|
||
/* API_BASE = Endpunkt der GameJam-API (Next.js-Routes in der mITapp, Postgres/Prisma).
|
||
Solange leer -> Upload & Galerie zeigen einen Hinweis, der Live-Teil läuft trotzdem.
|
||
Erwartete Endpunkte:
|
||
GET {API_BASE}/games -> [ { id, teilnehmer, game, url } ]
|
||
POST {API_BASE}/upload -> FormData { teilnehmer, game, file } */
|
||
const API_BASE = "/api/gamejam";
|
||
|
||
const teamEl = document.getElementById("teamName");
|
||
const gameEl = document.getElementById("gameName");
|
||
const dz = document.getElementById("dropzone");
|
||
const fileInput = document.getElementById("fileInput");
|
||
const uploadBtn = document.getElementById("uploadBtn");
|
||
const statusEl = document.getElementById("uploadStatus");
|
||
const gallery = document.getElementById("gallery");
|
||
const galleryCount = document.getElementById("galleryCount");
|
||
const galleryNotice = document.getElementById("galleryNotice");
|
||
let chosenFile = null;
|
||
|
||
function escapeHtml(s) {
|
||
return String(s).replace(/[&<>"]/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
||
}
|
||
function setStatus(msg, kind) {
|
||
statusEl.textContent = msg;
|
||
statusEl.className = "status" + (kind ? " " + kind : "");
|
||
}
|
||
|
||
dz.addEventListener("click", () => fileInput.click());
|
||
dz.addEventListener("dragover", e => { e.preventDefault(); dz.classList.add("drag"); });
|
||
dz.addEventListener("dragleave", () => dz.classList.remove("drag"));
|
||
dz.addEventListener("drop", e => {
|
||
e.preventDefault(); dz.classList.remove("drag");
|
||
if (e.dataTransfer.files[0]) pickFile(e.dataTransfer.files[0]);
|
||
});
|
||
fileInput.addEventListener("change", () => { if (fileInput.files[0]) pickFile(fileInput.files[0]); });
|
||
|
||
function pickFile(f) {
|
||
if (!/\.html?$/i.test(f.name)) { setStatus("Nur einzelne .html-Dateien.", "err"); return; }
|
||
chosenFile = f;
|
||
dz.querySelector(".big").textContent = f.name + " ausgewählt";
|
||
refreshUploadBtn();
|
||
}
|
||
function refreshUploadBtn() {
|
||
uploadBtn.disabled = !(API_BASE && chosenFile && teamEl.value.trim() && gameEl.value.trim());
|
||
}
|
||
teamEl.addEventListener("input", refreshUploadBtn);
|
||
gameEl.addEventListener("input", refreshUploadBtn);
|
||
|
||
uploadBtn.addEventListener("click", async () => {
|
||
if (!API_BASE || !chosenFile) return;
|
||
uploadBtn.disabled = true;
|
||
setStatus("Wird eingereicht…", null);
|
||
try {
|
||
const fd = new FormData();
|
||
fd.append("teilnehmer", teamEl.value.trim());
|
||
fd.append("game", gameEl.value.trim());
|
||
fd.append("file", chosenFile);
|
||
const res = await fetch(API_BASE + "/upload", { method: "POST", body: fd });
|
||
if (!res.ok) throw new Error((await res.text()) || ("HTTP " + res.status));
|
||
setStatus("Eingereicht — dein Spiel ist jetzt in der Galerie.", "ok");
|
||
chosenFile = null;
|
||
dz.querySelector(".big").textContent = "HTML-Datei hier ablegen";
|
||
teamEl.value = ""; gameEl.value = "";
|
||
loadGallery();
|
||
} catch (e) {
|
||
setStatus("Fehler: " + (e.message || e), "err");
|
||
}
|
||
refreshUploadBtn();
|
||
});
|
||
|
||
async function loadGallery() {
|
||
if (!API_BASE) {
|
||
galleryCount.textContent = "";
|
||
galleryNotice.style.display = "block";
|
||
galleryNotice.textContent = "Upload & Galerie sind noch nicht verbunden — die GameJam-API fehlt (siehe API_BASE im Quelltext). Der Live-Teil der Seite funktioniert bereits.";
|
||
return;
|
||
}
|
||
galleryCount.textContent = "Lädt…";
|
||
gallery.innerHTML = "";
|
||
galleryNotice.style.display = "none";
|
||
let games = [];
|
||
try {
|
||
const res = await fetch(API_BASE + "/games?v=" + Date.now());
|
||
if (res.ok) games = await res.json();
|
||
} catch (_) {}
|
||
if (!Array.isArray(games) || games.length === 0) {
|
||
galleryCount.textContent = "";
|
||
galleryNotice.style.display = "block";
|
||
galleryNotice.textContent = "Sobald die Teams ihre Spiele eingereicht haben, erscheinen sie hier.";
|
||
return;
|
||
}
|
||
galleryCount.textContent = games.length + (games.length > 1 ? " Spiele" : " Spiel");
|
||
for (const g of games) {
|
||
const url = g.url || "";
|
||
const name = escapeHtml(g.game || "Spiel");
|
||
const who = escapeHtml(g.teilnehmer || g.team || "");
|
||
gallery.insertAdjacentHTML("beforeend", `
|
||
<div class="game">
|
||
<iframe class="frame" src="${url}" sandbox="allow-scripts allow-pointer-lock"
|
||
loading="lazy" title="${name}"></iframe>
|
||
<div class="gm">
|
||
<div><div class="gt">${name}</div><div class="gte">${who}</div></div>
|
||
<a href="${url}" target="_blank" rel="noopener">Spielen
|
||
<svg class="ic" viewBox="0 0 24 24"><path d="M7 17 17 7M7 7h10v10"/></svg></a>
|
||
</div>
|
||
</div>`);
|
||
}
|
||
}
|
||
loadGallery();
|
||
document.getElementById("refreshBtn").addEventListener("click", loadGallery);
|
||
</script>
|
||
</body>
|
||
</html>
|