| .claude | ||
| assets | ||
| docs | ||
| scripts | ||
| 404.html | ||
| bewerbung.html | ||
| datenschutz.html | ||
| DEV-NOTES.md | ||
| impressum.html | ||
| index.html | ||
| karriere-ausbildung.html | ||
| karriere-kuechenmonteur.html | ||
| karriere-moebelmonteur.html | ||
| karriere-subunternehmer.html | ||
| karriere.html | ||
| kontakt.html | ||
| nachrichten.html | ||
| README.md | ||
| standort-hamburg.html | ||
| ueber-uns.html | ||
| unsere-mission.html | ||
Translogistik — Website-Build aus zwei Konzepten
Eine ganz normale statische Website: kein Build-Prozess, kein npm, kein Framework.
Wird per Doppelklick auf index.html oder über einen beliebigen Static-Server geöffnet.
1. Struktur
translogistik/
├── index.html ← Startseite, Markup ist per Kommentar in Blöcke gegliedert
├── ueber-uns.html ← Seite «Über uns» (siehe Punkt 7)
├── assets/
│ ├── css/
│ │ ├── tokens.css ← EINZIGE Quelle für Farbe/Theme/Geometrie
│ │ ├── base.css ← Reset, Typografie, Sektionsrhythmus, Utilities
│ │ ├── components.css ← .btn / .card / accordion / combobox / pill
│ │ ├── layout.css ← Header, Mobilmenü, Footer
│ │ ├── sections.css ← hero, prinzip, standorte, wrench, leistungen,
│ │ │ gruppe, team, faq, cta
│ │ └── about.css ← NUR «Über uns»: geschichte/timeline + werte
│ ├── img/
│ │ ├── wrench.png ← Schraubenschlüssel für die Scroll-Szene (war inline base64)
│ │ ├── *.jpg ← ARCHIV der Fotoshootings: 11 Bilder, 69 MB. Ist an
│ │ │ die Website nicht angebunden, liegt nur als Quelle
│ │ ├── photos/ ← Web-Set: 11 × 4 Breiten, 4 MB (siehe Punkt 8)
│ │ └── svg/
│ │ ├── Hamburg.svg ← Skyline-Masken für die Standort-Buttons (8 Stück,
│ │ ├── … Langenselbold fehlt noch)
│ │ └── _source/ ← Originale vor der Optimierung (18 MB)
│ └── js/
│ ├── theme.js ← Theme: system / light / dark
│ ├── accordion.js ← universelles Akkordeon auf data-Attributen
│ │ (damit läuft auch das FAQ auf der Startseite)
│ ├── form-modal.js ← «Gesendet»-Modal für kontakt/bewerbung
│ ├── locations.js ← Karte + Liste + Suche + Ansicht zurücksetzen
│ ├── wrench.js ← Scroll-Kinematik mit dem Schraubenschlüssel
│ ├── timeline.js ← «Unsere Geschichte»: Sticky-Jahre + Meilensteine
│ ├── gruppe.js ← «Krieger Gruppe»: Filter + Marken-Karussell
│ └── main.js ← Bootstrap, Mobilmenü, Scroll-Spy, Reveal
├── scripts/
│ ├── optimize-skyline.ps1 ← Vektorisierter SVG-Export → leichte Maske
│ ├── optimize-photos.ps1 ← Kamerafotos → Web-Set (siehe Punkt 8)
│ └── serve.js ← statischer Server ohne Abhängigkeiten
├── import/ ← ursprüngliche Konzepte (nicht an die Website angebunden)
└── README.md
Reihenfolge der Sektionen in index.html:
hero → logo-bar → prinzip → standorte → wrench-scene → leistungen
→ gruppe → team → faq → cta → footer
Reihenfolge der Sektionen in ueber-uns.html:
hero (Über uns) → logo-bar → geschichte → werte → team → footer
Jede Seite bindet nur ein, was sie wirklich braucht. «Über uns» lädt kein
Leaflet, kein locations.js, wrench.js, faq.js oder accordion.js — dort
gibt es weder eine Karte noch die Szene mit dem Schraubenschlüssel noch ein
Akkordeon. Das Bootstrap in main.js ruft ein Modul nur auf, wenn es
definiert ist (if (TL.timeline) …), deshalb ist die Liste der <script>-Tags
auf einer Seite zugleich ihre Konfiguration.
Externe Abhängigkeiten gibt es nur über CDN: Google Fonts (Inter + Instrument Serif) und Leaflet 1.9.4 (Karte).
2. Was woher stammt
| Block auf der Website | Quelle | Was geändert wurde |
|---|---|---|
| Header (sticky, TL-Logo, Nav, Theme-Umschalter, CTA) | Translogistik-Codex-V2-Mit.html |
Tailwind-Klassen → normales CSS; funktionierendes Mobilmenü und Scroll-Spy ergänzt |
| Footer (3 Spalten + untere Zeile) | Translogistik-Codex-V2-Mit.html |
Jahr wird per Skript eingesetzt; Links sind mit den Sektionen verknüpft |
| Hero (Titel Inter + Instrument Serif italic, Lead, 2 Buttons, 3 Zahlen, roter Gradient, Badge «Trusted by Küchenhäuser») | Translogistik-Codex-V2-Mit.html |
3 Fotos rechts durch EIN einziges (.hero__photo) ersetzt; auf der Startseite zusätzlich .hero--bg — siehe unten |
| Partnerleiste (Nobilia, Häcker, Nolte …) | Translogistik-Codex-V2-Mit.html |
Tippfehler Balller zu Ballerina korrigiert |
| «Worauf du dich bei uns verlassen kannst.» — 7 Prinzipien | Translogistik-Codex-V2-Mit.html |
Akkordeon auf semantisches HTML + ARIA umgebaut (aria-expanded, Pfeile, Home/End) |
| «Wir übernehmen den Rest – damit du dich aufs Verkaufen konzentrierst.» — 3 Karten + 2 Plaketten | Translogistik-Codex-V2-Mit.html |
Inhaltlich unverändert |
| Standorte: Filialkarte + Liste | translogistik-template.html (Leaflet, echte Koordinaten) |
Komplett neue Mechanik — siehe Punkt 3 |
| «Kurze Wege / 150 km Radius» | Translogistik-Codex-V2-Mit.html |
Unter das Kartenraster verschoben, damit die gleiche Spaltenhöhe nicht bricht |
| Team & Vertrauen: Fotocollage-«Bogen» aus 9 Bildern + 3 Bewertungen | translogistik-template.html |
Überschrift ersetzt durch «Bereit, mit uns zu wachsen? / Hier siehst du unser Team…» (wie gewünscht). Buttons aus dem alten Block grow-cta sind unter die Bewertungen gewandert |
| Scroll-Szene «VON DER LIEFERUNG BIS ZUR LETZTEN SCHRAUBE» + Raster 01–04 | translogistik-wrench-scroll.html |
PNG des Schlüssels aus Inline-base64 nach assets/img/wrench.png ausgelagert; das rote #c43c2e durch das Marken---accent ersetzt; Demo-Platzhalter «Scroll» / «Zurück» entfernt; Pre-Roll ergänzt (siehe Punkt 4) |
| FAQ: rote Karte, 4 Kacheln tauschen den Text rechts aus | Translogistik-Faq-Brighter-Style.html |
Umschalt-Kacheln durch ein normales Akkordeon ersetzt — siehe Punkt 3c. Antworten stehen im Markup (SEO + Funktion ohne JS); die internen Labels «BLOCK» und «FAQ Modul • Brighter Style» entfernt; «über 20 Jahren Erfahrung» → «seit 2014» (siehe unten) |
| Abschließendes CTA (Karte «Karriere» mit Foto eines Lkw) | Translogistik-Codex-V2-Mit.html |
Überschrift geändert zu «Wir suchen Monteure, Fahrer und Dispo-Talente.», weil «Bereit, mit uns zu wachsen?» jetzt im Team-Block steht |
| Mini-Block «Krieger Gruppe»: Filter + Marken-Karussell | krieger-gruppe-filter-karussell.html |
Auf Design-Tokens umgestellt, Marken ins Markup verlagert, Pillen wurden zu einem echten Filter nach Bereichen — siehe Punkt 7 |
Texte, Zahlen (98,2 %, 450+, 1200+, 9 Standorte, 150 km) und Standortdaten wurden unverändert aus den Konzepten übernommen — nichts wurde dazuerfunden.
Ein Widerspruch in den Quellen: Das FAQ-Konzept schrieb «mit über 20 Jahren Erfahrung», während der Rest der Website «seit 2014» / «Gründung 2014» sagt. Beibehalten wurde «seit 2014» — sonst stünden auf einer Seite zwei sich widersprechende Aussagen. Falls 20 Jahre stimmen, müssen auch die anderen Blöcke angepasst werden.
Entfernte «Hinweistexte»
Auf Wunsch wurden Mikro-Labels entfernt, die vom Inhalt ablenkten:
- Kicker-Labels über Überschriften:
Unsere Leistungen,Unsere Standorte,Das Translogistik-Prinzip,Karriere; - Scroll-Hinweise:
Scroll to explore/Deutschland Fokus,Scroll für Deutschland(Plakette auf der Karte),Öffne die Prinzipien →; - interne Labels des FAQ-Konzepts:
BLOCK,FAQ Modul • Brighter Style; - Demo-Platzhalter der Schlüssel-Szene:
Scroll,Zurück.
Sinnvolle Labels blieben erhalten, das waren keine reinen Hinweistexte:
Team & Vertrauen, Aktuell, Kurze Wege, Alle Hubs, die Statuszeile der
Karte und die Tags auf den Leistungskarten.
In einem zweiten Durchgang auf der Startseite entfernt:
FAQ / Häufige Fragenüber der FAQ-Überschrift sowie die Fußzeile der SektionTranslogistik • 9 Standorte • 450+ Monteure • Bundesweit im Einsatz;- «Mehr Details →» in den drei Leistungskarten (
.service__foot) — dahinter stand kein Link. Das CSS.service__foot*blieb erhalten: es wird vonunsere-mission.htmlgenutzt, wo diese Karten tatsächlich zu Links wurden (siehe Punkt 6b).
In einem dritten Durchgang — die Reste der FAQ-Kachelmechanik: der Hinweis
Wähle 01 / 02 / 03 / 04, die Untertexte in den Kacheln und die Buttons
Ansehen. Fragen klappen jetzt per Akkordeon auf, ein «was ist klickbar»
muss nicht mehr erklärt werden.
Die Klasse .eyebrow in components.css blieb — für künftige Blöcke.
2b. Hero und Partnerleiste auf Mobilgeräten
.hero--bg (nur index.html, unter 1024px):
.hero__visualwird zuposition: absolute; inset: 0— das Foto legt sich also über die gesamte Fläche der Sektion und fungiert als deren Hintergrund;.hero__copyliegt darüber (z-index: 1);.hero__frame::before(rote Unterlegung) ist deaktiviert,::aftererhält einen kräftigen Scrim — Titel und Lead liegen direkt auf dem Foto;.hero__badge(Bewertung «Trusted by Küchenhäuser • 4.97/5») ist ausgeblendet: sie doppelte die Zahlen aus.hero__statsund verlängerte den ersten Bildschirm.
Warum der Hintergrund über ein <img> läuft und nicht über CSS
background-image: Beim Bild bleiben srcset/sizes erhalten (Smartphone
lädt 800w statt 1200w), und das ist genau eine Anfrage. Mit separatem
background-image würde das Bild doppelt geladen — ein per display: none
verstecktes <img> lädt der Browser trotzdem.
Am Desktop wurde nichts verändert: dort gibt es weiterhin zwei Spalten mit
gerahmtem Foto und Bewertung. ueber-uns.html nutzt dasselbe Hero-Markup ohne
.hero--bg, deshalb bleiben die gemeinsamen .hero__*-Regeln erhalten.
.logo-bar: height: 72px + overflow: hidden wurden durch
min-height + padding-block ersetzt, beide Zeilen brechen um
(flex-wrap) und werden zentriert, die Abstände sind adaptiv (clamp).
Die Regeln span:nth-child(n + 4/5) { display: none } wurden entfernt — auf
schmalen Bildschirmen müssen Marken nicht mehr versteckt werden, sie rutschen
einfach in die zweite Zeile.
3. Sektion Standorte — Funktionsweise
Gleiche Höhe von Karte und Liste
Das Raster .locations besteht aus zwei Spalten und zwei Zeilen:
"search listhead" ← Zeile auto: Suche | «Alle Hubs» + Zähler
"map list" ← Zeile 1fr: Karte | scrollende Liste
Am Desktop bekommt das gesamte Raster eine feste Höhe
clamp(600px, 78vh, 800px) — das ist der einzige Weg, «Karte genauso hoch
wie die Liste» zu garantieren. Die Liste scrollt in sich selbst, die Karte
streckt sich über die ganze Zeile. Auf Mobilgeräten zerfällt das Raster in
eine Spalte, die Karte bekommt feste 460px, und der interne Scroll der
Liste wird deaktiviert (overflow: visible + touch-action: pan-y auf
.location): der Finger scrollt die Seite statt nach der schmalen Scroll-Spur
neben den Plaketten zu suchen.
Kartenstart
assets/js/locations.js initialisiert die Karte sofort auf Deutschland und
sofort interaktiv:
germanyView()ist keine Konstante, sondern einfitBoundsüber alle 9 Filialen (map.getBoundsZoom), sodass sich der Kartenausschnitt an den Container anpasst. Die Berechnung läuft erst nachL.map(): davor hat der Container keine Maße.- Die Statuszeile zeigt sofort «Deutschland – 9 Hubs», 9 Marker und 9 Listenzeilen erscheinen nacheinander (55 ms Abstand).
resize(auch bei Drehung des Smartphones) berechnet den Ausschnitt neu — aber nur, wenn die Kamera nicht auf einer ausgewählten Filiale steht (viewLocked).
Früher gab es hier ein Scroll-Intro «Welt → Deutschland»: die Karte startete
bei center [36, 2], zoom 2.4, näherte sich mit dem Scroll-Fortschritt und
aktivierte Drag/Zoom erst bei settle(). Das wurde entfernt — man musste bis
ans Ende der Sektion scrollen, damit die Karte überhaupt auf Eingaben
reagierte. Zusammen damit fielen progress/renderProgress/onScroll/rewind/settle
und der scroll-Listener weg.
zoomSnap: 0 bleibt erhalten, für gebrochenen Zoom aus getBoundsZoom.
Das Mausrad wird bewusst nicht abgefangen (scrollWheelZoom: false),
damit das Scrollen der Seite nicht bricht; gezoomt wird über die Buttons
unten rechts oder per Doppelklick.
Die CARTO-Kacheln werden bei devicePixelRatio > 1.3 als @2x angefragt,
detectRetina ist deaktiviert: es halbiert tileSize und erhöht
zoomOffset, lädt also die vierfache Menge an Kacheln — der teuerste
Anfrageblock der Sektion auf dem Smartphone.
Aktive Filiale
Die rote Plakette markiert die ausgewählte Filiale, immer genau eine.
Standardmäßig aktiv ist die Filiale mit highlight in DATA (Bremen) —
setActive() schaltet nur Klassen um, die Kamera bewegt sich nicht. Wird eine
andere Stadt gewählt, kehrt die vorherige Plakette zu Weiß zurück.
Früher war Rot fest an .location--new gebunden, deshalb leuchtete Bremen
immer, unabhängig von der Auswahl; die Klasse wurde entfernt, «Neuheit» trägt
jetzt nur noch die Pille NEU (.location__flag) und der weiße Pin
map-pin--new auf der Karte.
Das Leeren der Suche (Kreuz oder Backspace bis zum leeren Eingabefeld) löst
resetView() aus: die Kamera fliegt zurück zur Gesamtansicht Deutschlands
mit allen 9 Standorten, das Popup schließt sich, die Hervorhebung springt
zurück zur Standard-Filiale.
Bei prefers-reduced-motion: reduce sind alle Kameraflüge sofort, ohne
Animation.
Suche mit Dropdown-Liste
combobox über der Karte (Rolle combobox + listbox,
aria-activedescendant):
- filtert nach Name, Region, Notiz und Aliasen
(
keywords:koeln,cologne,nuernberg,leipzig,frankfurt…); - Umlaute werden normalisiert (
ä→a,ö→o,ü→u,ß→ss) — funktioniert sowohl mitKölnals auch mitkoeln; - Treffer werden mit
<mark>hervorgehoben; - Tastatur:
↓/↑— Navigation,Enter— auswählen,Esc— schließen; - Auswahl einer Filiale:
flyTobis Zoom 9.5, Popup öffnet sich, die Zeile in der Liste wird hervorgehoben und ins Bild gescrollt.
Die Standortdaten liegen an einer einzigen Stelle — im Array DATA am Anfang
von locations.js. Eine Filiale hinzufügen = ein Objekt ergänzen (name, lat, lng, region, note, keywords, highlight). Liste, Marker, Suche und Zähler
bauen sich von selbst zusammen.
Skyline-Illustrationen in den Buttons
Jeder Standort-Button kann eine Line-Art-Silhouette der Stadt zeigen.
Angebunden sind 8 von 9: Hamburg, Bremen, Berlin, Paderborn, Günthersdorf,
Köln, Fürth, München. Für Langenselbold gibt es noch keine Zeichnung,
dieser Standort hat kein skyline-Feld, der Button sieht aus wie zuvor.
So funktioniert es:
- die Datei liegt unter
assets/img/svg/<Name>.svg; - in
DATAbekommt die Filiale das Feldskyline: '<Name>'— fehlt es, bleibt der Button unverändert, es gibt also keinen 404; locations.jslegt die fertige URL in--skylineab (encodeURIComponentwegen der Umlaute inKöln.svg/München.svg);- CSS nutzt das SVG als Maske, nicht als Bild: die Farbe kommt aus
currentColor, die Grafik passt sich also automatisch dem Theme an (schwarz im Light-, weiß im Dark-Mode) und wird auf der roten Plakette der aktiven Filiale weiß.
Zwei verschachtelte Elemente — .location__skyline (Auflösung nach links,
damit der Hintergrund unter Nummer und Namen sauber bleibt) und
.location__skyline-art (die Stadt selbst). Getrennt, weil jedes genau eine
Maske braucht: mask-composite verhält sich browserabhängig unterschiedlich.
Die Transparenz setzt sich aus drei unabhängigen Faktoren zusammen — alle
liegen auf .location, damit sie nicht um Spezifität konkurrieren:
| Variable | Wer setzt sie | Wofür |
|---|---|---|
--skyline-op / --skyline-op-hover |
Dark-Theme | ein weißer Strich auf Schwarz «leuchtet» stärker |
--skyline-boost |
skylineBoost in DATA |
eine bestimmte Zeichnung abdämpfen (derzeit ungenutzt) |
--skyline-mob |
@media (max-width: 560px) |
in der schmalen Spalte braucht der Text die ganze Breite |
Ergebnis: opacity: calc(<Basiswert des Zustands> * var(--skyline-mul)).
Würden stattdessen Selektoren ergänzt, würde das Dark-Theme .is-active mit
seiner roten Plakette überschreiben.
Auch die Geometrie sind Variablen, beide werden aus DATA gespeist:
| Variable | Feld in DATA |
Standardwert |
|---|---|---|
--skyline-h |
skylineHeight |
82% der Button-Höhe |
--skyline-x |
skylineX |
calc(100% + 24px) — Verschiebung nach rechts, weg vom Text |
Die Verschiebung nach rechts schneidet rechts einen schmalen Streifen ab: bei
allen Panoramen steht dort gewöhnliche Bebauung, die markanten Bauwerke stehen
in der Mitte. Auflösung nach links — transparent 22% → #000 70%: die
detaillierte Architektur ist dichter als die konturierte Silhouette von
Hamburg, deshalb ist der Freiraum unter Name und Notiz größer, als für Hamburg
allein nötig wäre.
Der Kreis ↗ hat eine deckende Füllung var(--bg) bekommen — darunter
verläuft die Skyline, ohne Hintergrund ging der Pfeil in den Linien unter. Bei
Hover bekommt der Kreis eine rote Umrandung. var(--surface) passt nicht: im
Dark-Theme ist es halbtransparent.
⚠️ Die URL in --skyline muss unbedingt absolut sein
(new URL(..., document.baseURI)). Ein relativer Pfad innerhalb einer
Custom Property wird nicht relativ zum Dokument aufgelöst, sondern relativ
zum Stylesheet, das den var() einsetzt — also relativ zu assets/css/.
Aus assets/img/... würde assets/css/assets/img/... und ein 404, ohne
eine einzige Fehlermeldung in der Konsole.
Eine neue Stadt hinzufügen:
- Die Quelldatei nach
assets/img/svg/_source/<Name>.svglegen. - Durch den Optimierer laufen lassen (siehe unten) nach
assets/img/svg/<Name>.svg. skyline: '<Name>'im passendenDATA-Objekt ergänzen.
Der Dateiname entspricht dem Filialnamen in DATA. Umlaute sind kein Problem
(Köln.svg, München.svg, Fürth.svg) — die URL wird in JS kodiert.
Das Seitenverhältnis liegt bei etwa 6.6 : 1 (Panorama): darauf sind
mask-size: auto 82% und die Auflösung nach links abgestimmt. Ein echter
Vektor wird unverändert eingebunden, der Optimierer wird dafür nicht
gebraucht.
Der Optimierer
Die gelieferten SVGs sind Exporte eines Vektorisierers: dasselbe Raster-PNG ist doppelt eingebettet (base64), einmal als Luminanz-Maske, einmal als schwarze Fläche. Daher 0,7–4,8 MB pro Datei. Das Skript holt die Masken-Ebene heraus, wandelt Helligkeit in Alpha um, schneidet auf den Inhalt zu und baut daraus ein einziges leichtes SVG:
scripts/optimize-skyline.ps1 `
-Source assets/img/svg/_source/Berlin.svg `
-Target assets/img/svg/Berlin.svg `
-MaxWidth 900 -Dilate 0
Ergebnis: 18 MB → 455 KB für neun Dateien.
Die Maske wird über das <mask>-Element ermittelt, nicht über die Größe
des base64-Blocks: bei Köln, München und Fürth ist die schwarze Fläche
größer als die Maske (3,0–3,9 MB gegenüber 0,8–1,0 MB), und die Heuristik
«größter Block» hätte die falsche Ebene gewählt — das Ergebnis wäre ein
leeres Bild gewesen.
-Dilate <r> verdickt den Strich per Maximum-Filter. Der Bedarf ist
unterschiedlich — -MaxWidth wird auf die doppelte Anzeigegröße
abgestimmt, dann ergibt -Dilate 1 etwa +1px fertigen Strich:
| Zeichnung | MaxWidth | Dilate | Warum |
|---|---|---|---|
| Hamburg (einkonturige Silhouette) | 900 | 1 |
Linie mit 0,15 % der Breite, ohne Verdickung zerfällt sie auf Button-Größe zu Rauschen |
| Bremen | 900 | 1 |
entstand, als Bremens Plakette dauerhaft rot war: Weiß auf Rot lässt sich nicht kontrastreicher machen, der dünne Strich war kaum lesbar. Jetzt ist Rot = aktive Filiale, aber die Verdickung stört nicht |
| Berlin, Köln, München, Paderborn, Fürth | 900 | 0 |
Fenster und Ornamente verschmelzen bei Verdickung zu einem schwarzen Klumpen |
| Günthersdorf | 440 | 1 |
wird doppelt so schmal wie die Panoramen gezeichnet, daher auch die kleinere Quelle |
Bei Bremen füllt -Dilate 2 bereits die Kuppel des Universums zu einem
Fleck — mit dem Auge in echter Größe prüfen, nicht in der Vollbreiten-Vorschau.
Günthersdorf — ein Sonderfall
Das ist keine Skyline, sondern ein nachgezeichnetes Bodenfoto der Fläche: Seitenverhältnis 3.5 : 1 statt 6.5. Bei gleicher Höhe wird es halb so schmal wie die Panoramen, deshalb:
skylineHeight: '78%'— größer als bei den anderen, sonst geht sie unter;skylineX: '100%'— ohne Verschiebung nach rechts: die schmale Zeichnung würde sonst unter den Kreis↗geraten;- gebaut mit
-Dilate 1— der Strich passt zu den übrigen im Ton.
Sollte ein echtes Panorama entstehen, können beide Felder entfernt werden.
Stilunterschied
Hamburg ist mit einer Kontur ohne Details gezeichnet, die übrigen 7 sind detaillierte Architektur mit Fenstern und Ornamenten. In der Liste fällt das auf: Hamburg wirkt leichter und leerer. Damit das Set einheitlich wird, bräuchte es entweder Hamburg im Detailstil oder die anderen im Konturstil — das ist eine Frage der Quelldateien, per Code lässt sich das nicht lösen.
3b. Scroll-Szene mit dem Schraubenschlüssel
assets/js/wrench.js + .wrench-* in sections.css.
Die Szene ist 300vh hoch (240vh auf Mobilgeräten), darin steckt ein
Sticky-Frame über 100vh. p = 0…1 wird aus dem Fortschritt durch die Szene
berechnet und auf drei Phasen aufgeteilt:
| p | Was passiert |
|---|---|
| 0 → 0,08 | der Schlüssel fliegt von unten ein: translateY 75% → 30%, scale .35 → .9 |
| 0,08 → 0,55 | er dreht sich im Bild: rotate -28° → -2°, scale mit Sinuskurve |
| 0,55 → 1 | er zieht nach oben ab und löst sich auf |
Überschrift und Raster 01–04 laufen über eigene Rampen.
Pre-Roll. Während die Sektion noch von unten hereinfährt, ist p noch 0
— und das erste Bild der Szene war ein leerer schwarzer Bildschirm. Deshalb
läuft die Überschrift über einen eigenen Einfahr-Fortschritt (pre), und
sobald das Bild einrastet, steht der Schriftzug bereits an seinem Platz.
Schlüssel und Raster bleiben am Sticky-Fortschritt.
Bei prefers-reduced-motion: reduce wird das Skript gar nicht erst geladen:
CSS staucht die Szene zu einer normalen Sektion und zeigt ein statisches,
lesbares Bild.
3c. FAQ — ein gewöhnliches Akkordeon in roter Karte
Ein eigenes Skript hat das FAQ nicht mehr: es läuft über das allgemeine
assets/js/accordion.js, die Stile liegen in .faq-head / .faq-accordion
in sections.css (nur die Palette «Weiß auf Rot» ist speziell, Mechanik und
Markup sind dieselben .accordion__*).
Was es vorher gab und warum es ersetzt wurde. Das ursprüngliche Konzept
(Translogistik-Faq-Brighter-Style.html) bot 4 Umschalt-Kacheln 01–04 und
ein großes «Display» für die Antwort darüber: ein Klick auf die Kachel
tauschte Titel und Text im Display aus. Zusammen mit den Untertexten in den
Kacheln, den Buttons «Ansehen», dem Hinweis «Wähle 01 / 02 / 03 / 04» und
min-height: 180px bei den Kacheln nahm der Block auf dem Smartphone fast
drei Bildschirme ein — bei vier Fragen. All das ist entfernt; geblieben sind
die Fragestellung, das «Plus» und die aufklappende Antwort.
Die Gruppe trägt data-accordion-scroll: nach dem Aufklappen zieht
accordion.js den Punkt sanft ins Bild (scrollIntoView, block: 'start',
Reserve für den fixierten Header über scroll-margin-top an
.accordion__item). 'start' statt 'nearest': eine lange Antwort ist
höher als der Bildschirm, und 'nearest' würde ihr unteres Ende heranziehen
— der Leser sähe das Ende des Texts statt des Anfangs. Ist der Punkt ohnehin
komplett sichtbar (Desktop), gibt es keinen Scroll.
Wie bisher liegen die Antworttexte im Markup, nicht in einem
JS-Array: Fragen und Antworten werden von Suchmaschinen indexiert, und ohne
JS klappt <noscript> alle Panels sofort auf und entfernt die «Plus»-Zeichen.
Das «Plus»-Symbol in allen Akkordeons
.accordion__icon zeichnet das «Plus» mit zwei Balken (::before /
::after), nicht als Textzeichen +. Dafür gibt es zwei Gründe:
- Der optische Mittelpunkt des Glyphen liegt auf der mathematischen Achse
der Schrift, nicht in der Mitte seiner Zeilenbox. Bei jeder
flex-/grid-Zentrierung saß das+etwa 1,5px unterhalb der Kreismitte. - Früher tauschte
accordion.jsdas Zeichen gegen×, und CSS drehte den Kreis zusätzlich um 45°. Das gedrehte×sah wieder wie ein+aus — der geöffnete Zustand war optisch nicht vom geschlossenen zu unterscheiden. Jetzt macht die Drehung ehrlich aus dem+ein×, und das Skript rührt den Icon-Text gar nicht mehr an.
Mit demselben Kniff ist auch das Schließen-Kreuz des Modals gezeichnet
(.modal__x).
4. Theme (light / dark / system)
Von Anfang an eingeplant, wie gewünscht:
<html data-theme="light|dark">— wird per Inline-Skript im<head>vor der ersten Darstellung aufgelöst, deshalb gibt es kein Aufblitzen von Weiß.- Drei Modi:
system(Standard) →light→dark, der Button im Header schaltet durch; Icon◐ / ☀ / ☾. - Im Modus
systemfolgt das Theme dem Betriebssystem live — über den ListenermatchMedia('(prefers-color-scheme: dark)'). Schaltet der Nutzer das Theme in Windows/macOS um, färbt sich die Website ohne Neuladen um. - Die Wahl wird in
localStoragegespeichert (tl-theme);system= Key entfernt. - Das Event
tl:themechange({ mode, resolved }) ist ein Erweiterungspunkt. Wird bereits genutzt: die Karte schaltet die CARTO-Kacheln zwischenlight_allunddark_allum.
Alle Farben laufen ausschließlich über Tokens in tokens.css. Im CSS
braucht es genau einen Dark-Block :root[data-theme='dark'], weil das
Auflösen JS übernimmt.
Separat gibt es die Klasse .on-dark — für Sektionen, die immer dunkel
sind (Hero, Prinzip, Footer). Sie überschreibt lokal dieselben Tokens,
sodass die Komponenten darin (.btn, .card, .eyebrow) nichts vom
Kontext wissen müssen und überall gleich funktionieren.
5. Was es sonst noch gibt
- Mobilmenü (Burger → Drawer),
Escschließt es. - Scroll-Spy: der aktive Navigationspunkt wird rot unterstrichen.
[data-reveal]+data-reveal-delay— Einblenden von Blöcken beim Scrollen überIntersectionObserver.prefers-reduced-motionwird respektiert: Animationen und Kameraflüge der Karte werden deaktiviert.- Degradation ohne JS:
<noscript>macht versteckte Blöcke wieder sichtbar; lädt Leaflet nicht, wird die Karte durch Text ersetzt, während die Standortliste voll funktionsfähig bleibt. - Die Plakette «Aktuell: Bremen – Neueröffnung» (
.extra-flash) ist komplett ein<a>-Link zunachrichten.html, nicht nur ein klickbarer Pfeil in einem<div>.
Laden von Schriften und Karte
Eine Anfrage statt sechs: Inter:wght@400..900 — variabler Schriftschnitt,
Instrument+Serif:ital@1 — nur kursiv (.serif und .marquee-symbol
verwenden keinen geraden Schnitt). Die URL ist auf allen Seiten
identisch — sonst würde der Wechsel index → karriere einen zweiten
Dateisatz am Cache vorbei laden.
index.html öffnet vorab Verbindungen zu den Hosts, die nur die Karte
braucht: preconnect zu unpkg.com (CSS + JS von Leaflet) und zu
a.basemaps.cartocdn.com, dns-prefetch zu b./c. — ohne crossorigin,
alle drei Anfragearten laufen ohne CORS.
6. Die Seite «Über uns» — ueber-uns.html
Die zweite Seite der Website. Das Grundgerüst (Header, Hero, Logo-Leiste,
Footer, Block «Bereit, mit uns zu wachsen?») wurde ohne CSS-Änderung von
der Startseite übernommen — neue Stile brauchten nur zwei Sektionen, sie
liegen in einem eigenen about.css.
| Block | Quelle | Was geändert wurde |
|---|---|---|
| Header / Footer / Logo-Leiste | index.html |
Links zu Sektionen der Startseite führen über index.html#…; Punkt «Über uns» mit aria-current="page" ergänzt |
| Hero | index.html |
Gleiches Markup, Text passend zu «Über uns»: «Wir sind Translogistik. Und wir liefern mehr als nur Küchen.», Zahlen → 2014 / 450+ / 9, Buttons → Werte / Geschichte |
| «Von der Idee zum deutschlandweiten Netzwerk.» — Timeline 2014 → 2026 | Gestaltung: Translogistik-Codex-V2-Mit.html, Überschrift: translogistik-template.html |
Tailwind + React → semantisches HTML + timeline.js. Mini-Kicker «Unsere Geschichte» und Hinweis «Scroll • Timeline» entfernt |
| «Werte, die uns stark machen.» — 7 Werte | Gestaltung: translogistik-template.html, Block «Warum unsere Partner mit uns arbeiten» |
Dunkle Sektion und Kachel «Icon + Titel + Text» beibehalten, Inhalt durch 7 Werte ersetzt; Glyphen ◆ ◇ ▣ ◈ durch Stroke-SVGs ersetzt (24×24, currentColor) |
| «Bereit, mit uns zu wachsen?» (Collage + 3 Bewertungen + Buttons) | index.html |
Eins zu eins übernommen, Buttons führen zu index.html#karriere / #standorte |
Timeline — timeline.js
<div data-timeline>
<nav><button data-timeline-year aria-controls="ID">2014</button>…</nav>
<div><article data-timeline-item id="ID">…</article>…</div>
</div>
- Jahre und Meilensteine sind über ihre Reihenfolge im DOM verknüpft: der
n-te Button ↔ der n-te Meilenstein. Ein Meilenstein hinzufügen = einen
Button und ein
<article>ergänzen, das JS weiß nichts von Jahreszahlen. - Der aktive Meilenstein wird über die Nähe der Blockmitte zur
Bildschirmmitte bestimmt (
scroll+requestAnimationFrame), nicht überintersectionRatio: auf dem Smartphone sind Meilensteine kürzer als der Beobachtungsbereich, sonst wären mehrere gleichzeitig im Bild. - Der Scroll wird nur beobachtet, solange die Sektion im Bild ist — Gate über
IntersectionObservermitrootMargin: 20%. - Ein Klick auf ein Jahr zieht den Meilenstein in die Mitte
(
scrollIntoView), Pfeile /Home/Endschalten die Jahre per Tastatur um. - Die Rollen
tablist / tab / tabpanelwerden nicht verwendet: die Meilensteine sind alle gleichzeitig sichtbar, das ist eine Sprungmarken-Navigation, keine Tabs. Das aktive Jahr trägtaria-current, alle Buttons bleiben in der Tab-Reihenfolge. - Bei
prefers-reduced-motion: reduceund ohneIntersectionObserverzeigt das Skript einfach alle Meilensteine mit voller Deckkraft. Ohne JS macht<noscript>dasselbe.
Sticky-Jahre und Grid
Die Jahresspalte funktioniert mit position: sticky, weil bei einem
Grid-Element der Container für das Anheften seine Grid-Area ist, und die
Zeilenhöhe von der Nachbarspalte mit den Meilensteinen bestimmt wird. Auf
Mobilgeräten ist das Raster einspaltig, das Jahresband hat seine eigene
Zeile in Eigenhöhe — nichts, woran es haften könnte, deshalb wird daraus ein
horizontales Band über den Meilensteinen. Kein Bug, sondern Folge des
Modells: eine Reparatur müsste die Jahre aus dem Raster herauslösen.
Jahresband auf Mobilgeräten: Abschnitt links und Scrollbalken
Zwei Korrekturen an diesem Band:
-
Abschnitt links. Beim aktiven Jahr skaliert der Punkt auf
scale(1.8), und bei dem ersten (2014) steht er ganz am Anfang des Scroll-Containers — die linke Hälfte wurde vom Rand desoverflow-xabgeschnitten. Das Band bekampadding-inline: 8px 4pxundscroll-padding-inline: 8px, der gesamte Block ist also etwas nach rechts verschoben. -
Scrollbalken.
timeline.jsbaut unter dem Band.timeline__rail-barmit einem Schieber: die Breite entspricht dem sichtbaren Anteil, die PositionscrollLeft. Ohne ihn ist im statischen Bild nicht erkennbar, dass rechts noch weitere Jahre folgen. Die native Scrollbar passte nicht: auf Touch-Geräten liegt sie als Overlay darüber und erscheint nur während der Geste, deshalb wird sie ausgeblendet (scrollbar-width: none). Der Schieber lässt sich ziehen (Pointer Events +setPointerCapture), passt das Band komplett hinein, versteckt sich das Element von selbst.Der Schieber wird als Geschwister-Element des Bandes eingefügt, nicht als Wrapper: ein Wrapper würde
position: stickyam Desktop brechen (es gäbe innerhalb eines Blocks in Eigenhöhe des Bandes nichts, woran man haften könnte). Deshalb wird er eine eigene Rasterzelle, und ein negativermargin-topin Höhe von--geschichte-gapzieht ihn direkt unter die Jahre. Am Desktop ist erdisplay: none.
Das aktive Jahr wird zusätzlich im Band zentriert (revealYear) — dabei wird
rail.scrollLeft bewegt, nicht scrollIntoView: das würde den vertikalen
Seitenscroll gleichzeitig mit dem Scroll des Nutzers mitziehen.
Werte-Raster ohne «Waise»
7 Kacheln würden bei 2 und bei 3 Spalten eine unvollständige letzte Zeile
hinterlassen. Die siebte (Ehrlichkeit) trägt .wert--wide:
grid-column: 1 / -1 und horizontales Layout — die Zeile schließt bei jedem
Breakpoint.
Navigation zwischen den Seiten
- Header und Mobilmenü sind auf beiden Seiten identisch (7 Punkte): Home / Über uns / Prinzipien / Standorte / Leistungen / Team / FAQ.
.is-activesteuert das Scroll-Spy nach Sektionen der aktuellen Seite,[aria-current="page"]markiert dauerhaft eine einzelne Seite. Das Scroll-Spy stört das nicht: es fasst nur.is-activean.- 7 Punkte + Logo + CTA passten nicht in 1024px, deshalb ist bei 1024–1199px der Navigationsabstand auf 20px gestaucht und der Zusatz «Service GmbH» ausgeblendet.
- Die Sektionen von «Über uns» sind über den Footer der Startseite
erreichbar (
ueber-uns.html#geschichte,#werte), deshalb mussten sie nicht in den Header und dessen Einheitlichkeit zwischen den Seiten brechen.
6b. Die Seite «Unsere Mission» — unsere-mission.html
Stile — pages.css (plus die allgemeinen sections.css / karriere.css).
Benefits — «Eine neue Küche ist eine Investition ins Leben.»
Das Layout wurde vom «Zickzack» auf linksbündige Ausrichtung umgestellt.
Vorher: eine mittige Linie (left: 50% bei ≥768px), der Text abwechselnd
links und rechts davon. Bei langen Überschriften überlappten sich die
Spalten — die Linie schnitt Text und Icons.
Jetzt ein einziges Layout für alle Größen:
[Icon 72px] [Text mit padding-left]
↑
Linie left: 36px — genau mittig in der Icon-Spalte
grid-column / grid-row sind explizit gesetzt, die Reihenfolge der
Elemente im Markup hat also keinen Einfluss auf die Anordnung;
.benefits-timeline__spacer (leere Zellen des Zickzacks) wurden aus dem
Markup entfernt. margin-inline: 0 beim Viewport statt auto: das Band
beginnt an derselben Führungslinie wie die Sektionsüberschrift. Unter 768px
werden Icon-Spalte und Abstand gestaucht (56px / 20px), sonst blieben der
Überschrift weniger als 250px. Das Kicker-Label «Benefits für Kunden,
Partner & Team» über der Überschrift wurde entfernt — es trug nichts bei,
was Überschrift und Lead nicht ohnehin sagten.
Nebeneffekt der Linksausrichtung: die Icons rückten dicht an den linken Rand
der Container, und der Box-Shadow des aktiven Kreises (is-active/
is-passed, rotes Leuchten) wurde links abgeschnitten — die «Plakette» sah
aus, als fehle ihr eine Seite. Der Beschnitt geschah gleich auf drei
Ebenen:
.benefits-timeline__viewport—overflow: hiddenwird für die vertikale Sticky-Maske gebraucht (Fade oben/unten beim Scrollen);.benefits-timeline__sticky— der Wrapper der GESAMTEN Sektion (Überschriftentext + Viewport), stand ebenfalls aufoverflow: hidden, obwohl der vertikale Beschnitt nur ihn selbst betrifft — den horizontalen schnitt er als Nebeneffekt mit ab;body(base.css) — globalesoverflow-x: hidden, Schutz der gesamten Website vor versehentlichem horizontalem Scroll. Diese Kante schneidet JEDEN Box-Shadow ab, der darüber hinausragt, unabhängig vom Overflow der lokalen Sektions-Wrapper — sie ließ sich nicht einfach «öffnen», ohne den Schutz der ganzen Website aufzuheben.
Die ersten beiden Ebenen wurden über eine unabhängige Achse statt einem
pauschalen overflow geöffnet (overflow-x: visible; overflow-y: hidden; —
ein Box-Shadow erzeugt kein scrollbares Overflow, aktiviert also keinen
horizontalen Scroll). Das reichte aber nicht aus: auf schmalen Bildschirmen
bleiben zwischen Icon und echtem Seitenrand nur 24–36px (Container-Padding),
und der ursprüngliche Glow-Blur betrug 24px — er stieß direkt an die
Schutzkante von body.
Da die äußere Schutzkante nicht angetastet werden durfte und der Blur selbst nicht künstlich verkleinert werden sollte (das Leuchten würde kleiner wirken), wurde stattdessen der ganze Block nach rechts verschoben und bekam so eigenen Abstand zum Rand:
.benefits-timeline__track { --benefits-inset: 16px; }
.benefits-timeline__step { padding-left: var(--benefits-inset); }
.benefits-timeline__line { left: calc(var(--benefits-inset) + 36px); } /* war 36px */
--benefits-inset liegt auf .benefits-timeline__track, nicht auf
__step: .benefits-timeline__line ist ein Geschwisterelement der Steps
(beide liegen direkt im Track), nicht deren Nachfahre, und könnte die
Variable nicht vom Step übernehmen — es braucht einen gemeinsamen Elternteil,
von dem der Wert nach unten an beide vererbt wird. Linie und Icon-Spalte sind
um denselben Betrag verschoben, die Zentrierung (Linie genau mittig im
Kreis) bricht daher bei keinem Breakpoint (auf Mobilgeräten 28px statt
36px, aber --benefits-inset bleibt gleich).
Mit diesem Abstand wird selbst beim engsten Breakpoint (768–1023px, 44px
bis zur body-Kante) der ursprüngliche
box-shadow: 0 8px 24px var(--accent-glow) nicht mehr beschnitten — der
Blur wurde in Originalgröße zurückgesetzt, verkleinert werden musste nichts.
Challenges — «Herausforderungen wechseln.»
- Der Autowechsel läuft strikt der Reihe nach 1 → 2 → 3 → 4 → 1. In der
Quelle (
import/Herausanforgerungen.html) wurde der nächste Punkt zufällig gewählt: Nutzer konnten nicht erkennen, wie viele Punkte es insgesamt gibt und ob sie alle gesehen hatten. - Auf dem Smartphone ist das kapselförmige Umschalt-Band komplett
ausgeblendet (
.challenges__track-wrap { display: none }): vertikal nahm es einen halben Bildschirm ein und doppelte die Überschriften der Karten selbst. Die Punkte blättert die Autoplay-Funktion durch. Das Markup bleibt bestehen — daraus werden Indizes und Prozentwerte gelesen. - Ist Bewegung deaktiviert (
prefers-reduced-motion), gibt es keine Autoplay-Funktion, deshalb werden auf dem Smartphone alle vier Punkte als Liste mit Trennlinien gezeigt — sonst wäre nur der erste sichtbar.
Karten-Links
Die drei Karten «Was wir uns selbst abverlangen» sind jetzt
<a class="service">, die gesamte Fläche ist klickbar: «Mehr Details →»
versprach schon vorher einen Link, klickbar war aber nur das Wort selbst.
Sie führen zu «Was du bekommst» auf karriere.html#benefits.
Die Plakette «Offene Stellen für Küchenmonteure» ist komplett ein Link zu
karriere.html#stellen (vorher war nur der Pfeil klickbar).
Der Hover-Zustand für a.service liegt in sections.css.
6c. Video-Sektion «Ein Tag auf Montage» — karriere.html
.montage__grid wurde von zentriertem Flex auf Grid umgestellt:
grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 440px));
justify-content: start;
gap: 56px; /* 40px ≤900px, 24px ≤640px */
Das bringt: Das erste Video liegt auf derselben linken Führungslinie wie der
Text von .section-head darüber (vorher war es zentriert, und das Raster
«hing» relativ zur Überschrift); ein dritter Clip erfordert keine
CSS-Anpassung — die Spalte entsteht von selbst, und auf schmalen
Bildschirmen rutschen die Clips in eine Spalte. max-width bei
.montage__card-wrap wurde entfernt — die Breite bestimmt die Rasterspur.
Der Gap ist bewusst groß (56px) — neben Videos bis 780px Höhe wirkten 32px
wie «verklebte» Karten statt wie ein Abstand.
Beim Play-Icon wurde margin-left: 2px entfernt — der «optische Mittelpunkt»
des Dreiecks wirkte wie ein Verlaufsfehler, der Container zentriert ohnehin
über Flex.
6d. Formulare: Sende-Bestätigung
assets/js/form-modal.js + .modal in components.css. Genutzt von
kontakt.html und bewerbung.html.
Vorher: ein Inline-Block .form__done / .wizard__done unter dem Button —
bei einem langen Formular musste man ihn erst herunterscrollen, um ihn zu
finden. Jetzt: ein modales Fenster über der Seite (role="dialog",
aria-modal, Scroll-Sperre über body.modal-open).
- Schließen: Button
Schließen, Kreuz, Klick auf die Unterlage,Esc. - Nach dem Schließen wird das Formular geleert (
resetForm: Werte + Klassen.is-invalid+aria-invalid), damit es erneut abgeschickt werden kann; der Wizard springt zusätzlich zurück auf den ersten Schritt. - Der Fokus geht beim Öffnen auf den Schließen-Button, beim Schließen kehrt er dorthin zurück, wo er herkam.
Nebenbei wurde die Validierung der Einwilligung repariert. Die Checkbox
«Ich bin damit einverstanden…» trägt nicht die Klasse .field__control
(sie hat ihr eigenes Markup .field--check), und die Skripte sammelten
Steuerelemente nur über diese Klasse. Das Formular steht mit novalidate,
der Browser prüfte also ebenfalls nicht — die Checkbox war nur auf dem
Papier verpflichtend, und die Bewerbung ließ sich ohne Einwilligung zur
Datenverarbeitung absenden. Jetzt greift der Selektor auf
[data-field] input[type="checkbox"] zu, und
.field--check.is-invalid input bekommt eine rote Umrandung.
7. Mini-Block «Krieger Gruppe» — #gruppe auf der Startseite
Steht zwischen leistungen und team: zuerst was wir tun, dann wozu wir
gehören, dann das Team. Ein Band auf --bg-alt mit Linien oben und unten —
so liest sich der Block als eigener Mini-Block, nicht als weitere große
Sektion.
Quelle der Gestaltung ist import/krieger-gruppe-filter-karussell.html.
Was bei der Anpassung an die Website geändert wurde:
| Im Konzept | Auf der Website | Warum |
|---|---|---|
--panel:#f2f1ee, --highlight:#eaeefb (blau) |
var(--surface), Hover → var(--accent) |
Blau gehört nicht zur Palette; der Block funktioniert jetzt im Light- und Dark-Theme ohne zweiten Farbsatz |
h1 Georgia 48px |
.display Inter 800 + .serif italic |
Typografie der Website: Inter + Instrument Serif |
Marken im JS-Array brands[] |
Karten im Markup, data-cat an jeder Karte |
Wie die FAQ-Antworten: indexierbar und funktionsfähig ohne JS |
| Icons als Strings in JS | <symbol>-Sprite im Markup + <use> |
Fünf Formen für 9 Karten ohne Wiederholung |
| Pille pro Marke, Klick hob eine Karte hervor | Pillen als Kategorien (Alle + Bereiche), Karussell wird gefiltert, Zähler zeigt das Ergebnis |
Der Block heißt «filter-karussell», einen Filter gab es aber nicht |
.faded (transparent, aber weiter im Fluss) |
hidden (verlassen den Fluss) |
Sonst müsste man durch den Filter hindurch Geister-Karten wegscrollen, und der Zähler würde falsch angezeigt |
Auflösung an den Rändern #fff |
linear-gradient(90deg, var(--bg-alt), transparent) |
Im Dark-Theme wäre der weiße Schleier sichtbar gewesen |
mousedown/mousemove |
pointerdown/pointermove, nur bei pointerType === 'mouse' |
Touch-Scroll funktioniert ohnehin über overflow-x |
| Pfeilschritt als Konstante 280px | card.getBoundingClientRect().width + columnGap |
verrutscht nicht bei geänderter Kartenbreite |
Die erste Karte — rot, «Das sind wir» — ist kein Link, sondern ein
div, um zu zeigen, wo Translogistik innerhalb der Gruppe steht. Sollte
Translogistik laut Firmenstruktur nicht als Marke der Gruppe geführt werden,
den Block .gruppe__card--self entfernen, die übrigen 8 Marken und der
Zähler passen sich von selbst an.
Die Marken-Links führen zu externen Websites (target="_blank" rel="noopener noreferrer"). Ohne JS gibt es keine Pillen, aber das
Karussell lässt sich mit Finger und Mausrad durchblättern; <noscript>
entfernt Pfeile und Randauflösung, weil sie ohne Skript nicht aktualisiert
werden.
8. Fotografien
assets/img/*.jpg — Archiv des Fotoshootings (11 Bilder, 69 MB, ~6000 px).
An die Website ist es nicht angebunden: im Markup steht das Web-Set aus
assets/img/photos/.
scripts/optimize-photos.ps1 # 400 / 800 / 1200 / 1600 px, q82
scripts/optimize-photos.ps1 -Force # fertige Dateien überschreiben
69,5 MB → 4,0 MB. Der Kameradateiname wird auf einen Slug «Datum-Bild»
verkürzt: 2025-08-21 - Translogistik Günthersdorf … -49.jpg →
2025-08-21-49-800.jpg. Keine Abhängigkeiten — System.Drawing aus dem
.NET Framework.
Bildausschnitt — --pos
Die Aufnahmen sind durchgehend im Format 3:2 (einzige Ausnahme
2025-09-02-2, Hochformat 2:3), die Slots sind teils vertikal: Hero-Rahmen,
Collage-Kacheln 100×140…136×196, runde Avatare 28 px. Ein zentraler
object-fit: cover schneidet Personen am Gesicht ab.
Deshalb wird bei allen Foto-Slots object-position aus einer Custom
Property gelesen:
<img style="--pos: 62% 26%" …>
.collage img { object-position: var(--pos, 50% 30%); }
Der Wert liegt direkt beim Bild, weil er eine Eigenschaft des
Ausschnitts ist, nicht des Blocks: dasselbe Foto braucht im Hero und im
Avatar einen unterschiedlichen Fokus. Die Hooks stehen auf .hero__photo,
.collage img, .service__media img, .cta__media img, .avatar-row img,
.gruppe__photo img; ohne --pos greift ein sinnvoller Standardwert.
Was wohin gewandert ist
| Slot | Bild | Warum |
|---|---|---|
| Hero der Startseite | 2025-09-02-2 |
Das einzige Hochformat 2:3 — nur es wird im vertikalen Rahmen nicht beschnitten; im Hintergrund ist der Fuhrpark zu sehen |
| Hero «Über uns» | 2025-08-21-49 |
Gruppenaufnahme im Lager — «Wir sind Translogistik» |
| Fachgerechter Transport | 2025-09-02-18 |
Umwickeln mit Stretchfolie im Lkw = wortwörtlich Ladungssicherung |
| Präzise Montage | 2025-09-02-37 |
Ein verpacktes Element wird beim Kunden hereingetragen |
| Komplexe Logistikprozesse | 2025-08-21-49 |
Lager, Rollcontainer, Team |
| Krieger Gruppe | 2025-09-02-15 |
Translogistik-Jacke + Lkw «Montageteam Höffner» — bildlicher Beleg der Verbindung zur Gruppe |
| Abschließendes CTA | 2025-08-21-56 |
Zwei lachende Kollegen — stärker für die Anwerbung als ein Lkw |
| Collage Team (9 Kacheln) | alle 11 außer dem Porträt | Menschen, Lager, Montage, Monteurcasino |
| Hero-Avatare | -57, 09-02-15, -52 |
Gesichter näher an der Bildmitte, weniger betroffen vom runden Zuschnitt |
Was bewusst von Unsplash geblieben ist: die drei Avatare bei den
Bewertungen (Markus R., Sandra L., Thomas K.). Das sind Kunden, keine
Mitarbeiter — dort Gesichter von Monteuren einzusetzen, hieße eigene Leute
als fremde Auftraggeber auszugeben. Es braucht echte Fotos — nur zu echten
Bewertungen.
Zwei Bildunterschriften wurden dem neuen Foto angepasst, um nicht falsch zu sein:
- CTA:
Fuhrpark • 120+ Fahrzeuge→Teamgeist • Standort Günthersdorf; die Angabe «120+ Fahrzeuge» ist auf den Tag der Karte «Fachgerechter Transport» gewandert.
9. Weiteres
Aus import/Translogistik-Codex-V2-Mit.html nicht verwendet:
- «Wir sind Translogistik» (Über uns, 2 Spalten mit Foto — Block «Wer wir sind / Wie wir arbeiten / Was uns antreibt»);
- Block mit 4 Zahlen (98,2 % / 9 / 450+ / 24h).
Aus translogistik-template.html blieben übrig: «Unsere Gruppe»
(Firmenumschalter) und der vierstufige Prozess.