feat(contact): add global WhatsApp FAB and mobile sticky CTA bar
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) Failing after 13m5s

- WhatsAppFab: 56px brand-green floating button (wa.me/919561417403), shown
  on every page; safe-area aware; lifts above the mobile CTA bar on <md.
- MobileStickyCtaBar: 3-column sticky bottom bar (Call / WhatsApp / Quote)
  visible only below the md breakpoint, with safe-area padding, 48px+ touch
  targets, and dark-mode-aware styling. Bottom spacer keeps the footer
  clear of the bar on mobile.
- Mounted both components globally via BaseLayout.
- Added tests/sticky-cta.spec.ts covering 360px and 1280px viewports,
  link integrity, no-overflow, hidden-at-md, and FAB/bar non-overlap.
This commit is contained in:
platform-mcp
2026-06-17 11:40:23 +05:30
parent b9ab4608fd
commit 85965e1a00
4 changed files with 424 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
---
// MobileStickyCtaBar — Sticky bottom action bar for mobile/tablet (<768px)
// 3 equal columns: Call, WhatsApp, Quote
// - Fixed to bottom of viewport
// - Respects safe-area-inset-bottom on iOS
// - Hidden at md+ breakpoints (>=768px)
// - Hidden on print
// - Light + dark theme aware
---
<nav
aria-label="Quick contact actions"
data-testid="mobile-sticky-cta-bar"
class="mobile-sticky-cta md:hidden"
>
<a
href="tel:+919561417403"
aria-label="Call WorkRoot"
data-testid="mobile-cta-call"
class="mobile-sticky-cta__btn"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.86 19.86 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.86 19.86 0 0 1 2.12 4.18 2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/>
</svg>
<span class="mobile-sticky-cta__label">Call</span>
</a>
<a
href="https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project."
target="_blank"
rel="noopener"
aria-label="Chat on WhatsApp"
data-testid="mobile-cta-whatsapp"
class="mobile-sticky-cta__btn mobile-sticky-cta__btn--whatsapp"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
focusable="false"
>
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488"/>
</svg>
<span class="mobile-sticky-cta__label">WhatsApp</span>
</a>
<a
href="/contact"
aria-label="Request a quote"
data-testid="mobile-cta-quote"
class="mobile-sticky-cta__btn mobile-sticky-cta__btn--accent"
>
<svg
class="mobile-sticky-cta__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
focusable="false"
>
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
<polyline points="22,6 12,13 2,6"/>
</svg>
<span class="mobile-sticky-cta__label">Quote</span>
</a>
</nav>
<style>
.mobile-sticky-cta {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0;
background-color: #ffffff;
border-top: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 -8px 20px -8px rgba(15, 23, 42, 0.18);
padding-bottom: env(safe-area-inset-bottom, 0px);
/* Ensure no horizontal overflow on very narrow viewports */
max-width: 100vw;
overflow: hidden;
}
:global(html.dark) .mobile-sticky-cta {
background-color: #0f172a;
border-top-color: rgba(255, 255, 255, 0.08);
box-shadow: 0 -8px 20px -8px rgba(0, 0, 0, 0.55);
}
.mobile-sticky-cta__btn {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
min-height: 56px;
padding: 8px 6px;
text-decoration: none;
color: #0f172a;
font-size: 12px;
font-weight: 600;
line-height: 1.1;
background-color: transparent;
transition: background-color 150ms ease, color 150ms ease;
-webkit-tap-highlight-color: transparent;
}
:global(html.dark) .mobile-sticky-cta__btn {
color: #e2e8f0;
}
.mobile-sticky-cta__btn:active {
background-color: rgba(8, 145, 178, 0.08);
}
:global(html.dark) .mobile-sticky-cta__btn:active {
background-color: rgba(34, 211, 238, 0.12);
}
.mobile-sticky-cta__btn:focus-visible {
outline: 2px solid #0891b2;
outline-offset: -2px;
}
.mobile-sticky-cta__btn--whatsapp {
color: #128c4f;
}
:global(html.dark) .mobile-sticky-cta__btn--whatsapp {
color: #25D366;
}
.mobile-sticky-cta__btn--accent {
color: #b45309;
}
:global(html.dark) .mobile-sticky-cta__btn--accent {
color: #fbbf24;
}
.mobile-sticky-cta__icon {
width: 22px;
height: 22px;
flex-shrink: 0;
}
.mobile-sticky-cta__label {
display: block;
font-size: 11px;
letter-spacing: 0.01em;
}
/* Hide on tablets/desktops (>=768px = Tailwind md breakpoint).
Note: the `md:hidden` utility on the element loses the cascade against
this Astro-scoped class selector (`[data-astro-cid-*]` raises specificity),
so we enforce the breakpoint here too. */
@media (min-width: 768px) {
.mobile-sticky-cta {
display: none;
}
}
/* Hide on print */
@media print {
.mobile-sticky-cta {
display: none !important;
}
}
</style>
+104
View File
@@ -0,0 +1,104 @@
---
// WhatsAppFab — Global floating WhatsApp contact button
// - Fixed bottom-right, 56x56, brand green (#25D366)
// - Visible on every page across desktop and mobile
// - Mobile: lifted above the MobileStickyCtaBar via safe CSS variable offset
// - Respects iOS safe-area-inset-bottom
// - Hidden when printing
---
<a
href="https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project."
target="_blank"
rel="noopener"
aria-label="Chat on WhatsApp"
data-testid="whatsapp-fab"
class="whatsapp-fab"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="whatsapp-fab__icon"
aria-hidden="true"
focusable="false"
>
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893A11.821 11.821 0 0020.464 3.488"/>
</svg>
</a>
<style>
.whatsapp-fab {
/* Mobile: lifted above the sticky CTA bar so they don't overlap. */
--wa-fab-bottom: calc(72px + env(safe-area-inset-bottom, 0px) + 16px);
position: fixed;
right: 16px;
right: max(16px, env(safe-area-inset-right, 16px));
bottom: var(--wa-fab-bottom);
width: 56px;
height: 56px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
background-color: #25D366;
color: #ffffff;
box-shadow:
0 6px 16px rgba(37, 211, 102, 0.35),
0 2px 6px rgba(0, 0, 0, 0.18);
z-index: 60;
text-decoration: none;
transition: transform 200ms ease, box-shadow 200ms ease, background-color 200ms ease;
-webkit-tap-highlight-color: transparent;
}
.whatsapp-fab:hover {
background-color: #1ebe5c;
transform: translateY(-2px);
box-shadow:
0 10px 20px rgba(37, 211, 102, 0.45),
0 4px 8px rgba(0, 0, 0, 0.22);
}
.whatsapp-fab:focus-visible {
outline: 2px solid #ffffff;
outline-offset: 3px;
box-shadow:
0 0 0 4px rgba(37, 211, 102, 0.55),
0 6px 16px rgba(37, 211, 102, 0.4);
}
.whatsapp-fab:active {
transform: translateY(0);
}
.whatsapp-fab__icon {
width: 28px;
height: 28px;
}
/* Desktop / tablet: no sticky CTA bar present, sit at standard offset. */
@media (min-width: 768px) {
.whatsapp-fab {
--wa-fab-bottom: calc(24px + env(safe-area-inset-bottom, 0px));
}
}
/* Reduce motion */
@media (prefers-reduced-motion: reduce) {
.whatsapp-fab {
transition: none;
}
.whatsapp-fab:hover {
transform: none;
}
}
/* Hide on print */
@media print {
.whatsapp-fab {
display: none !important;
}
}
</style>