Compare commits

...
25 Commits
Author SHA1 Message Date
raffaelandClaude Opus 5.5 204e11b966 chore(infra): Gitea-Host auf git.hg-int.dev umstellen
Gitea läuft seit 2026-11-11 auf hivegrid-control (git.hg-int.dev);
git.tool-ctrl.de wird abgeschaltet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 18:54:23 +01:00
ToolCTRLandClaude Opus 4.8 41664c3d0d SEO: Meta-Description auf neue Copy (Bedienung & Gestaltung)
Deploy / build-and-deploy (push) Successful in 22s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 15:42:27 +02:00
ToolCTRLandClaude Opus 4.8 d44a33e63b SEO: Favicon 96x96, GameJam-Uploads noindex, og:image-Metadaten
Deploy / build-and-deploy (push) Successful in 24s
- Favicon aus Emblem neu in 96x96 generiert (Google-SERP braucht min. 48x48),
  Favicon-Links versioniert
- /gamejam-games/: X-Robots-Tag noindex + robots.txt Disallow (Fremdcontent)
- og:image:width/height/alt auf Start, TechDay und GameJam

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:01:21 +02:00
ToolCTRLandClaude Opus 4.8 9f832bcec7 SEO: Canonicals/OG auf allen Seiten, WebP, Caching, www-Redirect
Deploy / build-and-deploy (push) Successful in 24s
- alle Unterseiten: Canonical-Tags; techday/gamejam zusaetzlich Open Graph
- techday: Event-JSON-LD (27. Mai 2026, HubWerk01 Bruchsal)
- mITapp-Screenshots als WebP (1,6 MB -> 65 KB) mit width/height/lazy
- dediziertes OG-Bild 1200x630 (Logo + Tagline) fuer Start & GameJam
- server.js: 301 www -> non-www, /index.html -> /, statische Assets
  1 Jahr immutable cachen (HTML weiter no-cache), .webp MIME
- CSS/JS ueberall versioniert (?v=20260610)
- sitemap: lastmod ergaenzt, noindex-Seiten (Impressum/Datenschutz) entfernt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 19:56:50 +02:00
ToolCTRLandClaude Opus 4.8 41bdd52be8 SEO: SERP-Titel/Description fixen, OG/JSON-LD, robots & sitemap
Deploy / build-and-deploy (push) Successful in 28s
- index.html: beschreibender Titel ohne Em-Dash, gekuerzte Meta-Description,
  Open-Graph/Twitter-Tags, Canonical, Organization-JSON-LD
- Em-Dashes aus allen Seitentiteln entfernt (techday, gamejam, impressum, datenschutz)
- gamejam.html: fehlende Meta-Description ergaenzt
- robots.txt + sitemap.xml neu, .txt/.xml MIME-Typen im Server

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:43:39 +02:00
ToolCTRLandClaude Opus 4.8 bfdd37ac40 Landing: Standort-Section (EU-Hosting) mit Paragraf-Wasserzeichen
Deploy / build-and-deploy (push) Successful in 21s
Neue Section zwischen Organisation und Events: positioniert ToolCTRL
als deutsch gehostete, dem deutschen Recht unterstellte Alternative zu
US-Software. Grau hinterlegt, hält den Grau/Weiss-Wechsel intakt.
Grosses, blasses Paragraf-Zeichen als Wasserzeichen im Freiraum oben
rechts (analog zum CTRL-Schriftzug im Hero).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:43:35 +02:00
ToolCTRLandClaude Opus 4.7 9ecfc015b1 Form: Vorname/Nachname + Branche, Hero ohne Leistungen-Button
Deploy / build-and-deploy (push) Successful in 21s
Form
- Name in Vorname + Nachname gesplittet (beide Pflicht), 2-Spalten-Grid
- Neues Feld Branche neben Firmenname (optional, 2-Spalten-Grid)
- Telefon-Label auf 'Telefonnummer' vereinheitlicht
- Placeholder ohne Em-Dash

script.js
- Validierung jetzt auf vorname/nachname/email/nachricht
- Payload sendet neue Einzelfelder (vorname, nachname, firmenname,
  branche) PLUS zusammengesetzte Backward-Compat-Felder (name, firma
  mit Branche-Suffix in Klammern), damit server.js/Inbox-Schema
  unverändert bleibt

Hero
- 'Leistungen ansehen' Ghost-Button entfernt, nur noch der schwarze
  'Kontakt aufnehmen' CTA

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 12:21:39 +02:00
ToolCTRLandClaude Opus 4.7 9d75831130 Impressum/Datenschutz mit echten Daten, Hero-Trennlinie raus
Deploy / build-and-deploy (push) Successful in 22s
Impressum
- Geschäftsführer: Raffael Sheikh
- Telefon +49 1523 7750939 (tel:-Link)
- Registereintrag: Amtsgericht Mannheim, HRB 756189
- USt-IdNr.: DE458565520
- Verantwortlich §18 Abs. 2 MStV: Raffael Sheikh

Datenschutz
- Hosting-Block: Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen
- Zusatzabsatz: Kontaktanfragen werden auf eigenen Servern/Services
  verarbeitet, keine Weitergabe an externe CRM-/Marketing-Dienste

UI
- border-bottom auf .hero entfernt, damit Hero nahtlos in Leistungen
  übergeht (Grid-Hintergrund läuft jetzt durch)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 20:12:30 +02:00
ToolCTRLandClaude Opus 4.7 6597f6b303 Landing: Events-Sektion, Grid-Hintergrund, scharfe Form, Konzept-Texte, Telefon im Footer
Deploy / build-and-deploy (push) Successful in 22s
UI / Layout
- Subtiler 72px-Grid auf html (rgba(10,10,10,.032)), body transparent
- Neue Events-Sektion vor #kontakt: TechDay-Card + AI-GameJam-Card mit
  3px-Border + 7px-Offset-Shadow, ganze Card klickbar via stretched-link
- Form mit scharfen Ecken und 12px-Offset-Shadow (raffael-Stil),
  Section-Dark-Button-Schatten, monochromer Form-Status

Navigation
- Nur noch Start / TechDay / GameJam + Kontakt-Button (Anchor-Links
  zur Landingpage raus, da sie ohnehin direkt darunter erscheinen)
- Größere Hover-Fläche pro Nav-Link via padding, Unterstreichung
  via ::after fest positioniert
- Mobile-Nav synchron, TechDay-Page synchron

Copy
- Hero: CTA 'Projekt besprechen' -> 'Kontakt aufnehmen'
- Leistungen: neuer Intro + alle sechs Card-Texte (Hands-Off, Anzug,
  Kommandozentrale, Tools-aneinander-vorbei, zerstueckelte Teil-
  loesungen, waechst-mit-Ihrem-Betrieb)
- Vorgehen -> 'Konzept' / 'Das ToolCTRL Konzept.' mit vier Schritten
  (Situationsanalyse, Grundkonzepte, Umsetzung, Betrieb & Wartung)
- Kontakt-Intro umgeschrieben, Form-Label 'Worum geht es Ihnen?'

Kontakt
- E-Mail global admin@ -> info@ (5 Dateien inkl. script.js Fallback)
- Telefon +49 1523 7750939 in Footer-Kontaktspalte (index + techday),
  klickbar via tel:

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 19:59:42 +02:00
raffaelandClaude Opus 4.8 0a9983b6d9 feat: forward leads to central lead-inbox (ADR 012)
Deploy / build-and-deploy (push) Successful in 22s
handleLead forwards to LEAD_INBOX_URL/v1/leads (DE field aliases map
server-side); local ndjson/csv stays as fallback. No-op until
LEAD_INBOX_URL/LEAD_INBOX_KEY are set, so safe to deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 19:27:55 +02:00
ToolCTRLandClaude Opus 4.7 d9fd1621ff GameJam: Preise-Section entfernt (durch Gewinner-Section ersetzt)
Deploy / build-and-deploy (push) Successful in 21s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:41:31 +02:00
ToolCTRLandClaude Opus 4.7 e94e0a36ec GameJam: Gewinner korrigiert + Section nach unten verschoben
Deploy / build-and-deploy (push) Successful in 21s
Richtige Reihenfolge: Platz 1 Banana Bandit, Platz 2 Ship IT,
Platz 3 Firestarter. Gewinner-Section von oben (unter Hero) ans
Seitenende verschoben (zwischen Galerie und Footer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:38:03 +02:00
ToolCTRLandClaude Opus 4.7 ba7ee347e6 GameJam: Gewinner-Section ergänzt (Top 3)
Deploy / build-and-deploy (push) Successful in 21s
Nach dem Event: Podium direkt unter dem Hero im Preise-Stil.
Platz 1 Firestarter (hervorgehoben, Pokal), Platz 2 Ship IT,
Platz 3 Banana Bandit, jeweils mit Gutschein-Betrag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:32:46 +02:00
ToolCTRLandClaude Opus 4.7 92f113f89b GameJam: Galerie-iframes laden wieder sofort, aber ohne Auto-Ton
Deploy / build-and-deploy (push) Successful in 21s
Klick-Platzhalter zurückgenommen. Stattdessen am iframe
allow="autoplay 'none'" gesetzt: das Spiel lädt sichtbar, der Browser
blockiert automatischen Ton (auch via AudioContext). Erst eine
User-Geste im Spiel (Reinklicken) erlaubt den Ton.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:21:26 +02:00
ToolCTRLandClaude Opus 4.7 49c24c1833 GameJam: Galerie-Spiele erst auf Klick laden (kein Auto-Ton)
Deploy / build-and-deploy (push) Successful in 20s
Statt jedes eingereichte Spiel sofort in ein iframe zu laden (wodurch
mehrere Spiele gleichzeitig Ton abspielten), zeigt die Galerie jetzt
einen "Spiel starten"-Platzhalter. Das iframe (und damit Ton) wird
erst beim Klick erzeugt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:13:28 +02:00
ToolCTRLandClaude Opus 4.7 bede371528 GameJam: Mitbringen-Section spezifiziert Mac/Linux-Laptop
Deploy / build-and-deploy (push) Successful in 37s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:24:22 +02:00
ToolCTRLandClaude Opus 4.7 0cfc264c4b GameJam: Hart-Deadline für Einreichungen am 27.05.2026 22:00 Berlin
Deploy / build-and-deploy (push) Successful in 21s
server.js: handleGamejamUpload lehnt nach UPLOAD_DEADLINE mit HTTP 410
und Klartext-Hinweis ab. gamejam.html: Formular wird nach Ablauf
clientseitig gesperrt und ein roter Status zeigt den abgelaufenen
Termin. Doppelschicht (Server + UI), Server ist verbindlich.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:36:59 +02:00
ToolCTRLandClaude Opus 4.7 63be60b88a GameJam: kleine "Mitbringen"-Section ergänzt
Deploy / build-and-deploy (push) Successful in 20s
Zwischen "So läuft's" und "Bewertung": ein Satz, dass Teilnehmer ihr
eigenes Gerät (am besten Laptop) mitbringen und sonst nur Lust auf
ein KI-Spiel brauchen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:22:32 +02:00
ToolCTRLandClaude Opus 4.7 616cd60c6d Produktion: ClamAV-Scan deaktiviert (CLAMAV_ENABLED=false)
Deploy / build-and-deploy (push) Successful in 20s
Solange in der Coolify-Umgebung kein clamd erreichbar ist, würden
GameJam-Uploads sonst mit 503 (fail-closed) abgelehnt. Für den
TechDay morgen schalten wir den Scan im Container-ENV ab; Uploads
laufen unverändert, nur ohne Virenprüfung. Lokal bleibt der Default
true (Scan aktiv, wenn clamd läuft).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:16:20 +02:00
ToolCTRL 730aac65f8 Revert "Produktion: ClamAV in den App-Container integriert"
Deploy / build-and-deploy (push) Successful in 20s
This reverts commit bec5dc7363.
2026-05-26 14:14:20 +02:00
ToolCTRLandClaude Opus 4.7 8e05f323e2 GameJam: Logos verlinken zu LinkedIn
Deploy / build-and-deploy (push) Successful in 32s
- Topbar mITup-Logo: linkedin.com/company/it-meetup-bruchsal/
- Footer ToolCTRL-Logo: linkedin.com/company/toolctrl/
- Footer mITup-Logo: linkedin.com/company/it-meetup-bruchsal/

Externe Links öffnen in neuem Tab. Das Topbar-ToolCTRL-Logo bleibt
der Zurück-zur-Startseite-Link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:11:39 +02:00
ToolCTRLandClaude Opus 4.7 bec5dc7363 Produktion: ClamAV in den App-Container integriert
Deploy / build-and-deploy (push) Successful in 33s
Dockerfile installiert clamav-daemon + clamav-freshclam und konfiguriert
clamd auf TCP 127.0.0.1:3310 mit DB-Verzeichnis auf der persistenten
Coolify-Volume (/app/data/clamav-db).

entrypoint.sh startet clamd und freshclam vor dem Node-Server, wartet
auf clamd-PING und exec't dann node als unprivilegierter Nutzer. tini
übernimmt PID 1 für Signal-Forwarding.

Folge: GameJam-Uploads werden in Produktion automatisch per ClamAV
geprüft, ohne dass Coolify manuell angepasst werden muss. Erster
Deploy lädt einmalig ~300 MB Virendefinitionen auf die Volume,
Folge-Starts in ~30 s. RAM-Bedarf des Containers ~1.5 GB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:40:19 +02:00
ToolCTRLandClaude Opus 4.7 9639e2d72b GameJam-Preise: Amazon-Gutscheine statt Claude-Accounts
Deploy / build-and-deploy (push) Successful in 21s
Platz 1: 50 EUR Amazon-Gutschein
Platz 2: 25 EUR Amazon-Gutschein
Platz 3: 25 EUR Amazon-Gutschein

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:55:27 +02:00
Raffael Sheikh 95d0e21bbf Fix deploy workflow syntax check
Deploy / build-and-deploy (push) Successful in 22s
2026-05-24 22:18:51 +02:00
Raffael Sheikh a9dbc23d7e Add Coolify deployment workflow
Deploy / build-and-deploy (push) Failing after 11m57s
2026-05-24 22:13:55 +02:00
19 changed files with 958 additions and 174 deletions

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
.git
.gitea
.DS_Store
node_modules
*.log
leads
gamejam
public/gamejam-games
+62
View File
@@ -0,0 +1,62 @@
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: toolctrl-ci
steps:
- name: Setup hosts override and Docker CLI
run: |
echo "195.201.133.26 git.hg-int.dev" >> /etc/hosts
apt-get update -qq
apt-get install -qq -y --no-install-recommends ca-certificates curl git
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | tee /etc/apt/keyrings/docker.asc >/dev/null
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list >/dev/null
apt-get update -qq
apt-get install -qq -y --no-install-recommends docker-ce-cli docker-buildx-plugin
docker --version
docker buildx version
- name: Checkout
run: |
mkdir -p /workspace/repo
cd /workspace/repo
git clone "https://${{ secrets.REGISTRY_USER }}:${{ secrets.REGISTRY_PAT }}@git.hg-int.dev/${{ github.repository }}.git" .
git checkout "${{ github.sha }}"
- name: Static checks
run: |
cd /workspace/repo
node --check server.js
git diff --check
- name: Login to Gitea Registry
run: echo "${{ secrets.REGISTRY_PAT }}" | docker login git.hg-int.dev --username "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build and push image
env:
IMAGE: git.hg-int.dev/${{ github.repository }}
SHA: ${{ github.sha }}
run: |
cd /workspace/repo
SHORT_SHA=${SHA:0:7}
IMAGE_LC=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')
docker build \
--build-arg APP_REVISION="$SHA" \
-t "$IMAGE_LC:$SHORT_SHA" \
-t "$IMAGE_LC:main" \
.
docker push "$IMAGE_LC:$SHORT_SHA"
docker push "$IMAGE_LC:main"
- name: Trigger Coolify deploy
run: |
curl -fsS -X POST \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
"https://saas-ctrl.com/api/v1/applications/ocwtt7rd2zijtlszgua005k9/start"
+35
View File
@@ -0,0 +1,35 @@
FROM node:22-bookworm-slim
LABEL org.opencontainers.image.description="ToolCTRL Website"
ARG APP_REVISION=local
ENV NODE_ENV=production \
PORT=3000 \
HOST=0.0.0.0 \
DATA_DIR=/app/data \
CLAMAV_ENABLED=false \
APP_REVISION=${APP_REVISION}
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1001 nodejs \
&& useradd --uid 1001 --gid nodejs --home-dir /app --shell /usr/sbin/nologin nodejs
WORKDIR /app
COPY --chown=nodejs:nodejs public ./public
COPY --chown=nodejs:nodejs server.js ./server.js
RUN mkdir -p /app/data \
&& chown -R nodejs:nodejs /app/data
USER nodejs
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:3000/healthz >/dev/null || exit 1
CMD ["node", "server.js"]
+30
View File
@@ -83,6 +83,22 @@ Host/Port und An/Aus stehen in `server.js` im Objekt `CLAMAV`. Mit
`enabled: false` läuft der Upload ohne Virenscan (nur für lokale Tests
ohne ClamAV gedacht).
Per Environment kann der Runtime-Betrieb konfiguriert werden:
```bash
PORT=3000
HOST=0.0.0.0
DATA_DIR=/app/data
CLAMAV_HOST=127.0.0.1
CLAMAV_PORT=3310
CLAMAV_ENABLED=true
APP_REVISION=<git-sha>
```
`/healthz` ist die leichte Liveness-Probe für Container und Coolify.
`/readyz` prüft zusätzlich Schreibzugriff auf die Datenverzeichnisse und,
falls aktiv, die Erreichbarkeit von ClamAV.
### Funktion testen
Eine Datei mit der EICAR-Testsignatur hochladen — `clamd` meldet dann
@@ -93,3 +109,17 @@ Eine Datei mit der EICAR-Testsignatur hochladen — `clamd` meldet dann
Für den QR-Code am Stand muss der Rechner im selben WLAN erreichbar sein.
Soll die Seite öffentlich laufen, hinter einen Reverse-Proxy mit HTTPS setzen
oder bei einem Hoster bereitstellen — `tool-ctrl.de`.
## Deployment
Deployment folgt dem Gitea-Infra-Standard aus `toolctrl-labs/infra`:
1. Push auf `main` triggert `.gitea/workflows/deploy.yml`.
2. Gitea Actions baut das Docker-Image und pusht es nach
`git.hg-int.dev/toolctrl-labs/toolctrl-website`.
3. Tags: kurzer Commit-SHA und `main`.
4. Coolify-App `toolctrl-website` (`ocwtt7rd2zijtlszgua005k9`) zieht
`:main` auf der Marketing-VM und startet neu.
Die Coolify-App läuft im Projekt `Marketing`, Environment `production`, Server
`marketing-vm`, Port `3000`, mit persistentem Storage auf `/app/data`.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+16 -9
View File
@@ -3,11 +3,12 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Datenschutzerklärung — ToolCTRL</title>
<meta name="description" content="Datenschutzerklärung von ToolCTRL — Informationen zur Verarbeitung personenbezogener Daten auf dieser Website." />
<title>Datenschutzerklärung: ToolCTRL</title>
<meta name="description" content="Datenschutzerklärung von ToolCTRL: Informationen zur Verarbeitung personenbezogener Daten auf dieser Website." />
<meta name="robots" content="noindex" />
<link rel="icon" type="image/png" href="/assets/favicon.png" />
<link rel="stylesheet" href="/styles.css" />
<link rel="canonical" href="https://tool-ctrl.de/datenschutz.html" />
<link rel="icon" type="image/png" href="/assets/favicon.png?v=20260610" />
<link rel="stylesheet" href="/styles.css?v=20260610" />
</head>
<body>
@@ -37,16 +38,17 @@
R&amp;D² UG (haftungsbeschränkt)<br />
Orbinstraße 1<br />
76646 Bruchsal<br />
E-Mail: <a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a>
E-Mail: <a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
</p>
<p>Die vollständigen Anbieterangaben finden Sie im <a href="/impressum.html">Impressum</a>.</p>
<h2>2. Hosting und Server-Logfiles</h2>
<p>
Diese Website wird bei einem externen Dienstleister gehostet:
<span class="legal-tbd">[ Hosting-Anbieter — vom Betreiber zu ergänzen ]</span>.
Beim Aufruf der Website verarbeitet dieser automatisch technische
Informationen, die Ihr Browser übermittelt (sogenannte Server-Logfiles):
Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland.
Beim Aufruf der Website verarbeitet der Hosting-Anbieter automatisch
technische Informationen, die Ihr Browser übermittelt (sogenannte
Server-Logfiles):
</p>
<ul>
<li>IP-Adresse des anfragenden Geräts</li>
@@ -88,6 +90,11 @@
Die Angabe der Pflichtfelder ist erforderlich, um Ihre Anfrage bearbeiten
zu können; die übrigen Angaben sind freiwillig.
</p>
<p>
Die eingehenden Kontaktanfragen werden auf unseren eigenen Servern und
eigenen Services verarbeitet. Eine Weitergabe an externe CRM- oder
Marketing-Dienste findet nicht statt.
</p>
<h2>4. Speicherdauer</h2>
<p>
@@ -129,7 +136,7 @@
</ul>
<p>
Zur Ausübung Ihrer Rechte genügt eine formlose Nachricht an
<a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a>.
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>.
</p>
<h2>8. Beschwerderecht bei der Aufsichtsbehörde</h2>
+74 -31
View File
@@ -3,8 +3,21 @@
<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>
<link rel="icon" type="image/png" href="/assets/favicon.png">
<title>AI GameJam: Human in the Loop, TechDay Bruchsal 2026</title>
<meta name="description" content="AI GameJam von ToolCTRL beim TechDay Bruchsal 2026: In wenigen Stunden mit KI ein spielbares Game bauen. Human in the Loop, im HubWerk01 Bruchsal.">
<link rel="canonical" href="https://tool-ctrl.de/gamejam.html">
<meta property="og:type" content="website">
<meta property="og:site_name" content="ToolCTRL">
<meta property="og:locale" content="de_DE">
<meta property="og:url" content="https://tool-ctrl.de/gamejam.html">
<meta property="og:title" content="AI GameJam: Human in the Loop, TechDay Bruchsal 2026">
<meta property="og:description" content="AI GameJam von ToolCTRL beim TechDay Bruchsal 2026: In wenigen Stunden mit KI ein spielbares Game bauen. Human in the Loop, im HubWerk01 Bruchsal.">
<meta property="og:image" content="https://tool-ctrl.de/assets/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="ToolCTRL Logo mit Tagline: Die Software, die eure Branche längst verdient hat.">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" type="image/png" href="/assets/favicon.png?v=20260610">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
@@ -352,7 +365,7 @@
</div>
<div class="right">
<span class="ev">TechDay Bruchsal · 27.05.2026</span>
<img class="mit" src="data:image/png;base64,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 truncated
<a href="https://www.linkedin.com/company/it-meetup-bruchsal/" target="_blank" rel="noopener" aria-label="mITup Bruchsal auf LinkedIn"><img class="mit" src="data:image/png;base64,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 truncated
</div>
</div>
</div>
@@ -432,6 +445,18 @@
</div>
</section>
<!-- ===================== MITBRINGEN ===================== -->
<section id="mitbringen">
<div class="wrap">
<div class="seclabel">Was du brauchst</div>
<h2 class="big">Mitbringen</h2>
<p class="lead">
Dein eigener Mac- oder Linux-Laptop. Sonst nichts. Nur Bock,
mit KI ein Spiel zu bauen.
</p>
</div>
</section>
<!-- ===================== BEWERTUNG ===================== -->
<section id="bewertung">
<div class="wrap">
@@ -447,30 +472,6 @@
</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">
@@ -509,12 +510,38 @@
</div>
</section>
<!-- ===================== GEWINNER ===================== -->
<section id="gewinner">
<div class="wrap">
<div class="seclabel">Ergebnisse</div>
<h2 class="big">Die Gewinner</h2>
<p class="lead" style="margin-bottom:44px;">Die AI GameJam ist gelaufen. Das sind die Top 3.</p>
<div class="prizes">
<div class="prizeitem first">
<div class="pr"><svg class="ic" viewBox="0 0 24 24" style="width:14px;height:14px;vertical-align:-2px;margin-right:6px;"><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>Platz 1</div>
<h3>Banana Bandit</h3>
<div class="dur">50 € Amazon-Gutschein</div>
</div>
<div class="prizeitem">
<div class="pr">Platz 2</div>
<h3>Ship IT</h3>
<div class="dur">25 € Amazon-Gutschein</div>
</div>
<div class="prizeitem">
<div class="pr">Platz 3</div>
<h3>Firestarter</h3>
<div class="dur">25 € Amazon-Gutschein</div>
</div>
</div>
</div>
</section>
<!-- ===================== FOOTER ===================== -->
<footer>
<div class="inner">
<div class="logos">
<img class="tc" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzEzLjMyIDY3OC42MSI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNSk7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNik7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtMyk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtMik7CiAgICAgIH0KCiAgICAgIC5jbHMtNiB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQpOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSI2NTguOTYiIHkxPSIzMS45NiIgeDI9IjI5Ny43IiB5Mj0iNDU4LjYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMDAwIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzMzMyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIxNjg0LjMiIHkxPSIxMTQyLjc1IiB4Mj0iMjIwNi4wOSIgeTI9IjE1NTMuNTEiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4MzMuNzQgLTExMDguOTYpIHJvdGF0ZSg1LjA2KSBza2V3WCgxNC4yNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjYjJiMmIyIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iLjc4IiBzdG9wLWNvbG9yPSIjZmZmIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjE3MTkuMTMiIHkxPSIxMDk4LjUxIiB4Mj0iMjI0MC45MSIgeTI9IjE1MDkuMjgiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQtMiIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjE3NjQuODIiIHkxPSIxMDQwLjQ3IiB4Mj0iMjI4Ni42IiB5Mj0iMTQ1MS4yNCIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iMTgwNi4yNyIgeTE9Ijk4Ny44MiIgeDI9IjIzMjguMDUiIHkyPSIxMzk4LjU5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTYiIHgxPSIzNDUuMDMiIHkxPSItMTAuMTUiIHgyPSIxMDE0LjU1IiB5Mj0iNTE2LjkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsMCwwKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+CiAgPC9kZWZzPgogIDxnPgogICAgPHBhdGggZD0iTTY4OC43LDU3Mi4wOWwtMTYwLDk4Ljk1Yy05LjE0LDUuNzMtMTkuOTksOC4zNS0zMC43Nyw3LjM3bC0zODAuNjgtMzQuMzZjLTIyLjQ4LTIuMDEtNDAuNzYtMTkuMDEtNDQuMzUtNDEuMzFMLjY0LDE1OS4zMWMtMy4xNy0xOS41Niw1LjYtMzkuMTIsMjIuMy00OS43MkwxODkuNDYsOC4wOGMtMTMuOTUsOS43NSwyMy43NywzMS41NSwyNi44OCw0OS45NWw5MS4zLDM2MS4xM2M2LjIxLDM2LjY4LDM2LjM3LDY0LjU4LDczLjQyLDY3LjkzbDI3OC42NCw0Mi45YzguNjUuNzksMjEuODcsNDUuNjgsMjguOTQsNDIuMDNsLjA2LjA2WiIvPgogICAgPHBhdGggY2xhc3M9ImNscy02IiBkPSJNNjg4LjY0LDU3Mi4wM2MtNy4wNywzLjY2LTE1LjIzLDUuMzYtMjMuODgsNC41N2wtMzQ5LTMxLjVjLTM3LjA0LTMuMzUtNjcuMjEtMzEuMjYtNzMuNDItNjcuOTNMMTcwLjM5LDUzLjM1Yy0zLjExLTE4LjQsNS4xMi0zNS41MiwxOS4wNy00NS4yN2wxLjk1LTEuMTZjNy45OC01LjA2LDE3LjYxLTcuNjIsMjcuOTEtNi43NmwzNTguNTYsMzAuMzRjMjkuNDksMi41LDUzLjU2LDI0LjYyLDU4LjYxLDUzLjhsNzYuMTYsNDQwLjM5YzMuNTMsMjAuMjktNy4xOSwzOC45OS0yNC4wMSw0Ny4zNFoiLz4KICA8L2c+CiAgPGc+CiAgICA8Zz4KICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNMzAwLjIyLDM0My45M2wtMTUuMDEsNC4yNiwxNS43Myw5Ni4wNWM0LjIzLDI1LjgzLDIxLjg4LDQyLjU5LDQ3LjIxLDQ0LjgzbDIwLjQyLDEuODEtMi40OC0xNS4xNC0yMC40Mi0xLjgxYy0xNS44MS0xLjQtMjYuMDQtMTEuNDQtMjguNzktMjguMjhsLTguMjQtNTAuMzQsNDQuOTMsMy45Ny0yLjQ1LTE0Ljk0LTQ0LjkzLTMuOTctNS45Ny0zNi40M1oiLz4KICAgICAgPHBhdGggY2xhc3M9ImNscy00IiBkPSJNNDIxLjk2LDM4Ny45N2MtMzEuODgtMi44Mi01MS4yNiwxOC4xLTQ2LjA4LDQ5LjczLDUuMTgsMzEuNjQsMzIuMDgsNTYuNjUsNjMuOTcsNTkuNDcsMzEuODgsMi44Miw1MS4yNi0xOC4xLDQ2LjA4LTQ5LjczLTUuMTgtMzEuNjQtMzIuMDgtNTYuNjUtNjMuOTctNTkuNDdaTTQzNy4zNiw0ODIuMDNjLTIyLjktMi4wMy00MS41NC0xOS42Ny00NS4zNS00Mi45LTMuOC0yMy4yMyw5LjUzLTM4LjA0LDMyLjQzLTM2LjAyLDIyLjksMi4wMyw0MS41NCwxOS42Niw0NS4zNSw0Mi45LDMuOCwyMy4yMy05LjUzLDM4LjA0LTMyLjQyLDM2LjAyWiIvPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik01NDEuMzcsMzk4LjUzYy0zMS44OC0yLjgyLTUxLjI2LDE4LjEtNDYuMDgsNDkuNzMsNS4xOCwzMS42NCwzMi4wOCw1Ni42NSw2My45Nyw1OS40NywzMS44OCwyLjgyLDUxLjI2LTE4LjEsNDYuMDgtNDkuNzMtNS4xOC0zMS42NC0zMi4wOC01Ni42NS02My45Ny01OS40N1pNNTU2Ljc3LDQ5Mi41OWMtMjIuOS0yLjAzLTQxLjU0LTE5LjY3LTQ1LjM1LTQyLjktMy44LTIzLjIzLDkuNTMtMzguMDQsMzIuNDMtMzYuMDIsMjIuOSwyLjAzLDQxLjU0LDE5LjY2LDQ1LjM1LDQyLjksMy44LDIzLjIzLTkuNTMsMzguMDQtMzIuNDIsMzYuMDJaIi8+CiAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMSIgcG9pbnRzPSI2MDcuNjcgMzY4LjQxIDYzMS4xIDUxMS40NiA2NDcuMDQgNTEyLjg3IDYyMi42OCAzNjQuMTUgNjA3LjY3IDM2OC40MSIvPgogICAgPC9nPgogICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTgyLjM5LDE2NC4wN2wxOC4zLDEuNjItNTYuMjktNzUuMTJjLTMuMTUtNC4yLTcuOTMtNi44Ni0xMy4xNi03LjMzaDBjLTcuNjEtLjY3LTE0Ljg2LDMuNDEtMTguMjIsMTAuMjdsLTMwLjMsNjEuNzUsMTkuMiwxLjcsMjcuNTctNTYuMmMuOTktMi4wMSwzLjc1LTIuMjcsNS4xLS40OGw0Ny44MSw2My43OVoiLz4KICA8L2c+Cjwvc3ZnPg==" alt="ToolCTRL">
<img src="data:image/png;base64,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 truncated
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener" aria-label="ToolCTRL auf LinkedIn"><img class="tc" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzEzLjMyIDY3OC42MSI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNSk7CiAgICAgIH0KCiAgICAgIC5jbHMtMiB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNik7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtMyk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQtMik7CiAgICAgIH0KCiAgICAgIC5jbHMtNiB7CiAgICAgICAgZmlsbDogdXJsKCNsaW5lYXItZ3JhZGllbnQpOwogICAgICB9CiAgICA8L3N0eWxlPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSI2NTguOTYiIHkxPSIzMS45NiIgeDI9IjI5Ny43IiB5Mj0iNDU4LjYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMDAwIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzMzMyIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIxNjg0LjMiIHkxPSIxMTQyLjc1IiB4Mj0iMjIwNi4wOSIgeTI9IjE1NTMuNTEiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4MzMuNzQgLTExMDguOTYpIHJvdGF0ZSg1LjA2KSBza2V3WCgxNC4yNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjYjJiMmIyIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iLjc4IiBzdG9wLWNvbG9yPSIjZmZmIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjE3MTkuMTMiIHkxPSIxMDk4LjUxIiB4Mj0iMjI0MC45MSIgeTI9IjE1MDkuMjgiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQtMiIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjE3NjQuODIiIHkxPSIxMDQwLjQ3IiB4Mj0iMjI4Ni42IiB5Mj0iMTQ1MS4yNCIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iMTgwNi4yNyIgeTE9Ijk4Ny44MiIgeDI9IjIzMjguMDUiIHkyPSIxMzk4LjU5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTYiIHgxPSIzNDUuMDMiIHkxPSItMTAuMTUiIHgyPSIxMDE0LjU1IiB5Mj0iNTE2LjkxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsMCwwLDEsMCwwKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+CiAgPC9kZWZzPgogIDxnPgogICAgPHBhdGggZD0iTTY4OC43LDU3Mi4wOWwtMTYwLDk4Ljk1Yy05LjE0LDUuNzMtMTkuOTksOC4zNS0zMC43Nyw3LjM3bC0zODAuNjgtMzQuMzZjLTIyLjQ4LTIuMDEtNDAuNzYtMTkuMDEtNDQuMzUtNDEuMzFMLjY0LDE1OS4zMWMtMy4xNy0xOS41Niw1LjYtMzkuMTIsMjIuMy00OS43MkwxODkuNDYsOC4wOGMtMTMuOTUsOS43NSwyMy43NywzMS41NSwyNi44OCw0OS45NWw5MS4zLDM2MS4xM2M2LjIxLDM2LjY4LDM2LjM3LDY0LjU4LDczLjQyLDY3LjkzbDI3OC42NCw0Mi45YzguNjUuNzksMjEuODcsNDUuNjgsMjguOTQsNDIuMDNsLjA2LjA2WiIvPgogICAgPHBhdGggY2xhc3M9ImNscy02IiBkPSJNNjg4LjY0LDU3Mi4wM2MtNy4wNywzLjY2LTE1LjIzLDUuMzYtMjMuODgsNC41N2wtMzQ5LTMxLjVjLTM3LjA0LTMuMzUtNjcuMjEtMzEuMjYtNzMuNDItNjcuOTNMMTcwLjM5LDUzLjM1Yy0zLjExLTE4LjQsNS4xMi0zNS41MiwxOS4wNy00NS4yN2wxLjk1LTEuMTZjNy45OC01LjA2LDE3LjYxLTcuNjIsMjcuOTEtNi43NmwzNTguNTYsMzAuMzRjMjkuNDksMi41LDUzLjU2LDI0LjYyLDU4LjYxLDUzLjhsNzYuMTYsNDQwLjM5YzMuNTMsMjAuMjktNy4xOSwzOC45OS0yNC4wMSw0Ny4zNFoiLz4KICA8L2c+CiAgPGc+CiAgICA8Zz4KICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNMzAwLjIyLDM0My45M2wtMTUuMDEsNC4yNiwxNS43Myw5Ni4wNWM0LjIzLDI1LjgzLDIxLjg4LDQyLjU5LDQ3LjIxLDQ0LjgzbDIwLjQyLDEuODEtMi40OC0xNS4xNC0yMC40Mi0xLjgxYy0xNS44MS0xLjQtMjYuMDQtMTEuNDQtMjguNzktMjguMjhsLTguMjQtNTAuMzQsNDQuOTMsMy45Ny0yLjQ1LTE0Ljk0LTQ0LjkzLTMuOTctNS45Ny0zNi40M1oiLz4KICAgICAgPHBhdGggY2xhc3M9ImNscy00IiBkPSJNNDIxLjk2LDM4Ny45N2MtMzEuODgtMi44Mi01MS4yNiwxOC4xLTQ2LjA4LDQ5LjczLDUuMTgsMzEuNjQsMzIuMDgsNTYuNjUsNjMuOTcsNTkuNDcsMzEuODgsMi44Miw1MS4yNi0xOC4xLDQ2LjA4LTQ5LjczLTUuMTgtMzEuNjQtMzIuMDgtNTYuNjUtNjMuOTctNTkuNDdaTTQzNy4zNiw0ODIuMDNjLTIyLjktMi4wMy00MS41NC0xOS42Ny00NS4zNS00Mi45LTMuOC0yMy4yMyw5LjUzLTM4LjA0LDMyLjQzLTM2LjAyLDIyLjksMi4wMyw0MS41NCwxOS42Niw0NS4zNSw0Mi45LDMuOCwyMy4yMy05LjUzLDM4LjA0LTMyLjQyLDM2LjAyWiIvPgogICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik01NDEuMzcsMzk4LjUzYy0zMS44OC0yLjgyLTUxLjI2LDE4LjEtNDYuMDgsNDkuNzMsNS4xOCwzMS42NCwzMi4wOCw1Ni42NSw2My45Nyw1OS40NywzMS44OCwyLjgyLDUxLjI2LTE4LjEsNDYuMDgtNDkuNzMtNS4xOC0zMS42NC0zMi4wOC01Ni42NS02My45Ny01OS40N1pNNTU2Ljc3LDQ5Mi41OWMtMjIuOS0yLjAzLTQxLjU0LTE5LjY3LTQ1LjM1LTQyLjktMy44LTIzLjIzLDkuNTMtMzguMDQsMzIuNDMtMzYuMDIsMjIuOSwyLjAzLDQxLjU0LDE5LjY2LDQ1LjM1LDQyLjksMy44LDIzLjIzLTkuNTMsMzguMDQtMzIuNDIsMzYuMDJaIi8+CiAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMSIgcG9pbnRzPSI2MDcuNjcgMzY4LjQxIDYzMS4xIDUxMS40NiA2NDcuMDQgNTEyLjg3IDYyMi42OCAzNjQuMTUgNjA3LjY3IDM2OC40MSIvPgogICAgPC9nPgogICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNTgyLjM5LDE2NC4wN2wxOC4zLDEuNjItNTYuMjktNzUuMTJjLTMuMTUtNC4yLTcuOTMtNi44Ni0xMy4xNi03LjMzaDBjLTcuNjEtLjY3LTE0Ljg2LDMuNDEtMTguMjIsMTAuMjdsLTMwLjMsNjEuNzUsMTkuMiwxLjcsMjcuNTctNTYuMmMuOTktMi4wMSwzLjc1LTIuMjcsNS4xLS40OGw0Ny44MSw2My43OVoiLz4KICA8L2c+Cjwvc3ZnPg==" alt="ToolCTRL"></a>
<a href="https://www.linkedin.com/company/it-meetup-bruchsal/" target="_blank" rel="noopener" aria-label="mITup Bruchsal auf LinkedIn"><img src="data:image/png;base64,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 truncated
</div>
<div class="ft">AI GameJam · Human in the Loop · veranstaltet von ToolCTRL beim TechDay Bruchsal 2026</div>
</div>
@@ -620,6 +647,10 @@ setInterval(renderHud, 1000);
POST {API_BASE}/upload -> FormData { teilnehmer, game, file } */
const API_BASE = "/api/gamejam";
/* Hart-Deadline für Einreichungen — danach ist die UI gesperrt und der Server lehnt ab. */
const UPLOAD_DEADLINE = new Date("2026-05-27T22:00:00+02:00");
function uploadsOpen() { return Date.now() < UPLOAD_DEADLINE.getTime(); }
const teamEl = document.getElementById("teamName");
const gameEl = document.getElementById("gameName");
const dz = document.getElementById("dropzone");
@@ -655,11 +686,23 @@ function pickFile(f) {
refreshUploadBtn();
}
function refreshUploadBtn() {
uploadBtn.disabled = !(API_BASE && chosenFile && teamEl.value.trim() && gameEl.value.trim());
uploadBtn.disabled = !(uploadsOpen() && API_BASE && chosenFile && teamEl.value.trim() && gameEl.value.trim());
}
teamEl.addEventListener("input", refreshUploadBtn);
gameEl.addEventListener("input", refreshUploadBtn);
/* Wenn die Deadline überschritten ist: Formular sperren und Hinweis anzeigen. */
if (!uploadsOpen()) {
teamEl.disabled = true;
gameEl.disabled = true;
fileInput.disabled = true;
dz.style.pointerEvents = "none";
dz.style.opacity = "0.5";
uploadBtn.textContent = "Einreichungen geschlossen";
uploadBtn.disabled = true;
setStatus("Die Einreichungsfrist ist am 27.05.2026 um 22:00 Uhr abgelaufen.", "err");
}
uploadBtn.addEventListener("click", async () => {
if (!API_BASE || !chosenFile) return;
uploadBtn.disabled = true;
@@ -711,7 +754,7 @@ async function loadGallery() {
gallery.insertAdjacentHTML("beforeend", `
<div class="game">
<iframe class="frame" src="${url}" sandbox="allow-scripts allow-pointer-lock"
loading="lazy" title="${name}"></iframe>
allow="autoplay 'none'" 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
+11 -10
View File
@@ -3,11 +3,12 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Impressum — ToolCTRL</title>
<title>Impressum: ToolCTRL</title>
<meta name="description" content="Impressum und Anbieterkennzeichnung von ToolCTRL." />
<meta name="robots" content="noindex" />
<link rel="icon" type="image/png" href="/assets/favicon.png" />
<link rel="stylesheet" href="/styles.css" />
<link rel="canonical" href="https://tool-ctrl.de/impressum.html" />
<link rel="icon" type="image/png" href="/assets/favicon.png?v=20260610" />
<link rel="stylesheet" href="/styles.css?v=20260610" />
</head>
<body>
@@ -34,31 +35,31 @@
<h2>Vertreten durch</h2>
<p>
<span class="legal-tbd">[ Name der vertretungsberechtigten Person(en) — bei Gesellschaften ]</span>
Raffael Sheikh, Geschäftsführer
</p>
<h2>Kontakt</h2>
<p>
Telefon: <span class="legal-tbd">[ Telefonnummer ]</span><br />
E-Mail: <a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a><br />
Telefon: <a href="tel:+4915237750939">+49 1523 7750939</a><br />
E-Mail: <a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a><br />
Web: <a href="https://tool-ctrl.de">tool-ctrl.de</a>
</p>
<h2>Registereintrag</h2>
<p>
<span class="legal-tbd">[ Registergericht — falls vorhanden, sonst entfällt dieser Abschnitt ]</span><br />
<span class="legal-tbd">[ Registernummer (z. B. HRB …) ]</span>
Registergericht: Amtsgericht Mannheim<br />
Registernummer: HRB 756189
</p>
<h2>Umsatzsteuer-Identifikationsnummer</h2>
<p>
Umsatzsteuer-Identifikationsnummer gemäß § 27a Umsatzsteuergesetz:<br />
<span class="legal-tbd">[ USt-IdNr. — falls vorhanden, sonst entfällt dieser Abschnitt ]</span>
DE458565520
</p>
<h2>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
<p>
<span class="legal-tbd">[ Name der verantwortlichen Person ]</span><br />
Raffael Sheikh<br />
Orbinstraße 1<br />
76646 Bruchsal
</p>
+190 -58
View File
@@ -3,11 +3,44 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ToolCTRL — Die Software, die eure Branche längst verdient hat</title>
<meta name="description" content="ToolCTRL baut moderne, durchdachte All-in-One-Software für Branchen, die zu lange mit veralteten Insellösungen auskommen mussten. Mit Fokus auf Bedienung und Gestaltung." />
<link rel="icon" type="image/png" href="/assets/favicon.png" />
<title>ToolCTRL: Moderne All-in-One-Software für eure Branche</title>
<meta name="description" content="Software mit echtem Fokus auf Bedienung und Gestaltung, die gut funktioniert, klar aussieht und sich schnell bedienen lässt." />
<link rel="canonical" href="https://tool-ctrl.de/" />
<meta name="robots" content="index, follow" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="ToolCTRL" />
<meta property="og:locale" content="de_DE" />
<meta property="og:url" content="https://tool-ctrl.de/" />
<meta property="og:title" content="ToolCTRL: Moderne All-in-One-Software für eure Branche" />
<meta property="og:description" content="Software mit echtem Fokus auf Bedienung und Gestaltung, die gut funktioniert, klar aussieht und sich schnell bedienen lässt." />
<meta property="og:image" content="https://tool-ctrl.de/assets/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="ToolCTRL Logo mit Tagline: Die Software, die eure Branche längst verdient hat." />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="ToolCTRL: Moderne All-in-One-Software für eure Branche" />
<meta name="twitter:description" content="Software mit echtem Fokus auf Bedienung und Gestaltung, die gut funktioniert, klar aussieht und sich schnell bedienen lässt." />
<meta name="twitter:image" content="https://tool-ctrl.de/assets/og-image.png" />
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "ToolCTRL",
"url": "https://tool-ctrl.de",
"logo": "https://tool-ctrl.de/assets/logo-a.png",
"description": "Software mit echtem Fokus auf Bedienung und Gestaltung, die gut funktioniert, klar aussieht und sich schnell bedienen lässt.",
"sameAs": ["https://www.linkedin.com/company/toolctrl/"]
}
</script>
<link rel="icon" type="image/png" href="/assets/favicon.png?v=20260610" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="/styles.css?v=20260523" />
<link rel="stylesheet" href="/styles.css?v=20260610" />
</head>
<body>
@@ -18,9 +51,7 @@
<img src="/assets/logo-black.svg" alt="ToolCTRL" class="brand-logo" />
</a>
<nav class="nav" aria-label="Hauptnavigation">
<a href="#leistungen">Leistungen</a>
<a href="#vorgehen">Vorgehen</a>
<a href="#arbeit">Organisation</a>
<a href="/">Start</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a class="btn btn-solid btn-sm" href="#kontakt">Kontakt aufnehmen</a>
@@ -30,9 +61,7 @@
</button>
</div>
<div class="mobile-nav" hidden>
<a href="#leistungen">Leistungen</a>
<a href="#vorgehen">Vorgehen</a>
<a href="#arbeit">Organisation</a>
<a href="/">Start</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a class="btn btn-solid" href="#kontakt">Kontakt aufnehmen</a>
@@ -56,8 +85,7 @@
klar aussieht und sich schnell bedienen lässt.
</p>
<div class="hero-actions">
<a class="btn btn-solid" href="#kontakt">Projekt besprechen</a>
<a class="btn btn-ghost" href="#leistungen">Leistungen ansehen</a>
<a class="btn btn-solid" href="#kontakt">Kontakt aufnehmen</a>
</div>
</div>
@@ -143,10 +171,11 @@
<span class="eyebrow">Leistungen</span>
<h2 class="section-title">Die digitale Infrastruktur Ihres Betriebs.</h2>
<p class="section-intro">
Wir arbeiten dort, wo Tabellen, manuelle Schritte und getrennte
Systeme zu viel Zeit kosten. Daraus bauen wir eine tragfähige
Infrastruktur: Software, Automatisierung und Schnittstellen,
zugeschnitten auf Ihren Ablauf, nicht umgekehrt.
Wir arbeiten dort, wo manuelle Schritte an alleinstehenden Tabellen
und veralteten Insellösungen zu viel Zeit und Nerven kosten. Daraus
bauen wir moderne und tragfähige Infrastrukturen aus Prozessen,
Automatisierung und Schnittstellen, zugeschnitten auf Ihren
Betrieb und Ihren Ablauf, nicht angepasst.
</p>
</div>
@@ -154,39 +183,40 @@
<article class="card">
<span class="icon-key"><img src="/assets/icons/graph.svg" alt="" class="card-icon" /></span>
<h3>Prozessautomatisierung</h3>
<p>Wiederkehrende Abläufe werden zu verlässlichen Workflows, die
ohne manuelles Zutun ausgelöst, verarbeitet und dokumentiert
werden.</p>
<p>ToolCTRL wandelt repetitive Prozesse in eine komplett
automatische Hands-Off-Erfahrung, während Sie sich um die
wichtigeren Aufgaben im Betrieb kümmern.</p>
</article>
<article class="card">
<span class="icon-key"><img src="/assets/icons/settings.svg" alt="" class="card-icon" /></span>
<h3>Individuelle Software</h3>
<p>Anwendungen, die exakt zu Ihrem Geschäft passen. Schlank
gebaut, klar bedienbar und auf Dauer wartbar.</p>
<p>Wir bauen Software, die Ihrem Betrieb so gut sitzt wie ein
maßgeschneiderter Anzug.</p>
</article>
<article class="card">
<span class="icon-key"><img src="/assets/icons/chart.svg" alt="" class="card-icon" /></span>
<h3>Dashboards &amp; Datenflüsse</h3>
<p>Verstreute Daten an einem Ort: aktuell, übersichtlich und
als Grundlage für schnelle Entscheidungen.</p>
<p>Alle Datenpunkte integriert und aufbereitet. Ihre ganz eigene
Kommandozentrale.</p>
</article>
<article class="card">
<span class="icon-key"><img src="/assets/icons/share.svg" alt="" class="card-icon" /></span>
<h3>Schnittstellen &amp; Integrationen</h3>
<p>Bestehende Tools, APIs und Systeme greifen ineinander, statt
nebeneinander zu laufen.</p>
<p>Bestehende Tools arbeiten zusammen, statt aneinander vorbei.
Was in einem System passiert, ist im nächsten sofort da.</p>
</article>
<article class="card">
<span class="icon-key"><img src="/assets/icons/columns.svg" alt="" class="card-icon" /></span>
<h3>Interne Tools</h3>
<p>Werkzeuge für das eigene Team, die verstreute Tabellenlösungen
ablösen und mit Ihren Abläufen mitwachsen.</p>
<p>Tools, die ganze Teams durch ihre Arbeitstage tragen. Keine
zerstückelten Teillösungen und Tabellenblätter mehr.</p>
</article>
<article class="card">
<span class="icon-key"><img src="/assets/icons/time.svg" alt="" class="card-icon" /></span>
<h3>Betrieb &amp; Weiterentwicklung</h3>
<p>Nach dem Launch ist nicht Schluss: Wartung, Anpassung und
Ausbau aus einer Hand.</p>
<p>Nach dem Launch ist nicht Schluss: Wartung, Anpassung,
Erweiterungen und Updates aus einer Hand. Unsere Software
wächst mit Ihrem Betrieb.</p>
</article>
</div>
</div>
@@ -196,38 +226,46 @@
<section class="section section-alt" id="vorgehen">
<div class="wrap">
<div class="section-head">
<span class="eyebrow">Vorgehen</span>
<h2 class="section-title">In vier klaren Schritten.</h2>
<span class="eyebrow">Konzept</span>
<h2 class="section-title">Das ToolCTRL Konzept.</h2>
<p class="section-intro">
Kein Wasserfall, kein Blindflug. Ein Ablauf, der Ergebnisse früh
sichtbar macht und Sie an jeder Stelle einbezieht.
Ihr Anliegen wird in vier klare Schritte unterteilt: von der
Situationsanalyse über die ersten Grundkonzepte in die konkrete
Umsetzung und den Betrieb. Unser Team ist an jeder Stelle für
Sie da, um bestmögliche Ergebnisse zu liefern und
sicherzustellen.
</p>
</div>
<ol class="steps">
<li class="step">
<span class="step-num">01</span>
<h3>Analyse</h3>
<p>Wir sehen uns Ihren Ablauf an und legen offen, wo Zeit, Daten
und Klarheit verloren gehen.</p>
<h3>Situationsanalyse</h3>
<p>Unser Team geht mit Ihnen alle Prozesse und Abläufe in Ihrem
Betrieb durch, um klarzustellen, wo Zeit, Effizienz und damit
auch Geld verloren gehen.</p>
</li>
<li class="step">
<span class="step-num">02</span>
<h3>Konzept</h3>
<p>Ein konkreter Lösungsvorschlag mit Aufwand, Umfang und
erwartetem Nutzen, transparent kalkuliert.</p>
<h3>Grundkonzepte</h3>
<p>Aus der Situationsanalyse erstellen wir zusammen ein
Lastenheft. Daraus bauen wir Ihnen erste Lösungskonzepte, die
wir iterativ auf Basis Ihres Feedbacks anpassen, bis wir zu
Ihrem gewünschten Endergebnis kommen.</p>
</li>
<li class="step">
<span class="step-num">03</span>
<h3>Umsetzung</h3>
<p>Wir bauen in kurzen Zyklen. Sie sehen früh ein funktionierendes
Ergebnis statt nur Folien.</p>
<p>Nach Vereinbarung des Lösungswegs bekommen Sie früh Ihre
erste funktionierende Version mit allen Grundfunktionen.
Iterativ wird Ihre Software verbessert und zum gewünschten
Endergebnis geformt.</p>
</li>
<li class="step">
<span class="step-num">04</span>
<h3>Betrieb</h3>
<p>Übergabe, Einweisung und laufende Pflege, damit die Lösung
dauerhaft trägt.</p>
<h3>Betrieb &amp; Wartung</h3>
<p>Übergabe, Einweisungen, Updates und Wartung, damit Ihre
Software langfristig tragfähig bleibt.</p>
</li>
</ol>
</div>
@@ -280,6 +318,87 @@
</div>
</section>
<!-- ============ STANDORT / EU-HOSTING ============ -->
<section class="section section-alt" id="standort">
<div class="wrap">
<div class="section-head standort-head">
<span class="eyebrow">Standort</span>
<h2 class="section-title">Ihre Daten gehören nicht nach Virginia.</h2>
<p class="section-intro">
Die meisten Software-Anbieter, mit denen deutsche Unternehmen täglich
arbeiten, sind US-Konzerne. Heißt: Ihre Daten unterliegen US-Recht,
unabhängig davon, was im Vertrag steht oder wo der Server tatsächlich
läuft.
</p>
<p class="standort-claim">
Wir entwickeln in Deutschland, hosten in Deutschland und haften in
Deutschland.
</p>
</div>
<ol class="steps steps-3">
<li class="step">
<span class="step-num">01</span>
<h3>In Deutschland gehostet</h3>
<p>Unsere Server stehen in deutschen Rechenzentren. Keine versteckte
Subunternehmer-Kette, die im US-Cloud-Stack endet.</p>
</li>
<li class="step">
<span class="step-num">02</span>
<h3>Dem deutschen Recht unterstellt</h3>
<p>Verträge nach BGB, Datenschutz nach DSGVO, ohne Hintertür. Der
CLOUD Act hat hier nichts verloren.</p>
</li>
<li class="step">
<span class="step-num">03</span>
<h3>Von Menschen erreichbar</h3>
<p>Kein Support-Ticket im Niemandsland. Wenn etwas brennt, sprechen
Sie mit den Leuten, die Ihre Software gebaut haben.</p>
</li>
</ol>
</div>
</section>
<!-- ============ EVENTS / COMMUNITY ============ -->
<section class="section event-section" id="events">
<div class="wrap">
<div class="section-head event-head">
<span class="eyebrow">Events</span>
<h2 class="section-title">Lieber ausprobieren als schönreden.</h2>
<p class="section-intro">
TechDay und GameJam zeigen unsere Arbeit dort, wo echte Leute sie
anfassen: lokal, direkt und mit Ergebnissen, über die man reden kann.
</p>
</div>
<div class="event-grid">
<article class="event-card event-card-media">
<div class="event-poster">
<img src="/assets/techday-event.png" alt="TechDay Bruchsal 2026 Motiv" width="683" height="384" loading="lazy" decoding="async" />
</div>
<div>
<span class="event-label">27. Mai 2026</span>
<h3>TechDay Bruchsal</h3>
<p>Talks, Workshops und Austausch rund um Technologie in Bruchsal.</p>
<a href="/techday.html">TechDay ansehen</a>
</div>
</article>
<article class="event-card event-card-code">
<div class="mini-terminal" aria-hidden="true">
<span>AI_GAMEJAM</span>
<strong>Human in the Loop</strong>
<em>2 Stunden / KI / spielbar</em>
</div>
<div>
<span class="event-label">TechDay Format</span>
<h3>AI GameJam</h3>
<p>Ein kompaktes Format, bei dem aus Idee, KI-Unterstützung und menschlicher Steuerung ein spielbares Ergebnis wird.</p>
<a href="/gamejam.html">GameJam ansehen</a>
</div>
</article>
</div>
</div>
</section>
<!-- ============ KONTAKT ============ -->
<section class="section section-dark" id="kontakt">
<div class="wrap kontakt-grid">
@@ -287,13 +406,13 @@
<span class="eyebrow eyebrow-light">Kontakt</span>
<h2 class="section-title">Sprechen wir über Ihr Vorhaben.</h2>
<p class="section-intro">
Beschreiben Sie kurz, woran es hakt. Wir melden uns mit einer
ehrlichen Einschätzung. Unverbindlich und ohne Verkaufsdruck.
Interesse? Fragen? Lassen Sie uns darüber reden. Füllen Sie das
Formular aus, wir melden uns in Kürze bei Ihnen.
</p>
<dl class="contact-list">
<div>
<dt>E-Mail</dt>
<dd><a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a></dd>
<dd><a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a></dd>
</div>
<div>
<dt>LinkedIn</dt>
@@ -303,13 +422,25 @@
</div>
<form class="lead-form" id="lead-form" novalidate>
<div class="form-row">
<label for="f-name">Name <span>*</span></label>
<input id="f-name" name="name" type="text" autocomplete="name" required />
<div class="form-grid">
<div class="form-row">
<label for="f-vorname">Vorname <span>*</span></label>
<input id="f-vorname" name="vorname" type="text" autocomplete="given-name" required />
</div>
<div class="form-row">
<label for="f-nachname">Nachname <span>*</span></label>
<input id="f-nachname" name="nachname" type="text" autocomplete="family-name" required />
</div>
</div>
<div class="form-row">
<label for="f-firma">Firma</label>
<input id="f-firma" name="firma" type="text" autocomplete="organization" />
<div class="form-grid">
<div class="form-row">
<label for="f-firmenname">Firmenname</label>
<input id="f-firmenname" name="firmenname" type="text" autocomplete="organization" />
</div>
<div class="form-row">
<label for="f-branche">Branche</label>
<input id="f-branche" name="branche" type="text" />
</div>
</div>
<div class="form-grid">
<div class="form-row">
@@ -317,14 +448,14 @@
<input id="f-email" name="email" type="email" autocomplete="email" required />
</div>
<div class="form-row">
<label for="f-telefon">Telefon</label>
<label for="f-telefon">Telefonnummer</label>
<input id="f-telefon" name="telefon" type="tel" autocomplete="tel" />
</div>
</div>
<div class="form-row">
<label for="f-nachricht">Worum geht es? <span>*</span></label>
<label for="f-nachricht">Worum geht es Ihnen? <span>*</span></label>
<textarea id="f-nachricht" name="nachricht" rows="4" required
placeholder="Kurz Ihr Anliegen — wir kümmern uns um die Details."></textarea>
placeholder="Kurz Ihr Anliegen, wir kümmern uns um die Details."></textarea>
</div>
<!-- Honeypot gegen Spam -->
<div class="hp" aria-hidden="true">
@@ -363,7 +494,8 @@
</div>
<div class="footer-col">
<span class="footer-h">Kontakt</span>
<a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a>
<a href="tel:+4915237750939">+49 1523 7750939</a>
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<a href="https://tool-ctrl.de">tool-ctrl.de</a>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
</div>
@@ -375,6 +507,6 @@
</div>
</footer>
<script src="/script.js"></script>
<script src="/script.js?v=20260610"></script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
User-agent: *
Allow: /
Disallow: /gamejam-games/
Sitemap: https://tool-ctrl.de/sitemap.xml
+14 -5
View File
@@ -46,8 +46,9 @@
function validate() {
var ok = true;
["f-name", "f-email", "f-nachricht"].forEach(function (id) {
["f-vorname", "f-nachname", "f-email", "f-nachricht"].forEach(function (id) {
var el = document.getElementById(id);
if (!el) return;
var row = fieldRow(el);
var valid = el.value.trim() !== "";
if (id === "f-email" && valid) {
@@ -68,14 +69,22 @@
}
var btn = form.querySelector("button[type=submit]");
var vorname = form.vorname.value.trim();
var nachname = form.nachname.value.trim();
var firmenname = form.firmenname.value.trim();
var branche = form.branche.value.trim();
var payload = {
name: form.name.value.trim(),
firma: form.firma.value.trim(),
name: (vorname + " " + nachname).trim(),
vorname: vorname,
nachname: nachname,
firma: firmenname + (branche ? " (" + branche + ")" : ""),
firmenname: firmenname,
branche: branche,
email: form.email.value.trim(),
telefon: form.telefon.value.trim(),
nachricht: form.nachricht.value.trim(),
website: form.website.value.trim(), // Honeypot
quelle: "Messe — Landing Page"
quelle: "Landing Page"
};
if (btn) { btn.disabled = true; btn.textContent = "Wird gesendet …"; }
@@ -108,7 +117,7 @@
})
.catch(function () {
setStatus(
"Verbindung fehlgeschlagen. Bitte schreiben Sie uns direkt an admin@tool-ctrl.de.",
"Verbindung fehlgeschlagen. Bitte schreiben Sie uns direkt an info@tool-ctrl.de.",
"err"
);
})
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://tool-ctrl.de/</loc>
<lastmod>2026-06-10</lastmod>
<priority>1.0</priority>
</url>
<url>
<loc>https://tool-ctrl.de/techday.html</loc>
<lastmod>2026-06-10</lastmod>
<priority>0.8</priority>
</url>
<url>
<loc>https://tool-ctrl.de/gamejam.html</loc>
<lastmod>2026-06-10</lastmod>
<priority>0.6</priority>
</url>
</urlset>
+260 -18
View File
@@ -64,12 +64,21 @@
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
html {
scroll-behavior: smooth;
scroll-padding-top: 86px;
background:
linear-gradient(90deg, rgba(10,10,10,.032) 1px, transparent 1px),
linear-gradient(0deg, rgba(10,10,10,.032) 1px, transparent 1px),
var(--paper);
background-size: 72px 72px;
background-attachment: fixed;
}
body {
font-family: var(--grotesk);
color: var(--ink);
background: var(--paper);
background: transparent;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
@@ -232,10 +241,20 @@ a { color: inherit; text-decoration: none; }
font-size: 14px;
font-weight: 500;
color: var(--ink);
padding-bottom: 2px;
border-bottom: 1.5px solid transparent;
padding: 12px 10px;
position: relative;
}
.nav > a:not(.btn):hover { border-bottom-color: var(--black); }
.nav > a:not(.btn)::after {
content: "";
position: absolute;
left: 10px;
right: 10px;
bottom: 10px;
height: 1.5px;
background: transparent;
transition: background .15s ease;
}
.nav > a:not(.btn):hover::after { background: var(--black); }
.nav-toggle {
display: none;
@@ -272,9 +291,6 @@ a { color: inherit; text-decoration: none; }
/* ============================================================
HERO
============================================================ */
.hero {
border-bottom: 1px solid var(--line);
}
.hero-grid {
display: grid;
grid-template-columns: 1.05fr .95fr;
@@ -481,6 +497,37 @@ a { color: inherit; text-decoration: none; }
color: var(--gray-1);
}
/* ---------- Standort / EU-Hosting ---------- */
/* Großes, blasses Paragraf-Zeichen oben rechts — Wasserzeichen, analog
zum CTRL-Schriftzug im Hero. Thematisch: Recht, DSGVO, CLOUD Act. */
#standort {
position: relative;
isolation: isolate;
overflow: hidden;
}
#standort .wrap { position: relative; }
#standort .wrap::before {
content: "\00A7";
position: absolute;
top: -0.12em;
right: 11%;
z-index: -1;
color: rgba(10, 10, 10, .045);
font-size: clamp(240px, 28vw, 420px);
line-height: 1;
font-weight: 900;
pointer-events: none;
}
.steps-3 { grid-template-columns: repeat(3, 1fr); }
.standort-claim {
font-size: clamp(18px, 1.9vw, 22px);
font-weight: 700;
letter-spacing: -.01em;
line-height: 1.3;
margin-top: 22px;
color: var(--ink);
}
/* ============================================================
ARBEITSWEISE + KANBAN
============================================================ */
@@ -607,14 +654,196 @@ a { color: inherit; text-decoration: none; }
.contact-list dd { font-size: 15px; }
.contact-list a:hover { text-decoration: underline; }
/* ---------- Events Section ---------- */
.event-section {
background: transparent;
color: var(--black);
padding: clamp(48px, 8vh, 96px) 0;
overflow: clip;
}
.event-section .section-head {
max-width: 880px;
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
margin: 0 auto clamp(34px, 4.4vh, 48px);
text-align: center;
}
.event-section .section-title {
color: var(--black);
margin: 22px 0 0;
font-size: clamp(34px, 4vw, 54px);
line-height: .96;
max-width: 820px;
}
.event-section .eyebrow { align-self: center; }
.event-section .section-intro {
color: var(--gray-1);
font-size: 16px;
line-height: 1.45;
max-width: 620px;
margin: 24px auto 0;
}
.event-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: clamp(22px, 2.5vw, 30px);
align-items: stretch;
}
.event-card {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(178px, .82fr);
gap: clamp(16px, 1.8vw, 22px);
align-items: center;
background: var(--paper);
color: var(--black);
border: 3px solid var(--black);
padding: clamp(16px, 1.8vw, 22px);
box-shadow: 7px 7px 0 var(--black);
cursor: pointer;
transition: transform .15s cubic-bezier(.2, 0, 0, 1), box-shadow .15s ease;
}
.event-card a::after {
content: "";
position: absolute;
inset: 0;
}
.event-card:hover {
transform: translate(-2px, -2px);
box-shadow: 9px 9px 0 var(--black);
}
.event-card h3 {
font-size: clamp(22px, 2vw, 30px);
line-height: 1;
margin-bottom: 8px;
color: var(--black);
}
.event-card p {
font-size: 14px;
line-height: 1.42;
color: #3A3A3A;
margin-bottom: 14px;
}
.event-card a {
display: inline-flex;
align-items: center;
justify-content: center;
width: fit-content;
max-width: 100%;
min-height: 40px;
padding: 8px 12px;
font-size: 13px;
font-weight: 900;
line-height: 1.1;
letter-spacing: .08em;
text-transform: uppercase;
text-align: center;
color: var(--black);
border: 2px solid currentColor;
box-shadow: 3px 3px 0 currentColor;
}
.event-card-code h3 { color: var(--black); }
.event-card-code p { color: #3A3A3A; }
.event-card-code a {
color: var(--black);
border-color: var(--black);
box-shadow: 3px 3px 0 var(--black);
}
.event-poster,
.mini-terminal {
border: 3px solid var(--black);
box-shadow: 4px 4px 0 var(--black);
}
.event-poster {
height: clamp(168px, 23vh, 230px);
overflow: hidden;
background: var(--paper);
}
.event-poster img {
width: 100%;
height: 100%;
object-fit: contain;
filter: contrast(1.08) saturate(.92);
}
.mini-terminal {
height: clamp(168px, 23vh, 230px);
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 18px;
background:
linear-gradient(90deg, rgba(255,255,255,.12) 1px, transparent 1px),
linear-gradient(0deg, rgba(255,255,255,.12) 1px, transparent 1px),
var(--black);
background-size: 20px 20px;
color: var(--paper);
border-color: var(--paper);
box-shadow: 4px 4px 0 #A8A8A8;
}
.mini-terminal span {
font-size: 11px;
font-weight: 900;
letter-spacing: .18em;
}
.mini-terminal strong {
max-width: 8em;
font-size: clamp(26px, 3vw, 38px);
line-height: .94;
letter-spacing: -.04em;
color: var(--paper);
}
.mini-terminal em {
font-style: normal;
font-size: 13px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
}
.event-label {
display: block;
font-size: 11px;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--gray-2);
font-weight: 700;
margin-bottom: 6px;
}
@media (max-width: 760px) {
.event-grid { grid-template-columns: 1fr; }
.event-card { grid-template-columns: 1fr; }
}
/* ---------- Form ---------- */
.lead-form {
background: var(--paper-2);
color: var(--ink);
padding: 36px 34px;
border-radius: 14px;
padding: 28px;
border-radius: 0;
border: 1.5px solid var(--paper);
box-shadow: 12px 12px 0 rgba(255,255,255,.12);
}
.form-row { margin-bottom: 20px; }
.section-dark .lead-form .btn.btn-solid {
border-color: var(--black);
box-shadow:
0 4px 0 #000000,
0 10px 16px -12px rgba(0,0,0,.45),
inset 0 2px 0 rgba(255,255,255,.28);
}
.section-dark .lead-form .btn.btn-solid:hover {
box-shadow:
0 5px 0 #000000,
0 12px 18px -12px rgba(0,0,0,.45),
inset 0 2px 0 rgba(255,255,255,.28);
}
.section-dark .lead-form .btn.btn-solid:active {
box-shadow:
0 1px 0 #000000,
0 6px 10px -10px rgba(0,0,0,.4),
inset 0 1px 0 rgba(255,255,255,.2);
}
.form-row { margin-bottom: 15px; }
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -622,7 +851,7 @@ a { color: inherit; text-decoration: none; }
}
.form-row label {
display: block;
font-size: 12px;
font-size: 11px;
font-weight: 700;
letter-spacing: .08em;
text-transform: uppercase;
@@ -639,17 +868,26 @@ a { color: inherit; text-decoration: none; }
background: var(--paper);
border: 1.5px solid var(--line);
border-radius: 7px;
padding: 12px 14px;
transition: border-color .15s ease;
padding: 11px 13px;
transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.form-row:focus-within label { color: var(--black); }
.form-row input:focus,
.form-row textarea:focus {
outline: none;
border-color: var(--black);
background: #FFFFFF;
box-shadow: 0 0 0 3px rgba(255,255,255,.9), 0 0 0 4px rgba(10,10,10,.18);
}
.form-row textarea {
resize: vertical;
min-height: 118px;
}
.form-row textarea { resize: vertical; }
.form-row.invalid input,
.form-row.invalid textarea { border-color: #C0392B; }
.form-row.invalid textarea {
border-color: var(--black);
box-shadow: inset 0 0 0 2px var(--black);
}
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
@@ -661,7 +899,7 @@ a { color: inherit; text-decoration: none; }
}
.form-foot .btn { flex: none; }
.form-note {
font-size: 12px;
font-size: 11px;
color: var(--gray-2);
line-height: 1.45;
}
@@ -679,7 +917,7 @@ a { color: inherit; text-decoration: none; }
border-radius: 7px;
}
.form-status.ok { background: var(--black); color: var(--paper); }
.form-status.err { border-color: #C0392B; color: #C0392B; }
.form-status.err { border-color: var(--black); color: var(--black); }
/* ============================================================
FOOTER
@@ -744,6 +982,10 @@ a { color: inherit; text-decoration: none; }
.steps { grid-template-columns: 1fr 1fr; }
.step:nth-child(2) { border-right: none; }
.step:nth-child(1), .step:nth-child(2) { border-bottom: 1px solid var(--line); }
/* Standort-Grid bleibt dreispaltig — drei kurze Karten passen */
.steps-3 { grid-template-columns: repeat(3, 1fr); }
.steps-3 .step:nth-child(2) { border-right: 1px solid var(--line); }
.steps-3 .step:nth-child(1), .steps-3 .step:nth-child(2) { border-bottom: none; }
.arbeit-grid { grid-template-columns: 1fr; gap: 44px; }
.kontakt-grid { grid-template-columns: 1fr; gap: 44px; }
.footer-inner { grid-template-columns: 1fr; gap: 36px; }
+54 -15
View File
@@ -3,10 +3,52 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>TechDay Bruchsal 2026 — ToolCTRL</title>
<title>TechDay Bruchsal 2026: ToolCTRL</title>
<meta name="description" content="ToolCTRL beim Bruchsaler TechDay 2026: Vortrag, AI GameJam und die mITapp. Am 27. Mai 2026 im HubWerk01 Bruchsal." />
<link rel="icon" type="image/png" href="/assets/favicon.png" />
<link rel="stylesheet" href="/styles.css" />
<link rel="canonical" href="https://tool-ctrl.de/techday.html" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="ToolCTRL" />
<meta property="og:locale" content="de_DE" />
<meta property="og:url" content="https://tool-ctrl.de/techday.html" />
<meta property="og:title" content="TechDay Bruchsal 2026: ToolCTRL" />
<meta property="og:description" content="ToolCTRL beim Bruchsaler TechDay 2026: Vortrag, AI GameJam und die mITapp. Am 27. Mai 2026 im HubWerk01 Bruchsal." />
<meta property="og:image" content="https://tool-ctrl.de/assets/techday-event.png" />
<meta property="og:image:width" content="683" />
<meta property="og:image:height" content="384" />
<meta property="og:image:alt" content="TechDay Bruchsal 2026 Eventmotiv" />
<meta name="twitter:card" content="summary_large_image" />
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Event",
"name": "TechDay Bruchsal 2026",
"startDate": "2026-05-27",
"eventStatus": "https://schema.org/EventScheduled",
"eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
"location": {
"@type": "Place",
"name": "HubWerk01",
"address": {
"@type": "PostalAddress",
"addressLocality": "Bruchsal",
"addressCountry": "DE"
}
},
"image": "https://tool-ctrl.de/assets/techday-event.png",
"description": "ToolCTRL beim Bruchsaler TechDay 2026: Vortrag, AI GameJam und die mITapp. Am 27. Mai 2026 im HubWerk01 Bruchsal.",
"organizer": {
"@type": "Organization",
"name": "ToolCTRL",
"url": "https://tool-ctrl.de"
}
}
</script>
<link rel="icon" type="image/png" href="/assets/favicon.png?v=20260610" />
<link rel="stylesheet" href="/styles.css?v=20260610" />
</head>
<body>
@@ -17,9 +59,7 @@
<img src="/assets/logo-black.svg" alt="ToolCTRL" class="brand-logo" />
</a>
<nav class="nav" aria-label="Hauptnavigation">
<a href="/#leistungen">Leistungen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/">Start</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a class="btn btn-solid btn-sm" href="/#kontakt">Kontakt aufnehmen</a>
@@ -29,9 +69,7 @@
</button>
</div>
<div class="mobile-nav" hidden>
<a href="/#leistungen">Leistungen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/">Start</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a class="btn btn-solid" href="/#kontakt">Kontakt aufnehmen</a>
@@ -124,7 +162,7 @@
<aside class="speaker-card">
<div class="sp-label">Speaker</div>
<div class="sp-head">
<img class="sp-photo" src="/assets/speaker-david.jpg" alt="David Bertram" />
<img class="sp-photo" src="/assets/speaker-david.jpg" alt="David Bertram" width="320" height="320" loading="lazy" decoding="async" />
<div>
<div class="sp-name">David Bertram</div>
<div class="sp-role">Co-Founder &amp; Head of Product, ToolCTRL</div>
@@ -157,15 +195,15 @@
<div class="mitapp-shots">
<figure class="shot">
<img src="/assets/mitapp-dashboard.png" alt="mITapp Dashboard-Ansicht" />
<img src="/assets/mitapp-dashboard.webp" alt="mITapp Dashboard-Ansicht" width="1100" height="825" loading="lazy" decoding="async" />
<figcaption>Dashboard</figcaption>
</figure>
<figure class="shot">
<img src="/assets/mitapp-roadmap.png" alt="mITapp Event-Roadmap" />
<img src="/assets/mitapp-roadmap.webp" alt="mITapp Event-Roadmap" width="1100" height="825" loading="lazy" decoding="async" />
<figcaption>Event-Roadmap</figcaption>
</figure>
<figure class="shot">
<img src="/assets/mitapp-qr.png" alt="mITapp Networking per QR-Code" />
<img src="/assets/mitapp-qr.webp" alt="mITapp Networking per QR-Code" width="1100" height="825" loading="lazy" decoding="async" />
<figcaption>Networking per QR-Code</figcaption>
</figure>
</div>
@@ -258,7 +296,8 @@
</div>
<div class="footer-col">
<span class="footer-h">Kontakt</span>
<a href="mailto:admin@tool-ctrl.de">admin@tool-ctrl.de</a>
<a href="tel:+4915237750939">+49 1523 7750939</a>
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<a href="https://tool-ctrl.de">tool-ctrl.de</a>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
</div>
@@ -270,6 +309,6 @@
</div>
</footer>
<script src="/script.js"></script>
<script src="/script.js?v=20260610"></script>
</body>
</html>
+181 -28
View File
@@ -19,32 +19,79 @@ const net = require("net");
const ROOT = __dirname;
const PUBLIC_DIR = path.join(ROOT, "public");
const LEADS_DIR = path.join(ROOT, "leads");
const DATA_DIR = process.env.DATA_DIR
? path.resolve(process.env.DATA_DIR)
: ROOT;
const LEADS_DIR = path.join(DATA_DIR, "leads");
const LEADS_JSON = path.join(LEADS_DIR, "leads.ndjson");
const LEADS_CSV = path.join(LEADS_DIR, "leads.csv");
/* GameJam-Speicher */
const GAMEJAM_DIR = path.join(ROOT, "gamejam");
const GAMEJAM_DIR = path.join(DATA_DIR, "gamejam");
const SUBMISSIONS_JSON = path.join(GAMEJAM_DIR, "submissions.ndjson");
const GAMES_DIR = path.join(PUBLIC_DIR, "gamejam-games");
const GAMES_DIR = process.env.DATA_DIR
? path.join(DATA_DIR, "gamejam-games")
: path.join(PUBLIC_DIR, "gamejam-games");
const MAX_UPLOAD = 5 * 1024 * 1024; /* 5 MB pro eingereichtem Spiel */
/* Hart-Deadline für GameJam-Uploads: 27.05.2026, 22:00 Europe/Berlin (CEST = UTC+2). */
const UPLOAD_DEADLINE = new Date("2026-05-27T22:00:00+02:00");
const APP_REVISION = process.env.APP_REVISION || "local";
/* Central lead-inbox (infra ADR 012). Sites forward leads here; local files
stay as a fallback. Unset env => old local-only behavior. */
const LEAD_INBOX_URL = process.env.LEAD_INBOX_URL || "";
const LEAD_INBOX_KEY = process.env.LEAD_INBOX_KEY || "";
async function forwardLeadToInbox(lead) {
if (!LEAD_INBOX_URL || !LEAD_INBOX_KEY) return false;
try {
const res = await fetch(LEAD_INBOX_URL.replace(/\/+$/, "") + "/v1/leads", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + LEAD_INBOX_KEY
},
/* inbox accepts DE aliases: firma, telefon, nachricht, quelle */
body: JSON.stringify(lead),
signal: AbortSignal.timeout(5000)
});
return res.ok;
} catch (e) {
console.error("[lead] inbox forward failed:", e.message);
return false;
}
}
function envFlag(name, defaultValue) {
const raw = process.env[name];
if (raw == null || raw === "") return defaultValue;
return !/^(0|false|no|off)$/i.test(raw);
}
function envInt(name, defaultValue) {
const raw = Number.parseInt(process.env[name] || "", 10);
return Number.isFinite(raw) ? raw : defaultValue;
}
/* Virenscan über ClamAV. Der Server spricht das clamd-Socket-Protokoll
direkt — keine zusätzliche Abhängigkeit. Voraussetzung: ClamAV ist
installiert und der Daemon "clamd" läuft (Einrichtung siehe README).
enabled:false schaltet den Scan ab (z. B. lokal ohne ClamAV). */
const CLAMAV = {
enabled: true,
host: "127.0.0.1",
port: 3310, /* clamd TCPSocket-Standardport */
timeoutMs: 15000
enabled: envFlag("CLAMAV_ENABLED", true),
host: process.env.CLAMAV_HOST || "127.0.0.1",
port: envInt("CLAMAV_PORT", 3310), /* clamd TCPSocket-Standardport */
timeoutMs: envInt("CLAMAV_TIMEOUT_MS", 15000)
};
/* Bevorzugte Ports — der erste freie wird genutzt. */
const PORT_CANDIDATES = [4317, 4318, 4417, 5317, 8317, 0];
const PORT_CANDIDATES = process.env.PORT
? [envInt("PORT", 3000)]
: [4317, 4318, 4417, 5317, 8317, 0];
/* 0.0.0.0 = auf allen Schnittstellen erreichbar, auch für andere
Geräte im selben WLAN (z. B. Handys, die den QR-Code scannen). */
const HOST = "0.0.0.0";
const HOST = process.env.HOST || process.env.HOSTNAME || "0.0.0.0";
/* Erste IPv4-LAN-Adresse dieses Rechners ermitteln. */
function getLanIP() {
@@ -65,9 +112,12 @@ const MIME = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".ico": "image/x-icon",
".json": "application/json; charset=utf-8",
".woff2": "font/woff2"
".woff2": "font/woff2",
".txt": "text/plain; charset=utf-8",
".xml": "application/xml; charset=utf-8"
};
/* ---------- Lead-Speicher vorbereiten ---------- */
@@ -88,6 +138,18 @@ function ensureGamejamStore() {
if (!fs.existsSync(GAMES_DIR)) fs.mkdirSync(GAMES_DIR, { recursive: true });
}
function assertInside(baseDir, candidate) {
const relative = path.relative(baseDir, candidate);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function writeProbe(dir) {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const file = path.join(dir, ".write-probe-" + process.pid + "-" + Date.now());
fs.writeFileSync(file, "ok", "utf8");
fs.unlinkSync(file);
}
function csvCell(value) {
const s = String(value == null ? "" : value);
return '"' + s.replace(/"/g, '""').replace(/\r?\n/g, " ") + '"';
@@ -113,11 +175,30 @@ function saveLead(lead) {
/* ---------- Statische Dateien ---------- */
function serveStatic(req, res) {
let urlPath = decodeURIComponent(req.url.split("?")[0]);
/* SEO: kanonischer Host (www -> non-www) und /index.html -> / */
const host = (req.headers.host || "").toLowerCase();
if (host === "www.tool-ctrl.de") {
res.writeHead(301, { Location: "https://tool-ctrl.de" + req.url });
res.end();
return;
}
if (urlPath === "/index.html") {
res.writeHead(301, { Location: "/" });
res.end();
return;
}
if (urlPath === "/") urlPath = "/index.html";
/* Path-Traversal verhindern */
const filePath = path.normalize(path.join(PUBLIC_DIR, urlPath));
if (!filePath.startsWith(PUBLIC_DIR)) {
let baseDir = PUBLIC_DIR;
let filePath = path.normalize(path.join(PUBLIC_DIR, urlPath));
if (urlPath.startsWith("/gamejam-games/")) {
baseDir = GAMES_DIR;
filePath = path.normalize(path.join(GAMES_DIR, urlPath.replace(/^\/gamejam-games\//, "")));
}
if (!assertInside(baseDir, filePath)) {
res.writeHead(403).end("Forbidden");
return;
}
@@ -129,10 +210,19 @@ function serveStatic(req, res) {
return;
}
const ext = path.extname(filePath).toLowerCase();
res.writeHead(200, {
/* HTML immer frisch, statische Assets lange cachen (Versionierung via ?v=) */
const cacheControl = ext === ".html"
? "no-cache"
: "public, max-age=31536000, immutable";
const headers = {
"Content-Type": MIME[ext] || "application/octet-stream",
"Cache-Control": "no-cache"
});
"Cache-Control": cacheControl
};
/* Hochgeladene GameJam-Spiele nicht indexieren (Fremdcontent) */
if (urlPath.startsWith("/gamejam-games/")) {
headers["X-Robots-Tag"] = "noindex";
}
res.writeHead(200, headers);
fs.createReadStream(filePath).pipe(res);
});
}
@@ -188,20 +278,27 @@ function handleLead(req, res) {
quelle: String(data.quelle || "Landing Page").slice(0, 120)
};
try {
saveLead(lead);
} catch (e) {
console.error("[lead] Speichern fehlgeschlagen:", e.message);
return sendJSON(res, 500, {
error: "Speichern fehlgeschlagen. Bitte später erneut versuchen."
});
}
/* Central inbox is source of truth; local files are the fallback so a
lead is never lost if the inbox is unreachable (infra ADR 012). */
forwardLeadToInbox(lead).then((forwarded) => {
if (!forwarded) {
try {
saveLead(lead);
} catch (e) {
console.error("[lead] Speichern fehlgeschlagen:", e.message);
return sendJSON(res, 500, {
error: "Speichern fehlgeschlagen. Bitte später erneut versuchen."
});
}
}
console.log(
"[lead] " + lead.zeitpunkt + " — " + lead.name +
(lead.firma ? " (" + lead.firma + ")" : "") + " <" + lead.email + ">"
);
sendJSON(res, 200, { ok: true });
console.log(
"[lead] " + (forwarded ? "→inbox " : "→local ") +
lead.zeitpunkt + " — " + lead.name +
(lead.firma ? " (" + lead.firma + ")" : "") + " <" + lead.email + ">"
);
sendJSON(res, 200, { ok: true });
});
});
}
@@ -309,6 +406,13 @@ function clamavPing() {
/* ---------- GameJam: Spiel einreichen ---------- */
function handleGamejamUpload(req, res) {
/* Hart-Deadline durchsetzen — danach werden keine Einreichungen mehr angenommen. */
if (Date.now() > UPLOAD_DEADLINE.getTime()) {
return sendJSON(res, 410, {
error: "Einreichungen sind seit dem 27.05.2026, 22:00 Uhr geschlossen."
});
}
const ctype = req.headers["content-type"] || "";
const bM = ctype.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
if (!/multipart\/form-data/i.test(ctype) || !bM) {
@@ -439,10 +543,57 @@ function sendJSON(res, code, obj) {
res.end(JSON.stringify(obj));
}
function handleHealthz(req, res) {
if (req.method !== "GET" && req.method !== "HEAD") {
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
}
sendJSON(res, 200, {
ok: true,
service: "toolctrl-website",
revision: APP_REVISION
});
}
async function handleReadyz(req, res) {
if (req.method !== "GET" && req.method !== "HEAD") {
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
}
const checks = {
leadsWritable: false,
gamejamWritable: false,
gamesWritable: false,
clamav: !CLAMAV.enabled
};
try {
writeProbe(LEADS_DIR);
checks.leadsWritable = true;
writeProbe(GAMEJAM_DIR);
checks.gamejamWritable = true;
writeProbe(GAMES_DIR);
checks.gamesWritable = true;
if (CLAMAV.enabled) checks.clamav = await clamavPing();
} catch (e) {
console.error("[readyz] Prüfung fehlgeschlagen:", e.message);
}
const ok = Object.keys(checks).every((key) => checks[key] === true);
sendJSON(res, ok ? 200 : 503, {
ok: ok,
service: "toolctrl-website",
revision: APP_REVISION,
checks: checks
});
}
/* ---------- Router ---------- */
const server = http.createServer((req, res) => {
const route = req.url.split("?")[0];
if (route === "/healthz") return handleHealthz(req, res);
if (route === "/readyz") return handleReadyz(req, res);
if (route === "/api/lead") {
if (req.method !== "POST") {
return sendJSON(res, 405, { error: "Methode nicht erlaubt." });
@@ -502,6 +653,8 @@ function listen(candidates) {
}
console.log(" Leads: " + LEADS_CSV);
console.log(" GameJam-Einreichungen: " + SUBMISSIONS_JSON);
console.log(" Daten: " + DATA_DIR);
console.log(" Revision: " + APP_REVISION);
if (CLAMAV.enabled) {
clamavPing().then((ok) => {
console.log(" Virenscan: " + (ok