| import { useState, useEffect, useRef, createContext, useContext, type ReactElement, type ReactNode } from 'react'; |
| import type { UserResource } from '@clerk/types'; |
| import * as Sentry from '@sentry/react'; |
| import { motion } from 'motion/react'; |
| import { |
| Globe, 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, TrendingUp, |
| Filter, Lightbulb, SlidersHorizontal, Telescope, |
| LineChart, Search, Shield, Building2, |
| Landmark, Fuel |
| } from 'lucide-react'; |
| import { t } from './i18n'; |
| import { ensureClerk, tryResumeCheckoutFromUrl } from './services/checkout'; |
| import { scheduleClerkLoad, subscribeClerkLoaded } from './services/clerk'; |
| import { startClerkUserStateSync, type ClerkUserState } from './services/clerk-user-state'; |
| import { hasLiveClientSession } from './services/clerk-session'; |
| import { PricingSection } from './components/PricingSection'; |
| import { SoonBadge } from './components/SoonBadge'; |
| import { Logo } from './components/Logo'; |
| import { WiredBadge } from './components/WiredBadge'; |
| import { Footer } from './components/Footer'; |
| import { |
| DASHBOARD_SCREENSHOT_JPG, |
| DASHBOARD_SCREENSHOT_AVIF_SRCSET, |
| DASHBOARD_SCREENSHOT_WEBP_SRCSET, |
| } from './assets/dashboard-screenshot'; |
| import { ensureTurnstileScript } from './turnstile'; |
| import wiredLogo from './assets/wired-logo.svg'; |
| import { |
| DASHBOARD_EMBED_PREVIEW_URL, |
| DASHBOARD_PATH, |
| DASHBOARD_URL, |
| } from './routes'; |
|
|
| const API_BASE = 'https://api.worldmonitor.app/api'; |
| const TURNSTILE_SITE_KEY = '0x4AAAAAACnaYgHIyxclu8Tj'; |
|
|
| declare global { |
| interface Window { |
| turnstile?: { |
| render: (container: string | HTMLElement, opts: Record<string, unknown>) => string; |
| getResponse: (widgetOrId?: string | HTMLElement) => string | undefined; |
| reset: (widgetOrId?: string | HTMLElement) => void; |
| }; |
| } |
| } |
|
|
| export function renderTurnstileWidgets(): number { |
| if (!window.turnstile) return 0; |
| let count = 0; |
| document.querySelectorAll<HTMLElement>('.cf-turnstile:not([data-rendered])').forEach(el => { |
| const widgetId = window.turnstile!.render(el, { |
| sitekey: TURNSTILE_SITE_KEY, |
| size: 'flexible', |
| callback: (token: string) => { el.dataset.token = token; }, |
| 'expired-callback': () => { delete el.dataset.token; }, |
| 'error-callback': () => { delete el.dataset.token; }, |
| }); |
| el.dataset.rendered = 'true'; |
| el.dataset.widgetId = String(widgetId); |
| count++; |
| }); |
| return count; |
| } |
|
|
| function getRefCode(): string | undefined { |
| const params = new URLSearchParams(window.location.search); |
| return params.get('ref') || undefined; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const REFERRAL_CODE_REGEX = /^[a-zA-Z0-9_-]+$/; |
| function isValidRefCode(code: string): boolean { |
| return code.length > 0 && code.length <= 64 && REFERRAL_CODE_REGEX.test(code); |
| } |
|
|
| function appendRefToUrl(url: string, refCode: string | undefined): string { |
| if (!refCode || !isValidRefCode(refCode)) return url; |
| const sep = url.includes('?') ? '&' : '?'; |
| return `${url}${sep}wm_referral=${encodeURIComponent(refCode)}`; |
| } |
|
|
| function openSignIn(): void { |
| ensureClerk().then(c => c.openSignIn()).catch((err) => { |
| console.error('[auth] Failed to open sign in:', err); |
| Sentry.captureException(err, { tags: { surface: 'pro-marketing', action: 'open-sign-in' } }); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function useClerkUser(): ClerkUserState { |
| const [state, setState] = useState<ClerkUserState>(() => ({ |
| user: null, |
| isLoaded: true, |
| signedIn: hasLiveClientSession(), |
| })); |
|
|
| useEffect(() => { |
| return startClerkUserStateSync(setState, { |
| hasLiveClientSession, |
| subscribeClerkLoaded, |
| scheduleClerkLoad, |
| onLoadError(err) { |
| console.error('[auth] Failed to load Clerk for nav auth state:', err); |
| Sentry.captureException(err, { tags: { surface: 'pro-marketing', action: 'load-clerk-for-nav' } }); |
| setState({ user: null, isLoaded: true, signedIn: false }); |
| }, |
| }); |
| }, []); |
|
|
| return state; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| type ProEntitlementState = { isPro: boolean; isChecked: boolean }; |
| const ProEntitlementContext = createContext<ProEntitlementState>({ isPro: false, isChecked: false }); |
|
|
| function ProEntitlementProvider({ children }: { children: ReactNode }): ReactElement { |
| const { user, signedIn } = useClerkUser(); |
| const userId = user?.id ?? null; |
| const [state, setState] = useState<ProEntitlementState>({ isPro: false, isChecked: false }); |
|
|
| useEffect(() => { |
| if (!signedIn) { |
| setState({ isPro: false, isChecked: true }); |
| return; |
| } |
| if (!userId) { |
| setState({ isPro: false, isChecked: false }); |
| return; |
| } |
| let cancelled = false; |
| (async () => { |
| try { |
| const clerk = await ensureClerk(); |
| |
| |
| |
| |
| |
| |
| let token = await clerk.session?.getToken().catch(() => null); |
| if (!token) { |
| await new Promise((r) => setTimeout(r, 2000)); |
| token = await clerk.session?.getToken().catch(() => null); |
| } |
| if (!token) { |
| if (!cancelled) setState({ isPro: false, isChecked: true }); |
| return; |
| } |
| const resp = await fetch(`${API_BASE}/me/entitlement`, { |
| headers: { Authorization: `Bearer ${token}` }, |
| signal: AbortSignal.timeout(8_000), |
| }); |
| if (!resp.ok) { |
| if (!cancelled) setState({ isPro: false, isChecked: true }); |
| return; |
| } |
| const data = await resp.json() as { isPro?: boolean }; |
| if (!cancelled) setState({ isPro: data.isPro === true, isChecked: true }); |
| } catch (err) { |
| console.error('[auth] Failed to check pro entitlement:', err); |
| Sentry.captureException(err, { tags: { surface: 'pro-marketing', action: 'check-entitlement' } }); |
| if (!cancelled) setState({ isPro: false, isChecked: true }); |
| } |
| })(); |
| return () => { cancelled = true; }; |
| }, [signedIn, userId]); |
|
|
| return <ProEntitlementContext.Provider value={state}>{children}</ProEntitlementContext.Provider>; |
| } |
|
|
| function useProEntitlement(): ProEntitlementState { |
| return useContext(ProEntitlementContext); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function ClerkUserButton({ user }: { user: UserResource | null }): ReactElement { |
| const ref = useRef<HTMLDivElement | null>(null); |
|
|
| useEffect(() => { |
| if (!user) return; |
| if (!ref.current) return; |
| const el = ref.current; |
| let unmounted = false; |
|
|
| ensureClerk() |
| .then((clerk) => { |
| if (unmounted || !el) return; |
| clerk.mountUserButton(el, { |
| afterSignOutUrl: 'https://www.worldmonitor.app/pro', |
| }); |
| }) |
| .catch((err) => { |
| console.error('[auth] Failed to mount user button:', err); |
| Sentry.captureException(err, { tags: { surface: 'pro-marketing', action: 'mount-user-button' } }); |
| }); |
|
|
| return () => { |
| unmounted = true; |
| ensureClerk().then((clerk) => { |
| if (el) clerk.unmountUserButton(el); |
| }).catch(() => { }); |
| }; |
| }, [user]); |
|
|
| return ( |
| <div ref={ref} className="flex h-8 w-8 items-center justify-center"> |
| {!user && ( |
| <span |
| className="block h-8 w-8 rounded-full border border-wm-border bg-wm-card shadow-[inset_0_0_0_1px_rgba(255,255,255,0.04)]" |
| aria-hidden="true" |
| /> |
| )} |
| </div> |
| ); |
| } |
|
|
| 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 Navbar = () => { |
| const { user, isLoaded, signedIn } = useClerkUser(); |
| const { isPro, isChecked } = useProEntitlement(); |
| |
| |
| |
| |
| |
| |
| const showGoToDashboard = isLoaded && signedIn && !!user && isChecked && isPro; |
| return ( |
| <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">{t('nav.free')}</a> |
| <a href="#pro" className="hover:text-wm-green transition-colors">{t('nav.pro')}</a> |
| <a href="#api" className="hover:text-wm-text transition-colors">{t('nav.api')}</a> |
| <a href="#enterprise" className="hover:text-wm-text transition-colors">{t('nav.enterprise')}</a> |
| </div> |
| <div className="flex items-center gap-2"> |
| {isLoaded && (signedIn |
| ? <ClerkUserButton user={user} /> |
| : ( |
| <button |
| type="button" |
| onClick={openSignIn} |
| className="border border-wm-border text-wm-text px-4 py-2 rounded-sm font-mono text-xs uppercase tracking-wider font-bold hover:border-wm-text transition-colors" |
| > |
| {t('nav.signIn')} |
| </button> |
| ))} |
| {showGoToDashboard ? ( |
| <a |
| href={DASHBOARD_URL} |
| 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 inline-flex items-center gap-1.5" |
| > |
| {t('nav.goToDashboard')} <ArrowRight className="w-3 h-3" aria-hidden="true" /> |
| </a> |
| ) : ( |
| <a href="#pricing" 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"> |
| {t('nav.upgradeToPro')} |
| </a> |
| )} |
| </div> |
| </div> |
| </nav> |
| ); |
| }; |
|
|
| |
| const SignalBars = () => { |
| const total = 60; |
| const center = total / 2; |
| const signalRadius = 8; |
| const jitter = (index: number, salt: number) => { |
| const x = Math.sin(index * 12.9898 + salt * 78.233) * 43758.5453; |
| return x - Math.floor(x); |
| }; |
|
|
| return ( |
| <div className="relative my-4 md:my-8 -mx-6"> |
| <div className="absolute inset-0 flex items-center justify-center pointer-events-none"> |
| <div className="w-64 h-40 md:w-96 md:h-56 bg-wm-green/8 rounded-full blur-[80px]" /> |
| </div> |
| <div className="flex items-end justify-center gap-[3px] md:gap-1 h-28 md:h-44 relative px-4" aria-hidden="true"> |
| {Array.from({ length: total }).map((_, i) => { |
| const distFromCenter = Math.abs(i - center); |
| const isSignal = distFromCenter <= signalRadius; |
| const signalIntensity = isSignal ? 1 - distFromCenter / signalRadius : 0; |
| const peakHeight = 60 + signalIntensity * 110; |
| const noiseBase = Math.max(8, 35 - distFromCenter * 0.8); |
| const barHeight = isSignal ? peakHeight : noiseBase; |
| const initialScale = isSignal ? 0.3 : 0.5; |
| const scaleY = isSignal |
| ? [0.5, 1, 0.65, 0.9] |
| : [1, 0.3, 0.7, 0.15, 0.5]; |
| |
| return ( |
| <div |
| key={i} |
| className="flex flex-1 max-w-2 md:max-w-3 items-end" |
| style={{ height: `${barHeight}px` }} |
| > |
| <motion.div |
| className={`w-full rounded-sm ${isSignal ? 'bg-wm-green' : 'bg-wm-muted/20'}`} |
| style={{ |
| height: '100%', |
| transformOrigin: 'bottom', |
| ...(isSignal |
| ? { boxShadow: `0 0 ${6 + signalIntensity * 12}px rgba(74,222,128,${signalIntensity * 0.5})` } |
| : null), |
| }} |
| initial={{ scaleY: initialScale, opacity: isSignal ? 0.4 : 0.08 }} |
| animate={isSignal |
| ? { |
| scaleY, |
| opacity: [0.6 + signalIntensity * 0.3, 1, 0.75 + signalIntensity * 0.2, 0.95], |
| } |
| : { |
| scaleY, |
| opacity: [0.2, 0.06, 0.15, 0.04, 0.12], |
| } |
| } |
| transition={{ |
| duration: isSignal ? 2.5 + signalIntensity * 0.5 : 1 + jitter(i, 1) * 0.6, |
| repeat: Infinity, |
| repeatType: 'reverse', |
| delay: isSignal ? distFromCenter * 0.07 : jitter(i, 2) * 0.6, |
| ease: 'easeInOut', |
| }} |
| /> |
| </div> |
| ); |
| })} |
| </div> |
| </div> |
| ); |
| }; |
|
|
| const Hero = () => { |
| const { user, isLoaded, signedIn } = useClerkUser(); |
| const { isPro, isChecked } = useProEntitlement(); |
| |
| |
| |
| const showSignIn = isLoaded && !signedIn; |
| |
| |
| |
| |
| const showGoToDashboard = isLoaded && signedIn && !!user && isChecked && isPro; |
| return ( |
| <section className="pt-28 pb-12 px-6 relative overflow-hidden"> |
| <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_20%,rgba(74,222,128,0.08)_0%,transparent_50%)] 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-4"> |
| <WiredBadge /> |
| </div> |
| |
| <h1 className="text-6xl md:text-8xl font-display font-bold tracking-tighter leading-[0.95]"> |
| <span className="text-wm-muted/40">{t('hero.noiseWord')}</span> |
| <span className="mx-3 md:mx-5 text-wm-border/50">β</span> |
| <span className="text-transparent bg-clip-text bg-gradient-to-r from-wm-green to-emerald-300 text-glow">{t('hero.signalWord')}</span> |
| </h1> |
| |
| <SignalBars /> |
| |
| <p className="text-lg md:text-xl text-wm-muted max-w-xl mx-auto font-light leading-relaxed"> |
| {t('hero.valueProps')} |
| </p> |
| |
| <div className="flex flex-col sm:flex-row gap-3 justify-center mt-8"> |
| {showGoToDashboard ? ( |
| <a href={DASHBOARD_URL} 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"> |
| {t('hero.goToDashboard')} <ArrowRight className="w-4 h-4" aria-hidden="true" /> |
| </a> |
| ) : ( |
| <a href="#pricing" 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"> |
| {t('hero.choosePlan')} <ArrowRight className="w-4 h-4" aria-hidden="true" /> |
| </a> |
| )} |
| {showSignIn && ( |
| <button type="button" onClick={openSignIn} className="border border-wm-border text-wm-text px-6 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:border-wm-text transition-colors"> |
| {t('hero.signIn')} |
| </button> |
| )} |
| </div> |
| |
| <div className="flex items-center justify-center mt-4"> |
| <a href={appendRefToUrl(DASHBOARD_URL, getRefCode())} className="text-xs text-wm-green font-mono hover:text-green-300 transition-colors flex items-center gap-1"> |
| {t('hero.tryFreeDashboard')} <ArrowRight className="w-3 h-3" aria-hidden="true" /> |
| </a> |
| </div> |
| </motion.div> |
| </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: t('socialProof.uniqueVisitors') }, |
| { value: "421K", label: t('socialProof.peakDailyUsers') }, |
| { value: "190+", label: t('socialProof.countriesReached') }, |
| { value: "500+", label: t('socialProof.liveDataSources') }, |
| ].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"> |
| "{t('socialProof.quote')}" |
| </p> |
| <footer className="mt-6 flex items-center justify-center gap-3"> |
| <a href="https://www.wired.com/story/world-monitor-elie-habib/" target="_blank" rel="noreferrer" className="inline-flex items-center gap-2 text-wm-muted hover:text-wm-text transition-colors"> |
| <img src={wiredLogo} alt="WIRED" loading="lazy" className="h-5 brightness-0 invert opacity-60 hover:opacity-100 transition-opacity" /> |
| </a> |
| </footer> |
| </blockquote> |
| </div> |
| </section> |
| ); |
|
|
| |
| const TwoPathSplit = () => ( |
| <section className="py-24 px-6 max-w-5xl mx-auto" id="tiers"> |
| <h2 className="sr-only">Plans</h2> |
| <div className="grid md:grid-cols-2 gap-8"> |
| <div className="bg-wm-card border border-wm-green p-8 relative border-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">{t('twoPath.proTitle')}</h3> |
| <p className="text-sm text-wm-muted mb-6">{t('twoPath.proDesc')}</p> |
| <ul className="space-y-3 mb-8"> |
| {[t('twoPath.proF1'), t('twoPath.proF2'), t('twoPath.proF3'), t('twoPath.proF4'), t('twoPath.proF5'), t('twoPath.proF6'), t('twoPath.proF7'), t('twoPath.proF8'), t('twoPath.proF9')].map((f, i) => ( |
| <li key={i} className="flex items-start gap-3 text-sm"> |
| <Check className="w-4 h-4 shrink-0 mt-0.5 text-wm-green" aria-hidden="true" /> |
| <span className="text-wm-muted">{f}</span> |
| </li> |
| ))} |
| </ul> |
| <a href="#pricing" className="block text-center py-2.5 rounded-sm font-mono text-xs uppercase tracking-wider font-bold bg-wm-green text-wm-bg hover:bg-green-400 transition-colors"> |
| {t('twoPath.choosePlan')} |
| </a> |
| </div> |
| |
| <div className="bg-wm-card border border-wm-border p-8"> |
| <h3 className="font-display text-2xl font-bold mb-2">{t('twoPath.entTitle')}</h3> |
| <p className="text-sm text-wm-muted mb-6">{t('twoPath.entDesc')}</p> |
| <ul className="space-y-3 mb-8"> |
| <li className="text-xs font-mono text-wm-green uppercase tracking-wider mb-1">{t('twoPath.entF1')}</li> |
| {[t('twoPath.entF2'), t('twoPath.entF3'), t('twoPath.entF4'), t('twoPath.entF5'), t('twoPath.entF6'), t('twoPath.entF7'), t('twoPath.entF8'), t('twoPath.entF9'), t('twoPath.entF10'), t('twoPath.entF11')].map((f, i) => ( |
| <li key={i} className="flex items-start gap-3 text-sm"> |
| <Check className="w-4 h-4 shrink-0 mt-0.5 text-wm-muted" aria-hidden="true" /> |
| <span className="text-wm-muted">{f}</span> |
| </li> |
| ))} |
| </ul> |
| <a href="#enterprise" className="block text-center py-2.5 rounded-sm font-mono text-xs uppercase tracking-wider font-bold border border-wm-border text-wm-muted hover:text-wm-text hover:border-wm-text transition-colors"> |
| {t('twoPath.entCta')} |
| </a> |
| </div> |
| </div> |
| </section> |
| ); |
|
|
| |
| const WhyUpgrade = () => { |
| const items = [ |
| { icon: <Filter className="w-6 h-6" aria-hidden="true" />, title: t('whyUpgrade.noiseTitle'), desc: t('whyUpgrade.noiseDesc') }, |
| { icon: <TrendingUp className="w-6 h-6" aria-hidden="true" />, title: t('whyUpgrade.fasterTitle'), desc: t('whyUpgrade.fasterDesc') }, |
| { icon: <SlidersHorizontal className="w-6 h-6" aria-hidden="true" />, title: t('whyUpgrade.controlTitle'), desc: t('whyUpgrade.controlDesc') }, |
| { icon: <Telescope className="w-6 h-6" aria-hidden="true" />, title: t('whyUpgrade.deeperTitle'), desc: t('whyUpgrade.deeperDesc') }, |
| ]; |
|
|
| return ( |
| <section className="py-24 px-6 border-t border-wm-border bg-wm-card/20"> |
| <div className="max-w-5xl mx-auto"> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-16 text-center">{t('whyUpgrade.title')}</h2> |
| <div className="grid md:grid-cols-2 gap-8"> |
| {items.map((item, i) => ( |
| <div key={i} className="flex gap-5"> |
| <div className="text-wm-green shrink-0 mt-1">{item.icon}</div> |
| <div> |
| <h3 className="font-bold text-lg mb-2">{item.title}</h3> |
| <p className="text-sm text-wm-muted leading-relaxed">{item.desc}</p> |
| </div> |
| </div> |
| ))} |
| </div> |
| </div> |
| </section> |
| ); |
| }; |
|
|
| |
| const Pillars = () => { |
| const items = [ |
| { icon: <Brain className="w-7 h-7" aria-hidden="true" />, title: t('pillars.askIt'), desc: t('pillars.askItDesc') }, |
| { icon: <Bell className="w-7 h-7" aria-hidden="true" />, title: t('pillars.subscribeIt'), desc: t('pillars.subscribeItDesc') }, |
| { icon: <Plug className="w-7 h-7" aria-hidden="true" />, title: t('pillars.buildOnIt'), desc: t('pillars.buildOnItDesc') }, |
| ]; |
|
|
| return ( |
| <section className="py-20 px-6 border-t border-wm-border"> |
| <div className="max-w-5xl mx-auto"> |
| <div className="grid md:grid-cols-3 gap-6"> |
| {items.map((item, i) => ( |
| <div key={i} className="bg-wm-card border border-wm-border p-6 hover:border-wm-green/30 transition-colors"> |
| <div className="text-wm-green mb-4">{item.icon}</div> |
| <h3 className="font-display text-xl font-bold mb-2">{item.title}</h3> |
| <p className="text-sm text-wm-muted leading-relaxed">{item.desc}</p> |
| </div> |
| ))} |
| </div> |
| </div> |
| </section> |
| ); |
| }; |
|
|
| |
| const DeliveryDesk = () => ( |
| <section className="py-24 px-6 border-t border-wm-border bg-wm-card/20"> |
| <div className="max-w-4xl mx-auto text-center"> |
| <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"> |
| {t('deliveryDesk.eyebrow')} |
| </div> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-6">{t('deliveryDesk.title')}</h2> |
| <p className="text-lg text-wm-muted leading-relaxed mb-6"> |
| {t('deliveryDesk.body')} |
| </p> |
| <p className="text-xl md:text-2xl font-display font-bold text-wm-green mb-8"> |
| {t('deliveryDesk.closer')} |
| </p> |
| <p className="font-mono text-xs text-wm-muted uppercase tracking-widest"> |
| {t('deliveryDesk.channels')} |
| </p> |
| </div> |
| </section> |
| ); |
|
|
| |
| |
| const LIVE_PREVIEW_IMAGE_SIZES = '(min-width: 1200px) 1152px, calc(100vw - 3rem)'; |
|
|
| const LivePreview = () => ( |
| <section className="px-6 py-16"> |
| <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">{t('livePreview.windowTitle')}</span> |
| <a |
| href={appendRefToUrl(DASHBOARD_URL, getRefCode())} |
| 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" |
| > |
| {t('livePreview.openFullScreen')} <ExternalLink className="w-3 h-3" aria-hidden="true" /> |
| </a> |
| </div> |
| <div className="relative aspect-[16/9] bg-black"> |
| <picture> |
| <source type="image/avif" srcSet={DASHBOARD_SCREENSHOT_AVIF_SRCSET} sizes={LIVE_PREVIEW_IMAGE_SIZES} /> |
| <source type="image/webp" srcSet={DASHBOARD_SCREENSHOT_WEBP_SRCSET} sizes={LIVE_PREVIEW_IMAGE_SIZES} /> |
| <img |
| src={DASHBOARD_SCREENSHOT_JPG} |
| alt="World Monitor Dashboard" |
| className="absolute inset-0 w-full h-full object-cover" |
| loading="lazy" |
| decoding="async" |
| /> |
| </picture> |
| <iframe |
| // ?embed=pro-preview is the unique marker the main app's |
| // IS_EMBEDDED_PREVIEW helper keys off to silence premium-RPC |
| // 401s that otherwise surface in /pro's parent console. See |
| // src/utils/embedded-preview.ts. Not a generic iframe gate β |
| // enterprise white-label embeds without this marker keep |
| // firing premium RPCs normally. |
| src={DASHBOARD_EMBED_PREVIEW_URL} |
| title={t('livePreview.iframeTitle')} |
| className="relative w-full h-full border-0" |
| loading="lazy" |
| sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" |
| /> |
| <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={appendRefToUrl(DASHBOARD_URL, getRefCode())} |
| 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" |
| > |
| {t('livePreview.tryLiveDashboard')} <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"> |
| {t('livePreview.description')} |
| </p> |
| </div> |
| </section> |
| ); |
|
|
| |
| const SourceMarquee = () => { |
| const sources = [ |
| "Finnhub", "FRED", "Bloomberg", "CNBC", "Nikkei", "CoinGecko", "Polymarket", |
| "Reuters", "ACLED", "UCDP", "GDELT", "NASA FIRMS", "USGS", "OpenSky", "AISStream", |
| "Cloudflare Radar", "BGPStream", "GPSJam", "NOAA", "Copernicus", "IAEA", |
| "Al Jazeera", "Sky News", "Euronews", "DW News", "France 24", |
| "OilPrice", "Rigzone", "Maritime Executive", "Hellenic Shipping News", |
| "Defense One", "Jane's", "The War Zone", |
| "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 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"> |
| {t('proShowcase.proTier')} |
| </div> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-6">{t('proShowcase.title')}</h2> |
| <p className="text-wm-muted mb-8"> |
| {t('proShowcase.subtitle')} |
| </p> |
| |
| <div className="space-y-6"> |
| <div className="flex gap-4"> |
| <TrendingUp className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.equityResearch')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.equityResearchDesc')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <Globe className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.geopoliticalAnalysis')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.geopoliticalAnalysisDesc')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <BarChart3 className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.economyAnalytics')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.economyAnalyticsDesc')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <ShieldAlert className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.riskMonitoring')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.riskMonitoringDesc')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <Telescope className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h4 className="font-bold mb-1"> |
| {t('proShowcase.orbitalSurveillance')} |
| <SoonBadge /> |
| </h4> |
| <p className="text-sm text-wm-muted">{t('proShowcase.orbitalSurveillanceDesc').replace(/^\(Soon\)\s*/, '')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <Clock className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.morningBriefs')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.morningBriefsDesc')}</p> |
| </div> |
| </div> |
| <div className="flex gap-4"> |
| <Key className="w-6 h-6 text-wm-green shrink-0" aria-hidden="true" /> |
| <div> |
| <h3 className="font-bold mb-1">{t('proShowcase.oneKey')}</h3> |
| <p className="text-sm text-wm-muted">{t('proShowcase.oneKeyDesc')}</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">{t('proShowcase.deliveryLabel')}</p> |
| <div className="flex gap-6"> |
| {[ |
| { icon: <SlackIcon />, label: "Slack" }, |
| { icon: <MessageSquare className="w-5 h-5" aria-hidden="true" />, label: "Discord" }, |
| { icon: <Send className="w-5 h-5" aria-hidden="true" />, label: "Telegram" }, |
| { icon: <Mail className="w-5 h-5" aria-hidden="true" />, label: "Email" }, |
| { icon: <Plug className="w-5 h-5" aria-hidden="true" />, label: "Webhook" }, |
| ].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">{t('slackMock.morningBrief')} · Mar 6</p> |
| |
| <div className="space-y-3"> |
| <div className="pl-3 border-l-2 border-blue-500"> |
| <span className="text-blue-400 font-bold text-xs uppercase tracking-wider">{t('slackMock.markets')}</span> |
| <p className="text-gray-300 mt-1">{t('slackMock.marketsText')}</p> |
| </div> |
| |
| <div className="pl-3 border-l-2 border-orange-500"> |
| <span className="text-orange-400 font-bold text-xs uppercase tracking-wider">{t('slackMock.elevated')}</span> |
| <p className="text-gray-300 mt-1">{t('slackMock.elevatedText')}</p> |
| </div> |
| |
| <div className="pl-3 border-l-2 border-yellow-500"> |
| <span className="text-yellow-400 font-bold text-xs uppercase tracking-wider">{t('slackMock.watch')}</span> |
| <p className="text-gray-300 mt-1">{t('slackMock.watchText')}</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </section> |
| ); |
|
|
| |
| const AudiencePersonas = () => { |
| const personas = [ |
| { icon: <LineChart className="w-6 h-6" aria-hidden="true" />, title: t('audience.investorsTitle'), desc: t('audience.investorsDesc') }, |
| { icon: <Fuel className="w-6 h-6" aria-hidden="true" />, title: t('audience.tradersTitle'), desc: t('audience.tradersDesc') }, |
| { icon: <Search className="w-6 h-6" aria-hidden="true" />, title: t('audience.researchersTitle'), desc: t('audience.researchersDesc') }, |
| { icon: <Globe className="w-6 h-6" aria-hidden="true" />, title: t('audience.journalistsTitle'), desc: t('audience.journalistsDesc') }, |
| { icon: <Landmark className="w-6 h-6" aria-hidden="true" />, title: t('audience.govTitle'), desc: t('audience.govDesc') }, |
| { icon: <Building2 className="w-6 h-6" aria-hidden="true" />, title: t('audience.teamsTitle'), desc: t('audience.teamsDesc') }, |
| ]; |
|
|
| return ( |
| <section className="py-24 px-6"> |
| <div className="max-w-5xl mx-auto"> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-16 text-center">{t('audience.title')}</h2> |
| <div className="grid md:grid-cols-3 gap-6"> |
| {personas.map((p, i) => ( |
| <div key={i} className="bg-wm-card border border-wm-border p-6 hover:border-wm-green/30 transition-colors"> |
| <div className="text-wm-green mb-4">{p.icon}</div> |
| <h3 className="font-bold mb-2">{p.title}</h3> |
| <p className="text-sm text-wm-muted">{p.desc}</p> |
| </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"> |
| {t('apiSection.apiTier')} |
| </div> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-6">{t('apiSection.title')}</h2> |
| <p className="text-wm-muted mb-8"> |
| {t('apiSection.subtitle')} |
| </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">{t('apiSection.restApi')}</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">{t('apiSection.authenticated')}</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">{t('apiSection.structured')}</span> |
| </li> |
| <li className="flex items-start gap-3"> |
| <Terminal className="w-5 h-5 text-wm-muted shrink-0" aria-hidden="true" /> |
| <span className="text-sm"> |
| {t('apiSection.cli')}{' β '} |
| <a |
| href="https://www.npmjs.com/package/worldmonitor" |
| target="_blank" |
| rel="noopener noreferrer" |
| className="font-mono text-wm-green hover:underline" |
| > |
| npx worldmonitor |
| </a> |
| </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">{t('apiSection.starter')}</p> |
| <p className="text-sm font-bold">{t('apiSection.starterReqs')}</p> |
| <p className="text-xs text-wm-muted">{t('apiSection.starterWebhooks')}</p> |
| </div> |
| <div> |
| <p className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('apiSection.business')}</p> |
| <p className="text-sm font-bold">{t('apiSection.businessReqs')}</p> |
| <p className="text-xs text-wm-muted">{t('apiSection.businessWebhooks')}</p> |
| </div> |
| </div> |
| |
| <p className="text-sm text-wm-muted border-l-2 border-wm-border pl-4"> |
| {t('apiSection.feedData')} |
| </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"> |
| {t('enterpriseShowcase.enterpriseTier')} |
| </div> |
| <h2 className="text-3xl md:text-5xl font-display font-bold mb-6">{t('enterpriseShowcase.title')}</h2> |
| <p className="text-wm-muted max-w-2xl mx-auto"> |
| {t('enterpriseShowcase.subtitle')} |
| </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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.security')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.securityDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.aiAgents')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.aiAgentsDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.dataLayers')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.dataLayersDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.connectors')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.connectorsDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.whiteLabel')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.whiteLabelDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.financial')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.financialDesc')}</p> |
| </div> |
| </div> |
| |
| <div className="data-grid mb-12"> |
| <div className="data-cell"> |
| <h4 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.commodity')}</h4> |
| <p className="text-sm">{t('enterpriseShowcase.commodityDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h4 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.government')}</h4> |
| <p className="text-sm">{t('enterpriseShowcase.governmentDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h4 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.risk')}</h4> |
| <p className="text-sm">{t('enterpriseShowcase.riskDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h4 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.soc')}</h4> |
| <p className="text-sm">{t('enterpriseShowcase.socDesc')}</p> |
| </div> |
| </div> |
| |
| <div className="text-center mt-12"> |
| <a |
| href="#enterprise-contact" |
| aria-label="Talk to sales about Enterprise plans" |
| className="inline-flex items-center gap-2 bg-wm-green text-wm-bg px-8 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors" |
| > |
| {t('enterpriseShowcase.talkToSales')} <ArrowRight className="w-4 h-4" aria-hidden="true" /> |
| </a> |
| </div> |
| </div> |
| </section> |
| ); |
|
|
| |
| const PricingTable = () => { |
| const rows = [ |
| { feature: t('pricingTable.dataRefresh'), free: t('pricingTable.f5_15min'), pro: t('pricingTable.fLt60s'), api: t('pricingTable.fPerRequest'), ent: t('pricingTable.fLiveEdge') }, |
| { feature: t('pricingTable.dashboard'), free: t('pricingTable.f50panels'), pro: t('pricingTable.f50panels'), api: "\u2014", ent: t('pricingTable.fWhiteLabel') }, |
| { feature: t('pricingTable.ai'), free: t('pricingTable.fBYOK'), pro: t('pricingTable.fIncluded'), api: "\u2014", ent: t('pricingTable.fAgentsPersonas') }, |
| { feature: t('pricingTable.briefsAlerts'), free: "\u2014", pro: t('pricingTable.fDailyFlash'), api: "\u2014", ent: t('pricingTable.fTeamDist') }, |
| { feature: t('pricingTable.delivery'), free: "\u2014", pro: t('pricingTable.fSlackTgWa'), api: t('pricingTable.fWebhook'), ent: t('pricingTable.fSiemMcp') }, |
| { feature: t('pricingTable.apiRow'), free: "\u2014", pro: "\u2014", api: t('pricingTable.fRestWebhook'), ent: t('pricingTable.fMcpBulk') }, |
| { feature: t('pricingTable.infraLayers'), free: t('pricingTable.f50plus'), pro: t('pricingTable.f50plus'), api: "\u2014", ent: t('pricingTable.fTensOfThousands') }, |
| { feature: t('pricingTable.satellite'), free: t('pricingTable.fLiveTracking'), pro: t('pricingTable.fPassAlerts'), api: "\u2014", ent: t('pricingTable.fImagerySar') }, |
| { feature: t('pricingTable.connectorsRow'), free: "\u2014", pro: "\u2014", api: "\u2014", ent: t('pricingTable.f100plus') }, |
| { feature: t('pricingTable.deployment'), free: t('pricingTable.fCloud'), pro: t('pricingTable.fCloud'), api: t('pricingTable.fCloud'), ent: t('pricingTable.fCloudOnPrem') }, |
| { feature: t('pricingTable.securityRow'), free: t('pricingTable.fStandard'), pro: t('pricingTable.fStandard'), api: t('pricingTable.fKeyAuth'), ent: t('pricingTable.fSsoMfa') }, |
| ]; |
|
|
| 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">{t('pricingTable.title')}</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>{t('pricingTable.feature')}</div> |
| <div>{t('pricingTable.freeHeader')}</div> |
| <div className="text-wm-green">{t('pricingTable.proHeader')}</div> |
| <div>{t('pricingTable.apiHeader')}</div> |
| <div>{t('pricingTable.entHeader')}</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">{t('tiers.free')}:</span> {row.free}</div> |
| <div><span className="text-wm-green">{t('tiers.pro')}:</span> <span className="text-wm-green">{row.pro}</span></div> |
| <div><span className="text-wm-muted">{t('nav.api')}:</span> {row.api}</div> |
| <div><span className="text-wm-muted">{t('tiers.enterprise')}:</span> {row.ent}</div> |
| </div> |
| </div> |
| ))} |
| </div> |
| <p className="text-center text-sm text-wm-muted mt-8"> |
| {t('pricingTable.noteBelow')} |
| </p> |
| </section> |
| ); |
| }; |
|
|
| |
| const FAQ = () => { |
| const faqs = [ |
| { q: t('faq.q1'), a: t('faq.a1'), open: true }, |
| { q: t('faq.q2'), a: t('faq.a2') }, |
| { q: t('faq.q3'), a: t('faq.a3') }, |
| { q: t('faq.q4'), a: t('faq.a4') }, |
| { q: t('faq.q5'), a: t('faq.a5') }, |
| { q: t('faq.q6'), a: t('faq.a6') }, |
| { q: t('faq.q7'), a: t('faq.a7') }, |
| { q: t('faq.q8'), a: t('faq.a8') }, |
| { q: t('faq.q9'), a: t('faq.a9') }, |
| { q: t('faq.q10'), a: t('faq.a10') }, |
| { q: t('faq.q11'), a: t('faq.a11') }, |
| { q: t('faq.q12'), a: t('faq.a12') }, |
| { q: t('faq.q13'), a: t('faq.a13') }, |
| ]; |
|
|
| return ( |
| <section className="py-24 px-6 max-w-3xl mx-auto"> |
| <h2 className="text-3xl font-display font-bold mb-12 text-center">{t('faq.title')}</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 EnterprisePage = () => ( |
| <div className="min-h-screen selection:bg-wm-green/30 selection:text-wm-green"> |
| <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"> |
| <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }}><Logo /></a> |
| <div className="hidden md:flex items-center gap-8 text-sm font-mono text-wm-muted"> |
| <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }} className="hover:text-wm-text transition-colors">{t('nav.pro')}</a> |
| <a href="#enterprise" onClick={(e) => { e.preventDefault(); document.getElementById('features')?.scrollIntoView({ behavior: 'smooth' }); }} className="hover:text-wm-text transition-colors">{t('nav.enterprise')}</a> |
| <a href="#enterprise-contact" onClick={(e) => { e.preventDefault(); document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); }} className="hover:text-wm-green transition-colors">{t('enterpriseShowcase.talkToSales')}</a> |
| </div> |
| <a href="#enterprise-contact" onClick={(e) => { e.preventDefault(); document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); }} 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"> |
| {t('enterpriseShowcase.talkToSales')} |
| </a> |
| </div> |
| </nav> |
| |
| <main className="pt-24"> |
| {/* Hero */} |
| <section className="py-24 px-6 text-center"> |
| <div className="max-w-4xl mx-auto"> |
| <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"> |
| {t('enterpriseShowcase.enterpriseTier')} |
| </div> |
| <h2 className="text-4xl md:text-6xl font-display font-bold mb-6">{t('enterpriseShowcase.title')}</h2> |
| <p className="text-lg text-wm-muted max-w-2xl mx-auto mb-10"> |
| {t('enterpriseShowcase.subtitle')} |
| </p> |
| <a href="#enterprise-contact" onClick={(e) => { e.preventDefault(); document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); }} className="inline-flex items-center gap-2 bg-wm-green text-wm-bg px-8 py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors"> |
| {t('enterpriseShowcase.talkToSales')} <ArrowRight className="w-4 h-4" aria-hidden="true" /> |
| </a> |
| </div> |
| </section> |
| |
| {/* Features grid */} |
| <section className="py-24 px-6" id="features"> |
| <div className="max-w-7xl mx-auto"> |
| <h2 className="sr-only">Enterprise Features</h2> |
| <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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.security')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.securityDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.aiAgents')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.aiAgentsDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.dataLayers')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.dataLayersDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.connectors')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.connectorsDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.whiteLabel')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.whiteLabelDesc')}</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" /> |
| <h3 className="font-bold mb-2">{t('enterpriseShowcase.financial')}</h3> |
| <p className="text-sm text-wm-muted">{t('enterpriseShowcase.financialDesc')}</p> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| {/* Use cases */} |
| <section className="py-24 px-6 border-t border-wm-border"> |
| <div className="max-w-7xl mx-auto"> |
| <h2 className="text-3xl font-display font-bold mb-12 text-center">{t('enterpriseShowcase.title')}</h2> |
| <div className="data-grid"> |
| <div className="data-cell"> |
| <h3 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.commodity')}</h3> |
| <p className="text-sm">{t('enterpriseShowcase.commodityDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h3 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.government')}</h3> |
| <p className="text-sm">{t('enterpriseShowcase.governmentDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h3 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.risk')}</h3> |
| <p className="text-sm">{t('enterpriseShowcase.riskDesc')}</p> |
| </div> |
| <div className="data-cell"> |
| <h3 className="font-mono text-xs text-wm-muted uppercase tracking-widest mb-2">{t('enterpriseShowcase.soc')}</h3> |
| <p className="text-sm">{t('enterpriseShowcase.socDesc')}</p> |
| </div> |
| </div> |
| </div> |
| </section> |
| |
| {/* Contact form */} |
| <section className="py-24 px-6 border-t border-wm-border" id="contact"> |
| <div className="max-w-xl mx-auto"> |
| <h2 className="font-display text-3xl font-bold mb-2 text-center">{t('enterpriseShowcase.contactFormTitle')}</h2> |
| <p className="text-sm text-wm-muted mb-10 text-center">{t('enterpriseShowcase.contactFormSubtitle')}</p> |
| <form className="space-y-4" onSubmit={async (e) => { |
| e.preventDefault(); |
| const form = e.currentTarget; |
| const btn = form.querySelector('button[type="submit"]') as HTMLButtonElement; |
| const origText = btn.textContent; |
| btn.disabled = true; |
| btn.textContent = t('enterpriseShowcase.contactSending'); |
| const fd = new FormData(form); |
| const honeypot = (form.querySelector('input[name="website"]') as HTMLInputElement)?.value || ''; |
| const turnstileWidget = form.querySelector('.cf-turnstile') as HTMLElement | null; |
| let turnstileToken = turnstileWidget?.dataset.token || ''; |
| if (!turnstileToken && turnstileWidget) { |
| // Turnstile loads lazily (viewport-triggered), so a fast or |
| // autofilled submit can arrive before the widget produced a |
| // token. Kick the loader and give the challenge a bounded |
| // window; if it still hasn't resolved, POST anyway β the |
| // server verdict (and the existing failure UX + widget |
| // reset) stays the authority, same as an expired token. |
| if (await ensureTurnstileScript()) renderTurnstileWidgets(); |
| for (let i = 0; i < 40 && !turnstileWidget.dataset.token; i++) { |
| await new Promise((resolve) => setTimeout(resolve, 250)); |
| } |
| turnstileToken = turnstileWidget.dataset.token || ''; |
| } |
| try { |
| const res = await fetch(`${API_BASE}/leads/v1/submit-contact`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| email: fd.get('email'), |
| name: fd.get('name'), |
| organization: fd.get('organization'), |
| phone: fd.get('phone'), |
| message: fd.get('message'), |
| source: 'enterprise-contact', |
| website: honeypot, |
| turnstileToken, |
| }), |
| }); |
| const errorEl = form.querySelector('[data-form-error]') as HTMLElement | null; |
| if (!res.ok) { |
| const data = await res.json().catch(() => ({})); |
| if (res.status === 422 && errorEl) { |
| errorEl.textContent = data.message || data.error || t('enterpriseShowcase.workEmailRequired'); |
| errorEl.classList.remove('hidden'); |
| btn.textContent = origText; |
| btn.disabled = false; |
| return; |
| } |
| throw new Error(); |
| } |
| if (errorEl) errorEl.classList.add('hidden'); |
| btn.textContent = t('enterpriseShowcase.contactSent'); |
| btn.className = btn.className.replace('bg-wm-green', 'bg-wm-card border border-wm-green text-wm-green'); |
| } catch { |
| btn.textContent = t('enterpriseShowcase.contactFailed'); |
| btn.disabled = false; |
| if (turnstileWidget?.dataset.widgetId && window.turnstile) { |
| window.turnstile.reset(turnstileWidget.dataset.widgetId); |
| delete turnstileWidget.dataset.token; |
| } |
| setTimeout(() => { btn.textContent = origText; }, 4000); |
| } |
| }}> |
| <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="grid grid-cols-2 gap-4"> |
| <input type="text" name="name" placeholder={t('enterpriseShowcase.namePlaceholder')} required className="bg-wm-bg border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono" /> |
| <input type="email" name="email" placeholder={t('enterpriseShowcase.emailPlaceholder')} required className="bg-wm-bg border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono" /> |
| </div> |
| <span data-form-error className="hidden text-red-400 text-xs font-mono block" /> |
| <div className="grid grid-cols-2 gap-4"> |
| <input type="text" name="organization" placeholder={t('enterpriseShowcase.orgPlaceholder')} required className="bg-wm-bg border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono" /> |
| <input type="tel" name="phone" placeholder={t('enterpriseShowcase.phonePlaceholder')} required className="bg-wm-bg border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono" /> |
| </div> |
| <textarea name="message" placeholder={t('enterpriseShowcase.messagePlaceholder')} rows={4} className="w-full bg-wm-bg border border-wm-border rounded-sm px-4 py-3 text-sm focus:outline-none focus:border-wm-green transition-colors font-mono resize-none" /> |
| <div className="cf-turnstile mx-auto" /> |
| <button type="submit" className="w-full bg-wm-green text-wm-bg py-3 rounded-sm font-mono text-sm uppercase tracking-wider font-bold hover:bg-green-400 transition-colors"> |
| {t('enterpriseShowcase.submitContact')} |
| </button> |
| </form> |
| </div> |
| </section> |
| </main> |
| |
| {/* Footer */} |
| <footer className="border-t border-wm-border bg-[#020202] py-8 px-6 text-center"> |
| <div className="flex flex-col md:flex-row items-center justify-between max-w-7xl mx-auto text-xs text-wm-muted font-mono"> |
| <div className="flex items-center gap-3 mb-4 md:mb-0"> |
| <img src="/favico/favicon-32x32.png" alt="" width="28" height="28" loading="lazy" className="rounded-full" /> |
| <div className="flex flex-col"> |
| <span className="font-display font-bold text-sm leading-none tracking-tight text-wm-text">WORLD MONITOR</span> |
| <span className="text-[9px] uppercase tracking-[2px] opacity-60 mt-0.5">by Someone.ceo</span> |
| </div> |
| </div> |
| <div className="flex items-center gap-6"> |
| <a href={DASHBOARD_PATH} className="hover:text-wm-text transition-colors">Dashboard</a> |
| <a href="https://www.worldmonitor.app/blog/" className="hover:text-wm-text transition-colors">Blog</a> |
| <a href="https://www.worldmonitor.app/docs" className="hover:text-wm-text transition-colors">Docs</a> |
| <a href="https://status.worldmonitor.app/" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">Status</a> |
| <a href="https://github.com/koala73/worldmonitor" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">GitHub</a> |
| <a href="https://discord.gg/re63kWKxaz" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">Discord</a> |
| <a href="https://x.com/worldmonitorai" target="_blank" rel="noreferrer" className="hover:text-wm-text transition-colors">X</a> |
| </div> |
| <span className="text-[10px] opacity-40 mt-4 md:mt-0">© {new Date().getFullYear()} WorldMonitor</span> |
| </div> |
| </footer> |
| </div> |
| ); |
|
|
| |
| export default function App() { |
| const [page, setPage] = useState(() => window.location.hash.startsWith('#enterprise') ? 'enterprise' : 'home'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| useEffect(() => { |
| |
| |
| |
| |
| |
| |
| |
| |
| void tryResumeCheckoutFromUrl(); |
| }, []); |
|
|
| useEffect(() => { |
| const onHash = () => { |
| const hash = window.location.hash; |
| const next = hash.startsWith('#enterprise') ? 'enterprise' : 'home'; |
| const wasEnterprise = page === 'enterprise'; |
| setPage(next); |
| if (next === 'enterprise' && !wasEnterprise) window.scrollTo(0, 0); |
| if (hash === '#enterprise-contact') { |
| setTimeout(() => { |
| document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); |
| }, wasEnterprise ? 0 : 100); |
| } |
| }; |
| window.addEventListener('hashchange', onHash); |
| return () => window.removeEventListener('hashchange', onHash); |
| }, [page]); |
|
|
| useEffect(() => { |
| if (page === 'enterprise' && window.location.hash === '#enterprise-contact') { |
| setTimeout(() => { |
| document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); |
| }, 100); |
| } |
| }, []); |
|
|
| if (page === 'enterprise') return <EnterprisePage />; |
|
|
| return ( |
| <ProEntitlementProvider> |
| <div className="min-h-screen selection:bg-wm-green/30 selection:text-wm-green"> |
| <Navbar /> |
| <main> |
| <Hero /> |
| <SourceMarquee /> |
| <Pillars /> |
| <WhyUpgrade /> |
| <TwoPathSplit /> |
| <ProShowcase /> |
| <DeliveryDesk /> |
| <AudiencePersonas /> |
| <SocialProof /> |
| <LivePreview /> |
| <PricingSection refCode={getRefCode()} /> |
| <PricingTable /> |
| <ApiSection /> |
| <EnterpriseShowcase /> |
| <FAQ /> |
| </main> |
| <Footer /> |
| </div> |
| </ProEntitlementProvider> |
| ); |
| } |
|
|