feat(contact): CRO improvements — align services dropdown, value-prop H1, response promise above form, tighter message field
E2E Test Suite / Form Interaction Tests (push) Failing after 10m53s
E2E Test Suite / Security Header Tests (push) Failing after 10m39s
E2E Test Suite / Mobile Device Tests (push) Failing after 11m31s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 12m23s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m16s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m8s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 15m0s
Ping Search Engines / Notify Search Engines (push) Failing after 14m47s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 21m30s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Test Report Summary (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Failing after 14m58s
Deploy to Production / Build & Verify (push) Failing after 19m22s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Railway (push) Failing after 14m58s
Deploy to Production / Deploy to Render (push) Failing after 14m57s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m6s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m5s
Deploy to Production / Notify on Failure (push) Failing after 14m58s

Addresses contact-page audit points 22–27:
- Confirmed no '200+ companies' credibility claim; canonical stat set retained
- Service dropdown now matches real services, led by ERP/Enterprise + Government IT;
  removed Cloud Services and AI & ML, added UI/UX Design
- Backend allowlist + labels updated to accept 'ui-ux' and reflect real service names
- H1 paired with value prop: free 1-page proposal in 24 hours
- '< 24h response' promise badge moved above the form
- Clarified contradictory hours to 'We reply within 24 business hours, Mon–Fri'
- Reduced message textarea height (rows 5→4) and max length (5000→2000)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
platform-mcp
2026-06-17 14:33:37 +05:30
co-authored by Claude Opus 4.8
parent 0b8c2163f3
commit 721d683eb9
3 changed files with 30 additions and 19 deletions
@@ -35,8 +35,8 @@ Both endpoints include server-side validation, rate limiting, spam protection, a
- `name` — required, 2100 characters - `name` — required, 2100 characters
- `email` — required, valid format, max 254 chars - `email` — required, valid format, max 254 chars
- `phone` — optional; if present, must match phone regex - `phone` — optional; if present, must match phone regex
- `subject` — required, allowlist enum (web-development, mobile-development, cloud-services, ai-ml, consulting, support, other) - `subject` — required, allowlist enum (erp-solutions, government-systems, web-development, mobile-development, ui-ux, consulting, other; legacy values cloud-services, ai-ml, support still accepted by the API but no longer offered in the contact-page dropdown)
- `message` — required, 105000 characters - `message` — required, 102000 characters
- `website` — honeypot, must be empty - `website` — honeypot, must be empty
**Rate limit:** 5 requests per IP per hour **Rate limit:** 5 requests per IP per hour
+5 -3
View File
@@ -46,6 +46,7 @@ const PHONE_REGEX = /^[\+]?[(]?[0-9]{1,3}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[
const VALID_SUBJECTS = [ const VALID_SUBJECTS = [
'web-development', 'web-development',
'mobile-development', 'mobile-development',
'ui-ux',
'cloud-services', 'cloud-services',
'ai-ml', 'ai-ml',
'consulting', 'consulting',
@@ -312,12 +313,13 @@ async function sendContactEmail(data: ContactFormData): Promise<SendResult | voi
const subjectLabels: Record<string, string> = { const subjectLabels: Record<string, string> = {
'web-development': 'Web Development', 'web-development': 'Web Development',
'mobile-development': 'Mobile Development', 'mobile-development': 'Mobile Development',
'ui-ux': 'UI/UX Design',
'cloud-services': 'Cloud Services', 'cloud-services': 'Cloud Services',
'ai-ml': 'AI & Machine Learning', 'ai-ml': 'AI & Machine Learning',
'consulting': 'IT Consulting', 'consulting': 'IT Consulting / DevOps',
'support': 'Technical Support', 'support': 'Technical Support',
'erp-solutions': 'ERP / Custom Software', 'erp-solutions': 'ERP / Enterprise Systems',
'government-systems': 'Government / GeM', 'government-systems': 'Government IT',
'other': 'Other', 'other': 'Other',
}; };
+23 -14
View File
@@ -34,7 +34,7 @@ const contactInfo = [
{ {
icon: 'clock', icon: 'clock',
title: 'Business Hours', title: 'Business Hours',
details: ['Mon Fri: 10:00 AM 6:00 PM', 'Sat Sun: Closed'], details: ['Mon Fri: 10:00 AM 6:00 PM', 'We reply within 24 business hours, Mon Fri.'],
link: null, link: null,
linkLabel: null, linkLabel: null,
color: 'violet', color: 'violet',
@@ -125,7 +125,7 @@ const trustStats = [
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-300 to-primary-500">Hear From You</span> <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-300 to-primary-500">Hear From You</span>
</h1> </h1>
<p class="text-xl text-secondary-300 max-w-2xl mx-auto reveal-on-scroll reveal-delay-2"> <p class="text-xl text-secondary-300 max-w-2xl mx-auto reveal-on-scroll reveal-delay-2">
Whether you have a project in mind, a question about our services, or just want to explore the possibilities — our team is ready to help. Tell us about your project — get a free 1-page proposal in 24 hours.
</p> </p>
<!-- Trust stats strip --> <!-- Trust stats strip -->
@@ -151,8 +151,17 @@ const trustStats = [
<div class="bg-white dark:bg-secondary-800 rounded-2xl shadow-card border border-secondary-100 dark:border-secondary-700 p-8 lg:p-10"> <div class="bg-white dark:bg-secondary-800 rounded-2xl shadow-card border border-secondary-100 dark:border-secondary-700 p-8 lg:p-10">
<!-- Form Header --> <!-- Form Header -->
<div class="mb-8"> <div class="mb-8">
<h2 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100 mb-2">Send Us a Message</h2> <div class="flex flex-wrap items-center justify-between gap-3 mb-2">
<p class="text-secondary-500 dark:text-secondary-400 text-sm">Fill out the form below and we'll respond within 24 hours.</p> <h2 class="text-2xl font-bold text-secondary-900 dark:text-secondary-100">Send Us a Message</h2>
<!-- Response promise — shown ABOVE the form so the commitment is visible before users invest time -->
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 text-emerald-700 dark:text-emerald-300 text-xs font-semibold whitespace-nowrap">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<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" />
</svg>
&lt; 24h response
</span>
</div>
<p class="text-secondary-500 dark:text-secondary-400 text-sm">Fill out the form below and we'll reply within 24 business hours, Mon Fri.</p>
</div> </div>
<form id="contact-form" class="space-y-5" novalidate> <form id="contact-form" class="space-y-5" novalidate>
@@ -325,12 +334,12 @@ const trustStats = [
class="form-input w-full pl-10 pr-8 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 appearance-none cursor-pointer" class="form-input w-full pl-10 pr-8 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 appearance-none cursor-pointer"
> >
<option value="">Select a service...</option> <option value="">Select a service...</option>
<option value="erp-solutions">ERP / Enterprise Systems</option>
<option value="government-systems">Government IT</option>
<option value="web-development">Web Development</option> <option value="web-development">Web Development</option>
<option value="mobile-development">Mobile Development</option> <option value="mobile-development">Mobile Development</option>
<option value="cloud-services">Cloud Services</option> <option value="ui-ux">UI/UX Design</option>
<option value="ai-ml">AI & Machine Learning</option> <option value="consulting">IT Consulting / DevOps</option>
<option value="consulting">IT Consulting</option>
<option value="support">Technical Support</option>
<option value="other">Other / General Inquiry</option> <option value="other">Other / General Inquiry</option>
</select> </select>
<div class="valid-check absolute inset-y-0 right-9 flex items-center hidden" aria-label="Valid"> <div class="valid-check absolute inset-y-0 right-9 flex items-center hidden" aria-label="Valid">
@@ -379,7 +388,7 @@ const trustStats = [
Your Message <span class="text-red-500" aria-hidden="true">*</span> Your Message <span class="text-red-500" aria-hidden="true">*</span>
<span class="sr-only">(required)</span> <span class="sr-only">(required)</span>
</label> </label>
<span id="char-count" class="text-xs text-secondary-400" aria-hidden="true">0 / 5000</span> <span id="char-count" class="text-xs text-secondary-400" aria-hidden="true">0 / 2000</span>
</div> </div>
<div class="relative"> <div class="relative">
<textarea <textarea
@@ -387,8 +396,8 @@ const trustStats = [
name="message" name="message"
required required
minlength="10" minlength="10"
maxlength="5000" maxlength="2000"
rows="5" rows="4"
aria-required="true" aria-required="true"
aria-describedby="message-error message-hint" aria-describedby="message-error message-hint"
class="form-input w-full px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 resize-none" class="form-input w-full px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all duration-200 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-sm bg-secondary-50 dark:bg-secondary-700 focus:bg-white dark:focus:bg-secondary-700 resize-none"
@@ -895,8 +904,8 @@ const trustStats = [
if (messageField && charCount) { if (messageField && charCount) {
messageField.addEventListener('input', () => { messageField.addEventListener('input', () => {
const len = messageField.value.length; const len = messageField.value.length;
charCount.textContent = `${len} / 5000`; charCount.textContent = `${len} / 2000`;
charCount.className = len > 4500 ? 'text-xs text-red-500' : 'text-xs text-secondary-400'; charCount.className = len > 1800 ? 'text-xs text-red-500' : 'text-xs text-secondary-400';
}); });
} }
@@ -1111,7 +1120,7 @@ const trustStats = [
form.querySelectorAll('.valid-check').forEach((el) => el.classList.add('hidden')); form.querySelectorAll('.valid-check').forEach((el) => el.classList.add('hidden'));
form.querySelectorAll('.invalid-check').forEach((el) => el.classList.add('hidden')); form.querySelectorAll('.invalid-check').forEach((el) => el.classList.add('hidden'));
form.querySelectorAll('[data-error]').forEach((el) => el.classList.add('hidden')); form.querySelectorAll('[data-error]').forEach((el) => el.classList.add('hidden'));
if (charCount) charCount.textContent = '0 / 5000'; if (charCount) charCount.textContent = '0 / 2000';
budgetOptions.forEach((o) => o.classList.remove('selected')); budgetOptions.forEach((o) => o.classList.remove('selected'));
}); });
} }