first commit
This commit is contained in:
69
templates/marketing-cloudflare/src/pages/404.astro
Normal file
69
templates/marketing-cloudflare/src/pages/404.astro
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
import Base from "../layouts/Base.astro";
|
||||
---
|
||||
|
||||
<Base title="Page not found">
|
||||
<div class="not-found">
|
||||
<div class="not-found-code">404</div>
|
||||
<h1>Page not found</h1>
|
||||
<p>The page you're looking for doesn't exist or has been moved.</p>
|
||||
<div class="not-found-actions">
|
||||
<a href="/" class="btn btn-primary btn-lg">Go home</a>
|
||||
<a href="/contact" class="btn btn-secondary btn-lg">Contact us</a>
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: var(--spacing-5xl) var(--spacing-lg);
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.not-found-code {
|
||||
font-size: 10rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.05em;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
opacity: 0.3;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.not-found h1 {
|
||||
font-size: var(--font-size-3xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.not-found p {
|
||||
font-size: var(--font-size-lg);
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.not-found-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.not-found-code {
|
||||
font-size: 6rem;
|
||||
}
|
||||
|
||||
.not-found-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.not-found-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
357
templates/marketing-cloudflare/src/pages/contact.astro
Normal file
357
templates/marketing-cloudflare/src/pages/contact.astro
Normal file
@@ -0,0 +1,357 @@
|
||||
---
|
||||
import { getEmDashEntry } from "emdash";
|
||||
import Base from "../layouts/Base.astro";
|
||||
import MarketingBlocks from "../components/MarketingBlocks.astro";
|
||||
|
||||
const { entry: page, cacheHint } = await getEmDashEntry("pages", "contact");
|
||||
|
||||
try {
|
||||
Astro.cache.set(cacheHint);
|
||||
} catch {}
|
||||
|
||||
// Handle form submission
|
||||
// NOTE: This is demo code. For production, add:
|
||||
// - CSRF token validation
|
||||
// - Rate limiting (e.g., via Cloudflare or middleware)
|
||||
// - Actual email sending or webhook integration
|
||||
let formStatus: "idle" | "success" | "error" = "idle";
|
||||
let formMessage = "";
|
||||
|
||||
if (Astro.request.method === "POST") {
|
||||
try {
|
||||
const formData = await Astro.request.formData();
|
||||
const name = formData.get("name")?.toString() || "";
|
||||
const email = formData.get("email")?.toString() || "";
|
||||
const company = formData.get("company")?.toString() || "";
|
||||
const message = formData.get("message")?.toString() || "";
|
||||
|
||||
if (!name || !email || !message) {
|
||||
formStatus = "error";
|
||||
formMessage = "Please fill in all required fields.";
|
||||
} else if (!email.includes("@")) {
|
||||
formStatus = "error";
|
||||
formMessage = "Please enter a valid email address.";
|
||||
} else {
|
||||
// TODO: Replace with actual email/webhook integration
|
||||
console.log("Contact form submission:", {
|
||||
name,
|
||||
email,
|
||||
company,
|
||||
message,
|
||||
});
|
||||
formStatus = "success";
|
||||
formMessage =
|
||||
"Thanks for reaching out! We'll get back to you within 24 hours.";
|
||||
}
|
||||
} catch {
|
||||
formStatus = "error";
|
||||
formMessage = "Something went wrong. Please try again.";
|
||||
}
|
||||
}
|
||||
|
||||
const pageContent = page?.data.content;
|
||||
---
|
||||
|
||||
<Base
|
||||
title="Contact"
|
||||
description="Have questions? Want a demo? We'd love to hear from you."
|
||||
>
|
||||
{pageContent && <MarketingBlocks value={pageContent} />}
|
||||
|
||||
<section class="contact-form-section section">
|
||||
<div class="container">
|
||||
<div class="contact-grid">
|
||||
<div class="contact-info">
|
||||
<h2>Talk to our team</h2>
|
||||
<p>Fill out the form and we'll be in touch within 24 hours.</p>
|
||||
|
||||
<div class="contact-methods">
|
||||
<div class="contact-method">
|
||||
<div class="contact-icon">
|
||||
<i class="ph ph-envelope" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="contact-method-content">
|
||||
<h4>Email</h4>
|
||||
<a href="mailto:hello@acme.example">hello@acme.example</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-method">
|
||||
<div class="contact-icon">
|
||||
<i class="ph ph-lifebuoy" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="contact-method-content">
|
||||
<h4>Support</h4>
|
||||
<a href="mailto:support@acme.example">support@acme.example</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-method">
|
||||
<div class="contact-icon">
|
||||
<i class="ph ph-currency-dollar" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="contact-method-content">
|
||||
<h4>Sales</h4>
|
||||
<a href="mailto:sales@acme.example">sales@acme.example</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contact-form-wrapper">
|
||||
{
|
||||
formStatus === "success" ? (
|
||||
<div class="form-success">
|
||||
<div class="success-icon">✓</div>
|
||||
<h3>Message Sent!</h3>
|
||||
<p>{formMessage}</p>
|
||||
<a href="/contact" class="btn btn-secondary">
|
||||
Send another message
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<form method="POST" class="contact-form">
|
||||
{formStatus === "error" && (
|
||||
<div class="form-error">
|
||||
<p>{formMessage}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-field">
|
||||
<label for="name">Name *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
placeholder="Your name"
|
||||
autocomplete="name"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="email">Email *</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="you@company.com"
|
||||
autocomplete="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="company">Company</label>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
name="company"
|
||||
placeholder="Your company"
|
||||
autocomplete="organization"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="message">Message *</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
required
|
||||
rows="5"
|
||||
placeholder="Tell us about your project or question..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.contact-form-section {
|
||||
padding-bottom: var(--spacing-5xl);
|
||||
}
|
||||
|
||||
.contact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: var(--spacing-4xl);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.contact-info h2 {
|
||||
font-size: var(--font-size-2xl);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.contact-info > p {
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.contact-methods {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.contact-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.contact-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
color: white;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary),
|
||||
var(--color-accent)
|
||||
);
|
||||
border-radius: var(--radius);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contact-method-content h4 {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.contact-method-content a {
|
||||
color: var(--color-primary);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.contact-method-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.contact-form-wrapper {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.contact-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-field input,
|
||||
.form-field textarea {
|
||||
padding: var(--spacing-md);
|
||||
font-family: inherit;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.form-field input:focus,
|
||||
.form-field textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
.form-field input::placeholder,
|
||||
.form-field textarea::placeholder {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.form-field textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
padding: var(--spacing-md);
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border-radius: var(--radius-sm);
|
||||
color: #dc2626;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.form-error {
|
||||
color: #f87171;
|
||||
}
|
||||
}
|
||||
|
||||
.form-success {
|
||||
text-align: center;
|
||||
padding: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2rem;
|
||||
color: white;
|
||||
background: var(--color-success);
|
||||
border-radius: var(--radius-full);
|
||||
margin: 0 auto var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-success h3 {
|
||||
font-size: var(--font-size-xl);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.form-success p {
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.contact-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
templates/marketing-cloudflare/src/pages/index.astro
Normal file
51
templates/marketing-cloudflare/src/pages/index.astro
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
import { getEmDashEntry } from "emdash";
|
||||
import Base from "../layouts/Base.astro";
|
||||
import MarketingBlocks from "../components/MarketingBlocks.astro";
|
||||
|
||||
const { entry: page, cacheHint } = await getEmDashEntry("pages", "home");
|
||||
|
||||
try {
|
||||
Astro.cache.set(cacheHint);
|
||||
} catch {}
|
||||
|
||||
const pageTitle = page?.data.title;
|
||||
const pageContent = page?.data.content;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={pageTitle !== "Home" ? pageTitle : undefined}
|
||||
description="Build products people actually want. The all-in-one platform for modern teams."
|
||||
>
|
||||
{
|
||||
pageContent ? (
|
||||
<MarketingBlocks value={pageContent} />
|
||||
) : (
|
||||
<div class="empty-state">
|
||||
<h1>Welcome to Acme</h1>
|
||||
<p>Edit the home page content in the admin to get started.</p>
|
||||
<a href="/_emdash/admin" class="btn btn-primary">
|
||||
Open Admin
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-5xl) var(--spacing-lg);
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.empty-state h1 {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
</style>
|
||||
50
templates/marketing-cloudflare/src/pages/pricing.astro
Normal file
50
templates/marketing-cloudflare/src/pages/pricing.astro
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
import { getEmDashEntry } from "emdash";
|
||||
import Base from "../layouts/Base.astro";
|
||||
import MarketingBlocks from "../components/MarketingBlocks.astro";
|
||||
|
||||
const { entry: page, cacheHint } = await getEmDashEntry("pages", "pricing");
|
||||
|
||||
try {
|
||||
Astro.cache.set(cacheHint);
|
||||
} catch {}
|
||||
|
||||
const pageContent = page?.data.content;
|
||||
---
|
||||
|
||||
<Base
|
||||
title="Pricing"
|
||||
description="Simple, transparent pricing. No hidden fees. No surprises."
|
||||
>
|
||||
{
|
||||
pageContent ? (
|
||||
<MarketingBlocks value={pageContent} />
|
||||
) : (
|
||||
<div class="empty-state">
|
||||
<h1>Pricing</h1>
|
||||
<p>Add pricing content in the admin.</p>
|
||||
<a href="/_emdash/admin" class="btn btn-primary">
|
||||
Open Admin
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-5xl) var(--spacing-lg);
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.empty-state h1 {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user