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
37 changed files with 1114 additions and 3044 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.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2300" height="2000" version="1.0">
<path style="fill:#666" d="M1157.3928 199.37139c-.1561-.351-3.243-2.137-6.86-3.969-9.658-4.894-20.13-13.0491-32.228-25.098-10.972-10.9291-12.004-12.329-31.6471-42.912-3.1689-4.934-6.796-9.645-8.059-10.468-2.087-1.36-4.58-1.469-27.4779-1.206-27.382.315-37.043-.412-49.9371-3.755-18.12509-4.699-31.09801-12.288001-48.08206-28.123001-12.75503-11.893101-19.10594-18.966001-21.23697-23.651002-2.06203-4.532-1.98498-5.791.53398-8.781 2.6-3.086 6.57501-4.736 15.86602-6.585101 13.339-2.655 25.82102-10.881 42.01594-27.690001 27.24019-28.273002 49.92309-66.238004 62.40219-104.442006 22.544-69.020899 40.4279-213.453909 37.524-303.043919-2.466-76.05-9.554-108.479-24.358-111.436-9.0331-1.804-18.294 13.5529-25.24 41.853-4.2181 17.189-5.106 25.236-4.5591 41.309.538 15.8 2.3111 27.0349 7.9171 50.1769 2.126 8.7731 3.91 17.91601 3.964 20.31811.0869 3.815-.208 4.63-2.344 6.465-2.194 1.885-2.952 2.046-7.4181 1.58-15.831-1.652-31.538-10.826-59.80409-34.93001-15.40394-13.135-19.29891-15.761-23.38693-15.761-2.95903 0-3.14901.199-6.29802 6.588-8.40105 17.0499-30.92794 55.05191-47.98398 80.94701-13.42402 20.38-22.71602 33.032-32.47906 44.2229-3.87397 4.4421-7.98798 9.9231-9.14197 12.1802-6.07798 11.8959-1.32096 20.9719 10.99301 20.9719 6.33098 0 8.096-.788 21.70402-9.696 22.85602-14.963 39.85203-22.2741 53.95693-23.209 8.87509-.588 12.904.298 23.27309 5.118l7.66091 3.561 19.15103.04c17.53206.037 19.57116.1981 24.12316 1.894 6.138 2.287 14.5129 8.167 19.3109 13.5561 8.6371 9.7009 12.969 25.2479 11.197 40.18-1.724 14.5259-6.863 30.14591-13.6879 41.60681-3.3271 5.586-8.8301 11.9549-10.3311 11.9549-.4389 0-1.3849-1.4029-2.1029-3.1169-4.116-9.839-18.2071-26.49491-25.32818-29.93691-6.63397-3.208-9.25091-2.355-13.80897 4.50291-4.31795 6.4961-11.66698 13.4991-17.31103 16.494-10.31098 5.471-20.75796 6.758-43.13297 5.3169-18.58193-1.1979-30.32704-2.6529-49.36797-6.116-16.37707-2.9788-27.53798-3.3508-35.65998-1.1878-18.03399 4.8009-36.86601 22.6599-44.82301 42.5049-2.58505 6.447-2.691 7.1669-2.30905 15.711999.33105 7.418.85698 10.122 3.06498 15.755001 4.01603 10.251 8.26199 16.805001 17.01502 26.272001 22.32096 24.140101 33.88898 45.9380026 32.06408 60.4190034-.82907 6.5740006-3.50912 15.9820006-7.87506 27.6411016-4.90099 13.088001-5.04206 16.205001-1.07203 23.644001 4.27402 8.008001 10.37102 12.179001 22.72202 15.544001 3.66306.998 7.727 2.361 9.03202 3.029l2.37399 1.213-1.94803 3.472c-3.04497 5.426901-13.34996 14.474001-21.03296 18.466001-16.38598 8.514001-36.79802 9.508001-50.62102 2.464901-5.55703-2.830901-12.35897-10.466901-21.73599-24.397902-4.84702-7.202-10.50901-14.717001-12.581-16.700001-2.072-1.9821-8.01201-5.844-13.20101-8.5821-15.26999-8.056001-20.24599-13.750001-25.33299-28.990002-4.44-13.3020004-3.735-22.24700093 3.432-43.580902l3.551-10.571101-.361-11.07c-.299-9.210001-.692-11.825001-2.334-15.553001-2.315-5.259-5.55-8.2601-9.749-9.047001-9.155-1.715-14.093 9.350001-20.453 45.831003-5.554 31.858002-10.86401 47.075002-19.73301 56.545003-5.493 5.864-10.652 7.8711-21.374 8.315-7.90001.327-8.901.557-10.83201 2.485001-2.55899 2.5561-3.392 6.394-2.8 12.9101.544 5.993901 3.427 11.837901 9.095 18.435001 4.89802 5.701 8.06201 12.948001 7.25501 16.616001-2.156 9.807001-25.854 10.220001-45.97801.802-12.186-5.703-21.496-13.202001-27.711-22.322001-6.911-10.140001-8.373-17.960001-5.508-29.443102 1.931-7.735 1.489-13.448-1.373-17.782001-10.632-16.0949005-7.949-38.661002 6.365-53.549002 4.343-4.515901 6.944-6.245001 16.70701-11.105001 14.61-7.274 20.08099-10.911001 25.97599-17.270001 7.886-8.507 14.34-24.869101 12.879-32.649002-.793-4.219-5.715-9.252-10.288-10.5201-5.23-1.449999-13.107-1.133999-17.121.686-2.113.9591-7.73099 5.8041-14.044 12.1121-11.247 11.239001-15.697 14.228001-24.549 16.493001-4.987 1.276-5.901 1.263-13.955-.198-4.74-.861-11.361-1.564-14.713-1.564-5.524 0-6.399.244-9.362 2.61-3.855 3.078-8.229 10.837001-10.054 17.832901-.794 3.0461-1.302 8.714101-1.302 14.548101 0 10.604101-.879 13.648001-4.181 14.476001-4.816 1.207-20.202-4.755-29.09501-11.274001-5.61099-4.114-15.18499-14.536001-18.52999-20.172001-3.869-6.519-7.51701-17.451001-8.50501-25.482001-2.018-16.408001 5.87201-31.347 31.75101-60.1181 8.25799-9.1809 10.45099-12.8749 12.993-21.8908 3.563-12.63401 7.642-18.76101 16.86801-25.33611 6.08499-4.3359 11.93499-6.6639 20.05399-7.98 11.144-1.8059 22.516-.5989 55.681 5.9061 26.121 5.1231 38.328 6.448 47.582 5.163 8.37101-1.163 11.32702-2.588 23.51201-11.331 5.398-3.873 20.82001-13.675 34.27001-21.7809 27.81699-16.7641 29.583-18.2981 38.826-33.7191 2.99999-5.007 14.814-27.512 26.25297-50.0111 29.19398-57.42191 39.50902-76.45991 55.78305-102.96191 8.03497-13.084 9.79202-16.917 10.49498-22.8941.39199-3.3289.15103-5.2619-.943-7.564-2.68702-5.6559-8.75595-7.942-27.34401-10.2989-5.66996-.7191-13.71299-2.2851-17.87399-3.4791-9.99703-2.8689-22.15399-8.8389-25.86504-12.702-2.69697-2.806-2.83697-3.1799-1.47496-3.9459 12.07896-6.7901 43.73999-36.978 71.21204-67.899 50.11601-56.40801 88.41093-118.72001 116.41995-189.43401 13.96399-35.25401 17Line truncated
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120" fill="none">
<path d="M48 92h68c17 0 30-12 30-29 0-16-12-29-28-29h-4C107 18 93 8 75 8 50 8 31 27 31 52v3C18 58 10 68 10 81c0 7 3 11 8 11h12" stroke="#666" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 108 136 12" stroke="#666" stroke-width="13" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 377 B

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57 89">
<path fill="#666" d="m18.4003 8.5c-6.2 4.2-7.029 12.8268-1.7007 18.3999l2.2002 2.3013-3.7002 3.5991c-3.7898 3.6863-4.9641 6.3366-4.6991 10.8505.265 4.5138 4.0514 9.3178 11.7773 14.9176 11.8224 8.5692 12.5212 9.3018 12.6006 12.2007.0667 2.4333-1.8696 5.0283-3.7002 6.0996-4.0361 2.362-9.6607.1086-8.6001-4.8999.7395-3.4919-1.0001-6-4.1001-6-4.2 0-6.0102 4.6048-3.6006 9.0996 2.7904 5.2052 15.9001 6.8001 22.1001 2.6001s7.0308-12.8285 1.7007-18.3999l-2.2002-2.2998 3.7002-3.6006c3.4302-3.3378 4.6235-5.9098 4.8299-10.2809.2064-4.3713-3.6116-9.4751-11.9081-15.4872-11.8234-8.5678-12.5448-9.2998-12.6006-12.1992-.0471-2.4515 1.5375-4.8693 3.7002-6.0996 3.9056-2.2218 9.6607-.1086 8.6001 4.8999-.7395 3.4919 1.0001 6 4.1001 6 4.2 0 6.0066-4.6051 3.5991-9.1011-1.7425-3.254-7.523-5.0933-12.0011-5.0136-4.4781.0797-7.7725.8385-10.0975 2.4135zM21.1 31l6.3999 4.8003c3.4879 2.6162 7.2523 5.6546 8.2002 6.7998 2.5504 3.0815 2.7671 7.4482.3779 10.3682l-1.8003 2.2002-6.3999-4.8003c-3.4879-2.6162-7.2493-5.6571-8.2002-6.7998-2.9396-3.5328-2.5681-7.6916-.3779-10.3682z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 573.11 136.81">
<g transform="translate(0 -45.51)">
<path d="M40.05,168.37c-4.3,2.14-10,3.92-14.3,3.92-12.86,0-15.73-8.03-15.73-19.64v-51.8h29.48v-9.1H10.02v-26.42l-9.51,2.09c-.29.07-.51.34-.51.63v9.22h.07v49.8h-.07v25.6c0,18.4,6.61,28.95,25.19,28.95,6.08,0,12.33-1.8,18.22-4.65l-3.4-8.57.03-.02Z"/>
<path d="M94.52,89.96c-26.99,0-45.57,19.47-45.57,45.91s18.58,45.89,45.57,45.89,45.57-19.47,45.57-45.89-18.77-45.91-45.57-45.91ZM94.52,172.12c-20.91,0-35.38-15.72-35.38-36.25s14.47-36.27,35.38-36.27,35.38,15.72,35.38,36.27-14.66,36.25-35.38,36.25Z"/>
<path d="M197.39,89.96c-26.97,0-45.57,19.47-45.57,45.91s18.58,45.89,45.57,45.89,45.57-19.47,45.57-45.89-18.75-45.91-45.57-45.91ZM197.39,172.12c-20.91,0-35.38-15.72-35.38-36.25s14.47-36.27,35.38-36.27,35.4,15.72,35.4,36.27-14.66,36.25-35.4,36.25Z"/>
<path d="M259.33,57.7v122.28h10.19V54.95l-9.68,2.12c-.29.07-.49.34-.49.63h-.02Z"/>
<path d="M439.9,162.7c-8.85,0-13.3-5.4-13.3-15.03v-44.14h27.38v-18.3h-27.38l-.03-27.17-20.82,4.57c-.32.07-.54.36-.54.68v84.37c0,22.73,11.57,34.11,31.22,34.11,7.9,0,14.06-1.36,21.4-4.64l-5.98-17.35c-3.65,1.73-8.1,2.9-11.96,2.9h.02Z"/>
<path d="M516.47,82.49c-9.83,0-17.48,1.27-26.17,6.35-13.35,7.79-20.33,22.31-20.33,37.51v54.08h21.59v-46.44c0-18.69,9.83-30.07,25.25-30.07,5.79,0,10.6,1.55,16,5.21l9.63-17.54c-1.41-1.27-3.02-2.43-4.79-3.43-5.45-3.11-14.21-5.67-21.2-5.67h.02Z"/>
<path d="M552.06,50.13c-.32.07-.54.36-.54.68v129.6h21.59V45.51l-21.06,4.62h.02Z"/>
<path d="M303.47,132.12c0-29.44,20.4-49.65,50.63-49.65,13.86,0,25.8,4.8,35.99,14.62l-13.09,14.04c-5.78-5.77-14.05-9.05-22.52-9.05-17.32,0-29.45,12.31-29.45,30.02s11.94,30.02,29.45,30.02c9.24,0,17.71-3.46,23.49-9.81l13.28,14.43c-10.58,10.2-22.33,15.01-36.96,15.01-30.79,0-50.82-19.63-50.82-49.65v.02Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

-12
View File
@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 573.11 136.81">
<g transform="translate(0 -45.51)" fill="#fff">
<path d="M40.05,168.37c-4.3,2.14-10,3.92-14.3,3.92-12.86,0-15.73-8.03-15.73-19.64v-51.8h29.48v-9.1H10.02v-26.42l-9.51,2.09c-.29.07-.51.34-.51.63v9.22h.07v49.8h-.07v25.6c0,18.4,6.61,28.95,25.19,28.95,6.08,0,12.33-1.8,18.22-4.65l-3.4-8.57.03-.02Z"/>
<path d="M94.52,89.96c-26.99,0-45.57,19.47-45.57,45.91s18.58,45.89,45.57,45.89,45.57-19.47,45.57-45.89-18.77-45.91-45.57-45.91ZM94.52,172.12c-20.91,0-35.38-15.72-35.38-36.25s14.47-36.27,35.38-36.27,35.38,15.72,35.38,36.27-14.66,36.25-35.38,36.25Z"/>
<path d="M197.39,89.96c-26.97,0-45.57,19.47-45.57,45.91s18.58,45.89,45.57,45.89,45.57-19.47,45.57-45.89-18.75-45.91-45.57-45.91ZM197.39,172.12c-20.91,0-35.38-15.72-35.38-36.25s14.47-36.27,35.38-36.27,35.4,15.72,35.4,36.27-14.66,36.25-35.4,36.25Z"/>
<path d="M259.33,57.7v122.28h10.19V54.95l-9.68,2.12c-.29.07-.49.34-.49.63h-.02Z"/>
<path d="M439.9,162.7c-8.85,0-13.3-5.4-13.3-15.03v-44.14h27.38v-18.3h-27.38l-.03-27.17-20.82,4.57c-.32.07-.54.36-.54.68v84.37c0,22.73,11.57,34.11,31.22,34.11,7.9,0,14.06-1.36,21.4-4.64l-5.98-17.35c-3.65,1.73-8.1,2.9-11.96,2.9h.02Z"/>
<path d="M516.47,82.49c-9.83,0-17.48,1.27-26.17,6.35-13.35,7.79-20.33,22.31-20.33,37.51v54.08h21.59v-46.44c0-18.69,9.83-30.07,25.25-30.07,5.79,0,10.6,1.55,16,5.21l9.63-17.54c-1.41-1.27-3.02-2.43-4.79-3.43-5.45-3.11-14.21-5.67-21.2-5.67h.02Z"/>
<path d="M552.06,50.13c-.32.07-.54.36-.54.68v129.6h21.59V45.51l-21.06,4.62h.02Z"/>
<path d="M303.47,132.12c0-29.44,20.4-49.65,50.63-49.65,13.86,0,25.8,4.8,35.99,14.62l-13.09,14.04c-5.78-5.77-14.05-9.05-22.52-9.05-17.32,0-29.45,12.31-29.45,30.02s11.94,30.02,29.45,30.02c9.24,0,17.71-3.46,23.49-9.81l13.28,14.43c-10.58,10.2-22.33,15.01-36.96,15.01-30.79,0-50.82-19.63-50.82-49.65v.02Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

+20 -48
View File
@@ -3,42 +3,21 @@
<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="canonical" href="https://tool-ctrl.de/datenschutz" />
<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 />
<link rel="stylesheet" href="/styles.css?v=20260564" />
<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>
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="/" aria-label="ToolCTRL Startseite">
<img src="/assets/wordmark-black.svg" alt="ToolCTRL" class="brand-logo" />
<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="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid btn-sm" href="/#kontakt">Ablauf prüfen</a>
</nav>
<button class="nav-toggle" aria-label="Menü" aria-expanded="false">
<span></span><span></span>
</button>
</div>
<div class="mobile-nav" hidden>
<a href="/#leistungen">Leistungen</a>
<a href="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid" href="/#kontakt">Ablauf prüfen</a>
<a class="legal-back" href="/">Zurück zur Startseite</a>
</div>
</header>
@@ -57,18 +36,19 @@
</p>
<p>
R&amp;D² UG (haftungsbeschränkt)<br />
Orbinstr. 1<br />
Orbinstraße 1<br />
76646 Bruchsal<br />
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">Impressum</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:
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):
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>
@@ -175,24 +155,16 @@
</p>
</main>
<footer class="site-footer">
<div class="wrap footer-bar">
<div class="footer-brand">
<img src="/assets/wordmark-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>© <span id="year">2026</span> ToolCTRL · Bruchsal</p>
</div>
<div class="footer-links">
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<i></i>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
<i></i>
<a href="/impressum">Impressum</a>
·
<a href="/datenschutz">Datenschutz</a>
</div>
<footer class="legal-footer">
<div class="wrap legal-footer-inner">
<span>© 2026 ToolCTRL</span>
<span>
<a href="/">Startseite</a> ·
<a href="/impressum.html">Impressum</a> ·
<a href="/datenschutz.html">Datenschutz</a>
</span>
</div>
</footer>
<script src="/script.js?v=20260554"></script>
</body>
</html>
+89 -253
View File
@@ -3,43 +3,25 @@
<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">
<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: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 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">
<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>
<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">
<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;
@@ -127,18 +109,15 @@
.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; }
@@ -147,7 +126,6 @@
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;
@@ -175,10 +153,6 @@
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;
@@ -186,7 +160,6 @@
}
.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;
@@ -220,7 +193,6 @@
.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); }
@@ -301,159 +273,41 @@
.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; }
.field input:focus { outline: none; border-color: var(--ac); }
.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);
cursor: pointer; transition: all .15s; background: var(--bg2);
}
.dropzone:hover, .dropzone.drag { border-color: var(--ac); background: var(--panel); }
.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;
background: var(--ac); color: #ffffff; border: none;
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;
transition: opacity .15s;
}
.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; }
.btn:hover { opacity: 0.82; }
.btn:disabled { background: var(--line2); color: var(--muted); cursor: not-allowed; }
.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; }
.status.err { color: #c0392b; }
/* ===== 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);
background: var(--panel); border: 1px solid var(--line2); 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:hover { border-color: var(--ac); }
.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; }
@@ -485,22 +339,6 @@
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; }
@@ -514,17 +352,6 @@
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>
@@ -533,12 +360,12 @@
<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 href="/" aria-label="Zur ToolCTRL-Startseite"><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 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">
<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>
@@ -618,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">
@@ -633,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">
@@ -695,23 +510,40 @@
</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="/assets/wordmark-black.svg" alt="ToolCTRL">
<img src="/assets/mitup-logo.webp" alt="mITup Bruchsal">
<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 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>
@@ -761,13 +593,13 @@ function renderHud() {
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));
timerEl.textContent = 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, "//");
timerEl.textContent = "//";
timerLabelEl.textContent = "bis bald";
} else {
let idx = 0;
@@ -779,26 +611,14 @@ function renderHud() {
nameEl.textContent = cur.name;
subEl.textContent = cur.sub;
if (next) {
setTimerText(timerEl, fmt(phaseDate(next.t) - now));
timerEl.textContent = fmt(phaseDate(next.t) - now);
timerLabelEl.textContent = "bis: " + next.name;
} else { setTimerText(timerEl, ""); timerLabelEl.textContent = ""; }
} else { timerEl.textContent = ""; 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");
@@ -827,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");
@@ -862,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;
@@ -918,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
+17 -45
View File
@@ -3,42 +3,21 @@
<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="canonical" href="https://tool-ctrl.de/impressum" />
<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 />
<link rel="stylesheet" href="/styles.css?v=20260564" />
<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>
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="/" aria-label="ToolCTRL Startseite">
<img src="/assets/wordmark-black.svg" alt="ToolCTRL" class="brand-logo" />
<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="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid btn-sm" href="/#kontakt">Ablauf prüfen</a>
</nav>
<button class="nav-toggle" aria-label="Menü" aria-expanded="false">
<span></span><span></span>
</button>
</div>
<div class="mobile-nav" hidden>
<a href="/#leistungen">Leistungen</a>
<a href="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid" href="/#kontakt">Ablauf prüfen</a>
<a class="legal-back" href="/">Zurück zur Startseite</a>
</div>
</header>
@@ -49,7 +28,7 @@
<h2>Anbieter</h2>
<p>
R&amp;D² UG (haftungsbeschränkt)<br />
Orbinstr. 1<br />
Orbinstraße 1<br />
76646 Bruchsal<br />
Deutschland
</p>
@@ -61,6 +40,7 @@
<h2>Kontakt</h2>
<p>
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>
@@ -80,7 +60,7 @@
<h2>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
<p>
Raffael Sheikh<br />
Orbinstr. 1<br />
Orbinstraße 1<br />
76646 Bruchsal
</p>
@@ -110,24 +90,16 @@
</p>
</main>
<footer class="site-footer">
<div class="wrap footer-bar">
<div class="footer-brand">
<img src="/assets/wordmark-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>© <span id="year">2026</span> ToolCTRL · Bruchsal</p>
</div>
<div class="footer-links">
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<i></i>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
<i></i>
<a href="/impressum">Impressum</a>
·
<a href="/datenschutz">Datenschutz</a>
</div>
<footer class="legal-footer">
<div class="wrap legal-footer-inner">
<span>© 2026 ToolCTRL</span>
<span>
<a href="/">Startseite</a> ·
<a href="/impressum.html">Impressum</a> ·
<a href="/datenschutz.html">Datenschutz</a>
</span>
</div>
</footer>
<script src="/script.js?v=20260554"></script>
</body>
</html>
+248 -336
View File
@@ -3,39 +3,44 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ToolCTRL — EU-first Software für souveräne Betriebe</title>
<meta name="description" content="ToolCTRL baut schnelle, bezahlbare SaaS-Systeme für Mittelständler: EU-first gedacht, DSGVO-konform und ohne unnötige US-Cloud-Pflicht." />
<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:locale" content="de_DE" />
<meta property="og:site_name" content="ToolCTRL" />
<meta property="og:title" content="ToolCTRL — EU-first Software für souveräne Betriebe" />
<meta property="og:description" content="Schnelle SaaS-Systeme für Mittelständler, die keine US-Cloud-Pflicht und keine aufgeblasenen Agenturprojekte wollen." />
<meta property="og:locale" content="de_DE" />
<meta property="og:url" content="https://tool-ctrl.de/" />
<meta property="og:image" content="https://tool-ctrl.de/assets/logo-a.webp" />
<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="theme-color" content="#0A0A0A" />
<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 />
<link rel="stylesheet" href="/styles.css?v=20260564" />
<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",
"legalName": "R&D² UG (haftungsbeschränkt)",
"url": "https://tool-ctrl.de/",
"email": "info@tool-ctrl.de",
"address": {
"@type": "PostalAddress",
"streetAddress": "Orbinstr. 1",
"postalCode": "76646",
"addressLocality": "Bruchsal",
"addressCountry": "DE"
},
"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=20260610" />
</head>
<body>
@@ -43,29 +48,23 @@
<header class="site-header" id="top">
<div class="wrap header-inner">
<a class="brand" href="#top" aria-label="ToolCTRL Startseite">
<img src="/assets/wordmark-black.svg" alt="ToolCTRL" class="brand-logo" />
<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="#branchen">Branchen</a>
<a href="#vorgehen">Vorgehen</a>
<a href="#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid btn-sm" href="#kontakt">Ablauf prüfen</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>
</nav>
<button class="nav-toggle" aria-label="Menü" aria-expanded="false">
<span></span><span></span>
</button>
</div>
<div class="mobile-nav" hidden>
<a href="#leistungen">Leistungen</a>
<a href="#branchen">Branchen</a>
<a href="#vorgehen">Vorgehen</a>
<a href="#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid" href="#kontakt">Ablauf prüfen</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>
</div>
</header>
@@ -76,344 +75,197 @@
<div class="wrap hero-grid">
<div class="hero-copy">
<h1 class="hero-title">
<span class="no-break">EU-first</span> Software für souveräne Betriebe.
Moderne, durchdachte All-in-One-Software für Branchen, die zu
lange übersehen&nbsp;wurden.
</h1>
<p class="hero-lead">
Keine US-Cloud-Pflicht. Kein Agentur-Bullshit. ToolCTRL baut
individuelle SaaS-Systeme für Mittelständler, bei denen Anfragen,
Termine, Daten und Freigaben nicht länger in Tabellen, Postfächern
und halbfertigen Tools hängen bleiben.
Ganze Branchen arbeiten noch mit veralteten, zusammengestückelten
Insellösungen. Wir bei ToolCTRL bauen die Alternative: Software mit
echtem Fokus auf Bedienung und Gestaltung, die gut funktioniert,
klar aussieht und sich schnell bedienen lässt.
</p>
<div class="hero-actions">
<a class="btn btn-solid" href="#kontakt">Ablauf prüfen lassen</a>
<a class="btn btn-ghost" href="#leistungen">Leistungen ansehen</a>
</div>
<div class="trust-strip" aria-label="ToolCTRL Vertrauenssignale">
<span>Made in Germany</span>
<span>DSGVO-konform</span>
<span>Keine US-Cloud</span>
<a class="btn btn-solid" href="#kontakt">Kontakt aufnehmen</a>
</div>
</div>
<div class="hero-visual" aria-label="Interaktive Workflow-Control-Grafik">
<!-- Automatisierung trifft Kontrolle — generisch, kein reales Produkt -->
<div class="hero-visual" aria-hidden="true">
<div class="visual-frame">
<div class="visual-label" aria-hidden="true">
<span class="window-dots"><i></i><i></i><i></i></span>
</div>
<div class="control-board" data-control-board>
<svg class="control-lines" viewBox="0 0 540 400" preserveAspectRatio="none" aria-hidden="true">
<path data-line="inbox"></path>
<path data-line="docs"></path>
<path data-line="chat"></path>
<path data-line="crm"></path>
<path data-line="team"></path>
<path data-line="report"></path>
</svg>
<div class="logo-node" data-logo-node>
<img src="/assets/emblem-black.svg" alt="" draggable="false" />
</div>
<button class="flow-node is-input" type="button" data-node="inbox" style="--x: 6%; --y: 18%; --r: -2deg;">Anfrage</button>
<button class="flow-node is-input" type="button" data-node="docs" style="--x: 7%; --y: 56%; --r: 1deg;">Dokumente</button>
<button class="flow-node is-input" type="button" data-node="chat" style="--x: 18%; --y: 78%; --r: -1deg;">Chat</button>
<button class="flow-node is-output" type="button" data-node="crm" style="--x: 72%; --y: 16%; --r: 2deg;">CRM</button>
<button class="flow-node is-output" type="button" data-node="team" style="--x: 76%; --y: 56%; --r: -1deg;">Team</button>
<button class="flow-node is-output" type="button" data-node="report" style="--x: 63%; --y: 78%; --r: 1.5deg;">Report</button>
<div class="visual-label">
<span class="dot"></span> Workflow · ctrl
</div>
<svg class="node-graph" viewBox="0 0 540 400" role="presentation">
<defs>
<pattern id="grid" width="26" height="26" patternUnits="userSpaceOnUse">
<path d="M26 0H0V26" fill="none" stroke="#ECECEC" stroke-width="1" />
</pattern>
<linearGradient id="capface" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2A2A2A" />
<stop offset="1" stop-color="#101010" />
</linearGradient>
</defs>
<rect width="540" height="400" fill="url(#grid)" />
<!-- Leitungen -->
<g fill="none" stroke="#1A1A1A" stroke-width="1.6">
<path d="M116 78 C 165 78, 152 182, 200 182" />
<path d="M116 202 C 158 202, 168 202, 200 202" />
<path d="M116 326 C 165 326, 152 224, 200 224" />
<path d="M340 192 C 388 192, 378 118, 424 118" />
<path d="M340 214 C 388 214, 378 292, 424 292" />
</g>
<g fill="#1A1A1A">
<circle cx="116" cy="78" r="3" /><circle cx="116" cy="202" r="3" />
<circle cx="116" cy="326" r="3" /><circle cx="424" cy="118" r="3" />
<circle cx="424" cy="292" r="3" />
</g>
<!-- Eingangs-Knoten -->
<g class="gnode">
<rect x="24" y="56" width="92" height="44" />
<text x="70" y="83" text-anchor="middle">Daten</text>
</g>
<g class="gnode">
<rect x="24" y="180" width="92" height="44" />
<text x="70" y="207" text-anchor="middle">Auslöser</text>
</g>
<g class="gnode">
<rect x="24" y="304" width="92" height="44" />
<text x="70" y="331" text-anchor="middle">System</text>
</g>
<!-- Ausgangs-Knoten -->
<g class="gnode">
<rect x="424" y="96" width="92" height="44" />
<text x="470" y="123" text-anchor="middle">Aktion</text>
</g>
<g class="gnode">
<rect x="424" y="270" width="92" height="44" />
<text x="470" y="297" text-anchor="middle">Report</text>
</g>
<!-- TEST: echtes Logo (Emblem) in der Mitte -->
<image href="/assets/emblem-black.svg" x="190" y="128"
width="160" height="152" />
<g fill="#000000">
<circle cx="200" cy="182" r="3" /><circle cx="200" cy="202" r="3" />
<circle cx="200" cy="224" r="3" /><circle cx="340" cy="192" r="3" />
<circle cx="340" cy="214" r="3" />
</g>
</svg>
</div>
</div>
</div>
</section>
<!-- ============ KAMPF / WERTE ============ -->
<section class="section stance-section" id="kampf">
<div class="wrap">
<div class="section-head stance-head">
<span class="eyebrow eyebrow-light">Wofür wir kämpfen</span>
<h2 class="section-title">Keine Konzernleine. Keine Folienfabrik.</h2>
<p class="section-intro">
Wir bauen für Mittelständler, die Kontrolle über Daten, Kosten und
Abläufe zurückholen wollen. EU-first, pragmatisch und allergisch gegen
digitale Showprojekte.
</p>
</div>
<div class="stance-grid">
<article class="stance-card">
<span>01</span>
<h3>Digitale Souveränität</h3>
<p>
Keine automatische US-Cloud, kein blinder Plattformgehorsam. Wenn ein
System sinnvoll europäisch laufen kann, wird es so geplant.
</p>
</article>
<article class="stance-card stance-card-dark">
<span>02</span>
<h3>Agenturapparat aus</h3>
<p>
Keine Workshop-Show, keine Tagessatzmaschine, keine sechsstelligen
Projekte, die nach Monaten immer noch vor allem gut aussehen.
</p>
</article>
<article class="stance-card">
<span>03</span>
<h3>Ablauf vor Ego</h3>
<p>
Wir bauen keine Tools für Pitchdecks. Wir bauen Systeme, die im
Alltag führen, Zuständigkeiten klären und Arbeit sichtbar machen.
</p>
</article>
</div>
</div>
</section>
<!-- ============ PRODUKTWIRKUNG ============ -->
<section class="section product-section" id="produkt">
<div class="wrap product-grid">
<div class="product-copy">
<span class="eyebrow">Produktwirkung</span>
<h2 class="section-title">Nicht noch ein Login. Ein Arbeitsfluss, der führt.</h2>
<p class="section-intro">
Wir übersetzen gewachsene Prozesse in eine Oberfläche, die Teams
sofort verstehen: klare Zustände, weniger Rückfragen, automatische
Übergaben und ein Ort, an dem Arbeit sichtbar wird.
</p>
<div class="proof-points" aria-label="Typische Ergebnisse">
<div>
<strong>1 Oberfläche</strong>
<span>statt verstreuter Tabellen, Chats und Ordner</span>
</div>
<div>
<strong>0 Medienbrüche</strong>
<span>zwischen Anfrage, Bearbeitung und Auswertung</span>
</div>
<div>
<strong>Live-Status</strong>
<span>für Teams, Leitung und Kundenkommunikation</span>
</div>
<div class="visual-meta">
<span>Automatisiert</span>
<span>Kontrolliert</span>
<span>Wartbar</span>
</div>
</div>
<div class="product-ui" aria-label="Beispielhafte ToolCTRL Oberfläche">
<div class="ui-top">
<span class="ui-mark">toolctrl</span>
<span class="ui-status"><i></i> Auftrag #1842 · Live</span>
</div>
<div class="ui-layout">
<aside class="ui-sidebar">
<button class="ui-nav active" type="button" data-product-tab="eingang">Eingang</button>
<button class="ui-nav" type="button" data-product-tab="angebote">Angebote</button>
<button class="ui-nav" type="button" data-product-tab="planung">Planung</button>
<button class="ui-nav" type="button" data-product-tab="abschluss">Abschluss</button>
</aside>
<div class="ui-main">
<div class="ui-main-head">
<div>
<span class="ui-kicker" data-product-kicker>Heute · 14 aktive Vorgänge</span>
<strong data-product-title>Workflow Control</strong>
</div>
<span class="ui-pill" data-product-pill>3 Blocker</span>
</div>
<div class="ui-flow" data-product-flow>
<span class="done">Anfrage</span>
<span class="active">Prüfung</span>
<span>Angebot</span>
<span>Termin</span>
<span>Rechnung</span>
</div>
<div class="ui-metrics" data-product-metrics>
<div><span>Antwortzeit</span><strong>18 min</strong></div>
<div><span>Auto-Schritte</span><strong>74%</strong></div>
<div><span>Offene Übergaben</span><strong>03</strong></div>
</div>
<div class="ui-table" data-product-table>
<div class="ui-row is-hot"><span>Kunde wartet auf Rückmeldung</span><b>Jetzt</b></div>
<div class="ui-row"><span>Dokumente aus Postfach übernommen</span><b>Auto</b></div>
<div class="ui-row"><span>Team-Zuständigkeit gesetzt</span><b>11:15</b></div>
<div class="ui-row is-done"><span>Status an Leitung gemeldet</span><b>Erledigt</b></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ VORSPRUNG ============ -->
<section class="section velocity-section" id="vorsprung">
<div class="wrap velocity-grid">
<div class="velocity-copy">
<span class="eyebrow">Unser Vorsprung</span>
<h2 class="section-title">Schneller SaaS bauen. Ohne Baukasten-Vibe.</h2>
<p class="section-intro">
Wir starten nicht mit leerem Repository und endlosen Abstimmungen. Unsere
eigene technische Grundlage deckt Rollen, Datenmodelle, Workflows,
Dashboards, Schnittstellen und Betrieb ab. Dadurch wird Individualsoftware
schneller, ohne nach Standardtool zu riechen.
</p>
</div>
<div class="velocity-stack" aria-label="Warum ToolCTRL schneller liefert">
<article class="velocity-item">
<span>01</span>
<h3>Eigene SaaS-Basis</h3>
<p>Bewährte Infrastruktur statt leerem Repository. Das spart Zeit,
Kosten und Projektchaos.</p>
</article>
<article class="velocity-item">
<span>02</span>
<h3>Früh in den Betrieb</h3>
<p>Wir bauen schnell eine echte Version, testen sie am Ablauf und
verbessern dort, wo Arbeit wirklich passiert.</p>
</article>
<article class="velocity-item">
<span>03</span>
<h3>Budget ins Produkt</h3>
<p>Weniger Reibung, weniger Meeting-Schleifen, weniger Neuentwicklung.
Das Budget landet im System.</p>
</article>
<article class="velocity-item">
<span>04</span>
<h3>Individuell im Ergebnis</h3>
<p>Die Oberfläche und Logik werden auf Ihren Betrieb zugeschnitten.
Die Geschwindigkeit kommt aus unserer Technik.</p>
</article>
</div>
</div>
</section>
<!-- ============ LEISTUNGEN ============ -->
<section class="section services-section" id="leistungen">
<section class="section" id="leistungen">
<div class="wrap">
<div class="section-head">
<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>
<div class="cards">
<article class="card">
<span class="icon-key"><img src="/assets/icons/graph.svg" alt="" class="card-icon" /></span>
<h3>Automation</h3>
<p>Wiederkehrende Abläufe werden zu verlässlichen Workflows, die
ohne manuelles Zutun ausgelöst, verarbeitet und dokumentiert
werden.</p>
<h3>Prozessautomatisierung</h3>
<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; Ausbau</h3>
<p>Nach dem Launch ist nicht Schluss: Wartung, Anpassung und
Ausbau aus einer Hand.</p>
<h3>Betrieb &amp; Weiterentwicklung</h3>
<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>
</section>
<!-- ============ BRANCHEN ============ -->
<section class="section industry-section" id="branchen">
<div class="wrap">
<div class="section-head">
<span class="eyebrow">Branchen</span>
<h2 class="section-title">Nicht die Branche ist speziell. Der Ablauf ist es.</h2>
<p class="section-intro">
ToolCTRL ist interessant, wenn Ihr Ablauf speziell genug ist, dass
Baukastenlösungen ständig Workarounds erzwingen und niemand mehr
genau weiß, wo der aktuelle Stand liegt.
</p>
</div>
<div class="industry-map" aria-label="Typische Abläufe">
<div class="industry-row">
<span>01</span>
<strong>Handwerk &amp; Service</strong>
<p>Anfrage, Angebot, Termin, mobile Rückmeldung und Abschluss in einem kontrollierten Ablauf.</p>
<small>Anfrage → Angebot → Termin → Abschluss</small>
</div>
<div class="industry-row">
<span>02</span>
<strong>Events</strong>
<p>Anmeldung, Check-in und Kommunikation laufen sichtbar zusammen.</p>
<small>Ticket → Check-in → Info → Auswertung</small>
</div>
<div class="industry-row">
<span>03</span>
<strong>Vereine &amp; Verbände</strong>
<p>Mitglieder, Rollen, Beiträge und Projekte werden verwaltbar, auch wenn vieles nebenbei passiert.</p>
<small>Mitglied → Rolle → Beitrag → Projekt</small>
</div>
<div class="industry-row">
<span>04</span>
<strong>Lokale Services</strong>
<p>Leads, Termine und Angebote bleiben im Blick.</p>
<small>Lead → Beratung → Auftrag → Bindung</small>
</div>
</div>
</div>
</section>
<!-- ============ VORGEHEN ============ -->
<section class="section" id="vorgehen">
<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>
@@ -466,6 +318,47 @@
</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">
@@ -477,17 +370,16 @@
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.webp" alt="TechDay Bruchsal 2026 Motiv" width="683" height="384" loading="lazy" decoding="async" />
<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">TechDay ansehen</a>
<a href="/techday.html">TechDay ansehen</a>
</div>
</article>
<article class="event-card event-card-code">
@@ -500,7 +392,7 @@
<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">GameJam ansehen</a>
<a href="/gamejam.html">GameJam ansehen</a>
</div>
</article>
</div>
@@ -512,16 +404,11 @@
<div class="wrap kontakt-grid">
<div class="kontakt-copy">
<span class="eyebrow eyebrow-light">Kontakt</span>
<h2 class="section-title">Lassen Sie uns Ihren Ablauf prüfen.</h2>
<h2 class="section-title">Sprechen wir über Ihr Vorhaben.</h2>
<p class="section-intro">
Schicken Sie uns kurz, wo Arbeit aktuell hängen bleibt. Wir melden uns
mit einer ehrlichen Einschätzung, ob ein eigenes System sinnvoll ist.
Interesse? Fragen? Lassen Sie uns darüber reden. Füllen Sie das
Formular aus, wir melden uns in Kürze bei Ihnen.
</p>
<ul class="contact-promises">
<li>Antwort in der Regel innerhalb eines Werktags</li>
<li>Erstgespräch ohne Kosten und ohne Pitch-Deck</li>
<li>Klare Einschätzung, auch wenn wir nicht die Richtigen sind</li>
</ul>
<dl class="contact-list">
<div>
<dt>E-Mail</dt>
@@ -535,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">
@@ -549,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">
@@ -567,7 +466,7 @@
<button type="submit" class="btn btn-solid">Anfrage senden</button>
<p class="form-note">Mit dem Absenden stimmen Sie der Bearbeitung
Ihrer Angaben gemäß der
<a href="/datenschutz">Datenschutzerklärung</a> zu.</p>
<a href="/datenschutz.html">Datenschutzerklärung</a> zu.</p>
</div>
<p class="form-status" id="form-status" role="status" hidden></p>
</form>
@@ -578,23 +477,36 @@
<!-- ============ FOOTER ============ -->
<footer class="site-footer">
<div class="wrap footer-bar">
<div class="wrap footer-inner">
<div class="footer-brand">
<img src="/assets/wordmark-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>© <span id="year">2026</span> ToolCTRL · Bruchsal</p>
<img src="/assets/logo-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>Die Software, die eure Branche längst verdient hat.</p>
</div>
<div class="footer-links">
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<i></i>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
<i></i>
<a href="/impressum">Impressum</a>
·
<a href="/datenschutz">Datenschutz</a>
<div class="footer-cols">
<div class="footer-col">
<span class="footer-h">Navigation</span>
<a href="#leistungen">Leistungen</a>
<a href="#vorgehen">Vorgehen</a>
<a href="#arbeit">Organisation</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a href="#kontakt">Kontakt</a>
</div>
<div class="footer-col">
<span class="footer-h">Kontakt</span>
<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>
</div>
</div>
<div class="wrap footer-base">
<span>© <span id="year">2026</span> ToolCTRL</span>
<span><a href="/impressum.html">Impressum</a> · <a href="/datenschutz.html">Datenschutz</a></span>
</div>
</footer>
<script src="/script.js?v=20260554"></script>
<script src="/script.js?v=20260610"></script>
</body>
</html>
+1
View File
@@ -1,4 +1,5 @@
User-agent: *
Allow: /
Disallow: /gamejam-games/
Sitemap: https://tool-ctrl.de/sitemap.xml
+13 -309
View File
@@ -9,14 +9,6 @@
/* ---- Mobile-Navigation ---- */
var toggle = document.querySelector(".nav-toggle");
var mobileNav = document.querySelector(".mobile-nav");
var header = document.querySelector(".site-header");
function updateHeaderState() {
if (!header) return;
header.classList.toggle("is-scrolled", window.scrollY > 12);
}
updateHeaderState();
window.addEventListener("scroll", updateHeaderState, { passive: true });
if (toggle && mobileNav) {
toggle.addEventListener("click", function () {
var open = mobileNav.hasAttribute("hidden");
@@ -36,303 +28,6 @@
});
}
/* ---- Console-Easter-Eggs fuer neugierige Inspektoren ---- */
var inspectAscii = [
" _______ _______ _______ ___ _______ _______ _______ ",
"| || || || | | || || |",
"|_ _|| _ || _ || | | ||_ _|| ___|",
" | | | | | || | | || | | | | | | |___ ",
" | | | |_| || |_| || |___ | _| | | | ___|",
" | | | || || || |_ | | | |___ ",
" |___| |_______||_______||_______||_______| |___| |_______|"
].join("\n");
var inspectHints = [
"CTRL-KEY EVENT: Inspect gedrueckt. Bitte keine Produktivsysteme anfassen.",
"Hidden combo: window.toolctrl.press()",
"Status: schwarz/weiss. Farbe wurde zurueck in den Werkzeugkoffer gelegt."
];
var inspectLogged = false;
function logControlConsole(reason) {
if (reason === "devtools" && inspectLogged) return;
if (reason === "devtools") inspectLogged = true;
console.log(
"%c" + inspectAscii,
"font-family: monospace; font-weight: 900; color: #000; background: #fff; padding: 10px; border: 3px solid #000;"
);
console.log("%c" + inspectHints[Math.floor(Math.random() * inspectHints.length)],
"font-family: monospace; color: #fff; background: #000; padding: 6px 8px;");
}
window.toolctrl = window.toolctrl || {};
window.toolctrl.press = function () {
console.log("%c[ CTRL ] *klack* Ablauf uebernommen.", "font-family: monospace; font-weight: 900;");
return "pressed";
};
window.addEventListener("load", function () {
setTimeout(function () { logControlConsole("load"); }, 450);
});
setInterval(function () {
var open = Math.abs(window.outerWidth - window.innerWidth) > 160 ||
Math.abs(window.outerHeight - window.innerHeight) > 160;
document.documentElement.toggleAttribute("data-inspecting", open);
if (open) logControlConsole("devtools");
}, 1200);
/* ---- Dezente Scroll-Reveals ---- */
var reduceMotion = window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var revealEls = Array.prototype.slice.call(document.querySelectorAll([
".hero-copy > *",
".hero-visual",
".trust-strip span",
".stance-head > *",
".stance-card",
".product-copy > *",
".product-ui",
".velocity-copy > *",
".velocity-item",
".section-head",
".card",
".industry-card",
".step",
".arbeit-copy > *",
".kanban-wrap",
".event-poster",
".mini-terminal",
".event-card",
".kontakt-copy > *",
".contact-promises li",
".lead-form",
".td-hero .wrap > *",
".td-feature > *",
".td-talk",
".speaker-card",
".shot",
".gj-teaser"
].join(",")));
revealEls.forEach(function (el, index) {
el.classList.add("reveal", "reveal-delay-" + (index % 5));
});
if (reduceMotion || !("IntersectionObserver" in window)) {
revealEls.forEach(function (el) { el.classList.add("is-visible"); });
} else {
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
});
}, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
revealEls.forEach(function (el) { observer.observe(el); });
}
/* ---- Hero Workflow Nodes: ziehbare Kontrollpunkte ---- */
var board = document.querySelector("[data-control-board]");
if (board) {
var lineSvg = board.querySelector(".control-lines");
var logoNode = board.querySelector("[data-logo-node]");
var nodes = Array.prototype.slice.call(board.querySelectorAll("[data-node]"));
var draggableItems = nodes.concat(logoNode ? [logoNode] : []);
var activeDrag = null;
var lineFrame = 0;
if (logoNode) {
logoNode.addEventListener("dragstart", function (e) {
e.preventDefault();
});
}
function boardRect() {
return board.getBoundingClientRect();
}
function centerOf(el, b) {
var r = el.getBoundingClientRect();
return {
x: r.left - b.left + r.width / 2,
y: r.top - b.top + r.height / 2
};
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function updateLines() {
if (!lineSvg || !logoNode) return;
var b = boardRect();
var target = centerOf(logoNode, b);
lineSvg.setAttribute("viewBox", "0 0 " + b.width + " " + b.height);
nodes.forEach(function (node) {
var path = lineSvg.querySelector('[data-line="' + node.dataset.node + '"]');
if (!path) return;
var nodeCenter = centerOf(node, b);
var isOutput = node.classList.contains("is-output");
var start = isOutput ? target : nodeCenter;
var end = isOutput ? nodeCenter : target;
var bend = Math.max(44, Math.abs(end.x - start.x) * 0.38);
var c1x = start.x + (end.x > start.x ? bend : -bend);
var c2x = end.x + (end.x > start.x ? -bend : bend);
var d = "M " + start.x + " " + start.y +
" C " + c1x + " " + start.y +
", " + c2x + " " + end.y +
", " + end.x + " " + end.y;
path.classList.toggle("is-output", isOutput);
path.setAttribute("d", d);
});
lineFrame = 0;
}
function requestLineUpdate() {
if (lineFrame) return;
lineFrame = window.requestAnimationFrame(updateLines);
}
draggableItems.forEach(function (item) {
item.addEventListener("pointerdown", function (e) {
var r = item.getBoundingClientRect();
if (item === logoNode) item.classList.add("is-positioned");
activeDrag = {
node: item,
isLogo: item === logoNode,
offsetX: e.clientX - r.left,
offsetY: e.clientY - r.top
};
item.classList.add("is-dragging");
item.setPointerCapture(e.pointerId);
e.preventDefault();
});
item.addEventListener("pointermove", function (e) {
if (!activeDrag || activeDrag.node !== item) return;
var b = boardRect();
var x = clamp(e.clientX - b.left - activeDrag.offsetX, 8, b.width - item.offsetWidth - 8);
var y = clamp(e.clientY - b.top - activeDrag.offsetY, 8, b.height - item.offsetHeight - 8);
item.style.left = x + "px";
item.style.top = y + "px";
if (activeDrag.isLogo) {
item.classList.add("is-positioned");
} else {
item.style.removeProperty("--x");
item.style.removeProperty("--y");
}
requestLineUpdate();
});
item.addEventListener("pointerup", function (e) {
if (!activeDrag || activeDrag.node !== item) return;
item.classList.remove("is-dragging");
item.releasePointerCapture(e.pointerId);
activeDrag = null;
requestLineUpdate();
});
item.addEventListener("pointercancel", function () {
item.classList.remove("is-dragging");
activeDrag = null;
});
});
window.addEventListener("resize", requestLineUpdate, { passive: true });
updateLines();
setTimeout(requestLineUpdate, 150);
}
/* ---- Produktwirkung: klickbare Workflow-Zustaende ---- */
var productUi = document.querySelector(".product-ui");
if (productUi) {
var productStates = {
eingang: {
kicker: "Heute · 14 aktive Vorgänge",
title: "Workflow Control",
pill: "3 Blocker",
flow: ["Anfrage", "Prüfung", "Angebot", "Termin", "Rechnung"],
active: 1,
done: 0,
metrics: [["Antwortzeit", "18 min"], ["Auto-Schritte", "74%"], ["Offene Übergaben", "03"]],
rows: [["Kunde wartet auf Rückmeldung", "Jetzt", "is-hot"], ["Dokumente aus Postfach übernommen", "Auto", ""], ["Team-Zuständigkeit gesetzt", "11:15", ""], ["Status an Leitung gemeldet", "Erledigt", "is-done"]]
},
angebote: {
kicker: "Vertrieb · 8 Angebote offen",
title: "Angebote steuern",
pill: "2 fällig",
flow: ["Lead", "Kalkulation", "Freigabe", "Versand", "Follow-up"],
active: 2,
done: 1,
metrics: [["Durchlauf", "2,4 h"], ["Vorlagen", "91%"], ["Rückfragen", "05"]],
rows: [["Angebot braucht interne Freigabe", "Heute", "is-hot"], ["PDF aus Kundendaten erstellt", "Auto", ""], ["Nachfass-Termin gesetzt", "Morgen", ""], ["Alte Version archiviert", "Erledigt", "is-done"]]
},
planung: {
kicker: "Team · 21 Einsätze geplant",
title: "Planung sichtbar",
pill: "1 Konflikt",
flow: ["Auftrag", "Ressource", "Slot", "Route", "Update"],
active: 2,
done: 1,
metrics: [["Auslastung", "82%"], ["Auto-Routen", "68%"], ["Konflikte", "01"]],
rows: [["Termin überschneidet sich", "Prüfen", "is-hot"], ["Route für Außendienst gebaut", "Auto", ""], ["Materialliste angehängt", "09:40", ""], ["Kunde informiert", "Erledigt", "is-done"]]
},
abschluss: {
kicker: "Abschluss · 36 Vorgänge fertig",
title: "Betrieb im Blick",
pill: "0 kritisch",
flow: ["Abnahme", "Doku", "Rechnung", "Feedback", "Archiv"],
active: 3,
done: 2,
metrics: [["Abnahmen", "96%"], ["Auto-Doku", "88%"], ["Offen", "00"]],
rows: [["Feedback für nächste Version notiert", "Neu", ""], ["Rechnung vorbereitet", "Auto", ""], ["Abnahme protokolliert", "13:20", "is-done"], ["Projekt sauber archiviert", "Erledigt", "is-done"]]
}
};
var productTabs = Array.prototype.slice.call(productUi.querySelectorAll("[data-product-tab]"));
var productKicker = productUi.querySelector("[data-product-kicker]");
var productTitle = productUi.querySelector("[data-product-title]");
var productPill = productUi.querySelector("[data-product-pill]");
var productFlow = productUi.querySelector("[data-product-flow]");
var productMetrics = productUi.querySelector("[data-product-metrics]");
var productTable = productUi.querySelector("[data-product-table]");
function setProductState(key) {
var state = productStates[key];
if (!state) return;
productUi.classList.add("is-switching");
productTabs.forEach(function (tab) {
tab.classList.toggle("active", tab.dataset.productTab === key);
});
window.setTimeout(function () {
if (productKicker) productKicker.textContent = state.kicker;
if (productTitle) productTitle.textContent = state.title;
if (productPill) productPill.textContent = state.pill;
if (productFlow) {
productFlow.innerHTML = state.flow.map(function (label, index) {
var cls = index === state.active ? "active" : (index <= state.done ? "done" : "");
return '<span class="' + cls + '">' + label + '</span>';
}).join("");
}
if (productMetrics) {
productMetrics.innerHTML = state.metrics.map(function (metric) {
return "<div><span>" + metric[0] + "</span><strong>" + metric[1] + "</strong></div>";
}).join("");
}
if (productTable) {
productTable.innerHTML = state.rows.map(function (row) {
return '<div class="ui-row ' + row[2] + '"><span>' + row[0] + '</span><b>' + row[1] + '</b></div>';
}).join("");
}
productUi.classList.remove("is-switching");
}, 90);
}
productTabs.forEach(function (tab) {
tab.addEventListener("click", function () {
setProductState(tab.dataset.productTab);
});
});
}
/* ---- Lead-Formular ---- */
var form = document.getElementById("lead-form");
var status = document.getElementById("form-status");
@@ -351,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) {
@@ -373,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 …"; }
+8 -8
View File
@@ -2,17 +2,17 @@
<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</loc>
<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</loc>
</url>
<url>
<loc>https://tool-ctrl.de/impressum</loc>
</url>
<url>
<loc>https://tool-ctrl.de/datenschutz</loc>
<loc>https://tool-ctrl.de/gamejam.html</loc>
<lastmod>2026-06-10</lastmod>
<priority>0.6</priority>
</url>
</urlset>
+329 -1864
View File
File diff suppressed because it is too large. Load diff
+79 -41
View File
@@ -3,21 +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="canonical" href="https://tool-ctrl.de/techday" />
<link rel="canonical" href="https://tool-ctrl.de/techday.html" />
<!-- Open Graph -->
<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="ToolCTRL beim TechDay Bruchsal 2026" />
<meta property="og:description" content="Vortrag, AI GameJam und mITapp beim ersten Bruchsaler TechDay am 27. Mai 2026." />
<meta property="og:url" content="https://tool-ctrl.de/techday" />
<meta property="og:image" content="https://tool-ctrl.de/assets/techday-event.webp" />
<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" />
<meta name="theme-color" content="#0A0A0A" />
<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 />
<link rel="stylesheet" href="/styles.css?v=20260564" />
<!-- 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>
@@ -25,29 +56,23 @@
<header class="site-header" id="top">
<div class="wrap header-inner">
<a class="brand" href="/" aria-label="ToolCTRL Startseite">
<img src="/assets/wordmark-black.svg" alt="ToolCTRL" class="brand-logo" />
<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="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid btn-sm" href="/#kontakt">Ablauf prüfen</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>
</nav>
<button class="nav-toggle" aria-label="Menü" aria-expanded="false">
<span></span><span></span>
</button>
</div>
<div class="mobile-nav" hidden>
<a href="/#leistungen">Leistungen</a>
<a href="/#branchen">Branchen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday">TechDay</a>
<a href="/gamejam">GameJam</a>
<a class="btn btn-solid" href="/#kontakt">Ablauf prüfen</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>
</div>
</header>
@@ -90,7 +115,7 @@
mit der mITapp als digitalem Zuhause des Events.
</p>
</div>
<img class="td-feature-img" src="/assets/techday-event.webp" width="683" height="384" decoding="async" fetchpriority="high"
<img class="td-feature-img" src="/assets/techday-event.png"
alt="TechDay Bruchsal 2026: Human in the Loop" />
</div>
</div>
@@ -137,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.webp" alt="David Bertram" width="320" height="320" loading="lazy" decoding="async" />
<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>
@@ -205,7 +230,7 @@
Upload für die fertigen Spiele. Die GameJam hat ihre eigene Seite.
</p>
<div class="hero-actions" style="margin-top:28px;">
<a class="btn btn-solid" href="/gamejam">Zur GameJam-Seite</a>
<a class="btn btn-solid" href="/gamejam.html">Zur GameJam-Seite</a>
</div>
</div>
<div class="gj-teaser" aria-hidden="true">
@@ -254,23 +279,36 @@
<!-- ============ FOOTER ============ -->
<footer class="site-footer">
<div class="wrap footer-bar">
<div class="wrap footer-inner">
<div class="footer-brand">
<img src="/assets/wordmark-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>© <span id="year">2026</span> ToolCTRL · Bruchsal</p>
<img src="/assets/logo-white.svg" alt="ToolCTRL" class="footer-logo" />
<p>Die Software, die eure Branche längst verdient hat.</p>
</div>
<div class="footer-links">
<a href="mailto:info@tool-ctrl.de">info@tool-ctrl.de</a>
<i></i>
<a href="https://www.linkedin.com/company/toolctrl/" target="_blank" rel="noopener">LinkedIn</a>
<i></i>
<a href="/impressum">Impressum</a>
·
<a href="/datenschutz">Datenschutz</a>
<div class="footer-cols">
<div class="footer-col">
<span class="footer-h">Navigation</span>
<a href="/#leistungen">Leistungen</a>
<a href="/#vorgehen">Vorgehen</a>
<a href="/#arbeit">Organisation</a>
<a href="/techday.html">TechDay</a>
<a href="/gamejam.html">GameJam</a>
<a href="/#kontakt">Kontakt</a>
</div>
<div class="footer-col">
<span class="footer-h">Kontakt</span>
<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>
</div>
</div>
<div class="wrap footer-base">
<span>© <span id="year">2026</span> ToolCTRL</span>
<span><a href="/impressum.html">Impressum</a> · <a href="/datenschutz.html">Datenschutz</a></span>
</div>
</footer>
<script src="/script.js?v=20260554"></script>
<script src="/script.js?v=20260610"></script>
</body>
</html>
+175 -106
View File
@@ -16,36 +16,82 @@ const path = require("path");
const crypto = require("crypto");
const os = require("os");
const net = require("net");
const zlib = require("zlib");
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() {
@@ -69,45 +115,11 @@ const MIME = {
".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"
};
const CLEAN_ROUTES = {
"/techday": "/techday.html",
"/gamejam": "/gamejam.html",
"/impressum": "/impressum.html",
"/datenschutz": "/datenschutz.html"
};
const IMMUTABLE_EXTS = new Set([".webp", ".svg", ".woff2", ".png", ".jpg", ".jpeg", ".ico"]);
const COMPRESSIBLE_EXTS = new Set([".html", ".css", ".js", ".svg", ".json", ".txt"]);
function redirect(res, location) {
res.writeHead(308, { Location: location });
res.end();
}
function cacheControlFor(urlPath, ext, cacheKey) {
if (urlPath.startsWith("/gamejam-games/")) return "public, max-age=60";
if (ext === ".html") return "no-cache";
if (ext === ".css" || ext === ".js") {
return cacheKey ? "public, max-age=31536000, immutable" : "no-cache";
}
if (IMMUTABLE_EXTS.has(ext)) return "public, max-age=31536000, immutable";
return "no-cache";
}
function etagFor(stat) {
return 'W/"' + stat.size.toString(16) + "-" + Math.floor(stat.mtimeMs).toString(16) + '"';
}
function preferredEncoding(req) {
const accepted = req.headers["accept-encoding"] || "";
if (/\bbr\b/.test(accepted)) return "br";
if (/\bgzip\b/.test(accepted)) return "gzip";
return null;
}
/* ---------- Lead-Speicher vorbereiten ---------- */
function ensureLeadStore() {
if (!fs.existsSync(LEADS_DIR)) fs.mkdirSync(LEADS_DIR, { recursive: true });
@@ -126,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, " ") + '"';
@@ -150,34 +174,31 @@ function saveLead(lead) {
/* ---------- Statische Dateien ---------- */
function serveStatic(req, res) {
let parsedUrl;
try {
parsedUrl = new URL(req.url, "http://local");
} catch (e) {
res.writeHead(400).end("Bad Request");
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;
}
let urlPath = decodeURIComponent(parsedUrl.pathname);
if (urlPath === "/") urlPath = "/index.html";
if (urlPath.endsWith("/") && CLEAN_ROUTES[urlPath.slice(0, -1)]) {
redirect(res, urlPath.slice(0, -1) + parsedUrl.search);
return;
}
for (const cleanPath of Object.keys(CLEAN_ROUTES)) {
if (urlPath === CLEAN_ROUTES[cleanPath]) {
redirect(res, cleanPath + parsedUrl.search);
return;
}
}
if (CLEAN_ROUTES[urlPath]) urlPath = CLEAN_ROUTES[urlPath];
/* 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;
}
@@ -189,35 +210,20 @@ function serveStatic(req, res) {
return;
}
const ext = path.extname(filePath).toLowerCase();
const etag = etagFor(stat);
/* 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": cacheControlFor(urlPath, ext, parsedUrl.search),
"ETag": etag,
"Last-Modified": stat.mtime.toUTCString(),
"X-Content-Type-Options": "nosniff"
"Cache-Control": cacheControl
};
if (COMPRESSIBLE_EXTS.has(ext)) headers["Vary"] = "Accept-Encoding";
if ((req.headers["if-none-match"] || "").includes(etag)) {
res.writeHead(304, headers);
res.end();
return;
/* Hochgeladene GameJam-Spiele nicht indexieren (Fremdcontent) */
if (urlPath.startsWith("/gamejam-games/")) {
headers["X-Robots-Tag"] = "noindex";
}
let stream = fs.createReadStream(filePath);
const encoding = COMPRESSIBLE_EXTS.has(ext) ? preferredEncoding(req) : null;
if (encoding === "br") {
headers["Content-Encoding"] = "br";
stream = stream.pipe(zlib.createBrotliCompress());
} else if (encoding === "gzip") {
headers["Content-Encoding"] = "gzip";
stream = stream.pipe(zlib.createGzip({ level: 6 }));
}
res.writeHead(200, headers);
stream.pipe(res);
fs.createReadStream(filePath).pipe(res);
});
}
@@ -272,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 });
});
});
}
@@ -393,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) {
@@ -523,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." });
@@ -586,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