first commit
This commit is contained in:
33
templates/marketing/src/components/MarketingBlocks.astro
Normal file
33
templates/marketing/src/components/MarketingBlocks.astro
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
/**
|
||||
* Custom Portable Text renderer for marketing blocks.
|
||||
*
|
||||
* This component maps custom block types (marketing.hero, marketing.features, etc.)
|
||||
* to their corresponding Astro components. Pass it to the PortableText component
|
||||
* via the `components` prop.
|
||||
*/
|
||||
import type { PortableTextBlock } from "emdash";
|
||||
import { PortableText } from "emdash/ui";
|
||||
import Hero from "./blocks/Hero.astro";
|
||||
import Features from "./blocks/Features.astro";
|
||||
import Testimonials from "./blocks/Testimonials.astro";
|
||||
import Pricing from "./blocks/Pricing.astro";
|
||||
import FAQ from "./blocks/FAQ.astro";
|
||||
|
||||
interface Props {
|
||||
value: PortableTextBlock[];
|
||||
}
|
||||
|
||||
const { value } = Astro.props;
|
||||
|
||||
// Custom block type components
|
||||
const marketingTypes = {
|
||||
"marketing.hero": Hero,
|
||||
"marketing.features": Features,
|
||||
"marketing.testimonials": Testimonials,
|
||||
"marketing.pricing": Pricing,
|
||||
"marketing.faq": FAQ,
|
||||
};
|
||||
---
|
||||
|
||||
<PortableText value={value} components={{ type: marketingTypes }} />
|
||||
114
templates/marketing/src/components/blocks/FAQ.astro
Normal file
114
templates/marketing/src/components/blocks/FAQ.astro
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
interface Props {
|
||||
node: {
|
||||
_key?: string;
|
||||
headline?: string;
|
||||
items: Array<{
|
||||
question: string;
|
||||
answer: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { _key, headline, items } = node;
|
||||
---
|
||||
|
||||
<section class="faq section" id={_key}>
|
||||
<div class="container">
|
||||
{headline && (
|
||||
<header class="faq-header">
|
||||
<h2 class="faq-headline">{headline}</h2>
|
||||
</header>
|
||||
)}
|
||||
<div class="faq-list">
|
||||
{items.map((item) => (
|
||||
<details class="faq-item" name="faq">
|
||||
<summary class="faq-question">
|
||||
<span>{item.question}</span>
|
||||
<svg class="faq-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="faq-answer">
|
||||
<p>{item.answer}</p>
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.faq-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-4xl);
|
||||
}
|
||||
|
||||
.faq-headline {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.faq-item:hover {
|
||||
border-color: var(--color-muted);
|
||||
}
|
||||
|
||||
.faq-item[open] {
|
||||
border-color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-lg);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.faq-question::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-muted);
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
|
||||
.faq-item[open] .faq-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
padding: 0 var(--spacing-lg) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.faq-answer p {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
134
templates/marketing/src/components/blocks/Features.astro
Normal file
134
templates/marketing/src/components/blocks/Features.astro
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
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;
|
||||
|
||||
// Map feature icon names to Phosphor icon names
|
||||
const iconMap: Record<string, string> = {
|
||||
zap: "lightning",
|
||||
shield: "shield-check",
|
||||
users: "users-three",
|
||||
chart: "chart-bar",
|
||||
code: "code",
|
||||
globe: "globe",
|
||||
heart: "heart",
|
||||
star: "star",
|
||||
check: "check-circle",
|
||||
lock: "lock",
|
||||
clock: "clock",
|
||||
cloud: "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">
|
||||
<i class={`ph ph-${iconMap[feature.icon] || "sparkle"}`} aria-hidden="true"></i>
|
||||
</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>
|
||||
167
templates/marketing/src/components/blocks/Hero.astro
Normal file
167
templates/marketing/src/components/blocks/Hero.astro
Normal file
@@ -0,0 +1,167 @@
|
||||
---
|
||||
interface Props {
|
||||
node: {
|
||||
headline: string;
|
||||
subheadline?: string;
|
||||
primaryCta?: { label: string; url: string };
|
||||
secondaryCta?: { label: string; url: string };
|
||||
image?: { url: string; alt?: string };
|
||||
centered?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { headline, subheadline, primaryCta, secondaryCta, image, centered } = node;
|
||||
---
|
||||
|
||||
<section class:list={["hero", { "hero-centered": centered, "hero-with-image": !!image }]}>
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-headline">{headline}</h1>
|
||||
{subheadline && <p class="hero-subheadline">{subheadline}</p>}
|
||||
{(primaryCta || secondaryCta) && (
|
||||
<div class="hero-actions">
|
||||
{primaryCta && (
|
||||
<a href={primaryCta.url} class="btn btn-primary btn-lg">
|
||||
{primaryCta.label}
|
||||
</a>
|
||||
)}
|
||||
{secondaryCta && (
|
||||
<a href={secondaryCta.url} class="btn btn-secondary btn-lg">
|
||||
{secondaryCta.label}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{image ? (
|
||||
<div class="hero-image">
|
||||
<img src={image.url} alt={image.alt || ""} loading="eager" />
|
||||
</div>
|
||||
) : !centered && (
|
||||
<div class="hero-visual" aria-hidden="true">
|
||||
<img src="/hero-visual.svg" alt="" width="800" height="800" />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
max-width: var(--wide-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-4xl) var(--spacing-lg);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-2xl);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-centered {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
max-width: var(--max-width);
|
||||
padding-top: var(--spacing-4xl);
|
||||
padding-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.hero-centered .hero-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.hero-centered .hero-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.hero-headline {
|
||||
font-size: var(--font-size-5xl);
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.03em;
|
||||
background: linear-gradient(135deg, var(--color-text) 0%, var(--color-muted) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero-subheadline {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: 1.6;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
.hero-image::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -10px;
|
||||
background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-accent-light) 100%);
|
||||
border-radius: var(--radius-lg);
|
||||
z-index: -1;
|
||||
opacity: 0.3;
|
||||
filter: blur(20px);
|
||||
}
|
||||
|
||||
/* Hero visual (external SVG image) */
|
||||
.hero-visual {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.hero-visual img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--spacing-2xl) var(--spacing-lg);
|
||||
gap: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.hero-headline {
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
|
||||
.hero-subheadline {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.hero-with-image {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-with-image .hero-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-image,
|
||||
.hero-visual {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
187
templates/marketing/src/components/blocks/Pricing.astro
Normal file
187
templates/marketing/src/components/blocks/Pricing.astro
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
interface Props {
|
||||
node: {
|
||||
headline?: string;
|
||||
plans: Array<{
|
||||
name: string;
|
||||
price: string;
|
||||
period?: string;
|
||||
description?: string;
|
||||
features: string[];
|
||||
cta: { label: string; url: string };
|
||||
highlighted?: boolean;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { headline, plans } = node;
|
||||
---
|
||||
|
||||
<section class="pricing section">
|
||||
<div class="container">
|
||||
{headline && (
|
||||
<header class="pricing-header">
|
||||
<h2 class="pricing-headline">{headline}</h2>
|
||||
</header>
|
||||
)}
|
||||
<div class="pricing-grid">
|
||||
{plans.map((plan) => (
|
||||
<div class:list={["pricing-card", { "pricing-highlighted": plan.highlighted }]}>
|
||||
{plan.highlighted && <div class="pricing-badge">Most popular</div>}
|
||||
<div class="pricing-plan-header">
|
||||
<h3 class="pricing-name">{plan.name}</h3>
|
||||
<div class="pricing-price">
|
||||
<span class="pricing-amount">{plan.price}</span>
|
||||
{plan.period && <span class="pricing-period">{plan.period}</span>}
|
||||
</div>
|
||||
{plan.description && (
|
||||
<p class="pricing-description">{plan.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<ul class="pricing-features">
|
||||
{plan.features.map((feature) => (
|
||||
<li>
|
||||
<svg class="check-icon" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a
|
||||
href={plan.cta.url}
|
||||
class:list={["btn", "btn-lg", "pricing-cta", { "btn-primary": plan.highlighted, "btn-secondary": !plan.highlighted }]}
|
||||
>
|
||||
{plan.cta.label}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.pricing-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.pricing-headline {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.pricing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-xl);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
position: relative;
|
||||
padding: var(--spacing-xl);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.pricing-highlighted {
|
||||
background: var(--color-bg);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-xl);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.pricing-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
|
||||
.pricing-plan-header {
|
||||
text-align: center;
|
||||
padding-bottom: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.pricing-name {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.pricing-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.pricing-amount {
|
||||
font-size: var(--font-size-4xl);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.pricing-period {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.pricing-description {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.pricing-features {
|
||||
list-style: none;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.pricing-features li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-success);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.pricing-cta {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.pricing-grid {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pricing-highlighted {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
135
templates/marketing/src/components/blocks/Testimonials.astro
Normal file
135
templates/marketing/src/components/blocks/Testimonials.astro
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
interface Props {
|
||||
node: {
|
||||
headline?: string;
|
||||
testimonials: Array<{
|
||||
quote: string;
|
||||
author: string;
|
||||
role?: string;
|
||||
company?: string;
|
||||
avatar?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
const { node } = Astro.props;
|
||||
const { headline, testimonials } = node;
|
||||
---
|
||||
|
||||
<section class="testimonials section">
|
||||
<div class="container">
|
||||
{headline && (
|
||||
<header class="testimonials-header">
|
||||
<h2 class="testimonials-headline">{headline}</h2>
|
||||
</header>
|
||||
)}
|
||||
<div class="testimonials-grid">
|
||||
{testimonials.map((testimonial) => (
|
||||
<div class="testimonial-card">
|
||||
<blockquote class="testimonial-quote">
|
||||
"{testimonial.quote}"
|
||||
</blockquote>
|
||||
<div class="testimonial-author">
|
||||
{testimonial.avatar && (
|
||||
<img
|
||||
src={testimonial.avatar}
|
||||
alt={testimonial.author}
|
||||
class="testimonial-avatar"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
<div class="testimonial-info">
|
||||
<span class="testimonial-name">{testimonial.author}</span>
|
||||
{(testimonial.role || testimonial.company) && (
|
||||
<span class="testimonial-role">
|
||||
{testimonial.role}
|
||||
{testimonial.role && testimonial.company && " at "}
|
||||
{testimonial.company}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.testimonials {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.testimonials-header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-4xl);
|
||||
}
|
||||
|
||||
.testimonials-headline {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.testimonials-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.testimonial-card {
|
||||
padding: var(--spacing-xl);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.testimonial-quote {
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: 1.6;
|
||||
color: var(--color-text);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.testimonial-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.testimonial-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--radius-full);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.testimonial-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.testimonial-name {
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.testimonial-role {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.testimonials-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.testimonials-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
templates/marketing/src/components/blocks/index.ts
Normal file
5
templates/marketing/src/components/blocks/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { default as Hero } from "./Hero.astro";
|
||||
export { default as Features } from "./Features.astro";
|
||||
export { default as Testimonials } from "./Testimonials.astro";
|
||||
export { default as Pricing } from "./Pricing.astro";
|
||||
export { default as FAQ } from "./FAQ.astro";
|
||||
Reference in New Issue
Block a user