refactor: redesign landing page — break AI-template monotony, add FAQ, fix trust signals

Bold layout overhaul based on design critique:
- Left-aligned hero with two-column layout (text + app preview)
- Asymmetric problem section (headline left, cards right)
- Zigzag How It Works layout (alternating text/visual sides)
- FlowPilot highlight card + 3+2 feature grid
- New FAQ section (5 questions: ChatGPT comparison, data safety, PSA, sessions, AI accuracy)
- Founder quote replaces anonymous testimonial
- Removed ambient glow + grid pattern (design system violations)
- Self-contained color palette (--lp-* vars, no invisible text-secondary)
- 8px border radius aligned to design system
- Alternating section backgrounds replace divider lines
- Form: client-side validation, persistent errors, aria-live
- Skip-to-content link, semantic <main>, FAQ aria-expanded
- Pricing CTAs pass plan context (?plan=free|pro|team)
- Code elements in chat mock properly styled (JetBrains Mono)
- FAQ uses grid-template-rows for smooth height animation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-30 03:30:10 +00:00
parent 841ce8d4ee
commit 930a21b2b6
2 changed files with 1353 additions and 1445 deletions

View File

@@ -5,21 +5,44 @@ import '@/styles/landing.css'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
const FAQ_ITEMS = [
{
q: 'How is this different from just using ChatGPT?',
a: 'FlowPilot is purpose-built for MSP troubleshooting. It understands your stack (AD, Exchange, networking, VPN), captures every diagnostic step as you work, and generates formatted ticket notes ready for your PSA. ChatGPT doesn\u2019t build documentation and can\u2019t push notes to ConnectWise.',
},
{
q: 'Is my data safe?',
a: 'Troubleshooting sessions are encrypted and isolated per team. We never use your data to train AI models. You control what gets documented and exported.',
},
{
q: 'What PSA tools do you integrate with?',
a: 'Launching with ConnectWise PSA \u2014 session documentation exports directly as internal ticket notes. Atera and Syncro integrations are next. During beta, you can copy formatted notes into any PSA.',
},
{
q: 'What counts as a \u201csession\u201d?',
a: 'One session = one troubleshooting conversation. Describe an issue, work through it with FlowPilot, resolve it. Whether that takes 2 minutes or 2 hours, it\u2019s one session. Free plan: 20 sessions/month. Pro and Team: unlimited.',
},
{
q: 'What if FlowPilot gets it wrong?',
a: 'FlowPilot is a copilot, not autopilot. Every suggestion is a recommendation \u2014 you decide what to act on. And because every step is documented, you always have a full audit trail of what was tried and why.',
},
]
export default function LandingPage() { export default function LandingPage() {
const [navScrolled, setNavScrolled] = useState(false) const [navScrolled, setNavScrolled] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [betaEmail, setBetaEmail] = useState('') const [betaEmail, setBetaEmail] = useState('')
const [betaStatus, setBetaStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle') const [betaStatus, setBetaStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
const [betaError, setBetaError] = useState('')
const [openFaq, setOpenFaq] = useState<number | null>(null)
const mobileMenuRef = useRef<HTMLDivElement>(null) const mobileMenuRef = useRef<HTMLDivElement>(null)
// Nav scroll effect
useEffect(() => { useEffect(() => {
const handleScroll = () => setNavScrolled(window.scrollY > 40) const handleScroll = () => setNavScrolled(window.scrollY > 40)
window.addEventListener('scroll', handleScroll) window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll)
}, []) }, [])
// Close mobile menu on click outside
useEffect(() => { useEffect(() => {
function handleClickOutside(e: MouseEvent) { function handleClickOutside(e: MouseEvent) {
if (mobileMenuRef.current && !mobileMenuRef.current.contains(e.target as Node)) { if (mobileMenuRef.current && !mobileMenuRef.current.contains(e.target as Node)) {
@@ -32,10 +55,8 @@ export default function LandingPage() {
} }
}, [mobileMenuOpen]) }, [mobileMenuOpen])
// Close mobile menu on scroll to section
const handleMobileNavClick = () => setMobileMenuOpen(false) const handleMobileNavClick = () => setMobileMenuOpen(false)
// Scroll reveal
useEffect(() => { useEffect(() => {
const els = document.querySelectorAll('.landing-reveal') const els = document.querySelectorAll('.landing-reveal')
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
@@ -52,458 +73,453 @@ export default function LandingPage() {
const handleBetaSubmit = useCallback(async (e: React.FormEvent) => { const handleBetaSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
if (!betaEmail.trim() || betaStatus === 'sending') return const trimmed = betaEmail.trim()
if (!trimmed || betaStatus === 'sending') return
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
setBetaStatus('error')
setBetaError('Enter a valid email address.')
return
}
setBetaStatus('sending') setBetaStatus('sending')
setBetaError('')
try { try {
const resp = await fetch(`${API_URL}/api/v1/beta-signup`, { const resp = await fetch(`${API_URL}/api/v1/beta-signup`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: betaEmail }), body: JSON.stringify({ email: trimmed }),
}) })
if (!resp.ok) throw new Error('Signup failed') if (!resp.ok) throw new Error('Signup failed')
setBetaStatus('sent') setBetaStatus('sent')
setBetaEmail('') setBetaEmail('')
} catch { } catch {
setBetaStatus('error') setBetaStatus('error')
setTimeout(() => setBetaStatus('idle'), 3000) setBetaError('Could not complete signup. Please try again or email hello@resolutionflow.com.')
} }
}, [betaEmail, betaStatus]) }, [betaEmail, betaStatus])
const toggleFaq = (index: number) => {
setOpenFaq(prev => prev === index ? null : index)
}
return ( return (
<> <>
<PageMeta <PageMeta
title="ResolutionFlow From Issue to Resolution, Documented" title="ResolutionFlow \u2014 From Issue to Resolution, Documented"
description="Your AI troubleshooting copilot. Describe the issue, get help fixing it, and get clean ticket notes automatically." description="Your AI troubleshooting copilot. Describe the issue, get help fixing it, and get clean ticket notes \u2014 automatically."
/> />
<div className="landing-page"> <div className="landing-page">
<div className="landing-ambient-glow" /> <a href="#main" className="landing-skip-link">Skip to content</a>
<div className="landing-grid-pattern" />
<div className="landing-page-content"> {/* Navigation */}
{/* Navigation */} <nav className={`landing-nav ${navScrolled ? 'scrolled' : ''}`} ref={mobileMenuRef}>
<nav className={`landing-nav ${navScrolled ? 'scrolled' : ''}`} ref={mobileMenuRef}> <div className="landing-nav-inner">
<div className="landing-nav-inner"> <a href="#" className="landing-nav-logo">
<a href="#" className="landing-nav-logo"> <div className="landing-nav-logo-icon">
<div className="landing-nav-logo-icon"> <svg viewBox="0 0 24 24" fill="none" stroke="#000" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<svg viewBox="0 0 24 24" fill="none" stroke="#000" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"> <circle cx="12" cy="5" r="2" />
<circle cx="12" cy="5" r="2"/> <line x1="12" y1="7" x2="12" y2="11" />
<line x1="12" y1="7" x2="12" y2="11"/> <circle cx="6" cy="15" r="2" />
<circle cx="6" cy="15" r="2"/> <circle cx="18" cy="15" r="2" />
<circle cx="18" cy="15" r="2"/> <line x1="12" y1="11" x2="6" y2="13" />
<line x1="12" y1="11" x2="6" y2="13"/> <line x1="12" y1="11" x2="18" y2="13" />
<line x1="12" y1="11" x2="18" y2="13"/> </svg>
</svg>
</div>
<div className="landing-nav-wordmark">Resolution<span>Flow</span></div>
</a>
<ul className="landing-nav-links">
<li><a href="#features">Features</a></li>
<li><a href="#how-it-works">How It Works</a></li>
<li><a href="#pricing">Pricing</a></li>
</ul>
<div className="landing-nav-cta">
<Link to="/login" className="landing-btn-ghost">Sign In</Link>
<Link to="/register" className="landing-btn-primary">Get Started Free</Link>
</div> </div>
<button <div className="landing-nav-wordmark">Resolution<span>Flow</span></div>
className={`landing-hamburger ${mobileMenuOpen ? 'open' : ''}`} </a>
onClick={() => setMobileMenuOpen(v => !v)} <ul className="landing-nav-links">
aria-label="Toggle menu" <li><a href="#features">Features</a></li>
aria-expanded={mobileMenuOpen} <li><a href="#how-it-works">How It Works</a></li>
> <li><a href="#pricing">Pricing</a></li>
<span /> <li><a href="#faq">FAQ</a></li>
<span /> </ul>
<span /> <div className="landing-nav-cta">
</button> <Link to="/login" className="landing-btn-ghost">Sign In</Link>
<Link to="/register" className="landing-btn-primary">Get Started Free</Link>
</div> </div>
{mobileMenuOpen && ( <button
<div className="landing-mobile-menu"> className={`landing-hamburger ${mobileMenuOpen ? 'open' : ''}`}
<a href="#features" onClick={handleMobileNavClick}>Features</a> onClick={() => setMobileMenuOpen(v => !v)}
<a href="#how-it-works" onClick={handleMobileNavClick}>How It Works</a> aria-label="Toggle menu"
<a href="#pricing" onClick={handleMobileNavClick}>Pricing</a> aria-expanded={mobileMenuOpen}
<div className="landing-mobile-menu-divider" /> >
<Link to="/login" onClick={handleMobileNavClick}>Sign In</Link> <span /><span /><span />
<Link to="/register" className="landing-btn-primary" onClick={handleMobileNavClick} style={{ textAlign: 'center' }}>Get Started Free</Link> </button>
</div> </div>
)} {mobileMenuOpen && (
</nav> <div className="landing-mobile-menu">
<a href="#features" onClick={handleMobileNavClick}>Features</a>
<a href="#how-it-works" onClick={handleMobileNavClick}>How It Works</a>
<a href="#pricing" onClick={handleMobileNavClick}>Pricing</a>
<a href="#faq" onClick={handleMobileNavClick}>FAQ</a>
<div className="landing-mobile-menu-divider" />
<Link to="/login" onClick={handleMobileNavClick}>Sign In</Link>
<Link to="/register" className="landing-btn-primary" onClick={handleMobileNavClick} style={{ textAlign: 'center' }}>Get Started Free</Link>
</div>
)}
</nav>
{/* Hero */} <main id="main" className="landing-main">
{/* Hero — left-aligned, two columns */}
<section className="landing-hero"> <section className="landing-hero">
<div className="landing-hero-badge">Now in Beta Join early access</div> <div className="landing-hero-inner">
<h1> <div className="landing-hero-content">
Resolve tickets faster.<br /> <div className="landing-hero-badge">Now in Beta</div>
<span className="landing-gradient-text">Notes write themselves.</span> <h1>
</h1> Resolve tickets faster.<br />
<p className="landing-hero-sub"> <span className="landing-hero-accent">Notes write themselves.</span>
ResolutionFlow is your AI troubleshooting copilot. Describe the issue, get expert guidance fixing it, and get clean ticket documentation without writing a single note. </h1>
</p> <p className="landing-hero-sub">
<div className="landing-hero-actions"> Your AI troubleshooting copilot for MSPs. Describe the issue, get expert guidance, and get clean ticket documentation &mdash; without writing a single note.
<Link to="/register" className="landing-btn-hero-primary">Start Free</Link> </p>
<a href="#how-it-works" className="landing-btn-hero-secondary">See How It Works</a> <div className="landing-hero-actions">
<Link to="/register" className="landing-btn-hero-primary">Start Free</Link>
<a href="#how-it-works" className="landing-btn-hero-secondary">See How It Works</a>
</div>
<p className="landing-hero-credibility">
Built by a 15-year MSP veteran who got tired of empty ticket notes.
</p>
</div>
<div className="landing-hero-visual">
<div className="landing-preview-window">
<div className="landing-preview-titlebar">
<div className="landing-preview-dots"><span /><span /><span /></div>
<div className="landing-preview-url">
<span className="landing-lock-icon">&#128274;</span>
app.resolutionflow.com/pilot
</div>
</div>
<div className="landing-preview-body">
<div className="landing-mock-session">
<div className="landing-chat-animated" style={{ '--chat-index': 0 } as React.CSSProperties}>
<div className="landing-mock-chat-line user">
<span className="label">You</span>
<span className="text">User can&apos;t access shared drive after password reset</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 1 } as React.CSSProperties}>
<div className="landing-typing-indicator">
<span /><span /><span />
<span className="landing-typing-label">FlowPilot is thinking&hellip;</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 2 } as React.CSSProperties}>
<div className="landing-mock-chat-line ai">
<span className="label">FlowPilot</span>
<span className="text">Likely a cached credential issue. Let&apos;s check:</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 3 } as React.CSSProperties}>
<div className="landing-mock-chat-line ai">
<span className="label">FlowPilot</span>
<span className="text">1. Run <code>klist purge</code> to clear Kerberos tickets</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 4 } as React.CSSProperties}>
<div className="landing-mock-chat-line ai">
<span className="label">FlowPilot</span>
<span className="text">2. Credential Manager &rarr; remove saved share entries</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 5 } as React.CSSProperties}>
<div className="landing-mock-chat-line doc">
<span className="label">Auto-doc</span>
<span className="text">3 steps captured &#10003;</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</section> </section>
{/* Social Proof Bar */} {/* Problem — asymmetric: headline left, cards right */}
<div className="landing-social-proof-bar"> <section id="problem" className="landing-section landing-section-alt landing-reveal">
<p>Built by MSP engineers, for MSP engineers</p>
<div className="landing-proof-stats">
<div className="landing-proof-stat">
<div className="number">15+</div>
<div className="label">Years MSP Experience</div>
</div>
<div className="landing-proof-stat">
<div className="number">70%</div>
<div className="label">Less Time on Documentation</div>
</div>
<div className="landing-proof-stat">
<div className="number">100%</div>
<div className="label">Auto-Generated Documentation</div>
</div>
</div>
</div>
{/* App Preview */}
<div className="landing-app-preview">
<div className="landing-preview-window">
<div className="landing-preview-titlebar">
<div className="landing-preview-tab">
<div className="landing-tab-icon" />
ResolutionFlow
<span className="landing-tab-close">&times;</span>
</div>
<div className="landing-preview-url-bar">
<div className="landing-preview-url">
<span className="landing-lock-icon">&#128274;</span>
app.resolutionflow.com/pilot
</div>
</div>
<div className="landing-preview-window-controls">
<div className="landing-win-btn">
<svg viewBox="0 0 12 12"><line x1="2" y1="6" x2="10" y2="6"/></svg>
</div>
<div className="landing-win-btn">
<svg viewBox="0 0 12 12"><rect x="2" y="2" width="8" height="8" rx="0.5"/></svg>
</div>
<div className="landing-win-btn close">
<svg viewBox="0 0 12 12"><line x1="2" y1="2" x2="10" y2="10"/><line x1="10" y1="2" x2="2" y2="10"/></svg>
</div>
</div>
</div>
<div className="landing-preview-body">
<div className="landing-preview-sidebar">
<div className="landing-preview-sidebar-item active">
<div className="dot" style={{ background: '#60a5fa' }} />
FlowPilot
</div>
<div className="landing-preview-sidebar-item">
<div className="dot" style={{ background: '#34d399' }} />
Session History
</div>
<div className="landing-preview-sidebar-item">
<div className="dot" style={{ background: '#a78bfa' }} />
Guided Flows
</div>
<div className="landing-preview-sidebar-item">
<div className="dot" style={{ background: '#2dd4bf' }} />
Scripts
</div>
<div className="landing-preview-sidebar-item">
<div className="dot" style={{ background: '#38bdf8' }} />
Analytics
</div>
</div>
<div className="landing-preview-canvas">
<div className="landing-mock-session">
<div className="landing-chat-animated" style={{ '--chat-index': 0 } as React.CSSProperties}>
<div className="landing-mock-chat-line">
<span className="label">You:</span>
<span className="text">User can&apos;t access shared drive after password reset</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 1 } as React.CSSProperties}>
<div className="landing-typing-indicator">
<span /><span /><span />
<span style={{ fontSize: '0.55rem', color: '#60a5fa', marginLeft: 4, fontWeight: 500, whiteSpace: 'nowrap' }}>FlowPilot is thinking...</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 2 } as React.CSSProperties}>
<div className="landing-mock-chat-line">
<span className="label" style={{ color: '#60a5fa' }}>FlowPilot:</span>
<span className="text">This is likely a cached credential issue. Let&apos;s check a few things:</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 3 } as React.CSSProperties}>
<div className="landing-mock-chat-line">
<span className="label" style={{ color: '#60a5fa' }}>FlowPilot:</span>
<span className="text">1. Run <code>klist purge</code> to clear Kerberos tickets</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 4 } as React.CSSProperties}>
<div className="landing-mock-chat-line">
<span className="label" style={{ color: '#60a5fa' }}>FlowPilot:</span>
<span className="text">2. Open Credential Manager &rarr; remove saved entries for the share</span>
</div>
</div>
<div className="landing-chat-animated" style={{ '--chat-index': 5 } as React.CSSProperties}>
<div className="landing-mock-chat-line doc">
<span className="label">Auto-doc:</span>
<span className="text">3 steps captured &#10003;</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="landing-section-divider" />
{/* Problem Section */}
<section id="problem" className="landing-reveal">
<div className="landing-section-inner"> <div className="landing-section-inner">
<div className="landing-section-label">The Problem</div> <div className="landing-problem-layout">
<h2 className="landing-section-title">Documentation is broken.<br />Everyone knows it.</h2> <div className="landing-problem-headline">
<div className="landing-section-desc"> <div className="landing-section-label">The Problem</div>
Engineers don&apos;t want to write it. Managers hate chasing it. Clients never see it. The same issues get solved from scratch every time. <h2>Documentation is broken.<br />Everyone knows it.</h2>
</div> <p>Engineers don&apos;t want to write it. Managers hate chasing it. Clients never see it. The same issues get solved from scratch &mdash; every time.</p>
<div className="landing-problem-grid"> </div>
<ProblemCard icon="&#9201;" color="red" title="1525 min lost per ticket" description="Engineers spend more time documenting what they did than actually doing it. After a complex issue, writing notes is the last thing anyone wants to do." /> <div className="landing-problem-grid">
<ProblemCard icon="&#128203;" color="amber" title="Vague, useless notes" description={`"Fixed Outlook" tells you nothing. Documentation written under pressure tends toward generalities that help nobody the second time around.`} /> <ProblemCard icon="&#9201;" color="red" title="15&ndash;25 min lost per ticket" description="More time documenting than resolving. After a complex issue, writing notes is the last thing anyone does." />
<ProblemCard icon="&#128260;" color="slate" title="Knowledge walks out the door" description="When a senior engineer leaves, years of tribal knowledge disappear overnight. New hires spend months building up what was never captured." /> <ProblemCard icon="&#128203;" color="amber" title="Vague, useless notes" description={`"Fixed Outlook" tells no one anything. Notes under pressure are always too vague to help next time.`} />
<ProblemCard icon="&#129504;" color="violet" title="Context switching kills speed" description="Jumping between the issue, documentation tools, PSA tickets, and knowledge bases fragments focus and slows resolution." /> <ProblemCard icon="&#128260;" color="slate" title="Knowledge walks out the door" description="When a senior engineer leaves, years of tribal knowledge vanish overnight." />
<ProblemCard icon="&#129504;" color="violet" title="Context switching kills speed" description="Jumping between the issue, docs, PSA tickets, and knowledge bases fragments focus." />
</div>
</div> </div>
</div> </div>
</section> </section>
<div className="landing-section-divider" /> {/* Equation */}
{/* Brand Equation */}
<div className="landing-equation-section landing-reveal"> <div className="landing-equation-section landing-reveal">
<div className="landing-section-label">The Answer</div> <div className="landing-equation-inner">
<div className="landing-brand-equation"> <div className="landing-section-label">The Answer</div>
<span className="landing-eq-item">Resolution</span> <div className="landing-brand-equation">
<span className="landing-eq-operator">+</span> <span className="landing-eq-item">Resolution</span>
<span className="landing-eq-item">Documentation</span> <span className="landing-eq-operator">+</span>
<span className="landing-eq-operator">&minus;</span> <span className="landing-eq-item">Documentation</span>
<span className="landing-eq-item">Time</span> <span className="landing-eq-operator">&minus;</span>
<span className="landing-eq-operator">=</span> <span className="landing-eq-item">Time</span>
<span className="landing-eq-result">ResolutionFlow</span> <span className="landing-eq-operator">=</span>
<span className="landing-eq-result">ResolutionFlow</span>
</div>
<p className="landing-equation-desc">
What if documentation was a <em>byproduct</em> of solving the issue &mdash; not a separate task?
</p>
</div> </div>
<p className="landing-equation-desc">
What if documentation was a <em>byproduct</em> of solving the issue not a separate task? What if every ticket your team touched had clean, detailed notes without anyone writing them?
</p>
</div> </div>
<div className="landing-section-divider" /> {/* How It Works — zigzag */}
<section id="how-it-works" className="landing-section landing-reveal">
{/* How It Works */}
<section id="how-it-works" className="landing-reveal">
<div className="landing-section-inner"> <div className="landing-section-inner">
<div className="landing-section-label">How It Works</div> <div className="landing-section-label">How It Works</div>
<h2 className="landing-section-title">Three steps. Zero note-writing.</h2> <h2 className="landing-section-title">Three steps. Zero note-writing.</h2>
<div className="landing-section-desc"> </div>
Just describe the issue. FlowPilot handles the rest. <div className="landing-zigzag">
</div> <div className="landing-zigzag-step">
<div className="landing-steps-container"> <div className="landing-zigzag-text">
<div className="landing-step-card"> <div className="landing-zigzag-number">01</div>
<h3>Describe the Issue</h3> <h3>Describe the Issue</h3>
<p>Type what&apos;s happening, paste an error message, or drop a screenshot. FlowPilot understands MSP environments AD, Exchange, networking, VPN, you name it.</p> <p>Type what&apos;s happening, paste an error, or drop a screenshot. FlowPilot understands MSP environments &mdash; AD, Exchange, networking, VPN, you name it.</p>
<div className="landing-step-visual"> </div>
<div className="landing-mock-editor"> <div className="landing-zigzag-visual">
<div className="landing-mock-node step" style={{ fontSize: '0.7rem', padding: '8px 12px' }}>&#128172; &ldquo;User can&apos;t access shared drive after password reset, getting Access Denied in Event Viewer&rdquo;</div> <div className="landing-mock-input">
</div> <span className="landing-mock-input-icon">&#128172;</span>
<span>User can&apos;t access shared drive after password reset, getting Access Denied in Event Viewer</span>
</div> </div>
</div> </div>
</div>
<div className="landing-step-card"> <div className="landing-zigzag-step reverse">
<div className="landing-zigzag-text">
<div className="landing-zigzag-number">02</div>
<h3>Troubleshoot Together</h3> <h3>Troubleshoot Together</h3>
<p>FlowPilot acts like a senior engineer on the call with you. It suggests next steps, provides commands to run, and captures every action documentation builds itself as you work.</p> <p>FlowPilot acts like a senior engineer on the call. It suggests next steps, provides commands, and captures every action &mdash; documentation builds itself as you work.</p>
<div className="landing-step-visual"> </div>
<div className="landing-mock-session"> <div className="landing-zigzag-visual">
<div className="landing-mock-chat-line"> <div className="landing-mock-session compact">
<span className="label">FlowPilot:</span> <div className="landing-mock-chat-line ai">
<span className="text">Is the user on VPN?</span> <span className="label">FlowPilot</span>
</div> <span className="text">Is the user on VPN?</span>
<div className="landing-mock-chat-line"> </div>
<span className="label" style={{ color: '#848b9b' }}>Engineer:</span> <div className="landing-mock-chat-line user">
<span className="text">Yes, Cisco AnyConnect</span> <span className="label">You</span>
</div> <span className="text">Yes, Cisco AnyConnect</span>
<div className="landing-mock-chat-line"> </div>
<span className="label">FlowPilot:</span> <div className="landing-mock-chat-line ai">
<span className="text">Check split tunnel config &rarr;</span> <span className="label">FlowPilot</span>
</div> <span className="text">Check split tunnel config &rarr;</span>
<div className="landing-mock-chat-line doc"> </div>
<span className="label">Auto-doc:</span> <div className="landing-mock-chat-line doc">
<span className="text">Step captured &#10003;</span> <span className="label">Auto-doc</span>
</div> <span className="text">Step captured &#10003;</span>
</div> </div>
</div> </div>
</div> </div>
</div>
<div className="landing-step-card"> <div className="landing-zigzag-step">
<div className="landing-zigzag-text">
<div className="landing-zigzag-number">03</div>
<h3>Resolve &amp; Document</h3> <h3>Resolve &amp; Document</h3>
<p>Hit resolve and get clean, timestamped ticket notes ready to paste into ConnectWise, Atera, or Syncro. Every step you took, every command you ran, documented automatically.</p> <p>Hit resolve and get clean, timestamped ticket notes &mdash; ready to paste into ConnectWise, Atera, or Syncro. Every step documented automatically.</p>
<div className="landing-step-visual"> </div>
<div className="landing-mock-ticket"> <div className="landing-zigzag-visual">
<div className="landing-mock-ticket-header">ConnectWise Ticket #48291</div> <div className="landing-mock-ticket">
<div className="landing-mock-ticket-line"><span className="time">10:04</span><span className="check">&#10003;</span><span>Verified VPN connection active</span></div> <div className="landing-mock-ticket-header">ConnectWise Ticket #48291</div>
<div className="landing-mock-ticket-line"><span className="time">10:06</span><span className="check">&#10003;</span><span>Split tunnel misconfigured fixed</span></div> <div className="landing-mock-ticket-line"><span className="time">10:04</span><span className="check">&#10003;</span><span>Verified VPN connection active</span></div>
<div className="landing-mock-ticket-line"><span className="time">10:08</span><span className="check">&#10003;</span><span>Confirmed Outlook sync restored</span></div> <div className="landing-mock-ticket-line"><span className="time">10:06</span><span className="check">&#10003;</span><span>Split tunnel misconfigured &mdash; fixed</span></div>
<div className="landing-mock-ticket-line"><span className="time">10:09</span><span className="check">&#10003;</span><span>Resolution: VPN split tunnel updated</span></div> <div className="landing-mock-ticket-line"><span className="time">10:08</span><span className="check">&#10003;</span><span>Confirmed Outlook sync restored</span></div>
</div> <div className="landing-mock-ticket-line"><span className="time">10:09</span><span className="check">&#10003;</span><span>Resolution: VPN split tunnel updated</span></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<div className="landing-section-divider" />
{/* Features */} {/* Features */}
<section id="features" className="landing-reveal"> <section id="features" className="landing-section landing-section-alt landing-reveal">
<div className="landing-section-inner"> <div className="landing-section-inner">
<div className="landing-section-label">Features</div> <div className="landing-section-label">Features</div>
<h2 className="landing-section-title">Troubleshoot faster.<br />Document everything. Automatically.</h2> <h2 className="landing-section-title">Everything you need to troubleshoot faster.</h2>
<div className="landing-feature-highlight">
<div className="landing-feature-highlight-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3" /><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" /></svg>
</div>
<div className="landing-feature-highlight-content">
<h3>FlowPilot &mdash; Your AI Copilot</h3>
<p>Like having a senior engineer on every call. Describe the issue, get expert troubleshooting guidance, and documentation writes itself &mdash; as a byproduct of solving the problem.</p>
</div>
</div>
<div className="landing-features-grid"> <div className="landing-features-grid">
<FeatureCard <FeatureCard
highlight icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" /><line x1="9" y1="3" x2="9" y2="21" /></svg>}
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>} title="Guided Flows"
title="FlowPilot — Your AI Copilot" description="Build step-by-step troubleshooting paths your team can follow. Great for onboarding and consistency."
description="Like having a senior engineer on every call. Describe the issue, get expert troubleshooting guidance, and documentation writes itself — as a byproduct of solving the problem."
/> />
<FeatureCard <FeatureCard
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>} icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /></svg>}
title="Guided Troubleshooting Flows" title="Zero Empty Tickets"
description="Build step-by-step troubleshooting paths your team can follow. Great for standard procedures, onboarding new engineers, or ensuring consistency." description="Every session generates timestamped notes, formatted for your PSA. No more empty ticket closures."
/> />
<FeatureCard <FeatureCard
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>} icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>}
title="Zero Empty Ticket Notes" title="Team Knowledge"
description="Every troubleshooting session generates timestamped, detailed notes — formatted for your PSA. Your team will never close a ticket with empty notes again." description="Solutions are saved and surfaced when the next engineer hits a similar issue."
/> />
<FeatureCard <FeatureCard
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>} icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>}
title="Team Knowledge That Grows" title="Session Analytics"
description="Every resolved session makes your team smarter. Solutions are saved and surfaced automatically when the next engineer hits a similar issue." description="Track resolution times, identify recurring issues, and measure team performance."
/> />
<FeatureCard <FeatureCard
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>} icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" /></svg>}
title="Session History & Analytics"
description="See every troubleshooting session your team has run. Track resolution times, identify common issues, and measure team performance."
/>
<FeatureCard
icon={<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>}
title="PSA Integration" title="PSA Integration"
description="Connect directly to ConnectWise, Atera, and Syncro. Export session docs straight to tickets — no copy-paste needed." description="Connect to ConnectWise, Atera, and Syncro. Push session docs straight to tickets."
/> />
</div> </div>
</div> </div>
</section> </section>
<div className="landing-section-divider" />
{/* Pricing */} {/* Pricing */}
<section id="pricing" className="landing-reveal"> <section id="pricing" className="landing-section landing-reveal">
<div className="landing-section-inner"> <div className="landing-section-inner">
<div className="landing-section-label">Pricing</div> <div className="landing-section-label">Pricing</div>
<h2 className="landing-section-title">Simple pricing. No surprises.</h2> <h2 className="landing-section-title">Simple pricing. No surprises.</h2>
<div className="landing-section-desc">Start free. Upgrade when your team is ready.</div> <p className="landing-section-desc">Start free. Upgrade when your team is ready.</p>
<div className="landing-pricing-grid"> <div className="landing-pricing-grid">
<PricingCard <PricingCard
name="Free" name="Free"
target="For individual techs evaluating" target="Individual techs evaluating"
amount="$0" amount="$0"
note="Free forever" note="Free forever"
features={['3 decision trees', '20 sessions per month', 'Auto-documentation export', 'Session history (30 days)', 'Community support']} features={['3 guided flows', '20 sessions per month', 'Auto-documentation export', '30-day session history']}
btnLabel="Get Started" btnLabel="Get Started"
btnStyle="outline" btnStyle="outline"
plan="free"
/> />
<PricingCard <PricingCard
featured featured
name="Pro" name="Pro"
target="For small MSPs with 15 techs" target="Small MSPs &middot; 1&ndash;5 techs"
amount="$15" amount="$15"
period="/user/mo" period="/user/mo"
note="Billed monthly or annually" note="Billed monthly or annually"
features={['Unlimited decision trees', 'Unlimited sessions', 'FlowPilot AI copilot', 'Auto-documentation export', 'Full session history', 'Flow templates library', 'Priority support']} features={['Unlimited flows & sessions', 'FlowPilot AI copilot', 'Full session history', 'Flow templates library', 'Priority support']}
btnLabel="Start Free Trial" btnLabel="Start Free Trial"
btnStyle="filled" btnStyle="filled"
plan="pro"
/> />
<PricingCard <PricingCard
name="Team" name="Team"
target="For growing MSPs with 525 techs" target="Growing MSPs &middot; 5&ndash;25 techs"
amount="$25" amount="$25"
period="/user/mo" period="/user/mo"
note="Billed monthly or annually" note="Billed monthly or annually"
features={['Everything in Pro', 'PSA integration (ConnectWise, Atera, Syncro)', 'Team analytics dashboard', 'Session sharing & collaboration', 'Client context system', 'Role-based permissions', 'Dedicated support']} features={['Everything in Pro', 'PSA integration', 'Team analytics dashboard', 'Session sharing', 'Role-based permissions', 'Dedicated support']}
btnLabel="Start Free Trial" btnLabel="Start Free Trial"
btnStyle="outline" btnStyle="outline"
plan="team"
/> />
</div> </div>
<p className="landing-pricing-session-note">One session = one troubleshooting conversation, regardless of length.</p>
<p className="landing-pricing-enterprise"> <p className="landing-pricing-enterprise">
Need Enterprise (25+ techs, SSO, custom branding)?{' '} Enterprise (25+ techs, SSO, custom branding)?{' '}
<a href="mailto:hello@resolutionflow.com">Contact us</a> <a href="mailto:hello@resolutionflow.com">Let&apos;s talk</a>
</p> </p>
</div> </div>
</section> </section>
<div className="landing-section-divider" /> {/* FAQ */}
<section id="faq" className="landing-section landing-section-alt landing-reveal">
{/* Testimonial */} <div className="landing-section-inner">
<div className="landing-testimonial-section landing-reveal"> <div className="landing-section-label">FAQ</div>
<div className="landing-testimonial-quote"> <h2 className="landing-section-title">Common questions</h2>
We used to spend more time writing ticket notes than solving the actual issue. Now it just&hellip; happens. The documentation writes itself while we work. <div className="landing-faq-list">
{FAQ_ITEMS.map((item, i) => (
<div key={i} className={`landing-faq-item ${openFaq === i ? 'open' : ''}`}>
<button
className="landing-faq-trigger"
onClick={() => toggleFaq(i)}
aria-expanded={openFaq === i}
>
<span>{item.q}</span>
<span className="landing-faq-icon" aria-hidden="true">{openFaq === i ? '\u2212' : '+'}</span>
</button>
<div className="landing-faq-answer" role="region">
<p>{item.a}</p>
</div>
</div>
))}
</div>
</div> </div>
<div className="landing-testimonial-author"> </section>
<strong>Beta Tester</strong> MSP Engineer, Southeast US
{/* Founder — replaces anonymous testimonial */}
<div className="landing-founder-section landing-reveal">
<div className="landing-founder-inner">
<div className="landing-section-label">Why We Built This</div>
<blockquote>
After 15 years in the MSP trenches, I got tired of the same cycle: solve the issue in 10 minutes, spend 20 minutes writing notes about it. Or worse &mdash; close the ticket with &ldquo;Fixed issue&rdquo; because there&apos;s no time. ResolutionFlow is the tool I wanted on every call.
</blockquote>
<div className="landing-founder-name">&mdash; Michael, Founder</div>
</div> </div>
</div> </div>
<div className="landing-section-divider" />
{/* CTA */} {/* CTA */}
<section className="landing-cta-section landing-reveal"> <section className="landing-cta-section landing-reveal">
<h2>Ready to never write ticket notes again?</h2> <div className="landing-cta-inner">
<p>Join the beta. Troubleshoot your next ticket with FlowPilot and see the documentation write itself.</p> <h2>Ready to stop writing ticket notes?</h2>
<form className="landing-cta-email-form" onSubmit={handleBetaSubmit}> <p>Join the beta. Troubleshoot your next ticket with FlowPilot.</p>
<input <form className="landing-cta-email-form" onSubmit={handleBetaSubmit} noValidate>
type="email" <div className="landing-cta-input-wrap">
className="landing-cta-email-input" <input
placeholder="you@yourmsp.com" type="email"
value={betaEmail} className="landing-cta-email-input"
onChange={e => setBetaEmail(e.target.value)} placeholder="you@yourmsp.com"
required value={betaEmail}
/> onChange={e => {
<button type="submit" className="landing-btn-hero-primary" style={{ whiteSpace: 'nowrap' }} disabled={betaStatus === 'sending'}> setBetaEmail(e.target.value)
{betaStatus === 'sending' ? 'Joining...' : betaStatus === 'sent' ? 'Joined!' : 'Join Beta'} if (betaStatus === 'error') { setBetaStatus('idle'); setBetaError('') }
</button> }}
</form> required
{betaStatus === 'sent' && ( aria-describedby="beta-status"
<p className="landing-cta-success">Thanks! We&apos;ll be in touch with beta access details.</p> />
)} <button type="submit" className="landing-btn-hero-primary" disabled={betaStatus === 'sending'}>
{betaStatus === 'error' && ( {betaStatus === 'sending' ? 'Joining\u2026' : betaStatus === 'sent' ? 'Joined!' : 'Join Beta'}
<p className="landing-cta-error">Something went wrong. Please try again.</p> </button>
)} </div>
<p className="landing-cta-fine-print">Free to start. No credit card required.</p> <div id="beta-status" aria-live="polite" className="landing-cta-status">
{betaStatus === 'sent' && (
<p className="landing-cta-success">You&apos;re in. We&apos;ll send beta access details soon.</p>
)}
{betaStatus === 'error' && betaError && (
<p className="landing-cta-error">{betaError}</p>
)}
</div>
</form>
<p className="landing-cta-fine-print">Free to start. No credit card required.</p>
</div>
</section> </section>
{/* Footer */} {/* Footer */}
<footer className="landing-footer"> <footer className="landing-footer">
<div className="landing-footer-inner"> <div className="landing-footer-inner">
<div className="landing-footer-left"> <div className="landing-footer-left">
<div className="landing-nav-logo-icon" style={{ width: 28, height: 28, borderRadius: 8 }}> <div className="landing-nav-logo-icon" style={{ width: 24, height: 24, borderRadius: 6 }}>
<svg viewBox="0 0 24 24" fill="none" stroke="#000" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ width: 16, height: 16 }}> <svg viewBox="0 0 24 24" fill="none" stroke="#000" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ width: 14, height: 14 }}>
<circle cx="12" cy="5" r="2"/> <circle cx="12" cy="5" r="2" />
<line x1="12" y1="7" x2="12" y2="11"/> <line x1="12" y1="7" x2="12" y2="11" />
<circle cx="6" cy="15" r="2"/> <circle cx="6" cy="15" r="2" />
<circle cx="18" cy="15" r="2"/> <circle cx="18" cy="15" r="2" />
<line x1="12" y1="11" x2="6" y2="13"/> <line x1="12" y1="11" x2="6" y2="13" />
<line x1="12" y1="11" x2="18" y2="13"/> <line x1="12" y1="11" x2="18" y2="13" />
</svg> </svg>
</div> </div>
<span className="landing-footer-copy">&copy; 2026 ResolutionFlow. All rights reserved.</span> <span className="landing-footer-copy">&copy; 2026 ResolutionFlow</span>
</div> </div>
<ul className="landing-footer-links"> <ul className="landing-footer-links">
<li><Link to="/privacy">Privacy</Link></li> <li><Link to="/privacy">Privacy</Link></li>
@@ -512,7 +528,7 @@ export default function LandingPage() {
</ul> </ul>
</div> </div>
</footer> </footer>
</div> </main>
</div> </div>
</> </>
) )
@@ -533,11 +549,11 @@ function ProblemCard({ icon, color, title, description }: {
) )
} }
function FeatureCard({ icon, title, description, highlight }: { function FeatureCard({ icon, title, description }: {
icon: React.ReactNode; title: string; description: string; highlight?: boolean icon: React.ReactNode; title: string; description: string
}) { }) {
return ( return (
<div className={`landing-feature-card ${highlight ? 'highlight' : ''}`}> <div className="landing-feature-card">
<div className="landing-feature-icon">{icon}</div> <div className="landing-feature-icon">{icon}</div>
<h3>{title}</h3> <h3>{title}</h3>
<p>{description}</p> <p>{description}</p>
@@ -545,12 +561,13 @@ function FeatureCard({ icon, title, description, highlight }: {
) )
} }
function PricingCard({ name, target, amount, period, note, features, btnLabel, btnStyle, featured }: { function PricingCard({ name, target, amount, period, note, features, btnLabel, btnStyle, featured, plan }: {
name: string; target: string; amount: string; period?: string; note: string name: string; target: string; amount: string; period?: string; note: string
features: string[]; btnLabel: string; btnStyle: 'outline' | 'filled'; featured?: boolean features: string[]; btnLabel: string; btnStyle: 'outline' | 'filled'; featured?: boolean; plan: string
}) { }) {
return ( return (
<div className={`landing-pricing-card ${featured ? 'featured' : ''}`}> <div className={`landing-pricing-card ${featured ? 'featured' : ''}`}>
{featured && <div className="landing-pricing-badge">Most Popular</div>}
<div className="landing-pricing-plan-name">{name}</div> <div className="landing-pricing-plan-name">{name}</div>
<div className="landing-pricing-target">{target}</div> <div className="landing-pricing-target">{target}</div>
<div className="landing-pricing-price"> <div className="landing-pricing-price">
@@ -561,7 +578,7 @@ function PricingCard({ name, target, amount, period, note, features, btnLabel, b
<ul className="landing-pricing-features"> <ul className="landing-pricing-features">
{features.map(f => <li key={f}>{f}</li>)} {features.map(f => <li key={f}>{f}</li>)}
</ul> </ul>
<Link to="/register" className={`landing-pricing-btn ${btnStyle}`}>{btnLabel}</Link> <Link to={`/register?plan=${plan}`} className={`landing-pricing-btn ${btnStyle}`}>{btnLabel}</Link>
</div> </div>
) )
} }

File diff suppressed because it is too large Load Diff