fix(landing): design audit fixes — hamburger menu, dead links, branding, spacing (#117)

* fix(landing): design audit fixes — hamburger menu, dead links, branding, spacing

- Add mobile hamburger menu with animated open/close and click-outside dismiss
- Create Privacy and Terms pages (footer links were dead # anchors)
- Change "Decision Tree Platform" to "AI-Powered Troubleshooting for MSPs" on login, register, and HTML title
- Fix register page icon color (was red/coral via CSS invert, now uses BrandLogo directly)
- Replace "0 Ticket Notes Written by Hand" stat with "100% Auto-Generated Documentation"
- Increase nav link touch targets to 44px minimum
- Fix heading hierarchy: section titles are now <h2>, standardize H3 to 1.25rem
- Tighten section spacing (6rem → 4rem padding, remove extra 5rem spacer)
- Add color-scheme: dark to HTML element
- Replace all transition: all with specific properties (10 occurrences)
- Fix loading ellipsis (... → …)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mobile): responsive modals + landing preview overflow

- PrepareSessionModal: bottom-sheet on mobile, centered on desktop
- IntakeFormModal: bottom-sheet on mobile, responsive padding
- ShareTreeModal: bottom-sheet on mobile, full-width on small screens
- Landing preview: hide URL bar and window controls on mobile (<900px)
  to prevent 189px horizontal overflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(mobile): collapse search bar to icon on mobile

On mobile (<640px), the full search bar with placeholder text and ⌘K
badge was taking too much space in the topbar. Now shows just a
magnifying glass icon that opens the same command palette on tap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: chihlasm <michael@resolutionflow.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #117.
This commit is contained in:
chihlasm
2026-03-21 09:55:37 -04:00
committed by GitHub
parent 9298f5a272
commit 0c51198556
13 changed files with 299 additions and 45 deletions

View File

@@ -1,10 +1,10 @@
<!doctype html>
<html lang="en">
<html lang="en" style="color-scheme: dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icons/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ResolutionFlow - Decision Tree Platform</title>
<title>ResolutionFlow — AI-Powered Troubleshooting for MSPs</title>
<meta name="description" content="Transform troubleshooting into guided workflows with automatic documentation" />
<!-- Google Fonts -->

View File

@@ -5,7 +5,7 @@ export function PageLoader() {
<div className="flex h-full items-center justify-center">
<div className="flex flex-col items-center gap-4">
<Spinner size="lg" />
<p className="text-sm text-muted-foreground">Loading...</p>
<p className="text-sm text-muted-foreground">Loading&hellip;</p>
</div>
</div>
)

View File

@@ -78,10 +78,10 @@ export function TopBar() {
{/* Spacer - push search to center */}
<div className="flex-1" />
{/* Search trigger */}
{/* Search trigger — icon on mobile, full bar on desktop */}
<button
onClick={() => setCommandPaletteOpen(true)}
className="relative w-full text-left"
className="hidden sm:relative sm:block w-full text-left"
style={{ maxWidth: '480px' }}
>
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
@@ -92,6 +92,13 @@ export function TopBar() {
{navigator.platform?.toLowerCase().includes('mac') ? '⌘K' : 'Ctrl+K'}
</span>
</button>
<button
onClick={() => setCommandPaletteOpen(true)}
className="sm:hidden rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
title="Search"
>
<Search size={18} />
</button>
{/* Spacer - push actions to right */}
<div className="flex-1" />

View File

@@ -112,7 +112,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
@@ -120,7 +120,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
/>
{/* Modal */}
<div className="relative w-full max-w-lg bg-card border border-border rounded-2xl shadow-lg">
<div className="relative w-full max-w-full rounded-t-2xl bg-card border border-border shadow-lg sm:max-w-lg sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<h2 className="text-lg font-semibold text-foreground">Share Tree</h2>

View File

@@ -212,8 +212,8 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs">
<div className="mx-4 w-full max-w-lg rounded-2xl border border-border bg-background shadow-xl">
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4 bg-black/60 backdrop-blur-xs">
<div className="w-full max-w-full rounded-t-2xl border border-border bg-background shadow-xl sm:max-w-lg sm:rounded-2xl">
{/* Header */}
<div className="border-b border-border px-6 py-4">
<h2 className="text-lg font-semibold text-foreground">Project Information</h2>
@@ -224,7 +224,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
{/* Form */}
<form onSubmit={handleSubmit}>
<div className="max-h-[60vh] space-y-4 overflow-y-auto px-6 py-4">
<div className="max-h-[70vh] space-y-4 overflow-y-auto px-4 py-4 sm:max-h-[60vh] sm:px-6">
{Array.from(groups.entries()).map(([groupName, groupFields]) => (
<div key={groupName}>
{groupName && (

View File

@@ -88,9 +88,9 @@ export function PrepareSessionModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
<div className="absolute inset-0 bg-background/60 backdrop-blur-xs" onClick={onClose} />
<div className="relative w-full max-w-lg rounded-2xl border border-border bg-card shadow-2xl">
<div className="relative w-full max-w-full rounded-t-2xl border border-border bg-card shadow-2xl sm:max-w-lg sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-5 py-4">
<div className="flex items-center gap-2">
@@ -106,7 +106,7 @@ export function PrepareSessionModal({
</div>
{/* Body */}
<div className="max-h-[60vh] overflow-y-auto p-5 space-y-5">
<div className="max-h-[70vh] overflow-y-auto p-4 space-y-5 sm:max-h-[60vh] sm:p-5">
{/* Flow name */}
<div className="rounded-lg bg-accent px-3 py-2">
<p className="text-xs text-muted-foreground">Flow</p>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'
import { useState, useEffect, useCallback, useRef } from 'react'
import { Link } from 'react-router-dom'
import { PageMeta } from '@/components/common/PageMeta'
import '@/styles/landing.css'
@@ -7,8 +7,10 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
export default function LandingPage() {
const [navScrolled, setNavScrolled] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [betaEmail, setBetaEmail] = useState('')
const [betaStatus, setBetaStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
const mobileMenuRef = useRef<HTMLDivElement>(null)
// Nav scroll effect
useEffect(() => {
@@ -17,6 +19,22 @@ export default function LandingPage() {
return () => window.removeEventListener('scroll', handleScroll)
}, [])
// Close mobile menu on click outside
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (mobileMenuRef.current && !mobileMenuRef.current.contains(e.target as Node)) {
setMobileMenuOpen(false)
}
}
if (mobileMenuOpen) {
document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}
}, [mobileMenuOpen])
// Close mobile menu on scroll to section
const handleMobileNavClick = () => setMobileMenuOpen(false)
// Scroll reveal
useEffect(() => {
const els = document.querySelectorAll('.landing-reveal')
@@ -64,7 +82,7 @@ export default function LandingPage() {
<div className="landing-page-content">
{/* Navigation */}
<nav className={`landing-nav ${navScrolled ? 'scrolled' : ''}`}>
<nav className={`landing-nav ${navScrolled ? 'scrolled' : ''}`} ref={mobileMenuRef}>
<div className="landing-nav-inner">
<a href="#" className="landing-nav-logo">
<div className="landing-nav-logo-icon">
@@ -88,7 +106,27 @@ export default function LandingPage() {
<Link to="/login" className="landing-btn-ghost">Sign In</Link>
<Link to="/register" className="landing-btn-primary">Get Started Free</Link>
</div>
<button
className={`landing-hamburger ${mobileMenuOpen ? 'open' : ''}`}
onClick={() => setMobileMenuOpen(v => !v)}
aria-label="Toggle menu"
aria-expanded={mobileMenuOpen}
>
<span />
<span />
<span />
</button>
</div>
{mobileMenuOpen && (
<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>
<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 */}
@@ -120,8 +158,8 @@ export default function LandingPage() {
<div className="label">Less Time on Documentation</div>
</div>
<div className="landing-proof-stat">
<div className="number">0</div>
<div className="label">Ticket Notes Written by Hand</div>
<div className="number">100%</div>
<div className="label">Auto-Generated Documentation</div>
</div>
</div>
</div>
@@ -198,14 +236,13 @@ export default function LandingPage() {
</div>
</div>
<div style={{ height: '5rem' }} />
<div className="landing-section-divider" />
{/* Problem Section */}
<section id="problem" className="landing-reveal">
<div className="landing-section-inner">
<div className="landing-section-label">The Problem</div>
<div className="landing-section-title">Documentation is broken.<br />Everyone knows it.</div>
<h2 className="landing-section-title">Documentation is broken.<br />Everyone knows it.</h2>
<div className="landing-section-desc">
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.
</div>
@@ -243,7 +280,7 @@ export default function LandingPage() {
<section id="how-it-works" className="landing-reveal">
<div className="landing-section-inner">
<div className="landing-section-label">How It Works</div>
<div className="landing-section-title">Three steps. Zero note-writing.</div>
<h2 className="landing-section-title">Three steps. Zero note-writing.</h2>
<div className="landing-section-desc">
Build once, run forever. Every session generates documentation automatically.
</div>
@@ -312,7 +349,7 @@ export default function LandingPage() {
<section id="features" className="landing-reveal">
<div className="landing-section-inner">
<div className="landing-section-label">Features</div>
<div className="landing-section-title">Everything your team needs to<br />resolve faster and document better.</div>
<h2 className="landing-section-title">Everything your team needs to<br />resolve faster and document better.</h2>
<div className="landing-features-grid">
<FeatureCard
highlight
@@ -355,7 +392,7 @@ export default function LandingPage() {
<section id="pricing" className="landing-reveal">
<div className="landing-section-inner">
<div className="landing-section-label">Pricing</div>
<div className="landing-section-title">Simple pricing. No surprises.</div>
<h2 className="landing-section-title">Simple pricing. No surprises.</h2>
<div className="landing-section-desc">Start free. Upgrade when your team is ready.</div>
<div className="landing-pricing-grid">
<PricingCard
@@ -453,8 +490,8 @@ export default function LandingPage() {
<span className="landing-footer-copy">&copy; 2026 ResolutionFlow. All rights reserved.</span>
</div>
<ul className="landing-footer-links">
<li><a href="#">Privacy</a></li>
<li><a href="#">Terms</a></li>
<li><Link to="/privacy">Privacy</Link></li>
<li><Link to="/terms">Terms</Link></li>
<li><a href="mailto:hello@resolutionflow.com">Contact</a></li>
</ul>
</div>

View File

@@ -79,7 +79,7 @@ export function LoginPage() {
<span>Resolution</span><span className="text-gradient-brand">Flow</span>
</h1>
<p className="mt-2 text-base font-medium text-muted-foreground sm:mt-3 sm:text-lg">
Decision Tree Platform
AI-Powered Troubleshooting for MSPs
</p>
<p className="mt-1 text-sm text-muted-foreground/70 sm:mt-2">
Sign in to your account

View File

@@ -0,0 +1,44 @@
import { Link } from 'react-router-dom'
import { PageMeta } from '@/components/common/PageMeta'
export default function PrivacyPage() {
return (
<>
<PageMeta title="Privacy Policy" description="ResolutionFlow Privacy Policy" />
<div className="min-h-screen bg-background text-foreground">
<div className="mx-auto max-w-3xl px-6 py-16">
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">&larr; Back to home</Link>
<h1 className="text-3xl font-bold font-heading mb-8">Privacy Policy</h1>
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
<div className="space-y-6 text-muted-foreground leading-relaxed">
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">1. Information We Collect</h2>
<p>When you use ResolutionFlow, we collect information you provide directly: your name, email address, and account credentials. We also collect usage data including session logs, flow configurations, and troubleshooting documentation generated through the platform.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">2. How We Use Your Information</h2>
<p>We use your information to provide and improve ResolutionFlow, including: operating your account, generating documentation from troubleshooting sessions, providing AI-powered guidance through FlowPilot, and communicating with you about the service.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">3. Data Storage & Security</h2>
<p>Your data is stored securely using industry-standard encryption. PSA credentials (ConnectWise, Atera, Syncro) are encrypted at rest. We do not sell your data to third parties.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">4. AI & Data Processing</h2>
<p>FlowPilot uses AI models to provide troubleshooting guidance. Session data sent to AI providers is used only for generating responses within your session and is not used to train AI models.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">5. Contact</h2>
<p>Questions about this policy? Email us at <a href="mailto:hello@resolutionflow.com" className="text-primary hover:underline">hello@resolutionflow.com</a>.</p>
</section>
</div>
</div>
</div>
</>
)
}

View File

@@ -86,15 +86,13 @@ export function RegisterPage() {
<div className="relative w-full max-w-md space-y-8">
<div className="text-center">
<div className="mb-4 flex justify-center sm:mb-6">
<div className="w-16 h-16 rounded-2xl bg-white flex items-center justify-center sm:w-20 sm:h-20">
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
</div>
<BrandLogo size="lg" />
</div>
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
ResolutionFlow
</h1>
<p className="mt-2 text-base font-medium text-muted-foreground sm:mt-3 sm:text-lg">
Decision Tree Platform
AI-Powered Troubleshooting for MSPs
</p>
<p className="mt-1 text-sm text-muted-foreground sm:mt-2">
Create your account

View File

@@ -0,0 +1,49 @@
import { Link } from 'react-router-dom'
import { PageMeta } from '@/components/common/PageMeta'
export default function TermsPage() {
return (
<>
<PageMeta title="Terms of Service" description="ResolutionFlow Terms of Service" />
<div className="min-h-screen bg-background text-foreground">
<div className="mx-auto max-w-3xl px-6 py-16">
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">&larr; Back to home</Link>
<h1 className="text-3xl font-bold font-heading mb-8">Terms of Service</h1>
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
<div className="space-y-6 text-muted-foreground leading-relaxed">
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">1. Acceptance of Terms</h2>
<p>By accessing or using ResolutionFlow, you agree to be bound by these Terms of Service. If you are using ResolutionFlow on behalf of an organization, you represent that you have authority to bind that organization to these terms.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">2. Description of Service</h2>
<p>ResolutionFlow provides AI-guided troubleshooting decision trees, automated documentation generation, and PSA integration tools for managed service providers (MSPs). The service includes FlowPilot AI copilot, flow editor, session management, and team collaboration features.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">3. Account Responsibilities</h2>
<p>You are responsible for maintaining the security of your account credentials and for all activities that occur under your account. You must notify us immediately of any unauthorized use.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">4. Data Ownership</h2>
<p>You retain ownership of all content you create within ResolutionFlow, including flows, session documentation, and team knowledge. We do not claim ownership of your data.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">5. Service Availability</h2>
<p>ResolutionFlow is currently in beta. We strive for high availability but do not guarantee uninterrupted access during the beta period. We will provide reasonable notice of planned maintenance.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-foreground mb-3">6. Contact</h2>
<p>Questions about these terms? Email us at <a href="mailto:hello@resolutionflow.com" className="text-primary hover:underline">hello@resolutionflow.com</a>.</p>
</section>
</div>
</div>
</div>
</>
)
}

View File

@@ -18,6 +18,8 @@ const PublicTemplatesPage = lazy(() => import('@/pages/PublicTemplatesPage'))
const SharedSessionPage = lazy(() => import('@/pages/SharedSessionPage'))
const SurveyPage = lazy(() => import('@/pages/SurveyPage'))
const SurveyThankYouPage = lazy(() => import('@/pages/SurveyThankYouPage'))
const PrivacyPage = lazy(() => import('@/pages/PrivacyPage'))
const TermsPage = lazy(() => import('@/pages/TermsPage'))
// Standalone auth pages
const VerifyEmailPage = lazy(() => import('@/pages/VerifyEmailPage'))
@@ -100,6 +102,16 @@ export const router = sentryCreateBrowserRouter([
element: page(PublicTemplatesPage),
errorElement: <RouteError />,
},
{
path: '/privacy',
element: page(PrivacyPage),
errorElement: <RouteError />,
},
{
path: '/terms',
element: page(TermsPage),
errorElement: <RouteError />,
},
{
path: '/login',
element: <LoginPage />,

View File

@@ -86,7 +86,7 @@
right: 0;
z-index: 100;
padding: 0 2rem;
transition: all 0.3s ease;
transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease, opacity 0.3s ease;
}
.landing-nav.scrolled {
@@ -158,6 +158,7 @@
text-decoration: none;
transition: color 0.2s;
letter-spacing: 0.01em;
padding: 0.75rem 0.5rem;
}
.landing-nav-links a:hover {
@@ -177,7 +178,7 @@
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.2s;
transition: color 0.2s, background 0.2s;
}
.landing-btn-ghost:hover {
@@ -194,7 +195,7 @@
border-radius: 8px;
background: linear-gradient(135deg, #06b6d4, #22d3ee);
box-shadow: 0 0 20px rgba(6, 182, 212, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transition: all 0.25s;
transition: opacity 0.25s, transform 0.25s, box-shadow 0.25s;
letter-spacing: -0.01em;
border: none;
cursor: pointer;
@@ -296,7 +297,7 @@
border-radius: 10px;
background: linear-gradient(135deg, #06b6d4, #22d3ee);
box-shadow: 0 4px 30px rgba(6, 182, 212, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.15);
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
letter-spacing: -0.01em;
border: none;
cursor: pointer;
@@ -322,7 +323,7 @@
border-radius: 10px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
}
.landing-btn-hero-secondary:hover {
@@ -618,7 +619,7 @@
/* ---- SECTION LAYOUT ---- */
.landing-page section {
padding: 6rem 2rem;
padding: 4rem 2rem;
}
.landing-section-inner {
@@ -642,6 +643,7 @@
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.15;
margin-top: 0;
margin-bottom: 1rem;
}
@@ -674,7 +676,7 @@
border-radius: 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
}
.landing-problem-card:hover {
@@ -716,7 +718,7 @@
.landing-problem-card h3 {
font-family: 'Bricolage Grotesque', sans-serif;
font-size: 1.15rem;
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.6rem;
letter-spacing: -0.01em;
@@ -731,7 +733,7 @@
/* ---- EQUATION ---- */
.landing-equation-section {
text-align: center;
padding: 5rem 2rem;
padding: 3.5rem 2rem;
position: relative;
}
@@ -804,7 +806,7 @@
border: 1px solid rgba(255, 255, 255, 0.06);
position: relative;
counter-increment: landing-step;
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
}
.landing-step-card:hover {
@@ -967,7 +969,7 @@
border-radius: 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.3s ease;
transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease, opacity 0.3s ease;
}
.landing-feature-card:hover {
@@ -990,7 +992,7 @@
.landing-feature-card h3 {
font-family: 'Bricolage Grotesque', sans-serif;
font-size: 1.05rem;
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.6rem;
letter-spacing: -0.01em;
@@ -1022,7 +1024,7 @@
border-radius: 16px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
}
.landing-pricing-card:hover {
@@ -1133,7 +1135,7 @@
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
transition: all 0.3s;
transition: background 0.3s, border-color 0.3s, box-shadow 0.3s, transform 0.3s, opacity 0.3s;
width: 100%;
}
@@ -1181,7 +1183,7 @@
text-align: center;
max-width: 700px;
margin: 0 auto;
padding: 5rem 2rem;
padding: 3.5rem 2rem;
}
.landing-testimonial-quote {
@@ -1234,7 +1236,7 @@
/* ---- CTA ---- */
.landing-cta-section {
text-align: center;
padding: 6rem 2rem;
padding: 4rem 2rem;
position: relative;
}
@@ -1396,6 +1398,87 @@
transform: translateY(0);
}
/* ---- Hamburger Button ---- */
.landing-hamburger {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 44px;
height: 44px;
padding: 10px;
background: none;
border: none;
cursor: pointer;
z-index: 110;
}
.landing-hamburger span {
display: block;
width: 22px;
height: 2px;
background: #a0a0b0;
border-radius: 2px;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.landing-hamburger:hover span {
background: #f0f0f5;
}
.landing-hamburger.open span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.landing-hamburger.open span:nth-child(2) {
opacity: 0;
}
.landing-hamburger.open span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
/* ---- Mobile Menu ---- */
.landing-mobile-menu {
display: none;
flex-direction: column;
gap: 0;
padding: 0.5rem 1.5rem 1.5rem;
background: rgba(14, 15, 24, 0.95);
backdrop-filter: blur(20px) saturate(1.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.landing-mobile-menu a {
display: block;
padding: 0.875rem 0;
font-size: 1rem;
font-weight: 500;
color: #a0a0b0;
text-decoration: none;
transition: color 0.2s;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.landing-mobile-menu a:hover {
color: #f0f0f5;
}
.landing-mobile-menu a:last-child {
border-bottom: none;
}
.landing-mobile-menu-divider {
height: 1px;
background: rgba(255, 255, 255, 0.08);
margin: 0.5rem 0;
}
.landing-mobile-menu .landing-btn-primary {
margin-top: 0.5rem;
display: block;
}
/* ---- RESPONSIVE ---- */
@media (max-width: 900px) {
@@ -1414,10 +1497,34 @@
display: none;
}
.landing-nav-cta {
display: none;
}
.landing-hamburger {
display: flex;
}
.landing-mobile-menu {
display: flex;
}
.landing-preview-sidebar {
display: none;
}
.landing-preview-url-bar {
display: none;
}
.landing-preview-window-controls {
display: none;
}
.landing-preview-titlebar {
justify-content: center;
}
.landing-tree-branch {
gap: 1rem;
}