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