Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
136 lines
2.8 KiB
Plaintext
136 lines
2.8 KiB
Plaintext
---
|
|
import { Icon } from "astro-iconset/components";
|
|
|
|
interface Props {
|
|
node: {
|
|
_key?: string;
|
|
headline?: string;
|
|
subheadline?: string;
|
|
features: Array<{
|
|
icon: string;
|
|
title: string;
|
|
description: string;
|
|
}>;
|
|
};
|
|
}
|
|
|
|
const { node } = Astro.props;
|
|
const { _key, headline, subheadline, features } = node;
|
|
|
|
const iconMap: Record<string, string> = {
|
|
zap: "ph:lightning",
|
|
shield: "ph:shield-check",
|
|
users: "ph:users-three",
|
|
chart: "ph:chart-bar",
|
|
code: "ph:code",
|
|
globe: "ph:globe",
|
|
heart: "ph:heart",
|
|
star: "ph:star",
|
|
check: "ph:check-circle",
|
|
lock: "ph:lock",
|
|
clock: "ph:clock",
|
|
cloud: "ph:cloud",
|
|
};
|
|
---
|
|
|
|
<section class="features section" id={_key}>
|
|
<div class="container">
|
|
{(headline || subheadline) && (
|
|
<header class="features-header">
|
|
{headline && <h2 class="features-headline">{headline}</h2>}
|
|
{subheadline && <p class="features-subheadline">{subheadline}</p>}
|
|
</header>
|
|
)}
|
|
<div class="features-grid">
|
|
{features.map((feature) => (
|
|
<div class="feature-card">
|
|
<div class="feature-icon">
|
|
<Icon name={iconMap[feature.icon] || "ph:sparkle"} aria-hidden="true" />
|
|
</div>
|
|
<h3 class="feature-title">{feature.title}</h3>
|
|
<p class="feature-description">{feature.description}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.features-header {
|
|
text-align: center;
|
|
max-width: var(--max-width);
|
|
margin: 0 auto var(--spacing-4xl);
|
|
}
|
|
|
|
.features-headline {
|
|
font-size: var(--font-size-3xl);
|
|
font-weight: 800;
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.features-subheadline {
|
|
font-size: var(--font-size-lg);
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.features-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: var(--spacing-xl);
|
|
}
|
|
|
|
.feature-card {
|
|
padding: var(--spacing-xl);
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
transition:
|
|
transform var(--transition-base),
|
|
box-shadow var(--transition-base),
|
|
border-color var(--transition-base);
|
|
}
|
|
|
|
.feature-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: var(--shadow-lg);
|
|
border-color: var(--color-primary-light);
|
|
}
|
|
|
|
.feature-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
color: white;
|
|
background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
|
|
border-radius: var(--radius);
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.feature-title {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 700;
|
|
margin-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
.feature-description {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-muted);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.features-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.features-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|