site: theme tokens, layout/header/footer, home page

This commit is contained in:
Matthew Meszaros
2026-05-25 16:37:17 +00:00
parent 45f51c2cbb
commit c9f2ae4a11
18 changed files with 7694 additions and 36 deletions
+4 -5
View File
@@ -2,13 +2,12 @@
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import sitemap from '@astrojs/sitemap';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
site: 'https://warmbly.com',
integrations: [sitemap()],
vite: {
plugins: [tailwindcss()]
plugins: [tailwindcss()],
},
site: "https://warmbly.com",
integrations: [sitemap(), react()]
});
});
+6704
View File
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -1,7 +1,8 @@
{
"name": "astro-app",
"name": "warmbly-site",
"type": "module",
"version": "0.0.1",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "astro dev",
"build": "astro build",
@@ -9,18 +10,10 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/react": "^4.3.0",
"@astrojs/sitemap": "^3.4.1",
"@fontsource/inter": "^5.2.6",
"@fontsource/poppins": "^5.2.6",
"@tailwindcss/vite": "^4.1.10",
"astro": "^5.9.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"remixicon": "^4.6.0",
"tailwindcss": "^4.1.10"
},
"devDependencies": {
"@types/react": "^19.1.8"
}
}
+30
View File
@@ -0,0 +1,30 @@
---
interface Props {
href: string;
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
external?: boolean;
class?: string;
}
const { href, variant = 'primary', size = 'md', external = false, class: className = '' } = Astro.props;
const base = 'inline-flex items-center justify-center gap-2 rounded-md font-medium transition-colors whitespace-nowrap';
const sizes = {
sm: 'h-8 px-3 text-[13px]',
md: 'h-10 px-4 text-sm',
lg: 'h-11 px-5 text-[15px]',
};
const variants = {
primary: 'bg-foreground text-background hover:bg-foreground/85 ring-1 ring-foreground/10',
secondary: 'bg-surface-2 text-foreground border border-border hover:bg-surface-3',
ghost: 'text-foreground hover:bg-surface-2',
};
---
<a
href={href}
class={`${base} ${sizes[size]} ${variants[variant]} ${className}`}
rel={external ? 'noopener noreferrer' : undefined}
target={external ? '_blank' : undefined}
>
<slot />
</a>
+7
View File
@@ -0,0 +1,7 @@
---
interface Props { class?: string; padded?: boolean }
const { class: className = '', padded = true } = Astro.props;
---
<div class={`rounded-xl border border-border bg-card text-card-foreground ${padded ? 'p-6' : ''} ${className}`}>
<slot />
</div>
+7
View File
@@ -0,0 +1,7 @@
---
interface Props { class?: string }
const { class: className = '' } = Astro.props;
---
<span class={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-semibold uppercase tracking-wider text-brand bg-brand-soft border border-brand/15 ${className}`}>
<slot />
</span>
+109
View File
@@ -0,0 +1,109 @@
---
import Wordmark from './Wordmark.astro';
const groups: { title: string; links: { label: string; href: string }[] }[] = [
{
title: 'Product',
links: [
{ label: 'Overview', href: '/product/' },
{ label: 'Warmup', href: '/warmup/' },
{ label: 'Sending engine', href: '/sending/' },
{ label: 'Unified inbox', href: '/inbox/' },
{ label: 'Campaigns', href: '/campaigns/' },
{ label: 'Analytics', href: '/analytics/' },
{ label: 'CRM', href: '/crm/' },
{ label: 'API & Webhooks', href: '/developers/' },
{ label: 'Integrations', href: '/integrations/' },
{ label: 'Changelog', href: '/changelog/' },
{ label: 'Roadmap', href: '/roadmap/' },
],
},
{
title: 'Use cases',
links: [
{ label: 'Founders', href: '/use-cases/founders/' },
{ label: 'Agencies', href: '/use-cases/agencies/' },
{ label: 'Sales teams', href: '/use-cases/sales/' },
{ label: 'Recruiters', href: '/use-cases/recruiters/' },
{ label: 'Fundraising', href: '/use-cases/fundraising/' },
],
},
{
title: 'Learn',
links: [
{ label: 'Email warmup guide', href: '/learn/email-warmup/' },
{ label: 'Deliverability handbook', href: '/learn/deliverability/' },
{ label: 'SPF, DKIM, DMARC', href: '/learn/spf-dkim-dmarc/' },
{ label: 'Cold email rules', href: '/learn/cold-email-rules/' },
{ label: 'Inbox placement', href: '/learn/inbox-placement/' },
{ label: 'Warmup pools', href: '/learn/warmup-pools/' },
{ label: 'Reputation recovery', href: '/learn/reputation-recovery/' },
{ label: 'Glossary', href: '/learn/glossary/' },
],
},
{
title: 'Company',
links: [
{ label: 'About', href: '/about/' },
{ label: 'Customers', href: '/customers/' },
{ label: 'Contact', href: '/contact/' },
{ label: 'Brand', href: '/brand/' },
{ label: 'Security', href: '/security/' },
{ label: 'Trust', href: '/trust/' },
],
},
{
title: 'Legal',
links: [
{ label: 'Terms', href: '/terms/' },
{ label: 'Privacy', href: '/privacy/' },
{ label: 'DPA', href: '/dpa/' },
{ label: 'Acceptable use', href: '/acceptable-use/' },
{ label: 'Subprocessors', href: '/subprocessors/' },
{ label: 'Cookies', href: '/cookies/' },
],
},
];
const year = new Date().getFullYear();
---
<footer class="border-t border-border bg-surface mt-24">
<div class="container-page py-16">
<div class="grid gap-10 md:grid-cols-12">
<div class="md:col-span-3">
<Wordmark size="md" />
<p class="mt-4 text-sm text-muted-foreground max-w-xs leading-relaxed">
The deliverability platform for cold email. Warm your inboxes, send at scale, keep your reputation clean.
</p>
<div class="mt-5 flex items-center gap-2">
<span class="inline-flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground bg-surface-2 border border-border rounded-full px-2 py-0.5">
<span class="w-1.5 h-1.5 rounded-full bg-success animate-pulse-soft" />
All systems operational
</span>
</div>
</div>
<div class="md:col-span-9 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-8">
{groups.map((g) => (
<div>
<h4 class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-3">{g.title}</h4>
<ul class="space-y-2">
{g.links.map((l) => (
<li>
<a href={l.href} class="text-[13px] text-foreground/80 hover:text-foreground transition-colors">{l.label}</a>
</li>
))}
</ul>
</div>
))}
</div>
</div>
<div class="mt-14 pt-6 border-t border-border flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<p class="text-xs text-muted-foreground">© {year} Warmbly Labs, Inc. All rights reserved.</p>
<p class="text-xs text-muted-foreground">
Built for people who send cold email and want it to land.
</p>
</div>
</div>
</footer>
+96
View File
@@ -0,0 +1,96 @@
---
import Wordmark from './Wordmark.astro';
const path = Astro.url.pathname;
const nav: { label: string; href: string; match?: string }[] = [
{ label: 'Product', href: '/product/', match: '/product' },
{ label: 'Warmup', href: '/warmup/', match: '/warmup' },
{ label: 'Deliverability', href: '/deliverability/', match: '/deliverability' },
{ label: 'Security', href: '/security/', match: '/security' },
{ label: 'Pricing', href: '/pricing/', match: '/pricing' },
{ label: 'Learn', href: '/learn/', match: '/learn' },
];
function active(match?: string) {
if (!match) return false;
return path === match || path.startsWith(match + '/');
}
---
<header class="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/65">
<div class="container-page flex h-14 items-center gap-6">
<Wordmark size="md" />
<nav class="hidden md:flex items-center gap-1 ml-2">
{nav.map((n) => (
<a
href={n.href}
class={
'h-8 px-3 inline-flex items-center rounded-md text-[13px] transition-colors ' +
(active(n.match)
? 'text-foreground bg-surface-2'
: 'text-muted-foreground hover:text-foreground hover:bg-surface-2/60')
}
>
{n.label}
</a>
))}
</nav>
<div class="ml-auto flex items-center gap-2">
<a
href="https://app.warmbly.com/login"
class="hidden sm:inline-flex h-8 px-3 items-center rounded-md text-[13px] text-muted-foreground hover:text-foreground hover:bg-surface-2 transition-colors"
>
Log in
</a>
<a
href="https://app.warmbly.com/register"
class="inline-flex h-8 px-3.5 items-center rounded-md text-[13px] font-medium text-brand-foreground bg-foreground hover:bg-foreground/85 transition-colors"
>
Start free
</a>
<button
id="nav-toggle"
class="md:hidden inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-surface-2"
aria-label="Toggle navigation"
aria-expanded="false"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<line x1="4" y1="7" x2="20" y2="7" />
<line x1="4" y1="12" x2="20" y2="12" />
<line x1="4" y1="17" x2="20" y2="17" />
</svg>
</button>
</div>
</div>
<div id="mobile-nav" class="hidden md:hidden border-t border-border bg-background">
<div class="container-page py-3 grid gap-1">
{nav.map((n) => (
<a
href={n.href}
class={
'h-9 px-2 inline-flex items-center rounded-md text-sm ' +
(active(n.match)
? 'text-foreground bg-surface-2'
: 'text-muted-foreground hover:text-foreground hover:bg-surface-2/60')
}
>
{n.label}
</a>
))}
<div class="h-px bg-border my-2" />
<a href="https://app.warmbly.com/login" class="h-9 px-2 inline-flex items-center rounded-md text-sm text-muted-foreground hover:text-foreground hover:bg-surface-2">Log in</a>
<a href="https://app.warmbly.com/register" class="h-9 px-2 inline-flex items-center rounded-md text-sm font-medium text-brand-foreground bg-foreground">Start free</a>
</div>
</div>
</header>
<script>
const toggle = document.getElementById('nav-toggle');
const menu = document.getElementById('mobile-nav');
toggle?.addEventListener('click', () => {
const open = menu?.classList.toggle('hidden') === false;
toggle.setAttribute('aria-expanded', String(open));
});
</script>
+68
View File
@@ -0,0 +1,68 @@
---
/**
* Inline icon component — uses Lucide-style stroke SVGs to keep the bundle
* empty (no JS, no font). Pass `name` for any of the icons below.
*/
interface Props {
name: string;
class?: string;
size?: number;
strokeWidth?: number;
}
const { name, class: className = '', size = 18, strokeWidth = 1.75 } = Astro.props;
// Each icon path data, drawn on a 24x24 viewBox.
const icons: Record<string, string> = {
mail: '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/>',
inbox: '<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11Z"/>',
flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>',
shield: '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
zap: '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>',
globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20"/><path d="M12 2a14.5 14.5 0 0 1 0 20"/><path d="M2 12h20"/>',
chart: '<line x1="18" x2="18" y1="20" y2="10"/><line x1="12" x2="12" y1="20" y2="4"/><line x1="6" x2="6" y1="20" y2="14"/>',
workflow: '<rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="15" width="6" height="6" rx="1"/><path d="M6 9v4a2 2 0 0 0 2 2h7"/>',
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
contact: '<rect x="3" y="4" width="18" height="18" rx="2"/><circle cx="12" cy="10" r="3"/><path d="M7 21v-1a4 4 0 0 1 4-4h2a4 4 0 0 1 4 4v1"/>',
send: '<path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.5.5 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/>',
lock: '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
key: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
cog: '<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h0a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v0a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
cloud: '<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/>',
bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
check: '<path d="M20 6 9 17l-5-5"/>',
arrowRight: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>',
arrowUpRight:'<path d="M7 7h10v10"/><path d="M7 17 17 7"/>',
layers: '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.65 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>',
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/>',
cpu: '<rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M9 2v3"/><path d="M15 2v3"/><path d="M9 19v3"/><path d="M15 19v3"/><path d="M2 9h3"/><path d="M2 15h3"/><path d="M19 9h3"/><path d="M19 15h3"/>',
spark: '<path d="M9.93 13.5h4.14L12 8z"/><path d="M4 4h16c1.1 0 2 .9 2 2v9.3l-3.3-3.3-2.4 2.4-3.3-3.3-3.3 3.3-2.4-2.4L2 16V6c0-1.1.9-2 2-2"/>',
filter: '<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>',
reply: '<polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/>',
list: '<line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/>',
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
webhook: '<path d="M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 1 1 8.93 13"/><path d="m13 2 5 8h-4l1 4"/><path d="M2 13.5A4 4 0 0 0 6 17h11"/>',
brain: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/>',
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
warning: '<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/>',
star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
rocket: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/>',
};
const path = icons[name] ?? '';
---
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-linejoin="round"
class={className}
aria-hidden="true"
set:html={path}
></svg>
+9
View File
@@ -0,0 +1,9 @@
---
interface Props {
class?: string;
}
const { class: className = 'w-6 h-6' } = Astro.props;
---
<svg class={className} viewBox="0 0 746 764" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M222.805 644.772L186.274 108.881L704.5 451.158L484.5 451.158L245.5 196.158L444 463.5L222.805 644.772Z" fill="currentColor" />
</svg>
+14
View File
@@ -0,0 +1,14 @@
---
interface Props {
id?: string;
class?: string;
variant?: 'default' | 'muted' | 'bordered';
}
const { id, class: className = '', variant = 'default' } = Astro.props;
const bg = variant === 'muted' ? 'bg-surface' : variant === 'bordered' ? 'bg-background border-y border-border' : 'bg-background';
---
<section id={id} class={`${bg} ${className}`}>
<div class="container-page py-20 md:py-24">
<slot />
</div>
</section>
+21
View File
@@ -0,0 +1,21 @@
---
interface Props {
eyebrow?: string;
title: string;
description?: string;
align?: 'left' | 'center';
}
const { eyebrow, title, description, align = 'left' } = Astro.props;
const alignCls = align === 'center' ? 'text-center mx-auto' : 'text-left';
---
<div class={`max-w-3xl ${alignCls}`}>
{eyebrow && (
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-semibold uppercase tracking-wider text-brand bg-brand-soft border border-brand/15 mb-4">
{eyebrow}
</span>
)}
<h2 class="text-3xl md:text-4xl font-extrabold tracking-tight text-foreground leading-[1.1]">{title}</h2>
{description && (
<p class="mt-4 text-base md:text-[17px] text-muted-foreground leading-relaxed">{description}</p>
)}
</div>
+19
View File
@@ -0,0 +1,19 @@
---
import Logo from './Logo.astro';
interface Props {
class?: string;
size?: 'sm' | 'md' | 'lg';
href?: string | null;
}
const { class: className = '', size = 'md', href = '/' } = Astro.props;
const logoSize = size === 'sm' ? 'w-5 h-5' : size === 'lg' ? 'w-8 h-8' : 'w-6 h-6';
const textSize = size === 'sm' ? 'text-[14px]' : size === 'lg' ? 'text-[20px]' : 'text-[15.5px]';
const Tag = href ? 'a' : 'div';
---
<Tag
href={href ?? undefined}
class={`inline-flex items-center gap-2.5 text-foreground hover:text-foreground/80 transition-colors ${className}`}
>
<Logo class={`${logoSize} text-foreground`} />
<span class={`font-extrabold tracking-tight ${textSize}`}>Warmbly</span>
</Tag>
+63
View File
@@ -0,0 +1,63 @@
---
import '../styles/global.css';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
interface Props {
title: string;
description?: string;
ogImage?: string;
noIndex?: boolean;
/** Toggle "marketing chrome" header/footer */
bare?: boolean;
}
const {
title,
description = 'Warmbly is the deliverability platform for cold email. Warm your inboxes, send at scale, keep your reputation clean.',
ogImage = '/web-app-manifest-512x512.png',
noIndex = false,
bare = false,
} = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site ?? 'https://warmbly.com').toString();
---
<!doctype html>
<html lang="en" class="bg-background text-foreground">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
{noIndex && <meta name="robots" content="noindex,nofollow" />}
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="apple-mobile-web-app-title" content="Warmbly" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0c0d10" media="(prefers-color-scheme: dark)" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Warmbly" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={new URL(ogImage, Astro.site ?? 'https://warmbly.com').toString()} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<link rel="sitemap" href="/sitemap-index.xml" />
</head>
<body class="min-h-screen flex flex-col antialiased">
{!bare && <Header />}
<main class="flex-1">
<slot />
</main>
{!bare && <Footer />}
</body>
</html>
+313
View File
@@ -0,0 +1,313 @@
---
import Layout from '../layouts/Layout.astro';
import Section from '../components/Section.astro';
import SectionHead from '../components/SectionHead.astro';
import Card from '../components/Card.astro';
import Button from '../components/Button.astro';
import Icon from '../components/Icon.astro';
import Eyebrow from '../components/Eyebrow.astro';
const features = [
{ icon: 'flame', title: 'Built-in warmup', desc: 'Reputation-safe warmup pools that spin up before you start, and keep running after you do.' },
{ icon: 'send', title: 'Distributed sending', desc: 'Sends across many mailboxes and many machines so no single IP carries the heat.' },
{ icon: 'inbox', title: 'Unified inbox', desc: 'Every reply, bounce, OOO, and click in one console with assignment and search.' },
{ icon: 'spark', title: 'Smart sequences', desc: 'Step delays, variants, conditions, and reply detection — without the dark magic.' },
{ icon: 'shield', title: 'Encrypted at rest', desc: 'Envelope encryption with AWS KMS. Per-user data keys. Worker boundary preserved.' },
{ icon: 'chart', title: 'Deliverability analytics', desc: 'Placement, complaints, bounces, and warmup health by mailbox, pool, and worker.' },
];
const logos = ['Loop', 'Northwind', 'Spruce', 'Heliogen', 'Foundry', 'Quanto', 'Cinder', 'Vellum'];
const stats = [
{ value: '99.97%', label: 'Worker uptime, last 90 days' },
{ value: '< 1ms', label: 'Tracking edge dedupe latency' },
{ value: '0.04%', label: 'Mean platform complaint rate' },
{ value: '12.4k', label: 'Mailboxes warming right now' },
];
---
<Layout
title="Warmbly — cold email that actually lands"
description="Warmbly is the deliverability platform for cold email. Warm your inboxes, send at scale, and keep your reputation clean across hundreds of mailboxes."
>
<!-- HERO ------------------------------------------------------------- -->
<section class="relative overflow-hidden hero-glow">
<div class="absolute inset-0 bg-linegrid opacity-[0.35] dark:opacity-[0.25] pointer-events-none" />
<div class="container-page relative pt-20 md:pt-28 pb-16 md:pb-24">
<div class="max-w-3xl">
<Eyebrow class="mb-5">
<Icon name="zap" size={12} />
Warmup &amp; sending, one platform
</Eyebrow>
<h1 class="text-4xl md:text-6xl font-extrabold tracking-tight leading-[1.02] text-foreground">
Cold email that actually lands in the inbox.
</h1>
<p class="mt-6 text-lg md:text-xl text-muted-foreground leading-relaxed max-w-2xl">
Warmbly warms your mailboxes, distributes sending across workers and IPs, and watches reputation in real time — so the cold campaign you run on Monday is still landing on Friday.
</p>
<div class="mt-8 flex flex-wrap items-center gap-3">
<Button href="https://app.warmbly.com/register" size="lg">
Start free
<Icon name="arrowRight" size={16} />
</Button>
<Button href="/pricing/" size="lg" variant="secondary">See pricing</Button>
<span class="text-sm text-muted-foreground ml-1">No credit card. 14-day pro trial.</span>
</div>
<ul class="mt-10 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-muted-foreground">
<li class="inline-flex items-center gap-2"><Icon name="check" size={14} class="text-success" /> SPF, DKIM &amp; DMARC checks</li>
<li class="inline-flex items-center gap-2"><Icon name="check" size={14} class="text-success" /> Free + premium warmup pools</li>
<li class="inline-flex items-center gap-2"><Icon name="check" size={14} class="text-success" /> Dedicated workers on Business</li>
<li class="inline-flex items-center gap-2"><Icon name="check" size={14} class="text-success" /> Native Gmail &amp; Outlook</li>
</ul>
</div>
<!-- Mock product surface -->
<div class="mt-16 md:mt-20 relative">
<div class="rounded-2xl border border-border bg-card shadow-[0_30px_80px_-30px_rgba(15,23,42,0.18)] overflow-hidden">
<div class="flex items-center gap-2 px-4 h-9 border-b border-border bg-surface-2/60">
<div class="flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-foreground/15" />
<span class="w-2.5 h-2.5 rounded-full bg-foreground/15" />
<span class="w-2.5 h-2.5 rounded-full bg-foreground/15" />
</div>
<span class="text-[11.5px] text-muted-foreground ml-3 font-mono">app.warmbly.com / accounts</span>
</div>
<div class="grid grid-cols-12 min-h-[420px]">
<aside class="hidden md:block col-span-3 border-r border-border p-4">
<p class="text-[10.5px] font-semibold uppercase tracking-wider text-muted-foreground px-1 mb-2">Workspace</p>
<nav class="space-y-1 text-[13px]">
{['Home','Accounts','Inbox','Campaigns','Contacts','Analytics','CRM','Settings'].map((it, i) => (
<div class={`h-7 px-2 rounded-md flex items-center gap-2 ${i===1 ? 'bg-surface-2 text-foreground' : 'text-muted-foreground'}`}>
<span class="w-1.5 h-1.5 rounded-full bg-foreground/30" />
{it}
</div>
))}
</nav>
<p class="text-[10.5px] font-semibold uppercase tracking-wider text-muted-foreground px-1 mt-5 mb-2">Pools</p>
<div class="space-y-1 text-[13px] text-muted-foreground">
<div class="h-7 px-2 rounded-md flex items-center justify-between"><span>Premium</span><span class="text-[11px] text-success">healthy</span></div>
<div class="h-7 px-2 rounded-md flex items-center justify-between"><span>Free</span><span class="text-[11px] text-success">healthy</span></div>
</div>
</aside>
<div class="col-span-12 md:col-span-9 p-4 md:p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-[15px] font-semibold">Mailbox accounts</h3>
<p class="text-[12.5px] text-muted-foreground">128 mailboxes · 4 workers · 5,420 sent today</p>
</div>
<div class="flex items-center gap-2">
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium bg-surface-2 border border-border text-muted-foreground">
<span class="w-1.5 h-1.5 rounded-full bg-success animate-pulse-soft" />
workers online
</span>
</div>
</div>
<div class="rounded-lg border border-border overflow-hidden">
<div class="grid grid-cols-12 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-surface-2/60 px-4 h-8 items-center">
<div class="col-span-4">Mailbox</div>
<div class="col-span-2">Worker</div>
<div class="col-span-2">Today</div>
<div class="col-span-2">Warmup</div>
<div class="col-span-2">Placement</div>
</div>
{[
['founder@acme.co', 'eu-worker-3', '37/50', '22/40', '98% inbox'],
['hello@acme.co', 'eu-worker-3', '24/40', '18/40', '96% inbox'],
['team@northwind.dev', 'us-worker-7', '46/50', '34/40', '94% inbox'],
['intro@northwind.dev', 'us-worker-7', '42/50', '30/40', '93% inbox'],
['ml@helio.gen', 'eu-worker-1', '18/30', '12/30', '97% inbox'],
['founder@spruce.bio', 'us-worker-2', '12/40', '8/30', '99% inbox'],
].map(([m, w, t, wm, p]) => (
<div class="grid grid-cols-12 text-[13px] px-4 h-10 items-center border-t border-border">
<div class="col-span-4 font-medium truncate flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-success" />
{m}
</div>
<div class="col-span-2 text-muted-foreground font-mono text-[12px]">{w}</div>
<div class="col-span-2 text-muted-foreground">{t}</div>
<div class="col-span-2 text-muted-foreground">{wm}</div>
<div class="col-span-2 text-success/90">{p}</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- LOGOS / SOCIAL PROOF --------------------------------------------- -->
<section class="border-y border-border bg-surface/50">
<div class="container-page py-8">
<p class="text-center text-[11.5px] font-semibold uppercase tracking-wider text-muted-foreground mb-5">
Trusted by founders, sales teams and recruiters at 4,000+ companies
</p>
<div class="flex flex-wrap items-center justify-center gap-x-10 gap-y-3 opacity-70">
{logos.map((l) => (
<span class="text-[15px] font-semibold tracking-tight text-foreground/60">{l}</span>
))}
</div>
</div>
</section>
<!-- FEATURE GRID ------------------------------------------------------ -->
<Section>
<SectionHead
eyebrow="Everything in one platform"
title="Warmup, sending and inbox — engineered together."
description="Most tools bolt warmup onto an SMTP relay. We built sending, warmup, analytics and inbox as one system so the signal one part collects is the signal the next part acts on."
/>
<div class="mt-12 grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{features.map((f) => (
<Card class="hover:border-border-strong transition-colors">
<div class="w-9 h-9 rounded-lg bg-brand-soft text-brand inline-flex items-center justify-center mb-4">
<Icon name={f.icon} size={18} />
</div>
<h3 class="text-[15.5px] font-semibold text-foreground">{f.title}</h3>
<p class="mt-1.5 text-[14px] text-muted-foreground leading-relaxed">{f.desc}</p>
</Card>
))}
</div>
</Section>
<!-- STATS ------------------------------------------------------------- -->
<section class="bg-surface border-y border-border">
<div class="container-page py-14">
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
{stats.map((s) => (
<div class="text-center md:text-left">
<div class="text-3xl md:text-4xl font-extrabold tracking-tight">{s.value}</div>
<div class="text-[13px] text-muted-foreground mt-1">{s.label}</div>
</div>
))}
</div>
</div>
</section>
<!-- HOW IT WORKS ------------------------------------------------------ -->
<Section>
<SectionHead
eyebrow="How it works"
title="From signup to first reply in an afternoon."
description="No SMTP wrangling. No DNS spelunking. We walk you through the pieces that actually move deliverability and skip the ones that don't."
/>
<div class="mt-12 grid md:grid-cols-2 lg:grid-cols-4 gap-4">
{[
{ n: '01', title: 'Connect mailboxes', body: 'Native OAuth for Google and Microsoft. SMTP + IMAP for everything else.', icon: 'mail' },
{ n: '02', title: 'Warm up safely', body: 'Pool placement, gradual ramp, automatic backoff if reputation slips.', icon: 'flame' },
{ n: '03', title: 'Run campaigns', body: 'Sequences, variables, A/B variants and reply detection across all senders.', icon: 'send' },
{ n: '04', title: 'Watch placement', body: 'Per-mailbox placement, complaint and bounce signals. Quarantine before providers do.', icon: 'chart' },
].map((s) => (
<Card>
<div class="flex items-center justify-between mb-4">
<span class="text-[12px] font-mono text-muted-foreground">{s.n}</span>
<Icon name={s.icon} class="text-muted-foreground" />
</div>
<h3 class="text-[15.5px] font-semibold">{s.title}</h3>
<p class="mt-1.5 text-[13.5px] text-muted-foreground leading-relaxed">{s.body}</p>
</Card>
))}
</div>
</Section>
<!-- DELIVERABILITY POSTURE ------------------------------------------- -->
<Section variant="muted">
<div class="grid lg:grid-cols-12 gap-12 items-start">
<div class="lg:col-span-5">
<Eyebrow class="mb-4">Why we're different</Eyebrow>
<h2 class="text-3xl md:text-4xl font-extrabold tracking-tight leading-[1.1]">
We refuse to be the reason your domain ends up on a blocklist.
</h2>
<p class="mt-5 text-muted-foreground leading-relaxed">
Most cold-email platforms reward you for sending more. We reward you for sending well. Mailbox caps default to 50/day. Workers are capped by the sum of their mailbox budgets — not an arbitrary global number. Shared warmup pools quarantine misbehaving senders before providers do.
</p>
<div class="mt-8 grid sm:grid-cols-2 gap-3">
{[
['50 / day', 'Default per-mailbox cold cap'],
['10 min', 'Default minimum gap between sends'],
['10 / day', 'Warmup starting volume'],
['+1 / day', 'Default warmup ramp'],
].map(([k, v]) => (
<div class="rounded-lg border border-border bg-card px-4 py-3">
<div class="text-[15.5px] font-semibold">{k}</div>
<div class="text-[12.5px] text-muted-foreground">{v}</div>
</div>
))}
</div>
</div>
<div class="lg:col-span-7">
<Card class="bg-card/80">
<div class="flex items-center justify-between mb-5">
<div>
<p class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">Pool health</p>
<h3 class="text-[15.5px] font-semibold">Premium warmup</h3>
</div>
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium bg-success/10 border border-success/20 text-success">
<span class="w-1.5 h-1.5 rounded-full bg-success animate-pulse-soft" />
healthy
</span>
</div>
<div class="grid grid-cols-3 gap-4 mb-6">
{[
['Complaint rate', '0.02%', 'success'],
['Bounce rate', '0.7%', 'success'],
['Spam placement', '3.1%', 'warn'],
].map(([k, v, color]) => (
<div class="rounded-lg border border-border bg-surface-2/60 px-3 py-3">
<div class="text-[11px] uppercase tracking-wider text-muted-foreground">{k}</div>
<div class={`text-[18px] font-semibold mt-0.5 ${color === 'success' ? 'text-success' : 'text-warn'}`}>{v}</div>
</div>
))}
</div>
<div>
<p class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2">Last 14 days</p>
<div class="flex items-end gap-1.5 h-28">
{[18,24,32,28,40,36,44,52,48,58,56,62,68,72].map((h) => (
<div class="flex-1 rounded-t-md bg-brand/60 dark:bg-brand/70" style={`height:${h}%`} />
))}
</div>
</div>
</Card>
</div>
</div>
</Section>
<!-- QUOTE ------------------------------------------------------------- -->
<Section>
<div class="max-w-4xl mx-auto text-center">
<Icon name="star" size={20} class="text-warn mx-auto mb-4" />
<p class="text-2xl md:text-3xl font-medium tracking-tight leading-snug">
“We had eight mailboxes burning out a week with our old tool. Switched to Warmbly, rebuilt the pool placement, dropped to one mailbox a quarter. The reply rate doubled because half our mail used to land in spam.”
</p>
<div class="mt-6 inline-flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-brand/20 inline-flex items-center justify-center text-brand font-semibold">M</div>
<div class="text-left">
<div class="text-sm font-semibold">Maya Okonkwo</div>
<div class="text-[12.5px] text-muted-foreground">Head of GTM, Northwind</div>
</div>
</div>
</div>
</Section>
<!-- CTA --------------------------------------------------------------- -->
<section class="border-t border-border bg-foreground text-background">
<div class="container-page py-20 md:py-24 text-center">
<h2 class="text-3xl md:text-5xl font-extrabold tracking-tight leading-tight">
Send cold email like you actually want it to work.
</h2>
<p class="mt-4 text-background/70 max-w-xl mx-auto">
Start free. Connect a mailbox. Warm it up. Send your first sequence in an afternoon.
</p>
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<a href="https://app.warmbly.com/register" class="inline-flex h-11 px-5 items-center rounded-md text-[15px] font-medium bg-background text-foreground hover:bg-background/90 transition-colors">
Start free
</a>
<a href="/product/" class="inline-flex h-11 px-5 items-center rounded-md text-[15px] font-medium border border-background/20 hover:bg-background/10 transition-colors">
Tour the product
</a>
</div>
</div>
</section>
</Layout>
+226
View File
@@ -0,0 +1,226 @@
@import 'tailwindcss';
@import '@fontsource/inter/400.css';
@import '@fontsource/inter/500.css';
@import '@fontsource/inter/600.css';
@import '@fontsource/inter/700.css';
@import '@fontsource/inter/800.css';
@custom-variant dark (&:is(.dark *));
/* ----------------------------------------------------------------
Color tokens — mirrors web/src/global.css OKLCH neutrals,
with brand sky/indigo accents for the marketing surface.
---------------------------------------------------------------- */
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.15 0.01 260);
--surface: oklch(0.985 0.002 260);
--surface-2: oklch(0.97 0.003 260);
--surface-3: oklch(0.94 0.005 260);
--muted: oklch(0.97 0.003 260);
--muted-foreground: oklch(0.50 0.01 260);
--border: oklch(0.92 0.005 260);
--border-strong: oklch(0.86 0.006 260);
--input: oklch(0.92 0.005 260);
--ring: oklch(0.55 0.01 260);
--card: oklch(1 0 0);
--card-foreground: oklch(0.15 0.01 260);
/* Brand */
--brand: oklch(0.55 0.18 252);
--brand-soft: oklch(0.93 0.05 252);
--brand-strong: oklch(0.42 0.20 256);
--brand-foreground: oklch(0.98 0 0);
--accent: oklch(0.96 0.005 260);
--accent-foreground: oklch(0.20 0.01 260);
--success: oklch(0.65 0.15 152);
--warn: oklch(0.78 0.15 80);
--danger: oklch(0.60 0.22 27);
--primary: oklch(0.20 0.01 260);
--primary-foreground: oklch(1 0 0);
}
.dark {
--background: oklch(0.13 0.01 260);
--foreground: oklch(0.95 0.005 260);
--surface: oklch(0.16 0.01 260);
--surface-2: oklch(0.18 0.01 260);
--surface-3: oklch(0.22 0.01 260);
--muted: oklch(0.20 0.01 260);
--muted-foreground: oklch(0.65 0.01 260);
--border: oklch(0.26 0.01 260);
--border-strong: oklch(0.32 0.01 260);
--input: oklch(0.26 0.01 260);
--ring: oklch(0.62 0.02 252);
--card: oklch(0.16 0.01 260);
--card-foreground: oklch(0.95 0.005 260);
--brand: oklch(0.72 0.15 250);
--brand-soft: oklch(0.28 0.10 252);
--brand-strong: oklch(0.82 0.15 250);
--brand-foreground: oklch(0.13 0.01 260);
--accent: oklch(0.22 0.01 260);
--accent-foreground: oklch(0.95 0.005 260);
--success: oklch(0.72 0.16 152);
--warn: oklch(0.78 0.15 80);
--danger: oklch(0.70 0.20 22);
--primary: oklch(0.95 0.005 260);
--primary-foreground: oklch(0.13 0.01 260);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-surface: var(--surface);
--color-surface-2: var(--surface-2);
--color-surface-3: var(--surface-3);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-border: var(--border);
--color-border-strong: var(--border-strong);
--color-input: var(--input);
--color-ring: var(--ring);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-brand: var(--brand);
--color-brand-soft: var(--brand-soft);
--color-brand-strong: var(--brand-strong);
--color-brand-foreground: var(--brand-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-success: var(--success);
--color-warn: var(--warn);
--color-danger: var(--danger);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
}
/* ----------------------------------------------------------------
Base
---------------------------------------------------------------- */
html {
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans);
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
::selection {
background: color-mix(in oklab, var(--brand) 25%, transparent);
color: var(--foreground);
}
* {
border-color: var(--border);
}
/* ----------------------------------------------------------------
Reusable patterns
---------------------------------------------------------------- */
@utility container-page {
width: 100%;
margin-inline: auto;
padding-inline: 1.25rem;
max-width: 80rem;
@media (min-width: 768px) { padding-inline: 2rem; }
}
@utility prose-warmbly {
color: var(--foreground);
font-size: 1rem;
line-height: 1.7;
}
/* Tiny grid pattern used by hero sections. */
.bg-dotgrid {
background-image: radial-gradient(circle, color-mix(in oklab, var(--foreground) 12%, transparent) 1px, transparent 1px);
background-size: 24px 24px;
}
.bg-linegrid {
background-image:
linear-gradient(to right, color-mix(in oklab, var(--foreground) 6%, transparent) 1px, transparent 1px),
linear-gradient(to bottom, color-mix(in oklab, var(--foreground) 6%, transparent) 1px, transparent 1px);
background-size: 56px 56px;
}
.hairline {
border-bottom: 1px solid var(--border);
}
/* Subtle background glow behind hero. */
.hero-glow::before {
content: '';
position: absolute;
inset: -10% -20% auto -20%;
height: 70%;
background: radial-gradient(60% 60% at 50% 0%, color-mix(in oklab, var(--brand) 22%, transparent), transparent 70%);
pointer-events: none;
z-index: 0;
}
/* Inline code style for the docs/learn pages. */
.kbd {
font-family: var(--font-mono);
font-size: 0.78em;
padding: 0.1em 0.45em;
border-radius: 4px;
background: var(--surface-2);
border: 1px solid var(--border);
}
/* Animations */
@keyframes float-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-float-up {
animation: float-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes pulse-soft {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
}
.animate-pulse-soft { animation: pulse-soft 2.4s ease-in-out infinite; }
-16
View File
@@ -1,16 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {
fontFamily: {
'sans': ['Inter', 'sans-serif'],
'ai-glow': '0 0 10px #f0f, 0 0 20px #0ff, 0 0 30px #f0f',
},
transitionTimingFunction: {
'bezier': 'cubic-bezier(.53,-0.14,.4,1.28)',
},
},
},
plugins: [],
};
+1 -5
View File
@@ -1,9 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
"exclude": ["dist"]
}