mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-11 00:05:01 +00:00
site(header): cooler mega-menu + trim to 6 meaningful products
- Removed /product, /security, /workers (Footer, 404, privacy, dpa, trust references updated). - Mega-menu now 620px wide with a sky-tinted header strip showing 'The platform · 6 systems', a 2-col grid of products where each item has a 40x40 icon tile that goes from quiet surface ring to a sky gradient on hover (with a sky drop-shadow), current page is shown with the gradient tile active + a sky dot next to the name. Footer band kept (GitHub link + pricing CTA).
This commit is contained in:
@@ -5,7 +5,6 @@ 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/' },
|
||||
@@ -47,7 +46,6 @@ const groups: { title: string; links: { label: string; href: string }[] }[] = [
|
||||
{ label: 'About', href: '/about/' },
|
||||
{ label: 'Contact', href: '/contact/' },
|
||||
{ label: 'Brand', href: '/brand/' },
|
||||
{ label: 'Security', href: '/security/' },
|
||||
{ label: 'Trust', href: '/trust/' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -10,30 +10,20 @@ const { onSky = false } = Astro.props;
|
||||
|
||||
const path = Astro.url.pathname;
|
||||
|
||||
// Product mega-menu items grouped into two columns
|
||||
const productGroups = [
|
||||
{
|
||||
label: 'Send & deliver',
|
||||
items: [
|
||||
{ icon: 'flame', href: '/warmup/', title: 'Warmup', desc: 'Reputation-safe pools and gradual ramps.' },
|
||||
{ icon: 'send', href: '/sending/', title: 'Sending engine', desc: 'Distributed across mailboxes, workers, IPs.' },
|
||||
{ icon: 'cpu', href: '/workers/', title: 'Workers', desc: 'One worker per machine, never NAT-shared.' },
|
||||
{ icon: 'shield', href: '/security/', title: 'Security', desc: 'Envelope encryption with AWS KMS.' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Engage & analyse',
|
||||
items: [
|
||||
{ icon: 'inbox', href: '/inbox/', title: 'Unified inbox', desc: 'Every reply across every mailbox, classified.' },
|
||||
{ icon: 'workflow', href: '/campaigns/', title: 'Campaigns', desc: 'Sequences with branching and A/B variants.' },
|
||||
{ icon: 'contact', href: '/crm/', title: 'CRM', desc: 'Pipelines, deals and tasks next to sending.' },
|
||||
{ icon: 'chart', href: '/analytics/', title: 'Analytics', desc: 'Placement, complaints, bounces, by mailbox.' },
|
||||
],
|
||||
},
|
||||
// Product mega-menu items
|
||||
const products = [
|
||||
{ icon: 'flame', href: '/warmup/', title: 'Warmup', desc: 'Reputation-safe pools, gradual ramps.' },
|
||||
{ icon: 'send', href: '/sending/', title: 'Sending engine', desc: 'Distributed across mailboxes and IPs.' },
|
||||
{ icon: 'inbox', href: '/inbox/', title: 'Unified inbox', desc: 'Every reply, classified on arrival.' },
|
||||
{ icon: 'workflow', href: '/campaigns/', title: 'Campaigns', desc: 'Sequences with branching and variants.' },
|
||||
{ icon: 'chart', href: '/analytics/', title: 'Analytics', desc: 'Placement, complaints, bounces per mailbox.' },
|
||||
{ icon: 'contact', href: '/crm/', title: 'CRM', desc: 'Pipelines, deals and tasks next to sending.' },
|
||||
{ icon: 'cpu', href: '/workers/', title: 'Workers', desc: 'One worker per machine, isolated IPs.' },
|
||||
{ icon: 'shield', href: '/security/', title: 'Security', desc: 'KMS envelope encryption, audit log.' },
|
||||
];
|
||||
|
||||
const nav: { label: string; href: string; match?: string; mega?: boolean }[] = [
|
||||
{ label: 'Product', href: '/product/', match: '/product', mega: true },
|
||||
{ label: 'Product', href: '/warmup/', match: '/warmup|/sending|/inbox|/campaigns|/analytics|/crm', mega: true },
|
||||
{ label: 'Deliverability',href: '/deliverability/',match: '/deliverability' },
|
||||
{ label: 'Pricing', href: '/pricing/', match: '/pricing' },
|
||||
{ label: 'Learn', href: '/learn/', match: '/learn' },
|
||||
@@ -41,7 +31,9 @@ const nav: { label: string; href: string; match?: string; mega?: boolean }[] = [
|
||||
|
||||
function active(match?: string) {
|
||||
if (!match) return false;
|
||||
return path === match || path.startsWith(match + '/');
|
||||
// Support pipe-delimited multi-match for the Product mega item
|
||||
const parts = match.split('|');
|
||||
return parts.some((m) => path === m || path.startsWith(m + '/'));
|
||||
}
|
||||
---
|
||||
<header
|
||||
@@ -75,71 +67,46 @@ function active(match?: string) {
|
||||
</a>
|
||||
|
||||
<!-- Mega-menu panel -->
|
||||
<div class="mega-panel absolute left-0 top-full pt-2 pointer-events-none opacity-0 transition-all duration-200 ease-out" style="width: 720px; max-width: 90vw;">
|
||||
<div class="rounded-[16px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: 0 24px 60px -20px rgba(15,23,42,0.25), 0 8px 24px -8px rgba(15,23,42,0.08);">
|
||||
|
||||
<!-- Top: overview + columns -->
|
||||
<div class="grid grid-cols-[1.4fr_1fr] gap-px bg-[color:var(--border)]">
|
||||
<!-- Featured: product overview -->
|
||||
<a href="/product/" class="group bg-white p-5 hover:bg-[color:var(--sky-1)]/40 transition-colors">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-9 h-9 rounded-[10px] bg-gradient-to-br from-[#0284c7] to-[#075985] inline-flex items-center justify-center shrink-0">
|
||||
<Icon name="layers" size={17} class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[13.5px] font-semibold text-heading group-hover:text-[#0369a1] transition-colors">Product overview</div>
|
||||
<div class="mt-0.5 text-[11.5px] text-foreground/65 leading-snug">Nine systems, one platform.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 ml-12 inline-flex items-center gap-1 text-[11.5px] font-medium text-[#0369a1] opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
See all <Icon name="arrowRight" size={11} />
|
||||
</div>
|
||||
</a>
|
||||
<!-- Quick links -->
|
||||
<div class="bg-[color:var(--surface-1)] p-5">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3">Resources</div>
|
||||
<div class="space-y-2">
|
||||
<a href="/deliverability/" class="block text-[12.5px] text-foreground/75 hover:text-[#0369a1] transition-colors">Deliverability →</a>
|
||||
<a href="/developers/" class="block text-[12.5px] text-foreground/75 hover:text-[#0369a1] transition-colors">API & webhooks →</a>
|
||||
<a href="/integrations/" class="block text-[12.5px] text-foreground/75 hover:text-[#0369a1] transition-colors">Integrations →</a>
|
||||
<a href="/changelog/" class="block text-[12.5px] text-foreground/75 hover:text-[#0369a1] transition-colors">Changelog →</a>
|
||||
</div>
|
||||
<div class="mega-panel absolute left-0 top-full pt-2 pointer-events-none opacity-0 transition-all duration-200 ease-out" style="width: 620px; max-width: 92vw;">
|
||||
<div class="rounded-[16px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden" style="box-shadow: 0 28px 70px -22px rgba(2,32,71,0.32), 0 10px 28px -10px rgba(15,23,42,0.10);">
|
||||
<!-- Subtle sky gradient header strip -->
|
||||
<div class="relative px-4 py-2.5 border-b border-[color:var(--border)] overflow-hidden">
|
||||
<div class="absolute inset-0 pointer-events-none" style="background: linear-gradient(180deg, rgba(186,230,253,0.55) 0%, rgba(186,230,253,0) 100%);"></div>
|
||||
<div class="relative flex items-center justify-between">
|
||||
<div class="text-[10px] uppercase tracking-[0.22em] font-mono text-[#075985]">The platform</div>
|
||||
<div class="text-[10px] uppercase tracking-[0.22em] font-mono text-muted-foreground">{products.length} systems</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product groups grid -->
|
||||
<div class="grid grid-cols-2 gap-px bg-[color:var(--border)] border-t border-[color:var(--border)]">
|
||||
{productGroups.map((g) => (
|
||||
<div class="bg-white p-5">
|
||||
<div class="text-[10px] uppercase tracking-[0.18em] font-mono text-muted-foreground mb-3 flex items-center gap-2">
|
||||
<span>{g.label}</span>
|
||||
<span class="flex-1 h-px bg-[color:var(--border)]"></span>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
{g.items.map((it) => (
|
||||
<a href={it.href} class="mega-item group flex items-start gap-3 p-2 -mx-2 rounded-[8px] hover:bg-[color:var(--sky-1)]/50 transition-colors">
|
||||
<div class="w-8 h-8 rounded-[8px] bg-[color:var(--surface-1)] text-[#0284c7] ring-1 ring-[color:var(--border)] group-hover:bg-white group-hover:ring-[color:var(--sky-3)] inline-flex items-center justify-center shrink-0 transition-all">
|
||||
<Icon name={it.icon} size={15} />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[13px] font-semibold text-heading group-hover:text-[#0369a1] transition-colors">{it.title}</div>
|
||||
<div class="text-[11.5px] text-foreground/60 leading-snug truncate">{it.desc}</div>
|
||||
</div>
|
||||
<Icon name="arrowRight" size={11} class="text-foreground/30 group-hover:text-[#0369a1] group-hover:translate-x-0.5 transition-all mt-2 shrink-0" />
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<!-- 2-column product grid -->
|
||||
<div class="p-3 grid grid-cols-2 gap-1.5">
|
||||
{products.map((it) => {
|
||||
const isCurrent = path === it.href || path.startsWith(it.href);
|
||||
return (
|
||||
<a href={it.href} aria-current={isCurrent ? 'page' : undefined} class={`mega-item group relative flex items-start gap-3.5 p-3.5 rounded-[12px] transition-all ${isCurrent ? 'bg-[color:var(--sky-1)]/70 ring-1 ring-[color:var(--sky-3)]' : 'hover:bg-[color:var(--sky-1)]/60 ring-1 ring-transparent hover:ring-[color:var(--border)]'}`}>
|
||||
<div class={`w-10 h-10 rounded-[10px] inline-flex items-center justify-center shrink-0 transition-all ${isCurrent ? 'bg-gradient-to-br from-[#0284c7] to-[#075985] text-white shadow-[0_4px_12px_-2px_rgba(2,132,199,0.4)]' : 'bg-[color:var(--surface-1)] text-[#0284c7] ring-1 ring-[color:var(--border)] group-hover:bg-gradient-to-br group-hover:from-[#0284c7] group-hover:to-[#075985] group-hover:text-white group-hover:ring-transparent group-hover:shadow-[0_4px_12px_-2px_rgba(2,132,199,0.4)]'}`}>
|
||||
<Icon name={it.icon} size={17} />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class={`text-[13.5px] font-semibold transition-colors ${isCurrent ? 'text-[#0369a1]' : 'text-heading group-hover:text-[#0369a1]'}`}>{it.title}</span>
|
||||
{isCurrent && <span class="w-1.5 h-1.5 rounded-full bg-[#0284c7]"></span>}
|
||||
</div>
|
||||
<div class="mt-0.5 text-[11.5px] text-foreground/60 leading-snug">{it.desc}</div>
|
||||
</div>
|
||||
<Icon name="arrowRight" size={11} class={`absolute right-3 top-1/2 -translate-y-1/2 transition-all ${isCurrent ? 'text-[#0369a1]' : 'text-foreground/0 group-hover:text-[#0369a1] group-hover:translate-x-0'}`} />
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="bg-[color:var(--surface-1)] px-5 py-3 border-t border-[color:var(--border)] flex items-center justify-between text-[11.5px]">
|
||||
<a href="https://github.com/warmbly/warmbly" class="inline-flex items-center gap-1.5 font-mono text-foreground/70 hover:text-foreground transition-colors">
|
||||
<!-- Footer band -->
|
||||
<div class="bg-[color:var(--surface-1)] px-4 py-3 border-t border-[color:var(--border)] flex items-center justify-between text-[11.5px]">
|
||||
<a href="https://github.com/warmbly/warmbly" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 font-mono text-foreground/70 hover:text-foreground transition-colors">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.8 10.9.6.1.8-.2.8-.5v-2c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.4-2.3 1.2-3.2-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2 1-.3 2-.4 3-.4s2 .1 3 .4c2.3-1.5 3.3-1.2 3.3-1.2.7 1.6.2 2.9.1 3.1.8.8 1.2 1.9 1.2 3.2 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.5 4.6-1.5 7.8-5.8 7.8-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>
|
||||
Open source · Apache 2.0
|
||||
</a>
|
||||
<a href="/pricing/" class="inline-flex items-center gap-1 font-medium text-[#0369a1] hover:text-[#075985]">
|
||||
<a href="/pricing/" class="inline-flex items-center gap-1 font-semibold text-[#0369a1] hover:text-[#075985]">
|
||||
See pricing <Icon name="arrowRight" size={11} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ import Icon from '../components/Icon.astro';
|
||||
|
||||
<div class="mt-12 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-[12px] font-mono text-white/55">
|
||||
{[
|
||||
['Product', '/product/'],
|
||||
['Warmup', '/warmup/'],
|
||||
['Pricing', '/pricing/'],
|
||||
['Changelog', '/changelog/'],
|
||||
['Contact', '/contact/'],
|
||||
|
||||
@@ -41,7 +41,7 @@ import Legal from '../layouts/Legal.astro';
|
||||
|
||||
<h2>6. Security</h2>
|
||||
<p>
|
||||
Warmbly implements appropriate technical and organisational measures, including envelope encryption with AWS KMS, AES-256-GCM at rest, TLS in transit, role-based access control, audit logging, and the security controls described at <a href="/security/">/security</a>.
|
||||
Warmbly implements appropriate technical and organisational measures, including envelope encryption with AWS KMS, AES-256-GCM at rest, TLS in transit, role-based access control, audit logging, and the security controls described at <a href="/trust/">/security</a>.
|
||||
</p>
|
||||
|
||||
<h2>7. Sub-processors</h2>
|
||||
|
||||
@@ -87,7 +87,7 @@ import Legal from '../layouts/Legal.astro';
|
||||
|
||||
<h2>8. Security</h2>
|
||||
<p>
|
||||
We protect customer data with envelope encryption (AES-256-GCM with KMS-derived data keys), TLS in transit, strict access controls, audit logging, and ongoing security review. Details at <a href="/security/">/security</a>.
|
||||
We protect customer data with envelope encryption (AES-256-GCM with KMS-derived data keys), TLS in transit, strict access controls, audit logging, and ongoing security review. Details at <a href="/trust/">/security</a>.
|
||||
</p>
|
||||
|
||||
<h2>9. Children</h2>
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Cloud from '../components/Cloud.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
import CTA from '../components/CTA.astro';
|
||||
|
||||
const pillars = [
|
||||
{
|
||||
n: '01',
|
||||
icon: 'flame',
|
||||
title: 'Warmup',
|
||||
href: '/warmup/',
|
||||
body: 'Reputation-safe pools, gradual ramps, automatic quarantine. Free and premium pools never mix.',
|
||||
bullets: [
|
||||
'Start at 10 / day · ramp +1 / day · ceiling 40',
|
||||
'Spam-score tracking + invalid token detection',
|
||||
'Auto-block at 3 invalid tokens / 24h or score > 50',
|
||||
'Recovery pool for cooldown senders',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '02',
|
||||
icon: 'send',
|
||||
title: 'Sending engine',
|
||||
href: '/sending/',
|
||||
body: 'Workers distribute campaign mail across machines. Per-mailbox caps, per-worker concentration limits.',
|
||||
bullets: [
|
||||
'Default 50 / day cold cap per mailbox',
|
||||
'Minimum 10-minute gap between sends',
|
||||
'Worker volume = sum of mailbox budgets',
|
||||
'Native Gmail, Outlook, SMTP / IMAP',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '03',
|
||||
icon: 'inbox',
|
||||
title: 'Unified inbox',
|
||||
href: '/inbox/',
|
||||
body: 'Every reply, OOO, bounce and click across every mailbox in one console. Assign, snooze, label, search.',
|
||||
bullets: [
|
||||
'Reply detection that survives quoted text',
|
||||
'OOO classification with auto-suppress',
|
||||
'Thread-level assignment to teammates',
|
||||
'Search across all connected mailboxes',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '04',
|
||||
icon: 'workflow',
|
||||
title: 'Sequences',
|
||||
href: '/campaigns/',
|
||||
body: 'Multi-step sequences with conditions, A/B variants and time-zone aware sending.',
|
||||
bullets: [
|
||||
'Step delays in business hours by recipient TZ',
|
||||
'A/B variants on subject and body',
|
||||
'If-replied / if-opened / if-clicked branching',
|
||||
'Plain-text first. HTML is opt-in.',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '05',
|
||||
icon: 'chart',
|
||||
title: 'Analytics',
|
||||
href: '/analytics/',
|
||||
body: 'Placement, complaint rate, bounce rate, warmup health by mailbox, pool and worker. Not just opens.',
|
||||
bullets: [
|
||||
'Inbox vs spam placement per mailbox',
|
||||
"Complaint rate vs Google's 0.1% / 0.3% bands",
|
||||
"Bounce rate vs SES's 5% / 10% bands",
|
||||
'Health: healthy → watch → quarantined',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '06',
|
||||
icon: 'contact',
|
||||
title: 'Contacts & CRM',
|
||||
href: '/crm/',
|
||||
body: 'Light CRM with pipelines, deals and tasks. Imports, dedup, categories, suppression-aware.',
|
||||
bullets: [
|
||||
'CSV / XLSX / JSON import with column mapping',
|
||||
'Dedup by email, domain or custom key',
|
||||
'Categories instead of tag soup',
|
||||
'Suppression list shared across campaigns',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '07',
|
||||
icon: 'cpu',
|
||||
title: 'Workers',
|
||||
href: '/workers/',
|
||||
body: 'Distributed execution plane. Shared free, shared premium, or dedicated to your org.',
|
||||
bullets: [
|
||||
'Kafka-driven command / result topics',
|
||||
'Per-worker IP identity, no shared NAT',
|
||||
'Live rebalance and migration',
|
||||
'Lightweight: no Postgres on workers',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '08',
|
||||
icon: 'shield',
|
||||
title: 'Security',
|
||||
href: '/security/',
|
||||
body: 'Envelope encryption with AWS KMS, per-user data keys, cached decryption, audit log.',
|
||||
bullets: [
|
||||
'AES-256-GCM via KMS-generated DEKs',
|
||||
'Encrypted DEK stored, plaintext cached briefly',
|
||||
'Workers never touch Postgres',
|
||||
'Admin actions audited',
|
||||
],
|
||||
},
|
||||
{
|
||||
n: '09',
|
||||
icon: 'webhook',
|
||||
title: 'API & webhooks',
|
||||
href: '/developers/',
|
||||
body: 'Programmatic control over mailboxes, campaigns, contacts. Signed webhooks for every event.',
|
||||
bullets: [
|
||||
'REST with idempotency keys',
|
||||
'HMAC-signed webhooks',
|
||||
'Per-key scopes and rotation',
|
||||
'Rate limits surfaced in headers',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const flow = [
|
||||
{ label: 'Connect mailbox', sub: 'OAuth · SMTP · IMAP' },
|
||||
{ label: 'Warm reputation', sub: 'Premium pool · gradual ramp' },
|
||||
{ label: 'Send sequences', sub: 'Distributed across workers' },
|
||||
{ label: 'Triage replies', sub: 'Unified inbox · classification' },
|
||||
{ label: 'Score health', sub: 'Placement · complaints · quarantine' },
|
||||
];
|
||||
|
||||
const principles = [
|
||||
['Mailbox first, not worker first', "A worker's safe send volume is the sum of its mailbox budgets. We don't reward forcing one inbox to do the work of ten."],
|
||||
['Quarantine before providers do', 'Shared pool thresholds are stricter than Google or Microsoft enforcement, so reputation never gets the chance to slip publicly.'],
|
||||
['Plain-text by default', "Cold mail is a person talking to a person. HTML is opt-in. We don't add tracking pixels or branded footers without permission."],
|
||||
['No black boxes', 'Every score, suppression and quarantine has a reason you can read. No "the algorithm decided."'],
|
||||
['Reputation is shared infrastructure', 'A bad sender in a shared pool taxes everyone. We act on warning bands, not catastrophic bands.'],
|
||||
['Workers are replaceable', 'Worker state is disposable. We migrate a mailbox between workers without you noticing.'],
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Product · Warmbly"
|
||||
description="Warmbly is warmup, sending, inbox, analytics and CRM in one platform. Open source, self-hostable, mailbox-first."
|
||||
>
|
||||
<!-- HERO: sky band with the system flow rendered inline -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[440px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
<div class="absolute -top-8 left-[-60px] w-[300px] opacity-25 pointer-events-none cloud-drift cloud-2" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={2} opacity={1} />
|
||||
</div>
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-20 md:pb-28">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-4">Product</div>
|
||||
<h1 class="text-[36px] sm:text-[52px] md:text-[64px] font-semibold tracking-[-0.03em] leading-[1.02] text-white max-w-3xl">
|
||||
Nine systems.<br/>One platform.
|
||||
</h1>
|
||||
<p class="mt-5 text-[15.5px] md:text-[17px] text-white/75 max-w-2xl leading-relaxed">
|
||||
Each piece feeds the next. Warmup gathers the signal sending throttles on. Inbox classifies the replies analytics scores on. Nothing exists in a vacuum.
|
||||
</p>
|
||||
|
||||
<!-- Flow strip -->
|
||||
<div class="mt-14 grid grid-cols-2 md:grid-cols-5 gap-y-6">
|
||||
{flow.map((f, i) => (
|
||||
<div class="relative flex flex-col items-start md:items-center text-left md:text-center">
|
||||
<div class="flex items-center w-full">
|
||||
<div class="shrink-0 inline-flex items-center justify-center w-9 h-9 rounded-full bg-white/12 ring-1 ring-white/25 text-[12px] font-mono text-white/90">
|
||||
{String(i + 1).padStart(2, '0')}
|
||||
</div>
|
||||
{i < flow.length - 1 && (
|
||||
<div class="hidden md:block flex-1 h-px bg-gradient-to-r from-white/30 to-white/5 mx-2"></div>
|
||||
)}
|
||||
</div>
|
||||
<div class="mt-3 text-[13.5px] font-medium text-white">{f.label}</div>
|
||||
<div class="mt-0.5 text-[11.5px] text-white/55 font-mono">{f.sub}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PILLARS: numbered horizontal rows, alternating accent side -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">The platform</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading max-w-2xl">
|
||||
Built so each part makes the next one safer.
|
||||
</h2>
|
||||
|
||||
<div class="mt-14 space-y-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{pillars.map((p, i) => (
|
||||
<a href={p.href} class="group block bg-white hover:bg-[color:var(--sky-1)]/40 transition-colors">
|
||||
<div class="grid grid-cols-12 gap-4 lg:gap-8 p-6 md:p-8 items-start">
|
||||
<div class="col-span-12 md:col-span-2 flex md:flex-col items-center md:items-start gap-3 md:gap-2">
|
||||
<div class="text-[11px] font-mono text-muted-foreground">{p.n}</div>
|
||||
<div class="inline-flex items-center justify-center w-10 h-10 rounded-[10px] bg-[color:var(--sky-1)] text-[color:var(--sky-6)] ring-1 ring-[color:var(--sky-2)]">
|
||||
<Icon name={p.icon} size={18} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4">
|
||||
<h3 class="text-[18px] font-semibold tracking-[-0.01em] text-heading">{p.title}</h3>
|
||||
<p class="mt-2 text-[14px] text-foreground/75 leading-relaxed">{p.body}</p>
|
||||
<div class="mt-3 inline-flex items-center gap-1.5 text-[13px] font-medium text-[color:var(--brand)] group-hover:translate-x-0.5 transition-transform">
|
||||
Read more <Icon name="arrowRight" size={12} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<ul class="grid sm:grid-cols-2 gap-x-6 gap-y-2">
|
||||
{p.bullets.map((b) => (
|
||||
<li class="flex items-start gap-2 text-[13px] text-foreground/80">
|
||||
<span class="mt-1.5 w-1 h-1 rounded-full bg-[color:var(--sky-5)] shrink-0"></span>
|
||||
<span>{b}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PRINCIPLES: mono-style annotated list -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="grid lg:grid-cols-[1fr_2fr] gap-12 lg:gap-20">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Design principles</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading">
|
||||
Opinionated where it matters. Quiet everywhere else.
|
||||
</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-[color:var(--border)]">
|
||||
{principles.map(([title, body], i) => (
|
||||
<div class="py-5 grid grid-cols-[40px_1fr] gap-4 first:pt-0">
|
||||
<div class="text-[11px] font-mono text-muted-foreground pt-1">{String(i + 1).padStart(2, '0')}</div>
|
||||
<div>
|
||||
<div class="text-[15.5px] font-semibold text-heading tracking-[-0.01em]">{title}</div>
|
||||
<p class="mt-1.5 text-[14px] text-foreground/75 leading-relaxed">{body}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA />
|
||||
</Layout>
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Cloud from '../components/Cloud.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
import CTA from '../components/CTA.astro';
|
||||
|
||||
const layers = [
|
||||
['Edge', 'Cloudflare · WAF · DDoS · Turnstile'],
|
||||
['Auth', 'OAuth · CAPTCHA · session pinning'],
|
||||
['App', 'Per-user rate limits · idempotency · webhook HMAC'],
|
||||
['Encryption', 'AWS KMS · per-user DEKs · AES-256-GCM · brief Redis cache'],
|
||||
['Storage', 'Postgres (control) · DynamoDB (DEKs) · S3 (payloads)'],
|
||||
['Execution', 'Worker plane · no SQL · per-machine IP · Kafka topics'],
|
||||
];
|
||||
|
||||
const compliance = {
|
||||
today: [
|
||||
'GDPR-aligned data handling',
|
||||
'DPA signature on request · 48h SLA',
|
||||
'Subprocessor list published + versioned',
|
||||
'Per-org audit log of admin actions',
|
||||
'Configurable data residency (US / EU) on Business',
|
||||
'TLS 1.2+ for all in-transit traffic',
|
||||
'Per-user data encryption keys backed by AWS KMS',
|
||||
],
|
||||
progress: [
|
||||
'SOC 2 Type II audit',
|
||||
'ISO 27001 gap assessment',
|
||||
'HIPAA covered-entity BAA pilot',
|
||||
'Customer-managed KMS keys (BYOK)',
|
||||
'Tenant-scoped IP allowlists for the API',
|
||||
'Hardware-key SSO enforcement',
|
||||
],
|
||||
};
|
||||
|
||||
const controls = [
|
||||
['CAPTCHA at auth', 'Cloudflare Turnstile on login, registration, password reset and confirmation flows.'],
|
||||
['API rate limiting', 'Per-user category-aware rate limiting backed by Redis and plan limits.'],
|
||||
['Realtime throttling', 'WebSocket joins, messages and events rate-limited as an anti-abuse boundary.'],
|
||||
['Event dedupe', 'Tracking events deduped at edge and again in the consumer. Idempotency keys everywhere.'],
|
||||
['Suppression', 'Bounce, complaint and unsubscribe signals suppress recipients across all campaigns.'],
|
||||
['Mailbox-sync limits', '100 new emails / 5min burst, 500 / hour ceiling. Exceeded mailboxes are rate-limited.'],
|
||||
['Admin enforcement', 'Manual ban / unban / rate-limit override with full audit history.'],
|
||||
['Webhook signatures', 'HMAC-signed webhooks with rotation primitives in the dashboard.'],
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Security · Warmbly"
|
||||
description="Envelope encryption with AWS KMS, per-user data keys, audit logs, and a worker plane that never touches your relational database."
|
||||
>
|
||||
<!-- HERO with envelope-encryption flow diagram inline -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[420px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-16 md:pb-20 grid lg:grid-cols-[1fr_1.1fr] gap-12 items-center">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-4">Security</div>
|
||||
<h1 class="text-[36px] sm:text-[52px] md:text-[60px] font-semibold tracking-[-0.03em] leading-[1.02] text-white">
|
||||
Encrypted at rest.<br/>Boundary-respecting<br/>by design.
|
||||
</h1>
|
||||
<p class="mt-5 text-[15.5px] md:text-[17px] text-white/75 max-w-xl leading-relaxed">
|
||||
Mailbox credentials, refresh tokens and message payloads are envelope-encrypted with per-user data keys. Workers never open a Postgres connection.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Envelope encryption diagram -->
|
||||
<div>
|
||||
<svg viewBox="0 0 420 340" class="w-full h-auto">
|
||||
<defs>
|
||||
<linearGradient id="secStroke" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#7dd3fc" stop-opacity="0.6"/>
|
||||
<stop offset="100%" stop-color="#7dd3fc" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- KMS -->
|
||||
<rect x="40" y="40" width="120" height="56" rx="10" fill="rgba(255,255,255,0.08)" stroke="#7dd3fc" stroke-opacity="0.55"/>
|
||||
<text x="100" y="62" text-anchor="middle" fill="#7dd3fc" font-family="ui-monospace,monospace" font-size="9" letter-spacing="1.5">ROOT KEY</text>
|
||||
<text x="100" y="80" text-anchor="middle" fill="#fff" font-size="13" font-weight="600">AWS KMS</text>
|
||||
|
||||
<!-- DEK -->
|
||||
<rect x="40" y="142" width="120" height="56" rx="10" fill="rgba(255,255,255,0.08)" stroke="#7dd3fc" stroke-opacity="0.45"/>
|
||||
<text x="100" y="164" text-anchor="middle" fill="#7dd3fc" font-family="ui-monospace,monospace" font-size="9" letter-spacing="1.5">PER-USER DEK</text>
|
||||
<text x="100" y="182" text-anchor="middle" fill="#fff" font-size="13" font-weight="600">AES-256-GCM</text>
|
||||
|
||||
<!-- Encrypted DEK in DDB -->
|
||||
<rect x="240" y="142" width="140" height="56" rx="10" fill="rgba(255,255,255,0.04)" stroke="#7dd3fc" stroke-opacity="0.3" stroke-dasharray="4 4"/>
|
||||
<text x="310" y="164" text-anchor="middle" fill="#7dd3fc" font-family="ui-monospace,monospace" font-size="9" letter-spacing="1.5">ENCRYPTED · STORED</text>
|
||||
<text x="310" y="182" text-anchor="middle" fill="#fff" font-size="13" font-weight="600">DynamoDB</text>
|
||||
|
||||
<!-- Payload -->
|
||||
<rect x="40" y="244" width="120" height="56" rx="10" fill="rgba(255,255,255,0.08)" stroke="#7dd3fc" stroke-opacity="0.45"/>
|
||||
<text x="100" y="266" text-anchor="middle" fill="#7dd3fc" font-family="ui-monospace,monospace" font-size="9" letter-spacing="1.5">PAYLOAD</text>
|
||||
<text x="100" y="284" text-anchor="middle" fill="#fff" font-size="13" font-weight="600">Sealed at rest</text>
|
||||
|
||||
<!-- Cache -->
|
||||
<rect x="240" y="244" width="140" height="56" rx="10" fill="rgba(255,255,255,0.04)" stroke="#7dd3fc" stroke-opacity="0.3" stroke-dasharray="4 4"/>
|
||||
<text x="310" y="266" text-anchor="middle" fill="#7dd3fc" font-family="ui-monospace,monospace" font-size="9" letter-spacing="1.5">SHORT TTL · CACHED</text>
|
||||
<text x="310" y="284" text-anchor="middle" fill="#fff" font-size="13" font-weight="600">Redis</text>
|
||||
|
||||
<!-- Arrows -->
|
||||
<line x1="100" y1="96" x2="100" y2="142" stroke="url(#secStroke)" stroke-width="1.5"/>
|
||||
<line x1="100" y1="198" x2="100" y2="244" stroke="url(#secStroke)" stroke-width="1.5"/>
|
||||
<line x1="160" y1="170" x2="240" y2="170" stroke="url(#secStroke)" stroke-width="1.5"/>
|
||||
<line x1="160" y1="272" x2="240" y2="272" stroke="url(#secStroke)" stroke-width="1.5"/>
|
||||
|
||||
<!-- moving dot -->
|
||||
<circle r="3" fill="#fff" opacity="0.95">
|
||||
<animateMotion dur="6s" repeatCount="indefinite" path="M 100 96 L 100 142 L 100 198 L 100 244 L 160 272 L 240 272"/>
|
||||
</circle>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- LAYER STACK -->
|
||||
<section class="py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Layered architecture</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Six layers, each with its own boundary.</h2>
|
||||
|
||||
<div class="mt-10 space-y-px rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{layers.map(([l, b], i) => (
|
||||
<div class="grid grid-cols-[64px_1fr] md:grid-cols-[80px_180px_1fr] gap-4 items-center px-5 py-4 bg-white border-t border-[color:var(--border)] first:border-t-0">
|
||||
<div class="text-[11px] font-mono text-muted-foreground">{String(i + 1).padStart(2, '0')}</div>
|
||||
<div class="text-[15px] font-semibold text-heading">{l}</div>
|
||||
<div class="col-span-2 md:col-span-1 text-[13px] font-mono text-foreground/70 break-words">{b}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ANTI-ABUSE CONTROLS -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Anti-abuse</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Layered controls, not one brittle gate.</h2>
|
||||
|
||||
<div class="mt-10 grid sm:grid-cols-2 lg:grid-cols-4 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{controls.map(([k, v]) => (
|
||||
<div class="bg-white p-5">
|
||||
<div class="text-[14px] font-semibold text-heading">{k}</div>
|
||||
<p class="mt-1.5 text-[12.5px] text-foreground/70 leading-relaxed">{v}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- COMPLIANCE: split today vs in-progress -->
|
||||
<section class="py-16 md:py-20">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Compliance</div>
|
||||
<h2 class="text-[26px] md:text-[34px] font-semibold tracking-[-0.025em] text-heading max-w-2xl">Where we are, honestly.</h2>
|
||||
|
||||
<div class="mt-10 grid md:grid-cols-2 gap-5">
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
<div class="text-[15.5px] font-semibold text-heading">Today</div>
|
||||
</div>
|
||||
<ul class="space-y-2.5">
|
||||
{compliance.today.map((l) => (
|
||||
<li class="flex items-start gap-2 text-[13.5px] text-foreground/85">
|
||||
<Icon name="check" size={13} class="text-emerald-600 mt-1 shrink-0" />
|
||||
<span>{l}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] p-6" style="box-shadow: var(--shadow-sm);">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span class="w-2 h-2 rounded-full bg-amber-500"></span>
|
||||
<div class="text-[15.5px] font-semibold text-heading">In progress</div>
|
||||
</div>
|
||||
<ul class="space-y-2.5">
|
||||
{compliance.progress.map((l) => (
|
||||
<li class="flex items-start gap-2 text-[13.5px] text-foreground/85">
|
||||
<Icon name="clock" size={13} class="text-amber-600 mt-1 shrink-0" />
|
||||
<span>{l}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Need our security package?"
|
||||
description="Pen test summaries, subprocessor list, DPA, SOC 2 progress letter and architecture overview, on request."
|
||||
primaryLabel="Request security pack"
|
||||
primaryHref="/contact/?topic=security"
|
||||
secondaryLabel="Read our trust page"
|
||||
secondaryHref="/trust/"
|
||||
/>
|
||||
</Layout>
|
||||
@@ -113,8 +113,8 @@ const subs = [
|
||||
<p class="mt-4 text-[14.5px] text-foreground/75 leading-relaxed max-w-md">
|
||||
Per-user data encryption keys. Encrypted DEK in DynamoDB, plaintext briefly cached. Workers never touch Postgres.
|
||||
</p>
|
||||
<a href="/security/" class="mt-5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">
|
||||
Read the full security posture <Icon name="arrowRight" size={12} />
|
||||
<a href="/contact/?topic=security" class="mt-5 inline-flex items-center gap-1.5 text-[13.5px] font-medium text-[color:var(--brand)] hover:text-[color:var(--brand-strong)]">
|
||||
Request the security pack <Icon name="arrowRight" size={12} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Cloud from '../components/Cloud.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
import CTA from '../components/CTA.astro';
|
||||
|
||||
const tiers = [
|
||||
{
|
||||
tier: 'Shared free',
|
||||
plan: 'Free trial',
|
||||
body: 'Pooled across free senders. Lower per-mailbox caps and conservative ramps.',
|
||||
bullets: ['Free warmup pool', 'Strict per-mailbox caps', 'Shared IPs'],
|
||||
},
|
||||
{
|
||||
tier: 'Shared premium',
|
||||
plan: 'Starter · Grow',
|
||||
body: 'Pooled across paid senders. Stricter quarantine policy than free.',
|
||||
bullets: ['Premium warmup pool', 'Default 50 / day cap', 'Shared paid IPs'],
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
tier: 'Dedicated',
|
||||
plan: 'Business · Enterprise',
|
||||
body: 'A worker process and IP allocated to your organisation. Still mailbox-first.',
|
||||
bullets: ['Dedicated worker process', 'Dedicated IP(s) · warmed', 'Custom data residency'],
|
||||
},
|
||||
];
|
||||
|
||||
const props = [
|
||||
{ icon: 'cpu', title: 'Per-machine identity', body: 'One worker process per machine. Each has its own IP. No shared NAT.' },
|
||||
{ icon: 'database', title: 'Disposable state', body: 'Workers store nothing locally that cannot be rebuilt from a Kafka replay.' },
|
||||
{ icon: 'workflow', title: 'Kafka-driven', body: 'Commands and results flow through worker-specific topics for traceability.' },
|
||||
{ icon: 'shield', title: 'No SQL on workers', body: 'Workers do not open Postgres. They talk to Kafka, KMS, DynamoDB, S3, Redis.' },
|
||||
{ icon: 'send', title: 'Migrable mailboxes', body: 'A mailbox can be reassigned between workers transparently.' },
|
||||
{ icon: 'layers', title: 'Concentration caps', body: 'A shared worker should not be a concentration point. Add more before each does more.' },
|
||||
];
|
||||
---
|
||||
<Layout
|
||||
title="Workers · Warmbly"
|
||||
description="Distributed worker plane that sends, syncs and tracks. Shared free, shared premium, or dedicated to your organisation."
|
||||
>
|
||||
<!-- HERO: split with worker fleet visualization -->
|
||||
<section class="relative isolate overflow-hidden text-white" style="background: radial-gradient(ellipse 130% 140% at 72% 28%, #0284c7 0%, #0369a1 25%, #075985 50%, #0c4a6e 80%, #0a3d5c 100%);">
|
||||
<div class="absolute top-0 right-0 w-[440px] opacity-30 pointer-events-none cloud-drift cloud-1" style="mix-blend-mode: screen;">
|
||||
<Cloud variant={5} opacity={1} />
|
||||
</div>
|
||||
|
||||
<div class="container-page relative pt-16 md:pt-20 pb-16 md:pb-20 grid lg:grid-cols-[1.1fr_1fr] gap-12 items-center">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-white/55 font-mono mb-4">Workers</div>
|
||||
<h1 class="text-[36px] sm:text-[52px] md:text-[60px] font-semibold tracking-[-0.03em] leading-[1.02] text-white">
|
||||
Many workers.<br/>Many IPs.<br/>By design.
|
||||
</h1>
|
||||
<p class="mt-5 text-[15.5px] md:text-[17px] text-white/75 leading-relaxed max-w-xl">
|
||||
Cold mail concentrated through one big sender is asking for trouble. Warmbly fans out: one worker per machine, each with its own network identity.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Worker fleet grid mock -->
|
||||
<div class="relative">
|
||||
<div class="grid grid-cols-5 gap-2 max-w-md mx-auto">
|
||||
{Array.from({ length: 20 }).map((_, i) => {
|
||||
const isYours = [6, 7, 11, 12].includes(i);
|
||||
const isPremium = [0, 1, 2, 3, 4, 5, 8, 9, 10, 13, 14, 15, 16, 17, 18, 19].includes(i);
|
||||
return (
|
||||
<div class={`relative rounded-[8px] p-2.5 ${isYours ? 'bg-white/15 ring-1 ring-white/45' : 'bg-white/[0.06] ring-1 ring-white/15'}`}>
|
||||
<div class="text-[9.5px] font-mono text-white/75">w-{String(i + 1).padStart(2, '0')}</div>
|
||||
<div class="mt-1.5 flex gap-0.5">
|
||||
{Array.from({ length: 6 }).map((_, j) => (
|
||||
<span class={`w-1 h-1 rounded-full ${isYours ? 'bg-emerald-300' : 'bg-sky-300/60'}`} style={`opacity: ${0.4 + (j % 3) * 0.2}`}></span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div class="mt-4 flex justify-center gap-4 text-[11px] text-white/70">
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-emerald-300"></span> Your dedicated</span>
|
||||
<span class="inline-flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full bg-sky-300/60"></span> Shared premium</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TIERS: 3-up with featured middle -->
|
||||
<section class="py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Three tiers</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading max-w-2xl">
|
||||
Three ways your mail moves.
|
||||
</h2>
|
||||
|
||||
<div class="mt-12 grid md:grid-cols-3 gap-5">
|
||||
{tiers.map((t) => (
|
||||
<div class={`relative rounded-[14px] bg-white p-6 ${t.featured ? 'ring-1 ring-[color:var(--sky-6)]/35 shadow-[0_30px_60px_-30px_rgba(2,132,199,0.4)]' : 'ring-1 ring-[color:var(--border)]'}`} style={t.featured ? '' : 'box-shadow: var(--shadow-sm);'}>
|
||||
<div class="text-[10.5px] uppercase tracking-[0.18em] font-mono text-[color:var(--sky-6)] mb-2">{t.plan}</div>
|
||||
<div class="text-[20px] font-semibold tracking-[-0.02em] text-heading">{t.tier}</div>
|
||||
<p class="mt-2 text-[13.5px] text-foreground/70 leading-relaxed">{t.body}</p>
|
||||
<ul class="mt-5 pt-5 border-t border-[color:var(--border)] space-y-2">
|
||||
{t.bullets.map((b) => (
|
||||
<li class="flex items-start gap-2 text-[13px] text-foreground/80">
|
||||
<Icon name="check" size={12} class="text-[color:var(--sky-6)] mt-1 shrink-0" />
|
||||
<span>{b}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- WORKER PROPERTIES: dense feature grid -->
|
||||
<section class="bg-[color:var(--surface-1)] border-y border-[color:var(--border)] py-16 md:py-24">
|
||||
<div class="container-page">
|
||||
<div class="text-[11px] uppercase tracking-[0.18em] text-muted-foreground font-mono mb-3">Why workers, not one big sender</div>
|
||||
<h2 class="text-[28px] md:text-[40px] font-semibold tracking-[-0.025em] leading-[1.06] text-heading max-w-2xl">
|
||||
We treat workers as cattle, not pets.
|
||||
</h2>
|
||||
|
||||
<div class="mt-12 grid md:grid-cols-2 lg:grid-cols-3 gap-px bg-[color:var(--border)] rounded-[14px] overflow-hidden ring-1 ring-[color:var(--border)]" style="box-shadow: var(--shadow-sm);">
|
||||
{props.map((s) => (
|
||||
<div class="bg-white p-5">
|
||||
<div class="w-9 h-9 rounded-[8px] bg-[color:var(--sky-1)] text-[color:var(--sky-6)] inline-flex items-center justify-center mb-3">
|
||||
<Icon name={s.icon} size={16} />
|
||||
</div>
|
||||
<div class="text-[14.5px] font-semibold text-heading">{s.title}</div>
|
||||
<p class="mt-1.5 text-[13px] text-foreground/70 leading-relaxed">{s.body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<CTA
|
||||
title="Want dedicated workers for your org?"
|
||||
description="Dedicated worker process and IP, plus monthly deliverability review."
|
||||
primaryLabel="Talk to sales"
|
||||
primaryHref="/contact/?topic=dedicated"
|
||||
secondaryLabel="See pricing"
|
||||
/>
|
||||
</Layout>
|
||||
Reference in New Issue
Block a user