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',
|
path: 'blog/:slug',
|
||||||
loadComponent: () => import('@features/blog').then(m => m.BlogDetailComponent),
|
loadComponent: () => import('@features/blog').then(m => m.BlogDetailComponent),
|
||||||
data: { trackName: 'BlogDetail' }
|
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) {
|
@if (getCoverUrl(post); as coverUrl) {
|
||||||
<div class="blog-detail__cover">
|
<div class="blog-detail__cover">
|
||||||
<img [src]="coverUrl" [alt]="post.title" />
|
<img [src]="coverUrl" [alt]="post.title" />
|
||||||
|
<div class="blog-detail__cover-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,17 +41,46 @@
|
|||||||
|
|
||||||
<h1 class="blog-detail__title">{{ post.title }}</h1>
|
<h1 class="blog-detail__title">{{ post.title }}</h1>
|
||||||
|
|
||||||
<time class="blog-detail__date" [dateTime]="post.published_at">
|
<div class="blog-detail__meta">
|
||||||
{{ post.published_at | date:'d. MMMM yyyy':'':'de' }}
|
<time class="blog-detail__date" [dateTime]="post.published_at">
|
||||||
</time>
|
<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>
|
<p class="blog-detail__summary">{{ post.summary }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<hr class="blog-detail__divider" />
|
|
||||||
|
|
||||||
<article class="blog-detail__content" [innerHTML]="parsedContent()"></article>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,95 +19,111 @@
|
|||||||
max-width: 740px;
|
max-width: 740px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cover ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
&__cover {
|
&__cover {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: abstracts.em(500);
|
height: clamp(300px, 50vh, 500px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: calc(var(--space-4) * 1.5);
|
margin-bottom: calc(var(--space-4) * 1.5);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin: auto;
|
width: 100%;
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
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 {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-2);
|
gap: var(--space-3);
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: calc(var(--space-4) * 1.5);
|
||||||
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-top: calc(var(--space-4) * 1.5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tags {
|
&__tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--space-1);
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tag {
|
&__tag {
|
||||||
font-size: 0.75rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
padding: 3px 10px;
|
padding: 4px 12px;
|
||||||
border-radius: 999px;
|
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);
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: var(--font-size-xl);
|
font-size: clamp(var(--font-size-xl), 4vw, 3rem);
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
line-height: 1.2;
|
line-height: 1.15;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__meta {
|
||||||
font-size: 0.85rem;
|
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);
|
color: var(--text-muted);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__summary {
|
&__summary {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
line-height: 1.6;
|
line-height: 1.65;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
padding-top: var(--space-3);
|
||||||
|
|
||||||
&__divider {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
margin-block: var(--space-4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Article content (from Directus HTML) ──────────────────────────────
|
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
line-height: 1.75;
|
line-height: 1.8;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
|
|
||||||
h2 {
|
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-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-top: var(--space-4);
|
margin-top: var(--space-4);
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
}
|
line-height: 1.3;
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: var(--space-3);
|
|
||||||
margin-bottom: var(--space-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -118,6 +134,7 @@
|
|||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-underline-offset: 3px;
|
text-underline-offset: 3px;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--accent-hover);
|
color: var(--accent-hover);
|
||||||
@@ -130,34 +147,35 @@
|
|||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-3);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin-bottom: var(--space-1);
|
margin-bottom: var(--space-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 3px solid var(--accent);
|
border-left: 3px solid var(--accent);
|
||||||
padding-left: var(--space-3);
|
padding-left: var(--space-4);
|
||||||
margin-inline: 0;
|
margin-inline: 0;
|
||||||
margin-block: var(--space-3);
|
margin-block: var(--space-4);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
font-size: var(--font-size-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
background-color: var(--bg-muted);
|
background-color: var(--bg-muted);
|
||||||
padding: 2px 6px;
|
padding: 3px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
background-color: var(--bg-muted);
|
background-color: var(--bg-muted);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: var(--space-3);
|
padding: var(--space-4);
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-4);
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background: none;
|
background: none;
|
||||||
@@ -169,7 +187,8 @@
|
|||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: var(--border-radius);
|
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 {
|
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 {
|
&__not-found {
|
||||||
padding-top: calc(var(--space-4) * 2);
|
padding-top: calc(var(--space-4) * 2);
|
||||||
@@ -191,11 +244,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Skeleton ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
&__cover-skeleton {
|
&__cover-skeleton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 380px;
|
height: clamp(300px, 50vh, 500px);
|
||||||
background-color: var(--bg-muted);
|
background-color: var(--bg-muted);
|
||||||
margin-bottom: calc(var(--space-4) * 1.5);
|
margin-bottom: calc(var(--space-4) * 1.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
<div class="blog-list__wrapper">
|
<div class="blog-list__wrapper">
|
||||||
|
|
||||||
<header class="blog-list__header">
|
<header class="blog-list__header">
|
||||||
|
<span class="blog-list__label">Wissen & Insights</span>
|
||||||
<h1>Blog</h1>
|
<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>
|
</header>
|
||||||
|
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
@@ -42,8 +43,16 @@
|
|||||||
@if (getCoverUrl(post); as coverUrl) {
|
@if (getCoverUrl(post); as coverUrl) {
|
||||||
<img [src]="coverUrl" [alt]="post.title" loading="lazy" />
|
<img [src]="coverUrl" [alt]="post.title" loading="lazy" />
|
||||||
} @else {
|
} @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>
|
||||||
|
|
||||||
<div class="blog-card__body">
|
<div class="blog-card__body">
|
||||||
@@ -56,9 +65,17 @@
|
|||||||
}
|
}
|
||||||
<h2 class="blog-card__title">{{ post.title }}</h2>
|
<h2 class="blog-card__title">{{ post.title }}</h2>
|
||||||
<p class="blog-card__summary">{{ post.summary }}</p>
|
<p class="blog-card__summary">{{ post.summary }}</p>
|
||||||
<time class="blog-card__date" [dateTime]="post.published_at">
|
<div class="blog-card__footer">
|
||||||
{{ post.published_at | date:'d. MMMM yyyy':'':'de' }}
|
<time class="blog-card__date" [dateTime]="post.published_at">
|
||||||
</time>
|
{{ 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>
|
</div>
|
||||||
|
|
||||||
</a>
|
</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 {
|
&__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -52,8 +62,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Blog Card ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
.blog-card {
|
.blog-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -62,17 +70,18 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--bg-surface);
|
background-color: var(--bg-surface);
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
|
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
|
||||||
box-shadow 0.22s ease,
|
box-shadow 0.25s ease,
|
||||||
border-color 0.22s ease;
|
border-color 0.25s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-6px);
|
||||||
border-color: var(--accent);
|
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 {
|
&__image {
|
||||||
|
position: relative;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--bg-muted);
|
background-color: var(--bg-muted);
|
||||||
@@ -86,7 +95,7 @@
|
|||||||
transition: transform 0.4s ease;
|
transition: transform 0.4s ease;
|
||||||
|
|
||||||
.blog-card:hover & {
|
.blog-card:hover & {
|
||||||
transform: scale(1.04);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +103,25 @@
|
|||||||
&__image-placeholder {
|
&__image-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, var(--bg-muted) 0%, var(--border-color) 100%);
|
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 {
|
&__body {
|
||||||
@@ -112,39 +139,65 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__tag {
|
&__tag {
|
||||||
font-size: 0.75rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
padding: 2px 8px;
|
padding: 3px 10px;
|
||||||
border-radius: 999px;
|
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);
|
color: var(--accent);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.25;
|
line-height: 1.3;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
.blog-card:hover & {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__summary {
|
&__summary {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
line-height: 1.6;
|
line-height: 1.65;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
// Clamp to 3 lines
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__footer {
|
||||||
font-size: 0.85rem;
|
display: flex;
|
||||||
color: var(--text-muted);
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: var(--space-2);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
margin-top: auto;
|
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 {
|
&--skeleton {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<section class="features-section" id="features-section">
|
<section class="features-section" id="features-section">
|
||||||
<div class="features-section__wrapper">
|
<div class="features-section__wrapper">
|
||||||
<div class="features-section__header">
|
<div class="features-section__header">
|
||||||
<h2>Warum Hurler Webdesign?</h2>
|
<span class="features-section__label">Unsere Vorteile</span>
|
||||||
<p class="text-muted">Handwerk statt Baukasten – das sind die Unterschiede, die zählen.</p>
|
<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>
|
||||||
<div class="features-section__grid">
|
<div class="features-section__grid">
|
||||||
@for (feature of featuresList; track feature.id; let i = $index) {
|
@for (feature of featuresList; track feature.id; let i = $index) {
|
||||||
@@ -13,8 +14,6 @@
|
|||||||
opTrack="feature_card_click"
|
opTrack="feature_card_click"
|
||||||
[opTrackProps]="{ feature_id: feature.id, claim: feature.claim }">
|
[opTrackProps]="{ feature_id: feature.id, claim: feature.claim }">
|
||||||
|
|
||||||
<span class="features-section__card-number">0{{ feature.id }}</span>
|
|
||||||
|
|
||||||
<div class="features-section__icon-wrap">
|
<div class="features-section__icon-wrap">
|
||||||
<ng-icon [name]="feature.icon" class="features-section__icon"></ng-icon>
|
<ng-icon [name]="feature.icon" class="features-section__icon"></ng-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,7 +21,10 @@
|
|||||||
<h3 class="features-section__claim">{{ feature.claim }}</h3>
|
<h3 class="features-section__claim">{{ feature.claim }}</h3>
|
||||||
<p class="features-section__description">{{ feature.description }}</p>
|
<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>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
@keyframes card-fade-up {
|
@keyframes card-fade-up {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(36px);
|
transform: translateY(32px);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -22,8 +22,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Header ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: calc(var(--space-4) * 1.5);
|
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 {
|
&__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -46,130 +52,98 @@
|
|||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
|
|
||||||
@include abstracts.breakpoint('md') {
|
@include abstracts.breakpoint('md') {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.features-section__card {
|
@include abstracts.breakpoint('lg') {
|
||||||
&:nth-child(1) { grid-column: 1 / span 2; grid-row: 1; }
|
grid-template-columns: repeat(4, 1fr);
|
||||||
&: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; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Card ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
&__card {
|
&__card {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
min-height: abstracts.rem(220);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: var(--space-4) var(--space-3) var(--space-3);
|
padding: var(--space-4);
|
||||||
gap: var(--space-2);
|
gap: var(--space-3);
|
||||||
background-color: var(--bg-surface);
|
background-color: var(--bg-surface);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
// Scroll-entrance: start hidden
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&.is-visible {
|
&.is-visible {
|
||||||
animation: card-fade-up 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--delay, 0ms) both;
|
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),
|
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
|
||||||
box-shadow 0.25s ease,
|
box-shadow 0.25s ease,
|
||||||
border-color 0.25s ease;
|
border-color 0.25s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-4px);
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow:
|
box-shadow: 0 12px 32px oklch(0% 0 0 / 0.08);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 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 {
|
&__icon-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: abstracts.rem(44);
|
width: abstracts.rem(48);
|
||||||
height: abstracts.rem(44);
|
height: abstracts.rem(48);
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
background-color: oklch(from var(--accent) l c h / 0.12);
|
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;
|
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 & {
|
.features-section__card:hover & {
|
||||||
background-color: oklch(from var(--accent) l c h / 0.2);
|
transform: scale(1.08);
|
||||||
transform: scale(1.1) rotate(-4deg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
font-size: abstracts.rem(22);
|
font-size: abstracts.rem(24);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Text ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
&__claim {
|
&__claim {
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 1.3;
|
||||||
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__description {
|
&__description {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
line-height: 1.65;
|
line-height: 1.6;
|
||||||
flex: 1;
|
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 {
|
&__check {
|
||||||
position: absolute;
|
display: flex;
|
||||||
bottom: 0;
|
align-items: center;
|
||||||
left: 0;
|
justify-content: center;
|
||||||
height: 3px;
|
width: 20px;
|
||||||
width: 0;
|
height: 20px;
|
||||||
background: linear-gradient(90deg, var(--accent), var(--accent-hover));
|
border-radius: 50%;
|
||||||
border-radius: 0 3px 0 var(--border-radius);
|
background-color: oklch(from var(--accent) l c h / 0.15);
|
||||||
transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1);
|
font-size: 0.7rem;
|
||||||
|
flex-shrink: 0;
|
||||||
.features-section__card:hover & {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface Feature {
|
|||||||
id: number;
|
id: number;
|
||||||
claim: string;
|
claim: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
benefit: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,30 +34,34 @@ export class FeaturesSectionComponent implements AfterViewInit {
|
|||||||
featuresList: Feature[] = [
|
featuresList: Feature[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
claim: 'Code statt Baukasten',
|
claim: 'Blitzschnelle Ladezeiten',
|
||||||
description:
|
description:
|
||||||
'Handgefertigter Code statt träger WordPress-Templates. Ihre Seite lädt in unter einer Sekunde – und das merken Google und Ihre Besucher.',
|
'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',
|
icon: 'cssCode',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
claim: 'Sicher per Design',
|
claim: 'Maximale Sicherheit',
|
||||||
description:
|
description:
|
||||||
'Kein Plugin-Dschungel, keine veralteten CMS-Versionen. Maximale Rechtskonformität durch eRecht24-Integration und eine klar strukturierte Infrastruktur.',
|
'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',
|
icon: 'cssLock',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
claim: 'Heimat für Ihre Daten',
|
claim: 'Europäisches Hosting',
|
||||||
description:
|
description:
|
||||||
'Hosting und alle Services laufen ausschließlich auf europäischen Servern – vollständig DSGVO-konform und ohne US-Cloudabhängigkeit.',
|
'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',
|
icon: 'cssDatabase',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
claim: 'Alles im Blick',
|
claim: 'Einfaches Dashboard',
|
||||||
description:
|
description:
|
||||||
'Ein Verwaltungsportal für alles: Inhalte pflegen, Anfragen verwalten und Ihren Webauftritt jederzeit selbst aktualisieren – ohne Programmierkenntnisse.',
|
'Ein Verwaltungsportal für alles: Inhalte pflegen, Anfragen verwalten und Ihren Webauftritt jederzeit selbst aktualisieren – ohne Programmierkenntnisse.',
|
||||||
|
benefit: 'Zeitersparnis & Unabhängigkeit',
|
||||||
icon: 'cssBrowser',
|
icon: 'cssBrowser',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@use 'abstracts';
|
@use 'abstracts';
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
background-color: var(--accent);
|
background-color: var(--text-main);
|
||||||
color: var(--text-on-accent);
|
color: var(--bg-surface);
|
||||||
padding-top: var(--space-4);
|
padding-top: calc(var(--space-4) * 2);
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
@include abstracts.container-wrapper;
|
@include abstracts.container-wrapper;
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
&__grid {
|
&__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: var(--space-4);
|
gap: calc(var(--space-4) * 1.5);
|
||||||
padding-bottom: var(--space-4);
|
padding-bottom: calc(var(--space-4) * 1.5);
|
||||||
|
|
||||||
@include abstracts.breakpoint('md') {
|
@include abstracts.breakpoint('md') {
|
||||||
grid-template-columns: 2fr 1fr 1fr;
|
grid-template-columns: 2fr 1fr 1fr;
|
||||||
@@ -21,12 +21,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__col-title {
|
&__col-title {
|
||||||
font-size: var(--font-size-base);
|
font-size: 0.75rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-3);
|
||||||
opacity: 0.7;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
@@ -36,38 +36,38 @@
|
|||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-3);
|
||||||
|
color: var(--bg-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__logo-icon {
|
&__logo-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: abstracts.rem(32);
|
width: abstracts.rem(36);
|
||||||
height: abstracts.rem(32);
|
height: abstracts.rem(36);
|
||||||
background-color: oklch(100% 0 0 / 0.2);
|
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
|
||||||
border: 1px solid oklch(100% 0 0 / 0.3);
|
border-radius: 8px;
|
||||||
border-radius: 5px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
color: var(--text-on-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__logo-accent {
|
&__logo-accent {
|
||||||
// "Hurler" in slightly brighter shade for contrast on accent bg
|
color: var(--accent);
|
||||||
opacity: 0.85;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tagline {
|
&__tagline {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
opacity: 0.8;
|
|
||||||
margin-bottom: var(--space-3);
|
margin-bottom: var(--space-3);
|
||||||
max-width: 34ch;
|
max-width: 34ch;
|
||||||
|
color: var(--bg-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__address {
|
&__address {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
opacity: 0.7;
|
color: var(--bg-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__links {
|
&__links {
|
||||||
@@ -80,23 +80,24 @@
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
opacity: 0.8;
|
color: var(--bg-muted);
|
||||||
transition: opacity 0.15s ease;
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bottom {
|
&__bottom {
|
||||||
border-top: 1px solid oklch(100% 0 0 / 0.15);
|
border-top: 1px solid var(--border-color);
|
||||||
padding-block: var(--space-3);
|
padding-block: var(--space-3);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
opacity: 0.6;
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-section__wrapper">
|
<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">
|
<h1 class="hero-section__header">
|
||||||
Digitales Handwerk<br />
|
Webseiten, die<br />
|
||||||
statt Standard-Baukasten
|
<span class="hero-section__header-accent">Kunden überzeugen</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="hero-section__claim">
|
<p class="hero-section__claim">
|
||||||
Wir programmieren blitzschnelle, sichere und maßgeschneiderte Webseiten
|
Wir programmieren blitzschnelle, sichere und maßgeschneiderte Webseiten
|
||||||
@@ -17,15 +21,26 @@
|
|||||||
<app-button
|
<app-button
|
||||||
opTrack="hero_cta_features"
|
opTrack="hero_cta_features"
|
||||||
[opTrackProps]="{ location: 'hero' }"
|
[opTrackProps]="{ location: 'hero' }"
|
||||||
[item]="{ label: 'Unsere Vorteile', type: 'anchor', target: '#features-section' }"
|
[item]="{ label: 'Vorteile entdecken', type: 'anchor', target: '#features-section' }"
|
||||||
variant="primary">
|
variant="primary">
|
||||||
</app-button>
|
</app-button>
|
||||||
<app-button
|
<app-button
|
||||||
opTrack="hero_cta_pricing"
|
opTrack="hero_cta_pricing"
|
||||||
[opTrackProps]="{ location: 'hero' }"
|
[opTrackProps]="{ location: 'hero' }"
|
||||||
[item]="{ label: 'Preise & Pakete', type: 'anchor', target: '#pricing' }"
|
[item]="{ label: 'Preise ansehen', type: 'anchor', target: '#pricing' }"
|
||||||
variant="outline">
|
variant="outline">
|
||||||
</app-button>
|
</app-button>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__video-container {
|
&__video-container {
|
||||||
@@ -36,18 +37,49 @@
|
|||||||
z-index: -2;
|
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 {
|
&__header {
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
font-size: var(--font-size-xxl);
|
font-size: var(--font-size-xxl);
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.1;
|
||||||
position: relative;
|
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 {
|
&__claim {
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
max-width: 60ch;
|
max-width: 55ch;
|
||||||
margin-bottom: var(--space-4);
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__links {
|
&__links {
|
||||||
@@ -58,6 +90,61 @@
|
|||||||
flex-wrap: wrap;
|
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 {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -67,3 +154,13 @@
|
|||||||
-webkit-mask-image: linear-gradient(to bottom, black 0%, black 70%, transparent 100%);
|
-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">
|
<section class="pricing" id="pricing">
|
||||||
<div class="pricing__wrapper">
|
<div class="pricing__wrapper">
|
||||||
<div class="pricing__header">
|
<div class="pricing__header">
|
||||||
|
<span class="pricing__label">Transparent & Fair</span>
|
||||||
<h2>Preise & Pakete</h2>
|
<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>
|
||||||
<div class="pricing__grid">
|
<div class="pricing__grid">
|
||||||
@for (tier of tiers; track tier.id) {
|
@for (tier of tiers; track tier.id) {
|
||||||
@@ -35,5 +36,19 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</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>
|
</div>
|
||||||
</section>
|
</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 {
|
&__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -42,23 +52,26 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: var(--space-4) var(--space-3);
|
padding: var(--space-4);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
background-color: var(--bg-surface);
|
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 {
|
&: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 {
|
&--highlighted {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
box-shadow: 0 4px 24px oklch(45% 0.22 250 / 0.15);
|
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 {
|
&: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;
|
top: -1px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%) translateY(-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);
|
color: var(--text-on-accent);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 4px 12px;
|
padding: 4px 14px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
box-shadow: 0 2px 8px oklch(45% 0.22 250 / 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-header {
|
&__card-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
padding-bottom: var(--space-3);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tier-name {
|
&__tier-name {
|
||||||
@@ -89,7 +105,7 @@
|
|||||||
|
|
||||||
&__price {
|
&__price {
|
||||||
font-size: var(--font-size-xl);
|
font-size: var(--font-size-xl);
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
margin-bottom: var(--space-1);
|
margin-bottom: var(--space-1);
|
||||||
}
|
}
|
||||||
@@ -139,4 +155,27 @@
|
|||||||
width: 100%;
|
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">
|
<section class="projects" id="projects">
|
||||||
<div class="projects__wrapper">
|
<div class="projects__wrapper">
|
||||||
<div class="projects__header">
|
<div class="projects__header">
|
||||||
<h2>Unsere Projekte</h2>
|
<span class="projects__label">Erfolgsgeschichten</span>
|
||||||
<p class="text-muted">Echte Webseiten für echte Unternehmen – sehen Sie selbst.</p>
|
<h2>Projekte, die überzeugen</h2>
|
||||||
|
<p class="text-muted">So helfen wir Unternehmen, online erfolgreich zu sein.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="projects__card-container">
|
<div class="projects__card-container">
|
||||||
@for (project of projects; track project.id) {
|
@for (project of projects; track project.id; let i = $index) {
|
||||||
<div
|
<a
|
||||||
|
[routerLink]="['/projekt', project.slug]"
|
||||||
class="projects__card"
|
class="projects__card"
|
||||||
|
[style.--delay]="(i * 100) + 'ms'"
|
||||||
opTrack="project_card_click"
|
opTrack="project_card_click"
|
||||||
[opTrackProps]="{ project_id: project.id, company: project.company }">
|
[opTrackProps]="{ project_id: project.id, company: project.company, slug: project.slug }">
|
||||||
<img [src]="project.image" [alt]="project.company" />
|
<div class="projects__card-image">
|
||||||
<div class="projects__card__overlay">
|
<img [src]="project.image" [alt]="project.company" loading="lazy" />
|
||||||
<h3>{{ project.company }}</h3>
|
<div class="projects__card-overlay"></div>
|
||||||
<p>{{ project.shortDescription }}</p>
|
</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">
|
<div class="projects__card-features">
|
||||||
@for (feature of project.features; track $index) {
|
@for (feature of project.features; track $index) {
|
||||||
<span class="projects__tag">{{ feature }}</span>
|
<span class="projects__tag">{{ feature }}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
@use 'abstracts';
|
@use 'abstracts';
|
||||||
|
|
||||||
|
@keyframes card-fade-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(24px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.projects {
|
.projects {
|
||||||
min-height: 100vh;
|
padding-block: calc(var(--space-4) * 2);
|
||||||
display: flex;
|
background-color: var(--bg-muted);
|
||||||
align-items: center;
|
|
||||||
padding-block: var(--space-4);
|
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
@include abstracts.container-wrapper;
|
@include abstracts.container-wrapper;
|
||||||
@@ -13,7 +22,7 @@
|
|||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: var(--space-4);
|
margin-bottom: calc(var(--space-4) * 1.5);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: var(--font-size-xl);
|
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 {
|
&__card-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -39,12 +58,28 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
aspect-ratio: 4 / 3;
|
background-color: var(--bg-surface);
|
||||||
background: linear-gradient(
|
border: 1px solid var(--border-color);
|
||||||
135deg,
|
cursor: pointer;
|
||||||
oklch(from var(--accent) calc(l + 0.1) calc(c * 0.6) h),
|
text-decoration: none;
|
||||||
oklch(from var(--accent) calc(l - 0.1) c h)
|
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 {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -54,50 +89,94 @@
|
|||||||
transition: transform 0.4s ease;
|
transition: transform 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover img {
|
.projects__card:hover & img {
|
||||||
transform: scale(1.04);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__overlay {
|
&__card-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: flex;
|
background: linear-gradient(
|
||||||
flex-direction: column;
|
to top,
|
||||||
justify-content: flex-end;
|
oklch(0% 0 0 / 0.4) 0%,
|
||||||
padding: var(--space-3);
|
transparent 50%
|
||||||
background: linear-gradient(to top, oklch(0% 0 0 / 0.8) 0%, transparent 60%);
|
);
|
||||||
opacity: 0;
|
pointer-events: none;
|
||||||
transition: opacity 0.3s ease-in-out;
|
}
|
||||||
color: var(--color-white);
|
|
||||||
|
|
||||||
h3 {
|
&__card-content {
|
||||||
font-size: var(--font-size-lg);
|
padding: var(--space-4);
|
||||||
margin-bottom: var(--space-1);
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
&__card-branch {
|
||||||
font-size: var(--font-size-base);
|
font-size: 0.7rem;
|
||||||
margin-bottom: var(--space-2);
|
font-weight: 700;
|
||||||
opacity: 0.85;
|
text-transform: uppercase;
|
||||||
}
|
letter-spacing: 0.1em;
|
||||||
}
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover &__overlay {
|
&__card-title {
|
||||||
opacity: 1;
|
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 {
|
&__card-features {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--space-1);
|
gap: var(--space-1);
|
||||||
|
margin-top: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tag {
|
&__tag {
|
||||||
font-size: 0.75rem;
|
font-size: 0.7rem;
|
||||||
padding: 2px 8px;
|
font-weight: 600;
|
||||||
|
padding: 4px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid oklch(100% 0 0 / 0.4);
|
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(--color-white);
|
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 { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
import { OpenPanelTrackDirective } from '@core/directives/openpanel.directive';
|
import { OpenPanelTrackDirective } from '@core/directives/openpanel.directive';
|
||||||
|
|
||||||
interface Project {
|
interface Project {
|
||||||
id: number;
|
id: number;
|
||||||
|
slug: string;
|
||||||
image: string;
|
image: string;
|
||||||
company: string;
|
company: string;
|
||||||
|
branch: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
features: string[];
|
features: string[];
|
||||||
|
result?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-projects',
|
selector: 'app-projects',
|
||||||
imports: [OpenPanelTrackDirective],
|
imports: [RouterLink, OpenPanelTrackDirective],
|
||||||
templateUrl: './projects.component.html',
|
templateUrl: './projects.component.html',
|
||||||
styleUrl: './projects.component.scss',
|
styleUrl: './projects.component.scss',
|
||||||
})
|
})
|
||||||
@@ -19,24 +23,33 @@ export class ProjectsComponent {
|
|||||||
projects: Project[] = [
|
projects: Project[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
company: 'Schreiner Müller GmbH',
|
slug: 'metzgerei-schlachthof-qualitaet',
|
||||||
image: '/images/schreiner-mueller.jpg',
|
company: 'Metzgerei Schlachthof-Qualität',
|
||||||
shortDescription: 'Handwerkswebsite mit Leistungsübersicht und Kontaktformular',
|
branch: 'Fleischerei & Metzgerei',
|
||||||
features: ['SEO', 'Kontaktformular', 'Dark/Light'],
|
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,
|
id: 2,
|
||||||
company: 'Schützenverein Nördlingen e.V.',
|
slug: 'finanzberatung-vermoegenswert',
|
||||||
image: '/images/schuetzenverein.jpg',
|
company: 'Finanzberatung Vermögenswert',
|
||||||
shortDescription: 'Vereinswebsite mit Terminen und Veranstaltungskalender',
|
branch: 'Finanzdienstleistung',
|
||||||
features: ['SEO', 'Termine', 'Mitglieder'],
|
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,
|
id: 3,
|
||||||
company: 'Bäckerei Huber',
|
slug: 'physiotherapie-beweglich',
|
||||||
image: '/images/baeckerei-huber.jpg',
|
company: 'Physiotherapie Beweglich',
|
||||||
shortDescription: 'Landingpage mit täglich wechselnden Tagesangeboten',
|
branch: 'Gesundheitswesen',
|
||||||
features: ['SEO', 'Angebote', 'Responsive'],
|
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-navigation></app-navigation>
|
||||||
<app-hero></app-hero>
|
<app-hero></app-hero>
|
||||||
|
<app-stats></app-stats>
|
||||||
<app-features-section></app-features-section>
|
<app-features-section></app-features-section>
|
||||||
<app-projects></app-projects>
|
<app-projects></app-projects>
|
||||||
|
<app-testimonials></app-testimonials>
|
||||||
<app-pricing></app-pricing>
|
<app-pricing></app-pricing>
|
||||||
<app-contact></app-contact>
|
<app-contact></app-contact>
|
||||||
<app-footer></app-footer>
|
<app-footer></app-footer>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { NavigationComponent } from '../components/navigation/navigation.component';
|
import { NavigationComponent } from '../components/navigation/navigation.component';
|
||||||
import { HeroComponent } from '../components/hero/hero.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 { 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 { ProjectsComponent } from '../components/projects/projects.component';
|
||||||
import { PricingComponent } from '../components/pricing/pricing.component';
|
import { PricingComponent } from '../components/pricing/pricing.component';
|
||||||
import { ContactComponent } from '../components/contact/contact.component';
|
import { ContactComponent } from '../components/contact/contact.component';
|
||||||
|
import { FooterComponent } from '../components/footer/footer.component';
|
||||||
import { SeoService } from '@core/services/seo.service';
|
import { SeoService } from '@core/services/seo.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,7 +15,9 @@ import { SeoService } from '@core/services/seo.service';
|
|||||||
imports: [
|
imports: [
|
||||||
NavigationComponent,
|
NavigationComponent,
|
||||||
HeroComponent,
|
HeroComponent,
|
||||||
|
StatsComponent,
|
||||||
FeaturesSectionComponent,
|
FeaturesSectionComponent,
|
||||||
|
TestimonialsComponent,
|
||||||
ProjectsComponent,
|
ProjectsComponent,
|
||||||
PricingComponent,
|
PricingComponent,
|
||||||
ContactComponent,
|
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