agent: Apply homepage CRO improvements

This commit is contained in:
platform-mcp
2026-06-17 14:05:47 +05:30
parent de0426cad9
commit 5e81c4bab5
2 changed files with 151 additions and 163 deletions
+38 -163
View File
@@ -119,15 +119,11 @@ const testimonials = [
},
];
// Stats data — canonical site-wide proof set. Keep this identical to the
// ProofNumbers band, the /portfolio stats bar, and the /about stats grid so
// a visitor browsing multiple pages never sees a conflicting number.
const stats = [
{ value: 20, suffix: '+', label: 'Projects', icon: 'briefcase' },
{ value: 11, suffix: '', label: 'Districts Deployed', icon: 'building' },
{ value: 50000, suffix: '+', label: 'Records Managed', icon: 'database' },
{ value: 8, suffix: '+', label: 'Years Avg Experience', icon: 'clock' },
];
// NOTE: The homepage stat band is rendered once by <ProofNumbers />, which
// holds the canonical site-wide proof set (20+ Projects · 11 Districts ·
// 50,000+ Records · 8+ Years). The previously-duplicated "Numbers That
// Matter" stats section (and its local `stats` array + count-up script) was
// removed so a visitor never sees the same four numbers twice on one page.
// Technology stack data — relocated below the fold so social proof leads the
// hero. Each entry pairs the framework name with a short context line and a
@@ -212,7 +208,7 @@ const techStack = [
</h1>
<p class="mt-8 text-xl sm:text-2xl text-secondary-200 max-w-3xl mx-auto opacity-0 animate-slide-up leading-relaxed" style="animation-delay: 0.6s;">
WorkRoot designs, builds, and runs production-grade web, mobile, and ERP systems for startups, enterprises, and government bodies.
We design, build, and run production-grade web, mobile, and ERP systems for startups, enterprises, and government bodies.
</p>
<!-- Dual CTA Buttons — action verb + clear secondary -->
@@ -236,7 +232,7 @@ const techStack = [
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
</svg>
See our work
See 20+ case studies
</a>
</div>
@@ -254,6 +250,35 @@ const techStack = [
<span>No commitment</span>
</p>
<!-- Product preview — framed dashboard mock so the hero leads with a
concrete product visual, not text alone. The image is a polished
placeholder UI (public/images/product-dashboard.svg); swap in a
real product screenshot or screen-recording poster frame
(≥1200×720, .webp/.png) at the same path — no template change
needed. Explicit width/height keep CLS at zero while it loads. -->
<figure class="mt-16 sm:mt-20 max-w-4xl mx-auto opacity-0 animate-slide-up" style="animation-delay: 1s;">
<div class="relative rounded-2xl border border-white/15 bg-white/5 backdrop-blur-sm shadow-2xl shadow-primary/20 overflow-hidden">
<!-- Glow behind the frame -->
<div class="absolute -inset-px rounded-2xl bg-gradient-to-tr from-primary-500/10 via-transparent to-accent-400/10 pointer-events-none" aria-hidden="true"></div>
<!-- Browser chrome -->
<div class="flex items-center gap-2 px-4 py-3 border-b border-white/10 bg-white/5" aria-hidden="true">
<span class="w-3 h-3 rounded-full bg-red-400/80"></span>
<span class="w-3 h-3 rounded-full bg-accent-400/80"></span>
<span class="w-3 h-3 rounded-full bg-green-400/80"></span>
<span class="ml-4 inline-flex items-center h-6 px-3 rounded-md bg-white/10 text-xs text-secondary-300 font-medium">app.workroot.in/dashboard</span>
</div>
<img
src="/images/product-dashboard.svg"
alt="WorkRoot project dashboard preview — delivery metrics, activity timeline, and deployment overview"
width="1200"
height="720"
loading="eager"
decoding="async"
class="block w-full h-auto"
/>
</div>
</figure>
</div>
</div>
@@ -426,54 +451,6 @@ const techStack = [
</div>
</section>
<!-- Stats/Metrics Section - Enhanced -->
<section class="py-24 bg-white dark:bg-secondary-900">
<div class="container-wrapper">
<div class="text-center max-w-3xl mx-auto mb-16" data-animate="fade-up">
<span class="inline-block px-4 py-2 rounded-full bg-accent-50 dark:bg-accent-900/20 text-accent-700 dark:text-accent-300 text-sm font-bold uppercase tracking-wider mb-4">Track Record</span>
<h2 class="text-4xl sm:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-6">Numbers That Matter</h2>
<p class="text-xl text-secondary-600 dark:text-secondary-400">
Our success is measured by the impact we create for our clients.
</p>
</div>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8">
{stats.map((stat, index) => (
<div class="group text-center bg-gradient-to-br from-secondary-50 to-white dark:from-secondary-800 dark:to-secondary-800/80 rounded-3xl p-10 border-2 border-secondary-100 dark:border-secondary-700 hover:border-primary-400 hover:shadow-2xl transition-all duration-500 hover:-translate-y-2" data-animate="scale-up" data-delay={String(index * 100)}>
{/* Icon */}
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-primary-500 to-primary-600 flex items-center justify-center mx-auto mb-6 group-hover:scale-110 group-hover:rotate-12 transition-all duration-500 shadow-lg">
{stat.icon === 'briefcase' && (
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg>
)}
{stat.icon === 'database' && (
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"></path>
</svg>
)}
{stat.icon === 'building' && (
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
)}
{stat.icon === 'clock' && (
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
)}
</div>
<div class="stat-counter text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-2" data-target={stat.value} data-suffix={stat.suffix}>
{stat.value.toLocaleString('en-IN')}{stat.suffix}
</div>
<div class="text-secondary-600 dark:text-secondary-400 font-semibold">{stat.label}</div>
</div>
))}
</div>
</div>
</section>
<!-- Testimonials Section - Enhanced -->
<section class="py-24 bg-gradient-to-b from-secondary-50 to-white dark:from-secondary-900 dark:to-secondary-900">
<div class="container-wrapper">
@@ -681,7 +658,7 @@ const techStack = [
<div class="container-wrapper text-center relative z-10">
<div class="max-w-4xl mx-auto" data-animate="fade-up">
<h2 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-8 leading-tight">
Ready to ship your next <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-300 to-accent-400">production system?</span>
Ready to ship in <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-300 to-accent-400">8 weeks</span> instead of 8 months?
</h2>
<p class="text-xl sm:text-2xl text-secondary-200 mb-10 max-w-2xl mx-auto leading-relaxed">
Tell us about your project and we'll send back a free, no-obligation scope and timeline within one business day.
@@ -722,7 +699,7 @@ const techStack = [
<svg class="mr-2 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
</svg>
See our work
See 20+ case studies
</a>
</div>
@@ -805,67 +782,11 @@ const techStack = [
animation: pulse-slower 6s ease-in-out infinite;
}
/* Stat counter animation */
.stat-counter {
font-variant-numeric: tabular-nums;
}
/* (FAQ accordion styles moved into <HomepageFaq />, which now renders the
only homepage FAQ section.) */
</style>
<script>
// Stats counter animation
// The initial textContent already renders the final value (server-side),
// so users with no JS / failed hydration / reduced-motion still see the
// correct number. The animation here briefly resets to 0 and counts up.
function animateCounters() {
const counters = document.querySelectorAll<HTMLElement>('.stat-counter');
const duration = 2000;
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
counters.forEach((counter) => {
const target = parseInt(counter.getAttribute('data-target') || '0', 10);
const suffix = counter.getAttribute('data-suffix') || '';
// Guaranteed final state — set the resolved value up front so a
// mid-animation failure can never leave the user staring at "0".
// Use the Indian locale grouping so large counters (e.g. 50,000+)
// render with the same comma format as the server-rendered markup.
const finalText = target.toLocaleString('en-IN') + suffix;
if (reduceMotion || !Number.isFinite(target) || target <= 0) {
counter.textContent = finalText;
return;
}
// Avoid running the animation more than once per counter.
if (counter.dataset.counted === 'true') return;
counter.dataset.counted = 'true';
const startTime = performance.now();
counter.textContent = (0).toLocaleString('en-IN') + suffix;
function updateCounter(currentTime: number) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
const current = Math.floor(easeOutQuart * target);
counter.textContent = current.toLocaleString('en-IN') + suffix;
if (progress < 1) {
requestAnimationFrame(updateCounter);
} else {
// Snap to exact final value to avoid floor-rounding drift.
counter.textContent = finalText;
}
}
requestAnimationFrame(updateCounter);
});
}
// Testimonials carousel
function initCarousel() {
const track = document.querySelector('.testimonial-track') as HTMLElement;
@@ -946,53 +867,7 @@ const techStack = [
// (FAQ accordion script moved into <HomepageFaq />, which now renders the
// only homepage FAQ section and ships its own inline init.)
// Intersection Observer for counter animation
function initStatsCounter() {
const firstCounter = document.querySelector('.stat-counter');
const statsSection = firstCounter?.closest('section');
// If the markup isn't there for any reason, bail silently — the
// server-rendered numbers are already correct.
if (!statsSection) return;
// Older browsers / failed feature detection: just render the final
// values immediately. They're already in the DOM, but this also
// protects against the path where data-target was patched at runtime.
if (typeof IntersectionObserver === 'undefined') {
animateCounters();
return;
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animateCounters();
observer.disconnect();
}
});
},
// Lower threshold so the animation fires reliably on tall mobile
// viewports; rootMargin lets it kick in just before the section
// scrolls into view for a smoother first impression.
{ threshold: 0.1, rootMargin: '0px 0px -10% 0px' }
);
observer.observe(statsSection);
// If the section is already in the viewport at load time the
// observer fires on its next tick, but we also guard with a fallback
// so anyone who lands mid-scroll never sees the static "0" path.
const rect = statsSection.getBoundingClientRect();
const inView = rect.top < window.innerHeight && rect.bottom > 0;
if (inView) {
animateCounters();
observer.disconnect();
}
}
function bootHomepageScripts() {
initStatsCounter();
initCarousel();
}