- @for (project of projects; track project.id) {
-
-
![]()
-
-
{{ project.company }}
-
{{ project.shortDescription }}
+ [opTrackProps]="{ project_id: project.id, company: project.company, slug: project.slug }">
+
+
![]()
+
+
+
+
{{ project.branch }}
+
{{ project.company }}
+
{{ project.shortDescription }}
@for (feature of project.features; track $index) {
{{ feature }}
}
+ @if (project.result) {
+
+
+
{{ project.result }}
+
+ }
+
+ Projekt ansehen
+
+
-
+
}
diff --git a/src/app/features/landing/components/projects/projects.component.scss b/src/app/features/landing/components/projects/projects.component.scss
index b5c9aba..4da8bc9 100644
--- a/src/app/features/landing/components/projects/projects.component.scss
+++ b/src/app/features/landing/components/projects/projects.component.scss
@@ -1,10 +1,19 @@
@use 'abstracts';
+@keyframes card-fade-up {
+ from {
+ opacity: 0;
+ transform: translateY(24px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
.projects {
- min-height: 100vh;
- display: flex;
- align-items: center;
- padding-block: var(--space-4);
+ padding-block: calc(var(--space-4) * 2);
+ background-color: var(--bg-muted);
&__wrapper {
@include abstracts.container-wrapper;
@@ -13,7 +22,7 @@
&__header {
text-align: center;
- margin-bottom: var(--space-4);
+ margin-bottom: calc(var(--space-4) * 1.5);
h2 {
font-size: var(--font-size-xl);
@@ -25,6 +34,16 @@
}
}
+ &__label {
+ display: inline-block;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--accent);
+ margin-bottom: var(--space-2);
+ }
+
&__card-container {
display: grid;
grid-template-columns: 1fr;
@@ -39,12 +58,28 @@
position: relative;
border-radius: var(--border-radius);
overflow: hidden;
- aspect-ratio: 4 / 3;
- background: linear-gradient(
- 135deg,
- oklch(from var(--accent) calc(l + 0.1) calc(c * 0.6) h),
- oklch(from var(--accent) calc(l - 0.1) c h)
- );
+ background-color: var(--bg-surface);
+ border: 1px solid var(--border-color);
+ cursor: pointer;
+ text-decoration: none;
+ color: inherit;
+ display: flex;
+ flex-direction: column;
+ transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
+ box-shadow 0.3s ease;
+
+ animation: card-fade-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0ms) both;
+
+ &:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 20px 50px oklch(0% 0 0 / 0.12);
+ }
+ }
+
+ &__card-image {
+ position: relative;
+ aspect-ratio: 16 / 10;
+ overflow: hidden;
img {
width: 100%;
@@ -54,50 +89,94 @@
transition: transform 0.4s ease;
}
- &:hover img {
- transform: scale(1.04);
+ .projects__card:hover & img {
+ transform: scale(1.05);
}
+ }
- &__overlay {
- position: absolute;
- inset: 0;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- padding: var(--space-3);
- background: linear-gradient(to top, oklch(0% 0 0 / 0.8) 0%, transparent 60%);
- opacity: 0;
- transition: opacity 0.3s ease-in-out;
- color: var(--color-white);
+ &__card-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to top,
+ oklch(0% 0 0 / 0.4) 0%,
+ transparent 50%
+ );
+ pointer-events: none;
+ }
- h3 {
- font-size: var(--font-size-lg);
- margin-bottom: var(--space-1);
- }
+ &__card-content {
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+ }
- p {
- font-size: var(--font-size-base);
- margin-bottom: var(--space-2);
- opacity: 0.85;
- }
- }
+ &__card-branch {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--accent);
+ }
- &:hover &__overlay {
- opacity: 1;
- }
+ &__card-title {
+ font-size: var(--font-size-lg);
+ font-weight: 700;
+ line-height: 1.3;
+ color: var(--text-main);
+ }
+
+ &__card-description {
+ font-size: var(--font-size-base);
+ color: var(--text-muted);
+ line-height: 1.6;
}
&__card-features {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
+ margin-top: var(--space-1);
}
&__tag {
- font-size: 0.75rem;
- padding: 2px 8px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ padding: 4px 10px;
border-radius: 999px;
- border: 1px solid oklch(100% 0 0 / 0.4);
- color: var(--color-white);
+ background: linear-gradient(135deg, oklch(from var(--accent) l c h / 0.12) 0%, oklch(from var(--accent) l c h / 0.08) 100%);
+ color: var(--accent);
+ }
+
+ &__card-result {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ margin-top: var(--space-2);
+ padding-top: var(--space-2);
+ border-top: 1px solid var(--border-color);
+ font-size: 0.875rem;
+ font-weight: 700;
+ color: var(--accent);
+
+ svg {
+ flex-shrink: 0;
+ }
+ }
+
+ &__card-cta {
+ display: flex;
+ align-items: center;
+ gap: var(--space-1);
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--accent);
+ margin-top: var(--space-2);
+ transition: gap 0.2s ease;
+
+ .projects__card:hover & {
+ gap: var(--space-2);
+ }
}
}
diff --git a/src/app/features/landing/components/projects/projects.component.ts b/src/app/features/landing/components/projects/projects.component.ts
index 3a5bce1..a41207f 100644
--- a/src/app/features/landing/components/projects/projects.component.ts
+++ b/src/app/features/landing/components/projects/projects.component.ts
@@ -1,17 +1,21 @@
import { Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
import { OpenPanelTrackDirective } from '@core/directives/openpanel.directive';
interface Project {
id: number;
+ slug: string;
image: string;
company: string;
+ branch: string;
shortDescription: string;
features: string[];
+ result?: string;
}
@Component({
selector: 'app-projects',
- imports: [OpenPanelTrackDirective],
+ imports: [RouterLink, OpenPanelTrackDirective],
templateUrl: './projects.component.html',
styleUrl: './projects.component.scss',
})
@@ -19,24 +23,33 @@ export class ProjectsComponent {
projects: Project[] = [
{
id: 1,
- company: 'Schreiner Müller GmbH',
- image: '/images/schreiner-mueller.jpg',
- shortDescription: 'Handwerkswebsite mit Leistungsübersicht und Kontaktformular',
- features: ['SEO', 'Kontaktformular', 'Dark/Light'],
+ slug: 'metzgerei-schlachthof-qualitaet',
+ company: 'Metzgerei Schlachthof-Qualität',
+ branch: 'Fleischerei & Metzgerei',
+ image: '/images/projekte/metzgerei.jpg',
+ shortDescription: 'Premium-Webauftritt für traditionelle Metzgerei mit Fleischerei in der Region',
+ features: ['SEO-Optimierung', 'Responsive Design', 'DSGVO-konform'],
+ result: '+40% mehr Anfragen über Website',
},
{
id: 2,
- company: 'Schützenverein Nördlingen e.V.',
- image: '/images/schuetzenverein.jpg',
- shortDescription: 'Vereinswebsite mit Terminen und Veranstaltungskalender',
- features: ['SEO', 'Termine', 'Mitglieder'],
+ slug: 'finanzberatung-vermoegenswert',
+ company: 'Finanzberatung Vermögenswert',
+ branch: 'Finanzdienstleistung',
+ image: '/images/projekte/finanzberatung.jpg',
+ shortDescription: 'Vertrauenswürdiger Online-Auftritt für unabhängige Finanzberatung',
+ features: ['Lead-Generierung', 'Terminbuchung', 'Premium-Design'],
+ result: '+60% neue Terminbuchungen',
},
{
id: 3,
- company: 'Bäckerei Huber',
- image: '/images/baeckerei-huber.jpg',
- shortDescription: 'Landingpage mit täglich wechselnden Tagesangeboten',
- features: ['SEO', 'Angebote', 'Responsive'],
+ slug: 'physiotherapie-beweglich',
+ company: 'Physiotherapie Beweglich',
+ branch: 'Gesundheitswesen',
+ image: '/images/projekte/physiotherapie.jpg',
+ shortDescription: 'Moderne Praxis-Website für Physiotherapie und Rehabilitation',
+ features: ['Online-Terminbuchung', 'Leistungen', 'Google-optimiert'],
+ result: 'Top 3 bei Google-Suche',
},
];
}
diff --git a/src/app/features/landing/components/stats/stats.component.html b/src/app/features/landing/components/stats/stats.component.html
new file mode 100644
index 0000000..856c7ce
--- /dev/null
+++ b/src/app/features/landing/components/stats/stats.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+ @for (stat of stats; track stat.id; let i = $index) {
+
+
+ {{ displayedValues()[i] }}{{ stat.suffix }}
+
+
{{ stat.label }}
+
{{ stat.description }}
+
+ }
+
+
+
diff --git a/src/app/features/landing/components/stats/stats.component.scss b/src/app/features/landing/components/stats/stats.component.scss
new file mode 100644
index 0000000..f177a87
--- /dev/null
+++ b/src/app/features/landing/components/stats/stats.component.scss
@@ -0,0 +1,90 @@
+@use 'abstracts';
+
+@keyframes fade-in-up {
+ from {
+ opacity: 0;
+ transform: translateY(24px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.stats {
+ padding-block: calc(var(--space-4) * 2);
+ background: linear-gradient(180deg, var(--bg-surface) 0%, var(--bg-muted) 100%);
+
+ &__wrapper {
+ @include abstracts.container-wrapper;
+ width: 100%;
+ }
+
+ &__header {
+ text-align: center;
+ margin-bottom: calc(var(--space-4) * 1.5);
+
+ h2 {
+ font-size: var(--font-size-xl);
+ margin-bottom: var(--space-2);
+ }
+ }
+
+ &__grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-3);
+
+ @include abstracts.breakpoint('md') {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ }
+
+ &__card {
+ text-align: center;
+ padding: var(--space-4) var(--space-3);
+ border-radius: var(--border-radius);
+ background-color: var(--bg-surface);
+ border: 1px solid var(--border-color);
+ opacity: 0;
+
+ &.is-visible {
+ animation: fade-in-up 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
+ }
+
+ &:nth-child(1).is-visible { animation-delay: 0ms; }
+ &:nth-child(2).is-visible { animation-delay: 100ms; }
+ &:nth-child(3).is-visible { animation-delay: 200ms; }
+ &:nth-child(4).is-visible { animation-delay: 300ms; }
+
+ &:hover {
+ border-color: var(--accent);
+ transform: translateY(-2px);
+ transition: transform 0.2s ease, border-color 0.2s ease;
+ }
+ }
+
+ &__value {
+ font-size: clamp(2.5rem, 5vw + 1rem, 4rem);
+ font-weight: 800;
+ line-height: 1;
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ margin-bottom: var(--space-2);
+ }
+
+ &__label {
+ font-size: var(--font-size-base);
+ font-weight: 700;
+ color: var(--text-main);
+ margin-bottom: var(--space-1);
+ }
+
+ &__description {
+ font-size: 0.8rem;
+ color: var(--text-muted);
+ line-height: 1.4;
+ }
+}
diff --git a/src/app/features/landing/components/stats/stats.component.ts b/src/app/features/landing/components/stats/stats.component.ts
new file mode 100644
index 0000000..a225f2b
--- /dev/null
+++ b/src/app/features/landing/components/stats/stats.component.ts
@@ -0,0 +1,95 @@
+import { Component, AfterViewInit, ElementRef, ViewChildren, QueryList, inject, PLATFORM_ID, signal } from '@angular/core';
+import { isPlatformBrowser } from '@angular/common';
+
+interface Stat {
+ id: number;
+ value: number;
+ suffix: string;
+ label: string;
+ description: string;
+}
+
+@Component({
+ selector: 'app-stats',
+ templateUrl: './stats.component.html',
+ styleUrl: './stats.component.scss',
+})
+export class StatsComponent implements AfterViewInit {
+ @ViewChildren('statRef') statElements!: QueryList
>;
+ private platformId = inject(PLATFORM_ID);
+
+ displayedValues = signal([0, 0, 0, 0]);
+
+ stats: Stat[] = [
+ {
+ id: 1,
+ value: 50,
+ suffix: '+',
+ label: 'Projekte',
+ description: 'Webseiten erfolgreich umgesetzt',
+ },
+ {
+ id: 2,
+ value: 99,
+ suffix: '%',
+ label: 'Kundenzufriedenheit',
+ description: 'Würden uns weiterempfehlen',
+ },
+ {
+ id: 3,
+ value: 7,
+ suffix: '+',
+ label: 'Jahre Erfahrung',
+ description: 'Im Webdesign & Development',
+ },
+ {
+ id: 4,
+ value: 100,
+ suffix: '%',
+ label: 'DSGVO-konform',
+ description: 'European Hosting & Datenschutz',
+ },
+ ];
+
+ ngAfterViewInit(): void {
+ if (!isPlatformBrowser(this.platformId)) return;
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ entry.target.classList.add('is-visible');
+ this.animateValues();
+ observer.unobserve(entry.target);
+ }
+ });
+ },
+ { threshold: 0.3 }
+ );
+
+ this.statElements.forEach((el) => observer.observe(el.nativeElement));
+ }
+
+ private animateValues(): void {
+ const duration = 2000;
+ const steps = 60;
+ const stepDuration = duration / steps;
+
+ this.stats.forEach((stat, index) => {
+ let current = 0;
+ const increment = stat.value / steps;
+ const timer = setInterval(() => {
+ current += increment;
+ if (current >= stat.value) {
+ current = stat.value;
+ clearInterval(timer);
+ }
+ this.displayedValues.update((values) => {
+ const newValues = [...values];
+ newValues[index] = Math.floor(current);
+ return newValues;
+ });
+ }, stepDuration);
+ });
+ }
+}
diff --git a/src/app/features/landing/components/testimonials/testimonials.component.html b/src/app/features/landing/components/testimonials/testimonials.component.html
new file mode 100644
index 0000000..1ae928c
--- /dev/null
+++ b/src/app/features/landing/components/testimonials/testimonials.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+ @for (testimonial of testimonials; track testimonial.id; let i = $index) {
+
+
+
+
+
+ @for (star of [1,2,3,4,5]; track star) {
+ ★
+ }
+
+
+
+ "{{ testimonial.quote }}"
+
+
+
+
+ {{ testimonial.initials }}
+
+
+
{{ testimonial.name }}
+
{{ testimonial.role }}, {{ testimonial.company }}
+
+
+
+ }
+
+
+
diff --git a/src/app/features/landing/components/testimonials/testimonials.component.scss b/src/app/features/landing/components/testimonials/testimonials.component.scss
new file mode 100644
index 0000000..bf407c5
--- /dev/null
+++ b/src/app/features/landing/components/testimonials/testimonials.component.scss
@@ -0,0 +1,128 @@
+@use 'abstracts';
+
+@keyframes card-fade-up {
+ from {
+ opacity: 0;
+ transform: translateY(32px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.testimonials {
+ padding-block: calc(var(--space-4) * 2);
+ background-color: var(--bg-muted);
+
+ &__wrapper {
+ @include abstracts.container-wrapper;
+ width: 100%;
+ }
+
+ &__header {
+ text-align: center;
+ margin-bottom: calc(var(--space-4) * 1.5);
+
+ h2 {
+ font-size: var(--font-size-xl);
+ margin-bottom: var(--space-2);
+ }
+ }
+
+ &__grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-3);
+
+ @include abstracts.breakpoint('md') {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ }
+
+ &__card {
+ position: relative;
+ background-color: var(--bg-surface);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
+ box-shadow 0.25s ease;
+
+ animation: card-fade-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0ms) both;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 32px oklch(0% 0 0 / 0.08);
+ }
+ }
+
+ &__quote-icon {
+ color: var(--accent);
+ opacity: 0.6;
+ position: absolute;
+ top: var(--space-3);
+ right: var(--space-3);
+ }
+
+ &__stars {
+ display: flex;
+ gap: 2px;
+ font-size: 1rem;
+ color: var(--border-color);
+
+ .filled {
+ color: oklch(75% 0.18 45);
+ }
+ }
+
+ &__text {
+ font-size: var(--font-size-base);
+ line-height: 1.7;
+ color: var(--text-main);
+ font-style: normal;
+ flex: 1;
+ margin: 0;
+ }
+
+ &__author {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding-top: var(--space-2);
+ border-top: 1px solid var(--border-color);
+ }
+
+ &__avatar {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.875rem;
+ font-weight: 700;
+ color: var(--text-on-accent);
+ flex-shrink: 0;
+ }
+
+ &__author-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+
+ &__name {
+ font-weight: 700;
+ font-size: var(--font-size-base);
+ color: var(--text-main);
+ }
+
+ &__role {
+ font-size: 0.8rem;
+ color: var(--text-muted);
+ }
+}
diff --git a/src/app/features/landing/components/testimonials/testimonials.component.ts b/src/app/features/landing/components/testimonials/testimonials.component.ts
new file mode 100644
index 0000000..a21b649
--- /dev/null
+++ b/src/app/features/landing/components/testimonials/testimonials.component.ts
@@ -0,0 +1,61 @@
+import { Component } from '@angular/core';
+import { OpenPanelTrackDirective } from '@core/directives/openpanel.directive';
+
+interface Testimonial {
+ id: number;
+ name: string;
+ role: string;
+ company: string;
+ quote: string;
+ rating: number;
+ initials: string;
+ gradientFrom: string;
+ gradientTo: string;
+}
+
+@Component({
+ selector: 'app-testimonials',
+ imports: [OpenPanelTrackDirective],
+ templateUrl: './testimonials.component.html',
+ styleUrl: './testimonials.component.scss',
+})
+export class TestimonialsComponent {
+ testimonials: Testimonial[] = [
+ {
+ id: 1,
+ name: 'Markus Krause',
+ role: 'Geschäftsführer',
+ company: 'Krause Metallbau GmbH',
+ quote:
+ 'Endlich eine Webseite, die nicht aussieht wie jede andere Handwerker-Homepage. Seit dem Relaunch haben wir 40% mehr Anfragen über die Website.',
+ rating: 5,
+ initials: 'MK',
+ gradientFrom: 'oklch(45% 0.22 250)',
+ gradientTo: 'oklch(55% 0.22 250)',
+ },
+ {
+ id: 2,
+ name: 'Anna Schlüter',
+ role: 'Vorstandsvorsitzende',
+ company: 'Turnverein Blau-Weiß 09',
+ quote:
+ 'Der neue Online-Auftritt hat uns geholfen, jüngere Mitglieder anzusprechen. Das Verwaltungsportal spart uns enorm viel Zeit.',
+ rating: 5,
+ initials: 'AS',
+ gradientFrom: 'oklch(70% 0.15 250)',
+ gradientTo: 'oklch(65% 0.18 250)',
+ },
+ {
+ id: 3,
+ name: 'Jan Winkler',
+ role: 'Inhaber',
+ company: 'Winkler IT-Services',
+ quote:
+ 'Professionell, zuverlässig und super Kommunikation. Die Seite lädt rasend schnell und unser Google-Ranking hat sich deutlich verbessert.',
+ rating: 5,
+ initials: 'JW',
+ gradientFrom: 'oklch(55% 0.2 250)',
+ gradientTo: 'oklch(60% 0.22 250)',
+ },
+ ];
+}
diff --git a/src/app/features/landing/pages/landingpage.component.html b/src/app/features/landing/pages/landingpage.component.html
index d1a2dfa..b2285b4 100644
--- a/src/app/features/landing/pages/landingpage.component.html
+++ b/src/app/features/landing/pages/landingpage.component.html
@@ -1,7 +1,9 @@
+
+
diff --git a/src/app/features/landing/pages/landingpage.component.ts b/src/app/features/landing/pages/landingpage.component.ts
index 8fff1ce..4780778 100644
--- a/src/app/features/landing/pages/landingpage.component.ts
+++ b/src/app/features/landing/pages/landingpage.component.ts
@@ -1,11 +1,13 @@
import { Component, OnInit, inject } from '@angular/core';
import { NavigationComponent } from '../components/navigation/navigation.component';
import { HeroComponent } from '../components/hero/hero.component';
+import { StatsComponent } from '../components/stats/stats.component';
import { FeaturesSectionComponent } from '../components/features-section/features-section.component';
-import { FooterComponent } from '../components/footer/footer.component';
+import { TestimonialsComponent } from '../components/testimonials/testimonials.component';
import { ProjectsComponent } from '../components/projects/projects.component';
import { PricingComponent } from '../components/pricing/pricing.component';
import { ContactComponent } from '../components/contact/contact.component';
+import { FooterComponent } from '../components/footer/footer.component';
import { SeoService } from '@core/services/seo.service';
@Component({
@@ -13,7 +15,9 @@ import { SeoService } from '@core/services/seo.service';
imports: [
NavigationComponent,
HeroComponent,
+ StatsComponent,
FeaturesSectionComponent,
+ TestimonialsComponent,
ProjectsComponent,
PricingComponent,
ContactComponent,
diff --git a/src/app/features/landing/pages/project-detail/project-detail.component.html b/src/app/features/landing/pages/project-detail/project-detail.component.html
new file mode 100644
index 0000000..ef60804
--- /dev/null
+++ b/src/app/features/landing/pages/project-detail/project-detail.component.html
@@ -0,0 +1,187 @@
+
+
+ @if (project(); as p) {
+
+
+
+
![]()
+
+
+
+
+
+ Zurück
+
+
{{ p.branch }}
+
{{ p.company }}
+
{{ p.description }}
+ @if (p.result) {
+
+
+
{{ p.result }}
+
+ }
+
+
+
+
+
+
Leistungen im Projekt
+
+ @for (feature of p.features; track feature.title; let i = $index) {
+
+
+ @switch (feature.icon) {
+ @case ('search') {
+
+ }
+ @case ('smartphone') {
+
+ }
+ @case ('shield') {
+
+ }
+ @case ('target') {
+
+ }
+ @case ('calendar') {
+
+ }
+ @case ('award') {
+
+ }
+ @case ('calendar-check') {
+
+ }
+ @case ('list') {
+
+ }
+ @case ('google') {
+
+ }
+ }
+
+
+
{{ feature.title }}
+
{{ feature.description }}
+
+
+ }
+
+
+
+
+ @if (p.testimonial) {
+
+
+
+
+ "{{ p.testimonial.quote }}"
+
+
+
+
+ }
+
+
+
+
Erfolgreich online – wie Ihr Projekt
+
Lassen Sie uns gemeinsam Ihr nächstes Projekt realisieren.
+
+ Projekt anfragen
+
+
+
+
+
+ @if (otherProjects().length > 0) {
+
+ }
+
+ } @else {
+
+ }
+
+
diff --git a/src/app/features/landing/pages/project-detail/project-detail.component.scss b/src/app/features/landing/pages/project-detail/project-detail.component.scss
new file mode 100644
index 0000000..529fac9
--- /dev/null
+++ b/src/app/features/landing/pages/project-detail/project-detail.component.scss
@@ -0,0 +1,391 @@
+@use 'abstracts';
+
+@keyframes fade-in-up {
+ from {
+ opacity: 0;
+ transform: translateY(24px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.project-detail {
+ min-height: calc(100vh - var(--nav-height));
+
+ &__wrapper {
+ @include abstracts.container-wrapper;
+ width: 100%;
+
+ &--narrow {
+ max-width: 740px;
+ }
+ }
+
+ // ── Hero ─────────────────────────────────────────────────────────────────────
+
+ &__hero {
+ position: relative;
+ }
+
+ &__hero-image {
+ position: relative;
+ width: 100%;
+ height: clamp(400px, 60vh, 600px);
+ overflow: hidden;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ &__hero-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to top,
+ oklch(from var(--bg-surface) l c h) 0%,
+ oklch(from var(--bg-surface) l c h / 0.8) 30%,
+ oklch(from var(--bg-surface) l c h / 0.4) 60%,
+ transparent 100%
+ );
+ }
+
+ &__hero-content {
+ position: relative;
+ margin-top: -200px;
+ padding-bottom: calc(var(--space-4) * 2);
+ @include abstracts.container-wrapper;
+ max-width: 800px;
+ animation: fade-in-up 0.6s ease-out both;
+ }
+
+ &__back {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--text-muted);
+ margin-bottom: var(--space-4);
+ transition: color 0.2s ease;
+
+ &:hover {
+ color: var(--accent);
+ }
+ }
+
+ &__branch {
+ display: inline-block;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--accent);
+ margin-bottom: var(--space-2);
+ }
+
+ &__title {
+ font-size: clamp(var(--font-size-xl), 5vw, 3rem);
+ font-weight: 800;
+ line-height: 1.1;
+ letter-spacing: -0.02em;
+ margin-bottom: var(--space-3);
+ }
+
+ &__description {
+ font-size: var(--font-size-lg);
+ color: var(--text-muted);
+ line-height: 1.7;
+ margin-bottom: var(--space-4);
+ }
+
+ &__result {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-3);
+ background: linear-gradient(135deg, oklch(from var(--accent) l c h / 0.15) 0%, oklch(from var(--accent) l c h / 0.08) 100%);
+ border: 1px solid oklch(from var(--accent) l c h / 0.2);
+ border-radius: 999px;
+ font-size: 0.875rem;
+ font-weight: 700;
+ color: var(--accent);
+
+ svg {
+ flex-shrink: 0;
+ }
+ }
+
+ // ── Features ─────────────────────────────────────────────────────────────────
+
+ &__features {
+ padding-block: calc(var(--space-4) * 2);
+ background-color: var(--bg-muted);
+ }
+
+ &__section-title {
+ font-size: var(--font-size-xl);
+ font-weight: 700;
+ margin-bottom: calc(var(--space-4) * 1.5);
+ text-align: center;
+ }
+
+ &__features-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-3);
+
+ @include abstracts.breakpoint('md') {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ }
+
+ &__feature {
+ background-color: var(--bg-surface);
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
+ box-shadow 0.25s ease;
+
+ animation: fade-in-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0ms) both;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 32px oklch(0% 0 0 / 0.08);
+ }
+ }
+
+ &__feature-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ background: linear-gradient(135deg, oklch(from var(--accent) l c h / 0.15) 0%, oklch(from var(--accent) l c h / 0.08) 100%);
+ color: var(--accent);
+ flex-shrink: 0;
+ }
+
+ &__feature-content {
+ h3 {
+ font-size: var(--font-size-base);
+ font-weight: 700;
+ margin-bottom: var(--space-1);
+ }
+
+ p {
+ font-size: var(--font-size-base);
+ color: var(--text-muted);
+ line-height: 1.6;
+ }
+ }
+
+ // ── Testimonial ──────────────────────────────────────────────────────────────
+
+ &__testimonial {
+ padding-block: calc(var(--space-4) * 2);
+ background-color: var(--bg-surface);
+ }
+
+ &__quote {
+ position: relative;
+ text-align: center;
+ padding: var(--space-4);
+ }
+
+ &__quote-icon {
+ color: var(--accent);
+ opacity: 0.15;
+ margin-bottom: var(--space-3);
+ }
+
+ &__quote p {
+ font-size: var(--font-size-xl);
+ font-weight: 500;
+ line-height: 1.5;
+ color: var(--text-main);
+ margin-bottom: var(--space-4);
+ font-style: italic;
+ }
+
+ &__quote footer cite {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ font-style: normal;
+
+ strong {
+ font-weight: 700;
+ color: var(--text-main);
+ }
+
+ span {
+ font-size: 0.875rem;
+ color: var(--text-muted);
+ }
+ }
+
+ // ── CTA ─────────────────────────────────────────────────────────────────────
+
+ &__cta {
+ padding-block: calc(var(--space-4) * 2);
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
+ text-align: center;
+
+ h2 {
+ font-size: var(--font-size-xl);
+ font-weight: 700;
+ color: var(--text-on-accent);
+ margin-bottom: var(--space-2);
+ }
+
+ p {
+ font-size: var(--font-size-lg);
+ color: oklch(from var(--text-on-accent) l c h / 0.85);
+ margin-bottom: var(--space-4);
+ }
+ }
+
+ &__cta-button {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-4);
+ background-color: var(--bg-surface);
+ color: var(--accent);
+ font-weight: 700;
+ font-size: var(--font-size-base);
+ border-radius: 999px;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+
+ &:hover {
+ transform: scale(1.05);
+ box-shadow: 0 8px 24px oklch(0% 0 0 / 0.2);
+ }
+ }
+
+ // ── More Projects ────────────────────────────────────────────────────────────
+
+ &__more {
+ padding-block: calc(var(--space-4) * 2);
+ }
+
+ &__more-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-3);
+
+ @include abstracts.breakpoint('md') {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ }
+
+ &__more-card {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ overflow: hidden;
+ background-color: var(--bg-surface);
+ transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
+ box-shadow 0.25s ease;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 32px oklch(0% 0 0 / 0.08);
+ }
+ }
+
+ &__more-image {
+ aspect-ratio: 16 / 9;
+ overflow: hidden;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 0.4s ease;
+
+ .project-detail__more-card:hover & {
+ transform: scale(1.05);
+ }
+ }
+ }
+
+ &__more-content {
+ padding: var(--space-4);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-1);
+
+ h3 {
+ font-size: var(--font-size-lg);
+ font-weight: 700;
+ }
+ }
+
+ &__more-branch {
+ font-size: 0.7rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--accent);
+ }
+
+ &__more-cta {
+ display: flex;
+ align-items: center;
+ gap: var(--space-1);
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--accent);
+ margin-top: var(--space-2);
+ transition: gap 0.2s ease;
+
+ .project-detail__more-card:hover & {
+ gap: var(--space-2);
+ }
+ }
+
+ // ── Not Found ────────────────────────────────────────────────────────────────
+
+ &__not-found {
+ padding-block: calc(var(--space-4) * 3);
+ text-align: center;
+
+ h1 {
+ font-size: var(--font-size-xl);
+ margin-bottom: var(--space-2);
+ }
+
+ p {
+ color: var(--text-muted);
+ margin-bottom: var(--space-4);
+ }
+ }
+
+ &__back-link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-2);
+ padding: var(--space-2) var(--space-4);
+ background-color: var(--accent);
+ color: var(--text-on-accent);
+ font-weight: 600;
+ border-radius: 999px;
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ background-color: var(--accent-hover);
+ }
+ }
+}
diff --git a/src/app/features/landing/pages/project-detail/project-detail.component.ts b/src/app/features/landing/pages/project-detail/project-detail.component.ts
new file mode 100644
index 0000000..0efa91d
--- /dev/null
+++ b/src/app/features/landing/pages/project-detail/project-detail.component.ts
@@ -0,0 +1,172 @@
+import { Component, OnInit, inject, signal } from '@angular/core';
+import { ActivatedRoute, RouterLink } from '@angular/router';
+import { SeoService } from '@core/services/seo.service';
+
+interface ProjectFeature {
+ icon: string;
+ title: string;
+ description: string;
+}
+
+interface Project {
+ slug: string;
+ company: string;
+ branch: string;
+ image: string;
+ galleryImages: string[];
+ shortDescription: string;
+ description: string;
+ features: ProjectFeature[];
+ result?: string;
+ testimonial?: {
+ quote: string;
+ author: string;
+ role: string;
+ };
+}
+
+const PROJECTS: Project[] = [
+ {
+ slug: 'metzgerei-schlachthof-qualitaet',
+ company: 'Metzgerei Schlachthof-Qualität',
+ branch: 'Fleischerei & Metzgerei',
+ image: '/images/projekte/metzgerei.jpg',
+ galleryImages: [
+ '/images/projekte/metzgerei.jpg',
+ '/images/projekte/metzgerei-detail1.jpg',
+ '/images/projekte/metzgerei-detail2.jpg'
+ ],
+ shortDescription: 'Premium-Webauftritt für traditionelle Metzgerei mit Fleischerei in der Region',
+ description: 'Eine traditionelle Metzgerei mit jahrzehntelanger Erfahrung wollte ihre handwerkliche Qualität auch online sichtbar machen. Wir haben einen Webauftritt geschaffen, der die Wertigkeit ihrer Produkte widerspiegelt – modern, aber mit einem Hauch von Tradition.',
+ features: [
+ {
+ icon: 'search',
+ title: 'SEO-Optimierung',
+ description: 'Auffindbar bei Google für regionale Suchbegriffe wie "Metzgerei in der Region" und "Frisches Fleisch online".'
+ },
+ {
+ icon: 'smartphone',
+ title: 'Responsive Design',
+ description: 'Perfekte Darstellung auf allen Geräten – vom Smartphone beim Einkauf bis zum Desktop.'
+ },
+ {
+ icon: 'shield',
+ title: 'DSGVO-konform',
+ description: 'Vollständig rechtssicher mit Cookie-Banner, Impressum und Datenschutzerklärung nach aktuellen Standards.'
+ }
+ ],
+ result: '+40% mehr Anfragen über Website',
+ testimonial: {
+ quote: 'Endlich eine Webseite, die unsere handwerkliche Qualität widerspiegelt. Die Zusammenarbeit war professionell und unkompliziert.',
+ author: 'Thomas B.',
+ role: 'Inhaber'
+ }
+ },
+ {
+ slug: 'finanzberatung-vermoegenswert',
+ company: 'Finanzberatung Vermögenswert',
+ branch: 'Finanzdienstleistung',
+ image: '/images/projekte/finanzberatung.jpg',
+ galleryImages: [
+ '/images/projekte/finanzberatung.jpg',
+ '/images/projekte/finanzberatung-detail1.jpg',
+ '/images/projekte/finanzberatung-detail2.jpg'
+ ],
+ shortDescription: 'Vertrauenswürdiger Online-Auftritt für unabhängige Finanzberatung',
+ description: 'Als unabhängige Finanzberatung ist Vertrauen das wichtigste Gut. Wir haben eine Website entwickelt, die Kompetenz und Seriosität vermittelt, ohne dabei steif oder unpersönlich zu wirken.',
+ features: [
+ {
+ icon: 'target',
+ title: 'Lead-Generierung',
+ description: 'Strategisch platzierte Call-to-Actions und ein optimiertes Kontaktformular für qualifizierte Anfragen.'
+ },
+ {
+ icon: 'calendar',
+ title: 'Terminbuchung',
+ description: 'Online-Terminvereinbarung direkt über die Website – rund um die Uhr, ohne telefonische Hürden.'
+ },
+ {
+ icon: 'award',
+ title: 'Premium-Design',
+ description: 'Hochwertige Optik, die das Qualitätsversprechen der Beratung visuell unterstreicht.'
+ }
+ ],
+ result: '+60% neue Terminbuchungen',
+ testimonial: {
+ quote: 'Unsere neuen Kunden sagen oft, dass sie uns wegen der professionellen Website kontaktiert haben. Das zeigt, wie wichtig ein guter erster Eindruck ist.',
+ author: 'Michael S.',
+ role: 'Geschäftsführer'
+ }
+ },
+ {
+ slug: 'physiotherapie-beweglich',
+ company: 'Physiotherapie Beweglich',
+ branch: 'Gesundheitswesen',
+ image: '/images/projekte/physiotherapie.jpg',
+ galleryImages: [
+ '/images/projekte/physiotherapie.jpg',
+ '/images/projekte/physiotherapie-detail1.jpg',
+ '/images/projekte/physiotherapie-detail2.jpg'
+ ],
+ shortDescription: 'Moderne Praxis-Website für Physiotherapie und Rehabilitation',
+ description: 'Eine modern aufgestellte Physiotherapie-Praxis mit focus auf ganzheitliche Behandlungsmethoden. Die Website sollte Patienten dabei helfen, die richtige Behandlung zu finden und einfach einen Termin zu buchen.',
+ features: [
+ {
+ icon: 'calendar-check',
+ title: 'Online-Terminbuchung',
+ description: 'Intuitives Buchungssystem mit Kalenderansicht und automatischen Erinnerungen per E-Mail.'
+ },
+ {
+ icon: 'list',
+ title: 'Leistungsübersicht',
+ description: 'Übersichtliche Darstellung aller Behandlungsangebote mit detaillierten Beschreibungen.'
+ },
+ {
+ icon: 'google',
+ title: 'Google-optimiert',
+ description: 'Lokale SEO-Strategie für Top-Platzierungen bei Suchbegriffen wie "Physiotherapie" in der Umgebung.'
+ }
+ ],
+ result: 'Top 3 bei Google-Suche',
+ testimonial: {
+ quote: 'Wir werden regelmäßig für unsere moderne Website gelobt. Viele Patienten buchen sogar direkt online – das spart uns Zeit.',
+ author: 'Sarah M.',
+ role: 'Praxisinhaberin'
+ }
+ }
+];
+
+@Component({
+ selector: 'app-project-detail',
+ imports: [RouterLink],
+ templateUrl: './project-detail.component.html',
+ styleUrl: './project-detail.component.scss',
+})
+export class ProjectDetailComponent implements OnInit {
+ private route = inject(ActivatedRoute);
+ private seo = inject(SeoService);
+
+ project = signal(null);
+ otherProjects = signal([]);
+
+ ngOnInit(): void {
+ const slug = this.route.snapshot.paramMap.get('slug');
+ const project = PROJECTS.find(p => p.slug === slug) || null;
+
+ this.project.set(project);
+ this.otherProjects.set(PROJECTS.filter(p => p.slug !== slug));
+
+ if (project) {
+ this.seo.updateMetadata({
+ title: `${project.company} | Hurler Webdesign`,
+ description: project.shortDescription,
+ socialsDescription: `Erfahren Sie, wie wir ${project.company} zu mehr Erfolg im Internet verholfen haben.`,
+ type: 'website'
+ });
+ }
+ }
+
+ getProjectBySlug(slug: string): Project | undefined {
+ return PROJECTS.find(p => p.slug === slug);
+ }
+}
diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo
new file mode 100644
index 0000000..a31caf3
--- /dev/null
+++ b/tsconfig.tsbuildinfo
@@ -0,0 +1 @@
+{"fileNames":[],"fileInfos":[],"root":[],"options":{"experimentalDecorators":true,"importHelpers":true,"module":200,"noFallthroughCasesInSwitch":true,"noImplicitOverride":true,"noImplicitReturns":true,"noPropertyAccessFromIndexSignature":true,"skipLibCheck":true,"strict":true,"target":9},"version":"5.9.3"}
\ No newline at end of file