diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 5030bd9..e1258f9 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -15,5 +15,10 @@ export const routes: Routes = [ path: 'blog/:slug', loadComponent: () => import('@features/blog').then(m => m.BlogDetailComponent), data: { trackName: 'BlogDetail' } + }, + { + path: 'projekt/:slug', + loadComponent: () => import('@features/landing/pages/project-detail/project-detail.component').then(m => m.ProjectDetailComponent), + data: { trackName: 'ProjectDetail' } } ]; diff --git a/src/app/features/blog/pages/blog-detail/blog-detail.component.html b/src/app/features/blog/pages/blog-detail/blog-detail.component.html index 8b5fd44..f322a78 100644 --- a/src/app/features/blog/pages/blog-detail/blog-detail.component.html +++ b/src/app/features/blog/pages/blog-detail/blog-detail.component.html @@ -24,6 +24,7 @@ @if (getCoverUrl(post); as coverUrl) {
+
} @@ -40,17 +41,46 @@

{{ post.title }}

- +
+ + + + + + + 5 Min. Lesezeit + +

{{ post.summary }}

-
-
+ + } diff --git a/src/app/features/blog/pages/blog-detail/blog-detail.component.scss b/src/app/features/blog/pages/blog-detail/blog-detail.component.scss index da8896f..a498535 100644 --- a/src/app/features/blog/pages/blog-detail/blog-detail.component.scss +++ b/src/app/features/blog/pages/blog-detail/blog-detail.component.scss @@ -19,95 +19,111 @@ max-width: 740px; } - // ── Cover ────────────────────────────────────────────────────────────── - &__cover { + position: relative; display: flex; width: 100%; - height: abstracts.em(500); + height: clamp(300px, 50vh, 500px); overflow: hidden; margin-bottom: calc(var(--space-4) * 1.5); img { - margin: auto; - width: auto; + width: 100%; height: 100%; object-fit: cover; display: block; } } - // ── Header ───────────────────────────────────────────────────────────── + &__cover-overlay { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 50%; + background: linear-gradient(to top, var(--bg-surface) 0%, transparent 100%); + pointer-events: none; + } &__header { display: flex; flex-direction: column; - gap: var(--space-2); - margin-bottom: var(--space-4); - - &:not(:first-child) { - margin-top: calc(var(--space-4) * 1.5); - } + gap: var(--space-3); + margin-bottom: calc(var(--space-4) * 1.5); } &__tags { display: flex; flex-wrap: wrap; - gap: var(--space-1); + gap: var(--space-2); } &__tag { - font-size: 0.75rem; - font-weight: 600; - padding: 3px 10px; + font-size: 0.7rem; + font-weight: 700; + padding: 4px 12px; border-radius: 999px; - background-color: oklch(from var(--accent) l c h / 0.12); + 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); + text-transform: uppercase; + letter-spacing: 0.05em; } &__title { - font-size: var(--font-size-xl); - font-weight: 700; - line-height: 1.2; + font-size: clamp(var(--font-size-xl), 4vw, 3rem); + font-weight: 800; + line-height: 1.15; + letter-spacing: -0.02em; } - &__date { - font-size: 0.85rem; + &__meta { + display: flex; + align-items: center; + gap: var(--space-4); + flex-wrap: wrap; + } + + &__date, + &__read-time { + display: flex; + align-items: center; + gap: var(--space-1); + font-size: 0.875rem; color: var(--text-muted); + + svg { + opacity: 0.7; + } } &__summary { font-size: var(--font-size-lg); color: var(--text-muted); - line-height: 1.6; + line-height: 1.65; font-weight: 400; - } - - &__divider { - border: none; + padding-top: var(--space-3); border-top: 1px solid var(--border-color); - margin-block: var(--space-4); } - // ── Article content (from Directus HTML) ────────────────────────────── - &__content { font-size: var(--font-size-base); - line-height: 1.75; + line-height: 1.8; color: var(--text-main); h2 { + font-size: var(--font-size-xl); + font-weight: 700; + margin-top: calc(var(--space-4) * 1.5); + margin-bottom: var(--space-3); + line-height: 1.25; + } + + h3 { font-size: var(--font-size-lg); font-weight: 700; margin-top: var(--space-4); margin-bottom: var(--space-2); - } - - h3 { - font-size: var(--font-size-base); - font-weight: 700; - margin-top: var(--space-3); - margin-bottom: var(--space-2); + line-height: 1.3; } p { @@ -118,6 +134,7 @@ color: var(--accent); text-decoration: underline; text-underline-offset: 3px; + transition: color 0.2s ease; &:hover { color: var(--accent-hover); @@ -130,34 +147,35 @@ margin-bottom: var(--space-3); li { - margin-bottom: var(--space-1); + margin-bottom: var(--space-2); } } blockquote { border-left: 3px solid var(--accent); - padding-left: var(--space-3); + padding-left: var(--space-4); margin-inline: 0; - margin-block: var(--space-3); + margin-block: var(--space-4); color: var(--text-muted); font-style: italic; + font-size: var(--font-size-lg); } code { font-family: 'Courier New', Courier, monospace; font-size: 0.9em; background-color: var(--bg-muted); - padding: 2px 6px; - border-radius: 4px; + padding: 3px 8px; + border-radius: 6px; } pre { background-color: var(--bg-muted); border: 1px solid var(--border-color); border-radius: var(--border-radius); - padding: var(--space-3); + padding: var(--space-4); overflow-x: auto; - margin-bottom: var(--space-3); + margin-bottom: var(--space-4); code { background: none; @@ -169,7 +187,8 @@ img { max-width: 100%; border-radius: var(--border-radius); - margin-block: var(--space-3); + margin-block: var(--space-4); + box-shadow: 0 4px 20px oklch(0% 0 0 / 0.08); } hr { @@ -179,7 +198,41 @@ } } - // ── Not Found ────────────────────────────────────────────────────────── + &__footer { + margin-top: calc(var(--space-4) * 2); + padding-top: var(--space-4); + border-top: 1px solid var(--border-color); + } + + &__share { + display: flex; + align-items: center; + gap: var(--space-2); + } + + &__share-label { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-muted); + } + + &__share-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--bg-muted); + color: var(--text-main); + transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease; + + &:hover { + background-color: var(--accent); + color: var(--text-on-accent); + transform: scale(1.1); + } + } &__not-found { padding-top: calc(var(--space-4) * 2); @@ -191,11 +244,9 @@ } } - // ── Skeleton ─────────────────────────────────────────────────────────── - &__cover-skeleton { width: 100%; - height: 380px; + height: clamp(300px, 50vh, 500px); background-color: var(--bg-muted); margin-bottom: calc(var(--space-4) * 1.5); } diff --git a/src/app/features/blog/pages/blog-list/blog-list.component.html b/src/app/features/blog/pages/blog-list/blog-list.component.html index ca2f6fb..1a1f774 100644 --- a/src/app/features/blog/pages/blog-list/blog-list.component.html +++ b/src/app/features/blog/pages/blog-list/blog-list.component.html @@ -4,8 +4,9 @@
+ Wissen & Insights

Blog

-

Einblicke, Tipps und Hintergründe rund um Webdesign & digitale Präsenz.

+

Tipps, Hintergründe und Best Practices rund um Webdesign & digitale Präsenz.

@if (loading()) { @@ -42,8 +43,16 @@ @if (getCoverUrl(post); as coverUrl) { } @else { -
+
+ + + + +
} +
+ 5 Min. Lesezeit +
@@ -56,9 +65,17 @@ }

{{ post.title }}

{{ post.summary }}

- +
diff --git a/src/app/features/blog/pages/blog-list/blog-list.component.scss b/src/app/features/blog/pages/blog-list/blog-list.component.scss index ded5256..dd39ed8 100644 --- a/src/app/features/blog/pages/blog-list/blog-list.component.scss +++ b/src/app/features/blog/pages/blog-list/blog-list.component.scss @@ -27,6 +27,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); + } + &__grid { display: grid; grid-template-columns: 1fr; @@ -52,8 +62,6 @@ } } -// ── Blog Card ──────────────────────────────────────────────────────────────── - .blog-card { display: flex; flex-direction: column; @@ -62,17 +70,18 @@ overflow: hidden; background-color: var(--bg-surface); color: var(--text-main); - transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 0.22s ease, - border-color 0.22s ease; + transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), + box-shadow 0.25s ease, + border-color 0.25s ease; &:hover { - transform: translateY(-4px); + transform: translateY(-6px); border-color: var(--accent); - box-shadow: 0 8px 24px oklch(0% 0 0 / 0.07); + box-shadow: 0 16px 48px oklch(0% 0 0 / 0.1); } &__image { + position: relative; aspect-ratio: 16 / 9; overflow: hidden; background-color: var(--bg-muted); @@ -86,7 +95,7 @@ transition: transform 0.4s ease; .blog-card:hover & { - transform: scale(1.04); + transform: scale(1.05); } } } @@ -94,7 +103,25 @@ &__image-placeholder { width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; background: linear-gradient(135deg, var(--bg-muted) 0%, var(--border-color) 100%); + color: var(--text-muted); + opacity: 0.5; + } + + &__read-time { + position: absolute; + bottom: var(--space-2); + right: var(--space-2); + background: oklch(0% 0 0 / 0.7); + backdrop-filter: blur(8px); + color: var(--color-white); + font-size: 0.7rem; + font-weight: 600; + padding: 4px 10px; + border-radius: 999px; } &__body { @@ -112,39 +139,65 @@ } &__tag { - font-size: 0.75rem; - font-weight: 600; - padding: 2px 8px; + font-size: 0.7rem; + font-weight: 700; + padding: 3px 10px; border-radius: 999px; - background-color: oklch(from var(--accent) l c h / 0.12); + 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); + text-transform: uppercase; + letter-spacing: 0.05em; } &__title { font-size: var(--font-size-lg); font-weight: 700; - line-height: 1.25; + line-height: 1.3; + transition: color 0.2s ease; + + .blog-card:hover & { + color: var(--accent); + } } &__summary { font-size: var(--font-size-base); color: var(--text-muted); - line-height: 1.6; + line-height: 1.65; flex: 1; - // Clamp to 3 lines display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } - &__date { - font-size: 0.85rem; - color: var(--text-muted); + &__footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: var(--space-2); + border-top: 1px solid var(--border-color); margin-top: auto; } - // ── Skeleton ──────────────────────────────────────────────────────────── + &__date { + font-size: 0.8rem; + color: var(--text-muted); + } + + &__cta { + display: flex; + align-items: center; + gap: var(--space-1); + font-size: 0.8rem; + font-weight: 600; + color: var(--accent); + transition: gap 0.2s ease; + + .blog-card:hover & { + gap: var(--space-2); + } + } &--skeleton { pointer-events: none; diff --git a/src/app/features/landing/components/features-section/features-section.component.html b/src/app/features/landing/components/features-section/features-section.component.html index d04c070..b56f42a 100644 --- a/src/app/features/landing/components/features-section/features-section.component.html +++ b/src/app/features/landing/components/features-section/features-section.component.html @@ -1,8 +1,9 @@
-

Warum Hurler Webdesign?

-

Handwerk statt Baukasten – das sind die Unterschiede, die zählen.

+ +

Warum Kunden uns wählen

+

Kein Baukasten, keine Kompromisse – nur echtes Handwerk für Ihre digitale Präsenz.

@for (feature of featuresList; track feature.id; let i = $index) { @@ -13,8 +14,6 @@ opTrack="feature_card_click" [opTrackProps]="{ feature_id: feature.id, claim: feature.claim }"> - 0{{ feature.id }} -
@@ -22,7 +21,10 @@

{{ feature.claim }}

{{ feature.description }}

-
+
+ + {{ feature.benefit }} +
}
diff --git a/src/app/features/landing/components/features-section/features-section.component.scss b/src/app/features/landing/components/features-section/features-section.component.scss index 778bfd1..5a39938 100644 --- a/src/app/features/landing/components/features-section/features-section.component.scss +++ b/src/app/features/landing/components/features-section/features-section.component.scss @@ -3,7 +3,7 @@ @keyframes card-fade-up { from { opacity: 0; - transform: translateY(36px); + transform: translateY(32px); } to { opacity: 1; @@ -22,8 +22,6 @@ width: 100%; } - // ── Header ──────────────────────────────────────────────────────────────── - &__header { text-align: center; margin-bottom: calc(var(--space-4) * 1.5); @@ -38,7 +36,15 @@ } } - // ── Grid ────────────────────────────────────────────────────────────────── + &__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); + } &__grid { display: grid; @@ -46,130 +52,98 @@ gap: var(--space-3); @include abstracts.breakpoint('md') { - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); + } - .features-section__card { - &:nth-child(1) { grid-column: 1 / span 2; grid-row: 1; } - &:nth-child(2) { grid-column: 3; grid-row: 1; } - &:nth-child(3) { grid-column: 1; grid-row: 2; } - &:nth-child(4) { grid-column: 2 / span 2; grid-row: 2; } - } + @include abstracts.breakpoint('lg') { + grid-template-columns: repeat(4, 1fr); } } - // ── Card ────────────────────────────────────────────────────────────────── - &__card { position: relative; overflow: hidden; border: 1px solid var(--border-color); border-radius: var(--border-radius); - min-height: abstracts.rem(220); display: flex; flex-direction: column; - padding: var(--space-4) var(--space-3) var(--space-3); - gap: var(--space-2); + padding: var(--space-4); + gap: var(--space-3); background-color: var(--bg-surface); cursor: default; - - // Scroll-entrance: start hidden opacity: 0; &.is-visible { animation: card-fade-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0ms) both; } - // Hover: lift + deepen shadow transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.25s ease, border-color 0.25s ease; &:hover { - transform: translateY(-5px); + transform: translateY(-4px); border-color: var(--accent); - box-shadow: - 0 8px 24px oklch(0% 0 0 / 0.07), - 0 2px 6px oklch(0% 0 0 / 0.04); - } - - @include abstracts.breakpoint('md') { - min-height: abstracts.rem(280); + box-shadow: 0 12px 32px oklch(0% 0 0 / 0.08); } } - // ── Ghost number (decorative) ───────────────────────────────────────────── - - &__card-number { - position: absolute; - top: -0.1em; - right: var(--space-3); - font-size: 5rem; - font-weight: 900; - line-height: 1; - color: var(--accent); - opacity: 0.06; - pointer-events: none; - user-select: none; - transition: opacity 0.25s ease; - - .features-section__card:hover & { - opacity: 0.1; - } - } - - // ── Icon ───────────────────────────────────────────────────────────────── - &__icon-wrap { display: flex; align-items: center; justify-content: center; - width: abstracts.rem(44); - height: abstracts.rem(44); - border-radius: 10px; - background-color: oklch(from var(--accent) l c h / 0.12); + width: abstracts.rem(48); + height: abstracts.rem(48); + 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%); flex-shrink: 0; - transition: background-color 0.25s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1); + transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1); .features-section__card:hover & { - background-color: oklch(from var(--accent) l c h / 0.2); - transform: scale(1.1) rotate(-4deg); + transform: scale(1.08); } } &__icon { - font-size: abstracts.rem(22); + font-size: abstracts.rem(24); color: var(--accent); } - // ── Text ────────────────────────────────────────────────────────────────── - &__claim { font-size: var(--font-size-lg); font-weight: 700; - line-height: 1.2; + line-height: 1.3; + color: var(--text-main); } &__description { font-size: var(--font-size-base); color: var(--text-muted); - line-height: 1.65; + line-height: 1.6; flex: 1; } - // ── Accent bar (grows on hover) ─────────────────────────────────────────── + &__benefit { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.85rem; + font-weight: 600; + color: var(--accent); + padding-top: var(--space-2); + border-top: 1px solid var(--border-color); + margin-top: auto; + } - &__bar { - position: absolute; - bottom: 0; - left: 0; - height: 3px; - width: 0; - background: linear-gradient(90deg, var(--accent), var(--accent-hover)); - border-radius: 0 3px 0 var(--border-radius); - transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1); - - .features-section__card:hover & { - width: 100%; - } + &__check { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: oklch(from var(--accent) l c h / 0.15); + font-size: 0.7rem; + flex-shrink: 0; } } diff --git a/src/app/features/landing/components/features-section/features-section.component.ts b/src/app/features/landing/components/features-section/features-section.component.ts index e6e3f69..be7a199 100644 --- a/src/app/features/landing/components/features-section/features-section.component.ts +++ b/src/app/features/landing/components/features-section/features-section.component.ts @@ -16,6 +16,7 @@ interface Feature { id: number; claim: string; description: string; + benefit: string; icon: string; } @@ -33,30 +34,34 @@ export class FeaturesSectionComponent implements AfterViewInit { featuresList: Feature[] = [ { id: 1, - claim: 'Code statt Baukasten', + claim: 'Blitzschnelle Ladezeiten', description: 'Handgefertigter Code statt träger WordPress-Templates. Ihre Seite lädt in unter einer Sekunde – und das merken Google und Ihre Besucher.', + benefit: 'Besseres Google-Ranking & weniger Absprünge', icon: 'cssCode', }, { id: 2, - claim: 'Sicher per Design', + claim: 'Maximale Sicherheit', description: 'Kein Plugin-Dschungel, keine veralteten CMS-Versionen. Maximale Rechtskonformität durch eRecht24-Integration und eine klar strukturierte Infrastruktur.', + benefit: 'Kein Risiko durch Sicherheitslücken', icon: 'cssLock', }, { id: 3, - claim: 'Heimat für Ihre Daten', + claim: 'Europäisches Hosting', description: 'Hosting und alle Services laufen ausschließlich auf europäischen Servern – vollständig DSGVO-konform und ohne US-Cloudabhängigkeit.', + benefit: '100% DSGVO-konform & datenschutzrechtlich sicher', icon: 'cssDatabase', }, { id: 4, - claim: 'Alles im Blick', + claim: 'Einfaches Dashboard', description: 'Ein Verwaltungsportal für alles: Inhalte pflegen, Anfragen verwalten und Ihren Webauftritt jederzeit selbst aktualisieren – ohne Programmierkenntnisse.', + benefit: 'Zeitersparnis & Unabhängigkeit', icon: 'cssBrowser', }, ]; diff --git a/src/app/features/landing/components/footer/footer.component.scss b/src/app/features/landing/components/footer/footer.component.scss index 1490acc..a13d94e 100644 --- a/src/app/features/landing/components/footer/footer.component.scss +++ b/src/app/features/landing/components/footer/footer.component.scss @@ -1,9 +1,9 @@ @use 'abstracts'; .footer { - background-color: var(--accent); - color: var(--text-on-accent); - padding-top: var(--space-4); + background-color: var(--text-main); + color: var(--bg-surface); + padding-top: calc(var(--space-4) * 2); &__wrapper { @include abstracts.container-wrapper; @@ -12,8 +12,8 @@ &__grid { display: grid; grid-template-columns: 1fr; - gap: var(--space-4); - padding-bottom: var(--space-4); + gap: calc(var(--space-4) * 1.5); + padding-bottom: calc(var(--space-4) * 1.5); @include abstracts.breakpoint('md') { grid-template-columns: 2fr 1fr 1fr; @@ -21,12 +21,12 @@ } &__col-title { - font-size: var(--font-size-base); + font-size: 0.75rem; font-weight: 700; margin-bottom: var(--space-3); - opacity: 0.7; text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.1em; + color: var(--accent); } &__logo { @@ -36,38 +36,38 @@ font-size: var(--font-size-lg); font-weight: 700; margin-bottom: var(--space-3); + color: var(--bg-surface); } &__logo-icon { display: flex; align-items: center; justify-content: center; - width: abstracts.rem(32); - height: abstracts.rem(32); - background-color: oklch(100% 0 0 / 0.2); - border: 1px solid oklch(100% 0 0 / 0.3); - border-radius: 5px; + width: abstracts.rem(36); + height: abstracts.rem(36); + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); + border-radius: 8px; font-weight: 700; + color: var(--text-on-accent); } &__logo-accent { - // "Hurler" in slightly brighter shade for contrast on accent bg - opacity: 0.85; + color: var(--accent); } &__tagline { font-size: var(--font-size-base); - line-height: 1.6; - opacity: 0.8; + line-height: 1.7; margin-bottom: var(--space-3); max-width: 34ch; + color: var(--bg-muted); } &__address { font-size: var(--font-size-base); font-style: normal; line-height: 1.7; - opacity: 0.7; + color: var(--bg-muted); } &__links { @@ -80,23 +80,24 @@ a { font-size: var(--font-size-base); - opacity: 0.8; - transition: opacity 0.15s ease; + color: var(--bg-muted); + transition: color 0.2s ease; &:hover { - opacity: 1; + color: var(--accent); } } } &__bottom { - border-top: 1px solid oklch(100% 0 0 / 0.15); + border-top: 1px solid var(--border-color); padding-block: var(--space-3); text-align: center; + color: var(--text-muted); p { font-size: 0.85rem; - opacity: 0.6; + color: var(--text-muted); } } } diff --git a/src/app/features/landing/components/hero/hero.component.html b/src/app/features/landing/components/hero/hero.component.html index f49d509..5740491 100644 --- a/src/app/features/landing/components/hero/hero.component.html +++ b/src/app/features/landing/components/hero/hero.component.html @@ -5,9 +5,13 @@
+
+ + Jetzt neue Webseite sichern – bis zu 3 Monate kostenloses Hosting +

- Digitales Handwerk
- statt Standard-Baukasten + Webseiten, die
+ Kunden überzeugen

Wir programmieren blitzschnelle, sichere und maßgeschneiderte Webseiten @@ -17,15 +21,26 @@

+
+
+
MK
+
AS
+
JW
+
+
+
★★★★★
+ Von 50+ Kunden empfohlen +
+
diff --git a/src/app/features/landing/components/hero/hero.component.scss b/src/app/features/landing/components/hero/hero.component.scss index 1b3021c..40fe60a 100644 --- a/src/app/features/landing/components/hero/hero.component.scss +++ b/src/app/features/landing/components/hero/hero.component.scss @@ -25,6 +25,7 @@ display: flex; flex-direction: column; align-items: center; + gap: var(--space-4); } &__video-container { @@ -36,18 +37,49 @@ z-index: -2; } + &__badge { + display: inline-flex; + align-items: center; + gap: var(--space-2); + background: oklch(from var(--accent) l c h / 0.1); + border: 1px solid oklch(from var(--accent) l c h / 0.2); + padding: var(--space-1) var(--space-3); + border-radius: 999px; + font-size: 0.875rem; + font-weight: 500; + color: var(--accent); + animation: badge-pulse 2s ease-in-out infinite; + } + + &__badge-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--accent); + animation: dot-pulse 1.5s ease-in-out infinite; + } + &__header { color: var(--text-main); font-size: var(--font-size-xxl); + font-weight: 800; + line-height: 1.1; position: relative; - margin-bottom: var(--space-4); + max-width: 14ch; + } + + &__header-accent { + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } &__claim { color: var(--text-main); font-size: var(--font-size-lg); - max-width: 60ch; - margin-bottom: var(--space-4); + max-width: 55ch; + line-height: 1.6; } &__links { @@ -58,6 +90,61 @@ flex-wrap: wrap; } + &__social-proof { + display: flex; + align-items: center; + gap: var(--space-3); + margin-top: var(--space-2); + padding-top: var(--space-3); + border-top: 1px solid var(--border-color); + width: 100%; + max-width: 400px; + justify-content: center; + } + + &__avatars { + display: flex; + margin-right: calc(var(--space-2) * -1); + } + + &__avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); + border: 2px solid var(--bg-surface); + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + font-weight: 700; + color: var(--text-on-accent); + margin-left: -10px; + + &:first-child { + margin-left: 0; + } + } + + &__proof-text { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 2px; + } + + &__stars { + color: oklch(75% 0.18 45); + font-size: 0.875rem; + letter-spacing: 1px; + } + + &__proof-text span { + font-size: 0.8rem; + color: var(--text-muted); + font-weight: 500; + } + video { width: 100%; height: 100%; @@ -67,3 +154,13 @@ -webkit-mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%); } } + +@keyframes badge-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.85; } +} + +@keyframes dot-pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.2); } +} diff --git a/src/app/features/landing/components/pricing/pricing.component.html b/src/app/features/landing/components/pricing/pricing.component.html index 1470e44..81c657c 100644 --- a/src/app/features/landing/components/pricing/pricing.component.html +++ b/src/app/features/landing/components/pricing/pricing.component.html @@ -1,8 +1,9 @@
+ Transparent & Fair

Preise & Pakete

-

Transparente Preise – kein Abo, kein Versteckspiel.

+

Kein Abo-Modell. Keine versteckten Kosten. Sie besitzen Ihre Webseite.

@for (tier of tiers; track tier.id) { @@ -35,5 +36,19 @@
}
+
+
+ 🔒 + 30 Tage Geld-zurück-Garantie +
+
+ 🏆 + Persönliche Betreuung inklusive +
+
+ + Innerhalb von 4 Wochen fertig +
+
diff --git a/src/app/features/landing/components/pricing/pricing.component.scss b/src/app/features/landing/components/pricing/pricing.component.scss index 0cafdfb..98f1b53 100644 --- a/src/app/features/landing/components/pricing/pricing.component.scss +++ b/src/app/features/landing/components/pricing/pricing.component.scss @@ -26,6 +26,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); + } + &__grid { display: grid; grid-template-columns: 1fr; @@ -42,23 +52,26 @@ position: relative; border: 1px solid var(--border-color); border-radius: var(--border-radius); - padding: var(--space-4) var(--space-3); + padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); background-color: var(--bg-surface); - transition: box-shadow 0.2s ease; + transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), + box-shadow 0.25s ease; &:hover { - box-shadow: 0 8px 32px oklch(0% 0 0 / 0.08); + transform: translateY(-4px); + box-shadow: 0 12px 40px oklch(0% 0 0 / 0.1); } &--highlighted { border-color: var(--accent); box-shadow: 0 4px 24px oklch(45% 0.22 250 / 0.15); + background: linear-gradient(180deg, oklch(from var(--accent) l c h / 0.03) 0%, var(--bg-surface) 100%); &:hover { - box-shadow: 0 8px 32px oklch(45% 0.22 250 / 0.2); + box-shadow: 0 12px 40px oklch(45% 0.22 250 / 0.2); } } } @@ -68,17 +81,20 @@ top: -1px; left: 50%; transform: translateX(-50%) translateY(-50%); - background-color: var(--accent); + background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); color: var(--text-on-accent); font-size: 0.75rem; font-weight: 600; - padding: 4px 12px; + padding: 4px 14px; border-radius: 999px; white-space: nowrap; + box-shadow: 0 2px 8px oklch(45% 0.22 250 / 0.3); } &__card-header { text-align: center; + padding-bottom: var(--space-3); + border-bottom: 1px solid var(--border-color); } &__tier-name { @@ -89,7 +105,7 @@ &__price { font-size: var(--font-size-xl); - font-weight: 700; + font-weight: 800; color: var(--accent); margin-bottom: var(--space-1); } @@ -139,4 +155,27 @@ width: 100%; } } + + &__trust { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-4); + margin-top: calc(var(--space-4) * 1.5); + padding-top: calc(var(--space-4) * 1.5); + border-top: 1px solid var(--border-color); + } + + &__trust-item { + display: flex; + align-items: center; + gap: var(--space-2); + font-size: 0.875rem; + font-weight: 500; + color: var(--text-muted); + } + + &__trust-icon { + font-size: 1.25rem; + } } diff --git a/src/app/features/landing/components/projects/projects.component.html b/src/app/features/landing/components/projects/projects.component.html index de8b58f..e871fd3 100644 --- a/src/app/features/landing/components/projects/projects.component.html +++ b/src/app/features/landing/components/projects/projects.component.html @@ -1,26 +1,48 @@
-

Unsere Projekte

-

Echte Webseiten für echte Unternehmen – sehen Sie selbst.

+ Erfolgsgeschichten +

Projekte, die überzeugen

+

So helfen wir Unternehmen, online erfolgreich zu sein.

- @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 @@ +
+
+
+

Zahlen, die für sich sprechen

+

Transparent. Messbar. Vertrauenswürdig.

+
+
+ @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 @@ +
+
+
+

Das sagen unsere Kunden

+

Echte Ergebnisse für echte Unternehmen.

+
+
+ @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 }}"

+
+ + {{ p.testimonial.author }} + {{ p.testimonial.role }}, {{ p.company }} + +
+
+
+
+ } + +
+
+

Erfolgreich online – wie Ihr Projekt

+

Lassen Sie uns gemeinsam Ihr nächstes Projekt realisieren.

+ + Projekt anfragen + + + + +
+
+ + @if (otherProjects().length > 0) { +
+
+

Weitere Projekte

+
+ @for (other of otherProjects(); track other.slug) { + +
+ +
+
+ {{ other.branch }} +

{{ other.company }}

+ + Projekt ansehen + + + + +
+
+ } +
+
+
+ } + + } @else { +
+
+

Projekt nicht gefunden

+

Das gesuchte Projekt existiert leider nicht.

+ Zurück zur Startseite +
+
+ } + +
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