mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
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:
@@ -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;">◎</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;">⚡</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;">🧠</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;">📨</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;">🔑</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 },
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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"]),
|
||||
});
|
||||
|
||||
58
index.html
58
index.html
@@ -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
9
pro-test/.env.example
Normal 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
8
pro-test/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
build/
|
||||
dist/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
.env*
|
||||
!.env.example
|
||||
20
pro-test/README.md
Normal file
20
pro-test/README.md
Normal 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
86
pro-test/index.html
Normal 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
5
pro-test/metadata.json
Normal 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
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
26
pro-test/package.json
Normal 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
814
pro-test/src/App.tsx
Normal 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) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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 · 45+ interactive map layers · 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 · 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→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
61
pro-test/src/index.css
Normal 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
10
pro-test/src/main.tsx
Normal 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
26
pro-test/tsconfig.json
Normal 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
18
pro-test/vite.config.ts
Normal 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, '.'),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
|
||||
239
public/pro/assets/index-Ba8d3DJQ.js
Normal file
239
public/pro/assets/index-Ba8d3DJQ.js
Normal file
File diff suppressed because one or more lines are too long
1
public/pro/assets/index-D2QULaqm.css
Normal file
1
public/pro/assets/index-D2QULaqm.css
Normal file
File diff suppressed because one or more lines are too long
87
public/pro/index.html
Normal file
87
public/pro/index.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
17
vercel.json
17
vercel.json
@@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user