Files
ToolCTRL-website/public/gamejam.html
T

935 lines
38 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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/wordmark-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 &amp; Feel</div><div class="cs">Präsentation &amp; 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/wordmark-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 => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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>