Files
CompanySite/src/layouts/BaseLayout.astro
T
platform-mcpandClaude Opus 4.8 01cafba6c1
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 10m48s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 11m40s
E2E Test Suite / Form Interaction Tests (push) Failing after 12m32s
Ping Search Engines / Notify Search Engines (push) Failing after 11m26s
E2E Test Suite / Security Header Tests (push) Failing after 12m18s
E2E Test Suite / Mobile Device Tests (push) Failing after 13m10s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 14m3s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 14m55s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 22m34s
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) Failing after 14m58s
Deploy to Production / Post-Deploy Verification (push) Failing after 10m26s
Deploy to Production / Build & Verify (push) Failing after 14m49s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m58s
Deploy to Production / Deploy to Railway (push) Failing after 14m5s
Deploy to Production / Deploy to Render (push) Failing after 14m5s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 13m13s
Deploy to Production / Deploy to Fly.io (push) Failing after 13m13s
Deploy to Production / Notify on Failure (push) Failing after 13m53s
feat: apply WorkRoot logo sitewide and as favicon
- Add WorkRoot Icon as public/favicon.svg, regenerate logo.png (512x512)
  and apple-touch-icon.png (180x180) from the brand vector; add
  favicon-32x32.png raster
- Register 32x32 PNG favicon link in BaseLayout <head> alongside the
  SVG and apple-touch-icon
- Replace placeholder "W" logo boxes with the WorkRoot mark in the
  header, mobile menu, and footer (preserving "WorkRoot" text spans
  required by the dark-mode contrast tests and the home link wrapper)

JSON-LD/SEO logo (/logo.png) and PWA manifest icons now resolve to the
real brand assets. Build passes; BUG-5 dark-mode and navigation tests
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:08:31 +05:30

404 lines
16 KiB
Plaintext

---
// Base Layout for WorkRoot IT Solutions
// Features: SEO meta tags, global styles, header, footer slot, skip link
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import Analytics from '../components/Analytics.astro';
import PWAInstallPrompt from '../components/PWAInstallPrompt.astro';
import WhatsAppFab from '../components/WhatsAppFab.astro';
import MobileStickyCtaBar from '../components/MobileStickyCtaBar.astro';
import '../styles/global.css';
interface Props {
title: string;
description?: string;
canonicalUrl?: string;
ogImage?: string;
noIndex?: boolean;
}
const {
title,
description = 'WorkRoot IT Solutions LLP — Expert software development, web & mobile apps, ERP systems, and government IT solutions. Based in Maharashtra, India. Founded 2022.',
canonicalUrl = Astro.url.href,
ogImage = '/og-image.jpg',
noIndex = false,
} = Astro.props;
const siteName = 'WorkRoot IT Solutions LLP';
const fullTitle = title === 'Home' ? siteName : `${title} | ${siteName}`;
---
<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- Primary Meta Tags -->
<title>{fullTitle}</title>
<meta name="title" content={fullTitle} />
<meta name="description" content={description} />
<meta name="author" content="WorkRoot IT Solutions" />
<meta name="keywords" content="web development, mobile app development, ERP solutions, government IT systems, cloud services, IT consulting, digital transformation, custom software, enterprise solutions, Angular, React, Next.js, Astro, Spring Boot, Golang, Expo, React Native, MySQL, PostgreSQL" />
<meta name="language" content="English" />
<meta name="revisit-after" content="7 days" />
<meta name="rating" content="general" />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalUrl} />
<!-- DNS Prefetch & Preconnect for external resources -->
<link rel="dns-prefetch" href="https://images.unsplash.com" />
<link rel="preconnect" href="https://images.unsplash.com" crossorigin />
<!-- Robots -->
{noIndex ? (
<meta name="robots" content="noindex, nofollow" />
) : (
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
)}
<meta name="googlebot" content="index, follow" />
<meta name="bingbot" content="index, follow" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(ogImage, Astro.url).href} />
<meta property="og:image:secure_url" content={new URL(ogImage, Astro.url).href} />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content={fullTitle} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content={siteName} />
<meta property="og:locale" content="en_US" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@GhanwatAjay" />
<meta name="twitter:creator" content="@GhanwatAjay" />
<meta name="twitter:url" content={canonicalUrl} />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={new URL(ogImage, Astro.url).href} />
<meta name="twitter:image:alt" content={fullTitle} />
<meta name="twitter:domain" content="workroot.in" />
<meta name="twitter:label1" content="Est. reading time" />
<meta name="twitter:data1" content="3 minutes" />
<!-- Theme Color: dual media query for OS-level dark/light, overridden by JS for stored preference -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#0891b2" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0f172a" />
<!-- JSON-LD Structured Data - Organization + LocalBusiness Schema -->
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": ["Organization", "LocalBusiness"],
"name": "WorkRoot IT Solutions LLP",
"alternateName": ["WorkRoot", "WorkRoot IT Solutions"],
"url": "https://workroot.in",
"logo": {
"@type": "ImageObject",
"url": "https://workroot.in/logo.png",
"width": "512",
"height": "512"
},
"image": "https://workroot.in/og-image.jpg",
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
"foundingDate": "2022",
"founder": {
"@type": "Person",
"name": "Ajay Ghanwat"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "At Post Rajapur Shantinagar, Taluka Khatav",
"addressLocality": "Satara",
"addressRegion": "MH",
"postalCode": "415503",
"addressCountry": "IN"
},
"geo": {
"@type": "GeoCoordinates",
"latitude": "17.7733632",
"longitude": "74.3309312"
},
"hasMap": "https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5",
"telephone": "+91-9561417403",
"email": "admin@workroot.in",
"contactPoint": [{
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "customer service",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "Worldwide"
}, {
"@type": "ContactPoint",
"telephone": "+91-9561417403",
"contactType": "sales",
"email": "admin@workroot.in",
"availableLanguage": ["English", "Hindi", "Marathi"],
"areaServed": "Worldwide"
}],
"openingHoursSpecification": [
{
"@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
"opens": "10:00",
"closes": "18:00"
}
],
"priceRange": "$$",
"currenciesAccepted": "INR",
"paymentAccepted": "Cash, Credit Card, Bank Transfer",
"sameAs": [
"https://x.com/GhanwatAjay",
"https://www.linkedin.com/company/workroot",
"https://git.workroot.in/WorkRoot/",
"https://www.facebook.com/profile.php?id=61576503897897"
],
"areaServed": {
"@type": "GeoShape",
"name": "Worldwide"
},
"serviceType": ["Web Development", "Mobile App Development", "AI & Machine Learning", "Cloud Services"],
"knowsAbout": ["Web Development", "Software Engineering", "Cloud Computing", "Artificial Intelligence", "Machine Learning", "Mobile Development"],
"slogan": "Transforming Ideas Into Digital Reality",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5.0",
"reviewCount": "20",
"bestRating": "5",
"worstRating": "1"
}
})} />
<!-- JSON-LD Structured Data - WebSite Schema -->
<script type="application/ld+json" set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "WebSite",
"name": "WorkRoot IT Solutions LLP",
"url": "https://workroot.in",
"description": "Professional IT services including web development, cloud solutions, and digital transformation for businesses.",
"publisher": {
"@type": "Organization",
"name": "WorkRoot IT Solutions LLP"
},
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://workroot.in/blog?search={search_term_string}"
},
"query-input": "required name=search_term_string"
},
"inLanguage": "en-US"
})} />
<!-- Preconnect to Google Fonts (must come before font stylesheet) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Non-blocking font load: Plus Jakarta Sans (variable) + JetBrains Mono (variable) -->
<!-- Replaces the render-blocking @import that was previously in global.css -->
<link
rel="preload"
as="style"
href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
onload="this.onload=null;this.rel='stylesheet'"
/>
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" />
</noscript>
<!-- Theme init: runs inline before render to prevent FOUC -->
<!-- Also syncs theme-color meta to match stored preference (overrides OS-level media query) -->
<script is:inline>
(function() {
try {
var stored = localStorage.getItem('theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var isDark = stored === 'dark' || (!stored && prefersDark);
if (isDark) document.documentElement.classList.add('dark');
// Sync theme-color meta tags when user has an explicit stored preference
// (without stored preference the OS media query on the meta tags handles it natively)
if (stored) {
var metas = document.querySelectorAll('meta[name="theme-color"]');
var color = isDark ? '#0f172a' : '#0891b2';
metas.forEach(function(m) { m.setAttribute('content', color); });
}
} catch(e) {}
})();
</script>
<!-- Critical CSS for above-the-fold content -->
<style is:inline>
/* Critical CSS for faster FCP - system fonts render immediately */
*,*::before,*::after{box-sizing:border-box}
html{line-height:1.5;-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{margin:0;font-family:'Plus Jakarta Sans',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh;display:flex;flex-direction:column;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
img,video{max-width:100%;height:auto;display:block}
/* Prevent layout shift from lazy images */
img[loading="lazy"]{background:#e2e8f0}
/* Prevent CLS from images without explicit dimensions */
img{aspect-ratio:attr(width)/attr(height)}
/* Hero section above-the-fold critical styles */
.min-h-screen{min-height:100vh}
.flex{display:flex}
.flex-col{flex-direction:column}
.items-center{align-items:center}
/* Prevent animation flicker on initial load */
.animate-slide-up{animation-fill-mode:forwards}
/* Skip link - always accessible */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}
</style>
<!-- Analytics (GA4 or Plausible) -->
<Analytics />
<!-- Additional head content slot -->
<slot name="head" />
</head>
<body class="min-h-screen flex flex-col antialiased overflow-x-hidden">
<!-- Skip to main content link for accessibility -->
<a
href="#main-content"
class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:px-4 focus:py-2 focus:bg-primary focus:text-white focus:rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2"
>
Skip to main content
</a>
<!-- Header -->
<Header />
<!-- Main Content -->
<main id="main-content" class="flex-1" tabindex="-1">
<slot />
</main>
<!-- Footer -->
<Footer />
<!-- Spacer to ensure footer/content (incl. PricingTimelineBand, GuaranteeBand,
and any other bottom-of-page sections) clears the mobile sticky CTA bar.
Bar min-height is 64px; we use 72px here so there is always an 8px
visual breathing strip between the last band and the sticky bar.
<md only — bar is hidden at md+. -->
<div aria-hidden="true" class="md:hidden h-[calc(72px+env(safe-area-inset-bottom,0px))] w-full"></div>
<!-- Mobile sticky bottom CTA bar (visible <md) -->
<MobileStickyCtaBar />
<!-- Floating WhatsApp action (global) -->
<WhatsAppFab />
<!-- Scripts slot for page-specific scripts -->
<slot name="scripts" />
<!-- Global external link tracking -->
<script>
import { bindExternalLinkTracking } from '../utils/analytics';
document.addEventListener('DOMContentLoaded', bindExternalLinkTracking);
</script>
<!-- Scroll-reveal & micro-interactions -->
<script>
import { initAllAnimations } from '../utils/animations';
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAllAnimations);
} else {
initAllAnimations();
}
</script>
<!-- Button radial highlight (cursor tracker) -->
<script>
function initButtonHighlight() {
document.querySelectorAll<HTMLElement>('.btn-primary, .btn-primary-lg, .btn-accent').forEach((btn) => {
btn.addEventListener('mousemove', (e: MouseEvent) => {
const rect = btn.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
btn.style.setProperty('--x', `${x}%`);
btn.style.setProperty('--y', `${y}%`);
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initButtonHighlight);
} else {
initButtonHighlight();
}
</script>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.catch(() => {
// SW registration failed silently — site still works normally
});
});
}
</script>
<!-- PWA Install Prompt -->
<PWAInstallPrompt />
</body>
</html>
<style is:global>
/* Smooth scrolling for anchor links */
html {
scroll-behavior: smooth;
}
/* Focus styles are defined in global.css via Tailwind ring utilities.
Removed duplicate outline here to avoid conflicting with ring-based focus styles. */
/* Remove focus outline for mouse users */
:focus:not(:focus-visible) {
outline: none;
}
/* Selection color */
::selection {
background-color: theme('colors.primary.200');
color: theme('colors.secondary.900');
}
/* Custom scrollbar (optional - webkit only) */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: theme('colors.secondary.100');
}
::-webkit-scrollbar-thumb {
background: theme('colors.secondary.300');
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: theme('colors.secondary.400');
}
</style>