Ambience buro
Website für ein Architektur- und Innenarchitekturbüro mit Full-Service-Anspruch
Rolle
Frontend-/Fullstack-Entwickler (solo, gesamter Zyklus)
Jahr
2021
Stack
FigmaHTMLSCSSJavaScriptGulpWebpackGoogle Maps JS APIPHPWordPress

Die Aufgabe
Ein Büro mit Full-Service-Anspruch: Architektur von Häusern, Interieurs für Wohnungen und Gewerbeflächen, private und gewerbliche Auftraggeber. Als Ausgangsmaterial gab es ein Briefing und ein Logo — keine Vorgängerseite, kein Designsystem.
Die zentrale Anforderung kam vom Kunden selbst: Die realisierten Projekte sollten über ein monochromes Schema hervorgehoben werden. Das Interface darf der Fotografie der Räume nicht die Aufmerksamkeit streitig machen — klare Linien, feine Typografie, minimale Dekoration. Dazu kamen: ein Portfolio, in dem sich ein Objekt beurteilen lässt, ohne die Karte zu öffnen; ein Projekt-Template mit Grundrissen und Galerien; drei Leistungsseiten; ein Blog, der auf E-E-A-T ausgelegt ist. Und das alles auf WordPress, damit das Büro Portfolio und Artikel eigenständig pflegen kann — ohne Entwickler.
Die Phasen
Analyse und Auswertung des Briefings
- Durcharbeiten des seitenweisen Briefings: Zielgruppe, Alleinstellungsmerkmal, fünf Referenz-Websites mit Notizen dazu, was genau gefallen hat
- Entwurf der Sitemap: Startseite, Portfolio, Projektseite, drei Leistungsseiten, Über uns, Blog, Kontakt
- Übersetzung der Kundenwünsche in technische Anforderungen — aus „der Header soll die Farbe wechseln, gegensätzlich zum jeweiligen Abschnitt" wurde ein eigenes ScrollTrigger-Modul
Designkonzept in Figma
- Monochromes Schema, feine Typografie: Manrope 200 im Fließtext, Ysabeau SC in den Überschriften
- Minimale grafische Elemente — das visuelle Gewicht liegt bewusst auf der Projektfotografie
- Containerbreite nach der Abstimmung von 1690 auf 1240 px reduziert: Das Layout wurde dichter, die Projektaufnahmen im Verhältnis zu den Rändern größer
Umsetzung und Build
- Eigene Pipeline: Gulp orchestriert Webpack, danach folgt die Nachbearbeitung der Assets
- Mobile-First als harte Regel, nicht als Absichtserklärung
- HTML wird aus Partials zusammengesetzt — sechs Seiten als reine Komposition von Blöcken
Animation und Interaktion
- GSAP ScrollTrigger: kontextabhängiger Header, gepinnter Leistungs-Stack, gepinnte Vorteilsblöcke
- Swiper: Portfolio-Karte, die beim Hover durch die Aufnahmen blättert
- lightGallery in Kombination mit einem Justified-Layout für die Projektgalerien
- Click-to-Load-Fassade für YouTube, zwei Google-Maps-Instanzen, Verschieben von DOM-Knoten in das mobile Menü
WordPress-Integration
- Content-Modell auf ACF: Projektfelder, Artikelfelder, Options-Seite
- 12 Seiten-Templates auf Basis eines gemeinsamen Headers und Footers
- AJAX-Filterung für Portfolio und Blog mit Zustand in der Adressleiste
- Formulare mit Contact Form 7 und einem Yandex.Metrica-Ziel bei erfolgreichem Versand
SEO, E-E-A-T und Launch
- JSON-LD für die Organisation sowie eine
FAQPage, die aus einem Inhaltsfeld generiert wird - Autorenblock im Artikel, „Lesen Sie auch", Breadcrumbs in allen Bereichen
- Eigenes Cookie-Consent-Banner
- Meta-Tags
Der Prozess
Umsetzung
Designsystem in SCSS
Die Tokens liegen an einer einzigen Stelle: $mainFont / $titleFont, die Palette #000 / #fff / #747474, $maxWidthContainer: 1240 plus 30 px Gutter.
Der Container ist über einen Attributselektor umgesetzt, [class*='__container']: Jeder Block, der sein inneres Element nach der BEM-Konvention benennt, erhält Raster und Abstände automatisch — ohne Utility-Klasse im Markup und ohne Mixin in jeder Datei.
Mobile-First ohne Ausnahmen
295 min-width-Media-Queries gegenüber 8 max-width — die Basis ist für Mobilgeräte geschrieben, Desktop kommt obendrauf. Die Skala hat drei Stufen: Basis (< 768) → ≥ 768 → ≥ 992, ohne Zwischenlösungen.
Sämtliche :hover-Zustände sind in @media (any-hover: hover) gekapselt, damit auf Touch-Geräten der Hover nach dem Tippen nicht hängen bleibt — ein verbreitetes Ärgernis bei Katalogen mit großen Karten.
Projektkarte mit durchblätternden Aufnahmen
Die zentrale Interaktionsidee des Portfolios: Eine Karte soll das Objekt zeigen, nicht ein einzelnes Titelbild.
In jeder Karte steckt ein Swiper mit effect: 'fade'. Auf dem Desktop wird Autoplay direkt im init-Handler gestoppt, danach steuert ausschließlich der Zeiger den Slider: mouseenter setzt die Verzögerung auf 100 ms und startet, slideChange erhöht sie auf 800 ms, mouseleave stoppt. Dadurch wechselt die erste Aufnahme nahezu sofort — der Nutzer bekommt unmittelbar Rückmeldung — und danach laufen die Aufnahmen in einem ruhigen Rhythmus weiter, statt zu flackern. Auf Touch-Geräten kehrt sich der Zweig um: Es gibt keinen Hover, also läuft Autoplay von selbst.
Die Aufnahmen werden nicht von Hand gepflegt: Es sind das Titelbild des Beitrags und die ersten drei Bilder der Projektgalerie. Die Redaktion füllt eine Galerie, und die Vorschauen an drei Stellen der Website werden daraus automatisch zusammengesetzt.
Kontextabhängiger Header mit ScrollTrigger
Die Abschnitte sind mit den Klassen _dark / _light / _transparent ausgezeichnet. Für jeden wird ein ScrollTrigger erzeugt, dessen Grenzen um die Höhe des Headers verschoben sind (start: 'top-=${headerHeight} top') — der Wechsel erfolgt genau dann, wenn der Header physisch in einen neuen Abschnitt eintritt, und nicht, wenn dessen Oberkante den Viewport erreicht. Der Header wechselt auf das zum Abschnitt kontrastierende Thema, der Übergang ist weich.
Darüber liegt ein unabhängiges Scroll-Verhalten: Der Hintergrund erscheint, sobald der erste Bildschirm verlassen wird, und der Header verbirgt sich bei Abwärtsbewegung und kehrt bei Aufwärtsbewegung zurück. Beide Mechanismen lesen die Position über einen gemeinsamen Scroll-Listener aus, statt jeweils einen eigenen zu registrieren.
Gepinnter Leistungs-Stack
Jede Leistungskarte wird über die Höhe des Viewports gepinnt und blendet per opacity über, die via scrub an die Scrollposition gekoppelt ist — die Karten schieben sich als Stapel übereinander. Das erste und das letzte Element werden über eigene Zweige behandelt, damit der Stapel sauber beginnt und die Seite am Ende sauber wieder freigibt.
Projektgalerie
Ein Projekt hat drei unabhängige Galerien mit eigenen Bildunterschriften — in der Regel Renderings, Fotos der Umsetzung und Details. Jede davon kann leer bleiben, das Template kommt damit zurecht.
Das Layout ist ein Justified-Raster: Die Zeilen werden auf eine gemeinsame Höhe ausgerichtet (532 px auf dem Desktop, 400 auf Mobilgeräten), während die ursprünglichen Seitenverhältnisse der Aufnahmen erhalten bleiben. Für Interieurfotografie, in der Querformate, Hochformate und Panoramen gemischt vorkommen, funktioniert das besser als ein starres Raster — nichts wird mittig beschnitten.
Für die Ansicht sorgt lightGallery, gebaut mit nur den beiden tatsächlich benötigten Plugins (Thumbnails und Vollbild); die übrigen Module der Bibliothek werden schlicht nicht importiert und landen nicht im Bundle.
Content-Modell auf ACF
Ein Projekt wird über Felder beschrieben: Stadt, Jahr, Fläche, Tagline, ausführliche Beschreibung, Grundrissbild und drei Galerien mit Bildunterschriften. Ein Artikel besteht aus einem Vorschautext und einem FAQ-Repeater. Die Telefonnummer liegt auf einer Options-Seite, damit sie an einer Stelle geändert wird.
Die Tags der Beiträge dienen als Filterachse des Portfolios — der Objekttyp (Wohnungen, Häuser, Gewerbeimmobilien). Die Filterliste ist nirgends fest codiert: Sie wird aus den Tags tatsächlich veröffentlichter Projekte zusammengestellt, sodass ein neuer Objekttyp keine Codeänderung erfordert.
AJAX-Filterung mit URL-Zustand
Zwei Filter — das Portfolio nach Objekttyp und der Blog nach Artikel-Unterkategorien. Die Handler auf WordPress-Seite liefern fertiges Karten-Markup, der Client tauscht den Inhalt der Liste aus und zeigt für die Dauer der Anfrage einen Loader.
Entscheidend ist, dass der Zustand in der Adressleiste lebt. history.pushState schreibt ?tag=, sodass ein Link auf die Auswahl „Wohnungen" geteilt werden kann und bereits gefiltert öffnet: Beim Kaltstart wird der Parameter aus URLSearchParams gelesen, hebt den passenden Chip hervor und wiederholt die Anfrage. Der Filter ist damit nicht mehr der Zustand eines einzelnen Tabs, sondern adressierbar.
Brücke zwischen AJAX und den Slidern
In jeder nachgeladenen Karte steckt ein eigener Swiper, der nach dem Einfügen ins DOM initialisiert werden muss. Statt ihn direkt aus dem Request-Handler aufzurufen, löst der Code ein eigenes DOM-Event aus, ajaxContentLoaded, auf das das Slider-Modul hört.
Dadurch wissen die Request-Schicht und die Webpack-Modulschicht nichts voneinander: Der Filter importiert kein Swiper, und die Slider wissen nichts von der Existenz des Filters. Einen weiteren Abonnenten für „der Inhalt hat sich aktualisiert" hinzuzufügen, ist eine Zeile — ohne Änderungen am Filter.
Lazy Loading der Vorschaubilder
Ein eigener IntersectionObserver für das Portfolio-Raster: Im src der Karte liegt ein leichtes Thumbnail, das Bild in voller Größe steht in data-src und wird eingesetzt, sobald sich die Karte dem Viewport nähert. Der erste Bildschirm des Katalogs lädt als Thumbnails, die vollen Aufnahmen werden beim Scrollen nachgezogen. Für Browser ohne IntersectionObserver gibt es einen Zweig — dort erfolgt der Austausch sofort.
E-E-A-T im Artikel-Template
Der Autorenblock wird aus dem WordPress-Profil zusammengesetzt: Avatar, Name, Rolle — ein Autorschaftssignal für Suchmaschinen und schlicht eine ehrliche Signatur unter dem Beitrag.
Das FAQ liegt in einem einzigen ACF-Repeater und wird zweifach gerendert: als Akkordeon für die Leserschaft und als JSON-LD FAQPage für die Suchmaschine. Das Markup entsteht über json_encode mit dem Flag JSON_UNESCAPED_UNICODE, damit Kyrillisch nicht in \u-Escape-Sequenzen umgewandelt wird. Die Redaktion füllt die Fragen einmal aus — die strukturierten Daten entstehen von selbst.
Der Block „Lesen Sie auch" wählt Beiträge über gemeinsame Rubriken aus, und der Anrisstext wird per regulärem Ausdruck an einer Satzgrenze abgeschnitten statt nach Zeichenzahl. Eine Kleinigkeit, die aber jene Abbrüche mitten im Wort beseitigt, an denen man eine Baukasten-Website erkennt.
Karten
Zwei Instanzen der Google Maps JS API: eine Präsenzkarte mit Markern in sieben Städten und einem eigenen Zoomlevel für Mobilgeräte sowie eine Bürokarte auf der Kontaktseite. Die Gestaltung ist in eine cloudbasierte mapId ausgelagert, sodass sich das Kartenbild ohne Neubau des Frontends ändern lässt. Die Marker sind AdvancedMarkerElement mit eigenem SVG statt der Standardnadel.
Initialisiert wird nur, wenn der Kartencontainer auf der Seite vorhanden ist; die Bibliotheken werden über Promise.all(importLibrary(...)) nachgeladen — auf Seiten ohne Karte wird Google Maps gar nicht erst geladen.
Die Ergebnisse
sechs statische Seitentypen, ausgebaut zu zwölf WordPress-Templates auf Basis eines gemeinsamen Headers und Footers
sechs statische Seitentypen, ausgebaut zu zwölf WordPress-Templates auf Basis eines gemeinsamen Headers und Footers
AJAX-Filter mit Zustand in der URL — ein Link auf eine Auswahl lässt sich teilen und öffnet fertig