refactor(home): remove duplicate FAQ section, merge unique Q&As into HomepageFaq
Deploy to Production / Build & Verify (push) Has been cancelled
Deploy to Production / Pre-Deploy Tests (push) Has been cancelled
Deploy to Production / Deploy to Railway (push) Has been cancelled
Deploy to Production / Deploy to Render (push) Has been cancelled
Deploy to Production / Deploy to VPS (PM2) (push) Has been cancelled
Deploy to Production / Deploy to Fly.io (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Has been cancelled
Deploy to Production / Notify on Failure (push) Has been cancelled
E2E Test Suite / Critical User Journeys (push) Has been cancelled
E2E Test Suite / API Integration Tests (push) Has been cancelled
E2E Test Suite / Form Interaction Tests (push) Has been cancelled
E2E Test Suite / Destructive & Chaos Tests (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (chromium) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (firefox) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (webkit) (push) Has been cancelled
E2E Test Suite / Mobile Device Tests (push) Has been cancelled
E2E Test Suite / Security Header Tests (push) Has been cancelled
E2E Test Suite / Test Report Summary (push) Has been cancelled
E2E Test Suite / Smoke Tests (P0) (push) Has been cancelled
Ping Search Engines / Notify Search Engines (push) Successful in 3s
Deploy to Production / Build & Verify (push) Has been cancelled
Deploy to Production / Pre-Deploy Tests (push) Has been cancelled
Deploy to Production / Deploy to Railway (push) Has been cancelled
Deploy to Production / Deploy to Render (push) Has been cancelled
Deploy to Production / Deploy to VPS (PM2) (push) Has been cancelled
Deploy to Production / Deploy to Fly.io (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Has been cancelled
Deploy to Production / Notify on Failure (push) Has been cancelled
E2E Test Suite / Critical User Journeys (push) Has been cancelled
E2E Test Suite / API Integration Tests (push) Has been cancelled
E2E Test Suite / Form Interaction Tests (push) Has been cancelled
E2E Test Suite / Destructive & Chaos Tests (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (chromium) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (firefox) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (webkit) (push) Has been cancelled
E2E Test Suite / Mobile Device Tests (push) Has been cancelled
E2E Test Suite / Security Header Tests (push) Has been cancelled
E2E Test Suite / Test Report Summary (push) Has been cancelled
E2E Test Suite / Smoke Tests (P0) (push) Has been cancelled
Ping Search Engines / Notify Search Engines (push) Successful in 3s
The homepage rendered two FAQ blocks — an inline "Common Questions" section
plus the <HomepageFaq /> component above the final CTA — which produced two
FAQPage JSON-LD entries and a redundant accordion. Consolidate on
HomepageFaq (self-contained, polished accordion, better a11y) and:
- delete the inline FAQ <section>, the `faqs` array, the `faq={faqs}` prop
on <SEO>, the orphan .faq-question/.faq-answer/.faq-icon CSS, and the
initFAQ() boot script + its call in index.astro
- merge the 4 unique inline questions (support, process, team augmentation,
security) into HomepageFaq's moneyFaqs; the 2 duplicates (technologies,
timeline) were already covered there
- refresh HomepageFaq's lead-in copy and comments to reflect it as the
single canonical homepage FAQ
Result: one FAQ heading + one accordion in the DOM, exactly one FAQPage
JSON-LD with 10 Q&A pairs, no broken anchor links.
This commit is contained in:
@@ -1,17 +1,12 @@
|
|||||||
---
|
---
|
||||||
// HomepageFaq — Money & process FAQ rendered just above the final CTA band.
|
// HomepageFaq — single homepage FAQ section rendered just above the final
|
||||||
|
// CTA band. Covers both buyer-stage objections (cost, timeline, NDA, IP, GeM,
|
||||||
|
// stack) and the process/support/security questions that used to live in the
|
||||||
|
// older "Common Questions" block higher up the page. That earlier block was
|
||||||
|
// removed to avoid a duplicate FAQ rendering and a duplicate FAQPage JSON-LD
|
||||||
|
// emission — this component is now the canonical homepage FAQ.
|
||||||
//
|
//
|
||||||
// Why a dedicated component instead of reusing the "Common Questions" block
|
// Emits a single FAQPage JSON-LD inline (below) for rich-result eligibility.
|
||||||
// further up the page:
|
|
||||||
// - The questions here target buyers in late-stage evaluation (cost,
|
|
||||||
// timeline, NDA, IP, GeM, stack). They convert better when surfaced next
|
|
||||||
// to the final CTA, not next to the testimonials.
|
|
||||||
// - Emitting FAQPage JSON-LD only once per page is the recommended pattern
|
|
||||||
// for rich-result eligibility. The existing FAQ block above also emits a
|
|
||||||
// FAQPage via <SEO> — so this component renders ONLY the visible UI; the
|
|
||||||
// schema for these questions is appended to the parent's `faq` array in
|
|
||||||
// src/pages/index.astro so Google still sees a single, valid FAQPage with
|
|
||||||
// all 12 Q&A pairs.
|
|
||||||
//
|
//
|
||||||
// Accessibility:
|
// Accessibility:
|
||||||
// - Each question is a real <button> with aria-expanded toggled in JS.
|
// - Each question is a real <button> with aria-expanded toggled in JS.
|
||||||
@@ -54,13 +49,32 @@ const moneyFaqs = [
|
|||||||
answer:
|
answer:
|
||||||
'Astro, Next.js, Angular, React Native, Spring Boot, Go, Node.js, PostgreSQL, and the full AWS/GCP stack.',
|
'Astro, Next.js, Angular, React Native, Spring Boot, Go, Node.js, PostgreSQL, and the full AWS/GCP stack.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
question: 'Do you offer ongoing support after launch?',
|
||||||
|
answer:
|
||||||
|
'Yes. We offer maintenance packages covering bug fixes, security patches, performance monitoring, and feature enhancements — with response SLAs to match your uptime needs.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'What is your development process?',
|
||||||
|
answer:
|
||||||
|
'Agile with 2-week sprints, weekly check-ins, and a live staging environment from day one. You see working software early and steer the roadmap as it ships.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Can you work with our existing team?',
|
||||||
|
answer:
|
||||||
|
'Absolutely. We augment in-house teams as readily as we deliver fully managed builds — adapting to your repo, branch strategy, ticket system, and review process.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'How do you ensure project security?',
|
||||||
|
answer:
|
||||||
|
'Security is built into every phase: OWASP-aligned reviews, encrypted data at rest and in transit, role-based access control, regular dependency audits, and compliance with GDPR / SOC 2-style controls where the project demands them.',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// FAQPage JSON-LD — emitted inline so the schema travels with the component.
|
// FAQPage JSON-LD — emitted inline so the schema travels with the component.
|
||||||
// The site-wide <SEO> emits a separate FAQPage for the upper "Common
|
// This is the ONLY FAQPage emitted on the homepage; the site-wide <SEO>
|
||||||
// Questions" block; Google tolerates multiple FAQPage entries on the same
|
// no longer receives a `faq` prop on / so Google sees exactly one valid
|
||||||
// page as long as each Question is unique, which is true here (cost,
|
// FAQPage with every visible Q&A pair.
|
||||||
// timeline, NDA, IP, GeM, stack are all distinct from the generic ones).
|
|
||||||
const faqPageSchema = {
|
const faqPageSchema = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'FAQPage',
|
'@type': 'FAQPage',
|
||||||
@@ -97,7 +111,7 @@ const faqPageSchema = {
|
|||||||
Frequently asked questions
|
Frequently asked questions
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-base sm:text-lg text-secondary-600 dark:text-secondary-300">
|
<p class="text-base sm:text-lg text-secondary-600 dark:text-secondary-300">
|
||||||
Quick answers on cost, timelines, ownership, and the stack we ship.
|
Quick answers on cost, timelines, ownership, our process, support, security, and the stack we ship.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+17
-114
@@ -161,33 +161,11 @@ const techStack = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// FAQ data
|
// FAQ content lives in <HomepageFaq />, which renders the only homepage FAQ
|
||||||
const faqs = [
|
// section and emits the only FAQPage JSON-LD on this page. The duplicate
|
||||||
{
|
// "Common Questions" block that used to live above the tech-stack section
|
||||||
question: 'What technologies do you specialize in?',
|
// was removed; its unique questions were merged into HomepageFaq's
|
||||||
answer: 'We specialize in modern web and mobile technologies including Angular, React, Next.js, Astro, Spring Boot, Golang, and databases like MySQL and PostgreSQL. We also work with Expo for mobile development.',
|
// `moneyFaqs` array so no buyer-facing content was dropped.
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'How long does a typical project take?',
|
|
||||||
answer: 'Project timelines vary based on complexity. A static website might take 2-4 weeks, while a complex government management system could take 6-24 months. We provide detailed timelines during the planning phase.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'Do you offer ongoing support after launch?',
|
|
||||||
answer: 'Yes! We offer comprehensive support packages including bug fixes, security updates, performance monitoring, and feature enhancements. Our 24/7 support ensures your systems run smoothly.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'What is your development process?',
|
|
||||||
answer: 'We follow an agile methodology with 2-week sprints, regular check-ins, and iterative development. You\'ll have full visibility into progress with access to staging environments and regular demos.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'Can you work with our existing team?',
|
|
||||||
answer: 'Absolutely! We excel at collaborating with in-house teams. Whether you need to augment your team or prefer a fully managed solution, we adapt to your workflow and tools.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'How do you ensure project security?',
|
|
||||||
answer: 'Security is built into every phase. We follow OWASP guidelines, conduct regular security audits, implement encryption, and ensure compliance with industry standards like GDPR and SOC 2.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
@@ -197,7 +175,6 @@ const faqs = [
|
|||||||
<SEO
|
<SEO
|
||||||
slot="head"
|
slot="head"
|
||||||
type="WebPage"
|
type="WebPage"
|
||||||
faq={faqs}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Hero Section - Enhanced -->
|
<!-- Hero Section - Enhanced -->
|
||||||
@@ -617,53 +594,6 @@ const faqs = [
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- FAQ Section - NEW -->
|
|
||||||
<section class="py-24 bg-white dark:bg-secondary-900">
|
|
||||||
<div class="container-wrapper">
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16 items-start max-w-7xl mx-auto">
|
|
||||||
<!-- Left column - Header -->
|
|
||||||
<div class="lg:sticky lg:top-8" data-animate="fade-right">
|
|
||||||
<span class="inline-block px-4 py-2 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-bold uppercase tracking-wider mb-4">FAQ</span>
|
|
||||||
<h2 class="text-4xl sm:text-5xl font-bold text-secondary-900 dark:text-secondary-100 mb-6">Common Questions</h2>
|
|
||||||
<p class="text-xl text-secondary-600 dark:text-secondary-400 mb-8">
|
|
||||||
Everything you need to know about working with us. Can't find the answer you're looking for?
|
|
||||||
</p>
|
|
||||||
<a href="/contact" class="inline-flex items-center text-primary-600 dark:text-primary-400 font-semibold hover:text-primary-700 dark:hover:text-primary-300 transition-colors">
|
|
||||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
|
||||||
</svg>
|
|
||||||
Chat with our team
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right column - Accordion -->
|
|
||||||
<div class="space-y-4" data-animate="fade-left" data-delay="150">
|
|
||||||
{faqs.map((faq, index) => (
|
|
||||||
<div class="group bg-secondary-50 dark:bg-secondary-800 rounded-2xl border-2 border-secondary-100 dark:border-secondary-700 hover:border-primary-300 transition-all duration-300 overflow-hidden">
|
|
||||||
<button
|
|
||||||
class="faq-question w-full text-left px-8 py-6 flex items-start justify-between gap-4"
|
|
||||||
data-faq-index={index}
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls={`faq-answer-${index}`}
|
|
||||||
id={`faq-question-${index}`}
|
|
||||||
>
|
|
||||||
<span class="font-bold text-lg text-secondary-900 dark:text-secondary-100 pr-4">{faq.question}</span>
|
|
||||||
<svg class="faq-icon w-6 h-6 text-primary-500 flex-shrink-0 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div class="faq-answer max-h-0 overflow-hidden transition-all duration-300" id={`faq-answer-${index}`} role="region" aria-labelledby={`faq-question-${index}`}>
|
|
||||||
<div class="px-8 pb-6 text-secondary-600 dark:text-secondary-400 leading-relaxed">
|
|
||||||
{faq.answer}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Technologies We Work With — relocated from the hero so social proof
|
<!-- Technologies We Work With — relocated from the hero so social proof
|
||||||
leads above the fold. Kept intentionally subdued (border-only cards,
|
leads above the fold. Kept intentionally subdued (border-only cards,
|
||||||
neutral grayscale icons) so it reads as supporting context rather than
|
neutral grayscale icons) so it reads as supporting context rather than
|
||||||
@@ -710,18 +640,20 @@ const faqs = [
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Inline Lead Form — sits between the FAQ/tech-stack social-proof block
|
<!-- Inline Lead Form — sits between the tech-stack social-proof block
|
||||||
and the final CTA band so visitors who are ready to start a project can
|
and the final CTA band so visitors who are ready to start a project can
|
||||||
submit a 3-field enquiry without leaving the homepage. Submits to the
|
submit a 3-field enquiry without leaving the homepage. Submits to the
|
||||||
same /api/contact endpoint and is tagged `source: 'homepage-inline'` so
|
same /api/contact endpoint and is tagged `source: 'homepage-inline'` so
|
||||||
the lead can be segmented downstream. -->
|
the lead can be segmented downstream. -->
|
||||||
<InlineLeadForm />
|
<InlineLeadForm />
|
||||||
|
|
||||||
<!-- Money & process FAQ — covers the cost / timeline / NDA / IP / GeM /
|
<!-- Homepage FAQ — single canonical FAQ section for /. Covers the cost /
|
||||||
stack questions buyers ask right before converting. Sits between the
|
timeline / NDA / IP / GeM / stack questions buyers ask right before
|
||||||
inline lead form and the final CTA band so late-stage objections are
|
converting, plus the process / support / team-augmentation / security
|
||||||
resolved before the visitor hits the final call-to-action. Emits its
|
questions that used to live in the now-removed "Common Questions"
|
||||||
own FAQPage JSON-LD for Google rich results. -->
|
block. Sits between the inline lead form and the final CTA band so
|
||||||
|
late-stage objections are resolved before the visitor hits the final
|
||||||
|
call-to-action. Emits the only FAQPage JSON-LD on this page. -->
|
||||||
<HomepageFaq />
|
<HomepageFaq />
|
||||||
|
|
||||||
<!-- Final CTA Section - Enhanced -->
|
<!-- Final CTA Section - Enhanced -->
|
||||||
@@ -865,18 +797,8 @@ const faqs = [
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FAQ accordion styles */
|
/* (FAQ accordion styles moved into <HomepageFaq />, which now renders the
|
||||||
.faq-answer {
|
only homepage FAQ section.) */
|
||||||
transition: max-height 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-question[aria-expanded="true"] + .faq-answer {
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.faq-question[aria-expanded="true"] .faq-icon {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -1006,26 +928,8 @@ const faqs = [
|
|||||||
startAutoplay();
|
startAutoplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FAQ Accordion
|
// (FAQ accordion script moved into <HomepageFaq />, which now renders the
|
||||||
function initFAQ() {
|
// only homepage FAQ section and ships its own inline init.)
|
||||||
const faqButtons = document.querySelectorAll('.faq-question');
|
|
||||||
|
|
||||||
faqButtons.forEach((button) => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const isExpanded = button.getAttribute('aria-expanded') === 'true';
|
|
||||||
|
|
||||||
// Close all FAQs
|
|
||||||
faqButtons.forEach((btn) => {
|
|
||||||
btn.setAttribute('aria-expanded', 'false');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle current FAQ
|
|
||||||
if (!isExpanded) {
|
|
||||||
button.setAttribute('aria-expanded', 'true');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intersection Observer for counter animation
|
// Intersection Observer for counter animation
|
||||||
function initStatsCounter() {
|
function initStatsCounter() {
|
||||||
@@ -1075,7 +979,6 @@ const faqs = [
|
|||||||
function bootHomepageScripts() {
|
function bootHomepageScripts() {
|
||||||
initStatsCounter();
|
initStatsCounter();
|
||||||
initCarousel();
|
initCarousel();
|
||||||
initFAQ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
Reference in New Issue
Block a user