feat(pro): Pro waitlist landing page with referral system (#1140)

* fix(desktop): settings UI redesign, IPC security hardening, release profile

Settings window:
- Add titlebar drag region (macOS traffic light clearance)
- Move Export/Import from Overview to Debug & Logs section
- Category cards grid changed to 3-column layout

Security (IPC trust boundary):
- Add require_trusted_window() to get_desktop_runtime_info, open_url,
  open_live_channels_window_command, open_youtube_login
- Validate base_url in open_live_channels_window_command (localhost-only http)

Performance:
- Add [profile.release] with fat LTO, codegen-units=1, strip, panic=abort
- Reuse reqwest::Client via app state with connection pooling
- Debounce window resize handler (150ms) in EventHandlerManager

* feat(pro): add Pro waitlist landing page with referral system

- React 19 + Vite 6 + Tailwind v4 landing page at /pro
- Cloudflare Turnstile + honeypot bot protection
- Resend transactional confirmation emails with branded template
- Viral referral system: unique codes, position tracking, social share
- Convex schema: referralCode, referredBy, referralCount fields + counters table
- O(1) position counter pattern instead of O(n) collection scan
- SEO: structured data, sitemap, scrolling source marquee
- Vercel routing: /pro rewrite + cache headers + SPA exclusion
- XSS-safe DOM rendering (no innerHTML with user data)
This commit is contained in:
Elie Habib
2026-03-06 23:50:24 +04:00
committed by GitHub
parent 21bf298550
commit dfc175023a
22 changed files with 4142 additions and 44 deletions

View File

@@ -22,6 +22,166 @@ function isRateLimited(ip) {
return entry.count > RATE_LIMIT;
}
async function verifyTurnstile(token, ip) {
const secret = process.env.TURNSTILE_SECRET_KEY;
if (!secret) return true; // skip if not configured
try {
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ secret, response: token, remoteip: ip }),
});
const data = await res.json();
return data.success === true;
} catch {
return false;
}
}
async function sendConfirmationEmail(email, referralCode, position) {
const referralLink = `https://worldmonitor.app/pro?ref=${referralCode}`;
const shareText = encodeURIComponent('I just joined the World Monitor Pro waitlist \u2014 real-time global intelligence powered by AI. Join me:');
const shareUrl = encodeURIComponent(referralLink);
const twitterShare = `https://x.com/intent/tweet?text=${shareText}&url=${shareUrl}`;
const linkedinShare = `https://www.linkedin.com/sharing/share-offsite/?url=${shareUrl}`;
const whatsappShare = `https://wa.me/?text=${shareText}%20${shareUrl}`;
const telegramShare = `https://t.me/share/url?url=${shareUrl}&text=${encodeURIComponent('Join the World Monitor Pro waitlist:')}`;
const resendKey = process.env.RESEND_API_KEY;
if (!resendKey) return; // skip if not configured
try {
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${resendKey}`,
},
body: JSON.stringify({
from: 'World Monitor <noreply@worldmonitor.app>',
to: [email],
subject: 'You\u2019re on the World Monitor Pro waitlist',
html: `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 600px; margin: 0 auto; background: #0a0a0a; color: #e0e0e0;">
<div style="background: #4ade80; height: 4px;"></div>
<div style="padding: 40px 32px 0;">
<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 auto 32px;">
<tr>
<td style="width: 40px; height: 40px; border-radius: 50%; border: 1px solid #222; text-align: center; vertical-align: middle; background: #111;">
<span style="font-size: 20px; color: #4ade80;">&#9678;</span>
</td>
<td style="padding-left: 12px;">
<div style="font-size: 16px; font-weight: 800; color: #fff; letter-spacing: -0.5px;">WORLD MONITOR</div>
<div style="font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 2px;">by Someone.ceo</div>
</td>
</tr>
</table>
<div style="background: #111; border: 1px solid #1a1a1a; border-left: 3px solid #4ade80; padding: 20px 24px; margin-bottom: 28px;">
<p style="font-size: 18px; font-weight: 600; color: #fff; margin: 0 0 8px;">You\u2019re on the Pro waitlist.</p>
<p style="font-size: 14px; color: #999; margin: 0; line-height: 1.5;">We\u2019ll notify you the moment Pro launches. Here\u2019s what you\u2019ll get:</p>
</div>
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 28px;">
<tr>
<td style="width: 50%; padding: 12px; vertical-align: top;">
<div style="background: #111; border: 1px solid #1a1a1a; padding: 16px; height: 100%;">
<div style="font-size: 20px; margin-bottom: 8px;">&#9889;</div>
<div style="font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 4px;">Near-Real-Time</div>
<div style="font-size: 12px; color: #888; line-height: 1.4;">Data refresh under 60 seconds via priority pipeline</div>
</div>
</td>
<td style="width: 50%; padding: 12px; vertical-align: top;">
<div style="background: #111; border: 1px solid #1a1a1a; padding: 16px; height: 100%;">
<div style="font-size: 20px; margin-bottom: 8px;">&#129504;</div>
<div style="font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 4px;">AI Analyst</div>
<div style="font-size: 12px; color: #888; line-height: 1.4;">Morning briefs, flash alerts, pattern detection</div>
</div>
</td>
</tr>
<tr>
<td style="width: 50%; padding: 12px; vertical-align: top;">
<div style="background: #111; border: 1px solid #1a1a1a; padding: 16px; height: 100%;">
<div style="font-size: 20px; margin-bottom: 8px;">&#128232;</div>
<div style="font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 4px;">Delivered to You</div>
<div style="font-size: 12px; color: #888; line-height: 1.4;">Slack, Telegram, WhatsApp, Email, Discord</div>
</div>
</td>
<td style="width: 50%; padding: 12px; vertical-align: top;">
<div style="background: #111; border: 1px solid #1a1a1a; padding: 16px; height: 100%;">
<div style="font-size: 20px; margin-bottom: 8px;">&#128273;</div>
<div style="font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 4px;">22 Services, 1 Key</div>
<div style="font-size: 12px; color: #888; line-height: 1.4;">ACLED, NASA FIRMS, OpenSky, Finnhub, and more</div>
</div>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin-bottom: 28px; background: #111; border: 1px solid #1a1a1a;">
<tr>
<td style="text-align: center; padding: 16px 8px; width: 33%;">
<div style="font-size: 22px; font-weight: 800; color: #4ade80;">2M+</div>
<div style="font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px;">Users</div>
</td>
<td style="text-align: center; padding: 16px 8px; width: 33%; border-left: 1px solid #1a1a1a; border-right: 1px solid #1a1a1a;">
<div style="font-size: 22px; font-weight: 800; color: #4ade80;">435+</div>
<div style="font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px;">Sources</div>
</td>
<td style="text-align: center; padding: 16px 8px; width: 33%;">
<div style="font-size: 22px; font-weight: 800; color: #4ade80;">190+</div>
<div style="font-size: 10px; color: #666; text-transform: uppercase; letter-spacing: 1px;">Countries</div>
</td>
</tr>
</table>
<div style="text-align: center; margin-bottom: 24px;">
<div style="display: inline-block; background: #111; border: 1px solid #4ade80; padding: 12px 28px;">
<div style="font-size: 11px; color: #4ade80; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 4px;">Your position</div>
<div style="font-size: 32px; font-weight: 800; color: #fff;">#${position || '?'}</div>
</div>
</div>
<div style="background: #111; border: 1px solid #1a1a1a; border-left: 3px solid #4ade80; padding: 20px 24px; margin-bottom: 24px;">
<p style="font-size: 16px; font-weight: 700; color: #fff; margin: 0 0 8px;">Move up the line \u2014 invite friends</p>
<p style="font-size: 13px; color: #888; margin: 0 0 16px; line-height: 1.5;">Each friend who joins through your link bumps you closer to the front. Top referrers get early access.</p>
<div style="background: #0a0a0a; border: 1px solid #222; padding: 12px 16px; margin-bottom: 16px; word-break: break-all;">
<a href="${referralLink}" style="color: #4ade80; text-decoration: none; font-size: 13px; font-family: monospace;">${referralLink}</a>
</div>
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td style="width: 25%; text-align: center; padding: 4px;">
<a href="${twitterShare}" style="display: inline-block; background: #1a1a1a; border: 1px solid #222; color: #ccc; text-decoration: none; padding: 8px 0; width: 100%; font-size: 11px; font-weight: 600;">X</a>
</td>
<td style="width: 25%; text-align: center; padding: 4px;">
<a href="${linkedinShare}" style="display: inline-block; background: #1a1a1a; border: 1px solid #222; color: #ccc; text-decoration: none; padding: 8px 0; width: 100%; font-size: 11px; font-weight: 600;">LinkedIn</a>
</td>
<td style="width: 25%; text-align: center; padding: 4px;">
<a href="${whatsappShare}" style="display: inline-block; background: #1a1a1a; border: 1px solid #222; color: #ccc; text-decoration: none; padding: 8px 0; width: 100%; font-size: 11px; font-weight: 600;">WhatsApp</a>
</td>
<td style="width: 25%; text-align: center; padding: 4px;">
<a href="${telegramShare}" style="display: inline-block; background: #1a1a1a; border: 1px solid #222; color: #ccc; text-decoration: none; padding: 8px 0; width: 100%; font-size: 11px; font-weight: 600;">Telegram</a>
</td>
</tr>
</table>
</div>
<div style="text-align: center; margin-bottom: 36px;">
<a href="https://worldmonitor.app" style="display: inline-block; background: #4ade80; color: #0a0a0a; padding: 14px 36px; text-decoration: none; font-weight: 800; font-size: 13px; text-transform: uppercase; letter-spacing: 1.5px; border-radius: 2px;">Explore the Free Dashboard</a>
<p style="font-size: 12px; color: #555; margin-top: 12px;">The free dashboard stays free forever. Pro adds intelligence on top.</p>
</div>
</div>
<div style="border-top: 1px solid #1a1a1a; padding: 24px 32px; text-align: center;">
<div style="margin-bottom: 16px;">
<a href="https://x.com/eliehabib" style="color: #666; text-decoration: none; font-size: 12px; margin: 0 12px;">X / Twitter</a>
<a href="https://github.com/koala73/worldmonitor" style="color: #666; text-decoration: none; font-size: 12px; margin: 0 12px;">GitHub</a>
<a href="https://worldmonitor.app/pro" style="color: #666; text-decoration: none; font-size: 12px; margin: 0 12px;">Pro Waitlist</a>
</div>
<p style="font-size: 11px; color: #444; margin: 0; line-height: 1.6;">
World Monitor \u2014 Real-time intelligence for a connected world.<br />
<a href="https://worldmonitor.app" style="color: #4ade80; text-decoration: none;">worldmonitor.app</a>
</p>
</div>
</div>`,
}),
});
} catch (err) {
console.error('[register-interest] Resend error:', err);
}
}
export default async function handler(req) {
if (isDisallowedOrigin(req)) {
return new Response(JSON.stringify({ error: 'Origin not allowed' }), {
@@ -43,9 +203,8 @@ export default async function handler(req) {
});
}
// x-real-ip is injected by Vercel from the TCP connection and cannot be spoofed by
// clients. x-forwarded-for is client-settable and MUST NOT be the primary source for
// rate limiting — an attacker can rotate arbitrary values to bypass the limit entirely.
// x-real-ip is injected by Vercel from the TCP connection and cannot be spoofed.
// x-forwarded-for is client-settable — only use as last resort.
const ip =
req.headers.get('x-real-ip') ||
req.headers.get('cf-connecting-ip') ||
@@ -68,7 +227,24 @@ export default async function handler(req) {
});
}
const { email, source, appVersion } = body;
// Honeypot — bots auto-fill this hidden field; real users leave it empty
if (body.website) {
return new Response(JSON.stringify({ status: 'registered' }), {
status: 200,
headers: { 'Content-Type': 'application/json', ...cors },
});
}
// Cloudflare Turnstile verification
const turnstileOk = await verifyTurnstile(body.turnstileToken || '', ip);
if (!turnstileOk) {
return new Response(JSON.stringify({ error: 'Bot verification failed' }), {
status: 403,
headers: { 'Content-Type': 'application/json', ...cors },
});
}
const { email, source, appVersion, referredBy } = body;
if (!email || typeof email !== 'string' || email.length > MAX_EMAIL_LENGTH || !EMAIL_RE.test(email)) {
return new Response(JSON.stringify({ error: 'Invalid email address' }), {
status: 400,
@@ -76,16 +252,15 @@ export default async function handler(req) {
});
}
// Coerce metadata fields to strings and enforce length caps to prevent
// non-string values (objects/arrays are truthy and bypass `|| 'unknown'`)
// from being forwarded to the database as wrong types, and to prevent
// arbitrarily large payloads filling the registrations table.
const safeSource = typeof source === 'string'
? source.slice(0, MAX_META_LENGTH)
: 'unknown';
const safeAppVersion = typeof appVersion === 'string'
? appVersion.slice(0, MAX_META_LENGTH)
: 'unknown';
const safeReferredBy = typeof referredBy === 'string'
? referredBy.slice(0, 20)
: undefined;
const convexUrl = process.env.CONVEX_URL;
if (!convexUrl) {
@@ -101,7 +276,14 @@ export default async function handler(req) {
email,
source: safeSource,
appVersion: safeAppVersion,
referredBy: safeReferredBy,
});
// Send confirmation email for new registrations (awaited to avoid Edge isolate termination)
if (result.status === 'registered' && result.referralCode) {
await sendConfirmationEmail(email, result.referralCode, result.position);
}
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json', ...cors },

View File

@@ -1,11 +1,60 @@
import { mutation } from "./_generated/server";
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { DatabaseReader, DatabaseWriter } from "./_generated/server";
function hashCode(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
}
return Math.abs(hash);
}
async function generateUniqueReferralCode(
db: DatabaseReader,
email: string,
): Promise<string> {
for (let attempt = 0; attempt < 10; attempt++) {
const input = attempt === 0 ? email : `${email}:${attempt}`;
const code = hashCode(input).toString(36).padStart(6, "0").slice(0, 8);
const existing = await db
.query("registrations")
.withIndex("by_referral_code", (q) => q.eq("referralCode", code))
.first();
if (!existing) return code;
}
// Fallback: timestamp-based code (extremely unlikely path)
return Date.now().toString(36).slice(-8);
}
async function getCounter(db: DatabaseReader, name: string): Promise<number> {
const counter = await db
.query("counters")
.withIndex("by_name", (q) => q.eq("name", name))
.first();
return counter?.value ?? 0;
}
async function incrementCounter(db: DatabaseWriter, name: string): Promise<number> {
const counter = await db
.query("counters")
.withIndex("by_name", (q) => q.eq("name", name))
.first();
const newVal = (counter?.value ?? 0) + 1;
if (counter) {
await db.patch(counter._id, { value: newVal });
} else {
await db.insert("counters", { name, value: newVal });
}
return newVal;
}
export const register = mutation({
args: {
email: v.string(),
source: v.optional(v.string()),
appVersion: v.optional(v.string()),
referredBy: v.optional(v.string()),
},
handler: async (ctx, args) => {
const normalizedEmail = args.email.trim().toLowerCase();
@@ -16,17 +65,64 @@ export const register = mutation({
.first();
if (existing) {
return { status: "already_registered" as const };
return {
status: "already_registered" as const,
referralCode: existing.referralCode ?? "",
referralCount: existing.referralCount ?? 0,
};
}
const referralCode = await generateUniqueReferralCode(ctx.db, normalizedEmail);
// Credit the referrer
if (args.referredBy) {
const referrer = await ctx.db
.query("registrations")
.withIndex("by_referral_code", (q) => q.eq("referralCode", args.referredBy))
.first();
if (referrer) {
await ctx.db.patch(referrer._id, {
referralCount: (referrer.referralCount ?? 0) + 1,
});
}
}
const position = await incrementCounter(ctx.db, "registrations_total");
await ctx.db.insert("registrations", {
email: args.email.trim(),
normalizedEmail,
registeredAt: Date.now(),
source: args.source ?? "unknown",
appVersion: args.appVersion ?? "unknown",
referralCode,
referredBy: args.referredBy,
referralCount: 0,
});
return { status: "registered" as const };
return {
status: "registered" as const,
referralCode,
referralCount: 0,
position,
};
},
});
export const getPosition = query({
args: { referralCode: v.string() },
handler: async (ctx, args) => {
const reg = await ctx.db
.query("registrations")
.withIndex("by_referral_code", (q) => q.eq("referralCode", args.referralCode))
.first();
if (!reg) return null;
const total = await getCounter(ctx.db, "registrations_total");
return {
referralCount: reg.referralCount ?? 0,
total,
};
},
});

View File

@@ -8,5 +8,14 @@ export default defineSchema({
registeredAt: v.number(),
source: v.optional(v.string()),
appVersion: v.optional(v.string()),
}).index("by_normalized_email", ["normalizedEmail"]),
referralCode: v.optional(v.string()),
referredBy: v.optional(v.string()),
referralCount: v.optional(v.number()),
})
.index("by_normalized_email", ["normalizedEmail"])
.index("by_referral_code", ["referralCode"]),
counters: defineTable({
name: v.string(),
value: v.number(),
}).index("by_name", ["name"]),
});

View File

@@ -41,7 +41,7 @@
<meta name="twitter:title" content="World Monitor - Global Situation with AI Insights" />
<meta name="twitter:description" content="AI-powered real-time global intelligence dashboard with live news, markets, military tracking, infrastructure monitoring, and geopolitical data." />
<meta name="twitter:image" content="https://worldmonitor.app/favico/og-image.png" />
<meta name="twitter:site" content="@worldmonitorapp" />
<meta name="twitter:site" content="@eliehabib" />
<meta name="twitter:creator" content="@eliehabib" />
<!-- JSON-LD Structured Data -->
@@ -52,35 +52,45 @@
"name": "World Monitor",
"alternateName": "WorldMonitor",
"url": "https://worldmonitor.app/",
"description": "AI-powered real-time global intelligence dashboard with live news, markets, military tracking, infrastructure monitoring, and geopolitical data.",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"description": "Open-source real-time OSINT dashboard for geopolitical monitoring, conflict tracking, military flight tracking, maritime AIS, and global threat intelligence. Used by 2M+ people across 190+ countries.",
"applicationCategory": "SecurityApplication",
"operatingSystem": "Web, Windows, macOS, Linux",
"offers": [
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Free", "description": "Full dashboard with 435+ sources, 45 map layers, BYOK AI" },
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Pro (Waitlist)", "url": "https://worldmonitor.app/pro" }
],
"author": {
"@type": "Person",
"name": "Elie Habib"
"name": "Elie Habib",
"url": "https://x.com/eliehabib",
"jobTitle": "CEO",
"worksFor": { "@type": "Organization", "name": "Anghami", "url": "https://anghami.com" },
"sameAs": ["https://x.com/eliehabib", "https://github.com/koala73"]
},
"featureList": [
"AI-powered intelligence synthesis",
"Real-time news aggregation",
"Stock market tracking",
"Military flight monitoring",
"Ship AIS tracking",
"Earthquake alerts",
"Protest tracking",
"Power outage monitoring",
"Oil price analytics",
"Government spending data",
"Prediction markets",
"Infrastructure monitoring",
"Geopolitical intelligence"
"Real-time conflict tracking (ACLED, UCDP)",
"Military ADS-B flight monitoring",
"Maritime AIS ship tracking and dark vessel detection",
"NASA FIRMS satellite fire detection",
"Nuclear installation and critical infrastructure mapping",
"Submarine cable and internet outage monitoring",
"GPS jamming zone detection",
"AI-powered intelligence synthesis and briefs",
"Country Instability Index (CII) scoring",
"Stock market, commodity, and crypto tracking",
"Earthquake and natural disaster alerts",
"Civil unrest and protest monitoring",
"Cyber threat and BGP anomaly detection",
"435+ curated RSS news feeds",
"21 language support with RTL"
],
"screenshot": "https://worldmonitor.app/favico/og-image.png",
"keywords": "AI, OSINT, intelligence dashboard, geopolitical, real-time monitoring, situation awareness, AI-powered"
"sameAs": [
"https://github.com/koala73/worldmonitor",
"https://x.com/eliehabib",
"https://www.wired.me/story/the-music-streaming-ceo-who-built-a-global-war-map"
],
"keywords": "OSINT dashboard, geopolitical monitoring, real-time intelligence, conflict tracker, threat intelligence, ADS-B tracking, AIS ship tracking, global risk monitoring, situational awareness"
}
</script>

9
pro-test/.env.example Normal file
View File

@@ -0,0 +1,9 @@
# GEMINI_API_KEY: Required for Gemini AI API calls.
# AI Studio automatically injects this at runtime from user secrets.
# Users configure this via the Secrets panel in the AI Studio UI.
GEMINI_API_KEY="MY_GEMINI_API_KEY"
# APP_URL: The URL where this applet is hosted.
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
# Used for self-referential links, OAuth callbacks, and API endpoints.
APP_URL="MY_APP_URL"

8
pro-test/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
build/
dist/
coverage/
.DS_Store
*.log
.env*
!.env.example

20
pro-test/README.md Normal file
View File

@@ -0,0 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/ef577c64-7776-42d3-bb38-3f0a627564c3
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

86
pro-test/index.html Normal file
View File

@@ -0,0 +1,86 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>World Monitor Pro — Real-Time OSINT Dashboard & Global Threat Intelligence</title>
<meta name="description" content="Open-source global intelligence dashboard used by 2M+ people worldwide. Track geopolitics, markets, energy, infrastructure, and natural events in real time. AI-powered briefs delivered to Slack, Telegram, and email." />
<meta name="keywords" content="OSINT dashboard, geopolitical monitoring, real-time intelligence, global tracking, market intelligence, ADS-B tracking, AIS ship tracking, global risk monitoring, situational awareness, open source intelligence" />
<link rel="canonical" href="https://worldmonitor.app/pro" />
<meta property="og:title" content="World Monitor Pro — Real-Time Global Intelligence Platform" />
<meta property="og:description" content="2M+ users track geopolitics, markets, energy, infrastructure, and natural events in real time. AI briefs delivered where you work." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://worldmonitor.app/pro" />
<meta property="og:image" content="https://worldmonitor.app/favico/og-image.png" />
<meta property="og:site_name" content="World Monitor" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@eliehabib" />
<meta name="twitter:creator" content="@eliehabib" />
<link rel="icon" type="image/png" href="https://worldmonitor.app/favico/favicon-32x32.png" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "World Monitor",
"applicationCategory": "SecurityApplication",
"operatingSystem": "Web, Windows, macOS, Linux",
"url": "https://worldmonitor.app",
"description": "Open-source real-time OSINT dashboard for global monitoring — geopolitics, markets, energy, infrastructure, and natural events. Aggregates 435+ data sources across 22 service domains.",
"author": {
"@type": "Person",
"name": "Someone.ceo",
"url": "https://someone.ceo",
"jobTitle": "CEO of Anghami"
},
"offers": [
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Free", "description": "Full dashboard with 435+ sources, 45 map layers, BYOK AI" },
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Pro (Waitlist)", "description": "Near-real-time data, AI briefs, flash alerts, 22 services under 1 key" }
],
"featureList": "Real-time geopolitical monitoring, Global flight tracking, Maritime AIS ship monitoring, Infrastructure mapping, Submarine cable monitoring, Internet outage detection, Satellite fire detection, Financial market intelligence, AI-powered analysis, Slack and Telegram delivery, 21 language support"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is the free version of World Monitor going away?",
"acceptedAnswer": { "@type": "Answer", "text": "No. The free dashboard stays free forever. Pro adds AI intelligence, alerts, and delivery channels on top of the same dashboard you use today." }
},
{
"@type": "Question",
"name": "Can I still use my own API keys with World Monitor?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. Bring-your-own-keys always works. Pro simply means you don't have to register for 20+ separate services." }
},
{
"@type": "Question",
"name": "What is the difference between the API and Pro tiers?",
"acceptedAnswer": { "@type": "Answer", "text": "Pro delivers AI briefs and alerts to Slack, Telegram, WhatsApp, and email. API gives you programmatic REST access for your own code. They're independent tiers — use both or either." }
},
{
"@type": "Question",
"name": "What is MCP in World Monitor?",
"acceptedAnswer": { "@type": "Answer", "text": "Model Context Protocol lets AI agents (Claude, GPT, or custom LLMs) use World Monitor as a tool — querying all 22 services, reading map state, and triggering analysis. Enterprise only." }
},
{
"@type": "Question",
"name": "Can World Monitor be deployed on-premises?",
"acceptedAnswer": { "@type": "Answer", "text": "Enterprise includes Docker deployment, air-gapped mode with local Ollama AI, zero external network calls, full audit logging, and data residency options (EU, US, MENA)." }
},
{
"@type": "Question",
"name": "How fast is World Monitor's near-real-time data?",
"acceptedAnswer": { "@type": "Answer", "text": "Pro data refreshes under 60 seconds with priority pipeline. Free tier refreshes every 5-15 minutes. Enterprise gets live-edge streaming for critical event types." }
}
]
}
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

5
pro-test/metadata.json Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "World Monitor Launch",
"description": "Launch waitlist page for World Monitor, the open-source intelligence dashboard.",
"requestFramePermissions": []
}

2356
pro-test/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
pro-test/package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "worldmonitor-pro",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite --port=3000",
"build": "vite build",
"preview": "vite preview",
"lint": "tsc --noEmit"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.14",
"@vitejs/plugin-react": "^5.0.4",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vite": "^6.2.0"
},
"devDependencies": {
"@types/node": "^22.14.0",
"tailwindcss": "^4.1.14",
"typescript": "~5.8.2"
}
}

814
pro-test/src/App.tsx Normal file
View File

@@ -0,0 +1,814 @@
import { motion } from 'motion/react';
import {
Globe, Activity, ShieldAlert, Zap, Terminal, Database,
Send, MessageCircle, Mail, MessageSquare, ChevronDown,
ArrowRight, Check, Lock, Server, Cpu, Layers,
Bell, Brain, Key, Plug, PanelTop, ExternalLink,
BarChart3, Clock, Radio, Ship, Plane, Flame,
Cable, Wifi, MapPin, Users, TrendingUp
} from 'lucide-react';
const API_BASE = 'https://api.worldmonitor.app';
const TURNSTILE_SITE_KEY = '0x4AAAAAACnaYgHIyxclu8Tj';
const PRO_URL = 'https://worldmonitor.app/pro';
declare global {
interface Window { turnstile?: { getResponse: (id?: string) => string | undefined; reset: (id?: string) => void; }; }
}
function getRefCode(): string | undefined {
const params = new URLSearchParams(window.location.search);
return params.get('ref') || undefined;
}
function sanitize(val: unknown): string {
return String(val ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c] || c));
}
function showReferralSuccess(formEl: HTMLFormElement, data: { referralCode?: string; position?: number }) {
if (!data.referralCode) return;
const safeCode = sanitize(data.referralCode);
const safePosition = sanitize(data.position);
const referralLink = `${PRO_URL}?ref=${safeCode}`;
const shareText = encodeURIComponent('I just joined the World Monitor Pro waitlist \u2014 real-time global intelligence powered by AI. Join me:');
const shareUrl = encodeURIComponent(referralLink);
const el = (tag: string, cls: string, text?: string) => {
const node = document.createElement(tag);
node.className = cls;
if (text) node.textContent = text;
return node;
};
const successDiv = el('div', 'text-center');
const badge = el('div', 'inline-block bg-wm-card border border-wm-green/30 px-6 py-4 mb-4');
badge.appendChild(el('p', 'text-xs text-wm-green font-mono uppercase tracking-widest mb-1', 'Your position'));
badge.appendChild(el('p', 'text-4xl font-display font-bold text-wm-text', `#${safePosition || '?'}`));
successDiv.appendChild(badge);
successDiv.appendChild(el('p', 'text-sm text-wm-muted mb-4', 'Share your link to move up the line. Each friend who joins bumps you closer to the front.'));
const linkBox = el('div', 'bg-wm-card border border-wm-border px-4 py-3 mb-4 font-mono text-xs text-wm-green break-all select-all cursor-pointer', referralLink);
linkBox.addEventListener('click', () => {
navigator.clipboard.writeText(referralLink).then(() => {
linkBox.textContent = 'Copied!';
setTimeout(() => { linkBox.textContent = referralLink; }, 2000);
});
});
successDiv.appendChild(linkBox);
const shareRow = el('div', 'flex gap-3 justify-center flex-wrap');
const shareLinks = [
{ label: 'Share on X', href: `https://x.com/intent/tweet?text=${shareText}&url=${shareUrl}` },
{ label: 'LinkedIn', href: `https://www.linkedin.com/sharing/share-offsite/?url=${shareUrl}` },
{ label: 'WhatsApp', href: `https://wa.me/?text=${shareText}%20${shareUrl}` },
{ label: 'Telegram', href: `https://t.me/share/url?url=${shareUrl}&text=${encodeURIComponent('Join the World Monitor Pro waitlist:')}` },
];
for (const s of shareLinks) {
const a = el('a', 'bg-wm-card border border-wm-border px-4 py-2 text-xs font-mono text-wm-muted hover:text-wm-text hover:border-wm-text transition-colors', s.label);
(a as HTMLAnchorElement).href = s.href;
(a as HTMLAnchorElement).target = '_blank';
(a as HTMLAnchorElement).rel = 'noreferrer';
shareRow.appendChild(a);
}
successDiv.appendChild(shareRow);
formEl.replaceWith(successDiv);
}
async function submitWaitlist(email: string, formEl: HTMLFormElement) {
const btn = formEl.querySelector('button[type="submit"]') as HTMLButtonElement;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Submitting...';
const honeypot = (formEl.querySelector('input[name="website"]') as HTMLInputElement)?.value || '';
const turnstileWidget = formEl.querySelector('.cf-turnstile') as HTMLElement | null;
const widgetId = turnstileWidget?.dataset.widgetId;
const turnstileToken = window.turnstile?.getResponse(widgetId) || '';
const ref = getRefCode();
try {
const res = await fetch(`${API_BASE}/register-interest`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, source: 'pro-waitlist', website: honeypot, turnstileToken, referredBy: ref }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Registration failed');
showReferralSuccess(formEl, data);
} catch (err: any) {
btn.textContent = err.message === 'Too many requests' ? 'Too many requests' : 'Failed \u2014 try again';
btn.disabled = false;
window.turnstile?.reset(widgetId);
setTimeout(() => { btn.textContent = origText; }, 3000);
}
}
const SlackIcon = () => (
<svg viewBox="0 0 24 24" className="w-5 h-5" fill="currentColor" aria-hidden="true">
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"/>
</svg>
);
const Logo = () => (
<a href="https://worldmonitor.app" className="flex items-center gap-2 hover:opacity-80 transition-opacity" aria-label="World Monitor — Home">
<div className="relative w-8 h-8 rounded-full bg-wm-card border border-wm-border flex items-center justify-center overflow-hidden">
<Globe className="w-5 h-5 text-wm-blue opacity-50 absolute" aria-hidden="true" />
<Activity className="w-6 h-6 text-wm-green absolute z-10" aria-hidden="true" />
</div>
<div className="flex flex-col">
<span className="font-display font-bold text-sm leading-none tracking-tight">WORLD MONITOR</span>
<span className="text-[9px] text-wm-muted font-mono uppercase tracking-widest leading-none mt-1">by Someone.ceo</span>
</div>
</a>
);
const Navbar = () => (
<nav className="fixed top-0 left-0 right-0 z-50 glass-panel border-b-0 border-x-0 rounded-none" aria-label="Main navigation">
<div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
<Logo />
<div className="hidden md:flex items-center gap-8 text-sm font-mono text-wm-muted">
<a href="#tiers" className="hover:text-wm-text transition-colors">Free</a>
<a href="#pro" className="hover:text-wm-green transition-colors">Pro</a>
<a href="#api" className="hover:text-wm-text transition-colors">API</a>
<a href="#enterprise" className="hover:text-wm-text transition-colors">Enterprise</a>
</div>
<a href="#waitlist" className="bg-wm-green text-wm-bg px-4 py-2 rounded-sm font-mono text-xs uppercase tracking-wider font-bold hover:bg-green-400 transition-colors">
Join Waitlist
</a>
</div>
</nav>
);
const WiredBadge = () => (
<a
href="https://www.wired.me/story/the-music-streaming-ceo-who-built-a-global-war-map"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-wm-border bg-wm-card/50 text-wm-muted text-xs font-mono hover:border-wm-green/30 hover:text-wm-text transition-colors"
>
As featured in <span className="text-wm-text font-bold">WIRED</span> <ExternalLink className="w-3 h-3" aria-hidden="true" />
</a>
);
const Hero = () => (
<section className="pt-28 pb-16 px-6 relative overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_30%,rgba(74,222,128,0.05)_0%,transparent_60%)] pointer-events-none" />
<div className="max-w-4xl mx-auto text-center relative z-10">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<div className="mb-6">
<WiredBadge />
</div>
<h1 className="text-5xl md:text-7xl font-display font-bold tracking-tighter mb-6 leading-[1.1]">
Real-time intelligence <br className="hidden md:block" />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-wm-green to-emerald-300">for a connected world.</span>
</h1>
<p className="text-lg md:text-xl text-wm-muted mb-8 max-w-2xl mx-auto font-light">
Track geopolitics, markets, energy, infrastructure, and natural events across 435+ sources. AI that tells you what it means delivered where you work.
</p>
<form className="flex flex-col gap-3 max-w-md mx-auto" onSubmit={(e) => { e.preventDefault(); const form = e.currentTarget; const email = new FormData(form).get('email') as string; submitWaitlist(email, form); }}>
{/* Honeypot — hidden from humans, bots auto-fill it */}
<input type="text" name="website" autoComplete="off" tabIndex={-1} aria-hidden="true" className="absolute opacity-0 h-0 w-0 pointer-events-none" />
<div className="flex flex-col sm:flex-row gap-3">
<input
type="email"
name="email"
placeholder="Enter your email"
className="flex-1 bg-wm-card border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono"
required
aria-label="Email address for waitlist"
/>
<button type="submit" className="bg-wm-green text-wm-bg px-6 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors flex items-center justify-center gap-2 whitespace-nowrap">
Join Pro Waitlist <ArrowRight className="w-4 h-4" aria-hidden="true" />
</button>
</div>
<div className="cf-turnstile mx-auto" data-sitekey={TURNSTILE_SITE_KEY} data-theme="dark" data-size="compact" />
</form>
<div className="flex items-center justify-center gap-4 mt-4">
<p className="text-xs text-wm-muted font-mono">Launching soon</p>
<span className="text-wm-border">|</span>
<a href="https://worldmonitor.app" className="text-xs text-wm-green font-mono hover:text-green-300 transition-colors flex items-center gap-1">
Try the free dashboard <ArrowRight className="w-3 h-3" aria-hidden="true" />
</a>
</div>
</motion.div>
</div>
</section>
);
const LivePreview = () => (
<section className="px-6 pb-16 -mt-4">
<div className="max-w-6xl mx-auto">
<div className="relative rounded-lg overflow-hidden border border-wm-border shadow-2xl shadow-wm-green/5">
<div className="bg-wm-card px-4 py-2 border-b border-wm-border flex items-center gap-3">
<div className="flex gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500/70" />
<div className="w-3 h-3 rounded-full bg-yellow-500/70" />
<div className="w-3 h-3 rounded-full bg-green-500/70" />
</div>
<span className="font-mono text-xs text-wm-muted ml-2">worldmonitor.app Live Dashboard</span>
<a
href="https://worldmonitor.app"
target="_blank"
rel="noreferrer"
className="ml-auto text-xs text-wm-green font-mono hover:text-green-300 transition-colors flex items-center gap-1"
>
Open full screen <ExternalLink className="w-3 h-3" aria-hidden="true" />
</a>
</div>
<div className="relative aspect-[16/9] bg-black">
<iframe
src="https://worldmonitor.app"
title="World Monitor — Live OSINT Dashboard"
className="w-full h-full border-0"
loading="lazy"
sandbox="allow-scripts allow-same-origin"
/>
<div className="absolute inset-0 pointer-events-none bg-gradient-to-t from-wm-bg/80 via-transparent to-transparent" />
<div className="absolute bottom-4 left-0 right-0 text-center pointer-events-auto">
<a
href="https://worldmonitor.app"
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 bg-wm-green text-wm-bg px-6 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors"
>
Try the Live Dashboard <ArrowRight className="w-4 h-4" aria-hidden="true" />
</a>
</div>
</div>
</div>
<p className="text-center text-xs text-wm-muted font-mono mt-4">
3D WebGL globe &middot; 45+ interactive map layers &middot; Real-time geopolitical, market, energy, and infrastructure data
</p>
</div>
</section>
);
const SourceMarquee = () => {
const sources = [
"ACLED", "UCDP", "GDELT", "NASA FIRMS", "USGS", "OpenSky", "AISStream",
"Finnhub", "FRED", "CoinGecko", "Polymarket", "UNHCR", "Cloudflare Radar",
"BGPStream", "GPSJam", "NOAA", "Copernicus", "IAEA", "Bloomberg",
"Reuters", "Al Jazeera", "Sky News", "Euronews", "DW News", "France 24",
"CNBC", "Nikkei", "Haaretz", "Al Arabiya", "TRT World",
"Defense One", "Jane's", "The War Zone", "Maritime Executive",
"OilPrice", "Rigzone", "Hellenic Shipping News",
"TechCrunch", "Ars Technica", "The Verge", "Wired",
"Krebs on Security", "BleepingComputer", "The Record",
];
const items = sources.join(" · ");
return (
<section className="border-y border-wm-border bg-wm-card/20 overflow-hidden py-4" aria-label="Data sources">
<div className="marquee-track whitespace-nowrap font-mono text-xs text-wm-muted uppercase tracking-widest">
<span className="inline-block px-4">{items} · </span>
<span className="inline-block px-4">{items} · </span>
</div>
</section>
);
};
const SocialProof = () => (
<section className="border-y border-wm-border bg-wm-card/30 py-16 px-6">
<div className="max-w-5xl mx-auto">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-center mb-12">
{[
{ value: "2M+", label: "Unique visitors" },
{ value: "216K", label: "Peak daily users" },
{ value: "190+", label: "Countries reached" },
{ value: "435+", label: "Live data sources" },
].map((stat, i) => (
<div key={i}>
<p className="text-3xl md:text-4xl font-display font-bold text-wm-green">{stat.value}</p>
<p className="text-xs font-mono text-wm-muted uppercase tracking-widest mt-1">{stat.label}</p>
</div>
))}
</div>
<blockquote className="max-w-3xl mx-auto text-center">
<p className="text-lg md:text-xl text-wm-muted italic leading-relaxed">
"The news became genuinely hard to parse. Iran, Trump's decisions, financial markets, critical minerals, tensions compounding from every direction simultaneously. I needed something that showed me how these events connect to each other in real time."
</p>
<footer className="mt-6 flex items-center justify-center gap-3">
<div className="text-sm">
<span className="text-wm-text font-bold">Elie Habib</span>
<span className="text-wm-muted"> CEO of </span>
<a href="https://anghami.com" target="_blank" rel="noreferrer" className="text-wm-muted underline underline-offset-4 hover:text-wm-text transition-colors">Anghami</a>
<span className="text-wm-muted">, as told to </span>
<a href="https://www.wired.me/story/the-music-streaming-ceo-who-built-a-global-war-map" target="_blank" rel="noreferrer" className="text-wm-text underline underline-offset-4 hover:text-wm-green transition-colors">WIRED</a>
</div>
</footer>
</blockquote>
</div>
</section>
);
const DataCoverage = () => {
const domains = [
{ icon: <Radio className="w-5 h-5" aria-hidden="true" />, name: "Geopolitical Events", desc: "ACLED & UCDP events with escalation scoring and trend analysis" },
{ icon: <Plane className="w-5 h-5" aria-hidden="true" />, name: "Aviation Tracking", desc: "ADS-B transponder tracking of global flight patterns" },
{ icon: <Ship className="w-5 h-5" aria-hidden="true" />, name: "Maritime & AIS", desc: "Ship movements, vessel detection, port and trade activity" },
{ icon: <Flame className="w-5 h-5" aria-hidden="true" />, name: "Satellite Fire Detection", desc: "NASA FIRMS near-real-time fire and hotspot data" },
{ icon: <Cable className="w-5 h-5" aria-hidden="true" />, name: "Submarine Cables", desc: "Undersea cable routes and landing stations" },
{ icon: <Wifi className="w-5 h-5" aria-hidden="true" />, name: "Internet & GPS", desc: "Outage detection, BGP anomalies, GPS jamming zones" },
{ icon: <MapPin className="w-5 h-5" aria-hidden="true" />, name: "Critical Infrastructure", desc: "Nuclear sites, power grids, pipelines, refineries" },
{ icon: <TrendingUp className="w-5 h-5" aria-hidden="true" />, name: "Financial Markets", desc: "Equities, commodities, crypto, ETF flows, FRED macro data" },
{ icon: <ShieldAlert className="w-5 h-5" aria-hidden="true" />, name: "Cyber Threats", desc: "Ransomware feeds, BGP hijacks, DDoS detection" },
{ icon: <Globe className="w-5 h-5" aria-hidden="true" />, name: "GDELT & News", desc: "435+ RSS feeds, AI-scored GDELT events, live broadcasts" },
{ icon: <Users className="w-5 h-5" aria-hidden="true" />, name: "Civil Unrest & Displacement", desc: "Protests, refugee flows, UNHCR displacement data" },
{ icon: <Activity className="w-5 h-5" aria-hidden="true" />, name: "Seismology & Natural", desc: "USGS earthquakes, volcanic activity, severe weather" },
];
return (
<section className="py-24 px-6" id="coverage">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-5xl font-display font-bold mb-6">What World Monitor Tracks</h2>
<p className="text-wm-muted max-w-2xl mx-auto">
22 service domains ingested simultaneously. Everything normalized, geolocated, and rendered on a WebGL globe with thousands of markers.
</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{domains.map((d, i) => (
<div key={i} className="bg-wm-card border border-wm-border p-4 hover:border-wm-green/30 transition-colors">
<div className="text-wm-green mb-3">{d.icon}</div>
<h3 className="font-bold text-sm mb-1">{d.name}</h3>
<p className="text-xs text-wm-muted">{d.desc}</p>
</div>
))}
</div>
</div>
</section>
);
};
const Tiers = () => {
const tiers = [
{
name: "Free",
tagline: "See everything",
desc: "The open-source dashboard",
features: ["5-15 min refresh", "435+ feeds, 45 map layers", "BYOK for AI", "Free forever"],
color: "border-wm-border",
cta: { label: "Open Dashboard", href: "https://worldmonitor.app" }
},
{
name: "Pro",
tagline: "Know what matters",
desc: "The AI analyst",
features: ["Near-real-time (<60s)", "+ daily briefs, flash alerts", "AI included, 1 key", "Early access pricing"],
color: "border-wm-green",
glow: true,
cta: { label: "Join Waitlist", href: "#waitlist" }
},
{
name: "Enterprise",
tagline: "Act before anyone else",
desc: "The intelligence platform",
features: ["Live-edge + satellite", "+ AI agents, 50K+ infra points", "Custom AI, investor personas", "Contact us"],
color: "border-wm-border",
cta: { label: "Contact Sales", href: "mailto:enterprise@worldmonitor.app" }
}
];
return (
<section className="py-24 px-6 max-w-7xl mx-auto" id="tiers">
<div className="grid md:grid-cols-3 gap-6">
{tiers.map((tier, i) => (
<div key={i} className={`bg-wm-card border ${tier.color} p-8 relative ${tier.glow ? 'border-glow' : ''}`}>
{tier.glow && <div className="absolute top-0 left-0 w-full h-1 bg-wm-green" />}
<h3 className="font-display text-2xl font-bold mb-2">{tier.name}</h3>
<p className="text-wm-muted font-mono text-sm mb-1">{tier.tagline}</p>
<p className="text-sm font-medium mb-8 pb-8 border-b border-wm-border">{tier.desc}</p>
<ul className="space-y-4 mb-8">
{tier.features.map((f, j) => (
<li key={j} className="flex items-start gap-3 text-sm">
<Check className={`w-4 h-4 shrink-0 mt-0.5 ${tier.glow ? 'text-wm-green' : 'text-wm-muted'}`} aria-hidden="true" />
<span className="text-wm-muted">{f}</span>
</li>
))}
</ul>
<a
href={tier.cta.href}
className={`block text-center py-2.5 rounded-sm font-mono text-xs uppercase tracking-wider font-bold transition-colors ${
tier.glow
? 'bg-wm-green text-wm-bg hover:bg-green-400'
: 'border border-wm-border text-wm-muted hover:text-wm-text hover:border-wm-text'
}`}
>
{tier.cta.label}
</a>
</div>
))}
</div>
</section>
);
};
const ProShowcase = () => (
<section className="py-24 px-6 border-t border-wm-border bg-wm-card/30" id="pro">
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-start">
<div>
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-wm-green/30 bg-wm-green/10 text-wm-green text-xs font-mono mb-6">
PRO TIER
</div>
<h2 className="text-3xl md:text-5xl font-display font-bold mb-6">Your AI Analyst That Never Sleeps</h2>
<p className="text-wm-muted mb-8">
The free dashboard shows you the world. Pro tells you what it means and makes sure you never miss what matters.
</p>
<div className="space-y-6">
<div className="flex gap-4">
<Zap className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" />
<div>
<h4 className="font-bold mb-1">Near-Real-Time Data</h4>
<p className="text-sm text-wm-muted">Refresh accelerated from 5-15 min to under 60 seconds. Priority pipeline for your alerts.</p>
</div>
</div>
<div className="flex gap-4">
<Brain className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" />
<div>
<h4 className="font-bold mb-1">"So What?" Analysis</h4>
<p className="text-sm text-wm-muted">Impact chains, pattern recognition, convergence detection, and market-geopolitical correlation.</p>
</div>
</div>
<div className="flex gap-4">
<Clock className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" />
<div>
<h4 className="font-bold mb-1">Morning Briefs & Flash Alerts</h4>
<p className="text-sm text-wm-muted">AI-synthesized overnight developments ranked by your focus areas. Breaking events pushed in real-time.</p>
</div>
</div>
<div className="flex gap-4">
<Bell className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" />
<div>
<h4 className="font-bold mb-1">Configurable Alerting</h4>
<p className="text-sm text-wm-muted">Set rules for CII deltas, convergence events, proximity to saved locations, and market correlation triggers.</p>
</div>
</div>
<div className="flex gap-4">
<Key className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" />
<div>
<h4 className="font-bold mb-1">22 Services, 1 Key</h4>
<p className="text-sm text-wm-muted">ACLED, UCDP, Finnhub, FRED, NASA FIRMS, AISStream, OpenSky, and more all active, no separate registrations.</p>
</div>
</div>
</div>
<div className="mt-10 pt-8 border-t border-wm-border">
<p className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-4">Choose how intelligence finds you</p>
<div className="flex gap-6">
{[
{ icon: <SlackIcon />, label: "Slack" },
{ icon: <Send className="w-5 h-5" aria-hidden="true" />, label: "Telegram" },
{ icon: <MessageCircle className="w-5 h-5" aria-hidden="true" />, label: "WhatsApp" },
{ icon: <Mail className="w-5 h-5" aria-hidden="true" />, label: "Email" },
{ icon: <MessageSquare className="w-5 h-5" aria-hidden="true" />, label: "Discord" },
].map((ch, i) => (
<div key={i} className="flex flex-col items-center gap-1.5 text-wm-muted hover:text-wm-text transition-colors cursor-pointer">
{ch.icon}
<span className="text-[10px] font-mono">{ch.label}</span>
</div>
))}
</div>
</div>
</div>
<div className="bg-[#1a1d21] rounded-lg border border-[#35373b] overflow-hidden shadow-2xl sticky top-24">
<div className="bg-[#222529] px-4 py-3 border-b border-[#35373b] flex items-center gap-3">
<div className="w-3 h-3 rounded-full bg-red-500" />
<div className="w-3 h-3 rounded-full bg-yellow-500" />
<div className="w-3 h-3 rounded-full bg-green-500" />
<span className="ml-2 font-mono text-xs text-gray-400">#world-monitor-alerts</span>
</div>
<div className="p-6 space-y-6 font-sans text-sm">
<div className="flex gap-4">
<div className="w-10 h-10 rounded bg-wm-green/20 flex items-center justify-center shrink-0">
<Globe className="w-6 h-6 text-wm-green" aria-hidden="true" />
</div>
<div>
<div className="flex items-baseline gap-2 mb-1">
<span className="font-bold text-gray-200">World Monitor</span>
<span className="text-xs text-gray-500 bg-gray-800 px-1 rounded">APP</span>
<span className="text-xs text-gray-500">8:00 AM</span>
</div>
<p className="text-gray-300 font-bold mb-3">Morning Brief &middot; Mar 6</p>
<div className="space-y-3">
<div className="pl-3 border-l-2 border-red-500">
<span className="text-red-400 font-bold text-xs uppercase tracking-wider">Critical</span>
<p className="text-gray-300 mt-1">GPS jamming across 3 Baltic zones. Pattern matches prior infrastructure disruption signatures. NordBalt cable + Balticconnector in affected area.</p>
</div>
<div className="pl-3 border-l-2 border-orange-500">
<span className="text-orange-400 font-bold text-xs uppercase tracking-wider">Elevated</span>
<p className="text-gray-300 mt-1">Pakistan CII 67&rarr;74. 12 new protest events (Lahore, Karachi, Islamabad). Last comparable spike preceded 2024 political crisis.</p>
</div>
<div className="pl-3 border-l-2 border-yellow-500">
<span className="text-yellow-400 font-bold text-xs uppercase tracking-wider">Watch</span>
<p className="text-gray-300 mt-1">Brent +2.3% on Hormuz AIS anomaly. 4 dark ships in 6h. IRGC exercise announced next week.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
const ApiSection = () => (
<section className="py-24 px-6 border-y border-wm-border bg-[#0a0a0a]" id="api">
<div className="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center">
<div className="order-2 lg:order-1">
<div className="bg-black border border-wm-border rounded-lg overflow-hidden font-mono text-sm">
<div className="bg-wm-card px-4 py-2 border-b border-wm-border flex items-center gap-2">
<Terminal className="w-4 h-4 text-wm-muted" aria-hidden="true" />
<span className="text-wm-muted text-xs">api.worldmonitor.app</span>
</div>
<div className="p-6 text-gray-300 overflow-x-auto">
<pre><code>
<span className="text-wm-blue">curl</span> \<br/>
<span className="text-wm-green">"https://api.worldmonitor.app/v1/intelligence/convergence?region=MENA&time_window=6h"</span> \<br/>
-H <span className="text-wm-green">"Authorization: Bearer wm_live_xxx"</span><br/><br/>
<span className="text-wm-muted">{"{"}</span><br/>
<span className="text-wm-blue">"status"</span>: <span className="text-wm-green">"success"</span>,<br/>
<span className="text-wm-blue">"data"</span>: <span className="text-wm-muted">{"["}</span><br/>
<span className="text-wm-muted">{"{"}</span><br/>
<span className="text-wm-blue">"type"</span>: <span className="text-wm-green">"multi_signal_convergence"</span>,<br/>
<span className="text-wm-blue">"signals"</span>: <span className="text-wm-muted">["military_flights", "ais_dark_ships", "oref_sirens"]</span>,<br/>
<span className="text-wm-blue">"confidence"</span>: <span className="text-orange-400">0.92</span>,<br/>
<span className="text-wm-blue">"location"</span>: <span className="text-wm-muted">{"{"}</span> <span className="text-wm-blue">"lat"</span>: <span className="text-orange-400">34.05</span>, <span className="text-wm-blue">"lng"</span>: <span className="text-orange-400">35.12</span> <span className="text-wm-muted">{"}"}</span><br/>
<span className="text-wm-muted">{"}"}</span><br/>
<span className="text-wm-muted">{"]"}</span><br/>
<span className="text-wm-muted">{"}"}</span>
</code></pre>
</div>
</div>
</div>
<div className="order-1 lg:order-2">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-wm-border bg-wm-card text-wm-muted text-xs font-mono mb-6">
API TIER
</div>
<h2 className="text-3xl md:text-5xl font-display font-bold mb-6">Programmatic Intelligence</h2>
<p className="text-wm-muted mb-8">
For developers, analysts, and teams building on World Monitor data. Separate from Pro use both or either.
</p>
<ul className="space-y-4 mb-8">
<li className="flex items-start gap-3">
<Server className="w-5 h-5 text-wm-muted shrink-0" aria-hidden="true" />
<span className="text-sm">REST API across all 22 service domains</span>
</li>
<li className="flex items-start gap-3">
<Lock className="w-5 h-5 text-wm-muted shrink-0" aria-hidden="true" />
<span className="text-sm">Authenticated per-key, rate-limited per tier</span>
</li>
<li className="flex items-start gap-3">
<Database className="w-5 h-5 text-wm-muted shrink-0" aria-hidden="true" />
<span className="text-sm">Structured JSON with cache headers and OpenAPI 3.1 docs</span>
</li>
</ul>
<div className="grid grid-cols-2 gap-4 mb-8 p-4 bg-wm-card border border-wm-border rounded-sm">
<div>
<p className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">Starter</p>
<p className="text-sm font-bold">1,000 req/day</p>
<p className="text-xs text-wm-muted">5 webhook rules</p>
</div>
<div>
<p className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">Business</p>
<p className="text-sm font-bold">50,000 req/day</p>
<p className="text-xs text-wm-muted">Unlimited webhooks + SLA</p>
</div>
</div>
<p className="text-sm text-wm-muted border-l-2 border-wm-border pl-4">
Feed data into your dashboards, automate alerting via Zapier/n8n/Make, build custom scoring models on CII/risk data.
</p>
</div>
</div>
</section>
);
const EnterpriseShowcase = () => (
<section className="py-24 px-6" id="enterprise">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full border border-wm-border bg-wm-card text-wm-muted text-xs font-mono mb-6">
ENTERPRISE TIER
</div>
<h2 className="text-3xl md:text-5xl font-display font-bold mb-6">Intelligence Infrastructure</h2>
<p className="text-wm-muted max-w-2xl mx-auto">
For governments, institutions, trading desks, and organizations that need the full platform with maximum security, AI agents, and data depth.
</p>
</div>
<div className="grid md:grid-cols-3 gap-6 mb-6">
<div className="bg-wm-card border border-wm-border p-6">
<ShieldAlert className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">Government-Grade Security</h4>
<p className="text-sm text-wm-muted">Air-gapped deployment, on-premises Docker, dedicated cloud tenant, SOC 2 Type II path, SSO/MFA, and full audit trail.</p>
</div>
<div className="bg-wm-card border border-wm-border p-6">
<Cpu className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">AI Agents & MCP</h4>
<p className="text-sm text-wm-muted">Autonomous intelligence agents with investor personas. Connect World Monitor as a tool to Claude, GPT, or custom LLMs via MCP.</p>
</div>
<div className="bg-wm-card border border-wm-border p-6">
<Layers className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">Expanded Data Layers</h4>
<p className="text-sm text-wm-muted">Tens of thousands of infrastructure assets mapped globally. Satellite imagery integration with change detection and SAR.</p>
</div>
</div>
<div className="grid md:grid-cols-3 gap-6 mb-12">
<div className="bg-wm-card border border-wm-border p-6">
<Plug className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">100+ Data Connectors</h4>
<p className="text-sm text-wm-muted">PostgreSQL, Snowflake, Splunk, Sentinel, Jira, Slack, Teams, and more. Export to PDF, PowerPoint, CSV, GeoJSON.</p>
</div>
<div className="bg-wm-card border border-wm-border p-6">
<PanelTop className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">White-Label & Embeddable</h4>
<p className="text-sm text-wm-muted">Your brand, your domain, your desktop app. Embeddable iframe panels for SOC walls and trading floors.</p>
</div>
<div className="bg-wm-card border border-wm-border p-6">
<BarChart3 className="w-8 h-8 text-wm-muted mb-4" aria-hidden="true" />
<h4 className="font-bold mb-2">Financial Intelligence</h4>
<p className="text-sm text-wm-muted">Earnings calendar, energy grid data, enhanced commodity tracking with cargo inference, sanctions screening with AIS correlation.</p>
</div>
</div>
<div className="data-grid">
<div className="data-cell">
<h5 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">Commodity Trading</h5>
<p className="text-sm">Vessel tracking + cargo inference + supply chain graph. Know before the market moves.</p>
</div>
<div className="data-cell">
<h5 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">Government & Institutions</h5>
<p className="text-sm">Air-gapped, AI agents, full situational awareness, MCP. No data leaves your network.</p>
</div>
<div className="data-cell">
<h5 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">Risk Consultancies</h5>
<p className="text-sm">Scenario simulation, investor personas, branded PDF/PowerPoint reports on demand.</p>
</div>
<div className="data-cell">
<h5 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">SOCs & CERT</h5>
<p className="text-sm">Cyber threat layer, SIEM integration, BGP anomaly monitoring, ransomware feeds.</p>
</div>
</div>
</div>
</section>
);
const PricingTable = () => {
const rows = [
{ feature: "Data refresh", free: "5-15 min", pro: "<60 seconds", api: "Per-request", ent: "Live-edge" },
{ feature: "Dashboard", free: "50+ panels", pro: "50+ panels", api: "\u2014", ent: "White-label" },
{ feature: "AI", free: "BYOK", pro: "Included", api: "\u2014", ent: "Agents + personas" },
{ feature: "Briefs & alerts", free: "\u2014", pro: "Daily + flash", api: "\u2014", ent: "Team distribution" },
{ feature: "Delivery", free: "\u2014", pro: "Slack/TG/WA/Email", api: "Webhook", ent: "+ SIEM/MCP" },
{ feature: "API", free: "\u2014", pro: "\u2014", api: "REST + webhook", ent: "+ MCP + bulk" },
{ feature: "Infrastructure layers", free: "45", pro: "45", api: "\u2014", ent: "+ tens of thousands" },
{ feature: "Satellite", free: "\u2014", pro: "\u2014", api: "\u2014", ent: "Imagery + SAR" },
{ feature: "Connectors", free: "\u2014", pro: "\u2014", api: "\u2014", ent: "100+" },
{ feature: "Deployment", free: "Cloud", pro: "Cloud", api: "Cloud", ent: "Cloud/on-prem/air-gap" },
{ feature: "Security", free: "Standard", pro: "Standard", api: "Key auth", ent: "SSO/MFA/RBAC/audit" },
];
return (
<section className="py-24 px-6 max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-5xl font-display font-bold mb-6">Compare Tiers</h2>
</div>
<div className="hidden md:block">
<div className="grid grid-cols-5 gap-4 mb-4 pb-4 border-b border-wm-border font-mono text-xs uppercase tracking-widest text-wm-muted">
<div>Feature</div>
<div>Free ($0)</div>
<div className="text-wm-green">Pro (Early Access)</div>
<div>API (Coming Soon)</div>
<div>Enterprise (Contact)</div>
</div>
{rows.map((row, i) => (
<div key={i} className="grid grid-cols-5 gap-4 py-4 border-b border-wm-border/50 text-sm hover:bg-wm-card/50 transition-colors">
<div className="font-medium">{row.feature}</div>
<div className="text-wm-muted">{row.free}</div>
<div className="text-wm-green">{row.pro}</div>
<div className="text-wm-muted">{row.api}</div>
<div className="text-wm-muted">{row.ent}</div>
</div>
))}
</div>
<div className="md:hidden space-y-4">
{rows.map((row, i) => (
<div key={i} className="bg-wm-card border border-wm-border p-4 rounded-sm">
<p className="font-medium text-sm mb-3">{row.feature}</p>
<div className="grid grid-cols-2 gap-2 text-xs">
<div><span className="text-wm-muted">Free:</span> {row.free}</div>
<div><span className="text-wm-green">Pro:</span> <span className="text-wm-green">{row.pro}</span></div>
<div><span className="text-wm-muted">API:</span> {row.api}</div>
<div><span className="text-wm-muted">Ent:</span> {row.ent}</div>
</div>
</div>
))}
</div>
</section>
);
};
const FAQ = () => {
const faqs = [
{ q: "Is the free version going away?", a: "No. The free dashboard stays free forever. Pro adds AI intelligence, alerts, and delivery channels on top of the same dashboard you use today.", open: true },
{ q: "Can I still use my own API keys?", a: "Yes. Bring-your-own-keys always works. Pro simply means you don't have to register for 20+ separate services." },
{ q: "What's the difference between API and Pro?", a: "Pro delivers AI briefs and alerts to Slack, Telegram, WhatsApp, and email. API gives you programmatic REST access for your own code. They're independent tiers — use both or either." },
{ q: "What's MCP?", a: "Model Context Protocol lets AI agents (Claude, GPT, or custom LLMs) use World Monitor as a tool — querying all 22 services, reading map state, and triggering analysis. Enterprise only." },
{ q: "Can we deploy on-premises?", a: "Enterprise includes Docker deployment, air-gapped mode with local Ollama AI, zero external network calls, full audit logging, and data residency options (EU, US, MENA)." },
{ q: "How fast is near-real-time?", a: "Pro data refreshes under 60 seconds with priority pipeline. Free tier refreshes every 5-15 minutes. Enterprise gets live-edge streaming for critical event types." }
];
return (
<section className="py-24 px-6 max-w-3xl mx-auto">
<h2 className="text-3xl font-display font-bold mb-12 text-center">Frequently Asked Questions</h2>
<div className="space-y-4">
{faqs.map((faq, i) => (
<details key={i} open={faq.open} className="group bg-wm-card border border-wm-border rounded-sm [&_summary::-webkit-details-marker]:hidden">
<summary className="flex items-center justify-between p-6 cursor-pointer font-medium">
{faq.q}
<ChevronDown className="w-5 h-5 text-wm-muted group-open:rotate-180 transition-transform" aria-hidden="true" />
</summary>
<div className="px-6 pb-6 text-wm-muted text-sm border-t border-wm-border pt-4 mt-2">
{faq.a}
</div>
</details>
))}
</div>
</section>
);
};
const Footer = () => (
<footer className="border-t border-wm-border bg-[#020202] pt-24 pb-12 px-6 text-center" id="waitlist">
<div className="max-w-2xl mx-auto mb-16">
<h2 className="text-4xl font-display font-bold mb-6">Be first in line.</h2>
<form className="flex flex-col gap-3 max-w-md mx-auto mb-6" onSubmit={(e) => { e.preventDefault(); const form = e.currentTarget; const email = new FormData(form).get('email') as string; submitWaitlist(email, form); }}>
<input type="text" name="website" autoComplete="off" tabIndex={-1} aria-hidden="true" className="absolute opacity-0 h-0 w-0 pointer-events-none" />
<div className="flex flex-col sm:flex-row gap-3">
<input
type="email"
name="email"
placeholder="Enter your email"
className="flex-1 bg-wm-card border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono"
required
aria-label="Email address for waitlist"
/>
<button type="submit" className="bg-wm-green text-wm-bg px-6 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors whitespace-nowrap">
Join Waitlist
</button>
</div>
<div className="cf-turnstile mx-auto" data-sitekey={TURNSTILE_SITE_KEY} data-theme="dark" data-size="compact" />
</form>
<p className="text-sm text-wm-muted">
Looking for Enterprise? <a href="mailto:enterprise@worldmonitor.app" className="text-wm-text underline underline-offset-4 hover:text-wm-green transition-colors">Contact us</a>.
</p>
</div>
<div className="flex flex-col md:flex-row items-center justify-between max-w-7xl mx-auto pt-8 border-t border-wm-border/50 text-xs text-wm-muted font-mono">
<div className="flex items-center gap-4 mb-4 md:mb-0">
<Logo />
</div>
<div className="flex gap-6">
<a href="https://x.com/eliehabib" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">X</a>
<a href="https://github.com/koala73/worldmonitor" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">GitHub</a>
<a href="https://www.wired.me/story/the-music-streaming-ceo-who-built-a-global-war-map" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">WIRED Article</a>
</div>
</div>
</footer>
);
export default function App() {
return (
<div className="min-h-screen selection:bg-wm-green/30 selection:text-wm-green">
<Navbar />
<main>
<Hero />
<LivePreview />
<SourceMarquee />
<SocialProof />
<DataCoverage />
<Tiers />
<ProShowcase />
<ApiSection />
<EnterpriseShowcase />
<PricingTable />
<FAQ />
</main>
<Footer />
</div>
);
}

61
pro-test/src/index.css Normal file
View File

@@ -0,0 +1,61 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@500;700&display=swap');
@import "tailwindcss";
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
--font-display: "Space Grotesk", "Inter", sans-serif;
--color-wm-bg: #050505;
--color-wm-card: #111111;
--color-wm-border: #222222;
--color-wm-green: #4ade80;
--color-wm-blue: #60a5fa;
--color-wm-text: #f3f4f6;
--color-wm-muted: #9ca3af;
}
body {
background-color: var(--color-wm-bg);
color: var(--color-wm-text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
}
.glass-panel {
background: rgba(17, 17, 17, 0.7);
backdrop-filter: blur(12px);
border: 1px solid var(--color-wm-border);
}
.data-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1px;
background: var(--color-wm-border);
border: 1px solid var(--color-wm-border);
}
.data-cell {
background: var(--color-wm-bg);
padding: 1.5rem;
}
.text-glow {
text-shadow: 0 0 20px rgba(74, 222, 128, 0.3);
}
.border-glow {
box-shadow: 0 0 20px rgba(74, 222, 128, 0.1);
}
/* Marquee animation */
.marquee-track {
display: flex;
animation: marquee 45s linear infinite;
}
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}

10
pro-test/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import {StrictMode} from 'react';
import {createRoot} from 'react-dom/client';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);

26
pro-test/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

18
pro-test/vite.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
base: '/pro/',
build: {
outDir: path.resolve(__dirname, '../public/pro'),
emptyOutDir: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
},
},
});

View File

@@ -2,7 +2,7 @@
> Real-time global intelligence dashboard — AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface.
World Monitor is an open-source (AGPL-3.0) intelligence platform that aggregates 150+ news feeds, 35+ interactive map layers, and multiple AI models into a single dashboard. It runs as a web app, installable PWA, and native desktop application (Tauri) for macOS, Windows, and Linux.
World Monitor is an open-source (AGPL-3.0) intelligence platform that aggregates 435+ news feeds, 45+ interactive map layers, and multiple AI models into a single dashboard. Used by 2M+ people across 190+ countries, as featured in WIRED. It runs as a web app, installable PWA, and native desktop application (Tauri) for macOS, Windows, and Linux.
A single codebase produces three specialized variants — geopolitical, technology, and finance — each with distinct feeds, panels, map layers, and branding. The tri-variant architecture uses build-time selection via the VITE_VARIANT environment variable, with runtime switching available via the header bar.
@@ -22,12 +22,12 @@ The project is built with TypeScript, Vite, MapLibre GL JS, deck.gl, D3.js, and
## Key Features
- Interactive 3D WebGL globe with deck.gl rendering and 35+ toggleable data layers (conflicts, military bases, nuclear facilities, undersea cables, pipelines, datacenters, protests, disasters, cyber threats, and more)
- Interactive 3D WebGL globe with deck.gl rendering and 45+ toggleable data layers (conflicts, military bases, nuclear facilities, undersea cables, pipelines, datacenters, protests, disasters, cyber threats, and more)
- AI-powered intelligence: LLM-synthesized world briefs, hybrid threat classification, focal point detection, country instability index for 22 nations, trending keyword spike detection, and strategic posture assessment
- 150+ curated RSS feeds across geopolitics, defense, energy, tech, and finance with domain-allowlisted proxy
- 435+ curated RSS feeds across geopolitics, defense, energy, tech, and finance with domain-allowlisted proxy
- 8 live news video streams (Bloomberg, Sky News, Al Jazeera, Euronews, DW, France24, CNBC, Al Arabiya)
- 19 live webcams from geopolitical hotspots across 4 regions
- Multilingual UI supporting 14 languages (English, French, Spanish, German, Italian, Polish, Portuguese, Dutch, Swedish, Russian, Arabic, Chinese, Japanese, Turkish) with RTL support
- Multilingual UI supporting 21 languages with RTL support
- 7-signal macro market radar with composite BUY/CASH verdict, BTC ETF flow tracking, stablecoin peg monitoring, Fear & Greed Index
- Country brief pages with AI-generated analysis, CII score ring, prediction markets, 7-day timeline, infrastructure exposure, and stock index
- Geographic convergence detection — identifies when multiple signal types spike in the same area

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

87
public/pro/index.html Normal file
View File

@@ -0,0 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>World Monitor Pro — Real-Time OSINT Dashboard & Global Threat Intelligence</title>
<meta name="description" content="Open-source global intelligence dashboard used by 2M+ people worldwide. Track geopolitics, markets, energy, infrastructure, and natural events in real time. AI-powered briefs delivered to Slack, Telegram, and email." />
<meta name="keywords" content="OSINT dashboard, geopolitical monitoring, real-time intelligence, global tracking, market intelligence, ADS-B tracking, AIS ship tracking, global risk monitoring, situational awareness, open source intelligence" />
<link rel="canonical" href="https://worldmonitor.app/pro" />
<meta property="og:title" content="World Monitor Pro — Real-Time Global Intelligence Platform" />
<meta property="og:description" content="2M+ users track geopolitics, markets, energy, infrastructure, and natural events in real time. AI briefs delivered where you work." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://worldmonitor.app/pro" />
<meta property="og:image" content="https://worldmonitor.app/favico/og-image.png" />
<meta property="og:site_name" content="World Monitor" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@eliehabib" />
<meta name="twitter:creator" content="@eliehabib" />
<link rel="icon" type="image/png" href="https://worldmonitor.app/favico/favicon-32x32.png" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "World Monitor",
"applicationCategory": "SecurityApplication",
"operatingSystem": "Web, Windows, macOS, Linux",
"url": "https://worldmonitor.app",
"description": "Open-source real-time OSINT dashboard for global monitoring — geopolitics, markets, energy, infrastructure, and natural events. Aggregates 435+ data sources across 22 service domains.",
"author": {
"@type": "Person",
"name": "Someone.ceo",
"url": "https://someone.ceo",
"jobTitle": "CEO of Anghami"
},
"offers": [
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Free", "description": "Full dashboard with 435+ sources, 45 map layers, BYOK AI" },
{ "@type": "Offer", "price": "0", "priceCurrency": "USD", "name": "Pro (Waitlist)", "description": "Near-real-time data, AI briefs, flash alerts, 22 services under 1 key" }
],
"featureList": "Real-time geopolitical monitoring, Global flight tracking, Maritime AIS ship monitoring, Infrastructure mapping, Submarine cable monitoring, Internet outage detection, Satellite fire detection, Financial market intelligence, AI-powered analysis, Slack and Telegram delivery, 21 language support"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is the free version of World Monitor going away?",
"acceptedAnswer": { "@type": "Answer", "text": "No. The free dashboard stays free forever. Pro adds AI intelligence, alerts, and delivery channels on top of the same dashboard you use today." }
},
{
"@type": "Question",
"name": "Can I still use my own API keys with World Monitor?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. Bring-your-own-keys always works. Pro simply means you don't have to register for 20+ separate services." }
},
{
"@type": "Question",
"name": "What is the difference between the API and Pro tiers?",
"acceptedAnswer": { "@type": "Answer", "text": "Pro delivers AI briefs and alerts to Slack, Telegram, WhatsApp, and email. API gives you programmatic REST access for your own code. They're independent tiers — use both or either." }
},
{
"@type": "Question",
"name": "What is MCP in World Monitor?",
"acceptedAnswer": { "@type": "Answer", "text": "Model Context Protocol lets AI agents (Claude, GPT, or custom LLMs) use World Monitor as a tool — querying all 22 services, reading map state, and triggering analysis. Enterprise only." }
},
{
"@type": "Question",
"name": "Can World Monitor be deployed on-premises?",
"acceptedAnswer": { "@type": "Answer", "text": "Enterprise includes Docker deployment, air-gapped mode with local Ollama AI, zero external network calls, full audit logging, and data residency options (EU, US, MENA)." }
},
{
"@type": "Question",
"name": "How fast is World Monitor's near-real-time data?",
"acceptedAnswer": { "@type": "Answer", "text": "Pro data refreshes under 60 seconds with priority pipeline. Free tier refreshes every 5-15 minutes. Enterprise gets live-edge streaming for critical event types." }
}
]
}
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<script type="module" crossorigin src="/pro/assets/index-Ba8d3DJQ.js"></script>
<link rel="stylesheet" crossorigin href="/pro/assets/index-D2QULaqm.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -2,10 +2,32 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://worldmonitor.app/</loc>
<lastmod>2026-03-02</lastmod>
<lastmod>2026-03-06</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://worldmonitor.app/pro</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://tech.worldmonitor.app/</loc>
<lastmod>2026-03-02</lastmod>
<lastmod>2026-03-06</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://finance.worldmonitor.app/</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://happy.worldmonitor.app/</loc>
<lastmod>2026-03-06</lastmod>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
</urlset>

View File

@@ -2,7 +2,8 @@
"ignoreCommand": "if [ -z \"$VERCEL_GIT_PREVIOUS_SHA\" ]; then exit 1; fi; git cat-file -e $VERCEL_GIT_PREVIOUS_SHA 2>/dev/null || exit 1; git diff --quiet $VERCEL_GIT_PREVIOUS_SHA HEAD -- ':!*.md' ':!.planning' ':!docs/' ':!e2e/' ':!scripts/' ':!.github/'",
"crons": [],
"rewrites": [
{ "source": "/((?!api|assets|favico|map-styles|data|textures|sw\\.js|manifest\\.webmanifest|offline\\.html|robots\\.txt|sitemap\\.xml|llms\\.txt|llms-full\\.txt|\\.well-known).*)", "destination": "/index.html" }
{ "source": "/pro", "destination": "/pro/index.html" },
{ "source": "/((?!api|assets|favico|map-styles|data|textures|pro|sw\\.js|manifest\\.webmanifest|offline\\.html|robots\\.txt|sitemap\\.xml|llms\\.txt|llms-full\\.txt|\\.well-known).*)", "destination": "/index.html" }
],
"headers": [
{
@@ -25,7 +26,7 @@
]
},
{
"source": "/((?!api|assets|favico|map-styles|data|textures|sw\\.js|manifest\\.webmanifest|offline\\.html|robots\\.txt|sitemap\\.xml|llms\\.txt|llms-full\\.txt|\\.well-known).*)",
"source": "/((?!api|assets|favico|map-styles|data|textures|pro|sw\\.js|manifest\\.webmanifest|offline\\.html|robots\\.txt|sitemap\\.xml|llms\\.txt|llms-full\\.txt|\\.well-known).*)",
"headers": [
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
]
@@ -36,6 +37,18 @@
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/pro/assets/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/pro/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
]
},
{
"source": "/favico/(.*)",
"headers": [