feat: add official EmDash marketing template

- Marketing landing page with hero, features, testimonials, FAQ, pricing
- EmDash CMS with pages collection and marketing blocks
- Full seed data with all content sections
- Dockerfile with entrypoint for database persistence
- Responsive design with CSS variables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kunthawat Greethong
2026-04-30 20:58:06 +07:00
commit 065d92636a
23 changed files with 1580 additions and 0 deletions

156
src/layouts/Base.astro Normal file
View File

@@ -0,0 +1,156 @@
---
import { getMenu, getSiteSettings } from "emdash";
import { EmDashHead } from "emdash/ui";
import { createPublicPageContext } from "emdash/page";
import { Font } from "astro:assets";
import "../styles/theme.css";
interface Props {
title?: string;
description?: string;
image?: string;
}
const { title, description, image } = Astro.props;
const settings = await getSiteSettings();
const siteTitle = settings?.title || "Acme";
const fullTitle = title ? `${title} — ${siteTitle}` : siteTitle;
const siteDescription = settings?.tagline || "Build products people actually want";
const siteLogo = (settings?.logo as any)?.url ? settings.logo as { mediaId: string; alt?: string; url: string } : null;
const siteFavicon = (settings?.favicon as any)?.url ?? null;
const menu = await getMenu("primary");
const pageCtx = createPublicPageContext({
Astro,
kind: "custom",
pageType: "website",
title: fullTitle,
pageTitle: title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
image,
seo: { ogImage: image },
siteName: siteTitle,
});
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{fullTitle}</title>
{siteFavicon && <link rel="icon" href={siteFavicon} />}
{description && <meta name="description" content={description} />}
<EmDashHead emdash={Astro.locals.emdash} pageContext={pageCtx} />
<Font ...{({ variable: "--font-sans", provider: { type: "google", name: "Inter" } })} />
</head>
<body>
<header class="site-header">
<nav class="container nav">
<a href="/" class="logo">
{siteLogo ? (
<img src={siteLogo.url} alt={siteLogo.alt || siteTitle} width="32" height="32" />
) : (
<span>{siteTitle}</span>
)}
</a>
<ul class="nav-links">
{menu?.items.map((item: any) => (
<li>
<a href={item.url}>{item.label}</a>
</li>
))}
</ul>
<a href="/contact" class="btn btn-primary">Get Started</a>
</nav>
</header>
<main>
<slot />
</main>
<footer class="site-footer">
<div class="container">
<p>&copy; {new Date().getFullYear()} {siteTitle}. All rights reserved.</p>
</div>
</footer>
</body>
</html>
<style>
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) 0;
}
.logo {
font-weight: 700;
font-size: var(--font-size-xl);
text-decoration: none;
color: var(--color-text);
}
.nav-links {
display: flex;
gap: var(--spacing-xl);
list-style: none;
margin: 0;
padding: 0;
}
.nav-links a {
text-decoration: none;
color: var(--color-text);
font-weight: 500;
transition: color var(--transition-fast);
}
.nav-links a:hover {
color: var(--color-primary);
}
.site-footer {
border-top: 1px solid var(--color-border);
padding: var(--spacing-2xl) 0;
text-align: center;
color: var(--color-muted);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius);
font-weight: 600;
text-decoration: none;
transition: all var(--transition-fast);
}
.btn-primary {
background: var(--color-primary);
color: white;
}
.btn-primary:hover {
background: var(--color-primary-dark);
}
@media (max-width: 768px) {
.nav-links {
display: none;
}
}
</style>