Landingpage design geändert. routes ergänzt für projects, project detail ergänzt
This commit is contained in:
@@ -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' }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
@if (getCoverUrl(post); as coverUrl) {
|
||||
<div class="blog-detail__cover">
|
||||
<img [src]="coverUrl" [alt]="post.title" />
|
||||
<div class="blog-detail__cover-overlay"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -40,17 +41,46 @@
|
||||
|
||||
<h1 class="blog-detail__title">{{ post.title }}</h1>
|
||||
|
||||
<time class="blog-detail__date" [dateTime]="post.published_at">
|
||||
{{ post.published_at | date:'d. MMMM yyyy':'':'de' }}
|
||||
</time>
|
||||
<div class="blog-detail__meta">
|
||||
<time class="blog-detail__date" [dateTime]="post.published_at">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
</svg>
|
||||
{{ post.published_at | date:'d. MMMM yyyy':'':'de' }}
|
||||
</time>
|
||||
<span class="blog-detail__read-time">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
5 Min. Lesezeit
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="blog-detail__summary">{{ post.summary }}</p>
|
||||
</header>
|
||||
|
||||
<hr class="blog-detail__divider" />
|
||||
|
||||
<article class="blog-detail__content" [innerHTML]="parsedContent()"></article>
|
||||
|
||||
<footer class="blog-detail__footer">
|
||||
<div class="blog-detail__share">
|
||||
<span class="blog-detail__share-label">Artikel teilen:</span>
|
||||
<a href="#" class="blog-detail__share-btn" aria-label="Auf Twitter teilen">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" class="blog-detail__share-btn" aria-label="Auf LinkedIn teilen">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
<div class="blog-list__wrapper">
|
||||
|
||||
<header class="blog-list__header">
|
||||
<span class="blog-list__label">Wissen & Insights</span>
|
||||
<h1>Blog</h1>
|
||||
<p class="text-muted">Einblicke, Tipps und Hintergründe rund um Webdesign & digitale Präsenz.</p>
|
||||
<p class="text-muted">Tipps, Hintergründe und Best Practices rund um Webdesign & digitale Präsenz.</p>
|
||||
</header>
|
||||
|
||||
@if (loading()) {
|
||||
@@ -42,8 +43,16 @@
|
||||
@if (getCoverUrl(post); as coverUrl) {
|
||||
<img [src]="coverUrl" [alt]="post.title" loading="lazy" />
|
||||
} @else {
|
||||
<div class="blog-card__image-placeholder"></div>
|
||||
<div class="blog-card__image-placeholder">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M19 20H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h10l6 6v8a2 2 0 0 1-2 2Z"/>
|
||||
<path d="m14 14-3 3-2-2-4 4"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
<div class="blog-card__read-time">
|
||||
<span>5 Min. Lesezeit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blog-card__body">
|
||||
@@ -56,9 +65,17 @@
|
||||
}
|
||||
<h2 class="blog-card__title">{{ post.title }}</h2>
|
||||
<p class="blog-card__summary">{{ post.summary }}</p>
|
||||
<time class="blog-card__date" [dateTime]="post.published_at">
|
||||
{{ post.published_at | date:'d. MMMM yyyy':'':'de' }}
|
||||
</time>
|
||||
<div class="blog-card__footer">
|
||||
<time class="blog-card__date" [dateTime]="post.published_at">
|
||||
{{ post.published_at | date:'d. MMM yyyy':'':'de' }}
|
||||
</time>
|
||||
<span class="blog-card__cta">
|
||||
Weiterlesen
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<section class="features-section" id="features-section">
|
||||
<div class="features-section__wrapper">
|
||||
<div class="features-section__header">
|
||||
<h2>Warum Hurler Webdesign?</h2>
|
||||
<p class="text-muted">Handwerk statt Baukasten – das sind die Unterschiede, die zählen.</p>
|
||||
<span class="features-section__label">Unsere Vorteile</span>
|
||||
<h2>Warum Kunden uns wählen</h2>
|
||||
<p class="text-muted">Kein Baukasten, keine Kompromisse – nur echtes Handwerk für Ihre digitale Präsenz.</p>
|
||||
</div>
|
||||
<div class="features-section__grid">
|
||||
@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 }">
|
||||
|
||||
<span class="features-section__card-number">0{{ feature.id }}</span>
|
||||
|
||||
<div class="features-section__icon-wrap">
|
||||
<ng-icon [name]="feature.icon" class="features-section__icon"></ng-icon>
|
||||
</div>
|
||||
@@ -22,7 +21,10 @@
|
||||
<h3 class="features-section__claim">{{ feature.claim }}</h3>
|
||||
<p class="features-section__description">{{ feature.description }}</p>
|
||||
|
||||
<div class="features-section__bar"></div>
|
||||
<div class="features-section__benefit">
|
||||
<span class="features-section__check">✓</span>
|
||||
{{ feature.benefit }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
</video>
|
||||
</div>
|
||||
<div class="hero-section__wrapper">
|
||||
<div class="hero-section__badge">
|
||||
<span class="hero-section__badge-dot"></span>
|
||||
Jetzt neue Webseite sichern – bis zu 3 Monate kostenloses Hosting
|
||||
</div>
|
||||
<h1 class="hero-section__header">
|
||||
Digitales Handwerk<br />
|
||||
statt Standard-Baukasten
|
||||
Webseiten, die<br />
|
||||
<span class="hero-section__header-accent">Kunden überzeugen</span>
|
||||
</h1>
|
||||
<p class="hero-section__claim">
|
||||
Wir programmieren blitzschnelle, sichere und maßgeschneiderte Webseiten
|
||||
@@ -17,15 +21,26 @@
|
||||
<app-button
|
||||
opTrack="hero_cta_features"
|
||||
[opTrackProps]="{ location: 'hero' }"
|
||||
[item]="{ label: 'Unsere Vorteile', type: 'anchor', target: '#features-section' }"
|
||||
[item]="{ label: 'Vorteile entdecken', type: 'anchor', target: '#features-section' }"
|
||||
variant="primary">
|
||||
</app-button>
|
||||
<app-button
|
||||
opTrack="hero_cta_pricing"
|
||||
[opTrackProps]="{ location: 'hero' }"
|
||||
[item]="{ label: 'Preise & Pakete', type: 'anchor', target: '#pricing' }"
|
||||
[item]="{ label: 'Preise ansehen', type: 'anchor', target: '#pricing' }"
|
||||
variant="outline">
|
||||
</app-button>
|
||||
</div>
|
||||
<div class="hero-section__social-proof">
|
||||
<div class="hero-section__avatars">
|
||||
<div class="hero-section__avatar">MK</div>
|
||||
<div class="hero-section__avatar">AS</div>
|
||||
<div class="hero-section__avatar">JW</div>
|
||||
</div>
|
||||
<div class="hero-section__proof-text">
|
||||
<div class="hero-section__stars">★★★★★</div>
|
||||
<span>Von 50+ Kunden empfohlen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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); }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<section class="pricing" id="pricing">
|
||||
<div class="pricing__wrapper">
|
||||
<div class="pricing__header">
|
||||
<span class="pricing__label">Transparent & Fair</span>
|
||||
<h2>Preise & Pakete</h2>
|
||||
<p class="text-muted">Transparente Preise – kein Abo, kein Versteckspiel.</p>
|
||||
<p class="text-muted">Kein Abo-Modell. Keine versteckten Kosten. Sie besitzen Ihre Webseite.</p>
|
||||
</div>
|
||||
<div class="pricing__grid">
|
||||
@for (tier of tiers; track tier.id) {
|
||||
@@ -35,5 +36,19 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="pricing__trust">
|
||||
<div class="pricing__trust-item">
|
||||
<span class="pricing__trust-icon">🔒</span>
|
||||
<span>30 Tage Geld-zurück-Garantie</span>
|
||||
</div>
|
||||
<div class="pricing__trust-item">
|
||||
<span class="pricing__trust-icon">🏆</span>
|
||||
<span>Persönliche Betreuung inklusive</span>
|
||||
</div>
|
||||
<div class="pricing__trust-item">
|
||||
<span class="pricing__trust-icon">⚡</span>
|
||||
<span>Innerhalb von 4 Wochen fertig</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
<section class="projects" id="projects">
|
||||
<div class="projects__wrapper">
|
||||
<div class="projects__header">
|
||||
<h2>Unsere Projekte</h2>
|
||||
<p class="text-muted">Echte Webseiten für echte Unternehmen – sehen Sie selbst.</p>
|
||||
<span class="projects__label">Erfolgsgeschichten</span>
|
||||
<h2>Projekte, die überzeugen</h2>
|
||||
<p class="text-muted">So helfen wir Unternehmen, online erfolgreich zu sein.</p>
|
||||
</div>
|
||||
<div class="projects__card-container">
|
||||
@for (project of projects; track project.id) {
|
||||
<div
|
||||
@for (project of projects; track project.id; let i = $index) {
|
||||
<a
|
||||
[routerLink]="['/projekt', project.slug]"
|
||||
class="projects__card"
|
||||
[style.--delay]="(i * 100) + 'ms'"
|
||||
opTrack="project_card_click"
|
||||
[opTrackProps]="{ project_id: project.id, company: project.company }">
|
||||
<img [src]="project.image" [alt]="project.company" />
|
||||
<div class="projects__card__overlay">
|
||||
<h3>{{ project.company }}</h3>
|
||||
<p>{{ project.shortDescription }}</p>
|
||||
[opTrackProps]="{ project_id: project.id, company: project.company, slug: project.slug }">
|
||||
<div class="projects__card-image">
|
||||
<img [src]="project.image" [alt]="project.company" loading="lazy" />
|
||||
<div class="projects__card-overlay"></div>
|
||||
</div>
|
||||
<div class="projects__card-content">
|
||||
<span class="projects__card-branch">{{ project.branch }}</span>
|
||||
<h3 class="projects__card-title">{{ project.company }}</h3>
|
||||
<p class="projects__card-description">{{ project.shortDescription }}</p>
|
||||
<div class="projects__card-features">
|
||||
@for (feature of project.features; track $index) {
|
||||
<span class="projects__tag">{{ feature }}</span>
|
||||
}
|
||||
</div>
|
||||
@if (project.result) {
|
||||
<div class="projects__card-result">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
||||
<polyline points="17 6 23 6 23 12"></polyline>
|
||||
</svg>
|
||||
<span>{{ project.result }}</span>
|
||||
</div>
|
||||
}
|
||||
<span class="projects__card-cta">
|
||||
Projekt ansehen
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<section class="stats" id="stats">
|
||||
<div class="stats__wrapper">
|
||||
<div class="stats__header">
|
||||
<h2>Zahlen, die für sich sprechen</h2>
|
||||
<p class="text-muted">Transparent. Messbar. Vertrauenswürdig.</p>
|
||||
</div>
|
||||
<div class="stats__grid">
|
||||
@for (stat of stats; track stat.id; let i = $index) {
|
||||
<div class="stats__card" #statRef>
|
||||
<div class="stats__value">
|
||||
{{ displayedValues()[i] }}{{ stat.suffix }}
|
||||
</div>
|
||||
<div class="stats__label">{{ stat.label }}</div>
|
||||
<div class="stats__description">{{ stat.description }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
95
src/app/features/landing/components/stats/stats.component.ts
Normal file
95
src/app/features/landing/components/stats/stats.component.ts
Normal file
@@ -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<ElementRef<HTMLElement>>;
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
|
||||
displayedValues = signal<number[]>([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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<section class="testimonials" id="testimonials">
|
||||
<div class="testimonials__wrapper">
|
||||
<div class="testimonials__header">
|
||||
<h2>Das sagen unsere Kunden</h2>
|
||||
<p class="text-muted">Echte Ergebnisse für echte Unternehmen.</p>
|
||||
</div>
|
||||
<div class="testimonials__grid">
|
||||
@for (testimonial of testimonials; track testimonial.id; let i = $index) {
|
||||
<div
|
||||
class="testimonials__card"
|
||||
[style.--delay]="(i * 150) + 'ms'"
|
||||
opTrack="testimonial_view"
|
||||
[opTrackProps]="{ testimonial_id: testimonial.id, company: testimonial.company }">
|
||||
|
||||
<div class="testimonials__quote-icon">
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M10 8C6.686 8 4 10.686 4 14v10c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2v-6c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2v6c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2V14c0-3.314-2.686-6-6-6h-6z" fill="currentColor" opacity="0.15"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="testimonials__stars">
|
||||
@for (star of [1,2,3,4,5]; track star) {
|
||||
<span [class.filled]="star <= testimonial.rating">★</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<blockquote class="testimonials__text">
|
||||
"{{ testimonial.quote }}"
|
||||
</blockquote>
|
||||
|
||||
<div class="testimonials__author">
|
||||
<div
|
||||
class="testimonials__avatar"
|
||||
[style.background]="'linear-gradient(135deg, ' + testimonial.gradientFrom + ', ' + testimonial.gradientTo + ')'">
|
||||
{{ testimonial.initials }}
|
||||
</div>
|
||||
<div class="testimonials__author-info">
|
||||
<div class="testimonials__name">{{ testimonial.name }}</div>
|
||||
<div class="testimonials__role">{{ testimonial.role }}, {{ testimonial.company }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
<app-navigation></app-navigation>
|
||||
<app-hero></app-hero>
|
||||
<app-stats></app-stats>
|
||||
<app-features-section></app-features-section>
|
||||
<app-projects></app-projects>
|
||||
<app-testimonials></app-testimonials>
|
||||
<app-pricing></app-pricing>
|
||||
<app-contact></app-contact>
|
||||
<app-footer></app-footer>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
<main class="project-detail">
|
||||
|
||||
@if (project(); as p) {
|
||||
|
||||
<section class="project-detail__hero">
|
||||
<div class="project-detail__hero-image">
|
||||
<img [src]="p.image" [alt]="p.company" />
|
||||
<div class="project-detail__hero-overlay"></div>
|
||||
</div>
|
||||
<div class="project-detail__hero-content">
|
||||
<a routerLink="/" class="project-detail__back">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
Zurück
|
||||
</a>
|
||||
<span class="project-detail__branch">{{ p.branch }}</span>
|
||||
<h1 class="project-detail__title">{{ p.company }}</h1>
|
||||
<p class="project-detail__description">{{ p.description }}</p>
|
||||
@if (p.result) {
|
||||
<div class="project-detail__result">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
|
||||
<polyline points="17 6 23 6 23 12"></polyline>
|
||||
</svg>
|
||||
<span>{{ p.result }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="project-detail__features">
|
||||
<div class="project-detail__wrapper">
|
||||
<h2 class="project-detail__section-title">Leistungen im Projekt</h2>
|
||||
<div class="project-detail__features-grid">
|
||||
@for (feature of p.features; track feature.title; let i = $index) {
|
||||
<div class="project-detail__feature" [style.--delay]="(i * 100) + 'ms'">
|
||||
<div class="project-detail__feature-icon">
|
||||
@switch (feature.icon) {
|
||||
@case ('search') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
}
|
||||
@case ('smartphone') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect>
|
||||
<line x1="12" y1="18" x2="12.01" y2="18"></line>
|
||||
</svg>
|
||||
}
|
||||
@case ('shield') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
||||
</svg>
|
||||
}
|
||||
@case ('target') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<circle cx="12" cy="12" r="6"></circle>
|
||||
<circle cx="12" cy="12" r="2"></circle>
|
||||
</svg>
|
||||
}
|
||||
@case ('calendar') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
</svg>
|
||||
}
|
||||
@case ('award') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="8" r="7"></circle>
|
||||
<polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"></polyline>
|
||||
</svg>
|
||||
}
|
||||
@case ('calendar-check') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
<path d="m9 16 2 2 4-4"></path>
|
||||
</svg>
|
||||
}
|
||||
@case ('list') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="8" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="8" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="8" y1="18" x2="21" y2="18"></line>
|
||||
<line x1="3" y1="6" x2="3.01" y2="6"></line>
|
||||
<line x1="3" y1="12" x2="3.01" y2="12"></line>
|
||||
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
||||
</svg>
|
||||
}
|
||||
@case ('google') {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M12 8v8"></path>
|
||||
<path d="M8 12h8"></path>
|
||||
</svg>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="project-detail__feature-content">
|
||||
<h3>{{ feature.title }}</h3>
|
||||
<p>{{ feature.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (p.testimonial) {
|
||||
<section class="project-detail__testimonial">
|
||||
<div class="project-detail__wrapper project-detail__wrapper--narrow">
|
||||
<blockquote class="project-detail__quote">
|
||||
<svg class="project-detail__quote-icon" width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M10 8c-3.314 0-6 2.686-6 6v10c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2v-6c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2v6c0 1.1.9 2 2 2h2c1.1 0 2-.9 2-2V14c0-3.314-2.686-6-6-6h-6z"/>
|
||||
</svg>
|
||||
<p>"{{ p.testimonial.quote }}"</p>
|
||||
<footer>
|
||||
<cite>
|
||||
<strong>{{ p.testimonial.author }}</strong>
|
||||
<span>{{ p.testimonial.role }}, {{ p.company }}</span>
|
||||
</cite>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section class="project-detail__cta">
|
||||
<div class="project-detail__wrapper">
|
||||
<h2>Erfolgreich online – wie Ihr Projekt</h2>
|
||||
<p>Lassen Sie uns gemeinsam Ihr nächstes Projekt realisieren.</p>
|
||||
<a routerLink="/#contact" class="project-detail__cta-button">
|
||||
Projekt anfragen
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (otherProjects().length > 0) {
|
||||
<section class="project-detail__more">
|
||||
<div class="project-detail__wrapper">
|
||||
<h2 class="project-detail__section-title">Weitere Projekte</h2>
|
||||
<div class="project-detail__more-grid">
|
||||
@for (other of otherProjects(); track other.slug) {
|
||||
<a
|
||||
[routerLink]="['/projekt', other.slug]"
|
||||
class="project-detail__more-card">
|
||||
<div class="project-detail__more-image">
|
||||
<img [src]="other.image" [alt]="other.company" loading="lazy" />
|
||||
</div>
|
||||
<div class="project-detail__more-content">
|
||||
<span class="project-detail__more-branch">{{ other.branch }}</span>
|
||||
<h3>{{ other.company }}</h3>
|
||||
<span class="project-detail__more-cta">
|
||||
Projekt ansehen
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
} @else {
|
||||
<div class="project-detail__not-found">
|
||||
<div class="project-detail__wrapper">
|
||||
<h1>Projekt nicht gefunden</h1>
|
||||
<p>Das gesuchte Projekt existiert leider nicht.</p>
|
||||
<a routerLink="/" class="project-detail__back-link">Zurück zur Startseite</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</main>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Project | null>(null);
|
||||
otherProjects = signal<Project[]>([]);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
1
tsconfig.tsbuildinfo
Normal file
1
tsconfig.tsbuildinfo
Normal file
@@ -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"}
|
||||
Reference in New Issue
Block a user