From 39407585f2682380f650c1cde0a72a72b7daaaba Mon Sep 17 00:00:00 2001 From: Raffael Sheikh Date: Sun, 24 May 2026 00:07:24 +0200 Subject: [PATCH] Add subtle interface polish and clean URLs --- public/datenschutz.html | 6 +- public/gamejam.html | 71 ++++++++++++++--- public/impressum.html | 4 +- public/index.html | 28 ++++--- public/script.js | 46 +++++++++++ public/styles.css | 171 +++++++++++++++++++++++++++++++++++++++- public/techday.html | 16 ++-- server.js | 36 ++++++++- 8 files changed, 338 insertions(+), 40 deletions(-) diff --git a/public/datenschutz.html b/public/datenschutz.html index b7b8701..a9b4065 100644 --- a/public/datenschutz.html +++ b/public/datenschutz.html @@ -39,7 +39,7 @@ 76646 Bruchsal
E-Mail: info@tool-ctrl.de

-

Die vollständigen Anbieterangaben finden Sie im Impressum.

+

Die vollständigen Anbieterangaben finden Sie im Impressum.

2. Hosting und Server-Logfiles

@@ -158,8 +158,8 @@ © 2026 ToolCTRL Startseite · - Impressum · - Datenschutz + Impressum · + Datenschutz diff --git a/public/gamejam.html b/public/gamejam.html index 8157b2b..92fbb36 100644 --- a/public/gamejam.html +++ b/public/gamejam.html @@ -115,15 +115,18 @@ .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; } @@ -132,6 +135,7 @@ 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; @@ -159,6 +163,10 @@ 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; @@ -166,6 +174,7 @@ } .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; @@ -199,6 +208,7 @@ .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); } @@ -279,13 +289,20 @@ .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); } + .field input:focus { outline: none; border-color: var(--ac); background: #fff; } .dropzone { border: 1.5px dashed var(--line2); padding: 40px 24px; text-align: center; - cursor: pointer; transition: all .15s; background: var(--bg2); + cursor: pointer; + transition: border-color .15s ease, background .15s ease, transform .15s ease; + background: var(--bg2); + } + .dropzone:hover, .dropzone.drag { + border-color: var(--ac); + background: var(--panel); + transform: translateY(-2px); } - .dropzone: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; } @@ -294,9 +311,10 @@ 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; - transition: opacity .15s; + transition: opacity .15s ease, transform .12s ease; } - .btn:hover { opacity: 0.82; } + .btn:hover { opacity: 0.82; transform: translateY(-1px); } + .btn:active { transform: scale(.96); } .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; @@ -310,8 +328,9 @@ 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; + transition: border-color .15s ease, transform .12s ease; } - .btn-ghost:hover { border-color: var(--ac); } + .btn-ghost:hover { border-color: var(--ac); transform: translateY(-1px); } .btn-ghost svg.ic { width: 13px; height: 13px; } .games { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 6px; } @@ -346,6 +365,15 @@ letter-spacing: 0.5px; } + @keyframes heroRise { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } + } + @keyframes timerTick { + from { opacity: .78; } + to { opacity: 1; } + } + @media (max-width: 820px) { .steps { grid-template-columns: 1fr; } .step + .step { padding-left: 0; border-left: none; border-top: 1px solid var(--line); } @@ -358,6 +386,17 @@ 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; + } + } @@ -585,13 +624,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"; - timerEl.textContent = fmt(start - now); + setTimerText(timerEl, fmt(start - now)); timerLabelEl.textContent = "bis Start"; } else if (now >= end) { nowLabelEl.innerHTML = DOT + "Vorbei"; nameEl.textContent = "Event beendet"; subEl.textContent = "Danke an alle — die Games gibt's unten zum Spielen"; - timerEl.textContent = "//"; + setTimerText(timerEl, "//"); timerLabelEl.textContent = "bis bald"; } else { let idx = 0; @@ -603,14 +642,26 @@ function renderHud() { nameEl.textContent = cur.name; subEl.textContent = cur.sub; if (next) { - timerEl.textContent = fmt(phaseDate(next.t) - now); + setTimerText(timerEl, fmt(phaseDate(next.t) - now)); timerLabelEl.textContent = "bis: " + next.name; - } else { timerEl.textContent = ""; timerLabelEl.textContent = ""; } + } else { setTimerText(timerEl, ""); timerLabelEl.textContent = ""; } } renderSchedule(now); } const CHECK = ''; +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"); diff --git a/public/impressum.html b/public/impressum.html index 84ea71c..5e00ffa 100644 --- a/public/impressum.html +++ b/public/impressum.html @@ -93,8 +93,8 @@ © 2026 ToolCTRL Startseite · - Impressum · - Datenschutz + Impressum · + Datenschutz diff --git a/public/index.html b/public/index.html index fe38668..b42dd89 100644 --- a/public/index.html +++ b/public/index.html @@ -20,8 +20,8 @@ Leistungen Vorgehen Organisation - TechDay - GameJam + TechDay + GameJam Kontakt aufnehmen

Mit dem Absenden stimmen Sie der Bearbeitung Ihrer Angaben gemäß der - Datenschutzerklärung zu.

+ Datenschutzerklärung zu.

@@ -356,8 +360,8 @@ Leistungen Vorgehen Organisation - TechDay - GameJam + TechDay + GameJam Kontakt diff --git a/public/script.js b/public/script.js index 41e0134..8b28910 100644 --- a/public/script.js +++ b/public/script.js @@ -9,6 +9,14 @@ /* ---- 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"); @@ -28,6 +36,44 @@ }); } + /* ---- 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", + ".section-head", + ".card", + ".step", + ".arbeit-copy > *", + ".kanban-wrap", + ".kontakt-copy > *", + ".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); }); + } + /* ---- Lead-Formular ---- */ var form = document.getElementById("lead-form"); var status = document.getElementById("form-status"); diff --git a/public/styles.css b/public/styles.css index 5471499..7225bdb 100644 --- a/public/styles.css +++ b/public/styles.css @@ -119,9 +119,26 @@ a { color: inherit; text-decoration: none; } position: relative; cursor: pointer; white-space: nowrap; + overflow: hidden; transition: transform .09s ease, box-shadow .09s ease, background .15s ease, color .15s ease; } +.btn::before { + content: ""; + position: absolute; + inset: 1px 1px auto; + height: 42%; + border-radius: 5px 5px 3px 3px; + background: linear-gradient(180deg, rgba(255,255,255,.22), rgba(255,255,255,0)); + opacity: .28; + pointer-events: none; + transition: opacity .15s ease, transform .15s ease; +} +.btn:hover::before { + opacity: .42; + transform: translateY(-1px); +} +.btn:hover { transform: translateY(-1px); } /* Tastendruck — Kappe fährt auf die Grundfläche herunter */ .btn:active { transform: translateY(8px); } @@ -215,13 +232,21 @@ a { color: inherit; text-decoration: none; } background: rgba(255,255,255,.94); backdrop-filter: blur(8px); border-bottom: 1px solid var(--line); + transition: background .18s ease, box-shadow .18s ease, border-color .18s ease; +} +.site-header.is-scrolled { + background: rgba(255,255,255,.98); + border-bottom-color: rgba(10,10,10,.14); + box-shadow: 0 8px 28px -24px rgba(0,0,0,.55); } .header-inner { display: flex; align-items: center; justify-content: space-between; height: 74px; + transition: height .18s ease; } +.site-header.is-scrolled .header-inner { height: 66px; } .brand-logo { height: 26px; width: auto; } .nav { display: flex; @@ -245,11 +270,20 @@ a { color: inherit; text-decoration: none; } border: 1.5px solid var(--black); padding: 11px 10px; cursor: pointer; + transition: background .15s ease; } .nav-toggle span { width: 20px; height: 1.5px; background: var(--black); display: block; + transform-origin: center; + transition: transform .18s cubic-bezier(.2, 0, 0, 1), opacity .18s ease; +} +.nav-toggle[aria-expanded="true"] span:first-child { + transform: translateY(3.25px) rotate(45deg); +} +.nav-toggle[aria-expanded="true"] span:last-child { + transform: translateY(-3.25px) rotate(-45deg); } .mobile-nav { /* Standardmäßig nie sichtbar — wird nur im Mobil-Breakpoint @@ -260,6 +294,7 @@ a { color: inherit; text-decoration: none; } padding: 16px 32px 24px; border-bottom: 1px solid var(--line); background: var(--paper); + transform-origin: top center; } .mobile-nav a:not(.btn) { padding: 12px 0; @@ -334,6 +369,42 @@ a { color: inherit; text-decoration: none; } } .node-graph { display: block; width: 100%; height: auto; } +.flow-lines path { + stroke-dasharray: 260; + stroke-dashoffset: 260; + animation: graphLineIn .6s cubic-bezier(.2, 0, 0, 1) forwards; +} +.flow-lines path:nth-child(2) { animation-delay: .04s; } +.flow-lines path:nth-child(3) { animation-delay: .08s; } +.flow-lines path:nth-child(4) { animation-delay: .12s; } +.flow-lines path:nth-child(5) { animation-delay: .16s; } +.flow-ports circle, +.gnode, +.graph-emblem { + opacity: 0; + transform-box: fill-box; + transform-origin: center; + animation: graphNodeIn .28s cubic-bezier(.2, 0, 0, 1) forwards; +} +.gnode:nth-of-type(1) { animation-delay: .08s; } +.gnode:nth-of-type(2) { animation-delay: .11s; } +.gnode:nth-of-type(3) { animation-delay: .14s; } +.gnode:nth-of-type(4) { animation-delay: .20s; } +.gnode:nth-of-type(5) { animation-delay: .23s; } +.flow-ports circle { animation-delay: .26s; } +.flow-ports-center circle { animation-delay: .30s; } +.graph-emblem { + animation-name: graphEmblemIn; + animation-delay: .18s; + animation-duration: .36s; + animation-timing-function: cubic-bezier(.2, 0, 0, 1); + animation-fill-mode: forwards; +} +.flow-signal { + fill: var(--black); + opacity: .72; +} + .gnode rect { fill: var(--paper); stroke: var(--black); @@ -399,9 +470,30 @@ a { color: inherit; text-decoration: none; } border-bottom: 1px solid var(--line-strong); padding: 36px 32px 40px; background: var(--paper); - transition: background .15s ease; + position: relative; + transition: background .15s ease, box-shadow .15s ease; +} +.card::before { + content: ""; + position: absolute; + top: -1px; + left: -1px; + right: -1px; + height: 2px; + background: var(--black); + opacity: 0; + transform: scaleX(.65); + transform-origin: left center; + transition: opacity .15s ease, transform .15s ease; +} +.card:hover { + background: var(--paper-2); + box-shadow: inset 0 0 0 999px rgba(0,0,0,.012); +} +.card:hover::before { + opacity: 1; + transform: scaleX(1); } -.card:hover { background: var(--paper-2); } /* Icon-Keycap — gewölbte Taste mit erhöhter Oberfläche (3D) */ .icon-key { position: relative; @@ -413,6 +505,11 @@ a { color: inherit; text-decoration: none; } /* Tastenkörper / Seitenwände */ background: linear-gradient(180deg, #DADADA 0%, #C0C0C0 60%, #B0B0B0 100%); box-shadow: 0 9px 13px -5px rgba(0,0,0,.28); + transition: transform .16s cubic-bezier(.2, 0, 0, 1), box-shadow .16s ease; +} +.card:hover .icon-key { + transform: translateY(-3px); + box-shadow: 0 13px 16px -7px rgba(0,0,0,.32); } /* erhöhte, leicht gewölbte Tastenoberfläche */ .icon-key::before { @@ -640,12 +737,15 @@ a { color: inherit; text-decoration: none; } border: 1.5px solid var(--line); border-radius: 7px; padding: 12px 14px; - transition: border-color .15s ease; + 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; } .form-row.invalid input, @@ -677,6 +777,7 @@ a { color: inherit; text-decoration: none; } padding: 12px 14px; border: 1.5px solid var(--black); border-radius: 7px; + animation: statusIn .22s cubic-bezier(.2, 0, 0, 1); } .form-status.ok { background: var(--black); color: var(--paper); } .form-status.err { border-color: #C0392B; color: #C0392B; } @@ -738,7 +839,10 @@ a { color: inherit; text-decoration: none; } .nav { display: none; } .nav-toggle { display: flex; } /* Mobil-Menü nur sichtbar, wenn JS das hidden-Attribut entfernt */ - .mobile-nav:not([hidden]) { display: flex; } + .mobile-nav:not([hidden]) { + display: flex; + animation: mobileMenuIn .18s cubic-bezier(.2, 0, 0, 1); + } .hero-grid { grid-template-columns: 1fr; gap: 48px; padding: 140px 32px 72px; } .cards { grid-template-columns: 1fr 1fr; } .steps { grid-template-columns: 1fr 1fr; } @@ -768,8 +872,58 @@ a { color: inherit; text-decoration: none; } .footer-base { flex-direction: column; gap: 8px; } } +.reveal { + opacity: 0; + transform: translateY(8px); + transition: opacity .24s cubic-bezier(.2, 0, 0, 1), + transform .24s cubic-bezier(.2, 0, 0, 1); +} +.reveal.is-visible { + opacity: 1; + transform: translateY(0); +} +.reveal.reveal-delay-1 { transition-delay: .08s; } +.reveal.reveal-delay-2 { transition-delay: .10s; } +.reveal.reveal-delay-3 { transition-delay: .12s; } +.reveal.reveal-delay-4 { transition-delay: .14s; } + +@keyframes graphLineIn { + to { stroke-dashoffset: 0; } +} +@keyframes graphNodeIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0) scale(1); } +} +@keyframes graphEmblemIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0) scale(1) rotate(0deg); } +} +@keyframes mobileMenuIn { + from { opacity: 0; transform: translateY(-8px) scaleY(.98); } + to { opacity: 1; transform: translateY(0) scaleY(1); } +} +@keyframes statusIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} + @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } + *, + *::before, + *::after { + animation-duration: .001ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: .001ms !important; + } + .reveal { + opacity: 1; + transform: none; + } + .flow-signal { + display: none; + } } /* ============================================================ @@ -804,6 +958,15 @@ a { color: inherit; text-decoration: none; } display: block; border: 1.5px solid var(--black); } +.td-feature-img.reveal, +.shot.reveal img { + clip-path: inset(0 0 12% 0); + transition-property: opacity, transform, clip-path; +} +.td-feature-img.reveal.is-visible, +.shot.reveal.is-visible img { + clip-path: inset(0); +} /* GameJam-Teaser — Hero-Element der GameJam-Seite, als HTML nachgebaut */ .gj-teaser { diff --git a/public/techday.html b/public/techday.html index e2affeb..75c174f 100644 --- a/public/techday.html +++ b/public/techday.html @@ -20,8 +20,8 @@ Leistungen Vorgehen Organisation - TechDay - GameJam + TechDay + GameJam Kontakt aufnehmen